diff options
Diffstat (limited to 'gusb/gusb-device.c')
-rw-r--r-- | gusb/gusb-device.c | 412 |
1 files changed, 407 insertions, 5 deletions
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); |