/* * Copyright © 2020 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include "util-bits.h" #include "util-io.h" #include "util-logger.h" #include "util-macros.h" #include "util-mem.h" #include "util-sources.h" #include "util-strings.h" #include "libeis-private.h" #include "proto/ei.pb-c.h" /* The message type for the wire format */ enum message_type { MESSAGE_CONNECT, MESSAGE_DISCONNECT, MESSAGE_ADD_DEVICE, MESSAGE_REMOVE_DEVICE, MESSAGE_POINTER_REL, }; struct message { enum message_type type; union { struct message_connect { char *name; } connect; struct message_disconnect { uint8_t pad; /* no data */ } disconnect; struct message_add_device { uint32_t deviceid; uint32_t capabilities; } add_device; struct message_remove_device { uint32_t deviceid; } remove_device; struct message_pointer_rel { uint32_t deviceid; int32_t x; int32_t y; } pointer_rel; }; }; static void message_free(struct message *msg) { switch (msg->type) { case MESSAGE_CONNECT: free(msg->connect.name); break; default: break; } free(msg); } DEFINE_TRIVIAL_CLEANUP_FUNC(struct message*, message_free); static void eis_client_destroy(struct eis_client *client) { free(client->name); source_unref(client->source); list_remove(&client->link); } OBJECT_DECLARE_CREATE(eis_client); static OBJECT_DECLARE_PARENT(eis_client, eis); _public_ OBJECT_DECLARE_REF(eis_client); _public_ OBJECT_DECLARE_UNREF(eis_client); _public_ OBJECT_DECLARE_GETTER(eis_client, name, const char*); _public_ struct eis* eis_client_get_context(struct eis_client *client) { return eis_client_parent(client); } static int client_send_msg(struct eis_client *client, const ServerMessage *msg) { size_t sz = server_message__get_packed_size(msg); uint8_t buf[sz]; size_t len = server_message__pack(msg, buf); return min(0, xsend(source_get_fd(client->source), buf, len)); } static int client_send_hello(struct eis_client *client) { ServerMessage msg = SERVER_MESSAGE__INIT; Hello hello = HELLO__INIT; msg.hello = &hello; msg.msg_case = SERVER_MESSAGE__MSG_HELLO; return client_send_msg(client, &msg); } static int client_send_disconnect(struct eis_client *client) { ServerMessage msg = SERVER_MESSAGE__INIT; Disconnected disconnected = DISCONNECTED__INIT; msg.disconnected = &disconnected; msg.msg_case = SERVER_MESSAGE__MSG_DISCONNECTED; return client_send_msg(client, &msg); } static int client_send_connect(struct eis_client *client) { ServerMessage msg = SERVER_MESSAGE__INIT; Connected connected = CONNECTED__INIT; msg.connected = &connected; msg.msg_case = SERVER_MESSAGE__MSG_CONNECTED; return client_send_msg(client, &msg); } static int client_send_accepted(struct eis_client *client, struct eis_device *device) { ServerMessage msg = SERVER_MESSAGE__INIT; Accepted accepted = ACCEPTED__INIT; accepted.deviceid = device->id; msg.accepted = &accepted; msg.msg_case = SERVER_MESSAGE__MSG_ACCEPTED; return client_send_msg(client, &msg); } static int client_send_removed(struct eis_client *client, struct eis_device *device) { ServerMessage msg = SERVER_MESSAGE__INIT; Removed removed = REMOVED__INIT; msg.removed = &removed; msg.msg_case = SERVER_MESSAGE__MSG_REMOVED; return client_send_msg(client, &msg); } _public_ void eis_client_connect(struct eis_client *client) { switch(client->state) { case EIS_CLIENT_STATE_CONNECTING: break; default: return; } int rc = client_send_connect(client); if (rc) { eis_client_disconnect(client); } else { client->state = EIS_CLIENT_STATE_CONNECTED; } } _public_ void eis_client_disconnect(struct eis_client *client) { switch(client->state) { case EIS_CLIENT_STATE_DISCONNECTED: /* Client already disconnected? don't bother sending an * event */ return; case EIS_CLIENT_STATE_CONNECTING: case EIS_CLIENT_STATE_CONNECTED: { struct eis_device *d, *tmp; list_for_each_safe(d, tmp, &client->devices, link) eis_device_disconnect(d); } eis_queue_disconnect_event(client); /* fallthrough */ case EIS_CLIENT_STATE_HELLO: client_send_disconnect(client); client->state = EIS_CLIENT_STATE_DISCONNECTED; source_remove(client->source); break; } } static int client_new_device(struct eis_client *client, uint32_t id, uint32_t capabilities) { /* Check for duplicate IDs */ struct eis_device *d; list_for_each(d, &client->devices, link) { if (d->id == id) return -EINVAL; } if (capabilities == 0 || capabilities & ~(bit(EIS_DEVICE_CAP_POINTER) | bit(EIS_DEVICE_CAP_POINTER_ABSOLUTE) | bit(EIS_DEVICE_CAP_KEYBOARD) | bit(EIS_DEVICE_CAP_TOUCH))) return -EINVAL; struct eis_device *device = eis_device_new(client, id, capabilities); list_append(&client->devices, &device->link); log_debug(eis_client_parent(client), "New device %d caps: %#x\n", id, capabilities); eis_queue_added_event(device); return 0; } static int client_pointer_rel(struct eis_client *client, uint32_t deviceid, int32_t x, int32_t y) { struct eis_device *device; list_for_each(device, &client->devices, link) { if (device->id == deviceid) { return eis_device_pointer_rel(device, x, y); } } return -EINVAL; } static int client_hello_handle_msg(struct eis_client *client, struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_CONNECT: eis_queue_connect_event(client); client->name = steal(&msg->connect.name); client->state = EIS_CLIENT_STATE_CONNECTING; break; case MESSAGE_DISCONNECT: rc = -ECANCELED; break; case MESSAGE_ADD_DEVICE: case MESSAGE_REMOVE_DEVICE: case MESSAGE_POINTER_REL: rc = -EPROTO; break; } return rc; } static int client_connecting_handle_msg(struct eis_client *client, const struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_CONNECT: rc = -EPROTO; break; case MESSAGE_DISCONNECT: rc = -ECANCELED; break; case MESSAGE_ADD_DEVICE: case MESSAGE_REMOVE_DEVICE: case MESSAGE_POINTER_REL: rc = -EPROTO; break; } return rc; } static int client_connected_handle_msg(struct eis_client *client, const struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_CONNECT: rc = -EPROTO; break; case MESSAGE_DISCONNECT: client->state = EIS_CLIENT_STATE_DISCONNECTED; rc = -ECANCELED; break; case MESSAGE_ADD_DEVICE: rc = client_new_device(client, msg->add_device.deviceid, msg->add_device.capabilities); break; case MESSAGE_REMOVE_DEVICE: /* FIXME: remove device */ break; case MESSAGE_POINTER_REL: rc = client_pointer_rel(client, msg->pointer_rel.deviceid, msg->pointer_rel.x, msg->pointer_rel.y); break; } return rc; } static struct message * client_parse_message(const char *data_in, size_t *len) { _cleanup_(message_freep) struct message *msg = xalloc(sizeof(*msg)); ClientMessage *proto = client_message__unpack(NULL, *len, (const unsigned char*)data_in); if (!proto) return NULL; switch (proto->msg_case) { case CLIENT_MESSAGE__MSG_CONNECT: { Connect *c = proto->connect; *msg = (struct message) { .type = MESSAGE_CONNECT, .connect.name = xstrdup(c->name), }; } break; case CLIENT_MESSAGE__MSG_DISCONNECT: *msg = (struct message) { .type = MESSAGE_DISCONNECT, }; break; case CLIENT_MESSAGE__MSG_ADD: { AddDevice *a = proto->add; *msg = (struct message) { .type = MESSAGE_ADD_DEVICE, .add_device.deviceid = a->deviceid, .add_device.capabilities = a->capabilities, }; } break; case CLIENT_MESSAGE__MSG_REMOVE: { RemoveDevice *r = proto->remove; *msg = (struct message) { .type = MESSAGE_REMOVE_DEVICE, .remove_device.deviceid = r->deviceid, }; } break; case CLIENT_MESSAGE__MSG_REL: { PointerRelative *r = proto->rel; *msg = (struct message) { .type = MESSAGE_POINTER_REL, .pointer_rel.deviceid = r->deviceid, .pointer_rel.x = r->x, .pointer_rel.y = r->y, }; } break; default: client_message__free_unpacked(proto, NULL); return NULL; } *len = client_message__get_packed_size(proto); client_message__free_unpacked(proto, NULL); return steal(&msg); } static void client_dispatch(struct source *source, void *userdata) { struct eis_client *client = userdata; enum eis_client_state old_state = client->state; _cleanup_(message_freep) struct message *msg = NULL; _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); int rc = iobuf_append_from_fd(buf, source_get_fd(source)); if (rc == -EAGAIN) { return; } else if (rc == 0) { rc = -ECANCELED; goto error; } else if (rc < 0) { goto error; } size_t idx = 0; while (true) { const char *data = iobuf_data(buf) + idx; size_t len = iobuf_len(buf) - idx; if (len == 0) break; msg = client_parse_message(data, &len); if (!msg) { rc = -EBADMSG; goto error; } idx += len; switch (client->state) { case EIS_CLIENT_STATE_HELLO: rc = client_hello_handle_msg(client, msg); break; case EIS_CLIENT_STATE_CONNECTING: /* Client is waiting for us, shouldn't send anything * but disconnect */ rc = client_connecting_handle_msg(client, msg); break; case EIS_CLIENT_STATE_CONNECTED: rc = client_connected_handle_msg(client, msg); break; case EIS_CLIENT_STATE_DISCONNECTED: abort(); } } error: if (rc < 0) eis_client_disconnect(client); static const char *client_states[] = { "HELLO", "CONNECTING", "CONNECTED", "DISCONNECTED", }; if (rc) log_warn(eis_client_parent(client), "Client error: %s\n", strerror(-rc)); if (old_state != client->state) { log_debug(eis_client_parent(client), "Client dispatch: %s -> %s\n", client_states[old_state], client_states[client->state]); } } struct eis_client * eis_client_new(struct eis *eis, int fd) { static uint32_t client_id; struct eis_client *client = eis_client_create(&eis->object); client->id = ++client_id; list_init(&client->devices); list_append(&eis->clients, &client->link); client->source = source_add_autoclose(eis->sink, fd, client_dispatch, client); client->state = EIS_CLIENT_STATE_HELLO; int rc = client_send_hello(client); if (rc != 0) client = eis_client_unref(client); return client; } void eis_client_connect_device(struct eis_client *client, struct eis_device *device) { client_send_accepted(client, device); } void eis_client_disconnect_device(struct eis_client *client, struct eis_device *device) { client_send_removed(client, device); eis_queue_removed_event(device); list_remove(&device->link); eis_device_unref(device); }