/* * 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-io.h" #include "util-logger.h" #include "util-macros.h" #include "util-object.h" #include "util-sources.h" #include "util-strings.h" #include "libei.h" #include "libei-private.h" #include "proto/ei.pb-c.h" /* The message type for the wire format */ enum message_type { MESSAGE_HELLO, MESSAGE_CONNECTED, MESSAGE_DISCONNECTED, MESSAGE_ACCEPTED, MESSAGE_REMOVED, }; struct message { enum message_type type; union { struct message_hello { uint8_t pad; /* no data */ } hello; struct message_connected { uint8_t pad; /* no data */ } connected; struct message_disconnected { uint8_t pad; /* no data */ } disconnected; struct message_accepted { uint32_t deviceid; } accepted; struct message_removed { uint32_t deviceid; } removed; }; }; static void message_free(struct message *msg) { switch (msg->type) { default: break; } free(msg); } DEFINE_TRIVIAL_CLEANUP_FUNC(struct message*, message_free); static void ei_event_destroy(struct ei_event *event) { switch (event->type) { case EI_EVENT_CONNECT: case EI_EVENT_DISCONNECT: case EI_EVENT_DEVICE_ADDED: case EI_EVENT_DEVICE_REMOVED: break; default: abort(); /* not yet implemented */ } ei_device_unref(event->device); } static OBJECT_IMPLEMENT_INIT(ei_event); static OBJECT_IMPLEMENT_REF(ei_event); _public_ OBJECT_IMPLEMENT_UNREF(ei_event); _public_ OBJECT_IMPLEMENT_GETTER(ei_event, type, enum ei_event_type); _public_ OBJECT_IMPLEMENT_GETTER(ei_event, device, struct ei_device*); static void ei_disconnect(struct ei *ei); static void ei_destroy(struct ei *ei) { ei_disconnect(ei); struct ei_event *e; while ((e = ei_get_event(ei)) != NULL) ei_event_unref(e); ei->logger = logger_unref(ei->logger); if (ei->backend_interface.destroy) ei->backend_interface.destroy(ei, ei->backend); ei->backend = NULL; sink_unref(ei->sink); free(ei->name); } OBJECT_IMPLEMENT_CREATE(ei); _public_ OBJECT_IMPLEMENT_REF(ei); _public_ OBJECT_IMPLEMENT_UNREF(ei); #define _cleanup_ei_ _cleanup_(ei_cleanup) _public_ OBJECT_IMPLEMENT_SETTER(ei, user_data, void *); OBJECT_IMPLEMENT_GETTER(ei, user_data, void *); _public_ struct ei * ei_new(void *user_data) { _cleanup_ei_ struct ei *ei = ei_create(NULL); ei->state = EI_STATE_NEW; list_init(&ei->event_queue); list_init(&ei->devices); ei->logger = logger_new(ei); logger_set_priority(ei->logger, LOGGER_DEBUG); ei->sink = sink_new(); if (!ei->sink) return NULL; ei->user_data = user_data; ei->backend = NULL; return steal(&ei); } _public_ int ei_get_fd(struct ei *ei) { return sink_get_fd(ei->sink); } _public_ void ei_dispatch(struct ei *ei) { sink_dispatch(ei->sink); } static void ei_queue_event(struct ei *ei, struct ei_event *event) { list_append(&ei->event_queue, &event->link); } static void ei_queue_connect_event(struct ei *ei) { struct ei_event_client *e = xalloc(sizeof(*e)); ei_event_init_object(&e->base, &ei->object); e->base.type = EI_EVENT_CONNECT; ei_queue_event(ei, &e->base); } static void ei_queue_disconnect_event(struct ei *ei) { struct ei_event_client *e = xalloc(sizeof(*e)); ei_event_init_object(&e->base, &ei->object); e->base.type = EI_EVENT_DISCONNECT; ei_queue_event(ei, &e->base); } static void ei_queue_added_event(struct ei_device *device) { struct ei *ei= ei_device_get_context(device); struct ei_event_client *e = xalloc(sizeof(*e)); ei_event_init_object(&e->base, &ei->object); e->base.type = EI_EVENT_DEVICE_ADDED; e->base.device = ei_device_ref(device); ei_queue_event(ei, &e->base); } static void ei_queue_removed_event(struct ei_device *device) { struct ei *ei= ei_device_get_context(device); struct ei_event_client *e = xalloc(sizeof(*e)); ei_event_init_object(&e->base, &ei->object); e->base.type = EI_EVENT_DEVICE_REMOVED; e->base.device = ei_device_ref(device); ei_queue_event(ei, &e->base); } static int connection_send_msg(struct ei *ei, const ClientMessage *msg) { size_t msglen = client_message__get_packed_size(msg); Frame frame = FRAME__INIT; frame.length = msglen; size_t framelen = frame__get_packed_size(&frame); uint8_t buf[framelen + msglen]; frame__pack(&frame, buf); client_message__pack(msg, buf + framelen); return min(0, xsend(source_get_fd(ei->source), buf, sizeof(buf))); } static int connection_send_connect(struct ei *ei) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; Connect connect = CONNECT__INIT; msg.connect = &connect; msg.msg_case = CLIENT_MESSAGE__MSG_CONNECT; return connection_send_msg(ei, &msg); } static int connection_send_disconnect(struct ei *ei) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; Disconnect disconnect = DISCONNECT__INIT; msg.disconnect = &disconnect; msg.msg_case = CLIENT_MESSAGE__MSG_DISCONNECT; return connection_send_msg(ei, &msg); } static int connection_send_add(struct ei *ei, struct ei_device *device) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; AddDevice add = ADD_DEVICE__INIT; add.deviceid = device->id; add.capabilities = device->capabilities; msg.add = &add; msg.msg_case = CLIENT_MESSAGE__MSG_ADD; return connection_send_msg(ei, &msg); } static int connection_send_remove(struct ei *ei, struct ei_device *device) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; RemoveDevice remove = REMOVE_DEVICE__INIT; remove.deviceid = device->id; msg.remove = &remove; msg.msg_case = CLIENT_MESSAGE__MSG_REMOVE; return connection_send_msg(ei, &msg); } static int connection_send_rel(struct ei *ei, struct ei_device *device, int32_t x, int32_t y) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; PointerRelative rel = POINTER_RELATIVE__INIT; rel.deviceid = device->id; rel.x = x; rel.y = y; msg.rel = &rel; msg.msg_case = CLIENT_MESSAGE__MSG_REL; return connection_send_msg(ei, &msg); } static int connection_send_button(struct ei *ei, struct ei_device *device, uint32_t b, bool is_press) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; PointerButton button = POINTER_BUTTON__INIT; button.deviceid = device->id; button.button = b; button.state = is_press; msg.button = &button; msg.msg_case = CLIENT_MESSAGE__MSG_BUTTON; return connection_send_msg(ei, &msg); } static int connection_send_key(struct ei *ei, struct ei_device *device, uint32_t k, bool is_press) { if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; ClientMessage msg = CLIENT_MESSAGE__INIT; KeyboardKey key = KEYBOARD_KEY__INIT; key.deviceid = device->id; key.key = k; key.state = is_press; msg.key = &key; msg.msg_case = CLIENT_MESSAGE__MSG_KEY; return connection_send_msg(ei, &msg); } static void ei_disconnect(struct ei *ei) { if (ei->state == EI_STATE_DISCONNECTED || ei->state == EI_STATE_DISCONNECTING) return; enum ei_state state = ei->state; /* We need the disconnecting state to be re-entrant ei_device_remove() may call ei_disconnect() on a socket error */ ei->state = EI_STATE_DISCONNECTING; struct ei_device *d, *tmp; list_for_each_safe(d, tmp, &ei->devices, link) { ei_device_remove(d); } if (state != EI_STATE_NEW) { connection_send_disconnect(ei); ei_queue_disconnect_event(ei); } ei->state = EI_STATE_DISCONNECTED; if (ei->source) source_remove(ei->source); ei->source = source_unref(ei->source); } int ei_add_device(struct ei_device *device) { struct ei *ei = ei_device_get_context(device); int rc = connection_send_add(ei, device); if (rc) { ei_disconnect(ei); return rc; } ei_device_ref(device); list_append(&ei->devices, &device->link); return 0; } int ei_remove_device(struct ei_device *device) { struct ei *ei = ei_device_get_context(device); int rc = connection_send_remove(ei, device); list_remove(&device->link); ei_device_unref(device); if (rc) ei_disconnect(ei); return rc; } static int ei_accepted(struct ei *ei, uint32_t deviceid) { struct ei_device *d; list_for_each(d, &ei->devices, link) { if (d->id == deviceid) { ei_device_accepted(d); ei_queue_added_event(d); return 0; } } /* FIXME: could be a device since removed by the client */ return -EBADMSG; } static int ei_removed(struct ei *ei, uint32_t deviceid) { struct ei_device *d; list_for_each(d, &ei->devices, link) { if (d->id == deviceid) { ei_queue_removed_event(d); list_remove(&d->link); ei_device_unref(d); break; } } return -0; } int ei_pointer_rel(struct ei_device *device, int32_t x, int32_t y) { struct ei *ei = ei_device_get_context(device); int rc = connection_send_rel(ei, device, x, y); if (rc) ei_disconnect(ei); return rc; } int ei_pointer_button(struct ei_device *device, uint32_t button, bool is_press) { struct ei *ei = ei_device_get_context(device); int rc = connection_send_button(ei, device, button, is_press); if (rc) ei_disconnect(ei); return rc; } int ei_keyboard_key(struct ei_device *device, uint32_t key, bool is_press) { struct ei *ei = ei_device_get_context(device); int rc = connection_send_key(ei, device, key, is_press); if (rc) ei_disconnect(ei); return rc; } _public_ struct ei_event* ei_get_event(struct ei *ei) { if (list_empty(&ei->event_queue)) return NULL; struct ei_event *e = list_first_entry(&ei->event_queue, e, link); list_remove(&e->link); return e; } _public_ struct ei_event* ei_peek_event(struct ei *ei) { if (list_empty(&ei->event_queue)) return NULL; struct ei_event *e = list_first_entry(&ei->event_queue, e, link); return ei_event_ref(e); } _public_ enum ei_event_type ei_next_event_type(struct ei *ei) { if (list_empty(&ei->event_queue)) return EI_EVENT_NONE; struct ei_event *e = list_first_entry(&ei->event_queue, e, link); return e->type; } static struct message * connection_parse_message(const char *data, size_t *len) { /* Every message is prefixed by a fixed-length Frame message which * contains the length of the next message. Parse that one first, * then the real message */ static size_t framelen = 0; if (framelen == 0) { Frame f = FRAME__INIT; f.length = 0xffff; framelen = frame__get_packed_size(&f); assert(framelen >= 5); } _cleanup_(message_freep) struct message *msg = xalloc(sizeof(*msg)); Frame *frame = frame__unpack(NULL, framelen, (const unsigned char *)data); if (!frame) return NULL; size_t msglen = frame->length; frame__free_unpacked(frame, NULL); const unsigned char *msgdata = (const unsigned char *)data + framelen; ServerMessage *proto = server_message__unpack(NULL, msglen, msgdata); if (!proto) return NULL; bool success = true; switch (proto->msg_case) { case SERVER_MESSAGE__MSG_HELLO: *msg = (struct message) { .type = MESSAGE_HELLO, }; break; case SERVER_MESSAGE__MSG_CONNECTED: *msg = (struct message) { .type = MESSAGE_CONNECTED, }; break; case SERVER_MESSAGE__MSG_DISCONNECTED: *msg = (struct message) { .type = MESSAGE_DISCONNECTED, }; break; case SERVER_MESSAGE__MSG_ACCEPTED: { Accepted *a = proto->accepted; *msg = (struct message) { .type = MESSAGE_ACCEPTED, .accepted.deviceid = a->deviceid, }; } break; case SERVER_MESSAGE__MSG_REMOVED: { Removed *r = proto->removed; *msg = (struct message) { .type = MESSAGE_REMOVED, .removed.deviceid = r->deviceid, }; } break; default: success = false; break; } server_message__free_unpacked(proto, NULL); return success ? steal(&msg) : NULL; } static int connection_new_handle_msg(struct ei *ei, struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_HELLO: ei->state = EI_STATE_CONNECTING; rc = connection_send_connect(ei); break; case MESSAGE_CONNECTED: case MESSAGE_DISCONNECTED: case MESSAGE_ACCEPTED: case MESSAGE_REMOVED: rc = -EPROTO; break; } return rc; } static int connection_connecting_handle_msg(struct ei *ei, struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_HELLO: rc = -EPROTO; break; case MESSAGE_CONNECTED: ei->state = EI_STATE_CONNECTED; ei_queue_connect_event(ei); break; case MESSAGE_DISCONNECTED: rc = -ECANCELED; break; case MESSAGE_ACCEPTED: case MESSAGE_REMOVED: rc = -EPROTO; break; } return rc; } static int connection_connected_handle_msg(struct ei *ei, struct message *msg) { int rc = 0; switch (msg->type) { case MESSAGE_HELLO: case MESSAGE_CONNECTED: rc = -EPROTO; break; case MESSAGE_DISCONNECTED: rc = -ECANCELED; break; case MESSAGE_ACCEPTED: rc = ei_accepted(ei, msg->accepted.deviceid); break; case MESSAGE_REMOVED: rc = ei_removed(ei, msg->removed.deviceid); break; } return rc; } static void connection_dispatch(struct source *source, void *userdata) { struct ei *ei = userdata; enum ei_state old_state = ei->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 = connection_parse_message(data, &len); if (!msg) { rc = -EBADMSG; goto error; } idx += len; switch (ei->state) { case EI_STATE_NEW: abort(); case EI_STATE_BACKEND: rc = connection_new_handle_msg(ei, msg); break; case EI_STATE_CONNECTING: rc = connection_connecting_handle_msg(ei, msg); break; case EI_STATE_CONNECTED: rc = connection_connected_handle_msg(ei, msg); break; case EI_STATE_DISCONNECTING: case EI_STATE_DISCONNECTED: abort(); } } error: if (rc < 0) ei_disconnect(ei); static const char *states[] = { "NEW", "BACKEND", "CONNECTING", "CONNECTED", "DISCONNECTED", "DISCONNECTING", }; if (rc) log_warn(ei, "Connnection error: %s\n", strerror(-rc)); log_debug(ei, "Connnection dispatch: %s -> %s\n", states[old_state], states[ei->state]); } int ei_set_connection(struct ei *ei, int fd) { struct source *source = source_add_autoclose(ei->sink, fd, connection_dispatch, ei); if (!source) return -ENOMEM; ei->source = source_ref(source); ei->state = EI_STATE_BACKEND; return 0; } _public_ void ei_configure_name(struct ei *ei, const char *name) { if (ei->state != EI_STATE_NEW) return; if (strlen(name) > 1024) return; free(ei->name); ei->name = xstrdup(name); } #ifdef _enable_tests_ #include static MunitResult test_init_unref(const MunitParameter params[], void *user_data) { struct ei *ei = ei_new(NULL); munit_assert_int(ei->state, ==, EI_STATE_NEW); munit_assert(list_empty(&ei->event_queue)); munit_assert(list_empty(&ei->devices)); munit_assert_not_null(ei->sink); munit_assert_not_null(ei->logger); struct ei *refd = ei_ref(ei); munit_assert_ptr_equal(ei, refd); munit_assert_int(ei->object.refcount, ==, 2); struct ei *unrefd = ei_unref(ei); munit_assert_null(unrefd); unrefd = ei_unref(ei); munit_assert_null(unrefd); return MUNIT_OK; } static MunitResult test_configure_name(const MunitParameter params[], void *user_data) { struct ei *ei = ei_new(NULL); ei_configure_name(ei, "foo"); munit_assert_string_equal(ei->name, "foo"); ei_configure_name(ei, "bar"); munit_assert_string_equal(ei->name, "bar"); /* ignore names that are too long */ char buf[1200] = {0}; memset(buf, 'a', sizeof(buf) - 1); ei_configure_name(ei, buf); munit_assert_string_equal(ei->name, "bar"); /* ignore names in all other states */ for (enum ei_state state = EI_STATE_NEW + 1; state <= EI_STATE_DISCONNECTED; state++) { ei->state = state; ei_configure_name(ei, "expect ignored"); munit_assert_string_equal(ei->name, "bar"); } ei_unref(ei); return MUNIT_OK; } static MunitTest ei_tests[] = { { "/init_unref", test_init_unref, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, { "/name", test_configure_name, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, { NULL }, }; static const MunitSuite libei_suite __attribute__((used)) __attribute__((section("test_section"))) = { "", ei_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE, }; #endif