pretty much done with the ser conversion, one bug left (recursive struct (de)serialization)
This commit is contained in:
parent
eec4668856
commit
f6c5e749a6
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@ Q=@
|
|||
CC=gcc
|
||||
|
||||
GCCCFLAGS=-Wno-format-truncation
|
||||
CFLAGS=-std=c11 -pedantic -g -Wall -pthread -D_GNU_SOURCE
|
||||
CFLAGS=-std=gnu11 -pedantic -g -Wall -pthread -D_GNU_SOURCE -fsanitize=address
|
||||
LDFLAGS=-lm
|
||||
|
||||
# The root for /sys and /dev needs to be moved in docker, this should stay empty in most cases
|
||||
|
|
148
client.c
148
client.c
|
@ -37,13 +37,13 @@ static struct pollfd *socket_poll = &poll_fds[1];
|
|||
static int fifo = -1;
|
||||
static int sock = -1;
|
||||
// static to avoid having this on the stack because a message is about 2kb in memory
|
||||
static Message message;
|
||||
static DeviceMessage message;
|
||||
|
||||
static Vec devices_fd;
|
||||
static Vec devices_info;
|
||||
|
||||
static ClientConfig config;
|
||||
static MessageRequest device_request;
|
||||
static DeviceRequest device_request;
|
||||
|
||||
static void default_fifo_path(void *ptr) { *(char **)ptr = (char *)FIFO_PATH; }
|
||||
static void default_retry_delay(void *ptr) { *(struct timespec *)ptr = CONNECTION_RETRY_DELAY; }
|
||||
|
@ -58,17 +58,17 @@ static void default_to_white(void *ptr) {
|
|||
}
|
||||
|
||||
static const JSONPropertyAdapter ControllerStateAdapterProps[] = {
|
||||
{".led_color", &StringAdapter, offsetof(MessageControllerState, led), default_to_white, tsf_hex_to_color },
|
||||
{".rumble.0", &NumberAdapter, offsetof(MessageControllerState, small_rumble), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".rumble.1", &NumberAdapter, offsetof(MessageControllerState, big_rumble), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".flash.0", &NumberAdapter, offsetof(MessageControllerState, flash_on), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".flash.1", &NumberAdapter, offsetof(MessageControllerState, flash_off), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".index", &NumberAdapter, offsetof(MessageControllerState, index), default_to_zero_u32, tsf_num_to_int }
|
||||
{".led_color", &StringAdapter, offsetof(DeviceControllerState, led), default_to_white, tsf_hex_to_color },
|
||||
{".rumble.0", &NumberAdapter, offsetof(DeviceControllerState, small_rumble), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".rumble.1", &NumberAdapter, offsetof(DeviceControllerState, big_rumble), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".flash.0", &NumberAdapter, offsetof(DeviceControllerState, flash_on), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".flash.1", &NumberAdapter, offsetof(DeviceControllerState, flash_off), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||
{".index", &NumberAdapter, offsetof(DeviceControllerState, index), default_to_zero_u32, tsf_num_to_int }
|
||||
};
|
||||
static const JSONAdapter ControllerStateAdapter = {
|
||||
.props = (JSONPropertyAdapter *)ControllerStateAdapterProps,
|
||||
.prop_count = sizeof(ControllerStateAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||
.size = sizeof(MessageControllerState),
|
||||
.size = sizeof(DeviceControllerState),
|
||||
};
|
||||
|
||||
static const JSONPropertyAdapter ControllerAdapterProps[] = {
|
||||
|
@ -128,11 +128,11 @@ static void print_config() {
|
|||
void destroy_devices(void) {
|
||||
for (int i = 0; i < config.slot_count; i++) {
|
||||
int fd = *(int *)vec_get(&devices_fd, i);
|
||||
MessageDeviceInfo *info = vec_get(&devices_info, i);
|
||||
DeviceInfo *info = vec_get(&devices_info, i);
|
||||
|
||||
if (info->code == DeviceInfo) {
|
||||
if (info->tag == DeviceTagInfo) {
|
||||
ioctl(fd, UI_DEV_DESTROY);
|
||||
info->code = NoMessage;
|
||||
info->tag = DeviceTagNone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,8 +142,8 @@ bool device_exists(int index) {
|
|||
return false;
|
||||
}
|
||||
|
||||
MessageDeviceInfo *info = vec_get(&devices_info, index);
|
||||
return info->code == DeviceInfo;
|
||||
DeviceInfo *info = vec_get(&devices_info, index);
|
||||
return info->tag == DeviceTagInfo;
|
||||
}
|
||||
|
||||
void device_destroy(int slot) {
|
||||
|
@ -153,15 +153,15 @@ void device_destroy(int slot) {
|
|||
|
||||
int fd = *(int *)vec_get(&devices_fd, slot);
|
||||
|
||||
MessageDeviceInfo *info = vec_get(&devices_info, slot);
|
||||
DeviceInfo *info = vec_get(&devices_info, slot);
|
||||
|
||||
if (info->code == DeviceInfo) {
|
||||
if (info->tag == DeviceTagInfo) {
|
||||
ioctl(fd, UI_DEV_DESTROY);
|
||||
info->code = NoMessage;
|
||||
info->tag = DeviceTagNone;
|
||||
}
|
||||
}
|
||||
|
||||
void device_init(MessageDeviceInfo *dev) {
|
||||
void device_init(DeviceInfo *dev) {
|
||||
if (dev->slot >= devices_info.len) {
|
||||
printf("CLIENT: Got wrong device index\n");
|
||||
return;
|
||||
|
@ -172,35 +172,36 @@ void device_init(MessageDeviceInfo *dev) {
|
|||
int fd = *(int *)vec_get(&devices_fd, dev->slot);
|
||||
|
||||
// Abs
|
||||
if (dev->abs_count > 0) {
|
||||
if (dev->abs.len > 0) {
|
||||
ioctl(fd, UI_SET_EVBIT, EV_ABS);
|
||||
for (int i = 0; i < dev->abs_count; i++) {
|
||||
for (int i = 0; i < dev->abs.len; i++) {
|
||||
struct uinput_abs_setup setup = {0};
|
||||
|
||||
setup.code = dev->abs_id[i];
|
||||
setup.absinfo.minimum = dev->abs_min[i];
|
||||
setup.absinfo.maximum = dev->abs_max[i];
|
||||
setup.absinfo.fuzz = dev->abs_fuzz[i];
|
||||
setup.absinfo.flat = dev->abs_flat[i];
|
||||
setup.absinfo.resolution = dev->abs_res[i];
|
||||
Abs abs = dev->abs.data[i];
|
||||
setup.code = abs.id;
|
||||
setup.absinfo.minimum = abs.min;
|
||||
setup.absinfo.maximum = abs.max;
|
||||
setup.absinfo.fuzz = abs.fuzz;
|
||||
setup.absinfo.flat = abs.flat;
|
||||
setup.absinfo.resolution = abs.res;
|
||||
setup.absinfo.value = 0;
|
||||
ioctl(fd, UI_ABS_SETUP, &setup);
|
||||
}
|
||||
}
|
||||
|
||||
// Rel
|
||||
if (dev->rel_count > 0) {
|
||||
if (dev->rel.len > 0) {
|
||||
ioctl(fd, UI_SET_EVBIT, EV_REL);
|
||||
for (int i = 0; i < dev->rel_count; i++) {
|
||||
ioctl(fd, UI_SET_RELBIT, dev->rel_id[i]);
|
||||
for (int i = 0; i < dev->rel.len; i++) {
|
||||
ioctl(fd, UI_SET_RELBIT, dev->rel.data[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
// Key
|
||||
if (dev->key_count > 0) {
|
||||
if (dev->key.len > 0) {
|
||||
ioctl(fd, UI_SET_EVBIT, EV_KEY);
|
||||
for (int i = 0; i < dev->key_count; i++) {
|
||||
ioctl(fd, UI_SET_KEYBIT, dev->key_id[i]);
|
||||
for (int i = 0; i < dev->key.len; i++) {
|
||||
ioctl(fd, UI_SET_KEYBIT, dev->key.data[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,11 +218,11 @@ void device_init(MessageDeviceInfo *dev) {
|
|||
ioctl(fd, UI_DEV_SETUP, &setup);
|
||||
ioctl(fd, UI_DEV_CREATE);
|
||||
|
||||
MessageDeviceInfo *dst = vec_get(&devices_info, dev->slot);
|
||||
DeviceInfo *dst = vec_get(&devices_info, dev->slot);
|
||||
|
||||
memcpy(dst, dev, sizeof(MessageDeviceInfo));
|
||||
printf("CLIENT: Got device [%d]: '%s' (abs: %d, rel: %d, key: %d)\n", dev->slot, ctr->device_name, dev->abs_count,
|
||||
dev->rel_count, dev->key_count);
|
||||
memcpy(dst, dev, sizeof(DeviceInfo));
|
||||
printf("CLIENT: Got device [%d]: '%s' (abs: %d, rel: %d, key: %d)\n", dev->slot, ctr->device_name, dev->abs.len, dev->rel.len,
|
||||
dev->key.len);
|
||||
}
|
||||
|
||||
// Send an event to uinput, device must exist
|
||||
|
@ -241,33 +242,34 @@ bool device_emit(int index, uint16_t type, uint16_t id, uint32_t value) {
|
|||
}
|
||||
|
||||
// Update device with report
|
||||
void device_handle_report(MessageDeviceReport *report) {
|
||||
void device_handle_report(DeviceReport *report) {
|
||||
if (!device_exists(report->slot)) {
|
||||
printf("CLIENT: [%d] Got report before device info\n", report->slot);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageDeviceInfo *info = vec_get(&devices_info, report->slot);
|
||||
DeviceInfo *info = vec_get(&devices_info, report->slot);
|
||||
|
||||
if (report->abs_count != info->abs_count || report->rel_count != info->rel_count || report->key_count != info->key_count) {
|
||||
printf("CLIENT: Report doesn't match with device info\n");
|
||||
if (report->abs.len != info->abs.len || report->rel.len != info->rel.len || report->key.len != info->key.len) {
|
||||
printf("CLIENT: Report doesn't match with device info (expected %u/%u/%u, got %u/%u/%u)\n", info->abs.len, info->rel.len,
|
||||
info->key.len, report->abs.len, report->rel.len, report->key.len);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < report->abs_count; i++) {
|
||||
if (device_emit(report->slot, EV_ABS, info->abs_id[i], report->abs[i]) != 0) {
|
||||
for (int i = 0; i < report->abs.len; i++) {
|
||||
if (device_emit(report->slot, EV_ABS, info->abs.data[i].id, report->abs.data[i]) != 0) {
|
||||
printf("CLIENT: Error writing abs event to uinput\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < report->rel_count; i++) {
|
||||
if (device_emit(report->slot, EV_REL, info->rel_id[i], report->rel[i]) != 0) {
|
||||
for (int i = 0; i < report->rel.len; i++) {
|
||||
if (device_emit(report->slot, EV_REL, info->rel.data[i].id, report->rel.data[i]) != 0) {
|
||||
printf("CLIENT: Error writing rel event to uinput\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < report->key_count; i++) {
|
||||
if (device_emit(report->slot, EV_KEY, info->key_id[i], (uint32_t)(!report->key[i]) - 1) != 0) {
|
||||
for (int i = 0; i < report->key.len; i++) {
|
||||
if (device_emit(report->slot, EV_KEY, info->key.data[i].id, (uint32_t)(!report->key.data[i]) - 1) != 0) {
|
||||
printf("CLIENT: Error writing key event to uinput\n");
|
||||
}
|
||||
}
|
||||
|
@ -278,10 +280,10 @@ void device_handle_report(MessageDeviceReport *report) {
|
|||
|
||||
void setup_devices(void) {
|
||||
devices_fd = vec_of(int);
|
||||
devices_info = vec_of(MessageDeviceInfo);
|
||||
devices_info = vec_of(DeviceInfo);
|
||||
|
||||
MessageDeviceInfo no_info = {0};
|
||||
no_info.code = NoMessage;
|
||||
DeviceInfo no_info = {0};
|
||||
no_info.tag = DeviceTagNone;
|
||||
|
||||
for (int i = 0; i < config.slot_count; i++) {
|
||||
int fd = open(FSROOT "/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||
|
@ -350,9 +352,9 @@ void connect_server(void) {
|
|||
socket_poll->fd = sock;
|
||||
printf("CLIENT: Connected !\n");
|
||||
|
||||
uint8_t buf[2048] __attribute__((aligned(4))) = {0};
|
||||
uint8_t buf[2048] __attribute__((aligned(8))) = {0};
|
||||
|
||||
int len = msg_serialize(buf, 2048, (Message *)&device_request);
|
||||
int len = msg_device_serialize(buf, 2048, (DeviceMessage *)&device_request);
|
||||
if (len > 0) {
|
||||
if (send(sock, buf, len, 0) > 0) {
|
||||
printf("CLIENT: Sent device request\n");
|
||||
|
@ -382,20 +384,20 @@ void setup_server(char *address, uint16_t port) {
|
|||
}
|
||||
|
||||
void build_device_request(void) {
|
||||
TagList *reqs = malloc(config.slot_count * sizeof(TagList));
|
||||
device_request.tag = DeviceTagRequest;
|
||||
device_request.requests.len = config.slot_count;
|
||||
device_request.requests.data = malloc(config.slot_count * sizeof(TagList));
|
||||
for (int i = 0; i < config.slot_count; i++) {
|
||||
TagList *req = &reqs[i];
|
||||
req->count = config.slots[i].controller_count;
|
||||
req->tags = malloc(req->count * sizeof(char *));
|
||||
TagList *list = &device_request.requests.data[i];
|
||||
list->tags.len = config.slots[i].controller_count;
|
||||
list->tags.data = malloc(list->tags.len * sizeof(typeof(*list->tags.data)));
|
||||
|
||||
for (int j = 0; j < req->count; j++) {
|
||||
req->tags[j] = config.slots[i].controllers[j].tag;
|
||||
for (int j = 0; j < list->tags.len; j++) {
|
||||
char *name = config.slots[i].controllers[j].tag;
|
||||
list->tags.data[j].name.len = strlen(name);
|
||||
list->tags.data[j].name.data = name;
|
||||
}
|
||||
}
|
||||
|
||||
device_request.code = Request;
|
||||
device_request.request_count = config.slot_count;
|
||||
device_request.requests = reqs;
|
||||
}
|
||||
|
||||
void client_run(char *address, uint16_t port, char *config_path) {
|
||||
|
@ -431,10 +433,10 @@ void client_run(char *address, uint16_t port, char *config_path) {
|
|||
setup_devices();
|
||||
setup_server(address, port);
|
||||
|
||||
uint8_t buf[2048] __attribute__((aligned(4)));
|
||||
uint8_t buf[2048] __attribute__((aligned(8)));
|
||||
uint8_t json_buf[2048] __attribute__((aligned(8)));
|
||||
|
||||
while (1) {
|
||||
while (true) {
|
||||
int rc = poll(poll_fds, 2, -1);
|
||||
if (rc < 0) {
|
||||
perror("CLIENT: Error on poll");
|
||||
|
@ -451,11 +453,11 @@ void client_run(char *address, uint16_t port, char *config_path) {
|
|||
if (rc < 0) {
|
||||
printf("CLIENT: Error when parsing fifo message as json (%s at index %lu)\n", json_strerr(), json_errloc());
|
||||
} else {
|
||||
MessageControllerState msg;
|
||||
msg.code = ControllerState;
|
||||
DeviceControllerState msg;
|
||||
msg.tag = DeviceTagControllerState;
|
||||
json_adapt(json_buf, &ControllerStateAdapter, &msg);
|
||||
|
||||
int len = msg_serialize(buf, 2048, (Message *)&msg);
|
||||
int len = msg_device_serialize(buf, 2048, (DeviceMessage *)&msg);
|
||||
if (len > 0) {
|
||||
if (send(sock, buf, len, 0) > 0) {
|
||||
printf("CLIENT: Sent controller state: #%02x%02x%02x flash: (%d, %d) rumble: "
|
||||
|
@ -479,7 +481,7 @@ void client_run(char *address, uint16_t port, char *config_path) {
|
|||
continue;
|
||||
}
|
||||
|
||||
int msg_len = msg_deserialize(buf, len, &message);
|
||||
int msg_len = msg_device_deserialize(buf, len, &message);
|
||||
// We've got data from the server
|
||||
if (msg_len < 0) {
|
||||
recv(sock, buf, 2048, 0);
|
||||
|
@ -500,15 +502,15 @@ void client_run(char *address, uint16_t port, char *config_path) {
|
|||
|
||||
recv(sock, buf, msg_len, 0);
|
||||
|
||||
if (message.code == DeviceInfo) {
|
||||
if (device_exists(message.device_info.slot)) {
|
||||
if (message.tag == DeviceTagInfo) {
|
||||
if (device_exists(message.info.slot)) {
|
||||
printf("CLIENT: Got more than one device info for same device\n");
|
||||
}
|
||||
|
||||
device_init((MessageDeviceInfo *)&message);
|
||||
} else if (message.code == DeviceReport) {
|
||||
device_handle_report((MessageDeviceReport *)&message);
|
||||
} else if (message.code == DeviceDestroy) {
|
||||
device_init((DeviceInfo *)&message);
|
||||
} else if (message.tag == DeviceTagReport) {
|
||||
device_handle_report((DeviceReport *)&message);
|
||||
} else if (message.tag == DeviceTagDestroy) {
|
||||
device_destroy(message.destroy.index);
|
||||
printf("CLIENT: Lost device %d\n", message.destroy.index);
|
||||
} else {
|
||||
|
|
33
hid.c
33
hid.c
|
@ -55,10 +55,10 @@ uniq_t parse_uniq(char uniq[17]) {
|
|||
|
||||
// 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;
|
||||
dev->device_info.rel_count = 0;
|
||||
dev->device_info.key_count = 0;
|
||||
dev->device_info.tag = DeviceTagInfo;
|
||||
dev->device_info.abs.len = 0;
|
||||
dev->device_info.rel.len = 0;
|
||||
dev->device_info.key.len = 0;
|
||||
|
||||
for (int i = 0; i < ABS_CNT; i++)
|
||||
dev->mapping.abs_indices[i] = -1;
|
||||
|
@ -93,26 +93,27 @@ void setup_device(PhysicalDevice *dev) {
|
|||
struct input_absinfo abs;
|
||||
ioctl(dev->event, EVIOCGABS(i), &abs);
|
||||
|
||||
uint16_t index = dev->device_info.abs_count++;
|
||||
uint16_t index = dev->device_info.abs.len++;
|
||||
|
||||
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;
|
||||
Abs *dev_abs = &dev->device_info.abs.data[index];
|
||||
dev_abs->min = abs.minimum;
|
||||
dev_abs->max = abs.maximum;
|
||||
dev_abs->fuzz = abs.fuzz;
|
||||
dev_abs->flat = abs.flat;
|
||||
dev_abs->res = abs.resolution;
|
||||
dev_abs->id = i;
|
||||
// Bidirectional mapping id <-> index
|
||||
// We need this to avoid wasting space in packets because ids are sparse
|
||||
dev->device_info.abs_id[index] = i;
|
||||
dev->mapping.abs_indices[i] = index;
|
||||
} else if (type == EV_REL) {
|
||||
uint16_t index = dev->device_info.rel_count++;
|
||||
uint16_t index = dev->device_info.rel.len++;
|
||||
|
||||
dev->device_info.rel_id[index] = i;
|
||||
dev->device_info.rel.data[index].id = i;
|
||||
dev->mapping.rel_indices[i] = index;
|
||||
} else if (type == EV_KEY) {
|
||||
uint16_t index = dev->device_info.key_count++;
|
||||
uint16_t index = dev->device_info.key.len++;
|
||||
|
||||
dev->device_info.key_id[index] = i;
|
||||
dev->device_info.key.data[index].id = i;
|
||||
dev->mapping.key_indices[i] = index;
|
||||
}
|
||||
}
|
||||
|
@ -445,7 +446,7 @@ void poll_devices(void) {
|
|||
}
|
||||
|
||||
// "Execute" a MessageControllerState: set the led color, rumble and flash using the hidraw interface (Dualshock 4 only)
|
||||
void apply_controller_state(Controller *c, MessageControllerState *state) {
|
||||
void apply_controller_state(Controller *c, DeviceControllerState *state) {
|
||||
if (c->ctr.ps4_hidraw && c->dev.hidraw < 0) {
|
||||
printf("HID: Trying to apply controller state on incompatible device (%lu)\n", c->dev.id);
|
||||
return;
|
||||
|
|
4
hid.h
4
hid.h
|
@ -30,7 +30,7 @@ typedef struct {
|
|||
uint64_t id;
|
||||
char *name;
|
||||
DeviceMap mapping;
|
||||
MessageDeviceInfo device_info;
|
||||
DeviceInfo device_info;
|
||||
} PhysicalDevice;
|
||||
|
||||
typedef struct {
|
||||
|
@ -42,6 +42,6 @@ void *hid_thread(void *arg);
|
|||
void return_device(Controller *c);
|
||||
void forget_device(Controller *c);
|
||||
bool get_device(char **tags, size_t tag_count, bool *stop, Controller *res, uint8_t *index);
|
||||
void apply_controller_state(Controller *c, MessageControllerState *state);
|
||||
void apply_controller_state(Controller *c, DeviceControllerState *state);
|
||||
|
||||
#endif
|
||||
|
|
602
net.c
602
net.c
|
@ -1,434 +1,286 @@
|
|||
// Generated file, do not edit (its not like it'll explode if you do, but its better not to)
|
||||
#include "net.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// 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) {
|
||||
{
|
||||
if (len <= MAGIC_SIZE) {
|
||||
int msg_device_serialize(byte *buf, size_t len, DeviceMessage *msg) {
|
||||
const byte *base_buf = buf;
|
||||
if(len < 2 * MSG_MAGIC_SIZE)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*(MAGIC_TYPE *)buf != MAGIC_BEG) {
|
||||
printf("NET: No magic in message\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf += MAGIC_SIZE;
|
||||
len -= MAGIC_SIZE;
|
||||
}
|
||||
// Decrement len so that it becomes the len of the data without the code.
|
||||
if (len-- < 1)
|
||||
return -1;
|
||||
// This ensures that only a byte is read instead of a full enum value
|
||||
uint8_t code_byte = buf[0];
|
||||
MessageCode code = (MessageCode)code_byte;
|
||||
uint32_t size = 0;
|
||||
|
||||
uint16_t abs, rel, key, *buf16;
|
||||
uint8_t index, slot;
|
||||
|
||||
switch (code) {
|
||||
case DeviceInfo:
|
||||
if (len < 7)
|
||||
return -1;
|
||||
slot = buf[2];
|
||||
index = buf[3];
|
||||
// buf + 4: a byte for, code, padding, slot, index
|
||||
buf16 = (uint16_t *)(buf + 4);
|
||||
abs = buf16[0];
|
||||
rel = buf16[1];
|
||||
key = buf16[2];
|
||||
buf += 12;
|
||||
if (MSS_DEVICE_INFO(abs, rel, key) > len)
|
||||
return -1;
|
||||
|
||||
dst->device_info.code = code;
|
||||
dst->device_info.slot = slot;
|
||||
dst->device_info.index = index;
|
||||
dst->device_info.abs_count = abs;
|
||||
dst->device_info.rel_count = rel;
|
||||
dst->device_info.key_count = key;
|
||||
|
||||
// SOA in c but serialized as AOS
|
||||
for (int i = 0; i < abs; i++) {
|
||||
// buf + 4: 2 bytes for id and 2 bytes of padding
|
||||
uint32_t *buf32 = (uint32_t *)(buf + 4);
|
||||
|
||||
dst->device_info.abs_id[i] = *(uint16_t *)buf;
|
||||
dst->device_info.abs_min[i] = buf32[0];
|
||||
dst->device_info.abs_max[i] = buf32[1];
|
||||
dst->device_info.abs_fuzz[i] = buf32[2];
|
||||
dst->device_info.abs_flat[i] = buf32[3];
|
||||
dst->device_info.abs_res[i] = buf32[4];
|
||||
|
||||
*(MsgMagic*)buf = MSG_MAGIC_START;
|
||||
buf += MSG_MAGIC_SIZE;
|
||||
switch(msg->tag) {
|
||||
case DeviceTagNone:
|
||||
break;
|
||||
case DeviceTagInfo: {
|
||||
*(uint16_t *)buf = DeviceTagInfo;
|
||||
*(uint16_t *)&buf[2] = msg->info.key.len;
|
||||
*(uint8_t *)&buf[4] = msg->info.slot;
|
||||
*(uint8_t *)&buf[5] = msg->info.index;
|
||||
*(uint8_t *)&buf[6] = msg->info.abs.len;
|
||||
*(uint8_t *)&buf[7] = msg->info.rel.len;
|
||||
buf += 8;
|
||||
for(size_t i = 0; i < msg->info.abs.len; i++) {
|
||||
typeof(msg->info.abs.data[i]) e0 = msg->info.abs.data[i];
|
||||
*(uint32_t *)&buf[0] = e0.min;
|
||||
*(uint32_t *)&buf[4] = e0.max;
|
||||
*(uint32_t *)&buf[8] = e0.fuzz;
|
||||
*(uint32_t *)&buf[12] = e0.flat;
|
||||
*(uint32_t *)&buf[16] = e0.res;
|
||||
*(uint16_t *)&buf[20] = e0.id;
|
||||
buf += 24;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rel; i++) {
|
||||
dst->device_info.rel_id[i] = *(uint16_t *)buf;
|
||||
for(size_t i = 0; i < msg->info.rel.len; i++) {
|
||||
typeof(msg->info.rel.data[i]) e0 = msg->info.rel.data[i];
|
||||
*(uint16_t *)&buf[0] = e0.id;
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < key; i++) {
|
||||
dst->device_info.key_id[i] = *(uint16_t *)buf;
|
||||
for(size_t i = 0; i < msg->info.key.len; i++) {
|
||||
typeof(msg->info.key.data[i]) e0 = msg->info.key.data[i];
|
||||
*(uint16_t *)&buf[0] = e0.id;
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
size = MSS_DEVICE_INFO(abs, rel, key) + 1;
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 3) + 1) << 3);
|
||||
break;
|
||||
case DeviceReport:
|
||||
if (len < 7)
|
||||
return -1;
|
||||
|
||||
slot = buf[2];
|
||||
index = buf[3];
|
||||
// buf + 4: a byte for, code, padding, slot and index
|
||||
buf16 = (uint16_t *)(buf + 4);
|
||||
abs = buf16[0];
|
||||
rel = buf16[1];
|
||||
key = buf16[2];
|
||||
buf += 12;
|
||||
if (len < MSS_DEVICE_REPORT(abs, rel, key))
|
||||
return -1;
|
||||
|
||||
dst->device_report.code = code;
|
||||
dst->device_report.slot = slot;
|
||||
dst->device_report.index = index;
|
||||
dst->device_report.abs_count = abs;
|
||||
dst->device_report.rel_count = rel;
|
||||
dst->device_report.key_count = key;
|
||||
|
||||
for (int i = 0; i < abs; i++) {
|
||||
dst->device_report.abs[i] = *(uint32_t *)buf;
|
||||
}
|
||||
case DeviceTagReport: {
|
||||
*(uint16_t *)buf = DeviceTagReport;
|
||||
*(uint16_t *)&buf[2] = msg->report.key.len;
|
||||
*(uint8_t *)&buf[4] = msg->report.slot;
|
||||
*(uint8_t *)&buf[5] = msg->report.index;
|
||||
*(uint8_t *)&buf[6] = msg->report.abs.len;
|
||||
*(uint8_t *)&buf[7] = msg->report.rel.len;
|
||||
buf += 8;
|
||||
for(size_t i = 0; i < msg->report.abs.len; i++) {
|
||||
typeof(msg->report.abs.data[i]) e0 = msg->report.abs.data[i];
|
||||
*(uint32_t *)&buf[0] = e0;
|
||||
buf += 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rel; i++) {
|
||||
dst->device_report.rel[i] = *(uint32_t *)buf;
|
||||
for(size_t i = 0; i < msg->report.rel.len; i++) {
|
||||
typeof(msg->report.rel.data[i]) e0 = msg->report.rel.data[i];
|
||||
*(uint32_t *)&buf[0] = e0;
|
||||
buf += 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < key; i++)
|
||||
dst->device_report.key[i] = *(buf++);
|
||||
|
||||
buf += align_4(key) - key;
|
||||
|
||||
size = MSS_DEVICE_REPORT(abs, rel, key) + 1;
|
||||
break;
|
||||
case ControllerState:
|
||||
if (len < MSS_CONTROLLER_STATE)
|
||||
return -1;
|
||||
|
||||
dst->code = code;
|
||||
dst->controller_state.index = *(uint16_t *)(buf + 2);
|
||||
dst->controller_state.led[0] = buf[4];
|
||||
dst->controller_state.led[1] = buf[5];
|
||||
dst->controller_state.led[2] = buf[6];
|
||||
dst->controller_state.small_rumble = buf[7];
|
||||
dst->controller_state.big_rumble = buf[8];
|
||||
dst->controller_state.flash_on = buf[9];
|
||||
dst->controller_state.flash_off = buf[10];
|
||||
size = MSS_CONTROLLER_STATE + 1;
|
||||
buf += size;
|
||||
break;
|
||||
case Request: {
|
||||
if (len < 3)
|
||||
return -1;
|
||||
|
||||
dst->code = code;
|
||||
dst->request.request_count = *(uint16_t *)(buf + 2);
|
||||
buf += 4; // 1 bytes for code, 1 byte for padding and 2 bytes for count
|
||||
|
||||
int count = dst->request.request_count;
|
||||
TagList *reqs = malloc(count * sizeof(TagList));
|
||||
// The length of the message, will be updated as we read more.
|
||||
int expected_len = 3;
|
||||
|
||||
for (int i = 0; i < dst->request.request_count; i++) {
|
||||
expected_len += 2;
|
||||
if (len < expected_len) {
|
||||
return -1;
|
||||
for(size_t i = 0; i < msg->report.key.len; i++) {
|
||||
typeof(msg->report.key.data[i]) e0 = msg->report.key.data[i];
|
||||
*(uint8_t *)&buf[0] = e0;
|
||||
buf += 1;
|
||||
}
|
||||
|
||||
TagList *tags = &reqs[i];
|
||||
tags->count = *(uint16_t *)buf;
|
||||
tags->tags = malloc(tags->count * sizeof(char *));
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 3) + 1) << 3);
|
||||
break;
|
||||
}
|
||||
case DeviceTagControllerState: {
|
||||
*(uint16_t *)buf = DeviceTagControllerState;
|
||||
*(uint16_t *)&buf[2] = msg->controller_state.index;
|
||||
*(uint8_t *)&buf[4] = msg->controller_state.led[0];
|
||||
*(uint8_t *)&buf[5] = msg->controller_state.led[1];
|
||||
*(uint8_t *)&buf[6] = msg->controller_state.led[2];
|
||||
*(uint8_t *)&buf[7] = msg->controller_state.small_rumble;
|
||||
*(uint8_t *)&buf[8] = msg->controller_state.big_rumble;
|
||||
*(uint8_t *)&buf[9] = msg->controller_state.flash_on;
|
||||
*(uint8_t *)&buf[10] = msg->controller_state.flash_off;
|
||||
buf += 16;
|
||||
break;
|
||||
}
|
||||
case DeviceTagRequest: {
|
||||
*(uint16_t *)buf = DeviceTagRequest;
|
||||
msg->request._version = 1UL;
|
||||
*(uint64_t *)&buf[8] = msg->request._version;
|
||||
*(uint16_t *)&buf[16] = msg->request.requests.len;
|
||||
buf += 18;
|
||||
for(size_t i = 0; i < msg->request.requests.len; i++) {
|
||||
typeof(msg->request.requests.data[i]) e0 = msg->request.requests.data[i];
|
||||
*(uint16_t *)&buf[0] = e0.tags.len;
|
||||
buf += 2;
|
||||
|
||||
for (int j = 0; j < tags->count; j++) {
|
||||
expected_len += 2;
|
||||
if (len < expected_len) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t str_len = *(uint16_t *)buf;
|
||||
for(size_t i = 0; i < e0.tags.len; i++) {
|
||||
typeof(e0.tags.data[i]) e1 = e0.tags.data[i];
|
||||
*(uint16_t *)&buf[0] = e1.name.len;
|
||||
buf += 2;
|
||||
|
||||
expected_len += align_2(str_len);
|
||||
if (len < expected_len) {
|
||||
return -1;
|
||||
for(size_t i = 0; i < e1.name.len; i++) {
|
||||
typeof(e1.name.data[i]) e2 = e1.name.data[i];
|
||||
*(char *)&buf[0] = e2;
|
||||
buf += 1;
|
||||
}
|
||||
|
||||
char *str = malloc(str_len + 1);
|
||||
str[str_len] = '\0';
|
||||
|
||||
strncpy(str, (char *)buf, str_len);
|
||||
|
||||
tags->tags[j] = str;
|
||||
|
||||
buf += align_2(str_len);
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 1) + 1) << 1);
|
||||
}
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 1) + 1) << 1);
|
||||
}
|
||||
|
||||
dst->request.requests = reqs;
|
||||
size = expected_len + 1;
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 3) + 1) << 3);
|
||||
break;
|
||||
}
|
||||
case DeviceDestroy:
|
||||
if (len < MSS_DESTROY)
|
||||
return -1;
|
||||
|
||||
dst->code = code;
|
||||
dst->destroy.index = *(uint16_t *)(buf + 2);
|
||||
size = MSS_DESTROY + 1;
|
||||
buf += size;
|
||||
case DeviceTagDestroy: {
|
||||
*(uint16_t *)buf = DeviceTagDestroy;
|
||||
*(uint16_t *)&buf[2] = msg->destroy.index;
|
||||
buf += 8;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
*(MsgMagic*)buf = MSG_MAGIC_END;
|
||||
buf += MSG_MAGIC_SIZE;
|
||||
if(buf > base_buf + len)
|
||||
return -1;
|
||||
return (int)(buf - base_buf);
|
||||
}
|
||||
|
||||
if (align_m(size) + MAGIC_SIZE > len + 1) {
|
||||
int msg_device_deserialize(const byte *buf, size_t len, DeviceMessage *msg) {
|
||||
const byte *base_buf = buf;
|
||||
if(len < 2 * MSG_MAGIC_SIZE)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// WARN: This is technically bad, but should be ok nonetheless
|
||||
MAGIC_TYPE *mbuf = (MAGIC_TYPE *)align_m((uintptr_t)buf);
|
||||
|
||||
if (*mbuf != MAGIC_END) {
|
||||
printf("NET: Magic not found\n");
|
||||
if(*(MsgMagic*)buf != MSG_MAGIC_START)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return align_m(size) + 2 * MAGIC_SIZE;
|
||||
}
|
||||
|
||||
// Serialize the message msg in buf, buf must be at least 4 aligned. Returns -1 on error (buf not big enough);
|
||||
int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
||||
// If len is less than the two magic and the code we can't serialize any message
|
||||
if (len < MAGIC_SIZE * 2 + 1)
|
||||
return -1;
|
||||
|
||||
*(MAGIC_TYPE *)buf = MAGIC_BEG;
|
||||
buf += MAGIC_SIZE;
|
||||
len -= MAGIC_SIZE + 1;
|
||||
|
||||
uint16_t abs, rel, key, *buf16;
|
||||
uint32_t size;
|
||||
|
||||
switch (msg->code) {
|
||||
case DeviceInfo:
|
||||
abs = msg->device_info.abs_count;
|
||||
rel = msg->device_info.rel_count;
|
||||
key = msg->device_info.key_count;
|
||||
if (len < MSS_DEVICE_INFO(abs, rel, key))
|
||||
return -1;
|
||||
|
||||
buf[0] = (uint8_t)msg->code;
|
||||
// 1 byte of padding
|
||||
buf[2] = (uint8_t)msg->device_info.slot;
|
||||
buf[3] = (uint8_t)msg->device_info.index;
|
||||
// buf + 4: a byte for, code, padding, slot, index
|
||||
buf16 = (uint16_t *)(buf + 4);
|
||||
buf16[0] = abs;
|
||||
buf16[1] = rel;
|
||||
buf16[2] = key;
|
||||
buf += 12;
|
||||
|
||||
// Back to 4 aligned
|
||||
for (int i = 0; i < abs; i++) {
|
||||
// buf + 4: 2 bytes for id and 2 bytes of padding
|
||||
uint32_t *buf32 = (uint32_t *)(buf + 4);
|
||||
|
||||
*(uint16_t *)buf = msg->device_info.abs_id[i];
|
||||
|
||||
buf32[0] = msg->device_info.abs_min[i];
|
||||
buf32[1] = msg->device_info.abs_max[i];
|
||||
buf32[2] = msg->device_info.abs_fuzz[i];
|
||||
buf32[3] = msg->device_info.abs_flat[i];
|
||||
buf32[4] = msg->device_info.abs_res[i];
|
||||
|
||||
buf += MSG_MAGIC_SIZE;
|
||||
DeviceTag tag = *(uint16_t*)buf;
|
||||
switch(tag) {
|
||||
case DeviceTagNone:
|
||||
break;
|
||||
case DeviceTagInfo: {
|
||||
msg->tag = DeviceTagInfo;
|
||||
msg->info.key.len = *(uint16_t *)&buf[2];
|
||||
msg->info.slot = *(uint8_t *)&buf[4];
|
||||
msg->info.index = *(uint8_t *)&buf[5];
|
||||
msg->info.abs.len = *(uint8_t *)&buf[6];
|
||||
msg->info.rel.len = *(uint8_t *)&buf[7];
|
||||
buf += 8;
|
||||
for(size_t i = 0; i < msg->info.abs.len; i++) {
|
||||
typeof(&msg->info.abs.data[i]) e0 = &msg->info.abs.data[i];
|
||||
e0->min = *(uint32_t *)&buf[0];
|
||||
e0->max = *(uint32_t *)&buf[4];
|
||||
e0->fuzz = *(uint32_t *)&buf[8];
|
||||
e0->flat = *(uint32_t *)&buf[12];
|
||||
e0->res = *(uint32_t *)&buf[16];
|
||||
e0->id = *(uint16_t *)&buf[20];
|
||||
buf += 24;
|
||||
}
|
||||
// Still 4 aligned
|
||||
for (int i = 0; i < rel; i++) {
|
||||
*(uint16_t *)buf = msg->device_info.rel_id[i];
|
||||
for(size_t i = 0; i < msg->info.rel.len; i++) {
|
||||
typeof(&msg->info.rel.data[i]) e0 = &msg->info.rel.data[i];
|
||||
e0->id = *(uint16_t *)&buf[0];
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < key; i++) {
|
||||
*(uint16_t *)buf = msg->device_info.key_id[i];
|
||||
for(size_t i = 0; i < msg->info.key.len; i++) {
|
||||
typeof(&msg->info.key.data[i]) e0 = &msg->info.key.data[i];
|
||||
e0->id = *(uint16_t *)&buf[0];
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
size = MSS_DEVICE_INFO(abs, rel, key) + 1;
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 3) + 1) << 3);
|
||||
break;
|
||||
case DeviceReport:
|
||||
abs = msg->device_report.abs_count;
|
||||
rel = msg->device_report.rel_count;
|
||||
key = msg->device_report.key_count;
|
||||
if (len < MSS_DEVICE_REPORT(abs, rel, key))
|
||||
return -1;
|
||||
|
||||
buf[0] = (uint8_t)msg->code;
|
||||
// 1 byte of padding
|
||||
buf[2] = msg->device_report.slot;
|
||||
buf[3] = msg->device_report.index;
|
||||
// buf + 4: a byte for, code, padding, slot and index
|
||||
buf16 = (uint16_t *)(buf + 4);
|
||||
buf16[0] = abs;
|
||||
buf16[1] = rel;
|
||||
buf16[2] = key;
|
||||
buf += 12;
|
||||
// We're 4 aligned already
|
||||
for (int i = 0; i < abs; i++) {
|
||||
*(uint32_t *)buf = msg->device_report.abs[i];
|
||||
}
|
||||
case DeviceTagReport: {
|
||||
msg->tag = DeviceTagReport;
|
||||
msg->report.key.len = *(uint16_t *)&buf[2];
|
||||
msg->report.slot = *(uint8_t *)&buf[4];
|
||||
msg->report.index = *(uint8_t *)&buf[5];
|
||||
msg->report.abs.len = *(uint8_t *)&buf[6];
|
||||
msg->report.rel.len = *(uint8_t *)&buf[7];
|
||||
buf += 8;
|
||||
for(size_t i = 0; i < msg->report.abs.len; i++) {
|
||||
typeof(&msg->report.abs.data[i]) e0 = &msg->report.abs.data[i];
|
||||
*e0 = *(uint32_t *)&buf[0];
|
||||
buf += 4;
|
||||
}
|
||||
// Still 4 aligned
|
||||
for (int i = 0; i < rel; i++) {
|
||||
*(uint32_t *)buf = msg->device_report.rel[i];
|
||||
for(size_t i = 0; i < msg->report.rel.len; i++) {
|
||||
typeof(&msg->report.rel.data[i]) e0 = &msg->report.rel.data[i];
|
||||
*e0 = *(uint32_t *)&buf[0];
|
||||
buf += 4;
|
||||
}
|
||||
// Doesn't matter since we're writing individual bytes
|
||||
for (int i = 0; i < key; i++)
|
||||
*(buf++) = msg->device_report.key[i];
|
||||
|
||||
size = MSS_DEVICE_REPORT(abs, rel, key) + 1;
|
||||
buf += align_4(key) - key;
|
||||
break;
|
||||
case ControllerState:
|
||||
if (len < MSS_CONTROLLER_STATE)
|
||||
return -1;
|
||||
|
||||
buf[0] = (uint8_t)msg->code;
|
||||
|
||||
*(uint16_t *)(buf + 2) = msg->controller_state.index;
|
||||
|
||||
buf[4] = msg->controller_state.led[0];
|
||||
buf[5] = msg->controller_state.led[1];
|
||||
buf[6] = msg->controller_state.led[2];
|
||||
buf[7] = msg->controller_state.small_rumble;
|
||||
buf[8] = msg->controller_state.big_rumble;
|
||||
buf[9] = msg->controller_state.flash_on;
|
||||
buf[10] = msg->controller_state.flash_off;
|
||||
size = MSS_CONTROLLER_STATE + 1;
|
||||
buf += size;
|
||||
break;
|
||||
case Request: {
|
||||
int expected_len = MSS_REQUEST(msg->request.request_count);
|
||||
if (len < expected_len)
|
||||
return -1;
|
||||
|
||||
buf[0] = (uint8_t)msg->code;
|
||||
buf += 2;
|
||||
*(uint16_t *)buf = msg->request.request_count;
|
||||
buf += 2;
|
||||
|
||||
for (int i = 0; i < msg->request.request_count; i++) {
|
||||
|
||||
uint16_t tag_count = msg->request.requests[i].count;
|
||||
char **tags = msg->request.requests[i].tags;
|
||||
|
||||
*(uint16_t *)buf = tag_count;
|
||||
|
||||
buf += 2;
|
||||
|
||||
for (int j = 0; j < tag_count; j++) {
|
||||
int str_len = strlen(tags[j]);
|
||||
int byte_len = align_2(str_len);
|
||||
|
||||
expected_len += 2 + byte_len;
|
||||
if (len < expected_len) {
|
||||
return -1;
|
||||
for(size_t i = 0; i < msg->report.key.len; i++) {
|
||||
typeof(&msg->report.key.data[i]) e0 = &msg->report.key.data[i];
|
||||
*e0 = *(uint8_t *)&buf[0];
|
||||
buf += 1;
|
||||
}
|
||||
|
||||
*(uint16_t *)buf = str_len;
|
||||
buf += 2;
|
||||
|
||||
strncpy((char *)buf, tags[j], str_len);
|
||||
buf += byte_len;
|
||||
}
|
||||
}
|
||||
|
||||
size = expected_len + 1;
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 3) + 1) << 3);
|
||||
break;
|
||||
}
|
||||
case DeviceDestroy:
|
||||
if (len < MSS_DESTROY)
|
||||
return -1;
|
||||
|
||||
buf[0] = (uint8_t)msg->code;
|
||||
|
||||
*(uint16_t *)(buf + 2) = msg->controller_state.index;
|
||||
size = MSS_DESTROY + 1;
|
||||
buf += size;
|
||||
case DeviceTagControllerState: {
|
||||
msg->tag = DeviceTagControllerState;
|
||||
msg->controller_state.index = *(uint16_t *)&buf[2];
|
||||
msg->controller_state.led[0] = *(uint8_t *)&buf[4];
|
||||
msg->controller_state.led[1] = *(uint8_t *)&buf[5];
|
||||
msg->controller_state.led[2] = *(uint8_t *)&buf[6];
|
||||
msg->controller_state.small_rumble = *(uint8_t *)&buf[7];
|
||||
msg->controller_state.big_rumble = *(uint8_t *)&buf[8];
|
||||
msg->controller_state.flash_on = *(uint8_t *)&buf[9];
|
||||
msg->controller_state.flash_off = *(uint8_t *)&buf[10];
|
||||
buf += 16;
|
||||
break;
|
||||
default:
|
||||
printf("ERR(msg_serialize): Trying to serialize unknown message of code %d\n", msg->code);
|
||||
}
|
||||
case DeviceTagRequest: {
|
||||
msg->tag = DeviceTagRequest;
|
||||
msg->request._version = *(uint64_t *)&buf[8];
|
||||
msg->request.requests.len = *(uint16_t *)&buf[16];
|
||||
buf += 18;
|
||||
msg->request.requests.data = malloc(msg->request.requests.len * sizeof(typeof(*msg->request.requests.data)));
|
||||
for(size_t i = 0; i < msg->request.requests.len; i++) {
|
||||
typeof(&msg->request.requests.data[i]) e0 = &msg->request.requests.data[i];
|
||||
e0->tags.len = *(uint16_t *)&buf[0];
|
||||
buf += 2;
|
||||
e0->tags.data = malloc(e0->tags.len * sizeof(typeof(*e0->tags.data)));
|
||||
for(size_t i = 0; i < e0->tags.len; i++) {
|
||||
typeof(&e0->tags.data[i]) e1 = &e0->tags.data[i];
|
||||
e1->name.len = *(uint16_t *)&buf[0];
|
||||
buf += 2;
|
||||
e1->name.data = malloc(e1->name.len * sizeof(typeof(*e1->name.data)));
|
||||
for(size_t i = 0; i < e1->name.len; i++) {
|
||||
typeof(&e1->name.data[i]) e2 = &e1->name.data[i];
|
||||
*e2 = *(char *)&buf[0];
|
||||
buf += 1;
|
||||
}
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 1) + 1) << 1);
|
||||
}
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 1) + 1) << 1);
|
||||
}
|
||||
buf = (byte*)(((((uintptr_t)buf - 1) >> 3) + 1) << 3);
|
||||
if(msg->request._version != 1UL) {
|
||||
printf("Mismatched version: peers aren't the same version, expected 1 got %lu.\n", msg->request._version);
|
||||
msg_device_free(msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (align_m(size) + MAGIC_SIZE > len) {
|
||||
break;
|
||||
}
|
||||
case DeviceTagDestroy: {
|
||||
msg->tag = DeviceTagDestroy;
|
||||
msg->destroy.index = *(uint16_t *)&buf[2];
|
||||
buf += 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(*(MsgMagic*)buf != MSG_MAGIC_END) {
|
||||
msg_device_free(msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MAGIC_TYPE *mbuf = (MAGIC_TYPE *)align_m((uintptr_t)buf);
|
||||
|
||||
*mbuf = MAGIC_END;
|
||||
|
||||
return align_m(size) + MAGIC_SIZE * 2;
|
||||
buf += MSG_MAGIC_SIZE;
|
||||
if(buf > base_buf + len) {
|
||||
msg_device_free(msg);
|
||||
return -1;
|
||||
}
|
||||
return (int)(buf - base_buf);
|
||||
}
|
||||
|
||||
void msg_free(Message *msg) {
|
||||
if (msg->code == Request) {
|
||||
for (int i = 0; i < msg->request.request_count; i++) {
|
||||
for (int j = 0; j < msg->request.requests[i].count; j++) {
|
||||
free(msg->request.requests[i].tags[j]);
|
||||
void msg_device_free(DeviceMessage *msg) {
|
||||
switch(msg->tag) {
|
||||
case DeviceTagNone:
|
||||
break;
|
||||
case DeviceTagInfo: {
|
||||
break;
|
||||
}
|
||||
free(msg->request.requests[i].tags);
|
||||
case DeviceTagReport: {
|
||||
break;
|
||||
}
|
||||
free(msg->request.requests);
|
||||
case DeviceTagControllerState: {
|
||||
break;
|
||||
}
|
||||
case DeviceTagRequest: {
|
||||
for(size_t i = 0; i < msg->request.requests.len; i++) {
|
||||
typeof(msg->request.requests.data[i]) e0 = msg->request.requests.data[i];
|
||||
for(size_t i = 0; i < e0.tags.len; i++) {
|
||||
typeof(e0.tags.data[i]) e1 = e0.tags.data[i];
|
||||
free(e1.name.data);
|
||||
}
|
||||
|
||||
void print_message_buffer(const uint8_t *buf, int len) {
|
||||
bool last_beg = false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i + MAGIC_SIZE <= len) {
|
||||
MAGIC_TYPE magic = *(MAGIC_TYPE *)(&buf[i]);
|
||||
if (magic == MAGIC_BEG) {
|
||||
printf(" \033[32m%08X\033[0m", magic);
|
||||
i += MAGIC_SIZE - 1;
|
||||
last_beg = true;
|
||||
continue;
|
||||
} else if (magic == MAGIC_END) {
|
||||
printf(" \033[32m%08X\033[0m", magic);
|
||||
i += MAGIC_SIZE - 1;
|
||||
continue;
|
||||
free(e0.tags.data);
|
||||
}
|
||||
free(msg->request.requests.data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (last_beg) {
|
||||
last_beg = false;
|
||||
printf(" \033[034m%02X\033[0m", buf[i]);
|
||||
} else {
|
||||
printf(" %02X", buf[i]);
|
||||
case DeviceTagDestroy: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
205
net.h
205
net.h
|
@ -1,122 +1,133 @@
|
|||
// vi:ft=c
|
||||
#ifndef NET_H_
|
||||
#define NET_H_
|
||||
#include "util.h"
|
||||
|
||||
#include <linux/input-event-codes.h>
|
||||
// Generated file, do not edit (its not like it'll explode if you do, but its better not to)
|
||||
#ifndef NET_H
|
||||
#define NET_H
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAGIC_TYPE uint32_t
|
||||
#define MAGIC_SIZE sizeof(MAGIC_TYPE)
|
||||
static const MAGIC_TYPE MAGIC_BEG = 0xDEADCAFE;
|
||||
static const MAGIC_TYPE MAGIC_END = 0xCAFEDEAD;
|
||||
// Align n to the next MAGIC boundary
|
||||
static inline size_t align_m(uintptr_t n) { return (((n - 1) >> 2) + 1) << 2; }
|
||||
typedef unsigned char byte;
|
||||
typedef uint64_t MsgMagic;
|
||||
|
||||
typedef enum {
|
||||
NoMessage = 0,
|
||||
DeviceInfo = 1,
|
||||
DeviceReport = 2,
|
||||
DeviceDestroy = 3,
|
||||
ControllerState = 4,
|
||||
Request = 5,
|
||||
} MessageCode;
|
||||
#define MSG_MAGIC_SIZE sizeof(MsgMagic)
|
||||
static const MsgMagic MSG_MAGIC_START = 0xCAFEF00DBEEFDEAD;
|
||||
static const MsgMagic MSG_MAGIC_END = 0xF00DBEEFCAFEDEAD;
|
||||
|
||||
// Alignment 4
|
||||
typedef struct {
|
||||
MessageCode code;
|
||||
// + 1 byte of padding
|
||||
typedef struct Abs {
|
||||
uint16_t id;
|
||||
uint32_t min;
|
||||
uint32_t max;
|
||||
uint32_t fuzz;
|
||||
uint32_t flat;
|
||||
uint32_t res;
|
||||
} Abs;
|
||||
|
||||
typedef struct Key {
|
||||
uint16_t id;
|
||||
} Key;
|
||||
|
||||
typedef struct Rel {
|
||||
uint16_t id;
|
||||
} Rel;
|
||||
|
||||
typedef struct Tag {
|
||||
struct {
|
||||
uint16_t len;
|
||||
char *data;
|
||||
} name;
|
||||
} Tag;
|
||||
|
||||
typedef struct TagList {
|
||||
struct {
|
||||
uint16_t len;
|
||||
struct Tag *data;
|
||||
} tags;
|
||||
} TagList;
|
||||
|
||||
// Device
|
||||
|
||||
typedef enum DeviceTag {
|
||||
DeviceTagNone = 0,
|
||||
DeviceTagInfo = 1,
|
||||
DeviceTagReport = 2,
|
||||
DeviceTagControllerState = 3,
|
||||
DeviceTagRequest = 4,
|
||||
DeviceTagDestroy = 5,
|
||||
} DeviceTag;
|
||||
|
||||
typedef struct DeviceInfo {
|
||||
DeviceTag tag;
|
||||
uint8_t slot;
|
||||
uint8_t index;
|
||||
struct {
|
||||
uint8_t len;
|
||||
struct Abs data[64];
|
||||
} abs;
|
||||
struct {
|
||||
uint8_t len;
|
||||
struct Rel data[16];
|
||||
} rel;
|
||||
struct {
|
||||
uint16_t len;
|
||||
struct Key data[768];
|
||||
} key;
|
||||
} DeviceInfo;
|
||||
|
||||
uint16_t abs_count;
|
||||
uint16_t rel_count;
|
||||
uint16_t key_count;
|
||||
|
||||
uint16_t abs_id[ABS_CNT];
|
||||
// + 2 bytes of padding per abs
|
||||
uint32_t abs_min[ABS_CNT];
|
||||
uint32_t abs_max[ABS_CNT];
|
||||
uint32_t abs_fuzz[ABS_CNT];
|
||||
uint32_t abs_flat[ABS_CNT];
|
||||
uint32_t abs_res[ABS_CNT];
|
||||
|
||||
uint16_t rel_id[REL_CNT];
|
||||
|
||||
uint16_t key_id[KEY_CNT];
|
||||
} MessageDeviceInfo;
|
||||
#define MSS_DEVICE_INFO(abs, rel, key) (10 + abs * 24 + rel * 2 + key * 2 + 1)
|
||||
// MSS -> Message Serialized Size:
|
||||
// Size of the data of the message when serialized (no alignment / padding)
|
||||
|
||||
// 4 aligned
|
||||
typedef struct {
|
||||
MessageCode code;
|
||||
// + 1 byte of padding
|
||||
|
||||
typedef struct DeviceReport {
|
||||
DeviceTag tag;
|
||||
uint8_t slot;
|
||||
uint8_t index;
|
||||
struct {
|
||||
uint8_t len;
|
||||
uint32_t data[64];
|
||||
} abs;
|
||||
struct {
|
||||
uint8_t len;
|
||||
uint32_t data[16];
|
||||
} rel;
|
||||
struct {
|
||||
uint16_t len;
|
||||
uint8_t data[768];
|
||||
} key;
|
||||
} DeviceReport;
|
||||
|
||||
uint16_t abs_count;
|
||||
uint16_t rel_count;
|
||||
uint16_t key_count;
|
||||
|
||||
uint32_t abs[ABS_CNT];
|
||||
uint32_t rel[REL_CNT];
|
||||
uint8_t key[KEY_CNT];
|
||||
} MessageDeviceReport;
|
||||
#define MSS_DEVICE_REPORT(abs, rel, key) (11 + abs * 4 + rel * 4 + align_4(key))
|
||||
|
||||
// 1 aligned
|
||||
typedef struct {
|
||||
MessageCode code;
|
||||
// + 1 byte of padding
|
||||
|
||||
typedef struct DeviceControllerState {
|
||||
DeviceTag tag;
|
||||
uint16_t index;
|
||||
uint8_t led[3];
|
||||
uint8_t small_rumble;
|
||||
uint8_t big_rumble;
|
||||
uint8_t flash_on;
|
||||
uint8_t flash_off;
|
||||
} MessageControllerState;
|
||||
#define MSS_CONTROLLER_STATE 10
|
||||
} DeviceControllerState;
|
||||
|
||||
typedef struct {
|
||||
char **tags;
|
||||
uint16_t count;
|
||||
} TagList;
|
||||
|
||||
typedef struct {
|
||||
MessageCode code;
|
||||
// + 1 byte of padding
|
||||
|
||||
TagList *requests;
|
||||
uint16_t request_count;
|
||||
} MessageRequest;
|
||||
#define MSS_REQUEST(count) (2 + 2 * count + 1)
|
||||
|
||||
typedef struct {
|
||||
MessageCode code;
|
||||
// + 1 byte of padding
|
||||
typedef struct DeviceRequest {
|
||||
DeviceTag tag;
|
||||
struct {
|
||||
uint16_t len;
|
||||
struct TagList *data;
|
||||
} requests;
|
||||
uint64_t _version;
|
||||
} DeviceRequest;
|
||||
|
||||
typedef struct DeviceDestroy {
|
||||
DeviceTag tag;
|
||||
uint16_t index;
|
||||
} MessageDestroy;
|
||||
#define MSS_DESTROY 3
|
||||
} DeviceDestroy;
|
||||
|
||||
typedef union {
|
||||
MessageCode code;
|
||||
MessageRequest request;
|
||||
MessageDestroy destroy;
|
||||
MessageDeviceInfo device_info;
|
||||
MessageDeviceReport device_report;
|
||||
MessageControllerState controller_state;
|
||||
} Message;
|
||||
typedef union DeviceMessage {
|
||||
DeviceTag tag;
|
||||
DeviceInfo info;
|
||||
DeviceReport report;
|
||||
DeviceControllerState controller_state;
|
||||
DeviceRequest request;
|
||||
DeviceDestroy destroy;
|
||||
} DeviceMessage;
|
||||
|
||||
int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst);
|
||||
int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg);
|
||||
void msg_free(Message *msg);
|
||||
void print_message_buffer(const uint8_t *buf, int len);
|
||||
// Serialize the message msg to buffer dst of size len, returns the length of the serialized message, or -1 on error (buffer overflow)
|
||||
int msg_device_serialize(byte *dst, size_t len, DeviceMessage *msg);
|
||||
// Deserialize the message in the buffer src of size len into dst, return the length of the serialized message or -1 on error.
|
||||
int msg_device_deserialize(const byte *src, size_t len, DeviceMessage *dst);
|
||||
|
||||
// Free the message (created by msg_device_deserialize)
|
||||
void msg_device_free(DeviceMessage *msg);
|
||||
#endif
|
||||
|
|
68
net.ser
68
net.ser
|
@ -1,63 +1,3 @@
|
|||
// Abs -> { whatever: u16, field1: { a: (invalid)[^1] }, field2: A[1] }
|
||||
|
||||
// AbsArr -> { whatever: u16, field1: { a: (invalid)[^1] }, field2: A[1] }&[]
|
||||
// AbsArrArr -> { whatever: u16, field1: { a: (invalid)[^1] }, field2: A[1] }&[]&[][4]
|
||||
|
||||
// A -> { a: (invalid)[^1] }
|
||||
|
||||
// AA -> { a: (invalid)[^1] }[1]
|
||||
|
||||
// RBASIC -> (invalid)
|
||||
// RM1 -> (invalid)
|
||||
// RM2 -> (invalid)
|
||||
// RM3 -> (invalid)
|
||||
|
||||
// R1 -> (invalid)&[4]&[]
|
||||
// R2 -> (invalid)&[4]
|
||||
// R3 -> (invalid)&[4]
|
||||
// R4 -> (invalid)
|
||||
// R5 -> (invalid)
|
||||
|
||||
// SA -> { field: { field: SA } }
|
||||
// SC -> { field: SC&[] }
|
||||
// Node -> { data: u8, children: Node&[] }
|
||||
// Recur -> { field: Recur }
|
||||
// SB -> { field: { field: SB } }
|
||||
|
||||
// bool -> bool
|
||||
// char -> char
|
||||
// i16 -> i16
|
||||
// i32 -> i32
|
||||
// i64 -> i64
|
||||
// i8 -> i8
|
||||
// u16 -> u16
|
||||
// u32 -> u32
|
||||
// u64 -> u64
|
||||
// u8 -> u8
|
||||
|
||||
// Struct Layout: S2
|
||||
// field[0].field.len align(2) size(2)
|
||||
// field[1].field.len align(2) size(2)
|
||||
// field[0].field.data align(0) size(0)
|
||||
// field[1].field.data align(0) size(0)
|
||||
// Struct Layout: SC
|
||||
// field.len align(2) size(2)
|
||||
// field.data align(0) size(0)
|
||||
// Struct Layout: Node
|
||||
// children.len align(2) size(2)
|
||||
// data align(1) size(1)
|
||||
// children.data align(0) size(0)
|
||||
// Struct Layout: S1
|
||||
// field.len align(1) size(1)
|
||||
// field.data align(0) size(0)
|
||||
// Struct Layout: Abs
|
||||
// whatever align(2) size(2)
|
||||
// Struct Layout: S3
|
||||
// field.len align(2) size(2)
|
||||
// field.data align(0) size(0)
|
||||
// Struct Layout: Rel
|
||||
// a align(2) size(2)
|
||||
|
||||
struct Abs {
|
||||
id: u16,
|
||||
min: u32,
|
||||
|
@ -83,6 +23,11 @@ struct Tag {
|
|||
name: char[],
|
||||
}
|
||||
|
||||
struct TagList {
|
||||
tags: Tag[],
|
||||
}
|
||||
|
||||
version(1);
|
||||
messages Device {
|
||||
Info {
|
||||
slot: u8,
|
||||
|
@ -110,8 +55,7 @@ messages Device {
|
|||
}
|
||||
#[versioned]
|
||||
Request {
|
||||
requests: Tag[][],
|
||||
request_count: u16,
|
||||
requests: TagList[],
|
||||
}
|
||||
Destroy {
|
||||
index: u16,
|
||||
|
|
|
@ -166,3 +166,19 @@ char *pascal_to_snake_case(StringSlice str) {
|
|||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
char *snake_case_to_screaming_snake_case(StringSlice str) {
|
||||
CharVec res = vec_init();
|
||||
vec_grow(&res, str.len + 4);
|
||||
for(size_t i = 0; i < str.len; i++) {
|
||||
char c = str.ptr[i];
|
||||
if('a' <= c && c <= 'z') {
|
||||
vec_push(&res, c - 'a' + 'A');
|
||||
} else {
|
||||
vec_push(&res, c);
|
||||
}
|
||||
}
|
||||
vec_push(&res, '\0');
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ void wt_format(Writer *w, const char *fmt, ...);
|
|||
// Define the structs of a program in the correct order (respecting direct dependencies)
|
||||
void define_structs(Program *p, Writer *w, void (*define)(Writer *w, StructObject *));
|
||||
char *pascal_to_snake_case(StringSlice str);
|
||||
char *snake_case_to_screaming_snake_case(StringSlice str);
|
||||
|
||||
// Check if c is aligned to alignment to
|
||||
static inline bool calign_is_aligned(CurrentAlignment c, Alignment to) {
|
||||
|
|
218
ser/codegen_c.c
218
ser/codegen_c.c
|
@ -195,6 +195,10 @@ void write_struct(Writer *w, StructObject *obj) {
|
|||
wt_format(w, "} %.*s;\n\n", obj->name.len, obj->name.ptr);
|
||||
}
|
||||
|
||||
void write_align(Writer *w, const char *var, const Alignment align, size_t indent) {
|
||||
wt_format(w, "%*s%s = (byte*)(((((uintptr_t)%s - 1) >> %u) + 1) << %u);\n", indent, "", var, var, align.po2, align.po2);
|
||||
}
|
||||
|
||||
void write_accessor(Writer *w, TypeObject *base_type, FieldAccessor fa, bool ptr) {
|
||||
if (fa.indices.len == 0)
|
||||
return;
|
||||
|
@ -245,6 +249,37 @@ void write_accessor(Writer *w, TypeObject *base_type, FieldAccessor fa, bool ptr
|
|||
}
|
||||
}
|
||||
|
||||
bool is_field_accessor_heap_array(FieldAccessor fa, TypeObject *base_type) {
|
||||
if (fa.indices.len == 0)
|
||||
return base_type->kind == TypeArray && base_type->type.array.heap;
|
||||
|
||||
// In the case of a heap array the last index will choose between length and data,
|
||||
// but since we only care about the array
|
||||
fa.indices.len--;
|
||||
|
||||
TypeObject *t = base_type;
|
||||
for (size_t i = 0; i < fa.indices.len; i++) {
|
||||
uint64_t index = fa.indices.data[i];
|
||||
|
||||
if (t->kind == TypeStruct) {
|
||||
StructObject *st = (StructObject *)&t->type.struct_;
|
||||
t = st->fields.data[index].type;
|
||||
} else if (t->kind == TypeArray) {
|
||||
if (t->type.array.sizing == SizingMax) {
|
||||
if (index == 0) {
|
||||
return false;
|
||||
} else {
|
||||
t = t->type.array.type;
|
||||
}
|
||||
} else {
|
||||
t = t->type.array.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t->kind == TypeArray && t->type.array.heap;
|
||||
}
|
||||
|
||||
void write_type_serialization(
|
||||
Writer *w, const char *base, bool ptr, Layout *layout, CurrentAlignment al, Hashmap *layouts, size_t indent, size_t depth
|
||||
) {
|
||||
|
@ -275,14 +310,15 @@ void write_type_serialization(
|
|||
|
||||
for (; i < layout->fields.len; i++) {
|
||||
FieldAccessor farr = layout->fields.data[i];
|
||||
wt_format(w, "%*sfor(size_t i = 0; i < %s", indent, "", base);
|
||||
FieldAccessor flen = field_accessor_clone(&farr);
|
||||
// Access the length instead of data
|
||||
flen.indices.data[flen.indices.len - 1] = 0;
|
||||
|
||||
wt_format(w, "%*sfor(size_t i = 0; i < %s", indent, "", base);
|
||||
write_accessor(w, layout->type, flen, ptr);
|
||||
field_accessor_drop(flen);
|
||||
char *vname = msprintf("e%lu", depth);
|
||||
wt_format(w, "; i++) {\n%*stypeof(%s", indent, "", base);
|
||||
wt_format(w, "; i++) {\n%*stypeof(%s", indent + INDENT, "", base);
|
||||
write_accessor(w, layout->type, farr, ptr);
|
||||
wt_format(w, "[i]) %s = %s", vname, base);
|
||||
write_accessor(w, layout->type, farr, ptr);
|
||||
|
@ -303,7 +339,7 @@ void write_type_serialization(
|
|||
wt_format(w, "%*s}\n", indent, "");
|
||||
free(vname);
|
||||
}
|
||||
wt_format(w, "%*sbuf = (byte*)(((uintptr_t)buf - %u) & -%u);\n", indent, "", align.mask, align.value);
|
||||
write_align(w, "buf", align, indent);
|
||||
} else {
|
||||
offset += calign_to(al, align);
|
||||
wt_format(w, "%*sbuf += %lu;\n", indent, "", offset);
|
||||
|
@ -346,10 +382,20 @@ void write_type_deserialization(
|
|||
|
||||
for (; i < layout->fields.len; i++) {
|
||||
FieldAccessor farr = layout->fields.data[i];
|
||||
wt_format(w, "%*sfor(size_t i = 0; i < %s", indent, "", base);
|
||||
FieldAccessor flen = field_accessor_clone(&farr);
|
||||
// Access the length instead of data
|
||||
flen.indices.data[flen.indices.len - 1] = 0;
|
||||
|
||||
if (is_field_accessor_heap_array(farr, layout->type)) {
|
||||
wt_format(w, "%*s%s", indent, "", base);
|
||||
write_accessor(w, layout->type, farr, ptr);
|
||||
wt_format(w, " = malloc(%s", base);
|
||||
write_accessor(w, layout->type, flen, ptr);
|
||||
wt_format(w, " * sizeof(typeof(*%s", base);
|
||||
write_accessor(w, layout->type, farr, ptr);
|
||||
wt_format(w, ")));\n");
|
||||
}
|
||||
wt_format(w, "%*sfor(size_t i = 0; i < %s", indent, "", base);
|
||||
write_accessor(w, layout->type, flen, ptr);
|
||||
field_accessor_drop(flen);
|
||||
char *vname = msprintf("e%lu", depth);
|
||||
|
@ -374,24 +420,86 @@ void write_type_deserialization(
|
|||
wt_format(w, "%*s}\n", indent, "");
|
||||
free(vname);
|
||||
}
|
||||
wt_format(w, "%*sbuf = (byte*)(((uintptr_t)buf - %u) & -%u);\n", indent, "", align.mask, align.value);
|
||||
write_align(w, "buf", align, indent);
|
||||
} else {
|
||||
offset += calign_to(al, align);
|
||||
wt_format(w, "%*sbuf += %lu;\n", indent, "", offset);
|
||||
}
|
||||
}
|
||||
|
||||
int write_type_free(Writer *w, const char *base, TypeObject *type, size_t indent, size_t depth) {
|
||||
if (type->kind == TypePrimitif) {
|
||||
return 0;
|
||||
} else if (type->kind == TypeArray) {
|
||||
BufferedWriter b = buffered_writer_init();
|
||||
Writer *w2 = (Writer *)&b;
|
||||
|
||||
int total = 0;
|
||||
|
||||
wt_format(w2, "%*sfor(size_t i = 0; i < ", indent, "");
|
||||
if (type->type.array.sizing == SizingMax) {
|
||||
wt_format(w2, "%s.len; i++) {\n", base);
|
||||
wt_format(w2, "%*stypeof(%s.data[i]) e%lu = %s.data[i];\n", indent + INDENT, "", base, depth, base);
|
||||
} else {
|
||||
wt_format(w2, "%lu; i++) {\n", type->type.array.size);
|
||||
wt_format(w2, "%*stypeof(%s[i]) e%lu = %s[i];\n", indent + INDENT, "", base, depth, base);
|
||||
}
|
||||
|
||||
char *new_base = msprintf("e%lu", depth);
|
||||
total += write_type_free(w2, new_base, type->type.array.type, indent + INDENT, depth + 1);
|
||||
free(new_base);
|
||||
wt_format(w2, "%*s}\n", indent, "");
|
||||
|
||||
if (total > 0) {
|
||||
wt_write(w, b.buf.data, b.buf.len);
|
||||
}
|
||||
buffered_writer_drop(b);
|
||||
|
||||
if (type->type.array.heap) {
|
||||
wt_format(w, "%*sfree(%s.data);\n", indent, "", base);
|
||||
total++;
|
||||
}
|
||||
|
||||
return total;
|
||||
} else if (type->kind == TypeStruct) {
|
||||
StructObject *s = (StructObject *)&type->type.struct_;
|
||||
int total = 0;
|
||||
|
||||
for (size_t i = 0; i < s->fields.len; i++) {
|
||||
Field f = s->fields.data[i];
|
||||
char *new_base = msprintf("%s.%.*s", base, f.name.len, f.name.ptr);
|
||||
total += write_type_free(w, new_base, f.type, indent, depth);
|
||||
free(new_base);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
||||
char *uc_name = snake_case_to_screaming_snake_case((StringSlice){.ptr = name, .len = strlen(name)});
|
||||
wt_format(
|
||||
header,
|
||||
"// Generated file, do not edit (its not like it'll explode if you do, but its better not to)\n"
|
||||
"#ifndef %s_H\n"
|
||||
"#define %s_H\n"
|
||||
"#include <stdint.h>\n"
|
||||
"#include <stdlib.h>\n"
|
||||
"#include <stdbool.h>\n"
|
||||
"\n"
|
||||
"typedef unsigned char byte;\n"
|
||||
"typedef uint64_t MsgMagic;\n"
|
||||
"\n"
|
||||
"#define MSG_MAGIC_SIZE sizeof(MsgMagic)\n"
|
||||
"static const MsgMagic MSG_MAGIC_START = 0xCAFEF00DBEEFDEAD;\n"
|
||||
"static const MsgMagic MSG_MAGIC_END = 0xF00DBEEFCAFEDEAD;\n"
|
||||
"\n",
|
||||
uc_name,
|
||||
uc_name
|
||||
);
|
||||
free(uc_name);
|
||||
wt_format(
|
||||
source,
|
||||
"// Generated file, do not edit (its not like it'll explode if you do, but its better not to)\n"
|
||||
|
@ -407,7 +515,16 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
MessagesObject msgs = p->messages.data[i];
|
||||
|
||||
wt_format(header, "// %.*s\n\n", msgs.name.len, msgs.name.ptr);
|
||||
wt_format(header, "typedef enum %.*sTag {\n", msgs.name.len, msgs.name.ptr);
|
||||
wt_format(
|
||||
header,
|
||||
"typedef enum %.*sTag {\n%*s%.*sTagNone = 0,\n",
|
||||
msgs.name.len,
|
||||
msgs.name.ptr,
|
||||
INDENT,
|
||||
"",
|
||||
msgs.name.len,
|
||||
msgs.name.ptr
|
||||
);
|
||||
for (size_t j = 0; j < msgs.messages.len; j++) {
|
||||
wt_format(
|
||||
header,
|
||||
|
@ -418,7 +535,7 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
msgs.name.ptr,
|
||||
msgs.messages.data[j].name.len,
|
||||
msgs.messages.data[j].name.ptr,
|
||||
j
|
||||
j + 1
|
||||
);
|
||||
}
|
||||
wt_format(header, "} %.*sTag;\n\n", msgs.name.len, msgs.name.ptr);
|
||||
|
@ -477,6 +594,15 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
msgs.name.len,
|
||||
msgs.name.ptr
|
||||
);
|
||||
wt_format(
|
||||
header,
|
||||
"// Free the message (created by msg_%s_deserialize)\n"
|
||||
"void msg_%s_free(%.*sMessage *msg);\n",
|
||||
name,
|
||||
name,
|
||||
msgs.name.len,
|
||||
msgs.name.ptr
|
||||
);
|
||||
|
||||
char *tag_type = msprintf("%.*sTag", msgs.name.len, msgs.name.ptr);
|
||||
PointerVec message_tos = vec_init();
|
||||
|
@ -509,8 +635,13 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
msgs.name.ptr
|
||||
);
|
||||
|
||||
wt_format(source, "%*sbyte *base_buf = buf;\n%*s%s tag = msg->tag;\n", INDENT, "", INDENT, "", tag_type);
|
||||
wt_format(source, "%*sswitch(tag) {\n", INDENT, "");
|
||||
wt_format(source, "%*sconst byte *base_buf = buf;\n", INDENT, "");
|
||||
wt_format(source, "%*sif(len < 2 * MSG_MAGIC_SIZE)\n", INDENT, "");
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 2, "");
|
||||
wt_format(source, "%*s*(MsgMagic*)buf = MSG_MAGIC_START;\n", INDENT, "");
|
||||
wt_format(source, "%*sbuf += MSG_MAGIC_SIZE;\n", INDENT, "");
|
||||
wt_format(source, "%*sswitch(msg->tag) {\n", INDENT, "");
|
||||
wt_format(source, "%*scase %sNone:\n%*sbreak;\n", INDENT, "", tag_type, INDENT * 2, "");
|
||||
|
||||
for (size_t j = 0; j < msgs.messages.len; j++) {
|
||||
MessageObject m = msgs.messages.data[j];
|
||||
|
@ -541,7 +672,10 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
free(snake_case_name);
|
||||
}
|
||||
wt_format(source, "%*s}\n", INDENT, "");
|
||||
wt_format(source, "%*sbuf = (byte*)(((uintptr_t)buf - %u) & -%u);\n", INDENT, "", ALIGN_8.mask, ALIGN_8.value);
|
||||
wt_format(source, "%*s*(MsgMagic*)buf = MSG_MAGIC_END;\n", INDENT, "");
|
||||
wt_format(source, "%*sbuf += MSG_MAGIC_SIZE;\n", INDENT, "");
|
||||
wt_format(source, "%*sif(buf > base_buf + len)\n", INDENT, "");
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 2, "");
|
||||
wt_format(source, "%*sreturn (int)(buf - base_buf);\n", INDENT, "");
|
||||
wt_format(source, "}\n");
|
||||
}
|
||||
|
@ -555,8 +689,16 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
msgs.name.ptr
|
||||
);
|
||||
|
||||
wt_format(source, "%*sconst byte *base_buf = buf;\n%*s%s tag = *(uint16_t*)buf;\n", INDENT, "", INDENT, "", tag_type);
|
||||
wt_format(source, "%*sconst byte *base_buf = buf;\n", INDENT, "");
|
||||
wt_format(source, "%*sif(len < 2 * MSG_MAGIC_SIZE)\n", INDENT, "");
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 2, "");
|
||||
wt_format(source, "%*sif(*(MsgMagic*)buf != MSG_MAGIC_START)\n", INDENT, "");
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 2, "");
|
||||
wt_format(source, "%*sbuf += MSG_MAGIC_SIZE;\n", INDENT, "");
|
||||
wt_format(source, "%*s%s tag = *(uint16_t*)buf;\n", INDENT, "", tag_type);
|
||||
wt_format(source, "%*sswitch(tag) {\n", INDENT, "");
|
||||
wt_format(source, "%*scase %sNone:\n%*sbreak;\n", INDENT, "", tag_type, INDENT * 2, "");
|
||||
|
||||
|
||||
for (size_t j = 0; j < msgs.messages.len; j++) {
|
||||
MessageObject m = msgs.messages.data[j];
|
||||
|
@ -579,19 +721,12 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
0
|
||||
);
|
||||
if (m.attributes & Attr_versioned) {
|
||||
wt_format(
|
||||
source,
|
||||
"%*sif(msg->%s._version != %luUL) {\n%*sprintf(\"Mismatched message version: peers aren't the same "
|
||||
"version.\\n\");\n%*s}\n",
|
||||
INDENT * 2,
|
||||
"",
|
||||
snake_case_name,
|
||||
msgs.version,
|
||||
INDENT * 3,
|
||||
"",
|
||||
INDENT * 2,
|
||||
""
|
||||
);
|
||||
wt_format(source, "%*sif(msg->%s._version != %luUL) {\n", INDENT * 2, "", snake_case_name, msgs.version);
|
||||
wt_format(source, "%*sprintf(\"Mismatched version: peers aren't the same version", INDENT * 3, "");
|
||||
wt_format(source, ", expected %lu got %%lu.\\n\", msg->%s._version);\n", msgs.version, snake_case_name);
|
||||
wt_format(source, "%*smsg_%s_free(msg);\n", INDENT * 3, "", name);
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 3, "");
|
||||
wt_format(source, "%*s}\n", INDENT * 2, "");
|
||||
}
|
||||
wt_format(source, "%*sbreak;\n%*s}\n", INDENT * 2, "", INDENT, "");
|
||||
|
||||
|
@ -599,10 +734,43 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
free(snake_case_name);
|
||||
}
|
||||
wt_format(source, "%*s}\n", INDENT, "");
|
||||
wt_format(source, "%*sif(*(MsgMagic*)buf != MSG_MAGIC_END) {\n", INDENT, "");
|
||||
wt_format(source, "%*smsg_%s_free(msg);\n", INDENT * 2, "", name);
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 2, "");
|
||||
wt_format(source, "%*s}\n", INDENT, "");
|
||||
wt_format(source, "%*sbuf += MSG_MAGIC_SIZE;\n", INDENT, "");
|
||||
wt_format(source, "%*sif(buf > base_buf + len) {\n", INDENT, "");
|
||||
wt_format(source, "%*smsg_%s_free(msg);\n", INDENT * 2, "", name);
|
||||
wt_format(source, "%*sreturn -1;\n", INDENT * 2, "");
|
||||
wt_format(source, "%*s}\n", INDENT, "");
|
||||
wt_format(source, "%*sreturn (int)(buf - base_buf);\n", INDENT, "");
|
||||
wt_format(source, "}\n");
|
||||
}
|
||||
|
||||
{
|
||||
wt_format(source, "\nvoid msg_%s_free(%.*sMessage *msg) {\n", name, msgs.name.len, msgs.name.ptr);
|
||||
|
||||
wt_format(source, "%*sswitch(msg->tag) {\n", INDENT, "");
|
||||
wt_format(source, "%*scase %sNone:\n%*sbreak;\n", INDENT, "", tag_type, INDENT * 2, "");
|
||||
|
||||
for (size_t j = 0; j < msgs.messages.len; j++) {
|
||||
MessageObject m = msgs.messages.data[j];
|
||||
TypeObject *mtype = message_tos.data[j];
|
||||
|
||||
char *snake_case_name = pascal_to_snake_case(m.name);
|
||||
char *base = msprintf("msg->%s", snake_case_name);
|
||||
|
||||
wt_format(source, "%*scase %s%.*s: {\n", INDENT, "", tag_type, m.name.len, m.name.ptr);
|
||||
write_type_free(source, base, mtype, INDENT * 2, 0);
|
||||
wt_format(source, "%*sbreak;\n%*s}\n", INDENT * 2, "", INDENT, "");
|
||||
|
||||
free(base);
|
||||
free(snake_case_name);
|
||||
}
|
||||
wt_format(source, "%*s}\n", INDENT, "");
|
||||
wt_format(source, "}\n");
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < message_tos.len; j++) {
|
||||
TypeObject *to = message_tos.data[j];
|
||||
StructObject *s = (StructObject *)&to->type.struct_;
|
||||
|
@ -616,6 +784,8 @@ void codegen_c(Writer *header, Writer *source, const char *name, Program *p) {
|
|||
free(tag_type);
|
||||
free(name);
|
||||
}
|
||||
|
||||
wt_format(header, "#endif\n");
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#define PRIMITIF_TO(name, al) \
|
||||
const TypeObject PRIMITIF_##name = {.kind = TypePrimitif, .align = _ALIGN_##al, .type.primitif = Primitif_##name}
|
||||
PRIMITIF_TO(u8, 2);
|
||||
PRIMITIF_TO(u8, 1);
|
||||
PRIMITIF_TO(u16, 2);
|
||||
PRIMITIF_TO(u32, 4);
|
||||
PRIMITIF_TO(u64, 8);
|
||||
|
@ -444,7 +444,7 @@ static uint64_t get_ast_number_value(EvaluationContext *ctx, AstNumber number) {
|
|||
// If the constant is invalid we make up a value to continue checking for errors
|
||||
// (Since it is invalid there already has been at least one and we know this code
|
||||
// can't go to the next stage)
|
||||
return c->valid ? c->valid : 0;
|
||||
return c->valid ? c->value : 0;
|
||||
} else {
|
||||
// This constant doesn't exist: raise an error and return dummy value to continue
|
||||
vec_push(&ctx->errors, err_unknown(number.token.span, ATConstant, ident));
|
||||
|
|
79
server.c
79
server.c
|
@ -118,6 +118,33 @@ static void print_config() {
|
|||
printf("\n");
|
||||
}
|
||||
|
||||
void print_message_buffer(const uint8_t *buf, int len) {
|
||||
bool last_beg = false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i + MSG_MAGIC_SIZE <= len) {
|
||||
MsgMagic magic = *(MsgMagic *)(&buf[i]);
|
||||
if (magic == MSG_MAGIC_START) {
|
||||
printf(" \033[32m%08lX\033[0m", magic);
|
||||
i += MSG_MAGIC_SIZE - 1;
|
||||
last_beg = true;
|
||||
continue;
|
||||
} else if (magic == MSG_MAGIC_END) {
|
||||
printf(" \033[32m%08lX\033[0m", magic);
|
||||
i += MSG_MAGIC_SIZE - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_beg) {
|
||||
last_beg = false;
|
||||
printf(" \033[034m%04X\033[0m", *(uint16_t*)&buf[i]);
|
||||
i++;
|
||||
} else {
|
||||
printf(" %02X", buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void device_thread_exit(int _sig) {
|
||||
struct DeviceThreadArgs *args = pthread_getspecific(device_args_key);
|
||||
printf("CONN(%d): [%d] exiting\n", args->conn->id, args->index);
|
||||
|
@ -146,7 +173,7 @@ void *device_thread(void *args_) {
|
|||
TRAP(SIGTERM, device_thread_exit);
|
||||
|
||||
uint8_t buf[2048] __attribute__((aligned(4))) = {0};
|
||||
MessageDeviceInfo dev_info;
|
||||
DeviceInfo dev_info;
|
||||
|
||||
while (true) {
|
||||
if (*args->controller != NULL) {
|
||||
|
@ -169,19 +196,19 @@ void *device_thread(void *args_) {
|
|||
|
||||
// Send over device info
|
||||
{
|
||||
int len = msg_serialize(buf, 2048, (Message *)&dev_info);
|
||||
int len = msg_device_serialize(buf, 2048, (DeviceMessage *)&dev_info);
|
||||
if (write(args->conn->socket, buf, len) == -1) {
|
||||
printf("CONN(%d): [%d] Couldn't send device info\n", args->conn->id, args->index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MessageDeviceReport report = {0};
|
||||
DeviceReport report = {0};
|
||||
|
||||
report.code = DeviceReport;
|
||||
report.abs_count = ctr->dev.device_info.abs_count;
|
||||
report.rel_count = ctr->dev.device_info.rel_count;
|
||||
report.key_count = ctr->dev.device_info.key_count;
|
||||
report.tag = DeviceTagReport;
|
||||
report.abs.len = ctr->dev.device_info.abs.len;
|
||||
report.rel.len = ctr->dev.device_info.rel.len;
|
||||
report.key.len = ctr->dev.device_info.key.len;
|
||||
report.slot = args->index;
|
||||
report.index = controller_index;
|
||||
|
||||
|
@ -203,13 +230,12 @@ void *device_thread(void *args_) {
|
|||
}
|
||||
|
||||
if (event.type == EV_SYN) {
|
||||
int len = msg_serialize(buf, 2048, (Message *)&report);
|
||||
int len = msg_device_serialize(buf, 2048, (DeviceMessage *)&report);
|
||||
|
||||
if (len < 0) {
|
||||
printf("CONN(%d): [%d] Couldn't serialize report %d\n", args->conn->id, args->index, len);
|
||||
continue;
|
||||
};
|
||||
|
||||
send(args->conn->socket, buf, len, 0);
|
||||
} else if (event.type == EV_ABS) {
|
||||
int index = ctr->dev.mapping.abs_indices[event.code];
|
||||
|
@ -219,7 +245,7 @@ void *device_thread(void *args_) {
|
|||
continue;
|
||||
};
|
||||
|
||||
report.abs[index] = event.value;
|
||||
report.abs.data[index] = event.value;
|
||||
} else if (event.type == EV_REL) {
|
||||
int index = ctr->dev.mapping.rel_indices[event.code];
|
||||
|
||||
|
@ -228,7 +254,7 @@ void *device_thread(void *args_) {
|
|||
continue;
|
||||
};
|
||||
|
||||
report.rel[index] = event.value;
|
||||
report.rel.data[index] = event.value;
|
||||
} else if (event.type == EV_KEY) {
|
||||
int index = ctr->dev.mapping.key_indices[event.code];
|
||||
|
||||
|
@ -236,17 +262,17 @@ void *device_thread(void *args_) {
|
|||
printf("CONN(%d): [%d] Invalid key\n", args->conn->id, args->index);
|
||||
continue;
|
||||
};
|
||||
report.key[index] = !!event.value;
|
||||
report.key.data[index] = !!event.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Send device destroy message
|
||||
{
|
||||
MessageDestroy dstr;
|
||||
dstr.code = DeviceDestroy;
|
||||
DeviceDestroy dstr;
|
||||
dstr.tag = DeviceTagDestroy;
|
||||
dstr.index = args->index;
|
||||
|
||||
int len = msg_serialize(buf, 2048, (Message *)&dstr);
|
||||
int len = msg_device_serialize(buf, 2048, (DeviceMessage *)&dstr);
|
||||
if (write(args->conn->socket, buf, len) == -1) {
|
||||
printf("CONN(%d): [%d] Couldn't send device destroy message\n", args->conn->id, args->index);
|
||||
break;
|
||||
|
@ -310,7 +336,7 @@ void *server_handle_conn(void *args_) {
|
|||
if (len <= 0) {
|
||||
closing_message = "Lost peer (from recv)";
|
||||
goto conn_end;
|
||||
} else if (len > 1 + MAGIC_SIZE * 2) {
|
||||
} else if (len > 1) {
|
||||
printf("CONN(%d): Got message: ", args->id);
|
||||
printf("\n");
|
||||
} else {
|
||||
|
@ -318,10 +344,10 @@ void *server_handle_conn(void *args_) {
|
|||
}
|
||||
|
||||
// Parse message
|
||||
Message msg;
|
||||
int msg_len = msg_deserialize(buf, len, &msg);
|
||||
DeviceMessage msg;
|
||||
int msg_len = msg_device_deserialize(buf, len, &msg);
|
||||
if (msg_len < 0) {
|
||||
if (len > 1 + MAGIC_SIZE * 2) {
|
||||
if (len > 1) {
|
||||
printf("CONN(%d): Couldn't parse message: ", args->id);
|
||||
print_message_buffer(buf, len);
|
||||
printf("\n");
|
||||
|
@ -332,7 +358,7 @@ void *server_handle_conn(void *args_) {
|
|||
}
|
||||
|
||||
// Handle message
|
||||
if (msg.code == ControllerState) {
|
||||
if (msg.tag == DeviceTagControllerState) {
|
||||
int i = msg.controller_state.index;
|
||||
if (i >= device_controllers.len) {
|
||||
printf("CONN(%d): Invalid controller index in controller state message\n", args->id);
|
||||
|
@ -346,10 +372,10 @@ void *server_handle_conn(void *args_) {
|
|||
}
|
||||
|
||||
apply_controller_state(ctr, &msg.controller_state);
|
||||
} else if (msg.code == Request) {
|
||||
} else if (msg.tag == DeviceTagRequest) {
|
||||
if (got_request) {
|
||||
printf("CONN(%d): Illegal Request message after initial request\n", args->id);
|
||||
msg_free(&msg);
|
||||
msg_device_free(&msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -357,7 +383,7 @@ void *server_handle_conn(void *args_) {
|
|||
|
||||
printf("CONN(%d): Got client request\n", args->id);
|
||||
|
||||
for (int i = 0; i < msg.request.request_count; i++) {
|
||||
for (int i = 0; i < msg.request.requests.len; i++) {
|
||||
int index = device_controllers.len;
|
||||
Controller *ctr = NULL;
|
||||
vec_push(&device_controllers, &ctr);
|
||||
|
@ -365,13 +391,14 @@ void *server_handle_conn(void *args_) {
|
|||
struct DeviceThreadArgs *dev_args = malloc(sizeof(struct DeviceThreadArgs));
|
||||
|
||||
dev_args->controller = vec_get(&device_controllers, index);
|
||||
dev_args->tag_count = msg.request.requests[i].count;
|
||||
dev_args->tag_count = msg.request.requests.data[i].tags.len;
|
||||
dev_args->tags = malloc(dev_args->tag_count * sizeof(char *));
|
||||
dev_args->conn = args;
|
||||
dev_args->index = index;
|
||||
|
||||
for (int j = 0; j < dev_args->tag_count; j++) {
|
||||
dev_args->tags[j] = strdup(msg.request.requests[i].tags[j]);
|
||||
Tag t = msg.request.requests.data[i].tags.data[j];
|
||||
dev_args->tags[j] = strndup(t.name.data, t.name.len);
|
||||
}
|
||||
|
||||
pthread_t thread;
|
||||
|
@ -379,7 +406,7 @@ void *server_handle_conn(void *args_) {
|
|||
vec_push(&device_threads, &thread);
|
||||
}
|
||||
|
||||
msg_free(&msg);
|
||||
msg_device_free(&msg);
|
||||
} else {
|
||||
printf("CONN(%d): Illegal message\n", args->id);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue