diff options
Diffstat (limited to 'shared')
-rw-r--r-- | shared/bin.c | 101 | ||||
-rw-r--r-- | shared/bin.h | 81 | ||||
-rw-r--r-- | shared/consts.h | 10 | ||||
-rw-r--r-- | shared/pclient.c | 164 | ||||
-rw-r--r-- | shared/pclient.h | 91 | ||||
-rw-r--r-- | shared/protocol-tests/.gitignore | 1 | ||||
-rw-r--r-- | shared/protocol-tests/get-node-response.src | 81 | ||||
-rw-r--r-- | shared/protocol-tests/makefile | 15 | ||||
-rw-r--r-- | shared/protocol-tests/ping-response.src | 10 | ||||
-rw-r--r-- | shared/protocol-tests/ping.src | 4 | ||||
-rw-r--r-- | shared/protocol.c | 58 | ||||
-rw-r--r-- | shared/protocol.h | 183 | ||||
-rw-r--r-- | shared/protocol.md | 15 | ||||
-rw-r--r-- | shared/serial_parse.c | 61 | ||||
-rw-r--r-- | shared/serial_parse.h | 26 |
15 files changed, 901 insertions, 0 deletions
diff --git a/shared/bin.c b/shared/bin.c new file mode 100644 index 0000000..875d013 --- /dev/null +++ b/shared/bin.c @@ -0,0 +1,101 @@ +#include <stdlib.h> +#include <string.h> + +#include "bin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CD_ENDIAN_LITTLE (1) +#define CD_ENDIAN_BIG (0) + +#define _SHIFT_0B (8 * 0) +#define _SHIFT_1B (8 * 1) +#define _SHIFT_2B (8 * 2) +#define _SHIFT_3B (8 * 3) +#define _BYTE_0 ((uint32_t)0xff << (_SHIFT_0B)) +#define _BYTE_1 ((uint32_t)0xff << (_SHIFT_1B)) +#define _BYTE_2 ((uint32_t)0xff << (_SHIFT_2B)) +#define _BYTE_3 ((uint32_t)0xff << (_SHIFT_3B)) + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +cd_s_bin *cd_bin_from_uint8_t(uint8_t data) { + size_t size = 1; + cd_s_bin *ret = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * size); + ret->bytes = size; + ret->data[0] = data; + return ret; +} + +cd_s_bin *cd_bin_from_uint16_t(uint16_t data) { + size_t size = 2; + cd_s_bin *ret = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * size); + data = cd_bin_hton16(data); + ret->bytes = size; + ret->data[0] = (data & _BYTE_1) >> _SHIFT_1B; + ret->data[1] = (data & _BYTE_0) >> _SHIFT_0B; + return ret; +} + +cd_s_bin *cd_bin_from_uint32_t(uint32_t data) { + size_t size = 4; + cd_s_bin *ret = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * size); + data = cd_bin_hton32(data); + ret->bytes = size; + ret->data[0] = (data & _BYTE_3) >> _SHIFT_3B; + ret->data[1] = (data & _BYTE_2) >> _SHIFT_2B; + ret->data[2] = (data & _BYTE_1) >> _SHIFT_1B; + ret->data[3] = (data & _BYTE_0) >> _SHIFT_0B; + return ret; +} + +uint32_t cd_bin_hton32(uint32_t h32) { + if (g_cd_endianness == CD_ENDIAN_BIG) return h32; + return ((h32 & _BYTE_0) << _SHIFT_3B) | ((h32 & _BYTE_1) << _SHIFT_1B) | + ((h32 & _BYTE_2) >> _SHIFT_1B) | ((h32 & _BYTE_3) >> _SHIFT_3B); +} +#pragma GCC diagnostic pop + +uint16_t cd_bin_hton16(uint16_t h16) { + if (g_cd_endianness == CD_ENDIAN_BIG) return h16; + return ((h16 & _BYTE_0) << _SHIFT_1B) | ((h16 & _BYTE_1) >> _SHIFT_1B); +} + +uint32_t cd_bin_ntoh32(uint32_t n32) { return cd_bin_hton32(n32); } +uint16_t cd_bin_ntoh16(uint16_t n16) { return cd_bin_hton16(n16); } +uint32_t cd_bin_ntohd(uint8_t* n, size_t s) { return cd_bin_htond(n, s); } + +uint32_t cd_bin_htond(uint8_t* h, size_t s) { + if (s == sizeof(uint8_t)) return *h; + else if (s == sizeof(uint16_t)) return cd_bin_hton16(*(uint16_t*) h); + else if (s == sizeof(uint32_t)) return cd_bin_hton32(*(uint32_t*) h); + else return 0; +} + +cd_s_bin *cd_bin_s_alloc(uint16_t bytes, uint8_t *data) { + cd_s_bin *temp = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * bytes); + temp->bytes = bytes; + memcpy(&temp->data, data, bytes); + return temp; +} + +cd_s_bin *cd_bin_s_cat(cd_s_bin *a, cd_s_bin *b) { + uint8_t data[a->bytes + b->bytes]; + memcpy(data, a->data, a->bytes); + memcpy(data + a->bytes, b->data, b->bytes); + cd_s_bin *c = cd_bin_s_alloc(a->bytes + b->bytes, data); + free(a); + free(b); + return c; +} + +void cd_bin_repl_hton32(uint32_t *h32) { *h32 = cd_bin_hton32(*h32); } +void cd_bin_repl_hton16(uint16_t *h16) { *h16 = cd_bin_hton16(*h16); } +void cd_bin_repl_ntoh32(uint32_t *h32) { *h32 = cd_bin_ntoh32(*h32); } +void cd_bin_repl_ntoh16(uint16_t *h16) { *h16 = cd_bin_ntoh16(*h16); } + +#ifdef __cplusplus +} +#endif diff --git a/shared/bin.h b/shared/bin.h new file mode 100644 index 0000000..7506655 --- /dev/null +++ b/shared/bin.h @@ -0,0 +1,81 @@ +#pragma once + +/** @file bin.h */ + +/** + * helper file for binary data + * + * - fix endianness with functions inspired by UNIX arpa/inet.h + * - convert uint16_t and uint32_t to cd_s_bin + */ + +#include <stdint.h> +#include <malloc.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint8_t g_cd_endianness; + +/** @brief cast `in.data` to `type out` */ +#define CD_CAST_BIN(type, in, out) type *out = (type *)&in->data; +#define CD_CREATE_MSG_BIN(type, normal, bin) CD_CREATE_MSG_SIZE_BIN(type, sizeof(type), normal, bin) +/** @brief */ +#define CD_CREATE_MSG_SIZE_BIN(type, size, normal, bin) \ + cd_s_bin *bin = malloc(sizeof(cd_s_bin) + size); \ + bin->bytes = size; \ + type *normal = (type *)&bin->data; + +/** @brief hold binary data with fixed size */ +typedef struct { + uint16_t bytes; /** @brief data size */ + uint8_t data[]; /** @brief data */ +} cd_s_bin; + +/** @brief allocate new cd_s_bin struct and fill with `*data` for `bytes` bytes */ +cd_s_bin *cd_bin_s_alloc(uint16_t bytes, uint8_t *data); +/** @brief concatenate 2 cd_s_bin structs, deallocates `a` and `b` */ +cd_s_bin *cd_bin_s_cat(cd_s_bin *a, cd_s_bin *b); + +cd_s_bin *cd_bin_from_uint8_t(uint8_t data); +cd_s_bin *cd_bin_from_uint16_t(uint16_t data); +cd_s_bin *cd_bin_from_uint32_t(uint32_t data); + +/** @brief convert 32-bit value from host endian to network (big-endian) */ +uint32_t cd_bin_hton32(uint32_t h32); +/** @brief convert 16-bit value from host endian to network (big-endian) */ +uint16_t cd_bin_hton16(uint16_t h16); +/** @brief convert 32-bit value from network (big-endian) to host endian */ +uint32_t cd_bin_ntoh32(uint32_t n32); +/** @brief convert 16-bit value from network (big-endian) to host endian */ +uint16_t cd_bin_ntoh16(uint16_t n16); + +/** + * @brief convert (8*s)-bit value from network (big-endian) to host endian + * (dynamic size) + * + * @param n pointer to number + * @param s size of number in bytes + * + * @return 32-bit integer regardless of `s` + * + * this function is exclusively used by the CD_DYN_MEMBER_SIZEOF macro in + * shared/protocol.c + */ +uint32_t cd_bin_ntohd(uint8_t* n, size_t s); +uint32_t cd_bin_htond(uint8_t* h, size_t s); + +/** @brief replace 32-bit value from host endian to network (big-endian) */ +void cd_bin_repl_hton32(uint32_t *h32); +/** @brief replace 16-bit value from host endian to network (big-endian) */ +void cd_bin_repl_hton16(uint16_t *h16); +/** @brief replace 32-bit value from network (big-endian) to host endian */ +void cd_bin_repl_ntoh32(uint32_t *n32); +/** @brief replace 16-bit value from network (big-endian) to host endian */ +void cd_bin_repl_ntoh16(uint16_t *n16); + +#ifdef __cplusplus +} +#endif + diff --git a/shared/consts.h b/shared/consts.h index 6f70f09..57426bf 100644 --- a/shared/consts.h +++ b/shared/consts.h @@ -1 +1,11 @@ #pragma once + +/** @file consts.h */ + +/** @brief size of input (receive) buffer (in bytes) */ +#define CD_SERIAL_READ_BUFFER_SIZE 255 +/** @brief size of the error handling buffer (in errors, not bytes) */ +#define CD_ERROR_BUFFER_SIZE 16 +/** @brief size of the serial communication buffer (in messages, not bytes) */ +#define CD_SERCOMM_BUFFER_SIZE 16 + diff --git a/shared/pclient.c b/shared/pclient.c new file mode 100644 index 0000000..1b8e4e5 --- /dev/null +++ b/shared/pclient.c @@ -0,0 +1,164 @@ +#include <memory.h> + +#include "protocol.h" +#include "pclient.h" +#include "bin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +cd_s_bin* cd_cmd_gen_ping() { + CD_CREATE_MSG_BIN(cd_s_cmd_ping, msg, bin); + + msg->opcode = CD_CMD_PING; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + + return bin; +} + +cd_s_bin* cd_cmd_gen_get_node(bool all, cd_uuid_t uuid) { + CD_CREATE_MSG_BIN(cd_s_cmd_get_node, msg, bin); + + msg->opcode = CD_CMD_GET_NODE; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->all = all; + if (uuid != NULL) memcpy(msg->uuid, uuid, sizeof(cd_uuid_t)); + else memset(msg->uuid, 0, sizeof(cd_uuid_t)); + + return bin; +} + +cd_s_bin* cd_cmd_gen_post_led(bool on, cd_uuid_t uuid) { + CD_CREATE_MSG_BIN(cd_s_cmd_post_led, msg, bin); + + msg->opcode = CD_CMD_POST_LED; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->on = on; + memcpy(msg->uuid, uuid, sizeof(cd_uuid_t)); + + return bin; +} + +cd_s_bin* cd_cmd_gen_post_link_add(cd_uuid_t button, cd_uuid_t light, cd_e_cmd_link_type type) { + CD_CREATE_MSG_BIN(cd_s_cmd_post_link, msg, bin); + + msg->opcode = CD_CMD_POST_LINK; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->add = true; + memcpy(msg->button, button, sizeof(cd_uuid_t)); + memcpy(msg->led, light, sizeof(cd_uuid_t)); + msg->type = type; + + return bin; +} + +cd_s_bin* cd_cmd_gen_post_link_rm(cd_uuid_t button, cd_uuid_t light) { + CD_CREATE_MSG_BIN(cd_s_cmd_post_link, msg, bin); + + msg->opcode = CD_CMD_POST_LINK; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->add = false; + memcpy(msg->button, button, sizeof(cd_uuid_t)); + memcpy(msg->led, light, sizeof(cd_uuid_t)); + msg->type = 0; + + return bin; +} + +cd_s_bin* cd_cmd_gen_post_net_add(cd_uuid_t uuid) { + CD_CREATE_MSG_BIN(cd_s_cmd_post_net, msg, bin); + + msg->opcode = CD_CMD_POST_NET; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->join = true; + memcpy(msg->uuid, uuid, sizeof(cd_uuid_t)); + + return bin; +} + +cd_s_bin* cd_cmd_gen_post_net_rm(cd_uuid_t uuid) { + CD_CREATE_MSG_BIN(cd_s_cmd_post_net, msg, bin); + + msg->opcode = CD_CMD_POST_NET; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->join = false; + memcpy(msg->uuid, uuid, sizeof(cd_uuid_t)); + + return bin; +} + +cd_s_bin* cd_cmd_res_status(cd_e_scmds cmd, cd_cmd_id_t id, bool error) { + CD_CREATE_MSG_BIN(cd_s_cmd_response, msg, bin); + + msg->opcode = CD_CMD_RESPONSE; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->response_type = cmd; + msg->response_id = id; + msg->error = error; + msg->response_size = 0; + + return bin; +} + +cd_s_bin* cd_cmd_res(cd_e_scmds cmd, cd_cmd_id_t id, uint16_t len, uint8_t* data) { + CD_CREATE_MSG_SIZE_BIN(cd_s_cmd_response, sizeof(cd_s_cmd_response) + len, msg, bin); + + msg->opcode = CD_CMD_RESPONSE; + msg->id = cd_bin_hton16(cd_protocol_fresh_message_id()); + msg->response_type = cmd; + msg->response_id = id; + msg->error = false; + msg->response_size = cd_bin_hton16(len); + memcpy(msg->response_info, data, len); + + return bin; +} + +cd_s_cmd_node* cd_cmd_node_alloc(const char* name, cd_s_cmd_node base, uint16_t link_count, cd_uuid_t* links) { + size_t name_len = strlen(name); + size_t links_size = sizeof(cd_uuid_t) * link_count; + size_t remaining_size = sizeof(char) * name_len + links_size; + cd_s_cmd_node* node = malloc(sizeof(cd_s_cmd_node) + remaining_size); + + memcpy(node->uuid, base.uuid, sizeof(cd_uuid_t)); + memcpy(node->address, base.address, sizeof(cd_mac_addr_t)); + node->name_len = name_len; + node->light_on = base.light_on; + node->provisioned = base.provisioned; + node->button_pub = cd_bin_hton32(base.button_pub); + node->link_count = cd_bin_hton16(link_count); + node->remaining_size = cd_bin_hton16(remaining_size); + void* cursor = (void*) &node->remaining_data[0]; + memcpy(cursor, name, name_len); // copy name + cursor += name_len; + memcpy(cursor, links, links_size); // copy links + + return node; +} + +cd_s_cmd_response_get_node* cd_cmd_get_node_res_from_node_arr(uint16_t size, cd_s_cmd_node* arr[]) { + size_t remaining_size = 0; + + for (unsigned int i = 0; i < size; i++) { + remaining_size += sizeof(cd_s_cmd_node) + cd_bin_ntoh16(arr[i]->remaining_size); + } + + cd_s_cmd_response_get_node* response = malloc(sizeof(cd_s_cmd_response_get_node) + remaining_size); + response->node_count = cd_bin_hton16(size); + response->remaining_size = cd_bin_hton16(remaining_size); + + void* cursor = response->nodes; + for (unsigned int i = 0; i < size; i++) { + size_t size = sizeof(cd_s_cmd_node) + cd_bin_ntoh16(arr[i]->remaining_size); + memcpy(cursor, arr[i], size); + cursor += size; + } + + return response; +} + +#ifdef __cplusplus +} +#endif + diff --git a/shared/pclient.h b/shared/pclient.h new file mode 100644 index 0000000..03a8a25 --- /dev/null +++ b/shared/pclient.h @@ -0,0 +1,91 @@ +#pragma once + +/** @file pclient.h */ + +#include <stdbool.h> + +#include "protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief send data over platform standard serial out (doesn't free `data`) */ +void cd_pclient_send(cd_s_bin* data); + +/** @brief generate PING command */ +cd_s_bin* cd_cmd_gen_ping(); +/** + * @brief generate GET_NODE command + * @param all get all nodes + * @param uuid get specific node by uuid + */ +cd_s_bin* cd_cmd_gen_get_node(bool all, cd_uuid_t uuid); +/** + * @brief generate POST_LED command + * @param on light status + * @param uuid node to set light of + */ +cd_s_bin* cd_cmd_gen_post_led(bool on, cd_uuid_t uuid); +/** + * @brief generate POST_LINK command to add or update link + * @param button button node uuid + * @param light light node uuid + * @param type type of link to set + */ +cd_s_bin* cd_cmd_gen_post_link_add(cd_uuid_t button, cd_uuid_t light, cd_e_cmd_link_type type); +/** + * @brief generate POST_LINK command to remove link + * @param button button node uuid + * @param light light node uuid + */ +cd_s_bin* cd_cmd_gen_post_link_rm(cd_uuid_t button, cd_uuid_t light); +/** + * @brief generate POST_NET command to provision node into network + * @param uuid node uuid + */ +cd_s_bin* cd_cmd_gen_post_net_add(cd_uuid_t uuid); +/** + * @brief generate POST_NET command to provision node out of network + * @param uuid node uuid + */ +cd_s_bin* cd_cmd_gen_post_net_rm(cd_uuid_t uuid); + +/** + * @brief generate generic RESPONSE command with error field and no response_info + * @param cmd original command opcode + * @param id original command id + * @param error `true` if some error occurred + */ +cd_s_bin* cd_cmd_res_status(cd_e_scmds cmd, cd_cmd_id_t id, bool error); +/** + * @brief generate RESPONSE command with response_info + * @param cmd original command opcode + * @param id original command id + * @param len size of `data` in bytes + * @param data pointer to data + */ +cd_s_bin* cd_cmd_res(cd_e_scmds cmd, cd_cmd_id_t id, uint16_t len, uint8_t* data); +/** + * @brief generate cd_s_cmd_response_get_node struct from array of cd_s_cmd_node pointers + * @param size length of array + * @param arr array of pointer to cd_s_cmd_node + */ +cd_s_cmd_response_get_node* cd_cmd_get_node_res_from_node_arr(uint16_t size, cd_s_cmd_node* arr[]); +/** + * @brief allocate and fill cd_s_cmd_node struct + * + * @param base base struct with values that can be initialized using an initialization list + * @param name node name (length is calculated at runtime using strlen()) + * @param link_count amount of lights this node controls + * @param links array of light node uuids + */ +cd_s_cmd_node* cd_cmd_node_alloc(const char* name, cd_s_cmd_node base, uint16_t link_count, cd_uuid_t* links); + +#define cd_remaining_sizeof(type, input_struct) ((sizeof(type) + cd_bin_ntoh16(input_struct->remaining_size)) /* NOLINT */) +#define cd_cmd_node_sizeof(node) (cd_remaining_sizeof(cd_s_cmd_node, node) /* NOLINT */) +#define cd_cmd_response_get_node_sizeof(res) (cd_remaining_sizeof(cd_s_cmd_response_get_node, res) /* NOLINT */) + +#ifdef __cplusplus +} +#endif diff --git a/shared/protocol-tests/.gitignore b/shared/protocol-tests/.gitignore new file mode 100644 index 0000000..a8a0dce --- /dev/null +++ b/shared/protocol-tests/.gitignore @@ -0,0 +1 @@ +*.bin diff --git a/shared/protocol-tests/get-node-response.src b/shared/protocol-tests/get-node-response.src new file mode 100644 index 0000000..31bf1ff --- /dev/null +++ b/shared/protocol-tests/get-node-response.src @@ -0,0 +1,81 @@ +00: ff ; start byte +01: 05 ; opcode (0x05 = response) +02: 00 ; message id (0x0000) +03: 00 ; ^ +04: 00 ; error byte (0x00 = false) +05: 01 ; response type (0x01 = get node) +06: 8f ; original message id (0x8ff8) +07: f8 ; ^ +08: 00 ; remaining response size (0x39 = 57 bytes) +09: 39 ; ^ +-------; |- remaining response (cd_s_cmd_response_get_node) +0a: 00 ; | node count (0x0001 = 1) +0b: 01 ; | ^ +0c: 00 ; | remaining response size (0x35 = 53 bytes) +0d: 35 ; | ^ +-------; | |- remaining response (cd_s_cmd_node[1]) +0e: ff ; | | [esc] +0f: ff ; | | uuid (ffffffff-0000-0000-dead-beef00000000) +10: ff ; | | [esc] +11: ff ; | | ^ +12: ff ; | | [esc] +13: ff ; | | ^ +14: ff ; | | [esc] +15: ff ; | | ^ +16: 00 ; | | ^ +17: 00 ; | | ^ +18: 00 ; | | ^ +19: 00 ; | | ^ +1a: de ; | | ^ +1b: ad ; | | ^ +1c: be ; | | ^ +1d: ef ; | | ^ +1e: 00 ; | | ^ +1f: 00 ; | | ^ +20: 00 ; | | ^ +21: 00 ; | | ^ +22: ff ; | | [esc] +23: ff ; | | mac address (ff:00:ff:00:ff:00) +24: 00 ; | | ^ +25: ff ; | | [esc] +26: ff ; | | ^ +27: 00 ; | | ^ +28: ff ; | | [esc] +29: ff ; | | ^ +2a: 00 ; | | ^ +2b: 04 ; | | name length (0x04 = 4) +2c: 00 ; | | light on (0x00 = false) +2d: 00 ; | | provisioned (0x00 = false) +2e: de ; | | button publish address (0xdeadbeef) +2f: ad ; | | ^ +30: be ; | | ^ +31: ef ; | | ^ +32: 00 ; | | link count (0x0001 = 1) +33: 01 ; | | ^ +34: 00 ; | | remaining size (0x14 = 20 bytes) +35: 14 ; | | ^ +-------; | | |- remaining response (char[4], cd_uuid_t[1]) +36: 67 ; | | | node name ("gert") +37: 65 ; | | | ^ +38: 72 ; | | | ^ +39: 74 ; | | | ^ +3a: ff ; | | | [esc] +3b: ff ; | | | link[0] uuid (ffffffff-0000-0000-dead-beef00000000) +3c: ff ; | | | [esc] +3d: ff ; | | | ^ +3e: ff ; | | | [esc] +3f: ff ; | | | ^ +40: ff ; | | | [esc] +41: ff ; | | | ^ +42: 00 ; | | | ^ +43: 00 ; | | | ^ +44: 00 ; | | | ^ +45: 00 ; | | | ^ +46: de ; | | | ^ +47: ad ; | | | ^ +48: be ; | | | ^ +49: ef ; | | | ^ +4a: 00 ; | | | ^ +4b: 00 ; | | | ^ +4c: 00 ; | | | ^ +4d: 00 ; | | | ^ diff --git a/shared/protocol-tests/makefile b/shared/protocol-tests/makefile new file mode 100644 index 0000000..f8ac490 --- /dev/null +++ b/shared/protocol-tests/makefile @@ -0,0 +1,15 @@ +XXD := xxd +RM := rm -f + +SRCS := $(wildcard *.src) +TARGET := $(SRCS:.src=.bin) + +.PHONY: all clean + +all: $(TARGET) + +%.bin: %.src + $(XXD) -r -c1 $< $@ + +clean: + $(RM) $(TARGET) diff --git a/shared/protocol-tests/ping-response.src b/shared/protocol-tests/ping-response.src new file mode 100644 index 0000000..57418d5 --- /dev/null +++ b/shared/protocol-tests/ping-response.src @@ -0,0 +1,10 @@ +00: ff ; start byte +01: 05 ; response opcode +02: 00 ; message id (0x0001) +03: 01 ; ^ +04: 01 ; error (true) +05: 00 ; response type (0x00 = ping) +06: 00 ; original message id (0x0000) +07: 00 ; ^ +08: 00 ; remainder size (0x0000) +09: 00 ; ^ diff --git a/shared/protocol-tests/ping.src b/shared/protocol-tests/ping.src new file mode 100644 index 0000000..beec10a --- /dev/null +++ b/shared/protocol-tests/ping.src @@ -0,0 +1,4 @@ +00: ff ; start byte +01: 00 ; ping opcode +02: f8 ; message id (used as ping identifier) +03: 8f ; ^ diff --git a/shared/protocol.c b/shared/protocol.c new file mode 100644 index 0000000..fcc0f41 --- /dev/null +++ b/shared/protocol.c @@ -0,0 +1,58 @@ +#include <stddef.h> + +#include "protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +cd_cmd_id_t g_cd_protocol_fresh_message_id = 0; + +size_t cd_cmd_sizeof(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t data_length) { + cd_cmd_opcode_t opcode = data[0]; + if (CD_CMD_HANDLERS_SIZE[opcode] > 0) return CD_CMD_HANDLERS_SIZE[opcode]; + + cd_s_bin *copy = cd_bin_s_alloc(data_length, data); + size_t length = (*CD_CMD_HANDLERS_SIZEOF[opcode])(copy); + + free(copy); + + return length; +} + +/** + * @brief macro to calculate size of message based on struct with member to + * indicate length of dynamic (last) field + * + * @param data cd_s_bin pointer to currently received data + * @param struct_t message struct + * @param length_field struct field with dynamic length + * + * @return size_t with calculated size + * + * equivalent c code: + * + * size_t size = sizeof(struct_t); + * size_t dyn_member_offset = offsetof(struct_t, length_field); + * size_t dyn_member_size = sizeof(((struct_t*)0)->length_field); + * if (data->bytes >= (dyn_member_offset + dyn_member_size)) + * size += cd_bin_ntohd(&data->data[dyn_member_offset], dyn_member_size); + * return size; + */ +#define CD_DYN_MEMBER_SIZEOF(data, struct_t, length_field) \ + sizeof(struct_t) + ( \ + (data->bytes >= (offsetof(struct_t, length_field) + sizeof(((struct_t*)0)->length_field))) ? \ + (cd_bin_ntohd(&data->data[offsetof(struct_t, length_field)], sizeof(((struct_t*)0)->length_field))) :\ + 0); + +size_t cd_cmd_response_sizeof(cd_s_bin* data) { + return CD_DYN_MEMBER_SIZEOF(data, cd_s_cmd_response, response_size); +} + +cd_cmd_id_t cd_protocol_fresh_message_id() { + return g_cd_protocol_fresh_message_id++; +} + +#ifdef __cplusplus +} +#endif diff --git a/shared/protocol.h b/shared/protocol.h new file mode 100644 index 0000000..b9a2c93 --- /dev/null +++ b/shared/protocol.h @@ -0,0 +1,183 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file protocol.h */ + +#define CD_SERIAL_START_BYTE 0xff + +#include <stdint.h> +#include <stdlib.h> + +#include "bin.h" +#include "consts.h" + +/** @brief mac address (00:11:22:33:44:55) */ +typedef uint8_t cd_mac_addr_t[6]; +/** @brief uuid (ffeeddcc-bbaa-9988-7766-554433221100) */ +typedef uint8_t cd_uuid_t[16]; + +/** @brief pub/sub address type */ +typedef uint32_t cd_mesh_psub_addr; + +/** @brief command opcode (identifies message type) */ +typedef uint8_t cd_cmd_opcode_t; +/** @brief command id (identifies messages uniquely) */ +typedef uint16_t cd_cmd_id_t; +/** @brief smallest boolean type */ +typedef uint8_t cd_cmd_bool_t; + +/** @brief cmd handler function signature */ +typedef void (cd_cmd_handler_t)(cd_s_bin *data); + +/** @brief used for numbering messages */ +extern cd_cmd_id_t g_cd_protocol_fresh_message_id; + +/** @brief get new message id */ +cd_cmd_id_t cd_protocol_fresh_message_id(); + +#pragma pack(push, 1) + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ +} cd_s_cmd_ping; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_cmd_bool_t all; /** @brief get all known nodes */ + cd_uuid_t uuid; /** @brief node uuid to get details from, ignored if `all` = `true` */ +} cd_s_cmd_get_node; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_cmd_bool_t on; /** @brief new led status */ + cd_uuid_t uuid; /** @brief node uuid to set */ +} cd_s_cmd_post_led; + +typedef enum { + CD_CMD_LINK_TYPE_TOGGLE = 0x00, /** @brief button toggles light */ + CD_CMD_LINK_TYPE_TURN_ON = 0x01, /** @brief button always turns on light (regardless of previous state) */ + CD_CMD_LINK_TYPE_TURN_OFF = 0x02, /** @brief button always turns off light (regardless of previous state) */ +} cd_e_cmd_link_type; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_uuid_t button; /** @brief uuid of button node */ + cd_uuid_t led; /** @brief uuid of led node */ + cd_cmd_bool_t add; /** @brief `true` to create/overwrite link, `false` to remove link */ + uint8_t type; /** @brief link type cd_e_cmd_link_type */ +} cd_s_cmd_post_link; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_uuid_t uuid; /** @brief node uuid */ + cd_cmd_bool_t join; /** @brief `true` to join network, `false` to leave network */ +} cd_s_cmd_post_net; + +/** @brief protocol node */ +typedef struct { + cd_uuid_t uuid; /** @brief node network uuid */ + cd_mac_addr_t address; /** @brief node bluetooth mac address */ + uint8_t name_len; /** @brief name length in bytes */ + cd_cmd_bool_t light_on; /** @brief state of light on node */ + cd_cmd_bool_t provisioned; /** @brief whether the node is provisioned into the network */ + cd_mesh_psub_addr button_pub; /** @brief button publish address */ + uint16_t link_count; /** @brief amount of addresses to publish button press to */ + uint16_t remaining_size; /** @brief calculated size of remaining_data for convenience */ + const uint8_t remaining_data[]; /** + * @brief remaining data (name and link array) + * + * this data is stored adjacently in memory + * and is cast when reading/writing this + * struct + * + * 1. char[] name + * 2. cd_uuid_t[] light_publish_addresses; + */ +} cd_s_cmd_node; + +typedef struct { + uint16_t node_count; /** amount of nodes in nodes[] */ + uint16_t remaining_size; /** remaining size (for convenience) */ + cd_s_cmd_node nodes[]; /** nodes adjacent in memory (should be accessed using pointer arithmetic) */ +} cd_s_cmd_response_get_node; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief response message id */ + cd_cmd_bool_t error; /** @brief `true` if some error occurred */ + cd_cmd_opcode_t response_type; /** @brief response type, used to cast type of `response_info` */ + cd_cmd_id_t response_id; /** @brief original message id */ + uint16_t response_size; /** @brief size of remaining response */ + uint8_t response_info[]; /** @brief (CAST) remaining response struct, not read if `response_size`=`0` */ +} cd_s_cmd_response; + +#pragma pack(pop) + +/** @brief global handler for complete messages */ +void cd_cmd_handle(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t length); +/** @brief calculate message length for any message */ +size_t cd_cmd_sizeof(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t length); + +/** @brief calculate dynamic size of response message */ +size_t cd_cmd_response_sizeof(cd_s_bin* data); + +// down here is the garbage manual duplicate code section for constants + +#define CD_CMD_COUNT 6 +typedef enum { + CD_CMD_PING = 0x00, /** @brief send ping */ + CD_CMD_GET_NODE = 0x01, /** @brief get node(s) */ + CD_CMD_POST_LED = 0x02, /** @brief set led */ + CD_CMD_POST_LINK = 0x03, /** @brief set/remove link */ + CD_CMD_POST_NET = 0x04, /** @brief (un)register node with network */ + CD_CMD_RESPONSE = 0x05, /** @brief response message */ +} cd_e_scmds; + +cd_cmd_handler_t cd_cmd_ping, + cd_cmd_get_node, + cd_cmd_post_led, + cd_cmd_post_link, + cd_cmd_post_net, + cd_cmd_response; + +/** @brief constant message sizes, 0 for dynamic size */ +static const size_t CD_CMD_HANDLERS_SIZE[CD_CMD_COUNT] = { + [CD_CMD_PING] = sizeof(cd_s_cmd_ping), + [CD_CMD_GET_NODE] = sizeof(cd_s_cmd_get_node), + [CD_CMD_POST_LED] = sizeof(cd_s_cmd_post_led), + [CD_CMD_POST_LINK] = sizeof(cd_s_cmd_post_link), + [CD_CMD_POST_NET] = sizeof(cd_s_cmd_post_net), + [CD_CMD_RESPONSE] = 0, +}; + +/** @brief constant message sizes, 0 for dynamic size */ +static size_t (* const CD_CMD_HANDLERS_SIZEOF[CD_CMD_COUNT])(cd_s_bin*) = { + [CD_CMD_PING] = NULL, + [CD_CMD_GET_NODE] = NULL, + [CD_CMD_POST_LED] = NULL, + [CD_CMD_POST_LINK] = NULL, + [CD_CMD_POST_NET] = NULL, + [CD_CMD_RESPONSE] = &cd_cmd_response_sizeof, +}; + +/** @brief stores message handlers in array with opcode as index */ +static cd_cmd_handler_t* const CD_CMD_HANDLERS[CD_CMD_COUNT] = { + [CD_CMD_PING] = &cd_cmd_ping, + [CD_CMD_GET_NODE] = &cd_cmd_get_node, + [CD_CMD_POST_LED] = &cd_cmd_post_led, + [CD_CMD_POST_LINK] = &cd_cmd_post_link, + [CD_CMD_POST_NET] = &cd_cmd_post_net, + [CD_CMD_RESPONSE] = &cd_cmd_response, +}; + +#ifdef __cplusplus +} +#endif diff --git a/shared/protocol.md b/shared/protocol.md new file mode 100644 index 0000000..c3c229f --- /dev/null +++ b/shared/protocol.md @@ -0,0 +1,15 @@ +# protocol specs + +## commands + +each command consists of a start byte, opcode, and a payload. each opcode +defines logic to handle payload length, so certain commands might expect a +fixed-length payload, a variable-length payload, or none at all. the start byte +is `0xff`, and because most data sent is in binary format, if the data contains +an `0xff` byte, it will be escaped by replacing it with two `0xff` bytes. this +is converted to a single `0xff` on the receiving end, so these duplicated bytes +and the starting byte don't count towards message length. + +opcodes are picked sequentially, and are stored as enum constants inside +shared/protocol.h for code readability. + diff --git a/shared/serial_parse.c b/shared/serial_parse.c new file mode 100644 index 0000000..bfc374a --- /dev/null +++ b/shared/serial_parse.c @@ -0,0 +1,61 @@ +#include <string.h> + +#include "consts.h" +#include "serial_parse.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool cd_serial_parse(uint8_t byte) { + static uint8_t current_message[CD_SERIAL_READ_BUFFER_SIZE] = {0}; + static uint8_t current_message_index = 0; + static uint8_t complete_message_length = 2; + + static bool attentive = false; + static bool listening = false; + + if (byte == CD_SERIAL_START_BYTE) { + attentive = !attentive; + if (attentive && listening) return CD_SERIAL_READ_SUCCESS; + } else if (attentive) { + attentive = false; + listening = !listening; + if (!listening) return CD_SERIAL_READ_FAILURE; + } + + if (!listening) return CD_SERIAL_READ_SUCCESS; + current_message[current_message_index++] = byte; + + complete_message_length = cd_cmd_sizeof(current_message, current_message_index); + + if (current_message_index == complete_message_length) { + cd_cmd_handle(current_message, current_message_index); + + memset(¤t_message, 0, CD_SERIAL_READ_BUFFER_SIZE); + current_message_index = 0; + complete_message_length = 1; + attentive = false; + listening = false; + return CD_SERIAL_READ_SUCCESS; + } + + return CD_SERIAL_READ_SUCCESS; +} + +void cd_cmd_handle(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t data_length) { + cd_s_bin *copy = cd_bin_s_alloc(data_length, data); + + if (data[0] >= CD_CMD_COUNT) return; + cd_cmd_handler_t* handler = CD_CMD_HANDLERS[data[0]]; + + if (handler == NULL) return; + (*handler)(copy); + + free(copy); +} + +#ifdef __cplusplus +} +#endif + diff --git a/shared/serial_parse.h b/shared/serial_parse.h new file mode 100644 index 0000000..1e9c404 --- /dev/null +++ b/shared/serial_parse.h @@ -0,0 +1,26 @@ +#pragma once + +/** @file serial_parse.h */ + +#include <stdint.h> +#include <stdbool.h> + +#include "protocol.h" + +#define CD_SERIAL_READ_SUCCESS true +#define CD_SERIAL_READ_FAILURE false + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * parse serial data byte by byte + * @return true if read success, false if read fails + */ +bool cd_serial_parse(uint8_t byte); + +#ifdef __cplusplus +} +#endif + |