Running a server on a different port, plus using TLS


#1

First off, this is my first microcontroller project, so apologies if I’m misunderstanding anything or if this should go in a platform-specific section.

I’m hoping to code a simple Gemini protocol server to run on my Cardputer through uLisp. I’m trying to figure out how viable the project is, and it seems like it’s manageable, but I’m running into a couple of issues that I’m trying to understand. I think they’re fixable problems, but they appear to need some changes to the .ino file used to flash, and since I’m not used to working with the constraints of microcontrollers, I figured I should do some research and, if that doesn’t work, ask some people who know better than me.

By default, (wifi-server) starts listening on port 80, but Gemini specifies port 1965. Having tracked this back through the Arduino libraries WiFi.h, WiFiServer.h, and a couple others, I can’t see any real reason this is fixed. It just opens a generic TCP port, nothing specific to the port it’s running on. I also can’t see anything in the ESP32-S3FN8 datasheet that suggests there’s any sort of limit on ports. Is there any reason I shouldn’t just change the default port to whatever I need?

I also need to use TLS because the Gemini protocol specification requires it, and this chip does have support for it built-in. I’ve managed to find the library for it, so if I managed to figure out how to access it through the Arduino IDE so I can #include it, how reasonable would it be to include? Compilation claims the sketch uses 36% of program storage space, plus 73% of dynamic memory for global variables, so space constraints might be relevant, but it seems feasible so far. I could maybe try to do something in uLisp to add the functionality, but it seems much easier to use the preexisting library if I can tap into it.

Thanks for any advice. Even if you don’t know for sure, if you know any resources that might help me figure it out, they would be appreciated.


#2

By default, (wifi-server) starts listening on port 80, but Gemini specifies port 1965.

There’s a line in the uLisp source that specifies the port for wifi-server:

WiFiServer server(80);

I think it should work if you change this to 1965 before uploading uLisp.

I also need to use TLS because the Gemini protocol specification requires it

I don’t have any experience of using uLisp with TLS. I’m pretty sure you would have to make changes to the Wi-Fi initialisation in uLisp. Perhaps someone else has had experience of doing this?


#3

So that isn’t hard coded due to a restriction or something? I was planning to tweak that, but I didn’t know if that was set because of some limitation, although there’s nothing I can think to expect.

Yeah, once I figure out how to get the ESP32 TLS library accessible in the Arduino IDE, I’m planning to use it to tweak functionality. I’m not too worried about figuring out how to do that. My main concern is seeing the memory usage and the maximums listed in the IDE after I compile the normal version. It’s reporting pretty high usage of some portions of available space, so I was afraid that introducing the extra functionality might be an unreasonable burden when I still need to implement a simple server with what’s left. However, I think I might be misunderstanding the various types of memory available in a microcontroller like this because neither the max of 3,342,336 bytes in program storage space nor the max of 327,680 bytes align with anything I’m seeing on the data sheet. Even combined, that’s less than half the flash memory, and I thought the part about dynamic memory might be talking about the RAM, but the Cardputer should have 512 KB of RAM, so just over 320 KB doesn’t line up with that, either. I suspect I need to do more research on the different types of storage space in microcontrollers and how tools like the Arduino IDE evaluate and use that space.


#4

I forgot to answer this in your original question:

Compilation claims the sketch uses 36% of program storage space, plus 73% of dynamic memory for global variables, so space constraints might be relevant

The uLisp source allocates as much of the dynamic memory as possible to the Lisp workspace before the compiler gives a low memory warning message. You can simply increase the amount of dynamic memory available, if necessary, by reducing the WORKSPACE figure in the line:

#define WORKSPACESIZE 22250          /* Cells (8*bytes) */

#6

Here’s an extension for SSL, it’s in the early stages, with signifcant error checking disabled, but functions for me. This use SSLClient, and just gets things working client-side, you’ll have to add a little for server-side TLS, and it uses /significant/ amounts of memory. Uncomment “#extensions” and “#streamextensions” in your ulisp file, install the SSLClient library in your arduino IDE, and put the below in a file next to your ulisp file to get things going.

