proto: add a ping/pong request

This is effectively the same as connection.sync, but goes the other way.
This adds the ei_callback.done request.

In libeis this is (currently) enforced immediately after sending the
connection object. Not required there and makes the code a bit messier
but this way we can ensure that any client library handles that part of
the code.
This commit is contained in:
Peter Hutterer 2023-02-13 15:04:16 +10:00
parent 50013b4b3c
commit f41cc91599
10 changed files with 201 additions and 3 deletions

View file

@ -348,6 +348,28 @@
</description>
<arg name="invalid_id" type="uint" />
</event>
<event name="ping" since="1">
<description summary="ping event">
The ping event asks the client to emit the 'done' event
on the provided ei_callback object. Since requests are
handled in-order and events are delivered in-order, this can
be used as a synchronization point to ensure all previous requests
and the resulting events have been handled.
The object returned by this request must be destroyed by the
ei client implementation after the callback is fired and as
such the client must not attempt to use it after that point.
Note that for a server to use this request the client must announce
support for this interface in ei_connection_setup.interface. It is
a protocol violation to send this event to a client without the
"ei_callback" interface.
</description>
<arg name="callback" type="new_id" interface="ei_callback"
summary="callback object for the ping request"/>
<arg name="version" type="uint" summary="the version of the callback object"/>
</event>
</interface>
<interface name="ei_callback" version="1">
@ -360,6 +382,20 @@
support for this interface in ei_connection_setup.interface.
</description>
<!-- ei_callback client requests version 1 -->
<request name="done" type="destructor" since="1">
<description summary="done event">
Notify the server when the related event is done. Immediately after this event
the ei_callback object is destroyed by the client and as such must not be used
any further.
This request may only be called on an ei_callback object that was created by the
EIS implementation.
</description>
<arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
</request>
<!-- ei_callback events version 1 -->
<event name="done" type="destructor" since="1">
@ -367,6 +403,9 @@
Notify the client when the related request is done. Immediately after this event
the ei_callback object is destroyed by the EIS implementation and as such the
client must not attempt to use it after that point.
This event may only be sent on an ei_callback object that was created by the
client.
</description>
<arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
</event>

View file

@ -101,3 +101,18 @@ ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data)
return callback; /* ref owned by caller */
}
struct ei_callback *
ei_callback_new_for_id(struct ei *ei, uint32_t id, uint32_t version)
{
struct ei_callback *callback = ei_callback_create(&ei->object);
callback->proto_object.id = id;
callback->proto_object.implementation = callback;
callback->proto_object.interface = &ei_callback_proto_interface;
callback->proto_object.version = version; /* FIXME */
ei_register_object(ei, &callback->proto_object);
list_init(&callback->link);
return callback; /* ref owned by caller */
}

View file

@ -57,3 +57,6 @@ OBJECT_DECLARE_UNREF(ei_callback);
struct ei_callback *
ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data);
struct ei_callback *
ei_callback_new_for_id(struct ei *ei, uint32_t id, uint32_t version);

View file

