From 385b47211ea8674f97f014537d694bb2efbd6ab9 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Thu, 13 Oct 2022 15:59:14 +0200 Subject: WIP protocol implementation --- shared/bin.h | 12 ++++++++ shared/main | Bin 0 -> 21456 bytes shared/makefile | 24 +++++++++++++++ shared/protocol.c | 72 +++++++++++++++++++++++++++++++++++++++++++++ shared/protocol.h | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++ shared/protocol.md | 54 ++++++++++++++++++++++++++++++++++ shared/test.c | 36 +++++++++++++++++++++++ shared/testcmd | 1 + 8 files changed, 284 insertions(+) create mode 100644 shared/bin.h create mode 100755 shared/main create mode 100644 shared/makefile create mode 100644 shared/protocol.c create mode 100644 shared/protocol.h create mode 100644 shared/protocol.md create mode 100644 shared/test.c create mode 100644 shared/testcmd diff --git a/shared/bin.h b/shared/bin.h new file mode 100644 index 0000000..c7405be --- /dev/null +++ b/shared/bin.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +typedef struct { + uint16_t bytes; + uint8_t data[]; +} ws_s_bin; + +/** allocate new ws_s_bin struct and fill with `*data` for `bytes` bytes */ +ws_s_bin *ws_bin_s_alloc(uint16_t bytes, uint8_t *data); + diff --git a/shared/main b/shared/main new file mode 100755 index 0000000..7d7b7fe Binary files /dev/null and b/shared/main differ diff --git a/shared/makefile b/shared/makefile new file mode 100644 index 0000000..4d538da --- /dev/null +++ b/shared/makefile @@ -0,0 +1,24 @@ +CC = gcc +LD = gcc +RM = rm -f +CFLAGS = +LFLAGS = +TARGET = main + +SRCS := $(wildcard *.c) +OBJS := $(patsubst %.c,%.o, $(SRCS)) + +all: main + +%.o: %.c + $(CC) -c $(CFLAGS) $< -o $@ + +$(TARGET): $(OBJS) + $(LD) $^ $(LFLAGS) -o $@ + +clean: + $(RM) $(TARGET) $(OBJS) + +compile_commands: clean + compiledb make + diff --git a/shared/protocol.c b/shared/protocol.c new file mode 100644 index 0000000..85b320f --- /dev/null +++ b/shared/protocol.c @@ -0,0 +1,72 @@ +#include +#include + +#include "protocol.h" + +void ws_protocol_parse_byte(ws_s_protocol_parser_state* state, char input) { + switch(input) { + case WS_PROTOCOL_C_NEWLINE: { + if (!state->valid) return; + break; + } + + case WS_PROTOCOL_C_SPACE: { + if (!state->valid) return; + printf("argument delimiter\n"); + return; + } + + case WS_PROTOCOL_C_NULL: { + state->valid = false; + return; + } + + default: { + if (!state->valid) return; + printf("recv byte 0x%02x, (\"%c\")\n", input, input); + state->cmd[state->cmd_len++] = input; + state->args_len[state->arg_len] += 1; + if (state->cmd_len == WS_PROTOCOL_CMD_BUFFER_LEN) state->valid = false; + return; + } + } + + printf("command done!\n"); +} + +void ws_protocol_parse_bytes(ws_s_protocol_parser_state* state, char* input, unsigned int length) { + for (unsigned int i = 0; i < length; i++) ws_protocol_parse_byte(state, input[i]); +} + +ws_s_protocol_parser_state* ws_protocol_parser_alloc() { + ws_s_protocol_parser_state* parser_state = malloc(sizeof(ws_s_protocol_parser_state) + sizeof(uint16_t) * WS_PROTOCOL_CMD_MAX_ARGUMENTS); + parser_state->cmd = malloc(sizeof(char) * WS_PROTOCOL_CMD_BUFFER_LEN); + parser_state->valid = true; + parser_state->cmd_len = 0; + parser_state->arg_len = 0; + parser_state->target = NULL; + return parser_state; +} + +void ws_protocol_cmd_init(ws_s_protocol_parser_state* state) { + state->target = malloc(sizeof(ws_s_protocol_parsed_cmd) + sizeof(char*) * state->arg_len); + for (unsigned int i = 0; i < state->arg_len; i++) + state->target->argv[i] = malloc(sizeof(char) * state->args_len[i]); + state->target->argc = state->arg_len; +} + +void ws_protocol_parser_free(ws_s_protocol_parser_state* state) { + if (state == NULL) return; + if (state->target != NULL) ws_protocol_cmd_free(state->target); + free(state->cmd); + free(state); + state = NULL; + return; +} +void ws_protocol_cmd_free(ws_s_protocol_parsed_cmd* cmd) { + for (unsigned int i = 0; i < cmd->argc; i++) + free(cmd->argv[i]); + free(cmd); + cmd = NULL; + return; +} diff --git a/shared/protocol.h b/shared/protocol.h new file mode 100644 index 0000000..5cf7ac6 --- /dev/null +++ b/shared/protocol.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +#include "bin.h" + +#define WS_PROTOCOL_CMD_MAX_ARGUMENTS (1) +#define WS_PROTOCOL_CMD_BUFFER_LEN (40) + +#define WS_PROTOCOL_CMD_AMOUNT (1) + +#define WS_PROTOCOL_C_NEWLINE (0x0a) +#define WS_PROTOCOL_C_SPACE (0x20) +#define WS_PROTOCOL_C_NULL (0x00) + +typedef struct { + int argc; + char* argv[]; +} ws_s_protocol_parsed_cmd; + +typedef struct { + ws_s_protocol_parsed_cmd* target; + bool valid; + char* cmd; + uint16_t cmd_len; + uint16_t arg_len; + uint16_t args_len[]; +} ws_s_protocol_parser_state; + +//TODO: document +ws_s_protocol_parser_state* ws_protocol_parser_alloc(); +void ws_protocol_parser_free(ws_s_protocol_parser_state* state); +void ws_protocol_cmd_init(ws_s_protocol_parser_state* state); +void ws_protocol_cmd_free(ws_s_protocol_parsed_cmd* cmd); + +/** + * @brief parse incoming data byte by byte until a finished command is detected + * + * @remark [server] + * + * @param state parser state object, each incoming request should have it's own parser state + * @param input input byte + */ +void ws_protocol_parse_byte(ws_s_protocol_parser_state* state, char input); +/** + * @brief parse incoming data chunk + * + * @remark [server] + * + * @param state parser state object, each incoming request should have it's own parser state + * @param input input byte array + * @param length input byte array length + */ +void ws_protocol_parse_bytes(ws_s_protocol_parser_state* state, char* input, unsigned int length); + +/** + * @brief create a `last-records` request command + * @remark [client] + * @return ws_s_bin containing the command string + */ +ws_s_bin* ws_protocol_req_last_records(unsigned int record_amount); + +/** + * @brief `last-records` response handler + * + * @remark [server] + * + * gets fired when the weather station receives a complete `last-records` + * command, and returns the response string + * + * @param parsed_cmd complete parsed command from ws_protocol_parse_* + * + * @return ws_s_bin containing response string + */ +ws_s_bin* ws_protocol_res_last_records(ws_s_protocol_parsed_cmd* parsed_cmd); + +typedef enum { + WS_PROTOCOL_CMD_LAST_RECORDS = 0, +} ws_e_protocol_cmd; + +static ws_s_bin* (*g_ws_protocol_res_handlers[WS_PROTOCOL_CMD_AMOUNT])(ws_s_protocol_parsed_cmd* parsed_cmd) = { + [WS_PROTOCOL_CMD_LAST_RECORDS] = &ws_protocol_res_last_records +}; + diff --git a/shared/protocol.md b/shared/protocol.md new file mode 100644 index 0000000..bafec4d --- /dev/null +++ b/shared/protocol.md @@ -0,0 +1,54 @@ +# Protocol spec + +This is a brief overview of the protocol specifications that the weather +station uses to send and receive data between the weather station and qt +client. This protocol is text-based, and used over a TCP connection. This +document will only go into detail about the data sent over this connection, not +requirements about the connection itself. + +The protocol is only used in a request-response fashion, so all commands are +assumed to be sent by the qt client, and responded to by the weather station. + +Functions for generating commands and parsing incoming data are provided by the +protocol.c and protocol.h files. See [code +implementation](#code-implementation) section for more details about naming +conventions. + +- LF for newline instead of CRLF +- Commands are single-line +- Spaces used for separating command arguments +- Commands with malformed data are discarded and return error +- Response consist of `ok` or `error`, a comma, and the byte length of the + remaining response (if any) +- Numbers are sent as hexadecimal + +## Commands + +### `last-records ` + +Returns the last `n` records in csv format. The first line has the csv table +header, with the fields `id`, `temperature`, `humidity`, and +`atmospheric_pressure`. The rest of the response consists of 1 record per line. +When `n` is 0, or no records exist yet, the csv header is still returned, but +without any records. + +## Example transaction + +In the following example, newlines are indicated by `<0a>`, request by lines +starting with `<`, and response by lines starting with `>`. + +``` +< last-records 5<0a> +> ok,115<0a> +> id,temperature,humidity,atmospheric_pressure<0a> +> 10dc,2f,c5,7f<0a> +> 10dd,30,c6,7f<0a> +> 10de,31,c7,7f<0a> +> 10df,35,ca,7e<0a> +> 10e0,34,c9,7e<0a> +``` + +## Code implementation + + + diff --git a/shared/test.c b/shared/test.c new file mode 100644 index 0000000..788dc94 --- /dev/null +++ b/shared/test.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +#include "protocol.h" + +ws_s_bin* ws_protocol_res_last_records(ws_s_protocol_parsed_cmd* parsed_cmd) { + return NULL; +} + +int main() { + // disable echo and enable raw mode + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + struct termios term; + tcgetattr(STDIN_FILENO, &term); + term.c_lflag &= ~(ECHO | ICANON); + term.c_cc[VTIME] = 0; + term.c_cc[VMIN] = 1; + tcsetattr(STDIN_FILENO, 0, &term); + + ws_s_protocol_parser_state* parser1 = ws_protocol_parser_alloc(); + + for(;;) { + fflush(stdout); + + char byte; + while(read(STDIN_FILENO, &byte, 1) > 0) + ws_protocol_parse_byte(parser1, byte); + } + + ws_protocol_parser_free(parser1); + + return 0; +} diff --git a/shared/testcmd b/shared/testcmd new file mode 100644 index 0000000..17f8842 --- /dev/null +++ b/shared/testcmd @@ -0,0 +1 @@ +last-records 5 -- cgit v1.2.3