libei/src/libeis-client.c
Peter Hutterer 35d676e7f7 libeis: fix the client ref handling and move the list to the eis context
Let the context take care of adding the device so we have better separation
here. Removal isn't handled in a special way because any list node can remove
itself safel anyway.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2020-08-07 11:37:51 +10:00

627 lines
14 KiB
C

/*
* 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 <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#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,
MESSAGE_POINTER_BUTTON,
MESSAGE_KEYBOARD_KEY,
};
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;
struct message_pointer_button {
uint32_t deviceid;
uint32_t button;
bool state;
} pointer_button;
struct message_keyboard_key {
uint32_t deviceid;
uint32_t key;
bool state;
} keyboard_key;;
};
};
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_remove(client->source);
source_unref(client->source);
list_remove(&client->link);
}
OBJECT_IMPLEMENT_CREATE(eis_client);
static
OBJECT_IMPLEMENT_PARENT(eis_client, eis);
_public_
OBJECT_IMPLEMENT_REF(eis_client);
_public_
OBJECT_IMPLEMENT_UNREF(eis_client);
_public_
OBJECT_IMPLEMENT_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 msglen = server_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);
server_message__pack(msg, buf + framelen);
return min(0, xsend(source_get_fd(client->source), buf, sizeof(buf)));
}
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;
}
eis_client_unref(client);
}
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_pointer_button(struct eis_client *client, uint32_t deviceid,
uint32_t button, bool state)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_pointer_button(device, button, state);
}
}
return -EINVAL;
}
static int
client_keyboard_key(struct eis_client *client, uint32_t deviceid,
uint32_t key, bool state)
{
struct eis_device *device;
list_for_each(device, &client->devices, link) {
if (device->id == deviceid) {
return eis_device_keyboard_key(device, key, state);
}
}
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:
case MESSAGE_POINTER_BUTTON:
case MESSAGE_KEYBOARD_KEY:
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:
case MESSAGE_POINTER_BUTTON:
case MESSAGE_KEYBOARD_KEY:
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;
case MESSAGE_POINTER_BUTTON:
rc = client_pointer_button(client, msg->pointer_button.deviceid,
msg->pointer_button.button,
msg->pointer_button.state);
break;
case MESSAGE_KEYBOARD_KEY:
rc = client_keyboard_key(client, msg->keyboard_key.deviceid,
msg->keyboard_key.key,
msg->keyboard_key.state);
break;
}
return rc;
}
static struct message *
client_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);
}
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;
_cleanup_(message_freep) struct message *msg = xalloc(sizeof(*msg));
ClientMessage *proto = client_message__unpack(NULL, msglen, msgdata);
if (!proto) {
return NULL;
}
*len = framelen + msglen;
bool success = true;
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;
case CLIENT_MESSAGE__MSG_BUTTON:
{
PointerButton *b = proto->button;
*msg = (struct message) {
.type = MESSAGE_POINTER_BUTTON,
.pointer_button.deviceid = b->deviceid,
.pointer_button.button = b->button,
.pointer_button.state = b->state,
};
}
break;
case CLIENT_MESSAGE__MSG_KEY:
{
KeyboardKey *k = proto->key;
*msg = (struct message) {
.type = MESSAGE_KEYBOARD_KEY,
.keyboard_key.deviceid = k->deviceid,
.keyboard_key.key = k->key,
.keyboard_key.state = k->state,
};
}
break;
default:
success = false;
break;
}
client_message__free_unpacked(proto, NULL);
return success ? steal(&msg) : NULL;
}
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);
struct source *s = source_add_autoclose(eis->sink, fd,
client_dispatch, client);
client->source = source_ref(s);
client->state = EIS_CLIENT_STATE_HELLO;
int rc = client_send_hello(client);
if (rc != 0)
client = eis_client_unref(client);
eis_add_client(eis, eis_client_ref(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);
}