#include <SSLClient.h>

// generate this for the site you want to connect to below:
// https://openslab-osu.github.io/bearssl-certificate-utility/
// Example follows:

/* This file is auto-generated by the pycert_bearssl tool.  Do not change it manually.
 * Certificates are BearSSL br_x509_trust_anchor format.  Included certs:
 *
 * Index:    0
 * Label:    Starfield Services Root Certificate Authority - G2
 * Subject:  CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\, Inc.,L=Scottsdale,ST=Arizona,C=US
 * Domain(s): www.nhc.noaa.gov
 */

#define TAs_NUM 1

static const unsigned char TA_DN0[] = {
    0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
    0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04,
    0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13,
    0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f,
    0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, 0x06,
    0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69,
    0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f,
    0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x3b,
    0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, 0x61,
    0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69,
    0x63, 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72,
    0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74,
    0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32,
};

static const unsigned char TA_RSA_N0[] = {
    0xd5, 0x0c, 0x3a, 0xc4, 0x2a, 0xf9, 0x4e, 0xe2, 0xf5, 0xbe, 0x19, 0x97,
    0x5f, 0x8e, 0x88, 0x53, 0xb1, 0x1f, 0x3f, 0xcb, 0xcf, 0x9f, 0x20, 0x13,
    0x6d, 0x29, 0x3a, 0xc8, 0x0f, 0x7d, 0x3c, 0xf7, 0x6b, 0x76, 0x38, 0x63,
    0xd9, 0x36, 0x60, 0xa8, 0x9b, 0x5e, 0x5c, 0x00, 0x80, 0xb2, 0x2f, 0x59,
    0x7f, 0xf6, 0x87, 0xf9, 0x25, 0x43, 0x86, 0xe7, 0x69, 0x1b, 0x52, 0x9a,
    0x90, 0xe1, 0x71, 0xe3, 0xd8, 0x2d, 0x0d, 0x4e, 0x6f, 0xf6, 0xc8, 0x49,
    0xd9, 0xb6, 0xf3, 0x1a, 0x56, 0xae, 0x2b, 0xb6, 0x74, 0x14, 0xeb, 0xcf,
    0xfb, 0x26, 0xe3, 0x1a, 0xba, 0x1d, 0x96, 0x2e, 0x6a, 0x3b, 0x58, 0x94,
    0x89, 0x47, 0x56, 0xff, 0x25, 0xa0, 0x93, 0x70, 0x53, 0x83, 0xda, 0x84,
    0x74, 0x14, 0xc3, 0x67, 0x9e, 0x04, 0x68, 0x3a, 0xdf, 0x8e, 0x40, 0x5a,
    0x1d, 0x4a, 0x4e, 0xcf, 0x43, 0x91, 0x3b, 0xe7, 0x56, 0xd6, 0x00, 0x70,
    0xcb, 0x52, 0xee, 0x7b, 0x7d, 0xae, 0x3a, 0xe7, 0xbc, 0x31, 0xf9, 0x45,
    0xf6, 0xc2, 0x60, 0xcf, 0x13, 0x59, 0x02, 0x2b, 0x80, 0xcc, 0x34, 0x47,
    0xdf, 0xb9, 0xde, 0x90, 0x65, 0x6d, 0x02, 0xcf, 0x2c, 0x91, 0xa6, 0xa6,
    0xe7, 0xde, 0x85, 0x18, 0x49, 0x7c, 0x66, 0x4e, 0xa3, 0x3a, 0x6d, 0xa9,
    0xb5, 0xee, 0x34, 0x2e, 0xba, 0x0d, 0x03, 0xb8, 0x33, 0xdf, 0x47, 0xeb,
    0xb1, 0x6b, 0x8d, 0x25, 0xd9, 0x9b, 0xce, 0x81, 0xd1, 0x45, 0x46, 0x32,
    0x96, 0x70, 0x87, 0xde, 0x02, 0x0e, 0x49, 0x43, 0x85, 0xb6, 0x6c, 0x73,
    0xbb, 0x64, 0xea, 0x61, 0x41, 0xac, 0xc9, 0xd4, 0x54, 0xdf, 0x87, 0x2f,
    0xc7, 0x22, 0xb2, 0x26, 0xcc, 0x9f, 0x59, 0x54, 0x68, 0x9f, 0xfc, 0xbe,
    0x2a, 0x2f, 0xc4, 0x55, 0x1c, 0x75, 0x40, 0x60, 0x17, 0x85, 0x02, 0x55,
    0x39, 0x8b, 0x7f, 0x05,
};

