jsfw/client.c

522 lines
18 KiB
C
Raw Permalink Normal View History

2022-08-30 11:45:53 -05:00
#include "client.h"
2022-10-12 15:28:50 -05:00
#include "const.h"
#include "json.h"
2022-08-30 17:54:56 -05:00
#include "net.h"
#include "util.h"
2022-10-07 18:36:53 -05:00
#include "vec.h"
2022-08-30 17:54:56 -05:00
#include <arpa/inet.h>
#include <fcntl.h>
2022-08-31 11:59:06 -05:00
#include <linux/input-event-codes.h>
2022-08-30 17:54:56 -05:00
#include <linux/input.h>
#include <linux/uinput.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
2022-08-31 11:59:06 -05:00
#include <stddef.h>
2022-08-30 12:08:36 -05:00
#include <stdint.h>
2022-08-30 17:54:56 -05:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
2022-08-30 17:54:56 -05:00
#include <sys/socket.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
static int fifo_attempt = 0;
2022-10-07 18:36:53 -05:00
static struct sockaddr_in server_addr = {0};
static char server_addrp[64] = {0};
2022-08-30 17:54:56 -05:00
static uint16_t server_port = -1;
static struct pollfd poll_fds[2];
static struct pollfd *fifo_poll = &poll_fds[0];
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 DeviceMessage message;
2022-10-07 18:36:53 -05:00
static Vec devices_fd;
static Vec devices_info;
static ClientConfig config;
static DeviceRequest device_request;
2022-10-07 18:36:53 -05:00
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; }
static void default_vendor(void *ptr) { *(int32_t *)ptr = VIRTUAL_DEVICE_VENDOR; }
static void default_product(void *ptr) { *(int32_t *)ptr = VIRTUAL_DEVICE_PRODUCT; }
static void default_name(void *ptr) { *(char **)ptr = (char *)VIRTUAL_DEVICE_NAME; }
static void default_to_white(void *ptr) {
uint8_t *color = ptr;
color[0] = 255;
color[1] = 255;
color[2] = 255;
}
static const JSONPropertyAdapter ControllerStateAdapterProps[] = {
{".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 }
2022-10-07 18:36:53 -05:00
};
static const JSONAdapter ControllerStateAdapter = {
.props = (JSONPropertyAdapter *)ControllerStateAdapterProps,
.prop_count = sizeof(ControllerStateAdapterProps) / sizeof(JSONPropertyAdapter),
.size = sizeof(DeviceControllerState),
2022-10-07 18:36:53 -05:00
};
static const JSONPropertyAdapter ControllerAdapterProps[] = {
2023-03-18 12:41:17 -05:00
{".tag", &StringAdapter, offsetof(ClientController, tag), default_to_null, NULL },
2022-10-07 18:36:53 -05:00
{".vendor", &StringAdapter, offsetof(ClientController, device_vendor), default_vendor, tsf_hex_to_i32},
{".product", &StringAdapter, offsetof(ClientController, device_product), default_product, tsf_hex_to_i32},
{".name", &StringAdapter, offsetof(ClientController, device_name), default_name, NULL },
};
2022-10-07 18:36:53 -05:00
static const JSONAdapter ControllerAdapter = {
.props = ControllerAdapterProps,
.prop_count = sizeof(ControllerAdapterProps) / sizeof(JSONPropertyAdapter),
.size = sizeof(ClientController),
};
2023-03-18 12:41:17 -05:00
static const JSONPropertyAdapter SlotAdapterProps[] = {
{".controllers[]", &ControllerAdapter, offsetof(ClientSlot, controllers), default_to_null, NULL},
};
static const JSONAdapter SlotAdapter = {
.props = SlotAdapterProps,
.prop_count = sizeof(SlotAdapterProps) / sizeof(JSONPropertyAdapter),
.size = sizeof(ClientSlot),
};
2022-10-07 18:36:53 -05:00
static const JSONPropertyAdapter ClientConfigAdapterProps[] = {
2023-03-18 12:41:17 -05:00
{".slots[]", &SlotAdapter, offsetof(ClientConfig, slots), default_to_null, NULL },
{".fifo_path", &StringAdapter, offsetof(ClientConfig, fifo_path), default_fifo_path, NULL },
{".retry_delay", &NumberAdapter, offsetof(ClientConfig, retry_delay), default_retry_delay, tsf_numsec_to_timespec}
2022-10-07 18:36:53 -05:00
};
static const JSONAdapter ConfigAdapter = {
.props = ClientConfigAdapterProps,
.prop_count = sizeof(ClientConfigAdapterProps) / sizeof(JSONPropertyAdapter),
.size = sizeof(ClientConfig),
};
2023-04-15 11:24:25 -05:00
static void print_config() __attribute__((unused));
2023-03-18 10:31:33 -05:00
// Print the current config, for debugging purposes
static void print_config() {
printf("CLIENT: Config\n");
printf(" fifo_path: %s\n", config.fifo_path);
printf(" retry_delay: %fs\n", timespec_to_double(&config.retry_delay));
2023-03-18 12:41:17 -05:00
printf(" slots: \n");
for (size_t i = 0; i < config.slot_count; i++) {
ClientSlot *slot = &config.slots[i];
printf(" - controllers:\n");
for (size_t j = 0; j < slot->controller_count; j++) {
ClientController *ctr = &slot->controllers[j];
printf(" - tag: '%s'\n", ctr->tag);
printf(" name: %s\n", ctr->device_name);
printf(" vendor: %04x\n", ctr->device_vendor);
printf(" product: %04x\n", ctr->device_product);
2023-03-18 10:31:33 -05:00
}
}
printf("\n");
}
2022-10-12 12:33:18 -05:00
void destroy_devices(void) {
2023-03-18 12:41:17 -05:00
for (int i = 0; i < config.slot_count; i++) {
int fd = *(int *)vec_get(&devices_fd, i);
DeviceInfo *info = vec_get(&devices_info, i);
2022-10-07 18:36:53 -05:00
if (info->tag == DeviceTagInfo) {
2022-10-07 18:36:53 -05:00
ioctl(fd, UI_DEV_DESTROY);
info->tag = DeviceTagNone;
2022-10-07 18:36:53 -05:00
}
}
}
bool device_exists(int index) {
if (index >= devices_info.len) {
return false;
}
DeviceInfo *info = vec_get(&devices_info, index);
return info->tag == DeviceTagInfo;
2022-10-07 18:36:53 -05:00
}
2023-03-18 12:41:17 -05:00
void device_destroy(int slot) {
if (slot >= devices_info.len) {
2022-08-30 17:54:56 -05:00
return;
}
2023-03-18 12:41:17 -05:00
int fd = *(int *)vec_get(&devices_fd, slot);
2022-10-07 18:36:53 -05:00
DeviceInfo *info = vec_get(&devices_info, slot);
2022-10-07 18:36:53 -05:00
if (info->tag == DeviceTagInfo) {
2022-10-07 18:36:53 -05:00
ioctl(fd, UI_DEV_DESTROY);
info->tag = DeviceTagNone;
2022-10-07 18:36:53 -05:00
}
2022-08-30 17:54:56 -05:00
}
void device_init(DeviceInfo *dev) {
2023-03-18 12:41:17 -05:00
if (dev->slot >= devices_info.len) {
2022-10-07 18:36:53 -05:00
printf("CLIENT: Got wrong device index\n");
return;
2022-08-30 17:54:56 -05:00
}
2023-03-18 12:41:17 -05:00
device_destroy(dev->slot);
2022-10-07 18:36:53 -05:00
2023-03-18 12:41:17 -05:00
int fd = *(int *)vec_get(&devices_fd, dev->slot);
2022-08-30 17:54:56 -05:00
// Abs
if (dev->abs.len > 0) {
2022-08-30 17:54:56 -05:00
ioctl(fd, UI_SET_EVBIT, EV_ABS);
for (int i = 0; i < dev->abs.len; i++) {
2022-10-07 18:36:53 -05:00
struct uinput_abs_setup setup = {0};
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;
2022-10-07 18:36:53 -05:00
setup.absinfo.value = 0;
2022-08-30 17:54:56 -05:00
ioctl(fd, UI_ABS_SETUP, &setup);
}
}
// Rel
if (dev->rel.len > 0) {
2022-08-30 17:54:56 -05:00
ioctl(fd, UI_SET_EVBIT, EV_REL);
for (int i = 0; i < dev->rel.len; i++) {
ioctl(fd, UI_SET_RELBIT, dev->rel.data[i].id);
2022-08-30 17:54:56 -05:00
}
}
// Key
if (dev->key.len > 0) {
2022-08-30 17:54:56 -05:00
ioctl(fd, UI_SET_EVBIT, EV_KEY);
for (int i = 0; i < dev->key.len; i++) {
ioctl(fd, UI_SET_KEYBIT, dev->key.data[i].id);
2022-08-30 17:54:56 -05:00
}
}
2023-03-18 12:41:17 -05:00
ClientController *ctr = &config.slots[dev->slot].controllers[dev->index];
2022-10-07 18:36:53 -05:00
struct uinput_setup setup = {0};
2022-08-30 17:54:56 -05:00
setup.id.bustype = BUS_VIRTUAL;
2022-10-07 18:36:53 -05:00
setup.id.vendor = ctr->device_vendor;
setup.id.product = ctr->device_product;
setup.id.version = VIRTUAL_DEVICE_VERSION;
2022-10-07 18:36:53 -05:00
strncpy(setup.name, ctr->device_name, UINPUT_MAX_NAME_SIZE);
2022-08-30 17:54:56 -05:00
ioctl(fd, UI_DEV_SETUP, &setup);
ioctl(fd, UI_DEV_CREATE);
DeviceInfo *dst = vec_get(&devices_info, dev->slot);
2022-10-07 18:36:53 -05:00
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);
2022-08-30 17:54:56 -05:00
}
// Send an event to uinput, device must exist
2022-10-07 18:36:53 -05:00
bool device_emit(int index, uint16_t type, uint16_t id, uint32_t value) {
2022-10-12 15:28:50 -05:00
if (index >= devices_fd.len) {
2022-10-07 18:36:53 -05:00
return true;
}
2022-10-12 15:28:50 -05:00
int fd = *(int *)vec_get(&devices_fd, index);
2022-10-07 18:36:53 -05:00
struct input_event event = {0};
2022-08-30 17:54:56 -05:00
event.type = type;
event.code = id;
event.value = value;
2022-10-07 18:36:53 -05:00
return write(fd, &event, sizeof(event)) != sizeof(event);
2022-08-30 17:54:56 -05:00
}
// Update device with report
void device_handle_report(DeviceReport *report) {
2023-03-18 12:41:17 -05:00
if (!device_exists(report->slot)) {
printf("CLIENT: [%d] Got report before device info\n", report->slot);
2022-08-30 17:54:56 -05:00
return;
}
DeviceInfo *info = vec_get(&devices_info, report->slot);
2022-10-07 18:36:53 -05:00
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);
2022-08-30 17:54:56 -05:00
return;
}
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) {
2022-08-30 17:54:56 -05:00
printf("CLIENT: Error writing abs event to uinput\n");
}
2022-08-30 17:54:56 -05:00
}
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) {
2022-08-30 17:54:56 -05:00
printf("CLIENT: Error writing rel event to uinput\n");
}
2022-08-30 17:54:56 -05:00
}
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) {
2022-08-30 17:54:56 -05:00
printf("CLIENT: Error writing key event to uinput\n");
}
2022-08-30 17:54:56 -05:00
}
// 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
2023-03-18 12:41:17 -05:00
device_emit(report->slot, EV_SYN, 0, 0);
2022-10-07 18:36:53 -05:00
}
2022-10-12 12:33:18 -05:00
void setup_devices(void) {
2022-10-07 18:36:53 -05:00
devices_fd = vec_of(int);
devices_info = vec_of(DeviceInfo);
2022-10-07 18:36:53 -05:00
DeviceInfo no_info = {0};
no_info.tag = DeviceTagNone;
2022-10-07 18:36:53 -05:00
2023-03-18 12:41:17 -05:00
for (int i = 0; i < config.slot_count; i++) {
2023-04-15 11:24:25 -05:00
int fd = open(FSROOT "/dev/uinput", O_WRONLY | O_NONBLOCK);
2022-10-07 18:36:53 -05:00
if (fd < 0) {
perror("CLIENT: Can't open /dev/uinput, aborting now");
exit(1);
}
vec_push(&devices_fd, &fd);
vec_push(&devices_info, &no_info);
}
2022-08-30 17:54:56 -05:00
}
2022-10-12 12:33:18 -05:00
void setup_fifo(void);
2022-08-30 17:54:56 -05:00
// (Re)Open the fifo
2022-10-12 12:33:18 -05:00
void open_fifo(void) {
2022-08-30 17:54:56 -05:00
close(fifo);
2022-10-07 18:36:53 -05:00
fifo = open(config.fifo_path, O_RDONLY | O_NONBLOCK);
2022-08-30 17:54:56 -05:00
if (fifo < 0 && fifo_attempt == 0) {
fifo_attempt++;
2022-10-07 18:36:53 -05:00
unlink(config.fifo_path);
2022-08-30 17:54:56 -05:00
setup_fifo();
} else if (fifo < 0) {
panicf("CLIENT: Couldn't open fifo, aborting\n");
}
fifo_attempt = 0;
2022-08-30 17:54:56 -05:00
}
// Ensure the fifo exists and opens it (also setup poll_fd)
2022-10-12 12:33:18 -05:00
void setup_fifo(void) {
2022-08-30 17:54:56 -05:00
mode_t prev = umask(0);
2022-10-07 18:36:53 -05:00
mkfifo(config.fifo_path, 0666);
2022-08-30 17:54:56 -05:00
umask(prev);
open_fifo();
fifo_poll->fd = fifo;
fifo_poll->events = POLLIN;
}
// (Re)Connect to the server
2022-10-12 12:33:18 -05:00
void connect_server(void) {
2022-10-07 18:36:53 -05:00
while (true) {
2022-08-30 17:54:56 -05:00
if (sock > 0) {
// Close previous connection
2022-08-30 17:54:56 -05:00
shutdown(sock, SHUT_RDWR);
2022-10-07 18:36:53 -05:00
destroy_devices();
2022-08-30 17:54:56 -05:00
close(sock);
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
2022-08-30 17:54:56 -05:00
panicf("Couldn't create socket\n");
}
2022-08-30 17:54:56 -05:00
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
2022-10-07 18:36:53 -05:00
printf("CLIENT: Couldn't connect to %s:%d, retrying in %lu.%09lus\n", server_addrp, server_port,
config.retry_delay.tv_sec, config.retry_delay.tv_nsec);
nanosleep(&config.retry_delay, NULL);
2022-08-30 17:54:56 -05:00
continue;
}
// 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
2022-08-30 17:54:56 -05:00
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
socket_poll->fd = sock;
printf("CLIENT: Connected !\n");
2022-10-07 18:36:53 -05:00
uint8_t buf[2048] __attribute__((aligned(8))) = {0};
2022-10-07 18:36:53 -05:00
int len = msg_device_serialize(buf, 2048, (DeviceMessage *)&device_request);
2022-10-07 18:36:53 -05:00
if (len > 0) {
if (send(sock, buf, len, 0) > 0) {
printf("CLIENT: Sent device request\n");
};
};
2022-08-30 17:54:56 -05:00
return;
}
}
// Setup server address and connects to it (+ setup poll_fd)
2022-08-30 17:54:56 -05:00
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) {
2022-08-30 17:54:56 -05:00
printf("CLIENT: failed to parse address '%s', defaulting to 0.0.0.0 (localhost)\n", address);
}
2022-08-30 17:54:56 -05:00
inet_ntop(AF_INET, &server_addr.sin_addr, server_addrp, 64);
2022-08-30 17:54:56 -05:00
server_port = port;
server_addr.sin_port = htons(port);
socket_poll->events = POLLIN;
connect_server();
}
2022-10-12 12:33:18 -05:00
void build_device_request(void) {
device_request.tag = DeviceTagRequest;
device_request.requests.len = config.slot_count;
device_request.requests.data = malloc(config.slot_count * sizeof(TagList));
2023-03-18 12:41:17 -05:00
for (int i = 0; i < config.slot_count; i++) {
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 < 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;
2023-03-18 10:31:33 -05:00
}
2022-08-30 17:54:56 -05:00
}
}
2022-10-07 18:36:53 -05:00
void client_run(char *address, uint16_t port, char *config_path) {
// Parse the config
{
FILE *configfd = fopen(config_path, "r");
if (configfd == NULL) {
perror("CLIENT: Couldn't open config file");
exit(1);
}
char *cbuf = malloc(8192);
uint8_t *jbuf = (uint8_t *)cbuf + 4096;
int len = fread(cbuf, 1, 4096, configfd);
if (json_parse(cbuf, len, jbuf, 4096) != 0) {
printf("CLIENT: Couldn't parse config, %s (at index %lu)\n", json_strerr(), json_errloc());
exit(1);
}
json_adapt(jbuf, &ConfigAdapter, &config);
2023-03-18 10:31:33 -05:00
#ifdef VERBOSE
print_config();
#endif
2022-10-07 18:36:53 -05:00
free(cbuf);
fclose(configfd);
}
2022-08-30 17:54:56 -05:00
setup_fifo();
2022-10-07 18:36:53 -05:00
build_device_request();
setup_devices();
2022-10-12 12:33:18 -05:00
setup_server(address, port);
2022-08-30 17:54:56 -05:00
uint8_t buf[2048] __attribute__((aligned(8)));
uint8_t json_buf[2048] __attribute__((aligned(8)));
while (true) {
2022-08-30 17:54:56 -05:00
int rc = poll(poll_fds, 2, -1);
if (rc < 0) {
2022-10-07 18:36:53 -05:00
perror("CLIENT: Error on poll");
2022-08-30 17:54:56 -05:00
exit(1);
}
if (fifo_poll->revents & POLLIN || fifo_poll->revents & POLLHUP || fifo_poll->revents & POLLERR) {
2022-08-30 17:54:56 -05:00
int len = read(fifo, buf, 2048);
if (len <= 0) {
open_fifo();
} else {
// We've got data from the fifo
int rc = json_parse((char *)buf, len, json_buf, 2048);
if (rc < 0) {
2022-10-07 18:36:53 -05:00
printf("CLIENT: Error when parsing fifo message as json (%s at index %lu)\n", json_strerr(), json_errloc());
} else {
DeviceControllerState msg;
msg.tag = DeviceTagControllerState;
2022-10-07 18:36:53 -05:00
json_adapt(json_buf, &ControllerStateAdapter, &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: "
2022-10-07 18:36:53 -05:00
"(%d, %d) -> [%d]\n",
msg.led[0], msg.led[1], msg.led[2], msg.flash_on, msg.flash_off, msg.small_rumble,
msg.big_rumble, msg.index);
};
};
}
2022-08-30 17:54:56 -05:00
}
}
// A broken or closed socket produces a POLLIN event, so we check for error on the recv
if (socket_poll->revents & POLLIN) {
2022-10-12 12:33:18 -05:00
int len = recv(sock, buf, 2048, MSG_PEEK);
2022-08-30 17:54:56 -05:00
if (len <= 0) {
printf("CLIENT: Lost connection to server, reconnecting\n");
connect_server();
2022-10-07 18:36:53 -05:00
// we can use continue here because there's nothing after, unlike above for fifo (this reduces
// indentation instead of needing an else block)
2022-08-30 17:54:56 -05:00
continue;
}
int msg_len = msg_device_deserialize(buf, len, &message);
2022-08-30 17:54:56 -05:00
// We've got data from the server
2022-10-12 12:33:18 -05:00
if (msg_len < 0) {
recv(sock, buf, 2048, 0);
printf("CLIENT: Couldn't parse message (code: %d, len: %d)\n", buf[4], len);
2022-08-30 17:54:56 -05:00
int l = len > 100 ? 100 : len;
2022-08-30 17:56:56 -05:00
for (int i = 0; i < l; i++) {
2022-08-30 17:54:56 -05:00
printf("%02x", buf[i]);
}
2022-08-30 17:56:56 -05:00
if (len > 100) {
2022-08-30 17:54:56 -05:00
printf(" ... %d more bytes", len - 100);
}
2022-08-30 17:54:56 -05:00
printf("\n");
continue;
}
2022-10-12 12:33:18 -05:00
recv(sock, buf, msg_len, 0);
if (message.tag == DeviceTagInfo) {
if (device_exists(message.info.slot)) {
2022-10-07 18:36:53 -05:00
printf("CLIENT: Got more than one device info for same device\n");
}
2022-08-30 17:54:56 -05:00
device_init((DeviceInfo *)&message);
} else if (message.tag == DeviceTagReport) {
device_handle_report((DeviceReport *)&message);
} else if (message.tag == DeviceTagDestroy) {
2022-10-07 18:36:53 -05:00
device_destroy(message.destroy.index);
printf("CLIENT: Lost device %d\n", message.destroy.index);
2022-08-30 17:54:56 -05:00
} else {
printf("CLIENT: Illegal message\n");
}
}
}
}