@ -83,6 +83,7 @@ OBJECT_IMPLEMENT_GETTER(ei, connection, struct ei_connection *);
DEFINE_UNREF_CLEANUP_FUNC(ei_device);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
DEFINE_UNREF_CLEANUP_FUNC(ei_callback);
struct ei *
ei_get_context(struct ei *ei)
@ -670,6 +671,17 @@ handle_msg_invalid_object(struct ei_connection *connection, uint32_t object)
return 0;
}
static int
handle_msg_ping(struct ei_connection *connection, uint32_t id, uint32_t version)
{
struct ei *ei = ei_connection_get_context(connection);
_unref_(ei_callback) *callback = ei_callback_new_for_id(ei, id, version);
ei_callback_request_done(callback, 0);
return 0;
}
#define DISCONNECT_IF_SENDER_CONTEXT(ei_) do {\
if (ei_->is_sender) { \
log_bug_client(ei_, "Invalid event from receiver EIS context. Disconnecting"); \
@ -681,6 +693,7 @@ static const struct ei_connection_interface interface = {
.disconnected = handle_msg_disconnected,
.seat = handle_msg_seat,
.invalid_object = handle_msg_invalid_object,
.ping = handle_msg_ping,
};
const struct ei_connection_interface *

View file

@ -32,6 +32,7 @@
#include "util-mem.h"
#include "util-io.h"
#include "util-strings.h"
#include "util-version.h"
#include "libeis-private.h"
#include "eis-proto.h"
@ -46,6 +47,8 @@ eis_callback_destroy(struct eis_callback *callback)
OBJECT_IMPLEMENT_REF(eis_callback);
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_callback);
OBJECT_IMPLEMENT_GETTER_AS_REF(eis_callback, proto_object, const struct brei_object *);
OBJECT_IMPLEMENT_GETTER(eis_callback, user_data, void *);
OBJECT_IMPLEMENT_SETTER(eis_callback, user_data, void *);
static
OBJECT_IMPLEMENT_CREATE(eis_callback);
@ -71,7 +74,21 @@ eis_callback_get_id(struct eis_callback *callback)
return callback->proto_object.id;
}
uint32_t
eis_callback_get_version(struct eis_callback *callback)
{
return callback->proto_object.version;
}
static int
client_msg_done(struct eis_callback *callback, uint32_t callback_data)
{
callback->func(callback, callback->callback_data, callback_data);
return 0;
}
static const struct eis_callback_interface interface = {
.done = client_msg_done,
};
const struct eis_callback_interface *
@ -94,3 +111,21 @@ eis_callback_new(struct eis_client *client, uint32_t new_id, uint32_t version)
return callback; /* ref owned by caller */
}
struct eis_callback *
eis_callback_new_with_callback(struct eis_client *client, eis_callback_func func, void *callback_data)
{
struct eis_callback *callback = eis_callback_create(&client->object);
callback->proto_object.id = eis_client_get_new_id(client);
callback->proto_object.implementation = callback;
callback->proto_object.interface = &eis_callback_proto_interface;
callback->proto_object.version = VERSION_V(1); /* FIXME */
callback->callback_data = callback_data;
eis_client_register_object(client, &callback->proto_object);
callback->func = func;
list_init(&callback->link);
return callback; /* ref owned by caller */
}

View file

@ -30,20 +30,35 @@
struct eis;
struct eis_client;
struct eis_callback;
typedef void (*eis_callback_func)(struct eis_callback *callback, void *callback_data, uint32_t proto_data);
/* This is a protocol-only object, not exposed in the API */
struct eis_callback {
struct object object;
struct brei_object proto_object;
void *user_data; /* Note: user-data is attached to the object */
struct list link; /* for use by the callers, if needed */
eis_callback_func func;
void *callback_data; /* Note: callback-data is attached to the callback */
};
OBJECT_DECLARE_GETTER(eis_callback, context, struct eis *);
OBJECT_DECLARE_GETTER(eis_callback, client, struct eis_client *);
OBJECT_DECLARE_GETTER(eis_callback, id, uint32_t);
OBJECT_DECLARE_GETTER(eis_callback, version, uint32_t);
OBJECT_DECLARE_GETTER(eis_callback, proto_object, const struct brei_object *);
OBJECT_DECLARE_GETTER(eis_callback, interface, const struct eis_callback_interface *);
OBJECT_DECLARE_GETTER(eis_callback, user_data, void*);
OBJECT_DECLARE_SETTER(eis_callback, user_data, void*);
OBJECT_DECLARE_REF(eis_callback);
OBJECT_DECLARE_UNREF(eis_callback);
struct eis_callback *
eis_callback_new(struct eis_client *client, uint32_t new_id, uint32_t version);
struct eis_callback *
eis_callback_new_with_callback(struct eis_client *eis_client, eis_callback_func func, void *callback_data);

View file

