diff --git a/Makefile b/Makefile index c6e1433..7e3b7a2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ Q=@ CC=gcc -CFLAGS=-g -Wall -Wno-format-truncation -pthread -DJSFW_DEV -lm +CFLAGS=-g -Wall -Wno-format-truncation -pthread -lm LDFLAGS= BUILD_DIR=./objects BIN=jsfw diff --git a/README.md b/README.md index 7b91e9d..1302a31 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # jsfw -Linux only utility to forward uevent devices over network through a tcp connection. +Utility to forward uevent devices over network through a tcp connection. # Usage @@ -18,7 +18,7 @@ jsfw server [port] When a device is connected to the server host, jsfw will notice it and assign it to one of the client which will in turn create a virtual device based on it. -The code can theoretically support any kind of device (mouse, keyboard, joystick...) but is artificially limited to PS4 controllers (see `hid.c::filter_event`), because the hidraw interface used to set additional device state (led color, flashing, rumble) only works with them. This could be easily edited tho (see `hid.c::apply_controller_state`, `net.h::MessageControllerState`, `net.c::{msg_serialize, msg_deserialize}` and `client.c::JControllerState`). To set the controller state from the client write the json state to the fifo (either `/tmp/jsfw_fifo` or `/run/jsfw_fifo` depending on if `JSFW_DEV` was set during compilation, see `Makefile`). +The code can theoretically support any kind of device (mouse, keyboard, joystick...) but is artificially limited to PS4 controllers (see `hid.c::filter_event`), because the hidraw interface used to set additional device state (led color, flashing, rumble) only works with them. This could be easily edited tho (see `hid.c::apply_controller_state`, `net.h::MessageControllerState`, `net.c::{msg_serialize, msg_deserialize}` and `client.c::JControllerState`). To set the controller state from the client write the json state to the fifo (by default `/tmp/jsfw_fifo`). The format for the controller state takes this form (comments not allowed): @@ -32,6 +32,8 @@ The format for the controller state takes this form (comments not allowed): Any value can be ommitted, extra values will be ignored. +Some aspect are easily configurable through `const.c`. + # Building ## Dependencies diff --git a/client.c b/client.c index 6e79980..c92080f 100644 --- a/client.c +++ b/client.c @@ -1,5 +1,6 @@ #include "client.h" +#include "const.h" #include "json.h" #include "net.h" #include "util.h" @@ -24,24 +25,13 @@ #include #include +// The current device. +// The fd being -1 means there is none typedef struct { int fd; MessageDeviceInfo info; } VirtualDevice; -#ifdef JSFW_DEV -// Path for dev environment (no root) -const char *FIFO_PATH = "/tmp/jsfw_fifo"; -#else -const char *FIFO_PATH = "/run/jsfw_fifo"; -#endif -const int CONN_RETRY_DELAY = 5; -// Constant for the virtual device -const uint16_t VIRT_VENDOR = 0x6969; -const uint16_t VIRT_PRODUCT = 0x0420; -const uint16_t VIRT_VERSION = 1; -const char *VIRT_NAME = "JSFW Virtual Device"; - static int fifo_attempt = 0; static struct sockaddr_in server_addr = {}; @@ -57,8 +47,10 @@ static int sock = -1; static Message message; static VirtualDevice device = {}; +// Test if the device exists static inline bool device_exists() { return device.fd > 0; } +// Struct representing the received json typedef struct { char *led_color; double rumble_small; @@ -75,6 +67,7 @@ static const JSONAdapter JControllerStateAdapter[] = { {".flash.1", Number, offsetof(JControllerState, flash_off)}, }; +// Try to destroy the device void device_destroy() { if (!device_exists()) { return; @@ -86,6 +79,7 @@ void device_destroy() { printf("CLIENT: Destroyed device\n"); } +// (Re)Initialize the device void device_init(MessageDeviceInfo *dev) { device_destroy(); @@ -95,6 +89,8 @@ void device_init(MessageDeviceInfo *dev) { exit(1); } + // Setup device_info + // Abs if (dev->abs_count > 0) { ioctl(fd, UI_SET_EVBIT, EV_ABS); @@ -130,10 +126,10 @@ void device_init(MessageDeviceInfo *dev) { struct uinput_setup setup = {}; setup.id.bustype = BUS_VIRTUAL; - setup.id.vendor = VIRT_VENDOR; - setup.id.product = VIRT_PRODUCT; - setup.id.version = VIRT_VERSION; - strncpy(setup.name, VIRT_NAME, UINPUT_MAX_NAME_SIZE); + setup.id.vendor = VIRTUAL_DEVICE_VENDOR; + setup.id.product = VIRTUAL_DEVICE_PRODUCT; + setup.id.version = VIRTUAL_DEVICE_VERSION; + strncpy(setup.name, VIRTUAL_DEVICE_NAME, UINPUT_MAX_NAME_SIZE); ioctl(fd, UI_DEV_SETUP, &setup); ioctl(fd, UI_DEV_CREATE); @@ -144,6 +140,7 @@ void device_init(MessageDeviceInfo *dev) { dev->key_count); } +// Send an event to uinput, device must exist int device_emit(uint16_t type, uint16_t id, uint32_t value) { struct input_event event = {}; @@ -154,9 +151,10 @@ int device_emit(uint16_t type, uint16_t id, uint32_t value) { return write(device.fd, &event, sizeof(event)) != sizeof(event); } +// Update device with report void device_handle_report(MessageDeviceReport *report) { if (!device_exists()) { - printf("CLIENT: Got report but device info\n"); + printf("CLIENT: Got report before device info\n"); return; } @@ -167,25 +165,30 @@ void device_handle_report(MessageDeviceReport *report) { } for (int i = 0; i < report->abs_count; i++) { - if (device_emit(EV_ABS, device.info.abs_id[i], report->abs[i]) != 0) + if (device_emit(EV_ABS, device.info.abs_id[i], report->abs[i]) != 0) { printf("CLIENT: Error writing abs event to uinput\n"); + } } for (int i = 0; i < report->rel_count; i++) { - if (device_emit(EV_REL, device.info.rel_id[i], report->rel[i]) != 0) + if (device_emit(EV_REL, device.info.rel_id[i], report->rel[i]) != 0) { printf("CLIENT: Error writing rel event to uinput\n"); + } } for (int i = 0; i < report->key_count; i++) { - if (device_emit(EV_KEY, device.info.key_id[i], (uint32_t)(!report->key[i]) - 1) != 0) + if (device_emit(EV_KEY, device.info.key_id[i], (uint32_t)(!report->key[i]) - 1) != 0) { printf("CLIENT: Error writing key event to uinput\n"); + } } - + // Reports are sent by the server every time the server receives an EV_SYN from the physical device, so we + // send one when we receive the report to match device_emit(EV_SYN, 0, 0); } void setup_fifo(); +// (Re)Open the fifo void open_fifo() { close(fifo); fifo = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); @@ -196,8 +199,10 @@ void open_fifo() { } else if (fifo < 0) { panicf("CLIENT: Couldn't open fifo, aborting\n"); } + fifo_attempt = 0; } +// Ensure the fifo exists and opens it (also setup poll_fd) void setup_fifo() { mode_t prev = umask(0); mkfifo(FIFO_PATH, 0666); @@ -209,27 +214,31 @@ void setup_fifo() { fifo_poll->events = POLLIN; } +// (Re)Connect to the server void connect_server() { while (1) { if (sock > 0) { + // Close previous connection device_destroy(); shutdown(sock, SHUT_RDWR); close(sock); } sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) + if (sock < 0) { panicf("Couldn't create socket\n"); + } if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) { printf("CLIENT: Couldn't connect to %s:%d, retrying in %ds\n", server_addrp, server_port, - CONN_RETRY_DELAY); + CONNECTION_RETRY_DELAY); struct timespec ts = {}; - ts.tv_sec = CONN_RETRY_DELAY; + ts.tv_sec = CONNECTION_RETRY_DELAY; nanosleep(&ts, NULL); continue; } - // Set non blocking + // Set non blocking, only do that after connection (instead of with SOCK_NONBLOCK at socket creation) + // because we want to block on the connection itself fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); socket_poll->fd = sock; printf("CLIENT: Connected !\n"); @@ -237,12 +246,16 @@ void connect_server() { } } +// Setup server address and connects to it (+ setup poll_fd) void setup_server(char *address, uint16_t port) { // setup address server_addr.sin_family = AF_INET; - if (inet_pton(AF_INET, address, &server_addr.sin_addr) == 0) + + if (inet_pton(AF_INET, address, &server_addr.sin_addr) == 0) { printf("CLIENT: failed to parse address '%s', defaulting to 0.0.0.0 (localhost)\n", address); + } inet_ntop(AF_INET, &server_addr.sin_addr, server_addrp, 64); + server_port = port; server_addr.sin_port = htons(port); @@ -260,17 +273,6 @@ void early_checks() { close(fd); } -static uint8_t parse_hex_digit(char h) { - if (h >= '0' && h <= '9') - return h - '0'; - else if (h >= 'a' && h <= 'f') - return h - 'a' + 10; - else if (h >= 'A' && h <= 'F') - return h - 'A' + 10; - else - return 0; -} - void client_run(char *address, uint16_t port) { // Device doesn't exist yet device.fd = -1; @@ -281,6 +283,7 @@ void client_run(char *address, uint16_t port) { uint8_t buf[2048] __attribute__((aligned(4))); uint8_t json_buf[2048] __attribute__((aligned(8))); + while (1) { int rc = poll(poll_fds, 2, -1); if (rc < 0) { @@ -297,7 +300,7 @@ void client_run(char *address, uint16_t port) { int rc = json_parse((char *)buf, len, json_buf, 2048); if (rc < 0) { printf("CLIENT: Error when parsing fifo message as json (%s at index %lu)\n", - json_strerr(), json_err_loc()); + json_strerr(), json_errloc()); } else { JControllerState state; // default values @@ -306,8 +309,10 @@ void client_run(char *address, uint16_t port) { state.led_color = NULL; state.rumble_small = 0.0; state.rumble_big = 0.0; + json_adapt(json_buf, (JSONAdapter *)JControllerStateAdapter, sizeof(JControllerStateAdapter) / sizeof(JSONAdapter), &state); + MessageControllerState msg; msg.code = ControllerState; msg.small_rumble = (uint8_t)(fmax(fmin(1.0, state.rumble_small), 0.0) * 255.0); @@ -357,34 +362,35 @@ void client_run(char *address, uint16_t port) { shutdown(sock, SHUT_RDWR); connect_server(); // we can continue here because there's nothing after, unlike above for fifo (this reduces - // indentation) + // indentation instead of needing an else block) continue; } // We've got data from the server if (msg_deserialize(buf, len, &message) != 0) { printf("CLIENT: Couldn't parse message (code: %d, len: %d)\n", buf[0], len); + int l = len > 100 ? 100 : len; for (int i = 0; i < l; i++) { printf("%02x", buf[i]); } + if (len > 100) { printf(" ... %d more bytes", len - 100); } + printf("\n"); continue; } if (message.code == DeviceInfo) { - - if (device_exists()) + if (device_exists()) { printf("CLIENT: Got more than one device info\n"); + } + device_init((MessageDeviceInfo *)&message); - } else if (message.code == DeviceReport) { - device_handle_report((MessageDeviceReport *)&message); - } else { printf("CLIENT: Illegal message\n"); } diff --git a/const.c b/const.c new file mode 100644 index 0000000..1b87916 --- /dev/null +++ b/const.c @@ -0,0 +1,29 @@ +#include "const.h" + +#include +#include + +// How long between each device poll +const struct timespec POLL_DEVICE_INTERVAL = {.tv_sec = 1, .tv_nsec = 0}; +// Default name for physical device, only visible in logs +const char *DEVICE_DEFAULT_NAME = "Unnamed Device"; +// Path to the fifo +const char *FIFO_PATH = "/tmp/jsfw_fifo"; +// Delay (in seconds) between each connection retry for the client +const uint32_t CONNECTION_RETRY_DELAY = 5; +// Displayed bendor for the virtual device +const uint16_t VIRTUAL_DEVICE_VENDOR = 0x6969; +// Displayed product for the virtual device +const uint16_t VIRTUAL_DEVICE_PRODUCT = 0x0420; +// Displayed version for the virtual device +const uint16_t VIRTUAL_DEVICE_VERSION = 1; +// Displayed name for the virtual device +const char *VIRTUAL_DEVICE_NAME = "JSFW Virtual Device"; +// Wether to enable keepalive on the tcp spcket +const int TCP_KEEPALIVE_ENABLE = 1; +// How much idle time before sending probe packets +const int TCP_KEEPALIVE_IDLE_TIME = 10; +// How many probes before giving up +const int TCP_KEEPALIVE_RETRY_COUNT = 5; +// How long (in seconds) between each probes +const int TCP_KEEPALIVE_RETRY_INTERVAL = 2; diff --git a/const.h b/const.h new file mode 100644 index 0000000..260d5a8 --- /dev/null +++ b/const.h @@ -0,0 +1,20 @@ +// vi:ft=c +#ifndef CONST_H_ +#define CONST_H_ +#include +#include + +extern const struct timespec POLL_DEVICE_INTERVAL; +extern const char *DEVICE_DEFAULT_NAME; +extern const char *FIFO_PATH; +extern const uint32_t CONNECTION_RETRY_DELAY; +extern const uint16_t VIRTUAL_DEVICE_VENDOR; +extern const uint16_t VIRTUAL_DEVICE_PRODUCT; +extern const uint16_t VIRTUAL_DEVICE_VERSION; +extern const char *VIRTUAL_DEVICE_NAME; +extern const int TCP_KEEPALIVE_ENABLE; +extern const int TCP_KEEPALIVE_IDLE_TIME; +extern const int TCP_KEEPALIVE_RETRY_COUNT; +extern const int TCP_KEEPALIVE_RETRY_INTERVAL; + +#endif diff --git a/hid.c b/hid.c index 0977c67..92f2c4a 100644 --- a/hid.c +++ b/hid.c @@ -1,5 +1,7 @@ #include "hid.h" +#include "const.h" +#include "util.h" #include "vec.h" #include @@ -28,8 +30,6 @@ static pthread_cond_t devices_queue_cond = PTHREAD_COND_INITIALIZER; // Mutex for devices static pthread_mutex_t devices_mutex = PTHREAD_MUTEX_INITIALIZER; -static char *DEFAULT_NAME = "Unnamed Device"; - // uniqs are just hexadecimal numbers with colons in between each byte uniq_t parse_uniq(char uniq[17]) { uniq_t res = 0; @@ -50,8 +50,7 @@ uniq_t parse_uniq(char uniq[17]) { return res; } -static inline bool bit_set(uint8_t *bits, int i) { return bits[i / 8] & (1 << (i % 8)); } - +// Finish setup of a partially initialized device (set device_info and mapping) void setup_device(PhysicalDevice *dev) { dev->device_info.code = DeviceInfo; dev->device_info.abs_count = 0; @@ -65,74 +64,98 @@ void setup_device(PhysicalDevice *dev) { for (int i = 0; i < KEY_CNT; i++) dev->mapping.key_indices[i] = -1; - uint8_t bits[EV_MAX] = {}; + uint8_t type_bits[EV_MAX] = {}; uint8_t feat_bits[(KEY_MAX + 7) / 8] = {}; - ioctl(dev->event, EVIOCGBIT(0, EV_MAX), bits); - for (int i = 0; i < EV_MAX; i++) { - if (bit_set(bits, i)) { - memset(feat_bits, 0, sizeof(feat_bits)); - ioctl(dev->event, EVIOCGBIT(i, KEY_MAX), feat_bits); - for (int j = 0; j < KEY_MAX; j++) { - if (bit_set(feat_bits, j)) { - if (i == EV_ABS) { - struct input_absinfo abs; - ioctl(dev->event, EVIOCGABS(j), &abs); - uint16_t index = dev->device_info.abs_count++; - dev->device_info.abs_id[index] = j; - dev->device_info.abs_min[index] = abs.minimum; - dev->device_info.abs_max[index] = abs.maximum; - dev->device_info.abs_fuzz[index] = abs.fuzz; - dev->device_info.abs_flat[index] = abs.flat; - dev->device_info.abs_res[index] = abs.resolution; - dev->mapping.abs_indices[j] = index; - } else if (i == EV_REL) { - uint16_t index = dev->device_info.rel_count++; - dev->device_info.rel_id[index] = j; - dev->mapping.rel_indices[j] = index; - } else if (i == EV_KEY) { - uint16_t index = dev->device_info.key_count++; - dev->device_info.key_id[index] = j; - dev->mapping.key_indices[j] = index; - } - } + + ioctl(dev->event, EVIOCGBIT(0, EV_MAX), type_bits); + // Loop over all event types + for (int type = 0; type < EV_MAX; type++) { + // Ignore if the the device doesn't have this even type + if (!bit_set(type_bits, type)) { + continue; + } + + memset(feat_bits, 0, sizeof(feat_bits)); + ioctl(dev->event, EVIOCGBIT(type, KEY_MAX), feat_bits); + + // Loop over "instances" of type (i.e Each axis of a controller for EV_ABS) + for (int i = 0; i < KEY_MAX; i++) { + if (!bit_set(feat_bits, i)) { + continue; + } + + if (type == EV_ABS) { + struct input_absinfo abs; + ioctl(dev->event, EVIOCGABS(i), &abs); + + uint16_t index = dev->device_info.abs_count++; + + dev->device_info.abs_id[index] = i; + dev->device_info.abs_min[index] = abs.minimum; + dev->device_info.abs_max[index] = abs.maximum; + dev->device_info.abs_fuzz[index] = abs.fuzz; + dev->device_info.abs_flat[index] = abs.flat; + dev->device_info.abs_res[index] = abs.resolution; + dev->mapping.abs_indices[i] = index; + } else if (type == EV_REL) { + uint16_t index = dev->device_info.rel_count++; + + dev->device_info.rel_id[index] = i; + dev->mapping.rel_indices[i] = index; + } else if (type == EV_KEY) { + uint16_t index = dev->device_info.key_count++; + + dev->device_info.key_id[index] = i; + dev->mapping.key_indices[i] = index; } } } } +// Function used to filter out devices that we don't want. +// This is pretty arbritrary bool filter_event(int fd, char *event) { - char device_path[64]; - snprintf(device_path, 64, "/sys/class/input/%s/device", event); + // Check for existance of a js* directory in /sys/class/input/eventXX/device + // This is used to filter out the touchpad of PS4 controller (which have the same product and vendor id as + // the controller) + { + char device_path[64]; + snprintf(device_path, 64, "/sys/class/input/%s/device", event); - DIR *device_dir = opendir(device_path); - struct dirent *device_dirent; + DIR *device_dir = opendir(device_path); + struct dirent *device_dirent; - bool found = false; - while ((device_dirent = readdir(device_dir)) != NULL) { - if (device_dirent->d_type == DT_DIR && strncmp(device_dirent->d_name, "js", 2) == 0) { - found = true; - break; + bool found = false; + while ((device_dirent = readdir(device_dir)) != NULL) { + if (device_dirent->d_type == DT_DIR && strncmp(device_dirent->d_name, "js", 2) == 0) { + found = true; + break; + } + } + + closedir(device_dir); + + if (!found) { + return false; } } - closedir(device_dir); - - if (!found) { - return false; - } - + // Check product and vendor id 054c:05c4 => Dualshock 4 uint16_t info[4]; ioctl(fd, EVIOCGID, info); return info[1] == 0x054c && info[2] == 0x05c4; } +// Initialize vectors for polling void poll_devices_init() { devices = vec_of(uniq_t); new_devices = vec_of(PhysicalDevice); devices_queue = vec_of(PhysicalDevice); } +// Block to get a device, this is thread safe PhysicalDevice get_device() { + // Check if we can get one right away pthread_mutex_lock(&devices_queue_mutex); if (devices_queue.len > 0) { PhysicalDevice r; @@ -141,28 +164,43 @@ PhysicalDevice get_device() { return r; } + // Wait on condvar until there's a device and we can unlock the mutex while (devices_queue.len == 0) { pthread_cond_wait(&devices_queue_cond, &devices_queue_mutex); } + // Take a device from the queue PhysicalDevice res; vec_pop(&devices_queue, &res); + + // Signal another thread if there are still device(s) left in the queue if (devices_queue.len > 0) { pthread_cond_signal(&devices_queue_cond); } + pthread_mutex_unlock(&devices_queue_mutex); return res; } +// Forget about a device. This is used on two cases: +// - If the connection to a client is lost, the device is forgotten, picked up by the next device poll, and +// put back in the queue +// - If the device dies (i.e unplugged), the connection to the client is closed and the device forgotten. +// +// This is thread safe void return_device(PhysicalDevice *dev) { - if (dev->name != NULL && dev->name != DEFAULT_NAME) { + if (dev->name != NULL && dev->name != DEVICE_DEFAULT_NAME) { + // Free the name if it was allocated printf("HID: Returning device '%s' (%012lx)\n", dev->name, dev->uniq); free(dev->name); } else { printf("HID: Returning device %012lx\n", dev->uniq); } + // try to close the file descriptor, they may be already closed if the device was unpugged. close(dev->event); close(dev->hidraw); + + // Safely remove device from the known device list pthread_mutex_lock(&devices_mutex); for (int i = 0; i < devices.len; i++) { uniq_t *uniq = vec_get(&devices, i); @@ -174,38 +212,44 @@ void return_device(PhysicalDevice *dev) { pthread_mutex_unlock(&devices_mutex); } +// Find all available devices and pick up on new ones void poll_devices() { vec_clear(&new_devices); + // loop over all entries of /sys/class/input DIR *input_dir = opendir("/sys/class/input"); struct dirent *input; while ((input = readdir(input_dir)) != NULL) { - // Ignore if the entry isn't a linkg or doesn't start with event + // Ignore if the entry isn't a link or doesn't start with event if (input->d_type != DT_LNK || strncmp(input->d_name, "event", 5) != 0) { continue; } PhysicalDevice dev; + // Open /dev/input/eventXX char event_path[64]; snprintf(event_path, 64, "/dev/input/%s", input->d_name); dev.event = open(event_path, O_RDONLY); - if (dev.event < 0) { + if (dev.event < 0) { // Ignore device if we couldn't open continue; } - char name_buf[256] = {}; - char *name; + // Try to get the name, default to DEFAULT_NAME if impossible + char name_buf[256] = {}; + const char *name; if (ioctl(dev.event, EVIOCGNAME(256), name_buf) >= 0) name = name_buf; else - name = DEFAULT_NAME; + name = DEVICE_DEFAULT_NAME; + // Filter events we don't care about if (!filter_event(dev.event, input->d_name)) goto skip; + // Try to get uniq, drop device if we can't uniq_t uniq; { char uniq_str[17] = {}; @@ -218,6 +262,7 @@ void poll_devices() { goto skip; } + // Check if we already know of this device bool found = false; pthread_mutex_lock(&devices_mutex); @@ -230,13 +275,14 @@ void poll_devices() { } pthread_mutex_unlock(&devices_mutex); - if (found) + if (found) { // Device isn't new goto skip; + } dev.uniq = uniq; + // Try to find hidraw path for the device, drop the device if we can't char hidraw_path[64]; - { char hidraw_dir_path[256]; snprintf(hidraw_dir_path, 256, "/sys/class/input/%s/device/device/hidraw", input->d_name); @@ -244,8 +290,9 @@ void poll_devices() { DIR *hidraw_dir = opendir(hidraw_dir_path); struct dirent *hidraw = NULL; while ((hidraw = readdir(hidraw_dir)) != NULL) { - if (strncmp(hidraw->d_name, "hidraw", 6) == 0) + if (strncmp(hidraw->d_name, "hidraw", 6) == 0) { break; + } } if (hidraw == NULL) { @@ -259,32 +306,40 @@ void poll_devices() { } dev.hidraw = open(hidraw_path, O_WRONLY); - if (dev.hidraw < 0) + if (dev.hidraw < 0) { goto skip; + } - dev.name = malloc(256); - if (dev.name == NULL) - dev.name = DEFAULT_NAME; - else - strcpy(dev.name, name); + if (name != DEVICE_DEFAULT_NAME) { + dev.name = malloc(256); + + if (dev.name == NULL) { + dev.name = (char *)DEVICE_DEFAULT_NAME; + } else { + strcpy(dev.name, name); + } + } setup_device(&dev); pthread_mutex_lock(&devices_mutex); vec_push(&devices, &uniq); pthread_mutex_unlock(&devices_mutex); + vec_push(&new_devices, &dev); printf("HID: New device, %s (%s: %012lx)\n", name, input->d_name, dev.uniq); + // Continue here to avoid running cleanup code of skip continue; - // close open file descriptor and continue + // close open file descriptor and continue skip: close(dev.event); continue; }; - closedir(input_dir); + + // Safely add new devices to the queue if (new_devices.len > 0) { pthread_mutex_lock(&devices_queue_mutex); vec_extend(&devices_queue, new_devices.data, new_devices.len); @@ -294,10 +349,12 @@ void poll_devices() { } } +// "Execute" a MessageControllerState: set the led color and such using the hidraw interface void apply_controller_state(PhysicalDevice *dev, MessageControllerState *state) { - printf("HID: Controller state: #%02x%02x%02x (%d, %d) rumble: (%d, %d)\n", state->led[0], - state->led[1], state->led[2], state->flash_on, state->flash_off, state->small_rumble, - state->big_rumble); + printf("HID: (%012lx) Controller state: #%02x%02x%02x (%d, %d) rumble: (%d, %d)\n", dev->uniq, + state->led[0], state->led[1], state->led[2], state->flash_on, state->flash_off, + state->small_rumble, state->big_rumble); + uint8_t buf[32] = {0x05, 0xff, 0x00, 0x00}; buf[4] = state->small_rumble; @@ -310,21 +367,23 @@ void apply_controller_state(PhysicalDevice *dev, MessageControllerState *state) write(dev->hidraw, buf, 32); if (state->flash_on == 0 && state->flash_off == 0) { + // May not be necessary fsync(dev->hidraw); - // Send a second time because it doesn't work otherwise + // Send a second time, for some reason the flash doesn't stop otherwise write(dev->hidraw, buf, 32); }; } +// Body of the hid thread void *hid_thread() { printf("HID: start\n"); + poll_devices_init(); while (1) { poll_devices(); - struct timespec ts; - ts.tv_sec = 1; - ts.tv_nsec = 0; - nanosleep(&ts, NULL); + + nanosleep(&POLL_DEVICE_INTERVAL, NULL); } + return NULL; } diff --git a/hid.h b/hid.h index fa7b2d3..a4bcefa 100644 --- a/hid.h +++ b/hid.h @@ -6,14 +6,20 @@ #include #include +// Unique identifier for devices (provided by linux), May be the mac address typedef uint64_t uniq_t; +// Mapping to go from index to id of events +// the id of an event is the code field of a input_event struct +// the index is given (somewhat arbitrarily) by hid.c::setup_device, this is done because ids are sparse +// and innefficient to transfer over network (especially for keys that can range from 0 to 700). typedef struct { uint16_t abs_indices[ABS_CNT]; uint16_t rel_indices[REL_CNT]; uint16_t key_indices[KEY_CNT]; } DeviceMap; +// A struct representing a connected device typedef struct { int event; int hidraw; diff --git a/json.c b/json.c index 0eb65df..36e6549 100644 --- a/json.c +++ b/json.c @@ -1,6 +1,8 @@ #define JSON_C_ #include "json.h" +#include "util.h" + #include #include #include @@ -8,38 +10,58 @@ #include #include -static JSONError jerrno = NoError; -static size_t jerr_index = 0; +// Code for the last json parsing error +static JSONError jerrno = NoError; +// Location of the last json parsing error +static size_t jerr_index = 0; +// Get a string explaining the last json parsing error const char *json_strerr() { return JSONErrorMessage[jerrno]; } - -size_t json_err_loc() { return jerr_index; } +// Get the code of the last json parsing error +JSONError json_errno() { return jerrno; } +// Get the location of the last json parsing error +size_t json_errloc() { return jerr_index; } // Shorthand to set jerno and return -1; +// i.e +// ```c +// if(error) return set_jerrno(JSONError); +// ``` static inline int set_jerrno(JSONError err) { jerrno = err; return -1; } -static inline size_t align_8(size_t n) { return (((n - 1) >> 3) + 1) << 3; } -static inline bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n'; } + +// Return true if c is a whitespace character +static inline bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n'; } + +static inline void skip_whitespaces(const char **buf, const char *buf_end) { + while (*buf < buf_end && is_whitespace(**buf)) { + (*buf)++; + } +} static int json_parse_value(const char **buf, const char *buf_end, uint8_t **restrict dst, - const uint8_t *dst_end); + const uint8_t *dst_end); // Declaration for recursion // *dst must be 8 aligned static inline int json_parse_string(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { + // Ensure enough space for the header if (*dst + sizeof(JSONHeader) >= dst_end) { return set_jerrno(DstOverflow); } + // Build header JSONHeader *header = (JSONHeader *)(*dst); header->type = (uint32_t)String; header->len = 0; + // Increment dst pointer *dst += sizeof(JSONHeader); // Skip first quote (*buf)++; + // Ensure there is more in the buffer (there should be at least a closing ") if (*buf == buf_end) { return set_jerrno(SrcOverflow); } @@ -52,10 +74,12 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8 // The unicode codepoint we're parsing int un_codepoint = 0; + // Loop until return or we met the end of the buffer for (; *buf < buf_end; (*buf)++) { char c = **buf; - if (esc_unicode >= 0) { + if (esc_unicode >= 0) { // We're currently in a \uXXXX escape + // Parse hex digit int digit = 0; if (c >= '0' && c <= '9') digit = c - '0'; @@ -66,44 +90,53 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8 else { return set_jerrno(StringBadUnicode); } + un_codepoint <<= 4; un_codepoint += digit; + esc_unicode++; - if (esc_unicode == 4) { // UTF-8 Encoding - if (un_codepoint <= 0x7f) { - if (*dst + 1 >= dst_end) + // If we got all 4 hex digit, we UTF-8 encode the resulting codepoint + if (esc_unicode == 4) { + // From https://en.wikipedia.org/wiki/UTF-8#Encoding + if (un_codepoint <= 0x7f) { // 1 byte codepoint => ascii + if (*dst + 1 >= dst_end) { // Ensure enough space in the dst buffer return set_jerrno(DstOverflow); - + } + // *(*dst)++ => set **dst to RHS and increment *dst *(*dst)++ = un_codepoint; header->len++; - } else if (un_codepoint <= 0x7ff) { - if (*dst + 2 >= dst_end) + } else if (un_codepoint <= 0x7ff) { // 2 byte codepoint + if (*dst + 2 >= dst_end) { return set_jerrno(DstOverflow); + } *(*dst)++ = 0b11000000 | (un_codepoint >> 6 & 0b011111); *(*dst)++ = 0b10000000 | (un_codepoint >> 0 & 0b111111); header->len += 2; - } else if (un_codepoint <= 0xffff) { - if (*dst + 3 >= dst_end) + } else if (un_codepoint <= 0xffff) { // 3 byte codepoint + if (*dst + 3 >= dst_end) { return set_jerrno(DstOverflow); + } *(*dst)++ = 0b11100000 | (un_codepoint >> 12 & 0b1111); *(*dst)++ = 0b10000000 | (un_codepoint >> 6 & 0b111111); *(*dst)++ = 0b10000000 | (un_codepoint >> 0 & 0b111111); header->len += 3; - } else if (un_codepoint <= 0x10ffff) { - if (*dst + 4 >= dst_end) + } else if (un_codepoint <= 0x10ffff) { // 4 byte codepoint + if (*dst + 4 >= dst_end) { return set_jerrno(DstOverflow); + } *(*dst)++ = 0b11110000 | (un_codepoint >> 18 & 0b111); *(*dst)++ = 0b10000000 | (un_codepoint >> 12 & 0b111111); *(*dst)++ = 0b10000000 | (un_codepoint >> 6 & 0b111111); *(*dst)++ = 0b10000000 | (un_codepoint >> 0 & 0b111111); header->len += 4; - } else { + } else { // Illegal codepoint return set_jerrno(StringBadUnicode); } + // We finished parsing the \uXXXX escape esc_unicode = -1; } } else if (esc) { @@ -112,6 +145,7 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8 case '"': case '\\': case '/': // For some reason you can escape a slash in JSON + // Those stay the same r = c; break; case 'b': @@ -136,10 +170,11 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8 return set_jerrno(StringBadEscape); } - if (c != 'u') { + if (c != 'u') { // \u is the only escape that doesn't immediatly produce a character if (*dst + 1 >= dst_end) { return set_jerrno(DstOverflow); } + // *(*dst)++ => set **dst to RHS and increment *dst *(*dst)++ = r; header->len++; } @@ -149,32 +184,44 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8 if (c == '\\') { esc = true; continue; - } else if (c == '"') { + } else if (c == '"') { // Closing quote int padded_len = align_8(header->len); - if (*dst + (padded_len - header->len) >= dst_end) + // Ensure enough space in dst for padding + if (*dst + (padded_len - header->len) >= dst_end) { return set_jerrno(DstOverflow); - for (; padded_len > header->len; padded_len--) + } + // Pad to 8 align + for (; padded_len > header->len; padded_len--) { *(*dst)++ = '\0'; + } + // Skip " (*buf)++; + return 0; - } else if ((c < ' ' && c != '\t') || c == 0x7f) { + } else if ((c < ' ' && c != '\t') || + c == 0x7f) { // Illegal characters, technically tab isn't allowed either + // but it felt weird so I added it jerrno = StringBadChar; return -1; } - if (*dst + 1 >= dst_end) + + if (*dst + 1 >= dst_end) { return set_jerrno(DstOverflow); + } + + // *(*dst)++ => set **dst to RHS and increment *dst *(*dst)++ = c; header->len++; } } - // The only way to get out of the loop is if *buf >= buf_end + // The only way to get out of the loop is if *buf >= buf_end: buffer overflow return set_jerrno(SrcOverflow); } // *dst must be 8 aligned static int json_parse_number(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { - + // Ensure enough space for header and value if (*dst + sizeof(JSONHeader) + sizeof(double) >= dst_end) { return set_jerrno(DstOverflow); } @@ -186,18 +233,24 @@ static int json_parse_number(const char **buf, const char *buf_end, uint8_t **re header->type = (uint32_t)Number; header->len = sizeof(double); + *value = 0.0; + double sign = 1.0; if (**buf == '-') { - (*buf)++; + (*buf)++; // Skip - sign = -1.0; } - if (*buf >= buf_end) + // There has to be at least one digit + if (*buf >= buf_end) { return set_jerrno(SrcOverflow); + } + if (**buf != '0') { + // If the first character is not a zero we have a pententially mutli digit number for (; *buf < buf_end; (*buf)++) { char c = **buf; - if (c < '0' || c > '9') { + if (c < '0' || c > '9') { // if c isn't a number break; } @@ -205,54 +258,81 @@ static int json_parse_number(const char **buf, const char *buf_end, uint8_t **re *value += (double)(c - '0'); } } else { + // If c is zero we can't have anything else (for the integer part) (*buf)++; } + // If there another character and its a . we have a fractional part if (*buf < buf_end && **buf == '.') { + // Decimal place double place = 0.1; - (*buf)++; // Skip dot - if (*buf >= buf_end) + + (*buf)++; // Skip . + + // There must be at least one digit after the dot + if (*buf >= buf_end) { return set_jerrno(SrcOverflow); - if (**buf < '0' || **buf > '9') + } + if (**buf < '0' || **buf > '9') { return set_jerrno(NumberBadChar); + } for (; *buf < buf_end; (*buf)++) { char c = **buf; - if (c < '0' || c > '9') + + if (c < '0' || c > '9') { break; + } + double digit = (double)(c - '0'); *value += digit * place; + place *= 0.1; } } + // if theres at least one more character and its an e or an E we got an exponent if (*buf < buf_end && (**buf == 'e' || **buf == 'E')) { double exp = 0.0; double exp_sign = 1.0; (*buf)++; // Skip e/E - if (*buf >= buf_end) - return set_jerrno(SrcOverflow); - if (**buf == '+' || **buf == '-') { - exp_sign = **buf == '-' ? -1.0 : 1.0; - (*buf)++; - if (*buf >= buf_end) - return set_jerrno(SrcOverflow); + // There must be at least one more character (a digit or a sign followed by digit(s)) + if (*buf >= buf_end) { + return set_jerrno(SrcOverflow); } + // Handle sign of exponent + if (**buf == '+' || **buf == '-') { + exp_sign = **buf == '-' ? -1.0 : 1.0; + + (*buf)++; // Skip sign + + // If there's a sign there must be at least one digit following it + if (*buf >= buf_end) { + return set_jerrno(SrcOverflow); + } + } + + // Parse exponent for (; *buf < buf_end; (*buf)++) { char c = **buf; - if (c < '0' || c > '9') + + if (c < '0' || c > '9') { break; + } + exp *= 10; exp += (double)(c - '0'); } + // Apply exponent exp *= exp_sign; *value *= pow(10.0, exp); } + // Apply sign *value *= sign; return 0; @@ -261,8 +341,8 @@ static int json_parse_number(const char **buf, const char *buf_end, uint8_t **re // *dst must be 8 aligned static int json_parse_boolean(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { - - if (*dst + sizeof(JSONHeader) + 8 >= dst_end) { + // Ensure enough space for header and value + if (*dst + sizeof(JSONHeader) + 8 >= dst_end) { // 8: sizeof(uint64_t) return set_jerrno(DstOverflow); } @@ -273,32 +353,35 @@ static int json_parse_boolean(const char **buf, const char *buf_end, uint8_t **r header->type = (uint32_t)Boolean; header->len = 8; - if (**buf == 't') { + if (**buf == 't') { // The value can only be true, so we check it against that if (*buf + 4 > buf_end) return set_jerrno(SrcOverflow); if (strncmp(*buf, "true", 4) != 0) { return set_jerrno(BadKeyword); } + *buf += 4; *value = 1; - } else if (**buf == 'f') { + } else if (**buf == 'f') { // The value can only be false if (*buf + 5 > buf_end) return set_jerrno(SrcOverflow); if (strncmp(*buf, "false", 5) != 0) { return set_jerrno(BadKeyword); } + *buf += 5; *value = 0; } else { return set_jerrno(BadKeyword); } + return 0; } // *dst must be 8 aligned static int json_parse_null(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { - + // Ensure enough size for the header (no value) if (*dst + sizeof(JSONHeader) >= dst_end) { return set_jerrno(DstOverflow); } @@ -309,121 +392,155 @@ static int json_parse_null(const char **buf, const char *buf_end, uint8_t **rest header->type = (uint32_t)Null; header->len = 0; + // Check that the word is indeed null if (*buf + 4 > buf_end) return set_jerrno(SrcOverflow); if (strncmp(*buf, "null", 4) != 0) { return set_jerrno(BadKeyword); } + *buf += 4; + return 0; } // *dst must be 8 aligned static int json_parse_array(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { - + // Ensure enough space for the header if (*dst + sizeof(JSONHeader) >= dst_end) { return set_jerrno(DstOverflow); } + // Setup header JSONHeader *header = (JSONHeader *)(*dst); *dst += sizeof(JSONHeader); + // Keep track of pointer to the start of "value" of the array, used to compute the final length uint8_t *dst_arr_start = *dst; header->type = (uint32_t)Array; (*buf)++; // Skip [ + // skip initial whitespace - for (; *buf < buf_end && is_whitespace(**buf); (*buf)++) - ; - if (*buf == buf_end) + skip_whitespaces(buf, buf_end); + + // There should be at least one more character (a value or ]) + if (*buf == buf_end) { return set_jerrno(SrcOverflow); - if (**buf == ']') { + } + + if (**buf == ']') { // Array is empty header->len = 0; return 0; } + while (1) { - if (json_parse_value(buf, buf_end, dst, dst_end) != 0) + // Try to parse a value + if (json_parse_value(buf, buf_end, dst, dst_end) != 0) { return -1; - for (; *buf < buf_end && is_whitespace(**buf); (*buf)++) - ; - if (*buf == buf_end) + } + // Skip whitespaces after value + skip_whitespaces(buf, buf_end); + + // There should be at least one more char (, or ]) + if (*buf == buf_end) { return set_jerrno(SrcOverflow); + } + if (**buf == ',') { + // Skip , and go for another iteration (*buf)++; } else if (**buf == ']') { + // Skip ] and finish (*buf)++; break; } else { return set_jerrno(BadChar); } } + // Compute len header->len = *dst - dst_arr_start; + return 0; } // *dst must be 8 aligned static int json_parse_object(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { - + // Esnure enough space for the header if (*dst + sizeof(JSONHeader) >= dst_end) { return set_jerrno(DstOverflow); } + // Setup header JSONHeader *header = (JSONHeader *)(*dst); *dst += sizeof(JSONHeader); + // Keep track of pointer to start of value to compute length later uint8_t *dst_obj_start = *dst; header->type = (uint32_t)Object; (*buf)++; // Skip { - for (; *buf < buf_end && is_whitespace(**buf); (*buf)++) - ; - if (*buf == buf_end) + // Skip initial whitespace (after '{') + skip_whitespaces(buf, buf_end); + // There should be at least one more char (a value or }) + if (*buf == buf_end) { return set_jerrno(SrcOverflow); + } if (**buf == '}') { + // The object is empty header->len = 0; return 0; } while (1) { // Skip whitespace before key - for (; *buf < buf_end && is_whitespace(**buf); (*buf)++) - ; - // Parse key - if (json_parse_string(buf, buf_end, dst, dst_end) != 0) + skip_whitespaces(buf, buf_end); + // Try to parse key + if (json_parse_string(buf, buf_end, dst, dst_end) != 0) { return -1; + } // Skip whitespace after key - for (; *buf < buf_end && is_whitespace(**buf); (*buf)++) - ; - // There should be at least one char - if (*buf == buf_end) - return set_jerrno(SrcOverflow); + skip_whitespaces(buf, buf_end); + // There should be a colon - if (**buf != ':') - return set_jerrno(ObjectBadChar); - // Skip colon - (*buf)++; - // Parse value (takes char of whitespace) - if (json_parse_value(buf, buf_end, dst, dst_end) != 0) - return -1; - // Skip whitespace after value - for (; *buf < buf_end && is_whitespace(**buf); (*buf)++) - ; - // There should be at least one char (} or ,) - if (*buf == buf_end) + if (*buf == buf_end) { return set_jerrno(SrcOverflow); + } + if (**buf != ':') { + return set_jerrno(ObjectBadChar); + } + (*buf)++; + + // Try to parse value (takes care of whitespaces) + if (json_parse_value(buf, buf_end, dst, dst_end) != 0) { + return -1; + } + // Skip whitespace after value + skip_whitespaces(buf, buf_end); + + // There should be at least one char (} or ,) + if (*buf == buf_end) { + return set_jerrno(SrcOverflow); + } + if (**buf == ',') { + // Skip , and go for another iteration (*buf)++; } else if (**buf == '}') { + // Skip } and finish (*buf)++; break; } else { return set_jerrno(BadChar); } } + // + // Compute length header->len = *dst - dst_obj_start; + return 0; } @@ -431,6 +548,7 @@ static int json_parse_object(const char **buf, const char *buf_end, uint8_t **re static int json_parse_value(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) { for (; *buf < buf_end; (*buf)++) { + // Ignore initial whitespaces if (is_whitespace(**buf)) continue; @@ -462,26 +580,32 @@ static int json_parse_value(const char **buf, const char *buf_end, uint8_t **res return set_jerrno(BadChar); } } - if (*buf == buf_end) + + if (*buf == buf_end) { return set_jerrno(SrcOverflow); + } + return 0; } int json_parse(const char *src, size_t src_len, uint8_t *dst, size_t dst_len) { - memset(dst, 0, dst_len); const char *buf = src; const char *buf_end = src + src_len; uint8_t *dst_end = dst + dst_len; - int rc = json_parse_value(&buf, buf_end, &dst, dst_end); - jerr_index = buf - src; + + int rc = json_parse_value(&buf, buf_end, &dst, dst_end); + // Set location to the difference between were we got to and where we started + jerr_index = buf - src; return rc; } -void json_print_value(uint8_t **buf) { +void json_print_value_priv(uint8_t **buf) { JSONHeader *header = (JSONHeader *)*buf; *buf += sizeof(header); + switch (header->type) { case String: + // TODO: escapes printf("\"%.*s\"", header->len, *buf); *buf += align_8(header->len); break; @@ -507,9 +631,9 @@ void json_print_value(uint8_t **buf) { uint8_t *end = *buf + header->len; printf("["); while (1) { - json_print_value(buf); + json_print_value_priv(buf); if (*buf < end) { - printf(", "); + printf(","); } else { printf("]"); break; @@ -520,9 +644,9 @@ void json_print_value(uint8_t **buf) { uint8_t *end = *buf + header->len; printf("{"); while (1) { - json_print_value(buf); + json_print_value_priv(buf); printf(":"); - json_print_value(buf); + json_print_value_priv(buf); if (*buf < end) { printf(","); } else { @@ -534,21 +658,18 @@ void json_print_value(uint8_t **buf) { } } -struct Test { - double a; - char *b; -}; - -const JSONAdapter TestAdapter[] = { - {".a", Number, offsetof(struct Test, a)}, - {".b", String, offsetof(struct Test, b)}, -}; +// /!\ doesn't handle strings well +void json_print_value(uint8_t *buf) { json_print_value_priv(&buf); } +// Loop over adapters and set accordingly static void json_adapt_set(uint8_t *buf, JSONAdapter *adapters, size_t adapter_count, void *ptr, char *path) { JSONHeader *header = (JSONHeader *)buf; + for (int i = 0; i < adapter_count; i++) { if (strcmp(path, adapters[i].path) == 0 && header->type == adapters[i].type) { + void *p = ptr + adapters[i].offset; + switch (header->type) { case String: { char *v = malloc(header->len + 1); @@ -567,6 +688,7 @@ static void json_adapt_set(uint8_t *buf, JSONAdapter *adapters, size_t adapter_c } } +// Run adapters on a value static void json_adapt_priv(uint8_t **buf, JSONAdapter *adapters, size_t adapter_count, void *ptr, char *full_path, char *path) { JSONHeader *header = (JSONHeader *)*buf; @@ -611,6 +733,7 @@ static void json_adapt_priv(uint8_t **buf, JSONAdapter *adapters, size_t adapter } } +// Run adapters on a json value void json_adapt(uint8_t *buf, JSONAdapter *adapters, size_t adapter_count, void *ptr) { char path[512] = "."; json_adapt_priv(&buf, adapters, adapter_count, ptr, path, path); diff --git a/json.h b/json.h index 9feef36..5249814 100644 --- a/json.h +++ b/json.h @@ -53,6 +53,7 @@ static const char *JSONErrorMessage[JERRORNO_MAX + 1] = { }; #endif +// See client.c for usage of adapters typedef struct { char *path; JSONType type; @@ -61,7 +62,9 @@ typedef struct { void json_adapt(uint8_t *buf, JSONAdapter *adapters, size_t adapter_count, void *ptr); int json_parse(const char *src, size_t src_len, uint8_t *dst, size_t dst_len); +void json_print_value(uint8_t *buf); const char *json_strerr(); -size_t json_err_loc(); +size_t json_errloc(); +JSONError json_errno(); #endif diff --git a/main.c b/main.c index cb3d1a1..8876408 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,3 @@ -#include "main.h" - #include "client.h" #include "hid.h" #include "server.h" @@ -8,7 +6,6 @@ #include #include #include -#include #include const char *USAGE[] = { @@ -16,18 +13,12 @@ const char *USAGE[] = { "jsfw server [port]\n", }; -uint16_t parse_port(const char *str) { - long long n = atoll(str); - if (n <= 0 || n > UINT16_MAX) - panicf("Invalid port: Expected a number in the range 1..%d, got '%s'\n", UINT16_MAX, str); - return n; -} - void server(uint16_t port) { - printf("[Server (port: %u)]\n\n", port); + printf("[Server (0.0.0.0:%u)]\n\n", port); pthread_t thread; pthread_create(&thread, NULL, hid_thread, NULL); + server_run(port); } diff --git a/main.h b/main.h deleted file mode 100644 index 683ff2f..0000000 --- a/main.h +++ /dev/null @@ -1,5 +0,0 @@ -// vi:ft=c -#ifndef MAIN_H_ -#define MAIN_H_ - -#endif diff --git a/net.c b/net.c index 01a8dcc..a1c3edd 100644 --- a/net.c +++ b/net.c @@ -2,15 +2,6 @@ #include -Message msg_device_info() { - MessageDeviceInfo m; - m.code = DeviceInfo; - - Message s; - s.device_info = m; - return s; -} - // Deserialize the message in buf, buf must be at least 4 aligned. Returns -1 on error, otherwise returns 0 // and writes result to dst int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst) { diff --git a/server.c b/server.c index ad32e80..b509260 100644 --- a/server.c +++ b/server.c @@ -1,3 +1,4 @@ +#include "const.h" #include "hid.h" #include "net.h" #include "util.h" @@ -8,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -16,6 +16,7 @@ #include #include +// Arguments for a connecion thread struct Connection { int socket; uint32_t id; @@ -26,22 +27,18 @@ void *server_handle_conn(void *args_) { printf("CONN(%u): start\n", args->id); - int enable = true; - int idle_time = 10; - int keep_count = 5; - int keep_interval = 2; - - if (setsockopt(args->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)) != 0) + if (setsockopt(args->socket, SOL_SOCKET, SO_KEEPALIVE, &TCP_KEEPALIVE_ENABLE, sizeof(int)) != 0) printf("ERR(server_handle_conn): Enabling socket keepalives on client\n"); - if (setsockopt(args->socket, SOL_TCP, TCP_KEEPIDLE, &idle_time, sizeof(idle_time)) != 0) - printf("ERR(server_handle_conn): Setting initial ERR()-time value\n"); - if (setsockopt(args->socket, SOL_TCP, TCP_KEEPCNT, &keep_count, sizeof(keep_count)) != 0) + if (setsockopt(args->socket, SOL_TCP, TCP_KEEPIDLE, &TCP_KEEPALIVE_IDLE_TIME, sizeof(int)) != 0) + printf("ERR(server_handle_conn): Setting initial idle-time value\n"); + if (setsockopt(args->socket, SOL_TCP, TCP_KEEPCNT, &TCP_KEEPALIVE_RETRY_COUNT, sizeof(int)) != 0) printf("ERR(server_handle_conn): Setting idle retry count\n"); - if (setsockopt(args->socket, SOL_TCP, TCP_KEEPINTVL, &keep_interval, sizeof(keep_interval)) != 0) + if (setsockopt(args->socket, SOL_TCP, TCP_KEEPINTVL, &TCP_KEEPALIVE_RETRY_INTERVAL, sizeof(int)) != 0) printf("ERR(server_handle_conn): Setting idle retry interval\n"); - uint8_t buf[2048] __attribute__((aligned(4))) = {}; - PhysicalDevice dev = get_device(); + uint8_t buf[2048] __attribute__((aligned(4))) = {}; + + PhysicalDevice dev = get_device(); printf("CONN(%u): got device '%s'\n", args->id, dev.name); char *closing_message = ""; @@ -182,19 +179,22 @@ void server_run(uint16_t port) { printf("SERVER: start\n"); int sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) + if (sock < 0) { panicf("Couldn't open socket\n"); + } struct sockaddr_in addr = {}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { panicf("Couldn't bind to the socket\n"); + } - if (listen(sock, 16) != 0) + if (listen(sock, 16) != 0) { panicf("Couldn't listen on socket\n"); + } uint32_t ids = 0; while (1) { diff --git a/util.c b/util.c index 57fbaec..7e0ea19 100644 --- a/util.c +++ b/util.c @@ -1,6 +1,7 @@ #include "util.h" #include +#include #include #include @@ -15,3 +16,21 @@ void panicf(const char *fmt, ...) { va_end(args); exit(1); } + +uint16_t parse_port(const char *str) { + long long n = atoll(str); + if (n <= 0 || n > UINT16_MAX) + panicf("Invalid port: Expected a number in the range 1..%d, got '%s'\n", UINT16_MAX, str); + return n; +} + +uint8_t parse_hex_digit(char h) { + if (h >= '0' && h <= '9') + return h - '0'; + else if (h >= 'a' && h <= 'f') + return h - 'a' + 10; + else if (h >= 'A' && h <= 'F') + return h - 'A' + 10; + else + return 0; +} diff --git a/util.h b/util.h index 704dc30..a29d86a 100644 --- a/util.h +++ b/util.h @@ -1,7 +1,18 @@ // vi:ft=c #ifndef UTIL_H_ #define UTIL_H_ +#include +#include +#include -void panicf(const char *fmt, ...); +// Print a formatted message and exit with code 1 +void panicf(const char *fmt, ...); +uint16_t parse_port(const char *str); + +// Test if the bit with index i is set in the byte array bits +static inline bool bit_set(uint8_t *bits, int i) { return bits[i / 8] & (1 << (i % 8)); } +// Align n to the next 8 boundary +static inline size_t align_8(size_t n) { return (((n - 1) >> 3) + 1) << 3; } +uint8_t parse_hex_digit(char h); #endif diff --git a/vec.c b/vec.c index a2ee640..b2f9846 100644 --- a/vec.c +++ b/vec.c @@ -23,12 +23,16 @@ Vec vec_cap(size_t data_size, size_t initial_capacity) { } static inline void vec_grow(Vec *v, size_t cap) { - if (v->cap >= cap) + if (v->cap >= cap) { return; + } + size_t new_cap = cap > v->cap * 2 ? cap : v->cap * 2; void *new_data = realloc(v->data, new_cap * v->stride); - if (new_data == NULL) + if (new_data == NULL) { handle_alloc_error(); + } + v->data = new_data; v->cap = new_cap; } @@ -43,14 +47,16 @@ void vec_pop(Vec *v, void *data) { printf("ERR(vec_pop): Trying to pop an element from an empty vector\n"); return; } - if (data != NULL) + if (data != NULL) { memcpy(data, v->data + v->stride * (v->len - 1), v->stride); + } v->len--; } void *vec_get(Vec *v, size_t index) { - if (index >= v->len) + if (index >= v->len) { return NULL; + } return v->data + index * v->stride; } @@ -80,17 +86,20 @@ void vec_remove(Vec *v, size_t index, void *data) { } void *slot = v->data + index * v->stride; - if (data != NULL) + if (data != NULL) { memcpy(data, slot, v->stride); - if (index < --v->len) + } + if (index < --v->len) { memmove(slot, slot + v->stride, (v->len - index) * v->stride); + } } void vec_clear(Vec *v) { v->len = 0; } void vec_extend(Vec *v, void *data, size_t len) { - if (len == 0) + if (len == 0) { return; + } vec_grow(v, v->len + len); memcpy(v->data + v->stride * v->len, data, v->stride * len); v->len += len;