summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2022-09-05 20:50:06 +0100
committerRichard Hughes <richard@hughsie.com>2022-09-08 09:06:56 +0100
commit1c778b7013582bab13a66467e12c7573f615cc8c (patch)
tree953159ce969761acb608166d0a5c02bde618882d
parent4eca7b72db7f5c921927ca5b7a16a8d05b093861 (diff)
downloadgusb-1c778b7013582bab13a66467e12c7573f615cc8c.tar.gz
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.
-rw-r--r--gusb/gusb-context.h1
-rw-r--r--gusb/gusb-device-event-private.h26
-rw-r--r--gusb/gusb-device-event.c186
-rw-r--r--gusb/gusb-device-event.h24
-rw-r--r--gusb/gusb-device.c412
-rw-r--r--gusb/gusb-device.h3
-rw-r--r--gusb/gusb-self-test.c147
-rw-r--r--gusb/libgusb.ver5
-rw-r--r--gusb/meson.build6
-rw-r--r--tools/gusb-main.c13
10 files changed, 816 insertions, 7 deletions
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 <richard@hughsie.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gusb/gusb-device-event.h>
+#include <json-glib/json-glib.h>
+#include <libusb.h>
+
+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 <richard@hughsie.com>
+ *
+ * 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 <richard@hughsie.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+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);
@@ -815,6 +932,27 @@ g_usb_device_get_interfaces(GUsbDevice *self, GError **error)
}
/**
+ * 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
@@ -195,6 +195,9 @@ 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 *
g_usb_device_get_bos_descriptor(GUsbDevice *self, guint8 capability, GError **error);
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);