From 1c778b7013582bab13a66467e12c7573f615cc8c Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 5 Sep 2022 20:50:06 +0100 Subject: Save device events when required This allows us to simulate an entire context of devices, simulating responses as if the hardware is connected. This allows building rich device-specific unit tests in programs like fwupd, without having to have the hardware attached. --- gusb/gusb-context.h | 1 + gusb/gusb-device-event-private.h | 26 +++ gusb/gusb-device-event.c | 186 ++++++++++++++++++ gusb/gusb-device-event.h | 24 +++ gusb/gusb-device.c | 412 ++++++++++++++++++++++++++++++++++++++- gusb/gusb-device.h | 3 + gusb/gusb-self-test.c | 147 +++++++++++++- gusb/libgusb.ver | 5 + gusb/meson.build | 6 + tools/gusb-main.c | 13 +- 10 files changed, 816 insertions(+), 7 deletions(-) create mode 100644 gusb/gusb-device-event-private.h create mode 100644 gusb/gusb-device-event.c create mode 100644 gusb/gusb-device-event.h diff --git a/gusb/gusb-context.h b/gusb/gusb-context.h index a39565f..3352ce7 100644 --- a/gusb/gusb-context.h +++ b/gusb/gusb-context.h @@ -40,6 +40,7 @@ typedef enum { G_USB_CONTEXT_ERROR_INTERNAL } GUsbContextError; typedef enum { G_USB_CONTEXT_FLAGS_NONE = 0, G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES = 1 << 0, + G_USB_CONTEXT_FLAGS_SAVE_EVENTS = 1 << 1, /*< private >*/ G_USB_CONTEXT_FLAGS_LAST } GUsbContextFlags; diff --git a/gusb/gusb-device-event-private.h b/gusb/gusb-device-event-private.h new file mode 100644 index 0000000..0e78cff --- /dev/null +++ b/gusb/gusb-device-event-private.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +GUsbDeviceEvent * +_g_usb_device_event_new(const gchar *id); +void +_g_usb_device_event_set_bytes_raw(GUsbDeviceEvent *self, gconstpointer buf, gsize bufsz); + +gboolean +_g_usb_device_event_load(GUsbDeviceEvent *self, JsonObject *json_object, GError **error); +gboolean +_g_usb_device_event_save(GUsbDeviceEvent *self, JsonBuilder *json_builder, GError **error); + +G_END_DECLS diff --git a/gusb/gusb-device-event.c b/gusb/gusb-device-event.c new file mode 100644 index 0000000..9d3aa4e --- /dev/null +++ b/gusb/gusb-device-event.c @@ -0,0 +1,186 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +/** + * SECTION:gusb-device-event + * @short_description: An event that happened to a GUsbDevice. + */ + +#include "config.h" + +#include "gusb-device-event-private.h" + +struct _GUsbDeviceEvent { + GObject parent_instance; + gchar *id; + GBytes *bytes; +}; + +G_DEFINE_TYPE(GUsbDeviceEvent, g_usb_device_event, G_TYPE_OBJECT) + +static void +g_usb_device_event_finalize(GObject *object) +{ + GUsbDeviceEvent *self = G_USB_DEVICE_EVENT(object); + + g_free(self->id); + if (self->bytes != NULL) + g_bytes_unref(self->bytes); + + G_OBJECT_CLASS(g_usb_device_event_parent_class)->finalize(object); +} + +static void +g_usb_device_event_class_init(GUsbDeviceEventClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->finalize = g_usb_device_event_finalize; +} + +static void +g_usb_device_event_init(GUsbDeviceEvent *self) +{ +} + +gboolean +_g_usb_device_event_load(GUsbDeviceEvent *self, JsonObject *json_object, GError **error) +{ + const gchar *str; + + g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), FALSE); + g_return_val_if_fail(json_object != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, -1); + +#if JSON_CHECK_VERSION(1, 6, 0) + /* optional properties */ + self->id = json_object_get_string_member_with_default(json_object, "Id", NULL); + + /* extra data */ + str = json_object_get_string_member_with_default(json_object, "Data", NULL); + if (str != NULL) { + gsize bufsz = 0; + g_autofree guchar *buf = g_base64_decode(str, &bufsz); + self->bytes = g_bytes_new_take(g_steal_pointer(&buf), bufsz); + } +#else + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "json-glib version too old"); + return FALSE; +#endif + + /* success */ + return TRUE; +} + +gboolean +_g_usb_device_event_save(GUsbDeviceEvent *self, JsonBuilder *json_builder, GError **error) +{ + g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), FALSE); + g_return_val_if_fail(json_builder != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* start */ + json_builder_begin_object(json_builder); + + if (self->id != NULL) { + json_builder_set_member_name(json_builder, "Id"); + json_builder_add_string_value(json_builder, self->id); + } + if (self->bytes != NULL) { + g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->bytes, NULL), + g_bytes_get_size(self->bytes)); + json_builder_set_member_name(json_builder, "Data"); + json_builder_add_string_value(json_builder, str); + } + + /* success */ + json_builder_end_object(json_builder); + return TRUE; +} + +/** + * _g_usb_device_event_new: + * @id: a cache key + * + * Return value: a new #GUsbDeviceEvent object. + * + * Since: 0.4.0 + **/ +GUsbDeviceEvent * +_g_usb_device_event_new(const gchar *id) +{ + GUsbDeviceEvent *self; + self = g_object_new(G_USB_TYPE_DEVICE_EVENT, NULL); + self->id = g_strdup(id); + return G_USB_DEVICE_EVENT(self); +} + +/** + * g_usb_device_event_get_id: + * @self: a #GUsbDeviceEvent + * + * Gets the event ID. + * + * Return value: string, or %NULL + * + * Since: 0.4.0 + **/ +const gchar * +g_usb_device_event_get_id(GUsbDeviceEvent *self) +{ + g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), NULL); + return self->id; +} + +/** + * g_usb_device_event_get_bytes: + * @self: a #GUsbDeviceEvent + * + * Gets any bytes data from the event. + * + * Return value: (transfer none): a #GBytes, or %NULL + * + * Since: 0.4.0 + **/ +GBytes * +g_usb_device_event_get_bytes(GUsbDeviceEvent *self) +{ + g_return_val_if_fail(G_USB_IS_DEVICE_EVENT(self), NULL); + return self->bytes; +} + +/** + * g_usb_device_event_set_bytes: + * @self: a #GUsbDeviceEvent + * @bytes: a #GBytes + * + * Set the bytes data to the event. + * + * Since: 0.4.0 + **/ +void +g_usb_device_event_set_bytes(GUsbDeviceEvent *self, GBytes *bytes) +{ + g_return_if_fail(G_USB_IS_DEVICE_EVENT(self)); + g_return_if_fail(bytes != NULL); + if (self->bytes != NULL) + g_bytes_unref(self->bytes); + self->bytes = g_bytes_ref(bytes); +} + +void +_g_usb_device_event_set_bytes_raw(GUsbDeviceEvent *self, gconstpointer buf, gsize bufsz) +{ + g_return_if_fail(G_USB_IS_DEVICE_EVENT(self)); + g_return_if_fail(buf != NULL); + g_return_if_fail(bufsz > 0); + if (self->bytes != NULL) + g_bytes_unref(self->bytes); + self->bytes = g_bytes_new(buf, bufsz); +} diff --git a/gusb/gusb-device-event.h b/gusb/gusb-device-event.h new file mode 100644 index 0000000..f44db43 --- /dev/null +++ b/gusb/gusb-device-event.h @@ -0,0 +1,24 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define G_USB_TYPE_DEVICE_EVENT (g_usb_device_event_get_type()) +G_DECLARE_FINAL_TYPE(GUsbDeviceEvent, g_usb_device_event, G_USB, DEVICE_EVENT, GObject) + +const gchar * +g_usb_device_event_get_id(GUsbDeviceEvent *self); +GBytes * +g_usb_device_event_get_bytes(GUsbDeviceEvent *self); +void +g_usb_device_event_set_bytes(GUsbDeviceEvent *self, GBytes *bytes); + +G_END_DECLS diff --git a/gusb/gusb-device.c b/gusb/gusb-device.c index daacd6e..ebba9a9 100644 --- a/gusb/gusb-device.c +++ b/gusb/gusb-device.c @@ -21,6 +21,7 @@ #include "gusb-bos-descriptor-private.h" #include "gusb-context-private.h" +#include "gusb-device-event-private.h" #include "gusb-device-private.h" #include "gusb-interface-private.h" #include "gusb-util.h" @@ -40,6 +41,8 @@ typedef struct { gboolean bos_descriptors_valid; GPtrArray *interfaces; /* of GUsbInterface */ GPtrArray *bos_descriptors; /* of GUsbBosDescriptor */ + GPtrArray *events; /* of GUsbDeviceEvent */ + guint event_idx; } GUsbDevicePrivate; enum { PROP_0, PROP_LIBUSB_DEVICE, PROP_CONTEXT, PROP_PLATFORM_ID, N_PROPERTIES }; @@ -81,6 +84,7 @@ g_usb_device_finalize(GObject *object) g_free(priv->platform_id); g_ptr_array_unref(priv->interfaces); g_ptr_array_unref(priv->bos_descriptors); + g_ptr_array_unref(priv->events); G_OBJECT_CLASS(g_usb_device_parent_class)->finalize(object); } @@ -209,6 +213,7 @@ g_usb_device_init(GUsbDevice *self) GUsbDevicePrivate *priv = GET_PRIVATE(self); priv->interfaces = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->bos_descriptors = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); + priv->events = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } gboolean @@ -281,9 +286,23 @@ _g_usb_device_load(GUsbDevice *self, JsonObject *json_object, GError **error) } } + /* array of events */ + if (json_object_has_member(json_object, "UsbEvents")) { + JsonArray *json_array = json_object_get_array_member(json_object, "UsbEvents"); + for (guint i = 0; i < json_array_get_length(json_array); i++) { + JsonNode *node_tmp = json_array_get_element(json_array, i); + JsonObject *obj_tmp = json_node_get_object(node_tmp); + g_autoptr(GUsbDeviceEvent) event = _g_usb_device_event_new(NULL); + if (!_g_usb_device_event_load(event, obj_tmp, error)) + return FALSE; + g_ptr_array_add(priv->events, g_steal_pointer(&event)); + } + } + /* success */ priv->interfaces_valid = TRUE; priv->bos_descriptors_valid = TRUE; + priv->event_idx = 0; return TRUE; } @@ -379,6 +398,18 @@ _g_usb_device_save(GUsbDevice *self, JsonBuilder *json_builder, GError **error) json_builder_end_array(json_builder); } + /* events */ + if (priv->events->len > 0) { + json_builder_set_member_name(json_builder, "UsbEvents"); + json_builder_begin_array(json_builder); + for (guint i = 0; i < priv->events->len; i++) { + GUsbDeviceEvent *event = g_ptr_array_index(priv->events, i); + if (!_g_usb_device_event_save(event, json_builder, error)) + return FALSE; + } + json_builder_end_array(json_builder); + } + /* success */ json_builder_end_object(json_builder); return TRUE; @@ -637,6 +668,55 @@ g_usb_device_open(GUsbDevice *self, GError **error) return _g_usb_device_open_internal(self, error); } +/* transfer none */ +static GUsbDeviceEvent * +g_usb_device_load_event(GUsbDevice *self, const gchar *id) +{ + GUsbDevicePrivate *priv = GET_PRIVATE(self); + + /* reset back to the beginning */ + if (priv->event_idx >= priv->events->len) + priv->event_idx = 0; + + /* look for the next event in the sequence */ + for (guint i = priv->event_idx; i < priv->events->len; i++) { + GUsbDeviceEvent *event = g_ptr_array_index(priv->events, i); + if (g_strcmp0(g_usb_device_event_get_id(event), id) == 0) { + g_debug("found in-order %s at position %u", id, i); + priv->event_idx = i + 1; + return event; + } + } + + /* look for *any* event that matches */ + for (guint i = 0; i < priv->events->len; i++) { + GUsbDeviceEvent *event = g_ptr_array_index(priv->events, i); + if (g_strcmp0(g_usb_device_event_get_id(event), id) == 0) { + g_debug("found out-of-order %s at position %u", id, i); + return event; + } + } + + /* nothing found */ + return NULL; +} + +/* transfer none */ +static GUsbDeviceEvent * +g_usb_device_save_event(GUsbDevice *self, const gchar *id) +{ + GUsbDevicePrivate *priv = GET_PRIVATE(self); + GUsbDeviceEvent *event; + + g_return_val_if_fail(G_USB_IS_DEVICE(self), NULL); + g_return_val_if_fail(id != NULL, NULL); + + /* success */ + event = _g_usb_device_event_new(id); + g_ptr_array_add(priv->events, event); + return event; +} + /** * g_usb_device_get_custom_index: * @self: a #GUsbDevice @@ -659,14 +739,46 @@ g_usb_device_get_custom_index(GUsbDevice *self, GError **error) { GUsbDevicePrivate *priv = GET_PRIVATE(self); + GUsbDeviceEvent *event; const struct libusb_interface_descriptor *ifp; gint rc; guint8 idx = 0x00; struct libusb_config_descriptor *config; + g_autofree gchar *event_id = NULL; - /* sanity check */ - if (priv->device == NULL) - return 0x0; + /* build event key either for load or save */ + if (priv->device == NULL || + g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + event_id = g_strdup_printf( + "GetCustomIndex:ClassId=0x%02x,SubclassId=0x%02x,ProtocolId=0x%02x", + class_id, + subclass_id, + protocol_id); + } + + /* emulated */ + if (priv->device == NULL) { + GBytes *bytes; + event = g_usb_device_load_event(self, event_id); + if (event == NULL) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event for %s", + event_id); + return 0x00; + } + bytes = g_usb_device_event_get_bytes(event); + if (bytes == NULL || g_bytes_get_size(bytes) != 1) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return 0x00; + } + return ((const guint8 *)g_bytes_get_data(bytes, NULL))[0]; + } rc = libusb_get_active_config_descriptor(priv->device, &config); if (!g_usb_device_libusb_error_to_gerror(self, rc, error)) @@ -695,6 +807,11 @@ g_usb_device_get_custom_index(GUsbDevice *self, class_id, subclass_id, protocol_id); + + } else if (g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + /* save */ + event = g_usb_device_save_event(self, event_id); + _g_usb_device_event_set_bytes_raw(event, &idx, sizeof(idx)); } libusb_free_config_descriptor(config); @@ -814,6 +931,27 @@ g_usb_device_get_interfaces(GUsbDevice *self, GError **error) return g_ptr_array_ref(priv->interfaces); } +/** + * g_usb_device_get_events: + * @self: a #GUsbDevice + * + * Gets all the events saved by the device. + * + * Events are only collected when the `G_USB_CONTEXT_FLAGS_SAVE_EVENTS` flag is used before + * enumerating the context. Events can be used to replay device transactions. + * + * Return value: (transfer container) (element-type GUsbDeviceEvent): an array of events + * + * Since: 0.4.0 + **/ +GPtrArray * +g_usb_device_get_events(GUsbDevice *self) +{ + GUsbDevicePrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(G_USB_IS_DEVICE(self), NULL); + return g_ptr_array_ref(priv->events); +} + /** * g_usb_device_invalidate: * @self: a #GUsbDevice @@ -1239,13 +1377,44 @@ gchar * g_usb_device_get_string_descriptor(GUsbDevice *self, guint8 desc_index, GError **error) { GUsbDevicePrivate *priv = GET_PRIVATE(self); + GUsbDeviceEvent *event; gint rc; /* libusb_get_string_descriptor_ascii returns max 128 bytes */ unsigned char buf[128]; + g_autofree gchar *event_id = NULL; g_return_val_if_fail(G_USB_IS_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); + /* build event key either for load or save */ + if (priv->device == NULL || + g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + event_id = g_strdup_printf("GetStringDescriptor:DescIndex=0x%02x", desc_index); + } + + /* emulated */ + if (priv->device == NULL) { + GBytes *bytes; + event = g_usb_device_load_event(self, event_id); + if (event == NULL) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return NULL; + } + bytes = g_usb_device_event_get_bytes(event); + if (bytes == NULL) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return NULL; + } + return g_strndup(g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); + } if (priv->handle == NULL) { g_usb_device_not_open_error(self, error); return NULL; @@ -1257,6 +1426,12 @@ g_usb_device_get_string_descriptor(GUsbDevice *self, guint8 desc_index, GError * return NULL; } + /* save */ + if (g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + event = g_usb_device_save_event(self, event_id); + _g_usb_device_event_set_bytes_raw(event, buf, sizeof(buf)); + } + return g_strdup((const gchar *)buf); } @@ -1282,12 +1457,48 @@ g_usb_device_get_string_descriptor_bytes_full(GUsbDevice *self, GError **error) { GUsbDevicePrivate *priv = GET_PRIVATE(self); + GUsbDeviceEvent *event; gint rc; g_autofree guint8 *buf = g_malloc0(length); + g_autofree gchar *event_id = NULL; g_return_val_if_fail(G_USB_IS_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); + /* build event key either for load or save */ + if (priv->device == NULL || + g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + event_id = g_strdup_printf( + "GetStringDescriptorBytes:DescIndex=0x%02x,Langid=0x%04x,Length=0x%x", + desc_index, + langid, + (guint)length); + } + + /* emulated */ + if (priv->device == NULL) { + GBytes *bytes; + event = g_usb_device_load_event(self, event_id); + if (event == NULL) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event for %s", + event_id); + return NULL; + } + bytes = g_usb_device_event_get_bytes(event); + if (bytes == NULL) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return NULL; + } + return g_bytes_ref(bytes); + } + if (priv->handle == NULL) { g_usb_device_not_open_error(self, error); return NULL; @@ -1299,6 +1510,12 @@ g_usb_device_get_string_descriptor_bytes_full(GUsbDevice *self, return NULL; } + /* save */ + if (g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + event = g_usb_device_save_event(self, event_id); + _g_usb_device_event_set_bytes_raw(event, buf, rc); + } + return g_bytes_new(buf, rc); } @@ -1535,8 +1752,9 @@ typedef struct { GCancellable *cancellable; gulong cancellable_id; struct libusb_transfer *transfer; - guint8 *data; /* owned by the user */ - guint8 *data_raw; /* owned by the task */ + guint8 *data; /* owned by the user */ + guint8 *data_raw; /* owned by the task */ + GUsbDeviceEvent *event; /* no-ref */ } GcmDeviceReq; static void @@ -1611,6 +1829,7 @@ static void g_usb_device_async_transfer_cb(struct libusb_transfer *transfer) { GTask *task = transfer->user_data; + GcmDeviceReq *req = g_task_get_task_data(task); gboolean ret; GError *error = NULL; @@ -1619,6 +1838,11 @@ g_usb_device_async_transfer_cb(struct libusb_transfer *transfer) if (!ret) { g_task_return_error(task, error); } else { + if (req->event != NULL) { + _g_usb_device_event_set_bytes_raw(req->event, + transfer->buffer, + (gsize)transfer->actual_length); + } g_task_return_int(task, transfer->actual_length); } @@ -1647,6 +1871,12 @@ g_usb_device_control_transfer_cb(struct libusb_transfer *transfer) memmove(req->data, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, (gsize)transfer->actual_length); + if (req->event != NULL) { + _g_usb_device_event_set_bytes_raw(req->event, + transfer->buffer + + LIBUSB_CONTROL_SETUP_SIZE, + (gsize)transfer->actual_length); + } g_task_return_int(task, transfer->actual_length); } @@ -1691,9 +1921,68 @@ g_usb_device_control_transfer_async(GUsbDevice *self, gint rc; guint8 request_type_raw = 0; GError *error = NULL; + GUsbDeviceEvent *event = NULL; + g_autofree gchar *event_id = NULL; g_return_if_fail(G_USB_IS_DEVICE(self)); + /* build event key either for load or save */ + if (priv->device == NULL || + g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + g_autofree gchar *data_base64 = g_base64_encode(data, length); + event_id = g_strdup_printf("ControlTransfer:" + "Direction=0x%02x," + "RequestType=0x%02x," + "Recipient=0x%02x," + "Request=0x%02x," + "Value=0x%04x," + "Idx=0x%04x," + "Data=%s," + "Length=0x%x", + direction, + request_type, + recipient, + request, + value, + idx, + data_base64, + (guint)length); + } + + /* emulated */ + if (priv->device == NULL) { + GBytes *bytes; + event = g_usb_device_load_event(self, event_id); + if (event == NULL) { + g_task_report_new_error(self, + callback, + user_data, + g_usb_device_control_transfer_async, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event for %s", + event_id); + return; + } + bytes = g_usb_device_event_get_bytes(event); + if (bytes == NULL) { + g_task_report_new_error(self, + callback, + user_data, + g_usb_device_control_transfer_async, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return; + } + task = g_task_new(self, cancellable, callback, user_data); + memcpy(data, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); + g_task_return_int(task, g_bytes_get_size(bytes)); + g_object_unref(task); + return; + } + if (priv->handle == NULL) { g_usb_device_async_not_open_error(self, callback, @@ -1702,9 +1991,14 @@ g_usb_device_control_transfer_async(GUsbDevice *self, return; } + /* save */ + if (g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) + event = g_usb_device_save_event(self, event_id); + req = g_slice_new0(GcmDeviceReq); req->transfer = libusb_alloc_transfer(0); req->data = data; + req->event = event; task = g_task_new(self, cancellable, callback, user_data); g_task_set_task_data(task, req, (GDestroyNotify)g_usb_device_req_free); @@ -1807,9 +2101,58 @@ g_usb_device_bulk_transfer_async(GUsbDevice *self, GcmDeviceReq *req; gint rc; GError *error = NULL; + GUsbDeviceEvent *event = NULL; + g_autofree gchar *event_id = NULL; g_return_if_fail(G_USB_IS_DEVICE(self)); + /* build event key either for load or save */ + if (priv->device == NULL || + g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + g_autofree gchar *data_base64 = g_base64_encode(data, length); + event_id = g_strdup_printf("BulkTransfer:" + "Endpoint=0x%02x," + "Data=%s," + "Length=0x%x", + endpoint, + data_base64, + (guint)length); + } + + /* emulated */ + if (priv->device == NULL) { + GBytes *bytes; + event = g_usb_device_load_event(self, event_id); + if (event == NULL) { + g_task_report_new_error(self, + callback, + user_data, + g_usb_device_control_transfer_async, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event for %s", + event_id); + return; + } + bytes = g_usb_device_event_get_bytes(event); + if (bytes == NULL) { + g_task_report_new_error(self, + callback, + user_data, + g_usb_device_control_transfer_async, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return; + } + task = g_task_new(self, cancellable, callback, user_data); + memcpy(data, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); + g_task_return_int(task, g_bytes_get_size(bytes)); + g_object_unref(task); + return; + } + if (priv->handle == NULL) { g_usb_device_async_not_open_error(self, callback, @@ -1818,8 +2161,13 @@ g_usb_device_bulk_transfer_async(GUsbDevice *self, return; } + /* save */ + if (g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) + event = g_usb_device_save_event(self, event_id); + req = g_slice_new0(GcmDeviceReq); req->transfer = libusb_alloc_transfer(0); + req->event = event; task = g_task_new(self, cancellable, callback, user_data); g_task_set_task_data(task, req, (GDestroyNotify)g_usb_device_req_free); @@ -1912,9 +2260,58 @@ g_usb_device_interrupt_transfer_async(GUsbDevice *self, GcmDeviceReq *req; GError *error = NULL; gint rc; + GUsbDeviceEvent *event = NULL; + g_autofree gchar *event_id = NULL; g_return_if_fail(G_USB_IS_DEVICE(self)); + /* build event key either for load or save */ + if (priv->device == NULL || + g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) { + g_autofree gchar *data_base64 = g_base64_encode(data, length); + event_id = g_strdup_printf("InterruptTransfer:" + "Endpoint=0x%02x," + "Data=%s," + "Length=0x%x", + endpoint, + data_base64, + (guint)length); + } + + /* emulated */ + if (priv->device == NULL) { + GBytes *bytes; + event = g_usb_device_load_event(self, event_id); + if (event == NULL) { + g_task_report_new_error(self, + callback, + user_data, + g_usb_device_interrupt_transfer_async, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event for %s", + event_id); + return; + } + bytes = g_usb_device_event_get_bytes(event); + if (bytes == NULL) { + g_task_report_new_error(self, + callback, + user_data, + g_usb_device_interrupt_transfer_async, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no matching event data for %s", + event_id); + return; + } + task = g_task_new(self, cancellable, callback, user_data); + memcpy(data, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); + g_task_return_int(task, g_bytes_get_size(bytes)); + g_object_unref(task); + return; + } + if (priv->handle == NULL) { g_usb_device_async_not_open_error(self, callback, @@ -1923,8 +2320,13 @@ g_usb_device_interrupt_transfer_async(GUsbDevice *self, return; } + /* save */ + if (g_usb_context_get_flags(priv->context) & G_USB_CONTEXT_FLAGS_SAVE_EVENTS) + event = g_usb_device_save_event(self, event_id); + req = g_slice_new0(GcmDeviceReq); req->transfer = libusb_alloc_transfer(0); + req->event = event; task = g_task_new(self, cancellable, callback, user_data); g_task_set_task_data(task, req, (GDestroyNotify)g_usb_device_req_free); diff --git a/gusb/gusb-device.h b/gusb/gusb-device.h index 39d778c..4488653 100644 --- a/gusb/gusb-device.h +++ b/gusb/gusb-device.h @@ -194,6 +194,9 @@ g_usb_device_get_interface(GUsbDevice *self, GPtrArray * g_usb_device_get_interfaces(GUsbDevice *self, GError **error); +GPtrArray * +g_usb_device_get_events(GUsbDevice *self); + GPtrArray * g_usb_device_get_bos_descriptors(GUsbDevice *self, GError **error); GUsbBosDescriptor * diff --git a/gusb/gusb-self-test.c b/gusb/gusb-self-test.c index 462b8d7..2b8e12b 100644 --- a/gusb/gusb-self-test.c +++ b/gusb/gusb-self-test.c @@ -404,7 +404,133 @@ gusb_device_munki_func(void) static void gusb_device_json_func(void) { - // xxxxx + JsonObject *json_obj; + gboolean ret; + guint8 idx; + g_autoptr(GUsbDevice) device = NULL; + g_autofree gchar *tmp = NULL; + g_autoptr(GUsbContext) ctx = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(JsonParser) parser = json_parser_new(); + const gchar *json = + "{" + " \"UsbDevices\" : [" + " {" + " \"PlatformId\" : \"usb:01:00:06\"," + " \"IdVendor\" : 10047," + " \"IdProduct\" : 4100," + " \"Device\" : 2," + " \"USB\" : 512," + " \"Manufacturer\" : 1," + " \"UsbInterfaces\" : [" + " {" + " \"Length\" : 9," + " \"DescriptorType\" : 4," + " \"InterfaceNumber\" : 1," + " \"InterfaceClass\" : 255," + " \"InterfaceSubClass\" : 70," + " \"InterfaceProtocol\" : 87," + " \"Interface\" : 3" + " }," + " {" + " \"Length\" : 9," + " \"DescriptorType\" : 4," + " \"InterfaceNumber\" : 2," + " \"InterfaceClass\" : 255," + " \"InterfaceSubClass\" : 71," + " \"InterfaceProtocol\" : 85," + " \"Interface\" : 4" + " }," + " {" + " \"Length\" : 9," + " \"DescriptorType\" : 4," + " \"InterfaceClass\" : 3," + " \"UsbEndpoints\" : [" + " {" + " \"DescriptorType\" : 5," + " \"EndpointAddress\" : 129," + " \"Interval\" : 1," + " \"MaxPacketSize\" : 64" + " }," + " {" + " \"DescriptorType\" : 5," + " \"EndpointAddress\" : 1," + " \"Interval\" : 1," + " \"MaxPacketSize\" : 64" + " }" + " ]," + " \"ExtraData\" : \"CSERAQABIh0A\"" + " }" + " ]," + " \"UsbEvents\" : [" + " {" + " \"Id\" : " + "\"GetCustomIndex:ClassId=0xff,SubclassId=0x46,ProtocolId=0x57\"," + " \"Data\" : \"Aw==\"" + " }," + " {" + " \"Id\" : " + "\"GetCustomIndex:ClassId=0xff,SubclassId=0x46,ProtocolId=0x57\"," + " \"Data\" : \"Aw==\"" + " }," + " {" + " \"Id\" : \"GetStringDescriptor:DescIndex=0x03\"," + " \"Data\" : " + "\"Mi4wLjcAAAAR3HzMiH8AAAAAAAAAAAAA8HBIAQAAAACQ6AsW/n8AADeVQAAAAAAAsOgLFv5/" + "AABNkkAAVwAAAEboCxb/" + "fwAAkAZLAQAAAAAAZkAAAAAAAwAAAAAAAAAAsPpJAQAAAAAwBksBAAAAAJAZTwEAAAAAAFZZJp63C7g=\"" + " }" + " ]" + " }" + " ]" + "}"; + ctx = g_usb_context_new(&error); + g_assert_no_error(error); + g_assert(ctx != NULL); + + /* parse */ + ret = json_parser_load_from_data(parser, json, -1, &error); + g_assert_no_error(error); + g_assert(ret); + json_obj = json_node_get_object(json_parser_get_root(parser)); + ret = g_usb_context_load(ctx, json_obj, &error); + g_assert_no_error(error); + g_assert(ret); + + /* get vendor data */ + device = g_usb_context_find_by_vid_pid(ctx, 0x273f, 0x1004, &error); + g_assert_no_error(error); + g_assert(device != NULL); + idx = g_usb_device_get_custom_index(device, + G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, + 'F', + 'W', + &error); + g_assert_no_error(error); + g_assert_cmpint(idx, ==, 3); + + /* in-order, repeat */ + idx = g_usb_device_get_custom_index(device, + G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, + 'F', + 'W', + &error); + g_assert_no_error(error); + g_assert_cmpint(idx, ==, 3); + + /* out-of-order */ + idx = g_usb_device_get_custom_index(device, + G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, + 'F', + 'W', + &error); + g_assert_no_error(error); + g_assert_cmpint(idx, ==, 3); + + /* get the firmware version */ + tmp = g_usb_device_get_string_descriptor(device, idx, &error); + g_assert_no_error(error); + g_assert_cmpstr(tmp, ==, "2.0.7"); } static void @@ -412,12 +538,17 @@ gusb_device_ch2_func(void) { gboolean ret; guint8 idx; + g_autofree gchar *data = NULL; g_autofree gchar *tmp = NULL; g_autoptr(GError) error = NULL; g_autoptr(GUsbContext) ctx = NULL; g_autoptr(GUsbDevice) device = NULL; + g_autoptr(JsonBuilder) json_builder = json_builder_new(); + g_autoptr(JsonGenerator) json_generator = NULL; + g_autoptr(JsonNode) json_root = NULL; ctx = g_usb_context_new(&error); + g_usb_context_set_flags(ctx, G_USB_CONTEXT_FLAGS_SAVE_EVENTS); g_assert_no_error(error); g_assert(ctx != NULL); @@ -456,6 +587,20 @@ gusb_device_ch2_func(void) ret = g_usb_device_close(device, &error); g_assert_no_error(error); g_assert(ret); + + /* save context */ + ret = g_usb_context_save(ctx, json_builder, &error); + g_assert_no_error(error); + g_assert(ret); + + /* export as a string */ + json_root = json_builder_get_root(json_builder); + json_generator = json_generator_new(); + json_generator_set_pretty(json_generator, TRUE); + json_generator_set_root(json_generator, json_root); + data = json_generator_to_data(json_generator, NULL); + g_assert_nonnull(data); + g_print("%s\n", data); } int diff --git a/gusb/libgusb.ver b/gusb/libgusb.ver index d189ec4..fbe7890 100644 --- a/gusb/libgusb.ver +++ b/gusb/libgusb.ver @@ -174,8 +174,13 @@ LIBGUSB_0.4.0 { g_usb_bos_descriptor_get_type; g_usb_context_load; g_usb_context_save; + g_usb_device_event_get_bytes; + g_usb_device_event_get_id; + g_usb_device_event_get_type; + g_usb_device_event_set_bytes; g_usb_device_get_bos_descriptor; g_usb_device_get_bos_descriptors; + g_usb_device_get_events; g_usb_device_invalidate; local: *; } LIBGUSB_0.3.10; diff --git a/gusb/meson.build b/gusb/meson.build index eeef369..2b3ad07 100644 --- a/gusb/meson.build +++ b/gusb/meson.build @@ -29,6 +29,7 @@ install_headers([ 'gusb-context.h', 'gusb-context-private.h', 'gusb-device.h', + 'gusb-device-event.h', 'gusb-device-private.h', 'gusb-device-list.h', 'gusb-interface.h', @@ -55,6 +56,7 @@ gusb = library( sources : [ 'gusb-context.c', 'gusb-device.c', + 'gusb-device-event.c', 'gusb-device-list.c', 'gusb-interface.c', 'gusb-bos-descriptor.c', @@ -111,6 +113,9 @@ libgusb_girtarget = gnome.generate_gir(gusb, 'gusb-context-private.h', 'gusb-device.c', 'gusb-device.h', + 'gusb-device-event.c', + 'gusb-device-event.h', + 'gusb-device-event-private.h', 'gusb-device-list.c', 'gusb-device-list.h', 'gusb-device-private.h', @@ -200,6 +205,7 @@ if get_option('tests') sources : [ 'gusb-context.c', 'gusb-device.c', + 'gusb-device-event.c', 'gusb-device-list.c', 'gusb-interface.c', 'gusb-bos-descriptor.c', diff --git a/tools/gusb-main.c b/tools/gusb-main.c index ab8e4ad..1f0826d 100644 --- a/tools/gusb-main.c +++ b/tools/gusb-main.c @@ -461,7 +461,9 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUsbCmdPrivate, gusb_cmd_private_free) int main(int argc, char *argv[]) { + GUsbContextFlags context_flags = G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES; gboolean verbose = FALSE; + gboolean save_events = FALSE; g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *options_help = NULL; g_autoptr(GError) error = NULL; @@ -474,6 +476,13 @@ main(int argc, char *argv[]) &verbose, "Show extra debugging information", NULL}, + {"events", + '\0', + 0, + G_OPTION_ARG_NONE, + &save_events, + "Save USB events", + NULL}, {NULL}}; setlocale(LC_ALL, ""); @@ -503,7 +512,9 @@ main(int argc, char *argv[]) /* GUsbContext */ priv->usb_ctx = g_usb_context_new(NULL); - g_usb_context_set_flags(priv->usb_ctx, G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES); + if (save_events) + context_flags |= G_USB_CONTEXT_FLAGS_SAVE_EVENTS; + g_usb_context_set_flags(priv->usb_ctx, context_flags); /* add commands */ priv->cmd_array = g_ptr_array_new_with_free_func((GDestroyNotify)gusb_cmd_item_free); -- cgit v1.2.1