Refactor the network input parser to make it easier to extend for new formats.

This commit is contained in:
Oliver Jowett 2016-12-29 17:54:53 +00:00
parent 0526388bdc
commit 34aeb29347
2 changed files with 85 additions and 62 deletions

138
net_io.c
View file

@ -88,7 +88,7 @@ static void writeFATSVPositionUpdate(float lat, float lon, float alt);
// Init a service with the given read/write characteristics, return the new service.
// Doesn't arrange for the service to listen or connect
struct net_service *serviceInit(const char *descr, struct net_writer *writer, heartbeat_fn hb, const char *sep, read_fn handler)
struct net_service *serviceInit(const char *descr, struct net_writer *writer, heartbeat_fn hb, read_mode_t mode, const char *sep, read_fn handler)
{
struct net_service *service;
@ -105,6 +105,7 @@ struct net_service *serviceInit(const char *descr, struct net_writer *writer, he
service->connections = 0;
service->writer = writer;
service->read_sep = sep;
service->read_mode = mode;
service->read_handler = handler;
if (service->writer) {
@ -232,12 +233,12 @@ void serviceListen(struct net_service *service, char *bind_addr, char *bind_port
struct net_service *makeBeastInputService(void)
{
return serviceInit("Beast TCP input", NULL, NULL, NULL, decodeBinMessage);
return serviceInit("Beast TCP input", NULL, NULL, READ_MODE_BEAST, NULL, decodeBinMessage);
}
struct net_service *makeFatsvOutputService(void)
{
return serviceInit("FATSV TCP output", &Modes.fatsv_out, NULL, NULL, NULL);
return serviceInit("FATSV TCP output", &Modes.fatsv_out, NULL, READ_MODE_IGNORE, NULL, NULL);
}
void modesInitNet(void) {
@ -248,23 +249,23 @@ void modesInitNet(void) {
Modes.services = NULL;
// set up listeners
s = serviceInit("Raw TCP output", &Modes.raw_out, send_raw_heartbeat, NULL, NULL);
s = serviceInit("Raw TCP output", &Modes.raw_out, send_raw_heartbeat, READ_MODE_IGNORE, NULL, NULL);
serviceListen(s, Modes.net_bind_address, Modes.net_output_raw_ports);
s = serviceInit("Beast TCP output", &Modes.beast_out, send_beast_heartbeat, NULL, NULL);
s = serviceInit("Beast TCP output", &Modes.beast_out, send_beast_heartbeat, READ_MODE_IGNORE, NULL, NULL);
serviceListen(s, Modes.net_bind_address, Modes.net_output_beast_ports);
s = serviceInit("Basestation TCP output", &Modes.sbs_out, send_sbs_heartbeat, NULL, NULL);
s = serviceInit("Basestation TCP output", &Modes.sbs_out, send_sbs_heartbeat, READ_MODE_IGNORE, NULL, NULL);
serviceListen(s, Modes.net_bind_address, Modes.net_output_sbs_ports);
s = serviceInit("Raw TCP input", NULL, NULL, "\n", decodeHexMessage);
s = serviceInit("Raw TCP input", NULL, NULL, READ_MODE_ASCII, "\n", decodeHexMessage);
serviceListen(s, Modes.net_bind_address, Modes.net_input_raw_ports);
s = makeBeastInputService();
serviceListen(s, Modes.net_bind_address, Modes.net_input_beast_ports);
#ifdef ENABLE_WEBSERVER
s = serviceInit("HTTP server", NULL, NULL, "\r\n\r\n", handleHTTPRequest);
s = serviceInit("HTTP server", NULL, NULL, READ_MODE_ASCII, "\r\n\r\n", handleHTTPRequest);
serviceListen(s, Modes.net_bind_address, Modes.net_http_ports);
#endif
}
@ -1643,14 +1644,11 @@ static int handleHTTPRequest(struct client *c, char *p) {
static void modesReadFromClient(struct client *c) {
int left;
int nread;
int fullmsg;
int bContinue = 1;
char *s, *e, *p;
while(bContinue) {
while (bContinue) {
left = MODES_CLIENT_BUF_SIZE - c->buflen - 1; // leave 1 extra byte for NUL termination in the ASCII case
fullmsg = 0;
left = MODES_CLIENT_BUF_SIZE - c->buflen;
// If our buffer is full discard it, this is some badly formatted shit
if (left <= 0) {
c->buflen = 0;
@ -1690,77 +1688,95 @@ static void modesReadFromClient(struct client *c) {
c->buflen += nread;
// Always null-term so we are free to use strstr() (it won't affect binary case)
c->buf[c->buflen] = '\0';
char *som = c->buf; // first byte of next message
char *eod = som + c->buflen; // one byte past end of data
char *p;
e = s = c->buf; // Start with the start of buffer, first message
switch (c->service->read_mode) {
case READ_MODE_IGNORE:
// drop the bytes on the floor
som = eod;
break;
if (c->service->read_sep == NULL) {
case READ_MODE_BEAST:
// This is the Beast Binary scanning case.
// If there is a complete message still in the buffer, there must be the separator 'sep'
// in the buffer, note that we full-scan the buffer at every read for simplicity.
left = c->buflen; // Length of valid search for memchr()
while (left > 1 && ((s = memchr(e, (char) 0x1a, left)) != NULL)) { // The first byte of buffer 'should' be 0x1a
s++; // skip the 0x1a
if (*s == '1') {
e = s + MODEAC_MSG_BYTES + 8; // point past remainder of message
} else if (*s == '2') {
e = s + MODES_SHORT_MSG_BYTES + 8;
} else if (*s == '3') {
e = s + MODES_LONG_MSG_BYTES + 8;
} else if (*s == '4') {
e = s + MODES_LONG_MSG_BYTES + 8;
} else if (*s == '5') {
e = s + MODES_LONG_MSG_BYTES + 8;
} else {
e = s; // Not a valid beast message, skip
left = &(c->buf[c->buflen]) - e;
continue;
}
// we need to be careful of double escape characters in the message body
for (p = s; p < e; p++) {
if (0x1A == *p) {
p++; e++;
if (e > &(c->buf[c->buflen])) {
break;
}
}
}
left = &(c->buf[c->buflen]) - e;
if (left < 0) { // Incomplete message in buffer
e = s - 1; // point back at last found 0x1a.
while (som < eod && ((p = memchr(som, (char) 0x1a, eod - som)) != NULL)) { // The first byte of buffer 'should' be 0x1a
som = p; // consume garbage up to the 0x1a
++p; // skip 0x1a
if (p >= eod) {
// Incomplete message in buffer, retry later
break;
}
char *eom; // one byte past end of message
if (*p == '1') {
eom = p + MODEAC_MSG_BYTES + 8; // point past remainder of message
} else if (*p == '2') {
eom = p + MODES_SHORT_MSG_BYTES + 8;
} else if (*p == '3') {
eom = p + MODES_LONG_MSG_BYTES + 8;
} else if (*p == '4') {
eom = p + MODES_LONG_MSG_BYTES + 8;
} else if (*p == '5') {
eom = p + MODES_LONG_MSG_BYTES + 8;
} else {
// Not a valid beast message, skip 0x1a and try again
++som;
continue;
}
// we need to be careful of double escape characters in the message body
for (p = som + 1; p < eod && p < eom; p++) {
if (0x1A == *p) {
p++;
eom++;
}
}
if (eom > eod) { // Incomplete message in buffer, retry later
break;
}
// Have a 0x1a followed by 1/2/3/4/5 - pass message to handler.
if (c->service->read_handler(c, s)) {
if (c->service->read_handler(c, som + 1)) {
modesCloseClient(c);
return;
}
fullmsg = 1;
}
s = e; // For the buffer remainder below
} else {
// advance to next message
som = eom;
}
break;
case READ_MODE_ASCII:
//
// This is the ASCII scanning case, AVR RAW or HTTP at present
// If there is a complete message still in the buffer, there must be the separator 'sep'
// in the buffer, note that we full-scan the buffer at every read for simplicity.
//
while ((e = strstr(s, c->service->read_sep)) != NULL) { // end of first message if found
*e = '\0'; // The handler expects null terminated strings
if (c->service->read_handler(c, s)) { // Pass message to handler.
// Always NUL-terminate so we are free to use strstr()
// nb: we never fill the last byte of the buffer with read data (see above) so this is safe
*eod = '\0';
while (som < eod && (p = strstr(som, c->service->read_sep)) != NULL) { // end of first message if found
*p = '\0'; // The handler expects null terminated strings
if (c->service->read_handler(c, som)) { // Pass message to handler.
modesCloseClient(c); // Handler returns 1 on error to signal we .
return; // should close the client connection
}
s = e + strlen(c->service->read_sep); // Move to start of next message
fullmsg = 1;
som = p + strlen(c->service->read_sep); // Move to start of next message
}
break;
}
if (fullmsg) { // We processed something - so
c->buflen = &(c->buf[c->buflen]) - s; // Update the unprocessed buffer length
memmove(c->buf, s, c->buflen); // Move what's remaining to the start of the buffer
if (som > c->buf) { // We processed something - so
c->buflen = eod - som; // Update the unprocessed buffer length
memmove(c->buf, som, c->buflen); // Move what's remaining to the start of the buffer
} else { // If no message was decoded process the next client
return;
}

View file

@ -29,6 +29,12 @@ struct net_service;
typedef int (*read_fn)(struct client *, char *);
typedef void (*heartbeat_fn)(struct net_service *);
typedef enum {
READ_MODE_IGNORE,
READ_MODE_BEAST,
READ_MODE_ASCII
} read_mode_t;
// Describes one network service (a group of clients with common behaviour)
struct net_service {
struct net_service* next;
@ -41,6 +47,7 @@ struct net_service {
struct net_writer *writer; // shared writer state
const char *read_sep; // hander details for input data
read_mode_t read_mode;
read_fn read_handler;
};
@ -62,7 +69,7 @@ struct net_writer {
heartbeat_fn send_heartbeat; // function that queues a heartbeat if needed
};
struct net_service *serviceInit(const char *descr, struct net_writer *writer, heartbeat_fn hb_handler, const char *sep, read_fn read_handler);
struct net_service *serviceInit(const char *descr, struct net_writer *writer, heartbeat_fn hb_handler, read_mode_t mode, const char *sep, read_fn read_handler);
struct client *serviceConnect(struct net_service *service, char *addr, int port);
void serviceListen(struct net_service *service, char *bind_addr, char *bind_ports);
struct client *createSocketClient(struct net_service *service, int fd);