From f8595800e8147f6c12d52aef99c4e453ec4ad227 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Sun, 19 May 2024 14:28:07 +0200 Subject: finish puzzle bus writer/reader functions --- proto/puzbusv1.c | 43 +++++++++++++++++++++++++++++++------------ proto/puzbusv1.h | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c index 3ff7c63..d6cd597 100644 --- a/proto/puzbusv1.c +++ b/proto/puzbusv1.c @@ -1,25 +1,44 @@ #include #include +// MIN() macro +#include +// TODO: check if this works on pico as well + #include "puzbusv1.h" -int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { - mpack_reader_t reader; - printf("read %lu bytes...\n", buf_sz); +bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { + // remaining bytes to be read to target->data; this is the only variable that + // needs to persist between buffer blocks, and is therefore static + static size_t rdata = 0; + // a new reader is used per buffer block passed to this function + mpack_reader_t reader; mpack_reader_init_data(&reader, buf, buf_sz); - uint16_t address = mpack_expect_u16(&reader); - char data_buf[80]; - size_t data_size = mpack_expect_bin_buf(&reader, data_buf, sizeof(data_buf)); - - printf("0x%02x\n", address); - printf("\"%.*s\"\n", data_size, data_buf); - - return 0; + // at start of message + if (rdata == 0) { + // NOTE: This approach will crash and burn when target->addr can be read + // and target->length is past the end of the current buffer block. This is + // a highly unlikely scenario, as pb_read is called for each chunk of a TCP + // frame, and frames (should) include only one puzzle bus message. + target->addr = mpack_expect_u16(&reader); + target->length = rdata = mpack_expect_bin(&reader); + target->data = (char*) malloc(target->length); + } + + // continue reading chunks of target->data until the amount of bytes + // specified in target->length + size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), rdata); + char* data = target->data + target->length - rdata; // 'ol pointer arithmetic + mpack_read_bytes(&reader, data, to_read); + rdata -= to_read; + + // if rdata = 0, the message was completely read + return rdata == 0; } -int pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { +bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { mpack_writer_t writer; mpack_writer_init_growable(&writer, buf, buf_sz); diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 116dbf9..814bc93 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -13,8 +13,41 @@ struct pb_msg { size_t length; }; -int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); -int pb_write(struct pb_msg* target, char** buf, size_t* buf_sz); +/** + * \brief Read chunk of input stream, and store resulting message in \p target + * + * This function is called for each chunk of data from an input stream, and + * will parse the next puzzle bus message into \p target. The input stream is + * assumed to only contain messages encoded by \p pb_write() + * + * \param target pointer to struct that will contain the finished message data + * \param buf pointer to input stream data chunk + * \param buf_sz size of \p buf + * + * \returns boolean true if a message was completely read, false if more data + * is required + * + * \note target->data will automatically be allocated by this function, and + * must be `free()`d by the caller when finished + */ +bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz); +/** + * \brief Allocate and write a msgpack-formatted message to \p buf + * + * This function allocates a buffer large enough to fit the message specified + * in \p target, and encodes the data in \p target in a format that can be + * decoded later using \p pb_read() + * + * \param target pointer to struct that contains the message data + * \param buf pointer to \c char* that will contain the formatted message + * \param buf_sz pointer to \c size_t that will represent the final size of \p buf + * + * \returns boolean true if a the message could be encoded successfully, false + * if there was some kind of error + * + * \note the pointer stored in \p buf must be `free()`d by the caller afterwards + */ +bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz); #ifdef __cplusplus } -- cgit v1.2.3 From 2d3ba07806517f0d27b118df761675a05ab98fc7 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Sun, 19 May 2024 15:10:46 +0200 Subject: add puzzle bus serializer to main controller application --- main/CMakeLists.txt | 1 + main/sock.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index cd90499..90ca8e3 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -31,6 +31,7 @@ target_link_libraries(main pico_stdlib FreeRTOS-Kernel FreeRTOS-Kernel-Heap4 + puzbus mpack ) diff --git a/main/sock.c b/main/sock.c index dac62af..705e2eb 100644 --- a/main/sock.c +++ b/main/sock.c @@ -3,19 +3,61 @@ #include #include #include +#include #include "init.h" #include "config.h" +#include "puzbusv1.h" +#include "sock.h" struct netconn* current_connection = NULL; +struct pb_msg recv_msg; -void recv_handler(struct netconn* conn, struct netbuf* buf) { - void *data; - uint16_t len; +void i2c_send(uint16_t addr, char* data, size_t data_size) { + if (current_connection == NULL) return; + + struct pb_msg send_msg = { + .addr = addr, + .data = data, + .length = data_size, + }; + + char* buf; + size_t buf_sz; + + if (!pb_write(&send_msg, &buf, &buf_sz)) return; + + // NOTE: netconn does return an error code, but the data needs to be freed + // whether netconn throws an error or not, so it remains unused + netconn_write(current_connection, buf, buf_sz, NETCONN_COPY); + + free(buf); +} +void i2c_recv(uint16_t addr, char* data, size_t data_size) { + printf("address: 0x%02x\n", addr); + printf("data: \"%.*s\"\n", data_size, data); + + // send message back + char reply[] = "Test message back!"; + i2c_send(0x69, reply, strlen(reply)); + + // TODO: this function should forward the recieved message onto the puzzle + // bus instead of printing/replying +} + +void recv_handler(struct netconn* conn, struct netbuf* buf) { do { - netbuf_data(buf, &data, &len); - printf("got %d bytes!\n", len); + char* data; + uint16_t len; + netbuf_data(buf, (void**)&data, &len); + + // continue early if more data is needed to complete message + if (!pb_read(&recv_msg, data, len)) continue; + + // forward received message to puzzle bus + i2c_recv(recv_msg.addr, recv_msg.data, recv_msg.length); + free(recv_msg.data); } while (netbuf_next(buf) >= 0); netbuf_delete(buf); -- cgit v1.2.3 From 9ff66bfe22c5f378584db2a57160d00b585a77ce Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 10:25:21 +0200 Subject: make puzbus slightly more robust --- client/main.cpp | 27 ++++++++++++++++++++------- proto/puzbusv1.c | 33 ++++++++++++++++++++++++--------- proto/puzbusv1.h | 23 ++++++++++++++++++----- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/client/main.cpp b/client/main.cpp index 30d7045..dcc965b 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -17,31 +17,44 @@ int send_message() { size_t size; if (!pb_write(&output, &packed, &size)) { printf("error writing!\n"); - return 1; + return EXIT_FAILURE; } fwrite(packed, sizeof(packed[0]), size, stdout); fflush(stdout); - return 0; + return EXIT_SUCCESS; } int read_message() { freopen(NULL, "rb", stdin); // allow binary on stdin struct pb_msg input; - char buf[8]; // extremely small buffer to test chunked message parsing + char buf[4]; // extremely small buffer to test chunked message parsing size_t bytes = 0; + while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { - if (!pb_read(&input, buf, bytes)) continue; + int ret = pb_read(&input, buf, bytes); + + // header read error + if (ret < 0) { + printf("error reading!\n"); + return EXIT_FAILURE; + } + + // continue reading if more bytes needed... + if (ret > 0) continue; + // message read completely! printf("address: 0x%02x\n", input.addr); printf("data: \"%.*s\"\n", input.length, input.data); free(input.data); - return 0; + return EXIT_SUCCESS; } - return 1; + // if we reach this point, data was read but it did not contain a complete + // message, and is thus considered a failure + return EXIT_FAILURE; } int main() { @@ -49,6 +62,6 @@ int main() { if (!isatty(fileno(stdin))) return read_message(); printf("please pipe some data in or out to use this program\n"); - return 0; + return EXIT_SUCCESS; } diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c index d6cd597..3be4939 100644 --- a/proto/puzbusv1.c +++ b/proto/puzbusv1.c @@ -7,21 +7,32 @@ #include "puzbusv1.h" -bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { - // remaining bytes to be read to target->data; this is the only variable that - // needs to persist between buffer blocks, and is therefore static - static size_t rdata = 0; +/** + * \brief Remaining bytes to be read to target->data + * + * This is the only variable that needs to persist between buffer blocks. It is + * declared in the global scope to allow resetting using the \c pb_read_reset() + * function. + * + * \note \p rdata may be reset by calling \c pb_read_reset() + */ +static size_t rdata = 0; +int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { // a new reader is used per buffer block passed to this function mpack_reader_t reader; mpack_reader_init_data(&reader, buf, buf_sz); // at start of message if (rdata == 0) { - // NOTE: This approach will crash and burn when target->addr can be read - // and target->length is past the end of the current buffer block. This is - // a highly unlikely scenario, as pb_read is called for each chunk of a TCP - // frame, and frames (should) include only one puzzle bus message. + // NOTE: The entire start of a message needs to be readable from the buffer + // at this point. When target->addr can be read and target->length is past + // the end of the current buffer block, this function will crash and burn. + // This is a highly unlikely scenario, as pb_read is called for each chunk + // of a TCP frame, and frames (should) include only one puzzle bus message. + // The check here is kind of optional. + if (buf_sz < 4) return -1; + target->addr = mpack_expect_u16(&reader); target->length = rdata = mpack_expect_bin(&reader); target->data = (char*) malloc(target->length); @@ -35,7 +46,11 @@ bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { rdata -= to_read; // if rdata = 0, the message was completely read - return rdata == 0; + return rdata; +} + +void pb_read_reset() { + rdata = 0; } bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 814bc93..95130c9 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -24,13 +24,26 @@ struct pb_msg { * \param buf pointer to input stream data chunk * \param buf_sz size of \p buf * - * \returns boolean true if a message was completely read, false if more data - * is required + * \returns Integer representing amount of bytes required to finish message, or + * -1 if the message header could not be read. If this function returns 0, the + * message in \p target is complete. * - * \note target->data will automatically be allocated by this function, and - * must be `free()`d by the caller when finished + * \note target->data will automatically be allocated by this function, even if + * the message is not fully parsed. This variable must be `free()`d by the + * caller after each complete message to prevent memory leaks. */ -bool pb_read(struct pb_msg* target, char* buf, size_t buf_sz); +int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); + +/** + * \brief reset the remaining message data counter + * + * Calling this function has the effect of forcing \c pb_read() to parse the + * next buffer chunk as the start of a new message. This function may be called + * before reading a TCP frame's data to mitigate any synchronization issues + * arising from earlier corrupt or otherwise malformed messages. + */ +void pb_read_reset(); + /** * \brief Allocate and write a msgpack-formatted message to \p buf * -- cgit v1.2.3 From b854c4d6ac06c4a39006a086766deb90096c2998 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 11:44:29 +0200 Subject: WIP CLI --- client/CMakeLists.txt | 2 + client/examples/puzbus-hello-world.cpp | 67 +++++++++++++++++++++++++++++++++ client/main.cpp | 68 ++++++---------------------------- client/rl.c | 55 +++++++++++++++++++++++++++ client/rl.h | 18 +++++++++ 5 files changed, 153 insertions(+), 57 deletions(-) create mode 100644 client/examples/puzbus-hello-world.cpp create mode 100644 client/rl.c create mode 100644 client/rl.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index bcef4c0..d77b65b 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,11 +10,13 @@ include(../proto/include.cmake) add_executable(main main.cpp + rl.c ) target_link_libraries(main puzbus mpack + readline # this is such a common library that I did not bother adding it as a submodule ) diff --git a/client/examples/puzbus-hello-world.cpp b/client/examples/puzbus-hello-world.cpp new file mode 100644 index 0000000..dcc965b --- /dev/null +++ b/client/examples/puzbus-hello-world.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include "puzbusv1.h" + +int send_message() { + const char* data = "Test message data!"; + struct pb_msg output = { + .addr = 0x39, + .data = (char*) data, + .length = strlen(data), + }; + + char* packed; + size_t size; + if (!pb_write(&output, &packed, &size)) { + printf("error writing!\n"); + return EXIT_FAILURE; + } + + fwrite(packed, sizeof(packed[0]), size, stdout); + fflush(stdout); + + return EXIT_SUCCESS; +} + +int read_message() { + freopen(NULL, "rb", stdin); // allow binary on stdin + struct pb_msg input; + + char buf[4]; // extremely small buffer to test chunked message parsing + size_t bytes = 0; + + while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { + int ret = pb_read(&input, buf, bytes); + + // header read error + if (ret < 0) { + printf("error reading!\n"); + return EXIT_FAILURE; + } + + // continue reading if more bytes needed... + if (ret > 0) continue; + + // message read completely! + printf("address: 0x%02x\n", input.addr); + printf("data: \"%.*s\"\n", input.length, input.data); + free(input.data); + return EXIT_SUCCESS; + } + + // if we reach this point, data was read but it did not contain a complete + // message, and is thus considered a failure + return EXIT_FAILURE; +} + +int main() { + if (!isatty(fileno(stdout))) return send_message(); + if (!isatty(fileno(stdin))) return read_message(); + + printf("please pipe some data in or out to use this program\n"); + return EXIT_SUCCESS; +} + diff --git a/client/main.cpp b/client/main.cpp index dcc965b..3d3a68c 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,67 +1,21 @@ #include #include -#include -#include +#include -#include "puzbusv1.h" +#include "rl.h" -int send_message() { - const char* data = "Test message data!"; - struct pb_msg output = { - .addr = 0x39, - .data = (char*) data, - .length = strlen(data), - }; - - char* packed; - size_t size; - if (!pb_write(&output, &packed, &size)) { - printf("error writing!\n"); +int main(int argc, char** argv) { + if (argc < 2) { + printf("usage: %s addr [port]\n", argv[0]); return EXIT_FAILURE; } - fwrite(packed, sizeof(packed[0]), size, stdout); - fflush(stdout); - - return EXIT_SUCCESS; -} - -int read_message() { - freopen(NULL, "rb", stdin); // allow binary on stdin - struct pb_msg input; - - char buf[4]; // extremely small buffer to test chunked message parsing - size_t bytes = 0; - - while ((bytes = fread(buf, sizeof(buf[0]), sizeof(buf), stdin)) > 0) { - int ret = pb_read(&input, buf, bytes); - - // header read error - if (ret < 0) { - printf("error reading!\n"); - return EXIT_FAILURE; - } - - // continue reading if more bytes needed... - if (ret > 0) continue; - - // message read completely! - printf("address: 0x%02x\n", input.addr); - printf("data: \"%.*s\"\n", input.length, input.data); - free(input.data); - return EXIT_SUCCESS; - } - - // if we reach this point, data was read but it did not contain a complete - // message, and is thus considered a failure - return EXIT_FAILURE; -} + // parse arguments + char* addr_str = argv[1]; + uint16_t port = 9191; + if (argc >= 3) port = atoi(argv[2]); -int main() { - if (!isatty(fileno(stdout))) return send_message(); - if (!isatty(fileno(stdin))) return read_message(); - - printf("please pipe some data in or out to use this program\n"); - return EXIT_SUCCESS; + // enter main CLI (using GNU readline for comfyness) + return cli_main(); } diff --git a/client/rl.c b/client/rl.c new file mode 100644 index 0000000..fb26057 --- /dev/null +++ b/client/rl.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include +#include + +#include "rl.h" + +void rl_printf(const char *fmt, ...) { + // save line + char* saved_line = rl_copy_text(0, rl_end); + int saved_point = rl_point; + int saved_end = rl_end; + + // clear line + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + + // printf + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + // restore line + rl_restore_prompt(); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_end = saved_end; + rl_redisplay(); + + free(saved_line); +} + +int cli_main() { + char* input = NULL; + while (1) { + if (input != NULL) free(input); + input = readline(CLI_PROMPT); + + // exit on ^D or ^C (EOF) + if (input == NULL) return EXIT_SUCCESS; + + // add non-empty line to history + if (*input) add_history(input); + + if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; + } + + return EXIT_SUCCESS; +} + diff --git a/client/rl.h b/client/rl.h new file mode 100644 index 0000000..7eef4da --- /dev/null +++ b/client/rl.h @@ -0,0 +1,18 @@ +#pragma once + +#define COLOR_OFF "\x1B[0m" +#define COLOR_BLUE "\x1B[0;94m" + +#define CLI_PROMPT COLOR_BLUE "pbc" COLOR_OFF "% " + +#ifdef __cplusplus +extern "C" { +#endif + +int cli_main(); +void rl_printf(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif + -- cgit v1.2.3 From 5876e74fa32881b41478cd67c5b0895161fbdc9c Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 11:55:50 +0200 Subject: add socket class + test async prompt messages --- client/CMakeLists.txt | 1 + client/main.cpp | 6 +++++- client/sock.cpp | 29 +++++++++++++++++++++++++++++ client/sock.h | 21 +++++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 client/sock.cpp create mode 100644 client/sock.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d77b65b..e4990d7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -11,6 +11,7 @@ include(../proto/include.cmake) add_executable(main main.cpp rl.c + sock.cpp ) target_link_libraries(main diff --git a/client/main.cpp b/client/main.cpp index 3d3a68c..6aad0e3 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -3,6 +3,7 @@ #include #include "rl.h" +#include "sock.h" int main(int argc, char** argv) { if (argc < 2) { @@ -11,10 +12,13 @@ int main(int argc, char** argv) { } // parse arguments - char* addr_str = argv[1]; + char* addr = argv[1]; uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); + // connect to TCP socket (automatically spawns thread) + PBSocket sock(addr, port); + // enter main CLI (using GNU readline for comfyness) return cli_main(); } diff --git a/client/sock.cpp b/client/sock.cpp new file mode 100644 index 0000000..703ee24 --- /dev/null +++ b/client/sock.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include + +#include "sock.h" +#include "rl.h" + +PBSocket::PBSocket() { + printf("Init PBSocket!\n"); +} + +PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { + connect(addr, port); +} + +void PBSocket::connect(char* addr, uint16_t port) { + printf("Connect to %s on port %d\n", addr, port); + + this->_thread = std::thread(&PBSocket::sock_task, this); +} + +void PBSocket::sock_task() { + while(1) { + sleep(3); + rl_printf("Testing asynchronous messages in prompt...\n"); + } +} + diff --git a/client/sock.h b/client/sock.h new file mode 100644 index 0000000..e3d7ec8 --- /dev/null +++ b/client/sock.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class PBSocket { +public: + PBSocket(char* addr, uint16_t port); + + void connect(char* addr, uint16_t port); + +private: + PBSocket(); + + void sock_task(); + + std::thread _thread; + +}; + + -- cgit v1.2.3 From 41ed6fa61a65432843feb596726026bc5772ae19 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 13:24:13 +0200 Subject: socket connect working (sorta) --- client/main.cpp | 10 ++++++-- client/rl.h | 6 ++--- client/sock.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++------- client/sock.h | 17 +++++++++---- 4 files changed, 89 insertions(+), 19 deletions(-) diff --git a/client/main.cpp b/client/main.cpp index 6aad0e3..36fc7bb 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "rl.h" #include "sock.h" @@ -16,8 +17,13 @@ int main(int argc, char** argv) { uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); - // connect to TCP socket (automatically spawns thread) - PBSocket sock(addr, port); + try { + // connect to TCP socket (automatically spawns thread) + PBSocket sock(addr, port); + } catch (const std::exception& e) { + printf("error: %s\n", e.what()); + return EXIT_FAILURE; + } // enter main CLI (using GNU readline for comfyness) return cli_main(); diff --git a/client/rl.h b/client/rl.h index 7eef4da..313a8fe 100644 --- a/client/rl.h +++ b/client/rl.h @@ -1,9 +1,9 @@ #pragma once -#define COLOR_OFF "\x1B[0m" -#define COLOR_BLUE "\x1B[0;94m" +#define COLOR_OFF "\x1b[0m" +#define COLOR_BOLD "\x1b[1m" -#define CLI_PROMPT COLOR_BLUE "pbc" COLOR_OFF "% " +#define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " #ifdef __cplusplus extern "C" { diff --git a/client/sock.cpp b/client/sock.cpp index 703ee24..17d9e35 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -1,29 +1,86 @@ +#include +#include +#include #include #include +#include +#include +#include +#include #include #include "sock.h" #include "rl.h" -PBSocket::PBSocket() { - printf("Init PBSocket!\n"); -} +using std::logic_error; +using std::thread; +PBSocket::PBSocket() { } PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { - connect(addr, port); + set_server(addr, port); + sock_connect(); +} + +PBSocket::~PBSocket() { + // stop TCP listen thread + if (_thread != nullptr) { + _thread->detach(); + delete _thread; + } + + sock_close(); +} + +void PBSocket::set_server(char* addr, uint16_t port) { + _addr = addr; + _port = port; } -void PBSocket::connect(char* addr, uint16_t port) { - printf("Connect to %s on port %d\n", addr, port); +void PBSocket::sock_connect() { + if (_addr == NULL) throw logic_error("no server address defined"); + if (_port == 0) throw logic_error("no server port defined"); - this->_thread = std::thread(&PBSocket::sock_task, this); + if (_thread != nullptr) throw logic_error("already connected"); + + rl_printf("connecting to %s on port %d...\n", _addr, _port); + + _fd = socket(AF_INET, SOCK_STREAM, 0); + if (_fd < 0) throw logic_error("socket create failed"); + + struct sockaddr_in server = { + .sin_family = AF_INET, + .sin_port = htons(_port), + .sin_addr = { + .s_addr = inet_addr(_addr), + }, + }; + int ret = connect(_fd, (struct sockaddr*) &server, sizeof(server)); + if (ret != 0) throw logic_error(strerror(errno)); + + this->_thread = new thread(&PBSocket::sock_task, this); +} + +void PBSocket::sock_close() { + if (_fd < 0) return; // already closed + close(_fd); + _fd = -1; } void PBSocket::sock_task() { while(1) { - sleep(3); - rl_printf("Testing asynchronous messages in prompt...\n"); + char buf[80]; + ssize_t bytes = read(_fd, buf, sizeof(buf)); + + if (bytes == -1) { + rl_printf("error: %s (%d)\n", strerror(errno), errno); + sock_close(); + break; + } + + if (bytes > 0) { + rl_printf("received %d bytes\n", bytes); + } } } diff --git a/client/sock.h b/client/sock.h index e3d7ec8..0f9a3fc 100644 --- a/client/sock.h +++ b/client/sock.h @@ -5,17 +5,24 @@ class PBSocket { public: + PBSocket(); PBSocket(char* addr, uint16_t port); + virtual ~PBSocket(); - void connect(char* addr, uint16_t port); + void set_server(char* addr, uint16_t port); -private: - PBSocket(); + void sock_connect(); +private: void sock_task(); + void sock_close(); - std::thread _thread; + std::thread* _thread = nullptr; -}; + char* _addr = NULL; + uint16_t _port = 0; + + int _fd = -1; +}; -- cgit v1.2.3 From 27c8d89359b8d5e97c4c23ff464d5f3de7279709 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 13:33:25 +0200 Subject: fix bad file descriptor error --- client/main.cpp | 3 ++- client/sock.cpp | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/main.cpp b/client/main.cpp index 36fc7bb..c01dbb5 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -17,9 +17,10 @@ int main(int argc, char** argv) { uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); + PBSocket sock(addr, port); try { // connect to TCP socket (automatically spawns thread) - PBSocket sock(addr, port); + sock.sock_connect(); } catch (const std::exception& e) { printf("error: %s\n", e.what()); return EXIT_FAILURE; diff --git a/client/sock.cpp b/client/sock.cpp index 17d9e35..c10fba0 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -19,7 +19,6 @@ using std::thread; PBSocket::PBSocket() { } PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { set_server(addr, port); - sock_connect(); } PBSocket::~PBSocket() { -- cgit v1.2.3 From db8906d54cd9afbc57f0b40a0d618335c552f704 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Mon, 20 May 2024 13:47:37 +0200 Subject: back-and-forth in C++ w/o netcat --- client/CMakeLists.txt | 2 +- client/main.cpp | 12 ++++++--- client/rl.c | 55 ----------------------------------------- client/rl.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ client/rl.h | 8 ------ client/sock.cpp | 46 +++++++++++++++++++++++++++++++--- client/sock.h | 7 ++++++ 7 files changed, 128 insertions(+), 70 deletions(-) delete mode 100644 client/rl.c create mode 100644 client/rl.cpp diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index e4990d7..cae0111 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,7 +10,7 @@ include(../proto/include.cmake) add_executable(main main.cpp - rl.c + rl.cpp sock.cpp ) diff --git a/client/main.cpp b/client/main.cpp index c01dbb5..5c26107 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -6,6 +6,8 @@ #include "rl.h" #include "sock.h" +PBSocket* sock; + int main(int argc, char** argv) { if (argc < 2) { printf("usage: %s addr [port]\n", argv[0]); @@ -17,16 +19,20 @@ int main(int argc, char** argv) { uint16_t port = 9191; if (argc >= 3) port = atoi(argv[2]); - PBSocket sock(addr, port); + sock = new PBSocket(addr, port); try { // connect to TCP socket (automatically spawns thread) - sock.sock_connect(); + sock->sock_connect(); } catch (const std::exception& e) { printf("error: %s\n", e.what()); return EXIT_FAILURE; } // enter main CLI (using GNU readline for comfyness) - return cli_main(); + int ret = cli_main(); + + delete sock; + + return ret; } diff --git a/client/rl.c b/client/rl.c deleted file mode 100644 index fb26057..0000000 --- a/client/rl.c +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -#include "rl.h" - -void rl_printf(const char *fmt, ...) { - // save line - char* saved_line = rl_copy_text(0, rl_end); - int saved_point = rl_point; - int saved_end = rl_end; - - // clear line - rl_save_prompt(); - rl_replace_line("", 0); - rl_redisplay(); - - // printf - va_list args; - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - - // restore line - rl_restore_prompt(); - rl_replace_line(saved_line, 0); - rl_point = saved_point; - rl_end = saved_end; - rl_redisplay(); - - free(saved_line); -} - -int cli_main() { - char* input = NULL; - while (1) { - if (input != NULL) free(input); - input = readline(CLI_PROMPT); - - // exit on ^D or ^C (EOF) - if (input == NULL) return EXIT_SUCCESS; - - // add non-empty line to history - if (*input) add_history(input); - - if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; - } - - return EXIT_SUCCESS; -} - diff --git a/client/rl.cpp b/client/rl.cpp new file mode 100644 index 0000000..32a4df0 --- /dev/null +++ b/client/rl.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include +#include + +#include "rl.h" +#include "sock.h" + +void rl_printf(const char *fmt, ...) { + // save line + char* saved_line = rl_copy_text(0, rl_end); + int saved_point = rl_point; + int saved_end = rl_end; + + // clear line + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + + // printf + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + // restore line + rl_restore_prompt(); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_end = saved_end; + rl_redisplay(); + + free(saved_line); +} + +void cmd_test() { + const char* data = "Hello world!"; + i2c_send(0x39, (char*) data, strlen(data)); +} + +int cli_main() { + char* input = NULL; + while (1) { + if (input != NULL) free(input); + input = readline(CLI_PROMPT); + + // exit on ^D or ^C (EOF) + if (input == NULL) return EXIT_SUCCESS; + + // add non-empty line to history + if (*input) add_history(input); + + if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; + + if (strcmp(input, "test") == 0) { + cmd_test(); + continue; + } + + printf("unknown command!\n"); + } + + return EXIT_SUCCESS; +} + diff --git a/client/rl.h b/client/rl.h index 313a8fe..503225f 100644 --- a/client/rl.h +++ b/client/rl.h @@ -5,14 +5,6 @@ #define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " -#ifdef __cplusplus -extern "C" { -#endif - int cli_main(); void rl_printf(const char *fmt, ...); -#ifdef __cplusplus -} -#endif - diff --git a/client/sock.cpp b/client/sock.cpp index c10fba0..cc18a69 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -10,6 +10,7 @@ #include +#include "puzbusv1.h" #include "sock.h" #include "rl.h" @@ -66,20 +67,59 @@ void PBSocket::sock_close() { _fd = -1; } +void PBSocket::send(char* buf, size_t buf_sz) { + write(_fd, buf, buf_sz); +} + void PBSocket::sock_task() { + struct pb_msg input; + while(1) { char buf[80]; ssize_t bytes = read(_fd, buf, sizeof(buf)); if (bytes == -1) { rl_printf("error: %s (%d)\n", strerror(errno), errno); - sock_close(); break; } - if (bytes > 0) { - rl_printf("received %d bytes\n", bytes); + // skip empty frames + if (bytes == 0) continue; + + int ret = pb_read(&input, buf, bytes); + + // header read error + if (ret < 0) { + rl_printf("pb_read error!\n"); + break; } + + // continue reading if more bytes needed... + if (ret > 0) continue; + + // message read completely! + i2c_recv(input.addr, input.data, input.length); + free(input.data); } + + sock_close(); +} + +void i2c_send(uint16_t addr, char* data, size_t data_size) { + struct pb_msg msg = { + .addr = addr, + .data = data, + .length = data_size, + }; + + char* packed; + size_t size; + if (!pb_write(&msg, &packed, &size)) return; + + sock->send(packed, size); +} + +void i2c_recv(uint16_t addr, char* data, size_t data_size) { + rl_printf("[0x%02x]: %.*s\n", addr, data_size, data); } diff --git a/client/sock.h b/client/sock.h index 0f9a3fc..818ea72 100644 --- a/client/sock.h +++ b/client/sock.h @@ -13,6 +13,8 @@ public: void sock_connect(); + void send(char* buf, size_t buf_sz); + private: void sock_task(); void sock_close(); @@ -26,3 +28,8 @@ private: }; +extern PBSocket* sock; + +void i2c_send(uint16_t addr, char* data, size_t data_size); +void i2c_recv(uint16_t addr, char* data, size_t data_size); + -- cgit v1.2.3 From 68a5c65f9b0e1df30e9cef490d9b218b2f21f90d Mon Sep 17 00:00:00 2001 From: lonkaars Date: Tue, 21 May 2024 10:30:06 +0200 Subject: clean up puzbusv1 API --- client/sock.cpp | 12 ++++++------ client/sock.h | 13 ++++++------- main/sock.c | 10 ++++++---- main/sock.h | 4 ++-- proto/puzbusv1.c | 33 +++++++++++---------------------- proto/puzbusv1.h | 14 ++++++++------ 6 files changed, 39 insertions(+), 47 deletions(-) diff --git a/client/sock.cpp b/client/sock.cpp index cc18a69..f967f64 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -18,7 +18,7 @@ using std::logic_error; using std::thread; PBSocket::PBSocket() { } -PBSocket::PBSocket(char* addr, uint16_t port) : PBSocket() { +PBSocket::PBSocket(const char * addr, uint16_t port) : PBSocket() { set_server(addr, port); } @@ -32,7 +32,7 @@ PBSocket::~PBSocket() { sock_close(); } -void PBSocket::set_server(char* addr, uint16_t port) { +void PBSocket::set_server(const char * addr, uint16_t port) { _addr = addr; _port = port; } @@ -67,7 +67,7 @@ void PBSocket::sock_close() { _fd = -1; } -void PBSocket::send(char* buf, size_t buf_sz) { +void PBSocket::send(const char * buf, size_t buf_sz) { write(_fd, buf, buf_sz); } @@ -105,10 +105,10 @@ void PBSocket::sock_task() { sock_close(); } -void i2c_send(uint16_t addr, char* data, size_t data_size) { +void i2c_send(uint16_t addr, const char * data, size_t data_size) { struct pb_msg msg = { .addr = addr, - .data = data, + .data = (char *) data, .length = data_size, }; @@ -119,7 +119,7 @@ void i2c_send(uint16_t addr, char* data, size_t data_size) { sock->send(packed, size); } -void i2c_recv(uint16_t addr, char* data, size_t data_size) { +void i2c_recv(uint16_t addr, const char * data, size_t data_size) { rl_printf("[0x%02x]: %.*s\n", addr, data_size, data); } diff --git a/client/sock.h b/client/sock.h index 818ea72..42eba3b 100644 --- a/client/sock.h +++ b/client/sock.h @@ -6,14 +6,14 @@ class PBSocket { public: PBSocket(); - PBSocket(char* addr, uint16_t port); + PBSocket(const char * addr, uint16_t port); virtual ~PBSocket(); - void set_server(char* addr, uint16_t port); + void set_server(const char * addr, uint16_t port); void sock_connect(); - void send(char* buf, size_t buf_sz); + void send(const char * buf, size_t buf_sz); private: void sock_task(); @@ -21,15 +21,14 @@ private: std::thread* _thread = nullptr; - char* _addr = NULL; + const char * _addr = NULL; uint16_t _port = 0; int _fd = -1; - }; extern PBSocket* sock; -void i2c_send(uint16_t addr, char* data, size_t data_size); -void i2c_recv(uint16_t addr, char* data, size_t data_size); +void i2c_send(uint16_t addr, const char * data, size_t data_size); +void i2c_recv(uint16_t addr, const char * data, size_t data_size); diff --git a/main/sock.c b/main/sock.c index 705e2eb..4f50981 100644 --- a/main/sock.c +++ b/main/sock.c @@ -13,16 +13,16 @@ struct netconn* current_connection = NULL; struct pb_msg recv_msg; -void i2c_send(uint16_t addr, char* data, size_t data_size) { +void i2c_send(uint16_t addr, const char * data, size_t data_size) { if (current_connection == NULL) return; struct pb_msg send_msg = { .addr = addr, - .data = data, + .data = (char *) data, .length = data_size, }; - char* buf; + char * buf; size_t buf_sz; if (!pb_write(&send_msg, &buf, &buf_sz)) return; @@ -34,7 +34,7 @@ void i2c_send(uint16_t addr, char* data, size_t data_size) { free(buf); } -void i2c_recv(uint16_t addr, char* data, size_t data_size) { +void i2c_recv(uint16_t addr, const char * data, size_t data_size) { printf("address: 0x%02x\n", addr); printf("data: \"%.*s\"\n", data_size, data); @@ -47,6 +47,8 @@ void i2c_recv(uint16_t addr, char* data, size_t data_size) { } void recv_handler(struct netconn* conn, struct netbuf* buf) { + pb_read_reset(&recv_msg); + do { char* data; uint16_t len; diff --git a/main/sock.h b/main/sock.h index 2a73418..f2db35d 100644 --- a/main/sock.h +++ b/main/sock.h @@ -6,6 +6,6 @@ /** \brief start listening for TCP socket requests */ void serve_task(); -void i2c_send(uint16_t addr, char* data, size_t data_size); -void i2c_recv(uint16_t addr, char* data, size_t data_size); +void i2c_send(uint16_t addr, const char * data, size_t data_size); +void i2c_recv(uint16_t addr, const char * data, size_t data_size); diff --git a/proto/puzbusv1.c b/proto/puzbusv1.c index 3be4939..73deda5 100644 --- a/proto/puzbusv1.c +++ b/proto/puzbusv1.c @@ -7,24 +7,13 @@ #include "puzbusv1.h" -/** - * \brief Remaining bytes to be read to target->data - * - * This is the only variable that needs to persist between buffer blocks. It is - * declared in the global scope to allow resetting using the \c pb_read_reset() - * function. - * - * \note \p rdata may be reset by calling \c pb_read_reset() - */ -static size_t rdata = 0; - -int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { +int pb_read(struct pb_msg * target, const char * buf, size_t buf_sz) { // a new reader is used per buffer block passed to this function mpack_reader_t reader; mpack_reader_init_data(&reader, buf, buf_sz); // at start of message - if (rdata == 0) { + if (target->_rdata == 0) { // NOTE: The entire start of a message needs to be readable from the buffer // at this point. When target->addr can be read and target->length is past // the end of the current buffer block, this function will crash and burn. @@ -34,26 +23,26 @@ int pb_read(struct pb_msg* target, char* buf, size_t buf_sz) { if (buf_sz < 4) return -1; target->addr = mpack_expect_u16(&reader); - target->length = rdata = mpack_expect_bin(&reader); - target->data = (char*) malloc(target->length); + target->length = target->_rdata = mpack_expect_bin(&reader); + target->data = (char *) malloc(target->length); } // continue reading chunks of target->data until the amount of bytes // specified in target->length - size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), rdata); - char* data = target->data + target->length - rdata; // 'ol pointer arithmetic + size_t to_read = MIN(mpack_reader_remaining(&reader, NULL), target->_rdata); + char * data = target->data + target->length - target->_rdata; mpack_read_bytes(&reader, data, to_read); - rdata -= to_read; + target->_rdata -= to_read; // if rdata = 0, the message was completely read - return rdata; + return target->_rdata; } -void pb_read_reset() { - rdata = 0; +void pb_read_reset(struct pb_msg * target) { + target->_rdata = 0; } -bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz) { +bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz) { mpack_writer_t writer; mpack_writer_init_growable(&writer, buf, buf_sz); diff --git a/proto/puzbusv1.h b/proto/puzbusv1.h index 95130c9..0985b2b 100644 --- a/proto/puzbusv1.h +++ b/proto/puzbusv1.h @@ -7,10 +7,12 @@ extern "C" { #endif +/** \brief Puzzle bus message (v1) */ struct pb_msg { - uint16_t addr; - char* data; - size_t length; + uint16_t addr; //!< I^2^C address + char * data; //!< message content + size_t length; //!< message size + size_t _rdata; //!< \private remaining bytes to read until message is complete }; /** @@ -32,7 +34,7 @@ struct pb_msg { * the message is not fully parsed. This variable must be `free()`d by the * caller after each complete message to prevent memory leaks. */ -int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); +int pb_read(struct pb_msg * target, const char * buf, size_t buf_sz); /** * \brief reset the remaining message data counter @@ -42,7 +44,7 @@ int pb_read(struct pb_msg* target, char* buf, size_t buf_sz); * before reading a TCP frame's data to mitigate any synchronization issues * arising from earlier corrupt or otherwise malformed messages. */ -void pb_read_reset(); +void pb_read_reset(struct pb_msg * target); /** * \brief Allocate and write a msgpack-formatted message to \p buf @@ -60,7 +62,7 @@ void pb_read_reset(); * * \note the pointer stored in \p buf must be `free()`d by the caller afterwards */ -bool pb_write(struct pb_msg* target, char** buf, size_t* buf_sz); +bool pb_write(const struct pb_msg * target, char ** buf, size_t * buf_sz); #ifdef __cplusplus } -- cgit v1.2.3 From 53d27ebf10225274a50dc4a7c2343d4efce55a8a Mon Sep 17 00:00:00 2001 From: lonkaars Date: Wed, 22 May 2024 19:32:21 +0200 Subject: clean up command handling --- client/.gitignore | 1 - client/CMakeLists.txt | 5 +++-- client/cmd.cpp | 15 +++++++++++++++ client/cmd.h | 23 +++++++++++++++++++++++ client/pbc | 1 + client/readme.md | 14 ++++++++++++++ client/rl.cpp | 27 ++++++++++++--------------- 7 files changed, 68 insertions(+), 18 deletions(-) delete mode 100644 client/.gitignore create mode 100644 client/cmd.cpp create mode 100644 client/cmd.h create mode 120000 client/pbc create mode 100644 client/readme.md diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index ba2906d..0000000 --- a/client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -main diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index cae0111..c526345 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -8,13 +8,14 @@ project(puzzlebox_client C CXX) include(../proto/include.cmake) -add_executable(main +add_executable(pbc main.cpp rl.cpp sock.cpp + cmd.cpp ) -target_link_libraries(main +target_link_libraries(pbc puzbus mpack readline # this is such a common library that I did not bother adding it as a submodule diff --git a/client/cmd.cpp b/client/cmd.cpp new file mode 100644 index 0000000..99a4dd6 --- /dev/null +++ b/client/cmd.cpp @@ -0,0 +1,15 @@ +#include +#include + +#include "cmd.h" +#include "sock.h" + +void cmd_exit(char*) { + exit(EXIT_SUCCESS); +} + +void cmd_test(char*) { + const char* data = "Hello world!"; + i2c_send(0x39, (char*) data, strlen(data)); +} + diff --git a/client/cmd.h b/client/cmd.h new file mode 100644 index 0000000..509104a --- /dev/null +++ b/client/cmd.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef void cmd_fn_t(char *); + +struct cmd { + const char* name; + void (* handle)(char *); + const char* info; + // TODO: tab completion function? +}; + +cmd_fn_t cmd_exit; +cmd_fn_t cmd_test; + +static const struct cmd cmds[] = { + (struct cmd){ .name = "exit", .handle = cmd_exit, .info = NULL, }, + (struct cmd){ .name = "test", .handle = cmd_test, .info = NULL, }, +}; + +static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); + diff --git a/client/pbc b/client/pbc new file mode 120000 index 0000000..51eda50 --- /dev/null +++ b/client/pbc @@ -0,0 +1 @@ +build/pbc \ No newline at end of file diff --git a/client/readme.md b/client/readme.md new file mode 100644 index 0000000..9d755aa --- /dev/null +++ b/client/readme.md @@ -0,0 +1,14 @@ +# puzzle box client + +goal (in order of implementation): +``` +(pbc) help + exit exit pbc + test send a test puzbus message + help show this help + send [debug] send raw message + status show global puzzle box state (main controller state) + reset reset entire game state + ls list connected puzzle modules +``` + diff --git a/client/rl.cpp b/client/rl.cpp index 32a4df0..b016370 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -7,7 +7,7 @@ #include #include "rl.h" -#include "sock.h" +#include "cmd.h" void rl_printf(const char *fmt, ...) { // save line @@ -36,9 +36,13 @@ void rl_printf(const char *fmt, ...) { free(saved_line); } -void cmd_test() { - const char* data = "Hello world!"; - i2c_send(0x39, (char*) data, strlen(data)); +static bool cli_cmd(char* line) { + for (size_t i = 0; i < cmds_length; i++) { + if (strcmp(line, cmds[i].name) != 0) continue; + cmds[i].handle(line); + return true; + } + return false; } int cli_main() { @@ -47,18 +51,11 @@ int cli_main() { if (input != NULL) free(input); input = readline(CLI_PROMPT); - // exit on ^D or ^C (EOF) - if (input == NULL) return EXIT_SUCCESS; + if (input == NULL) return EXIT_SUCCESS; // exit on ^D (EOF) + if (*input == '\0') continue; // ignore empty lines + add_history(input); - // add non-empty line to history - if (*input) add_history(input); - - if (strcmp(input, "exit") == 0) return EXIT_SUCCESS; - - if (strcmp(input, "test") == 0) { - cmd_test(); - continue; - } + if (cli_cmd(input)) continue; printf("unknown command!\n"); } -- cgit v1.2.3 From 31c30df2a24a45c69a7c5c2f594fa3a9a835b1fb Mon Sep 17 00:00:00 2001 From: lonkaars Date: Fri, 24 May 2024 15:18:44 +0200 Subject: add tab completion + help function --- client/cmd.cpp | 17 +++++++++++++++++ client/cmd.h | 45 +++++++++++++++++++++++++++++++++++++++++---- client/rl.cpp | 20 +++++++++++++++++++- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/client/cmd.cpp b/client/cmd.cpp index 99a4dd6..0a73dad 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -13,3 +14,19 @@ void cmd_test(char*) { i2c_send(0x39, (char*) data, strlen(data)); } +void cmd_help(char*) { + printf("List of available commands:\n"); + for (size_t i = 0; i < cmds_length; i++) { + struct cmd cmd = cmds[i]; + printf(" %-*s", 10, cmd.name); + if (cmd.info != NULL) + printf(" %s", cmd.info); + printf("\n"); + } + + printf( + "\n" + "You can also use the TAB key to autocomplete commands\n" + ); +} + diff --git a/client/cmd.h b/client/cmd.h index 509104a..7ec67fc 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -5,19 +5,56 @@ typedef void cmd_fn_t(char *); struct cmd { - const char* name; void (* handle)(char *); + const char* name; const char* info; // TODO: tab completion function? }; cmd_fn_t cmd_exit; cmd_fn_t cmd_test; +cmd_fn_t cmd_help; +cmd_fn_t cmd_send; +cmd_fn_t cmd_status; +cmd_fn_t cmd_reset; +cmd_fn_t cmd_ls; static const struct cmd cmds[] = { - (struct cmd){ .name = "exit", .handle = cmd_exit, .info = NULL, }, - (struct cmd){ .name = "test", .handle = cmd_test, .info = NULL, }, + { + .handle = cmd_exit, + .name = "exit", + .info = "exit pbc", + }, + { + .handle = cmd_test, + .name = "test", + .info = "send a test puzbus message", + }, + { + .handle = cmd_help, + .name = "help", + .info = "show this help", + }, + // { + // .handle = cmd_send, + // .name = "send", + // .info = "[debug] send raw message", + // }, + // { + // .handle = cmd_status, + // .name = "status", + // .info = "show global puzzle box state (main controller state)", + // }, + // { + // .handle = cmd_reset, + // .name = "reset", + // .info = "reset entire game state", + // }, + // { + // .handle = cmd_ls, + // .name = "ls", + // .info = "list connected puzzle modules", + // }, }; - static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/rl.cpp b/client/rl.cpp index b016370..6073500 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -37,16 +37,34 @@ void rl_printf(const char *fmt, ...) { } static bool cli_cmd(char* line) { + char* cmd = strtok(line, " \t\n"); for (size_t i = 0; i < cmds_length; i++) { - if (strcmp(line, cmds[i].name) != 0) continue; + if (strncmp(cmds[i].name, cmd, strlen(cmd)) != 0) continue; cmds[i].handle(line); return true; } return false; } +static char* rl_completion_entries(const char *text, int state) { + static size_t i = 0; + if (state == 0) i = 0; + + while (i < cmds_length) { + struct cmd cmd = cmds[i]; + i++; + if (strncmp(text, cmd.name, strlen(text)) == 0) { + return strdup(cmd.name); + } + } + + return NULL; +} + int cli_main() { char* input = NULL; + rl_completion_entry_function = rl_completion_entries; + while (1) { if (input != NULL) free(input); input = readline(CLI_PROMPT); -- cgit v1.2.3 From 1a92ed5075aba4b41fe34422d21a2c66cdf1d4c9 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Fri, 24 May 2024 17:47:00 +0200 Subject: WIP `send` command --- client/CMakeLists.txt | 1 + client/cmd.cpp | 58 ++++++++++++++++++++++++++++++++++++++------------- client/cmd.h | 10 ++++----- client/parse.cpp | 32 ++++++++++++++++++++++++++++ client/parse.h | 38 +++++++++++++++++++++++++++++++++ client/rl.cpp | 47 ++++++++++++++++++++++------------------- 6 files changed, 145 insertions(+), 41 deletions(-) create mode 100644 client/parse.cpp create mode 100644 client/parse.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c526345..35a55b6 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(pbc rl.cpp sock.cpp cmd.cpp + parse.cpp ) target_link_libraries(pbc diff --git a/client/cmd.cpp b/client/cmd.cpp index 0a73dad..4a2c8a3 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -4,6 +4,12 @@ #include "cmd.h" #include "sock.h" +#include "parse.h" + +char* consume_token(char* input, const char* ifs) { + strtok(input, ifs); + return strtok(NULL, "\0"); +} void cmd_exit(char*) { exit(EXIT_SUCCESS); @@ -11,22 +17,46 @@ void cmd_exit(char*) { void cmd_test(char*) { const char* data = "Hello world!"; - i2c_send(0x39, (char*) data, strlen(data)); + i2c_send(0x39, data, strlen(data)); } void cmd_help(char*) { - printf("List of available commands:\n"); - for (size_t i = 0; i < cmds_length; i++) { - struct cmd cmd = cmds[i]; - printf(" %-*s", 10, cmd.name); - if (cmd.info != NULL) - printf(" %s", cmd.info); - printf("\n"); - } - - printf( - "\n" - "You can also use the TAB key to autocomplete commands\n" - ); + printf("List of available commands:\n"); + for (size_t i = 0; i < cmds_length; i++) { + struct cmd cmd = cmds[i]; + printf(" %-*s", 10, cmd.name); + if (cmd.info != NULL) + printf(" %s", cmd.info); + printf("\n"); + } + + printf( + "\n" + "You can also use the TAB key to autocomplete commands\n" + ); +} + +void cmd_send(char* addr_str) { + char* data_str = consume_token(addr_str, IFS); + + char* end; + uint16_t addr = strtol(addr_str, &end, 0); + if (addr_str + strlen(addr_str) != end) { + printf("address format error\n"); + return; + } + + char* data; + size_t data_size; + if (strtodata(data_str, &data, &data_size)) { + printf("data format error at index %d:\n%s\n%*s^\n", + (int) data_size, data_str, (int) data_size, ""); + return; + } + + // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); + i2c_send(addr, data, data_size); + + free(data); } diff --git a/client/cmd.h b/client/cmd.h index 7ec67fc..9d20328 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -35,11 +35,11 @@ static const struct cmd cmds[] = { .name = "help", .info = "show this help", }, - // { - // .handle = cmd_send, - // .name = "send", - // .info = "[debug] send raw message", - // }, + { + .handle = cmd_send, + .name = "send", + .info = "[debug] send raw message", + }, // { // .handle = cmd_status, // .name = "status", diff --git a/client/parse.cpp b/client/parse.cpp new file mode 100644 index 0000000..d15207c --- /dev/null +++ b/client/parse.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "parse.h" + +static unsigned ifsrun(const char* input, const char* ift) { + unsigned i; + for (i = 0; input[i] != '\0' && strchr(ift, input[i]); i++); + return i; +} + +int strtodata(const char* str, char** data, size_t* size) { + const char* ifs = IFS; + *size = 0; + size_t i; + size_t str_len = strlen(str); + + // TODO: finish this parser + // for (i = 0; i < str_len; i++) { + // unsigned ifs_run = ifsrun(&str[i], ifs); + // + // } + + *size = str_len; + + *data = (char*) malloc(*size); + + memcpy(*data, str, *size); + + return 0; +} + diff --git a/client/parse.h b/client/parse.h new file mode 100644 index 0000000..ac06446 --- /dev/null +++ b/client/parse.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#define IFS " \t\n" + +/** + * \brief modify \p token to point to the first token when broken up on \p ifs + * and return the remaining data + * + * \p token will be null-terminated to indicate the end of the first token. A + * pointer to the remaining line after the NULL byte is returned, or NULL when + * the end of the string has been reached. + * + * \param token input string + * \param ifs string containing field separators + * + * \return the remaining data after \p token and the first \p ifs + */ +char* consume_token(char* token, const char* ifs); + +/** + * \brief convert string with literals into raw data + * + * \param str input string containing literals + * \param data pointer to \c char* that will store the resulting data + * \param size size of \p data + * + * \return 0 if the string was parsed succesfully, or 1 if the string could not + * be parsed succesfully + * + * \note The pointer that \p data refers to will not be initialized by this + * function if parsing fails + * \note \p size will contain the index of \p str where the first invalid data + * was found if parsing fails + */ +int strtodata(const char* str, char** data, size_t* size); + diff --git a/client/rl.cpp b/client/rl.cpp index 6073500..3f93e99 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -8,13 +8,14 @@ #include "rl.h" #include "cmd.h" +#include "parse.h" void rl_printf(const char *fmt, ...) { // save line char* saved_line = rl_copy_text(0, rl_end); int saved_point = rl_point; int saved_end = rl_end; - + // clear line rl_save_prompt(); rl_replace_line("", 0); @@ -36,34 +37,38 @@ void rl_printf(const char *fmt, ...) { free(saved_line); } -static bool cli_cmd(char* line) { - char* cmd = strtok(line, " \t\n"); +static void cli_cmd(char* cmd) { + char* line = consume_token(cmd, IFS); + for (size_t i = 0; i < cmds_length; i++) { - if (strncmp(cmds[i].name, cmd, strlen(cmd)) != 0) continue; + if (strncmp(cmds[i].name, cmd, strlen(cmd)) != 0) + continue; + cmds[i].handle(line); - return true; + return; } - return false; + + printf("unknown command!\n"); } static char* rl_completion_entries(const char *text, int state) { - static size_t i = 0; - if (state == 0) i = 0; - - while (i < cmds_length) { - struct cmd cmd = cmds[i]; - i++; - if (strncmp(text, cmd.name, strlen(text)) == 0) { - return strdup(cmd.name); - } - } - - return NULL; + static size_t i = 0; + if (state == 0) i = 0; + + while (i < cmds_length) { + struct cmd cmd = cmds[i]; + i++; + if (strncmp(text, cmd.name, strlen(text)) == 0) { + return strdup(cmd.name); + } + } + + return NULL; } int cli_main() { char* input = NULL; - rl_completion_entry_function = rl_completion_entries; + rl_completion_entry_function = rl_completion_entries; while (1) { if (input != NULL) free(input); @@ -73,9 +78,7 @@ int cli_main() { if (*input == '\0') continue; // ignore empty lines add_history(input); - if (cli_cmd(input)) continue; - - printf("unknown command!\n"); + cli_cmd(input); } return EXIT_SUCCESS; -- cgit v1.2.3 From 5d5b186a5a82b7e2415eddd77ef93af851034a5b Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 25 May 2024 15:31:42 +0200 Subject: WIP send command parser --- client/cmd.cpp | 5 ++- client/parse.cpp | 111 ++++++++++++++++++++++++++++++++++++++++++++++++------- client/parse.h | 12 ++++-- client/readme.md | 7 ++++ 4 files changed, 116 insertions(+), 19 deletions(-) diff --git a/client/cmd.cpp b/client/cmd.cpp index 4a2c8a3..1ec2cb8 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -48,9 +48,10 @@ void cmd_send(char* addr_str) { char* data; size_t data_size; - if (strtodata(data_str, &data, &data_size)) { + int err = strtodata(data_str, &data, &data_size); + if (err <= 0) { printf("data format error at index %d:\n%s\n%*s^\n", - (int) data_size, data_str, (int) data_size, ""); + -err, data_str, -err, ""); return; } diff --git a/client/parse.cpp b/client/parse.cpp index d15207c..223dc5d 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -1,31 +1,116 @@ +#include #include #include +#include #include "parse.h" -static unsigned ifsrun(const char* input, const char* ift) { - unsigned i; - for (i = 0; input[i] != '\0' && strchr(ift, input[i]); i++); - return i; +static int parse_str(const char* str, char* data, size_t* size) { + char closing = str[0]; + char escape = false; + bool scan = data == NULL; + int i = 0; + size_t len = strlen(str); + + switch (str[i]) { + case '\'': + escape = false; + break; + case '\"': + escape = true; + break; + default: + return -i; + } + + for (i = 1; i < len && str[i] != '\0'; i++) { + char c = str[i]; + + if (c == closing) { + if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); + return i + 1; // +1 for closing quote + } + + if (scan) *size += 1; + } + + return -i; +} + +static int parse_num(const char* str, char* data, size_t* size) { + const char* ifs = IFS; + size_t len = strcspn(str, ifs); + bool scan = data == NULL; + int i = 0; + int base = 10; + bool bytestring = false; + + const char* colon = strchr(str, ':'); + if (colon != NULL && colon < str + len) { // byte string + base = 16; + bytestring = true; + } else if (len > 2 && strncmp(str, "0x", 2) == 0) { // hexadecimal prefix + base = 16; + i += 2; + }/* else if (len > 1 && strncmp(str, "0", 1) == 0) { // octal prefix + base = 8; + i += 1; + }*/ + + const char* set; + // if (base == 8) set = SET_OCT; + if (base == 10) set = SET_DEC; + if (base == 16) { + if (bytestring) set = SET_HEX_STR; + else set = SET_HEX; + } + + size_t len_ok = strspn(str + i, set) + i; + if (len != len_ok) return -len_ok; + + if (scan) { + if (base == 10) *size += 1; + else if (base == 16) { + if (!bytestring) { + *size += (len - i + 1) / 2; + } else { + for (; colon != NULL && colon < str + len; colon = strchr(str, ':')) { + *size += 1; + } + } + } + } + + if (scan) printf("number (base %d%s) of length %lu\n", base, bytestring ? " as bytestring" : "", len - i); + return len; } int strtodata(const char* str, char** data, size_t* size) { const char* ifs = IFS; *size = 0; size_t i; - size_t str_len = strlen(str); + size_t len = strlen(str); - // TODO: finish this parser - // for (i = 0; i < str_len; i++) { - // unsigned ifs_run = ifsrun(&str[i], ifs); - // - // } + for (i = 0; i < len;) { + // skip whitespace + int run; + run = strspn(&str[i], ifs); + if (run > 0) printf("skipping whitespace for %d bytes...\n", run); + i += run; + // end of string + if (str[i] == '\0') break; - *size = str_len; + if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } + if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } - *data = (char*) malloc(*size); + // no format detected + return -i + run; + } + printf("end of string w/o parse errors\n"); + printf("buffer size is now %lu\n", *size); + exit(0); - memcpy(*data, str, *size); + *data = (char*) malloc(*size); return 0; } diff --git a/client/parse.h b/client/parse.h index ac06446..10274e7 100644 --- a/client/parse.h +++ b/client/parse.h @@ -4,6 +4,11 @@ #define IFS " \t\n" +#define SET_OCT "01234567" +#define SET_DEC "0123456789" +#define SET_HEX SET_DEC"abcdefABCDEF" +#define SET_HEX_STR SET_HEX":" + /** * \brief modify \p token to point to the first token when broken up on \p ifs * and return the remaining data @@ -26,13 +31,12 @@ char* consume_token(char* token, const char* ifs); * \param data pointer to \c char* that will store the resulting data * \param size size of \p data * - * \return 0 if the string was parsed succesfully, or 1 if the string could not - * be parsed succesfully + * \return 0 or a negative integer representing the index where there is a + * syntax error if there was an error, or a positive integer representing the + * amount of bytes parsed from \p str * * \note The pointer that \p data refers to will not be initialized by this * function if parsing fails - * \note \p size will contain the index of \p str where the first invalid data - * was found if parsing fails */ int strtodata(const char* str, char** data, size_t* size); diff --git a/client/readme.md b/client/readme.md index 9d755aa..04471d2 100644 --- a/client/readme.md +++ b/client/readme.md @@ -12,3 +12,10 @@ goal (in order of implementation): ls list connected puzzle modules ``` + +``` +send 0x39 "Hello world!" de:ad:be:ef 0xff 5 0a 0750 + ^~~~~~~~~~~~~~ ^~~~~~~~~~~ ~^~~ ~^ ~^ ~~~~^ + STR_INTP BYTE_ARR UNSIGNED UNSIGNED UNSIGNED UNSIGNED + (hex+0x) (dec) (hex) (oct) +``` -- cgit v1.2.3