@ -242,9 +242,11 @@ eis_client_setup_done(struct eis_client *client, const char *name, bool is_sende
client->name = xstrdup(name);
client->is_sender = is_sender;
client->interface_versions = *versions;
eis_queue_connect_event(client);
client->state = EIS_CLIENT_STATE_CONNECTING;
/* We don't queue the connect event yet because we send an extra ping/pong
* out, just to make sure that part of the protocol works.
* See pong() in libeis-client.c
*/
}
#define DISCONNECT_IF_RECEIVER_CONTEXT(client_) do { \

View file

@ -75,6 +75,13 @@ eis_connection_setup_get_id(struct eis_connection_setup *setup)
return setup->proto_object.id;
}
static void
pong(struct eis_connection *connection, void *user_data)
{
struct eis_client *client = eis_connection_get_client(connection);
eis_queue_connect_event(client);
}
static int
client_msg_done(struct eis_connection_setup *setup)
{
@ -86,12 +93,15 @@ client_msg_done(struct eis_connection_setup *setup)
rc = 0;
}
client->connection = eis_connection_new(client);
eis_connection_setup_event_connection(setup, eis_connection_get_id(client->connection),
eis_connection_get_version(client->connection));
eis_connection_setup_unref(setup);
/* Force a ping/pong. This isn't necessary but it doesn't hurt much here
* and it ensures that any client implementation doesn't have that part missing */
eis_connection_ping(client->connection, pong, NULL);
return rc;
}

View file

@ -42,6 +42,13 @@ eis_connection_destroy(struct eis_connection *connection)
{
struct eis_client *client = eis_connection_get_client(connection);
eis_client_unregister_object(client, &connection->proto_object);
struct eis_callback *cb;
list_for_each_safe(cb, &connection->pending_callbacks, link) {
list_remove(&cb->link);
free(eis_callback_get_user_data(cb));
eis_callback_unref(cb);
}
}
OBJECT_IMPLEMENT_REF(eis_connection);
@ -95,5 +102,49 @@ eis_connection_new(struct eis_client *client)
connection->proto_object.version = client->interface_versions.ei_connection;
eis_client_register_object(client, &connection->proto_object);
list_init(&connection->pending_callbacks);
return connection; /* ref owned by caller */
}
struct callback_user_data {
eis_connection_ping_callback_t cb;
void *user_data;
};
static void
ping_callback(struct eis_callback *callback, void *callback_data, uint32_t proto_data)
{
struct eis_connection *connection = callback_data;
_cleanup_free_ struct callback_user_data *data = eis_callback_get_user_data(callback);
if (data->cb)
data->cb(connection, data->user_data);
/* remove from pending callbacks */
list_remove(&callback->link);
eis_callback_unref(callback);
}
void
eis_connection_ping(struct eis_connection *connection, eis_connection_ping_callback_t cb,
void *user_data)
{
struct eis_client *client = eis_connection_get_client(connection);
/* This is double-wrapped because we only use this for debugging purposes for
* now. The actual callback calls sync_callback with our connection,
* then we extract the user_data on the object and call into the
* cb supplied to this function.
*/
struct eis_callback *callback = eis_callback_new_with_callback(client, ping_callback, connection);
struct callback_user_data *data = xalloc(sizeof *data);
data->cb = cb;
data->user_data = user_data;
eis_callback_set_user_data(callback, data);
list_append(&connection->pending_callbacks, &callback->link);
eis_connection_event_ping(connection, eis_callback_get_id(callback),
eis_callback_get_version(callback));
}

View file

@ -35,6 +35,8 @@ struct eis_client;
struct eis_connection {
struct object object;
struct brei_object proto_object;
struct list pending_callbacks;
};
OBJECT_DECLARE_GETTER(eis_connection, context, struct eis *);
@ -48,3 +50,16 @@ OBJECT_DECLARE_UNREF(eis_connection);
struct eis_connection *
eis_connection_new(struct eis_client *client);
/**
* Called when the ei_callback.done request is received after
* an ei_connection_ping() event.
*/
typedef void (*eis_connection_ping_callback_t)(struct eis_connection *connection,
void *user_data);
void
eis_connection_ping(struct eis_connection *connection,
eis_connection_ping_callback_t callback,
void *user_data);