diff options
author | ThomasintAnker <thomasintanker1@gmail.com> | 2024-05-31 15:06:06 +0200 |
---|---|---|
committer | ThomasintAnker <thomasintanker1@gmail.com> | 2024-05-31 15:06:06 +0200 |
commit | 0278037aaf3fd497aae57d90f2638ceda3b12a6d (patch) | |
tree | 3354a14d4551b9524108d02971bfa42fc1ad4993 /client | |
parent | b865921e5dcf2ae2d6532b88eba1a0a49998eb27 (diff) | |
parent | 18d06c79b9f6a625eb218a15c8216556fb99dc02 (diff) |
Merge branch 'wip/client' into wip/i2c-communication
Diffstat (limited to 'client')
-rw-r--r-- | client/CMakeLists.txt | 12 | ||||
-rw-r--r-- | client/cmd.cpp | 82 | ||||
-rw-r--r-- | client/cmd.h | 74 | ||||
-rw-r--r-- | client/examples/puzbus-hello-world.cpp | 67 | ||||
-rw-r--r-- | client/i2c.cpp | 67 | ||||
-rw-r--r-- | client/i2c.h | 8 | ||||
-rw-r--r-- | client/parse.cpp | 169 | ||||
-rw-r--r-- | client/parse.h | 4 | ||||
-rw-r--r-- | client/pbc.1 | 94 | ||||
-rw-r--r-- | client/readme.md | 56 | ||||
-rw-r--r-- | client/rl.cpp | 37 | ||||
-rw-r--r-- | client/rl.h | 3 | ||||
-rw-r--r-- | client/sock.cpp | 29 | ||||
-rw-r--r-- | client/sock.h | 3 | ||||
-rw-r--r-- | client/xxd.c | 44 | ||||
-rw-r--r-- | client/xxd.h | 17 |
16 files changed, 565 insertions, 201 deletions
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 35a55b6..d838266 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -4,9 +4,14 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +# enable debug features +set(CMAKE_BUILD_TYPE Debug) +add_compile_definitions(DEBUG) + project(puzzlebox_client C CXX) -include(../proto/include.cmake) +include(../i2ctcp/include.cmake) +include(../shared/include.cmake) add_executable(pbc main.cpp @@ -14,12 +19,13 @@ add_executable(pbc sock.cpp cmd.cpp parse.cpp + xxd.c + i2c.cpp ) target_link_libraries(pbc - puzbus + i2ctcp 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 index 1ec2cb8..5ac2ff3 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -1,11 +1,16 @@ #include <cstdio> #include <cstdlib> +#include <readline/readline.h> #include <string.h> #include "cmd.h" -#include "sock.h" +#include "pb/types.h" +#include "rl.h" +#include "i2c.h" #include "parse.h" +#include "pb/bus.h" + char* consume_token(char* input, const char* ifs) { strtok(input, ifs); return strtok(NULL, "\0"); @@ -32,11 +37,12 @@ void cmd_help(char*) { printf( "\n" - "You can also use the TAB key to autocomplete commands\n" + "See man pbc(1) for more info about specific commands\n" + "Hint: you can use the TAB key to autocomplete commands\n" ); } -void cmd_send(char* addr_str) { +void cmd_send(char * addr_str) { char* data_str = consume_token(addr_str, IFS); char* end; @@ -55,9 +61,77 @@ void cmd_send(char* addr_str) { return; } - // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); + printf("sending char data[%lu = 0x%02lx] to 0x%02x\n", data_size, data_size, addr); i2c_send(addr, data, data_size); free(data); } +void cmd_reset(char*) { + const char msg[] = { + PB_CMD_WRITE, + 0x00, + PB_GS_IDLE, + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} + +void cmd_skip(char*) { + const char msg[] = { + PB_CMD_WRITE, + 0x00, + PB_GS_SOLVED, + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} + +void cmd_ls(char*) { + return; + const char msg[] = { + PB_CMD_READ, + // TODO: which address is this? + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} + +extern bool i2c_dump_send; +extern bool i2c_dump_recv; +const char * dump_modes[] = { + "none", + "send", + "recv", + "both", + NULL, +}; +void cmd_dump(char * mode) { + consume_token(mode, IFS); + mode += strspn(mode, IFS); + + for (int i = 0; dump_modes[i] != NULL; i++) { + if (strcmp(mode, dump_modes[i]) == 0) { + i2c_dump_send = (i >> 0) & 1; + i2c_dump_recv = (i >> 1) & 1; + return; + } + } + + printf("mode \"%s\" unknown\n", mode); +} +char** cmd_dump_complete(const char * text, int begin, int end) { + int word = rl_word(rl_line_buffer, begin); + if (word != 1) return NULL; + + return rl_completion_matches(text, [](const char * text, int state) -> char * { + static size_t i = 0; + if (state == 0) i = 0; + + while (dump_modes[i] != NULL) { + const char * mode = dump_modes[i++]; + if (strncmp(text, mode, strlen(text)) == 0) + return strdup(mode); + } + return NULL; + }); + + return NULL; +} diff --git a/client/cmd.h b/client/cmd.h index 9d20328..961ef89 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -2,59 +2,71 @@ #include <stddef.h> -typedef void cmd_fn_t(char *); +typedef void cmd_handle_t(char *); +typedef char** cmd_complete_t(const char*, int, int); struct cmd { - void (* handle)(char *); + cmd_handle_t * handle; const char* name; const char* info; - // TODO: tab completion function? + cmd_complete_t * complete; }; +typedef struct cmd cmd_t; -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; +cmd_handle_t cmd_exit; +cmd_handle_t cmd_test; +cmd_handle_t cmd_help; +cmd_handle_t cmd_reset; +cmd_handle_t cmd_ls; +cmd_handle_t cmd_send; +cmd_handle_t cmd_skip; +cmd_handle_t cmd_dump; +cmd_complete_t cmd_dump_complete; -static const struct cmd cmds[] = { +static const cmd_t cmds[] = { { .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_reset, + .name = "reset", + .info = "set game state to 'idle' for one or more puzzle modules", + }, + { + .handle = cmd_skip, + .name = "skip", + .info = "set game state to 'solved' for one or more puzzle modules", + }, + { + .handle = cmd_ls, + .name = "ls", + .info = "list connected puzzle modules and their state", + }, +#ifdef DEBUG + { .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", - // }, + { + .handle = cmd_test, + .name = "test", + .info = "[debug] send a test puzbus message", + }, + { + .handle = cmd_dump, + .name = "dump", + .info = "[debug] dump sent or received messages", + .complete = cmd_dump_complete, + }, +#endif }; static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/examples/puzbus-hello-world.cpp b/client/examples/puzbus-hello-world.cpp deleted file mode 100644 index dcc965b..0000000 --- a/client/examples/puzbus-hello-world.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <unistd.h> - -#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/i2c.cpp b/client/i2c.cpp new file mode 100644 index 0000000..ee57e20 --- /dev/null +++ b/client/i2c.cpp @@ -0,0 +1,67 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "i2ctcpv1.h" +#include "sock.h" +#include "xxd.h" + +#include "pb/bus.h" +#include "pb/types.h" + +#include "pb/mod/main.h" + +bool i2c_dump_send = false; +bool i2c_dump_recv = true; + +void i2c_send(uint16_t addr, const char * data, size_t data_size) { + i2ctcp_msg_t msg = { + .addr = addr, + .data = (char *) data, + .length = data_size, + }; + + char* packed; + size_t size; + if (!i2ctcp_write(&msg, &packed, &size)) return; + + sock->send(packed, size); + if (i2c_dump_send) { + printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); + xxd(data, data_size); + } + + free(packed); +} + +static void i2c_handle_cmd_read(uint16_t, const char *, size_t); + +void i2c_recv(uint16_t addr, const char * data, size_t data_size) { + if (i2c_dump_recv) { + printf("[%s] addr(0x%02x) data(0x%02lx):\n", __FUNCTION__, addr, data_size); + xxd(data, data_size); + } + + if (data_size == 0) return; + enum pb_cmd cmd = (enum pb_cmd) data[0]; + data++; data_size--; + + switch (cmd) { + case PB_CMD_READ: return i2c_handle_cmd_read(addr, data, data_size); + default: return; + } +} + +static void i2c_handle_cmd_read(uint16_t i2c_addr, const char * buf, size_t sz) { + if (sz < 2) return; // require data address + 1 byte of data + pb_cmd_read_t * cmd = (pb_cmd_read_t *) buf; + sz--; // sz now represents size of cmd->data + + if (i2c_addr == BUSADDR_MAIN && cmd->address == 0x01) { + if (sz % 2 != 0) return; // invalid data + for (size_t offset = 0; offset < sz; offset += sizeof(pb_mod_main_mod_t)) { + pb_mod_main_mod_t * mod = (pb_mod_main_mod_t *) (cmd->data + offset); + printf("module at addr 0x%02x with state %d\n", mod->addr, mod->state); + } + } +} + diff --git a/client/i2c.h b/client/i2c.h new file mode 100644 index 0000000..f9f58f9 --- /dev/null +++ b/client/i2c.h @@ -0,0 +1,8 @@ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +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/client/parse.cpp b/client/parse.cpp index 223dc5d..16f0781 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -1,14 +1,12 @@ -#include <math.h> #include <stdlib.h> #include <string.h> #include <stdio.h> +#include <netinet/in.h> #include "parse.h" -static int parse_str(const char* str, char* data, size_t* size) { - char closing = str[0]; +static int parse_string(const char * str, char * data, size_t * offset) { char escape = false; - bool scan = data == NULL; int i = 0; size_t len = strlen(str); @@ -22,34 +20,76 @@ static int parse_str(const char* str, char* data, size_t* size) { default: return -i; } + char closing = str[i]; - for (i = 1; i < len && str[i] != '\0'; i++) { + for (i = 1; i < len && str[i] != '\0'; i++, *offset += 1) { char c = str[i]; - if (c == closing) { - if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); + if (c == closing) return i + 1; // +1 for closing quote + + if (escape && c == '\\') { + char x = str[i + 1]; + if (x == '0') c = '\0'; + else if (x == 't') c = '\t'; + else if (x == 'n') c = '\n'; + else if (x == 'r') c = '\r'; + else if (x == '\\') c = '\\'; + else if (x == '\"') c = '\"'; + else if (x == '\'') c = '\''; + else break; + i++; } - if (scan) *size += 1; + if (data != NULL) + data[*offset] = c; } 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; +static int parse_hexstr(const char * str, char * data, size_t * offset) { + size_t len = strcspn(str, IFS); + int i = 0; + + // check if token contains at least one colon + const char* colon = strchr(str, ':'); + if (colon == NULL) return -i; + if (colon >= str + len) return -i; + + // check if token only contains allowed characters [0-9a-fA-F:] + size_t len_ok = strspn(str + i, SET_HEX_STR) + i; + if (len != len_ok) return -len_ok; + + size_t c = 0; + while (c < len) { // count bytes in bytestring + if (strspn(str + c, SET_HEX) != 2) + return -i -c; + + if (data != NULL) + data[*offset] = strtol(str + c, NULL, 16) & 0xff; + + c += 2; + *offset += 1; + + if (str[c] == ':') { + c += 1; + continue; + } + break; + } + + i += len; + return i; +} + +static int parse_number(const char * str, char * data, size_t * offset) { + size_t len = strcspn(str, IFS); 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 + 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 @@ -60,58 +100,85 @@ static int parse_num(const char* str, char* data, size_t* size) { 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; - } + if (base == 16) 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; - } - } + size_t size = 1; // default integer size in bytes + if (base == 16) { + size_t prefixless = len - i; + switch (prefixless) { + case 2: // 8-bit (2 hex characters) + case 4: // 16-bit + case 8: // 32-bit + case 16: // 64-bit + break; + default: + return -i; + } + size = prefixless / 2; + } + + if (data != NULL) { + unsigned long number = strtol(str + i, NULL, base); + long long mask = (1 << 8 * size) - 1; + number &= mask; + // NOTE: the hton? functions are used to convert host endianness to network + // endianness (big), and are required + switch (size) { + case 1: + data[*offset] = number & 0xff; + break; + case 2: + number = htons(number); + // TODO: check if the endianness is OK, or reverse these *offset indices* + data[*offset + 1] = (number) & 0xff; + data[*offset + 0] = (number >>= 8) & 0xff; + break; + case 4: + number = htonl(number); + data[*offset + 3] = (number) & 0xff; + data[*offset + 2] = (number >>= 8) & 0xff; + data[*offset + 1] = (number >>= 8) & 0xff; + data[*offset + 0] = (number >>= 8) & 0xff; + break; } } - if (scan) printf("number (base %d%s) of length %lu\n", base, bytestring ? " as bytestring" : "", len - i); + *offset += size; return len; } -int strtodata(const char* str, char** data, size_t* size) { - const char* ifs = IFS; - *size = 0; - size_t i; +static int _strtodata_main(const char * str, char* data, size_t * offset) { size_t len = strlen(str); - 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; + int i, run; + for (i = 0; i < len; i += run) { + i += strspn(&str[i], IFS); // skip whitespace + if (str[i] == '\0') break; // end of string - if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } - if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } + if ((run = parse_string(str + i, data, offset)) > 0) continue; + if ((run = parse_hexstr(str + i, data, offset)) > 0) continue; + if ((run = parse_number(str + i, data, offset)) > 0) continue; // 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); - *data = (char*) malloc(*size); + return i; +} - return 0; +int strtodata(const char * str, char ** data, size_t * size) { + *size = 0; + + // 1st pass: check data format + int ret = _strtodata_main(str, NULL, size); + if (ret <= 0) return ret; // on error + + // 2nd pass: convert string literals into binary data + *data = (char*) malloc(*size); + size_t written = 0; + return _strtodata_main(str, *data, &written); } diff --git a/client/parse.h b/client/parse.h index 10274e7..94afe70 100644 --- a/client/parse.h +++ b/client/parse.h @@ -22,7 +22,7 @@ * * \return the remaining data after \p token and the first \p ifs */ -char* consume_token(char* token, const char* ifs); +char* consume_token(char * token, const char * ifs); /** * \brief convert string with literals into raw data @@ -38,5 +38,5 @@ char* consume_token(char* token, const char* ifs); * \note The pointer that \p data refers to will not be initialized by this * function if parsing fails */ -int strtodata(const char* str, char** data, size_t* size); +int strtodata(const char * str, char ** data, size_t * size); diff --git a/client/pbc.1 b/client/pbc.1 new file mode 100644 index 0000000..f5a2198 --- /dev/null +++ b/client/pbc.1 @@ -0,0 +1,94 @@ +\# vim: ft=groff +.de I2C +I\*{2\*}C +.. +.TH pbc 1 +.SH NAME +pbc \- puzzle box client +.SH SYNPOSIS +pbc <addr> [port] +.SH DESCRIPTION +Connect to a puzzle box at the IPv4 address specified by \fIaddr\fP and +optionally port specified by \fIport\fP. The default port is 9191. Once +connected, a +.MR readline 3 -based +CLI is started, and commands can be sent. +.SH COMMANDS +.TP +exit +Disconnect from the puzzle box and exit pbc. This command takes no arguments. +.TP +help +Print a list of available commands with descriptions. This command takes no +arguments. +.TP +ls +List all puzzle modules, their state, and the combined state of all puzzle +modules (global state of the main controller). +.TP +reset [mod ...] +Set the main controller or specific puzzle module's global state to \fIidle\fP. +If no modules are specified, the main controller's state is updated. One or +more modules can be specified to update them at once. +.TP +skip [mod ...] +Set the main controller or specific puzzle module's global state to +\fIsolved\fP. If no modules are specified, the main controller's state is +updated. One or more modules can be specified to update them at once. +.SH DEBUG COMMANDS +The commands detailed under this section are only available in version of pbc +compiled with debug support. +.TP +send <addr> <data> +Send arbitrary data specified by \fIdata\fP to the +.I2C +address specified by \fIaddr\fP. \fIdata\fP may consist of multiple arguments +separated by IFS, in which case the arguments are concatenated. +.TP +test +Send a test command containing the ASCII string "Hello world!" to +.I2C +address 0x39. This command takes no arguments. +.SH DATA FORMATS +.TP +number +Numbers can be specified as decimal or hexadecimal using a "0x" prefix. All +numbers are unsigned. Decimal literals are always cast to 8-bit integers, while +hexadecimal literals are cast to the smallest type that will fit the specified +number. Numbers are always sent as little endian. + +Examples: 0 123 255 0x10 0x1245 0xdeadBEEF +.TP +hexstr +Hexadecimal string literals are specified by hexadecimal bytes separated by +colons. Each byte must be exactly 2 hexadecimal characters long and followed by +a colon (except for the last byte). The minimum length of a hexstr is 2 bytes, +as it must include at least a single colon. + +Examples: de:ad:be:ef 00:00 +.TP +string +A string literal starts and ends with a single quote. All characters within +this literal are sent as-is, and no escaping is possible. + +Examples: 'Hello world!' 'string' ' hello ' + +When double quotes are used instead of single quotes, the following escape +sequences are recognised and replaced with special characters: + +\\0 -> 0x00 (null) +.br +\\t -> 0x09 (tab) +.br +\\n -> 0x0a (newline) +.br +\\r -> 0x0d (carriage return) +.br +\\\\ -> 0x5c (backslash) +.br +\\" -> 0x22 (double quote) +.br +\\' -> 0x27 (single quote) + +Examples: "Hello world!\\0" "foo\\nbar" + diff --git a/client/readme.md b/client/readme.md index 04471d2..da48cf1 100644 --- a/client/readme.md +++ b/client/readme.md @@ -1,21 +1,47 @@ # puzzle box client -goal (in order of implementation): -``` -(pbc) help - exit exit pbc - test send a test puzbus message - help show this help - send <addr> <data> [debug] send raw message - status show global puzzle box state (main controller state) - reset reset entire game state - ls list connected puzzle modules -``` +This folder contains the source code for the puzzle box client (pbc). This is a +desktop application that communicates with the main controller over TCP to +send/receive I<sup>2</sup>C messages. This application is not only used by a +game operator to control and monitor the state of a puzzle box, but is also a +useful debugging tool when developing puzzle modules, as it allows you to send +arbitrary data over the puzzle bus. + +## WIP TODO + +- cleanup + - separate ../shared/pb/moddrv.c into a puzzle module specific and 'common' bit + - use the common bit in i2c.cpp instead + - cast to structs in ../shared/pb/moddrv.c +- functionality + - print pretty tree of connected puzzle modules + - add enum to string functions in CLIENT ONLY + +## Features + +- List detected puzzle modules +- Reset puzzle modules (individually or all to reset the box) +- Skip puzzle modules (individually or all) +- Request puzzle box state +Debug only: +- Send arbitrary messages + +## Building + +PBC is a standard CMake project, but a [makefile](./makefile) is provided for +convenience (still requires CMake and Ninja are installed). + +## Send data ``` -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) + ADDRESS DATA + v~~~ v~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +send 0x39 68:65:6c:6c:6f 44 0x20 'world' 33 + ^~~~~~~~~~~~~~ ^~ ^~~~ ^~~~~~~ ^~ + HEXSTR NUMBER NUMBER STRING NUMBER + (binary) (dec) (hex) (literal) (dec) ``` + +The data is concatenated, and may contain mixed types of literals + diff --git a/client/rl.cpp b/client/rl.cpp index 3f93e99..b8113aa 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -38,6 +38,7 @@ void rl_printf(const char *fmt, ...) { } static void cli_cmd(char* cmd) { + cmd += strspn(cmd, IFS); // skip leading whitespace char* line = consume_token(cmd, IFS); for (size_t i = 0; i < cmds_length; i++) { @@ -66,9 +67,31 @@ static char* rl_completion_entries(const char *text, int state) { return NULL; } +static char** rl_attempted_completion(const char * text, int start, int end) { + // do not suggest filenames + rl_attempted_completion_over = 1; + + // if first word in line buffer -> complete commands from cmds[] + size_t cmd_start = strspn(rl_line_buffer, IFS); + if (start == cmd_start) + return rl_completion_matches(text, rl_completion_entries); + + // else, check specialized completion functions + size_t cmd_len = strcspn(rl_line_buffer + cmd_start, IFS); + for (size_t i = 0; i < cmds_length; i++) { + cmd_t cmd = cmds[i]; + if (cmd.complete == NULL) continue; + if (strncmp(cmd.name, rl_line_buffer + cmd_start, cmd_len) != 0) continue; + return cmd.complete(text, start, end); + } + + // else, no completion available + return NULL; +} + int cli_main() { char* input = NULL; - rl_completion_entry_function = rl_completion_entries; + rl_attempted_completion_function = rl_attempted_completion; while (1) { if (input != NULL) free(input); @@ -84,3 +107,15 @@ int cli_main() { return EXIT_SUCCESS; } +int rl_word(const char * line, int cursor) { + int word = -1; + for (int i = 0; line[i] != '\0';) { + i += strspn(line + i, IFS); + int len = strcspn(line + i, IFS); + word++; + i += len; + if (i > cursor) break; + } + return word; +} + diff --git a/client/rl.h b/client/rl.h index 503225f..c3bf2c7 100644 --- a/client/rl.h +++ b/client/rl.h @@ -6,5 +6,6 @@ #define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " int cli_main(); -void rl_printf(const char *fmt, ...); +void rl_printf(const char * fmt, ...); +int rl_word(const char * line, int cursor); diff --git a/client/sock.cpp b/client/sock.cpp index f967f64..95a3685 100644 --- a/client/sock.cpp +++ b/client/sock.cpp @@ -7,12 +7,12 @@ #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> - #include <thread> -#include "puzbusv1.h" +#include "i2ctcpv1.h" #include "sock.h" #include "rl.h" +#include "i2c.h" using std::logic_error; using std::thread; @@ -72,7 +72,8 @@ void PBSocket::send(const char * buf, size_t buf_sz) { } void PBSocket::sock_task() { - struct pb_msg input; + i2ctcp_msg_t input; + i2ctcp_read_reset(&input); while(1) { char buf[80]; @@ -86,11 +87,11 @@ void PBSocket::sock_task() { // skip empty frames if (bytes == 0) continue; - int ret = pb_read(&input, buf, bytes); + int ret = i2ctcp_read(&input, buf, bytes); // header read error if (ret < 0) { - rl_printf("pb_read error!\n"); + rl_printf("i2ctcp_read error!\n"); break; } @@ -105,21 +106,3 @@ void PBSocket::sock_task() { sock_close(); } -void i2c_send(uint16_t addr, const char * data, size_t data_size) { - struct pb_msg msg = { - .addr = addr, - .data = (char *) 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, 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 42eba3b..0dee09e 100644 --- a/client/sock.h +++ b/client/sock.h @@ -29,6 +29,3 @@ private: extern PBSocket* sock; -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/client/xxd.c b/client/xxd.c new file mode 100644 index 0000000..5d83635 --- /dev/null +++ b/client/xxd.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <ctype.h> + +#include "xxd.h" + +void xxd(const char * data, size_t size) { + size_t fake_size = size + (16 - size % 16) % 16; + + for (size_t base = 0; base < fake_size; base += 16) { + printf("%08lx: ", base); + + // print bytes + for (size_t offset = 0; offset < 16; offset++) { + size_t i = base + offset; + + if (offset == 8) printf(" "); + + if (i >= size) { + printf(" "); + continue; + } + + printf("%02x ", data[i] & 0xff); + } + + // print ascii representation + printf(" |"); + for (size_t offset = 0; offset < 16; offset++) { + size_t i = base + offset; + + if (i >= size) { + printf(" "); + continue; + } + + if (isprint(data[i])) + printf("%c", data[i]); + else + printf("."); + } + printf("|\n"); + } +} + diff --git a/client/xxd.h b/client/xxd.h new file mode 100644 index 0000000..fb28bb1 --- /dev/null +++ b/client/xxd.h @@ -0,0 +1,17 @@ +#pragma once + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief utility function that prints hexdump of data + */ +void xxd(const char * data, size_t size); + +#ifdef __cplusplus +} +#endif + |