diff --git a/client.c b/client.c index 4b3c27d..2cbe418 100644 --- a/client.c +++ b/client.c @@ -34,22 +34,6 @@ bool char_to_hex_digit(char in, char *out) { return true; } -bool hex_digit_to_char(char in, char *out) { - if (in >= '0' && in <= '9') { - *out = in - '0'; - return true; - } - if (in >= 'a' && in <= 'f') { - *out = in - 'a' + 10; - return true; - } - if (in >= 'A' && in <= 'F') { - *out = in - 'A' + 10; - return true; - } - return false; -} - bool char_to_hex_digits(char in, char *hi, char *lo) { return char_to_hex_digit((in & 0xf0) >> 4, hi) && char_to_hex_digit(in & 0x0f, lo); @@ -62,106 +46,6 @@ void rdb_client_init(RdbClient *self, int connfd) { self->should_ack = true; } -#define BUFFER_LEN 8096 - -typedef struct Buffer { - char buf[BUFFER_LEN]; - size_t len; - size_t index; -} Buffer; - -static Buffer INBUF = { - .len = 0, - .index = 0, -}; - -static ssize_t read_next_char(int connfd, char *in) { - if (INBUF.index >= INBUF.len) { - ssize_t inlen = read(connfd, INBUF.buf, BUFFER_LEN); - if (inlen < 1) { - // either we got an error (-1) or the connection closed (0) - return inlen; - } - INBUF.index = 0; - INBUF.len = inlen; - } - *in = INBUF.buf[INBUF.index++]; - return 1; -} - -ssize_t rdb_client_read(RdbClient *self, char *buf, size_t len) { - // read any acknowledgements and continue - char in; - do { - ssize_t res = read_next_char(self->connfd, &in); - if (res < 1) return res; - } while (in == '+'); - - if (in == '\x03') { - // interrupt from the server - *buf = in; - return 1; - } - - if (in == '-') { - // we don't handle resending right now - return -1; - } - - // now, expect to be at the start of a packet - if (in != '$') { - fprintf(stderr, "unexpected packet start \"%c\"", in); - return -1; - } - - size_t outlen = 0; - char chk = 0; - while (1) { - ssize_t res = read_next_char(self->connfd, &in); - if (res < 1) return res; - - if (in == '#') { - // end of packet, checksum next - break; - } - if (outlen >= len) { - // ran out of room in the buffer - fprintf(stderr, "packet too big for buffer\n"); - return -1; - } - if (in == '}') { - // escape sequence - chk += in; - res = read_next_char(self->connfd, &in); - if (res < 1) return res; - chk += in; - buf[outlen++] = in ^ 0x20; - } else { - chk += in; - buf[outlen++] = in; - } - }; - - // validate the checksum - char hi, lo; - - ssize_t res = read_next_char(self->connfd, &in); - if (res < 1) return res; - if (!hex_digit_to_char(in, &hi)) return -1; - - res = read_next_char(self->connfd, &in); - if (res < 1) return res; - if (!hex_digit_to_char(in, &lo)) return -1; - - char real_chk = (hi << 4) | lo; - if (real_chk != chk) { - fprintf(stderr, "invalid checksum\n"); - return -1; - } - - return outlen; -} - void rdb_client_begin_packet(RdbClient *self) { self->len = 0; self->chk = 0; diff --git a/cmdbuf.c b/cmdbuf.c index 60104ec..fe74514 100644 --- a/cmdbuf.c +++ b/cmdbuf.c @@ -1,4 +1,5 @@ #include +#include #include bool cmd_match_str(CommandBuf *cmd, const char *str) { @@ -23,22 +24,6 @@ bool cmd_match_only_str(CommandBuf *cmd, const char *str) { return false; } -static bool parse_hex_digit(char digit, char *out) { - if (digit >= '0' && digit <= '9') { - *out = digit - '0'; - return true; - } - if (digit >= 'a' && digit <= 'f') { - *out = digit - 'a' + 10; - return true; - } - if (digit >= 'A' && digit <= 'F') { - *out = digit - 'A' + 10; - return true; - } - return false; -} - bool cmd_match_hex_number(CommandBuf *cmd, uint32_t *value) { size_t read = 0; size_t max_len = cmd->len; diff --git a/hex.c b/hex.c new file mode 100644 index 0000000..029bed0 --- /dev/null +++ b/hex.c @@ -0,0 +1,17 @@ +#include + +bool parse_hex_digit(char digit, char *out) { + if (digit >= '0' && digit <= '9') { + *out = digit - '0'; + return true; + } + if (digit >= 'a' && digit <= 'f') { + *out = digit - 'a' + 10; + return true; + } + if (digit >= 'A' && digit <= 'F') { + *out = digit - 'A' + 10; + return true; + } + return false; +} \ No newline at end of file diff --git a/include/client.h b/include/client.h index 8dba7e0..954697c 100644 --- a/include/client.h +++ b/include/client.h @@ -17,7 +17,6 @@ typedef struct RdbClient { } RdbClient; void rdb_client_init(RdbClient *self, int connfd); -ssize_t rdb_client_read(RdbClient *self, char *buf, size_t len); void rdb_client_begin_packet(RdbClient *self); bool rdb_client_write_str(RdbClient *self, const char *str); bool rdb_client_write_str_hex(RdbClient *self, const char *str); diff --git a/include/hex.h b/include/hex.h new file mode 100644 index 0000000..4d5536a --- /dev/null +++ b/include/hex.h @@ -0,0 +1,8 @@ +#ifndef V810_HEX_H_ +#define V810_HEX_H_ + +#include + +bool parse_hex_digit(char digit, char *out); + +#endif \ No newline at end of file diff --git a/include/request.h b/include/request.h new file mode 100644 index 0000000..399d58f --- /dev/null +++ b/include/request.h @@ -0,0 +1,41 @@ +#ifndef RDBSERVER_REQUEST_H +#define RDBSERVER_REQUEST_H + +#include + +#define INBUF_LEN 256 + +typedef enum rdb_read_result_t { + read_result_success, + read_result_error, + read_result_pending, + read_result_disconnected, +} rdb_read_result_t; + +typedef enum rdb_read_state_t { + read_state_header, + read_state_body, + read_state_body_escape, + read_state_checksum_1, + read_state_checksum_2, +} rdb_read_state_t; + +typedef struct RdbRequest { + int connfd; + struct Buffer { + char buf[INBUF_LEN]; + size_t len; + size_t index; + } inbuf; + rdb_read_state_t state; + char *cmd; + size_t cmdlen; + size_t outlen; + char chk; +} RdbRequest; + +void rdb_request_init(RdbRequest *req, int connfd, char *cmd, size_t cmdlen); +void rdb_request_reset(RdbRequest *req); +rdb_read_result_t rdb_request_read(RdbRequest *req, size_t *len); + +#endif \ No newline at end of file diff --git a/main.c b/main.c index 8d239d0..201c28d 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -209,24 +210,30 @@ int handle_command(RdbClient *client, CommandBuf *cmd, VB *sim) { } return rdb_client_send_packet(client); } - fprintf(stderr, "Unrecognized command."); + fprintf(stderr, "Unrecognized command.\n"); return rdb_client_send_packet(client); } int server(int connfd, VB *sim) { + RdbRequest req; RdbClient client; + char buf[BUFLEN]; + size_t len; + + rdb_request_init(&req, connfd, buf, BUFLEN); rdb_client_init(&client, connfd); - char buf[BUFLEN]; while (1) { - ssize_t len = rdb_client_read(&client, buf, BUFLEN); - if (len < 0) { - perror("could not read data"); - return -len; - } else if (len == 0) { + rdb_read_result_t result = rdb_request_read(&req, &len); + if (result == read_result_error) { + return -1; + } else if (result == read_result_disconnected) { printf("client has disconnected\n"); return 0; + } else if (result == read_result_pending) { + // TODO: should run the emulator while we wait + continue; } else { printf("received command \"%.*s\"\n", (int) len, buf); fflush(stdout); @@ -237,7 +244,7 @@ int server(int connfd, VB *sim) { if (res != 0) { return res; } - // +$QStartNoAckMode#b0 + rdb_request_reset(&req); } } diff --git a/makefile b/makefile index fb7cbe2..1aa5e15 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ build: @mkdir -p build - @gcc main.c client.c cmdbuf.c ../vbtest/vb.c -I include -I ../vbtest \ + @gcc main.c client.c cmdbuf.c hex.c request.c ../vbtest/vb.c -I include -I ../vbtest \ -Werror -Wall -Wextra -Wpedantic \ -Wno-unused-parameter -Wno-unused-function \ -o ./build/rdb diff --git a/request.c b/request.c new file mode 100644 index 0000000..ca66cf8 --- /dev/null +++ b/request.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +void rdb_request_init(RdbRequest *req, int connfd, char *cmd, size_t cmdlen) { + req->connfd = connfd; + req->cmd = cmd; + req->cmdlen = cmdlen; + rdb_request_reset(req); +} + +void rdb_request_reset(RdbRequest *req) { + req->state = read_state_header; + req->inbuf.len = 0; + req->inbuf.index = 0; + req->outlen = 0; + req->chk = 0; +} + +static rdb_read_result_t read_char(RdbRequest *req, char *in) { + if (req->inbuf.index >= req->inbuf.len) { + ssize_t inlen = read(req->connfd, req->inbuf.buf, INBUF_LEN); + if (inlen == 0) { + return read_result_disconnected; + } + if (inlen < 0) { + if (errno == EAGAIN) { + return read_result_pending; + } else { + perror("could not read incoming packet"); + return read_result_error; + } + } + req->inbuf.index = 0; + req->inbuf.len = inlen; + } + *in = req->inbuf.buf[req->inbuf.index++]; + return read_result_success; +} + +rdb_read_result_t rdb_request_read(RdbRequest *req, size_t *len) { + rdb_read_result_t res; + char in; + + switch (req->state) { + case read_state_header: + // read any acknowledgements and continue + do { + res = read_char(req, &in); + if (res != read_result_success) return res; + } while (in == '+'); + + if (in == '-') { + fprintf(stderr, "negative ack not supported\n"); + return read_result_error; + } + + if (in == '\x03') { + // interrupt from the server + req->cmd[0] = in; + req->outlen = 1; + return read_result_success; + } + + // now, we should be at the start of a packet + if (in != '$') { + fprintf(stderr, "unexpected packet start \"%c\"\n", in); + return read_result_error; + } + + req->state = read_state_body; + __attribute__ ((fallthrough)); + case read_state_body: + case read_state_body_escape: + while (1) { + res = read_char(req, &in); + if (res != read_result_success) return res; + + if (req->state == read_state_body && in == '#') { + // end of packet body + break; + } + + req->chk += in; + + if (req->state == read_state_body && in == '}') { + // escape sequence + req->state = read_state_body_escape; + continue; + } + + if (req->outlen >= req->cmdlen) { + // ran out of room in the buffer + fprintf(stderr, "packet too big for buffer\n"); + return read_result_error; + } + + if (req->state == read_state_body_escape) { + req->cmd[req->outlen++] = in ^ 0x20; + req->state = read_state_body; + } else { + req->cmd[req->outlen++] = in; + } + } + req->state = read_state_checksum_1; + __attribute__ ((fallthrough)); + case read_state_checksum_1: + res = read_char(req, &in); + if (res != read_result_success) return res; + + // check the high digit of the checksum + char hi; + if (!parse_hex_digit(in, &hi)) { + fprintf(stderr, "invalid checksum1\n"); + return read_result_error; + } + if (((req->chk >> 4) & 0x000f) != hi) { + fprintf(stderr, "invalid checksum2\n"); + return read_result_error; + } + req->state = read_state_checksum_2; + __attribute__ ((fallthrough)); + case read_state_checksum_2: + res = read_char(req, &in); + if (res != read_result_success) return res; + + // check the high digit of the checksum + char lo; + if (!parse_hex_digit(in, &lo)) { + fprintf(stderr, "invalid checksum3 %c\n", in); + return read_result_error; + } + if ((req->chk & 0x000f) != lo) { + fprintf(stderr, "invalid checksum4\n"); + return read_result_error; + } + + *len = req->outlen; + return read_result_success; + default: + fprintf(stderr, "invalid state\n"); + return read_result_error; + } +} \ No newline at end of file