proto: add a version exchange prior to connect

Add a new protocol message "GetVersion" and the matching reply from the
server with "Version" that can be sent at any time. The server always
replies with the highest protocol version it supports, allowing the
client to choose the protocol version it wants.

These two messages also have a fixed string to make the protocol easy to
identify in hexdumps.

To avoid roundtrips on connection, libeis immediately sends the Version
message. Ideally and by the time the client actually starts, that
version is already available and we can continue without requiring a
full roundtrip.

This patch only adds the version exchange with the server, it does not
yet add the bits for the client to actually set the version.
This commit is contained in:
Peter Hutterer 2022-07-26 10:39:02 +10:00
parent 1592fdd57f
commit 08a4ce4aac
9 changed files with 185 additions and 16 deletions

View file

@ -43,10 +43,14 @@ endif
add_project_arguments(cc.get_supported_arguments(cflags), language: 'c')
protocol_version = 1
config_h = configuration_data()
config_h.set('_GNU_SOURCE', '1')
config_h.set_quoted('EI_VERSION', meson.project_version())
config_h.set_quoted('EIS_VERSION', meson.project_version())
config_h.set('EI_PROTOCOL_VERSION', protocol_version)
config_h.set('EIS_PROTOCOL_VERSION', protocol_version)
subdir('proto')
@ -127,7 +131,10 @@ pkgconfig.generate(lib_libei,
description: 'Emulated Input client library',
version: meson.project_version(),
libraries: lib_libei,
variables: [ 'portal=' + get_option('portal').to_string() ],
variables: [
'portal=' + get_option('portal').to_string(),
'protocol_version=' + protocol_version.to_string(),
],
)
src_libeis = [
@ -166,6 +173,9 @@ pkgconfig.generate(lib_libeis,
description: 'Emulated Input server library',
version: meson.project_version(),
libraries: lib_libeis,
variables: [
'protocol_version=' + protocol_version.to_string(),
],
)
lib_libreis = shared_library('reis',
@ -188,6 +198,9 @@ pkgconfig.generate(lib_libreis,
description: 'Restriction handler for Emulated Input servers',
version: meson.project_version(),
libraries: lib_libreis,
variables: [
'protocol_version=' + protocol_version.to_string(),
],
)
dep_libxkbcommon = dependency('xkbcommon', required: false)

View file

@ -48,6 +48,21 @@ syntax = "proto3";
* Configure messages after a client has connected will be silently ignored.
*/
/* Request the server version. This request MAY be sent at any time and/or
* multiple times and the server always replies with the server's highest
* supported version, see Version.
*
* To avoid roundtrips during the initial connection, an EIS implementation
* *SHOULD* send a Version message immediately. A client thus may not need to
* request the version.
*
* The "ei" field is the constant string "EI" and helps identifying
* this connection in debugging tools.
*/
message GetVersion {
string ei = 1; /* always "EI" */
}
/* ConfigureName *must* be sent before the Connect event. Once a name is set,
* subsequent attempts to change the name are ignored, including the name
* provided in the Connect message.
@ -178,6 +193,7 @@ message ClientMessage {
BindSeat bind_seat = 4;
CloseDevice close_device = 6;
SetProperty set_property = 9;
GetVersion get_version = 10;
/* Events */
StartEmulating start_emulating = 20;
@ -200,6 +216,25 @@ message ClientMessage {
}
}
/**
* The highest version number supported by the server. This message SHOULD be
* sent by the EIS implementation immediately after a socket is
* created/obtained. This avoids roundtrips between the server and the client
* on connection.
*
* This message MAY be sent at any other time.
* This message MUST be sent in response to a GetVersion request.
*
* The "eis" field is the constant string "EIS" and helps identifying
* this connection in debugging tools.
*
* The content of this message never changes.
*/
message Version {
uint32 version = 1;
string eis = 2; /* always "EIS" */
}
message Connected {
}
@ -288,6 +323,7 @@ message ServerMessage {
DevicePaused device_paused = 12;
KeyboardModifiers keyboard_modifiers = 13;
Property property = 14;
Version version = 15;
/* Events */
StartEmulating start_emulating = 20;

View file

@ -41,6 +41,7 @@ struct ei_backend_interface {
enum ei_state {
EI_STATE_NEW, /* No backend yet */
EI_STATE_BACKEND, /* We have a backend */
EI_STATE_VERSION_QUERY, /* Waiting for server version */
EI_STATE_CONNECTING, /* client requested connect */
EI_STATE_CONNECTED, /* server has sent connect */
EI_STATE_DISCONNECTING, /* in the process of cleaning up */
@ -69,6 +70,7 @@ struct ei {
const struct ei_proto_requests *requests;
bool is_sender;
uint32_t server_version;
};
enum ei_seat_state {

View file

@ -135,6 +135,9 @@ ei_proto_handle_message(struct ei *ei,
proto->property->value[0] ? proto->property->value : NULL,
proto->property->permissions);
break;
case SERVER_MESSAGE__MSG_VERSION:
rc = call(version, ei, proto->version->version);
break;
/* Events */
case SERVER_MESSAGE__MSG_START_EMULATING:
rc = call(start_emulating, ei,
@ -241,6 +244,7 @@ log_wire_message(struct ei *ei, const ClientMessage *msg, int error)
MSG_STRING_CASE(BIND_SEAT);
MSG_STRING_CASE(CLOSE_DEVICE);
MSG_STRING_CASE(SET_PROPERTY);
MSG_STRING_CASE(GET_VERSION);
/* events */
MSG_STRING_CASE(START_EMULATING);
MSG_STRING_CASE(STOP_EMULATING);
@ -310,6 +314,16 @@ ei_proto_send_msg_with_fds(struct ei *ei, const ClientMessage *msg, int *fds)
msg.msg_case = CLIENT_MESSAGE__MSG_##_type; \
msg._field = &_field
static int
ei_proto_send_get_version(struct ei *ei)
{
prepare_msg(GET_VERSION, GetVersion, get_version);
get_version.ei = "EI";
return ei_proto_send_msg(ei, &msg);
}
static int
ei_proto_send_connect(struct ei *ei)
{
@ -545,6 +559,7 @@ static const struct ei_proto_requests requests = {
.bind_seat = ei_proto_send_bind_seat,
.set_property = ei_proto_send_set_property,
.close_device = ei_proto_send_close_device,
.get_version = ei_proto_send_get_version,
/* events */
.start_emulating = ei_proto_send_start_emulating,

View file

@ -59,6 +59,7 @@ struct ei_proto_interface {
int (*property)(struct ei *ei,
const char *name, const char *value,
uint32_t permissions);
int (*version)(struct ei *ei, uint32_t version);
/* events */
int (*start_emulating)(struct ei *ei, uint32_t deviceid);
@ -85,6 +86,7 @@ struct ei_proto_requests {
int (*bind_seat)(struct ei_seat *seat, enum ei_device_capability cap);
int (*unbind_seat)(struct ei_seat *seat, enum ei_device_capability cap);
int (*close_device)(struct ei_device *device);
int (*get_version)(struct ei *ei);
int (*start_emulating)(struct ei_device *device);
int (*stop_emulating)(struct ei_device *device);

View file

@ -38,6 +38,7 @@
#include "util-sources.h"
#include "util-strings.h"
#include "util-time.h"
#include "util-version.h"
#include "libei.h"
#include "libei-private.h"
@ -50,6 +51,9 @@ _Static_assert(sizeof(enum ei_keymap_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_event_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_log_priority) == sizeof(int), "Invalid enum size");
static int
ei_finish_set_connection(struct ei *ei);
static struct ei_seat *
ei_find_seat(struct ei *ei, uint32_t seatid)
{
@ -1325,14 +1329,50 @@ handle_msg_touch_up(struct ei *ei, uint32_t deviceid, uint32_t touchid)
return -EINVAL;
}
static int
handle_msg_version(struct ei *ei, uint32_t version)
{
if (version == 0) {
log_bug(ei, "server version is zero");
return -EINVAL;
} else if (ei->server_version && ei->server_version != version) {
log_bug(ei, "server version is not constant (was %u, now %u)",
ei->server_version, version);
return -EINVAL;
}
log_debug(ei, "server protocol version: %u\n", version);
ei->server_version = version;
return 0;
}
static int
handle_msg_version_during_connection(struct ei *ei, uint32_t version)
{
int rc = handle_msg_version(ei, version);
if (rc != 0)
return rc;
return ei_finish_set_connection(ei);
}
static const struct ei_proto_interface intf_state_backend = {
.version = handle_msg_version_during_connection,
/* Everything triggers -EPROTO */
.connected = NULL,
};
static const struct ei_proto_interface intf_state_version_query = {
.version = handle_msg_version_during_connection,
/* Everything else triggers -EPROTO */
.connected = NULL,
};
static const struct ei_proto_interface intf_state_connecting = {
.connected = handle_msg_connected,
.disconnected = handle_msg_disconnected,
.version = handle_msg_version,
};
static const struct ei_proto_interface intf_state_connected = {
@ -1348,6 +1388,7 @@ static const struct ei_proto_interface intf_state_connected = {
.device_done = handle_msg_device_added_done,
.keyboard_modifiers = handle_msg_keyboard_modifiers,
.property = handle_msg_property,
.version = handle_msg_version,
/* events */
.start_emulating = handle_msg_start_emulating,
@ -1368,6 +1409,7 @@ static const struct ei_proto_interface intf_state_connected = {
static const struct ei_proto_interface *interfaces[] = {
[EI_STATE_NEW] = NULL,
[EI_STATE_BACKEND] = &intf_state_backend,
[EI_STATE_VERSION_QUERY] = &intf_state_version_query,
[EI_STATE_CONNECTING] = &intf_state_connecting,
[EI_STATE_CONNECTED] = &intf_state_connected,
[EI_STATE_DISCONNECTING] = NULL,
@ -1424,23 +1466,18 @@ ei_set_connection(struct ei *ei, int fd)
if (rc == 0) {
ei->source = source_ref(source);
ei->state = EI_STATE_BACKEND;
rc = ei->requests->connect(ei);
struct ei_property *prop;
list_for_each_safe(prop, &ei->properties, link) {
if (rc == 0)
rc = ei->requests->set_property(ei, prop->name, prop->value, prop->permissions);
}
/* The server SHOULD have sent the version number, let's
* process that. If it's ready, we don't need a full roundtrip.
*
* FIXME: this will block if O_NONBLOCK is missing
*/
ei_dispatch(ei);
if (rc == 0) {
rc = ei->requests->connect_done(ei);
}
if (rc == 0) {
ei->state = EI_STATE_CONNECTING;
}
if (rc != 0) {
log_error(ei, "message failed to send: %s", strerror(-rc));
ei_disconnect(ei);
if (ei->state == EI_STATE_BACKEND) {
/* The server didn't send the version number, so request it. */
rc = ei->requests->get_version(ei);
ei->state = EI_STATE_VERSION_QUERY;
}
}
@ -1449,6 +1486,31 @@ ei_set_connection(struct ei *ei, int fd)
return rc;
}
static int
ei_finish_set_connection(struct ei *ei)
{
int rc = ei->requests->connect(ei);
struct ei_property *prop;
list_for_each_safe(prop, &ei->properties, link) {
if (rc == 0)
rc = ei->requests->set_property(ei, prop->name, prop->value, prop->permissions);
}
if (rc == 0) {
rc = ei->requests->connect_done(ei);
}
if (rc == 0) {
ei->state = EI_STATE_CONNECTING;
}
if (rc != 0) {
log_error(ei, "message failed to send: %s\n", strerror(-rc));
ei_disconnect(ei);
}
return rc;
}
_public_ void
ei_configure_name(struct ei *ei, const char *name)
{

View file

@ -36,11 +36,14 @@
#include "util-sources.h"
#include "util-strings.h"
#include "util-structs.h"
#include "util-tristate.h"
#include "libeis-private.h"
#include "libeis-proto.h"
#include "brei-shared.h"
DEFINE_TRISTATE(started, finished, connected);
static void
eis_client_destroy(struct eis_client *client)
{
@ -132,6 +135,13 @@ eis_client_find_seat(struct eis_client *client, uint32_t seatid)
return NULL;
}
static int
client_send_version(struct eis_client *client, uint32_t version)
{
struct eis *eis = eis_client_get_context(client);
return eis->requests->version(client, version);
}
static int
client_send_disconnect(struct eis_client *client)
{
@ -560,11 +570,18 @@ client_msg_set_property_with_event(struct eis_client *client,
return rc;
}
static int
client_msg_get_version(struct eis_client *client)
{
return client_send_version(client, EI_PROTOCOL_VERSION);
}
static const struct eis_proto_interface intf_state_new = {
.connect = client_msg_connect,
.set_property = client_msg_set_property,
.connect_done = client_msg_connect_done,
.disconnect = client_msg_disconnect,
.get_version = client_msg_get_version,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
@ -573,6 +590,7 @@ static const struct eis_proto_interface intf_state_new = {
/* Client is waiting for us, shouldn't send anything except disconnect */
static const struct eis_proto_interface intf_state_connecting = {
.disconnect = client_msg_disconnect,
.get_version = client_msg_get_version,
.configure_name = client_msg_configure_name,
.configure_capabilities = client_msg_configure_capabilities,
@ -583,6 +601,7 @@ static const struct eis_proto_interface intf_state_connected = {
.set_property = client_msg_set_property_with_event,
.bind_seat = client_msg_bind_seat,
.close_device = client_msg_close_device,
.get_version = client_msg_get_version,
/* events */
.start_emulating = client_msg_start_emulating,
@ -685,6 +704,9 @@ eis_client_new(struct eis *eis, int fd)
source_unref(s);
/* Immediately send our version so the client doesn't need a roundtrip */
client_send_version(client, EI_PROTOCOL_VERSION);
return client;
}

View file

@ -66,6 +66,7 @@ log_wire_message(struct eis *eis, const ServerMessage *msg)
MSG_STRING_CASE(DEVICE_PAUSED);
MSG_STRING_CASE(KEYBOARD_MODIFIERS);
MSG_STRING_CASE(PROPERTY);
MSG_STRING_CASE(VERSION);
/* events */
MSG_STRING_CASE(START_EMULATING);
MSG_STRING_CASE(STOP_EMULATING);
@ -129,6 +130,16 @@ eis_proto_send_msg_with_fds(struct eis_client *client, const ServerMessage *msg,
msg.msg_case = SERVER_MESSAGE__MSG_##_type; \
msg._field = &_field
static int
eis_proto_send_version(struct eis_client *client, uint32_t v)
{
prepare_msg(VERSION, Version, version);
version.version = v;
version.eis = "EIS";
return eis_proto_send_msg(client, &msg);
}
static int
eis_proto_send_disconnected(struct eis_client *client)
{
@ -464,6 +475,7 @@ static const struct eis_proto_requests requests = {
.device_region = eis_proto_send_device_region,
.keyboard_modifiers = eis_proto_send_keyboard_modifiers,
.property = eis_proto_send_property,
.version = eis_proto_send_version,
/* events */
.start_emulating = eis_proto_send_start_emulating,
@ -534,6 +546,9 @@ eis_proto_handle_message(struct eis_client *client,
proto->set_property->value[0] ? proto->set_property->value : NULL,
proto->set_property->permissions);
break;
case CLIENT_MESSAGE__MSG_GET_VERSION:
rc = call(get_version, client);
break;
/* Events */
case CLIENT_MESSAGE__MSG_START_EMULATING:
rc = call(start_emulating, client,

View file

@ -44,6 +44,7 @@ struct eis_proto_interface {
int (*close_device)(struct eis_client *client, uint32_t deviceid);
int (*set_property)(struct eis_client *client, const char *name,
const char *value, uint32_t permissions);
int (*get_version)(struct eis_client *client);
/* events */
int (*start_emulating)(struct eis_client *client, uint32_t deviceid);
int (*stop_emulating)(struct eis_client *client, uint32_t deviceid);
@ -83,6 +84,7 @@ struct eis_proto_requests {
const struct eis_xkb_modifiers *mods);
int (*property)(struct eis_client *client, const char *name,
const char *value, uint32_t permissions);
int (*version)(struct eis_client *client, uint32_t version);
/* events */
int (*start_emulating)(struct eis_device *device, uint32_t deviceid);