static const unsigned char TA_RSA_E0[] = {
    0x01, 0x00, 0x01,
};

static const br_x509_trust_anchor TAs[] = {
    {
        { (unsigned char *)TA_DN0, sizeof TA_DN0 },
        BR_X509_TA_CA,
        {
            BR_KEYTYPE_RSA,
            { .rsa = {
                (unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
                (unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
            } }
        }
    },
};

// End Cert insertion

#define NOISE_PIN 26

SSLClient sclient(client, TAs, TAs_NUM, NOISE_PIN, 1, SSLClient::SSL_INFO);

// Stream
enum stream2 { SSLSTREAM = USERSTREAMS };

void SSLwrite (char c) { sclient.write(c); }

pfun_t pfun_SSLstream (uint8_t address) {
  (void) address;
  return (pfun_t)SSLwrite;
}

int SSLread () { while (!sclient.available()) testescape(); return sclient.read(); }

gfun_t gfun_SSLstream (uint8_t address) {
  (void) address;
  return (gfun_t)SSLread;
}

// Stream names used by printobject
const char SSLstreamname[] = "SSLstream";

// Stream lookup table - needs to be in same order as enum stream2
const stream_entry_t stream_table2[] = {
 { SSLstreamname, pfun_SSLstream, gfun_SSLstream },
};

// Stream table cross-reference functions - do not edit below this line

stream_entry_t *streamtables[] = {stream_table, stream_table2};

const stream_entry_t *streamtable (int n) {
 return streamtables[n];
}

object *sp_withsclient (object *args, object *env) {
  object *params = checkarguments(args, 1, 3);
  object *var = first(params);
  char buffer[BUFFERSIZE];
  params = cdr(params);
  int n;
  if (params == NULL) {
    error2("not supported");
    return nil;
    // client = server.available(); //fix later
    // if (!client) return nil;
    // n = 2;
  } else {
    object *address = eval(first(params), env);
    object *port = eval(second(params), env);
    int success;
    if (stringp(address)) success = sclient.connect(cstring(address, buffer, BUFFERSIZE), checkinteger(port));
    else if (integerp(address)) success = sclient.connect(address->integer, checkinteger(port));
    else error2("invalid address");
    if (!success) return nil;
    n = 1;
  }
  object *pair = cons(var, stream(SSLSTREAM, n));
  push(pair,env);
  object *forms = cdr(args);
  object *result = eval(tf_progn(forms,env), env);
  sclient.stop();
  return result;
}

object *fn_savailable (object *args, object *env) {
  #if defined (ULISP_WIFI)
  (void) env;
  // if (isstream(first(args))>>8 != WIFISTREAM) error2("invalid stream");
  return number(sclient.available());
  #else
  (void) args, (void) env;
  error2("not supported");
  return nil;
  #endif
}

// Symbol names
const char stringwithsclient[] PROGMEM = "with-sclient";
const char stringsavailable[] PROGMEM = "savailable";

// Documentation strings
const char docwithsclient[] PROGMEM = "TBD";
const char docsavailable[] PROGMEM = "TBD";

// Symbol lookup table
const tbl_entry_t lookup_table2[] PROGMEM = {
  { stringwithsclient, sp_withsclient, 0317, docwithsclient },
  { stringsavailable, fn_savailable, 0211, docsavailable },
};

// Table cross-reference functions

tbl_entry_t *tables[] = {lookup_table, lookup_table2};
const int tablesizes[] = { arraysize(lookup_table), arraysize(lookup_table2) };

const tbl_entry_t *table (int n) {
  return tables[n];
}

int tablesize (int n) {
  return tablesizes[n];
}