From 88e7c32dea37a8db19256745a0b334ec2b1b80a6 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Sun, 14 Aug 2022 17:30:58 +0100 Subject: Serialize the GUsbDevice to and from JSON format This allows us to write tests that mock up entire devices. --- .github/workflows/main.yml | 2 +- contrib/ci/Dockerfile-debian | 2 +- contrib/ci/Dockerfile-fedora | 1 + contrib/libgusb.spec.in | 1 + gusb/gusb-bos-descriptor-private.h | 6 + gusb/gusb-bos-descriptor.c | 64 ++++++++++ gusb/gusb-context.c | 86 +++++++++++++ gusb/gusb-context.h | 5 + gusb/gusb-device-private.h | 4 + gusb/gusb-device.c | 246 +++++++++++++++++++++++++++++++++++-- gusb/gusb-device.h | 1 + gusb/gusb-endpoint-private.h | 7 ++ gusb/gusb-endpoint.c | 95 ++++++++++++++ gusb/gusb-interface-private.h | 6 + gusb/gusb-interface.c | 117 ++++++++++++++++++ gusb/gusb-self-test.c | 7 ++ gusb/libgusb.ver | 2 + gusb/meson.build | 5 +- meson.build | 2 + tools/gusb-main.c | 75 +++++++++++ tools/meson.build | 1 + 21 files changed, 722 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 368f2e8..95e2f1a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,6 @@ jobs: usesh: true mem: 8192 prepare: | - pkg install -y git python3 glib meson pkgconf gobject-introspection usbids + pkg install -y git python3 glib json-glib meson pkgconf gobject-introspection usbids sync: rsync run: ./contrib/ci/build_and_test.sh -Dvapi=false -Ddocs=false -Dusb_ids=/usr/local/share/usbids/usb.ids diff --git a/contrib/ci/Dockerfile-debian b/contrib/ci/Dockerfile-debian index 097e98f..2dfc3fe 100644 --- a/contrib/ci/Dockerfile-debian +++ b/contrib/ci/Dockerfile-debian @@ -2,7 +2,7 @@ FROM debian:buster RUN echo "deb-src http://deb.debian.org/debian/ buster main" >> /etc/apt/sources.list RUN apt-get update -qq -RUN apt-get install -yq --no-install-recommends meson +RUN apt-get install -yq --no-install-recommends meson libjson-glib-dev RUN apt-get build-dep --allow-unauthenticated -yq libgusb RUN mkdir /build diff --git a/contrib/ci/Dockerfile-fedora b/contrib/ci/Dockerfile-fedora index 993b285..00e5e23 100644 --- a/contrib/ci/Dockerfile-fedora +++ b/contrib/ci/Dockerfile-fedora @@ -4,6 +4,7 @@ RUN dnf -y update RUN dnf -y install \ diffutils \ glib2-devel \ + json-glib-devel \ gobject-introspection-devel \ gtk-doc \ libusb1-devel \ diff --git a/contrib/libgusb.spec.in b/contrib/libgusb.spec.in index 7343586..eb7589f 100644 --- a/contrib/libgusb.spec.in +++ b/contrib/libgusb.spec.in @@ -9,6 +9,7 @@ URL: https://github.com/hughsie/libgusb Source0: http://people.freedesktop.org/~hughsient/releases/%{name}-%{version}.tar.xz BuildRequires: glib2-devel >= 2.38.0 +BuildRequires: json-glib-devel BuildRequires: gobject-introspection-devel BuildRequires: gtk-doc BuildRequires: libusb1-devel >= 1.0.19 diff --git a/gusb/gusb-bos-descriptor-private.h b/gusb/gusb-bos-descriptor-private.h index fed994b..1b8cde7 100644 --- a/gusb/gusb-bos-descriptor-private.h +++ b/gusb/gusb-bos-descriptor-private.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include G_BEGIN_DECLS @@ -15,4 +16,9 @@ G_BEGIN_DECLS GUsbBosDescriptor * _g_usb_bos_descriptor_new(const struct libusb_bos_dev_capability_descriptor *bos_cap); +gboolean +_g_usb_bos_descriptor_load(GUsbBosDescriptor *self, JsonObject *json_object, GError **error); +gboolean +_g_usb_bos_descriptor_save(GUsbBosDescriptor *self, JsonBuilder *json_builder, GError **error); + G_END_DECLS diff --git a/gusb/gusb-bos-descriptor.c b/gusb/gusb-bos-descriptor.c index 48265dd..2e4134b 100644 --- a/gusb/gusb-bos-descriptor.c +++ b/gusb/gusb-bos-descriptor.c @@ -53,6 +53,70 @@ g_usb_bos_descriptor_init(GUsbBosDescriptor *self) { } +gboolean +_g_usb_bos_descriptor_load(GUsbBosDescriptor *self, JsonObject *json_object, GError **error) +{ + const gchar *str; + + g_return_val_if_fail(G_USB_IS_BOS_DESCRIPTOR(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->bos_cap.bDevCapabilityType = + json_object_get_int_member_with_default(json_object, "DevCapabilityType", 0x0); + + /* extra data */ + str = json_object_get_string_member_with_default(json_object, "ExtraData", NULL); + if (str != NULL) { + gsize bufsz = 0; + g_autofree guchar *buf = g_base64_decode(str, &bufsz); + if (self->extra != NULL) + g_bytes_unref(self->extra); + self->extra = 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_bos_descriptor_save(GUsbBosDescriptor *self, JsonBuilder *json_builder, GError **error) +{ + g_return_val_if_fail(G_USB_IS_BOS_DESCRIPTOR(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); + + /* optional properties */ + if (self->bos_cap.bDevCapabilityType != 0) { + json_builder_set_member_name(json_builder, "DevCapabilityType"); + json_builder_add_int_value(json_builder, self->bos_cap.bDevCapabilityType); + } + + /* extra data */ + if (self->extra != NULL && g_bytes_get_size(self->extra) > 0) { + g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->extra, NULL), + g_bytes_get_size(self->extra)); + json_builder_set_member_name(json_builder, "ExtraData"); + json_builder_add_string_value(json_builder, str); + } + + /* success */ + json_builder_end_object(json_builder); + return TRUE; +} + /** * _g_usb_bos_descriptor_new: * diff --git a/gusb/gusb-context.c b/gusb/gusb-context.c index 86640be..d32c3a4f 100644 --- a/gusb/gusb-context.c +++ b/gusb/gusb-context.c @@ -355,6 +355,92 @@ g_usb_context_remove_device(GUsbContext *self, struct libusb_device *dev) g_usb_context_emit_device_remove(self, device); } +/** + * g_usb_context_load: + * @context: a #GUsbContext + * @json_object: a #JsonObject + * @error: a #GError, or %NULL + * + * Loads the context from a loaded JSON object. + * + * Return value: %TRUE on success + * + * Since: 0.4.0 + **/ +gboolean +g_usb_context_load(GUsbContext *self, JsonObject *json_object, GError **error) +{ + GUsbContextPrivate *priv = GET_PRIVATE(self); + JsonArray *json_array; + + g_return_val_if_fail(G_USB_IS_CONTEXT(self), FALSE); + g_return_val_if_fail(json_object != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, -1); + + if (!json_object_has_member(json_object, "UsbDevices")) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no UsbDevices array"); + return FALSE; + } + json_array = json_object_get_array_member(json_object, "UsbDevices"); + 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(GUsbDevice) device = + g_object_new(G_USB_TYPE_DEVICE, "context", self, NULL); + if (!_g_usb_device_load(device, obj_tmp, error)) + return FALSE; + g_ptr_array_add(priv->devices, g_object_ref(device)); + g_signal_emit(self, signals[DEVICE_ADDED_SIGNAL], 0, device); + } + + /* success */ + priv->done_enumerate = TRUE; + return TRUE; +} + +/** + * g_usb_context_save: + * @context: a #GUsbContext + * @json_builder: a #JsonBuilder + * @error: a #GError, or %NULL + * + * Saves the context to an existing JSON builder. + * + * Return value: %TRUE on success + * + * Since: 0.4.0 + **/ +gboolean +g_usb_context_save(GUsbContext *self, JsonBuilder *json_builder, GError **error) +{ + GUsbContextPrivate *priv = GET_PRIVATE(self); + + g_return_val_if_fail(G_USB_IS_CONTEXT(self), FALSE); + g_return_val_if_fail(json_builder != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* start */ + g_usb_context_enumerate(self); + json_builder_begin_object(json_builder); + + /* array of devices */ + json_builder_set_member_name(json_builder, "UsbDevices"); + json_builder_begin_array(json_builder); + for (guint i = 0; i < priv->devices->len; i++) { + GUsbDevice *device = g_ptr_array_index(priv->devices, i); + if (!_g_usb_device_save(device, json_builder, error)) + return FALSE; + } + json_builder_end_array(json_builder); + + /* success */ + json_builder_end_object(json_builder); + return TRUE; +} + typedef struct { GUsbContext *self; libusb_device *dev; diff --git a/gusb/gusb-context.h b/gusb/gusb-context.h index 31c37b3..a39565f 100644 --- a/gusb/gusb-context.h +++ b/gusb/gusb-context.h @@ -70,6 +70,11 @@ g_usb_context_set_hotplug_poll_interval(GUsbContext *self, guint hotplug_poll_in void g_usb_context_enumerate(GUsbContext *self); +gboolean +g_usb_context_load(GUsbContext *self, JsonObject *json_object, GError **error); +gboolean +g_usb_context_save(GUsbContext *self, JsonBuilder *json_builder, GError **error); + void g_usb_context_set_debug(GUsbContext *self, GLogLevelFlags flags); GPtrArray * diff --git a/gusb/gusb-device-private.h b/gusb/gusb-device-private.h index 54acd83..70218b2 100644 --- a/gusb/gusb-device-private.h +++ b/gusb/gusb-device-private.h @@ -13,6 +13,10 @@ G_BEGIN_DECLS GUsbDevice * _g_usb_device_new(GUsbContext *context, libusb_device *device, GError **error); +gboolean +_g_usb_device_load(GUsbDevice *self, JsonObject *json_object, GError **error); +gboolean +_g_usb_device_save(GUsbDevice *self, JsonBuilder *json_builder, GError **error); libusb_device * _g_usb_device_get_device(GUsbDevice *self); diff --git a/gusb/gusb-device.c b/gusb/gusb-device.c index cd70d22..10422a9 100644 --- a/gusb/gusb-device.c +++ b/gusb/gusb-device.c @@ -148,17 +148,14 @@ static void g_usb_device_constructed(GObject *object) { GUsbDevice *self = G_USB_DEVICE(object); - GUsbDevicePrivate *priv; - gint rc; - - priv = GET_PRIVATE(self); - - if (!priv->device) - g_error("constructed without a libusb_device"); + GUsbDevicePrivate *priv = GET_PRIVATE(self); - rc = libusb_get_device_descriptor(priv->device, &priv->desc); - if (rc != LIBUSB_SUCCESS) - g_warning("Failed to get USB descriptor for device: %s", g_usb_strerror(rc)); + if (priv->device != NULL) { + gint rc = libusb_get_device_descriptor(priv->device, &priv->desc); + if (rc != LIBUSB_SUCCESS) + g_warning("Failed to get USB descriptor for device: %s", + g_usb_strerror(rc)); + } G_OBJECT_CLASS(g_usb_device_parent_class)->constructed(object); } @@ -212,6 +209,190 @@ g_usb_device_init(GUsbDevice *self) priv->bos_descriptors = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } +/** + * _g_usb_device_load: + * @device: a #GUsbDevice + * @json_object: a #JsonObject + * @error: a #GError, or %NULL + * + * Loads the device from a loaded JSON object. + * + * Return value: %TRUE on success + * + * Since: 0.4.0 + **/ +gboolean +_g_usb_device_load(GUsbDevice *self, JsonObject *json_object, GError **error) +{ + GUsbDevicePrivate *priv = GET_PRIVATE(self); + + g_return_val_if_fail(G_USB_IS_DEVICE(self), FALSE); + g_return_val_if_fail(json_object != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + +#if JSON_CHECK_VERSION(1, 6, 0) + /* optional properties */ + priv->desc.idVendor = json_object_get_int_member_with_default(json_object, "Vendor", 0x0); + priv->desc.idProduct = json_object_get_int_member_with_default(json_object, "Product", 0x0); + priv->desc.bcdDevice = json_object_get_int_member_with_default(json_object, "Device", 0x0); + priv->desc.bcdUSB = json_object_get_int_member_with_default(json_object, "USB", 0x0); + priv->desc.iManufacturer = + json_object_get_int_member_with_default(json_object, "Manufacturer", 0x0); + priv->desc.bDeviceClass = + json_object_get_int_member_with_default(json_object, "DeviceClass", 0x0); + priv->desc.bDeviceSubClass = + json_object_get_int_member_with_default(json_object, "DeviceSubClass", 0x0); + priv->desc.bDeviceProtocol = + json_object_get_int_member_with_default(json_object, "DeviceProtocol", 0x0); + priv->desc.iProduct = json_object_get_int_member_with_default(json_object, "Product", 0x0); + priv->desc.iSerialNumber = + json_object_get_int_member_with_default(json_object, "SerialNumber", 0x0); +#else + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "json-glib version too old"); + return FALSE; +#endif + + /* array of BOS descriptors */ + if (json_object_has_member(json_object, "UsbBosDescriptors")) { + JsonArray *json_array = + json_object_get_array_member(json_object, "UsbBosDescriptors"); + 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(GUsbBosDescriptor) bos_descriptor = + g_object_new(G_USB_TYPE_BOS_DESCRIPTOR, NULL); + if (!_g_usb_bos_descriptor_load(bos_descriptor, obj_tmp, error)) + return FALSE; + g_ptr_array_add(priv->bos_descriptors, g_object_ref(bos_descriptor)); + } + } + + /* array of interfaces */ + if (json_object_has_member(json_object, "UsbInterfaces")) { + JsonArray *json_array = json_object_get_array_member(json_object, "UsbInterfaces"); + 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(GUsbInterface) interface = + g_object_new(G_USB_TYPE_INTERFACE, NULL); + if (!_g_usb_interface_load(interface, obj_tmp, error)) + return FALSE; + g_ptr_array_add(priv->interfaces, g_object_ref(interface)); + } + } + + /* success */ + return TRUE; +} + +/** + * _g_usb_device_save: + * @device: a #GUsbDevice + * @json_builder: a #JsonBuilder + * @error: a #GError, or %NULL + * + * Saves the device to an existing JSON builder. + * + * Return value: %TRUE on success + * + * Since: 0.4.0 + **/ +gboolean +_g_usb_device_save(GUsbDevice *self, JsonBuilder *json_builder, GError **error) +{ + GUsbDevicePrivate *priv = GET_PRIVATE(self); + g_autoptr(GPtrArray) bos_descriptors = NULL; + g_autoptr(GPtrArray) interfaces = NULL; + g_autoptr(GError) error_bos = NULL; + g_autoptr(GError) error_interfaces = NULL; + + g_return_val_if_fail(G_USB_IS_DEVICE(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); + + /* optional properties */ + if (priv->desc.idVendor != 0) { + json_builder_set_member_name(json_builder, "Vendor"); + json_builder_add_int_value(json_builder, priv->desc.idVendor); + } + if (priv->desc.idProduct != 0) { + json_builder_set_member_name(json_builder, "Product"); + json_builder_add_int_value(json_builder, priv->desc.idProduct); + } + if (priv->desc.bcdDevice != 0) { + json_builder_set_member_name(json_builder, "Device"); + json_builder_add_int_value(json_builder, priv->desc.bcdDevice); + } + if (priv->desc.bcdUSB != 0) { + json_builder_set_member_name(json_builder, "USB"); + json_builder_add_int_value(json_builder, priv->desc.bcdUSB); + } + if (priv->desc.iManufacturer != 0) { + json_builder_set_member_name(json_builder, "Manufacturer"); + json_builder_add_int_value(json_builder, priv->desc.iManufacturer); + } + if (priv->desc.bDeviceClass != 0) { + json_builder_set_member_name(json_builder, "DeviceClass"); + json_builder_add_int_value(json_builder, priv->desc.bDeviceClass); + } + if (priv->desc.bDeviceSubClass != 0) { + json_builder_set_member_name(json_builder, "DeviceSubClass"); + json_builder_add_int_value(json_builder, priv->desc.bDeviceSubClass); + } + if (priv->desc.bDeviceProtocol != 0) { + json_builder_set_member_name(json_builder, "DeviceProtocol"); + json_builder_add_int_value(json_builder, priv->desc.bDeviceProtocol); + } + if (priv->desc.iProduct != 0) { + json_builder_set_member_name(json_builder, "Product"); + json_builder_add_int_value(json_builder, priv->desc.iProduct); + } + if (priv->desc.iSerialNumber != 0) { + json_builder_set_member_name(json_builder, "SerialNumber"); + json_builder_add_int_value(json_builder, priv->desc.iSerialNumber); + } + + /* array of BOS descriptors */ + bos_descriptors = g_usb_device_get_bos_descriptors(self, &error_bos); + if (bos_descriptors == NULL) { + g_debug("%s", error_bos->message); + } else if (bos_descriptors->len > 0) { + json_builder_set_member_name(json_builder, "UsbBosDescriptors"); + json_builder_begin_array(json_builder); + for (guint i = 0; i < bos_descriptors->len; i++) { + GUsbBosDescriptor *bos_descriptor = g_ptr_array_index(bos_descriptors, i); + if (!_g_usb_bos_descriptor_save(bos_descriptor, json_builder, error)) + return FALSE; + } + json_builder_end_array(json_builder); + } + + /* array of interfaces */ + interfaces = g_usb_device_get_interfaces(self, &error_interfaces); + if (interfaces == NULL) { + g_debug("%s", error_interfaces->message); + } else if (interfaces->len > 0) { + json_builder_set_member_name(json_builder, "UsbInterfaces"); + json_builder_begin_array(json_builder); + for (guint i = 0; i < interfaces->len; i++) { + GUsbInterface *interface = g_ptr_array_index(interfaces, i); + if (!_g_usb_interface_save(interface, json_builder, error)) + return FALSE; + } + json_builder_end_array(json_builder); + } + + /* success */ + json_builder_end_object(json_builder); + return TRUE; +} + /* not defined in FreeBSD */ #ifndef HAVE_LIBUSB_GET_PARENT static libusb_device * @@ -413,6 +594,10 @@ _g_usb_device_open_internal(GUsbDevice *self, GError **error) GUsbDevicePrivate *priv = GET_PRIVATE(self); gint rc; + /* sanity check */ + if (priv->device == NULL) + return TRUE; + if (priv->handle != NULL) { g_set_error(error, G_USB_DEVICE_ERROR, @@ -484,6 +669,10 @@ g_usb_device_get_custom_index(GUsbDevice *self, guint8 idx = 0x00; struct libusb_config_descriptor *config; + /* sanity check */ + if (priv->device == NULL) + return 0x0; + rc = libusb_get_active_config_descriptor(priv->device, &config); if (!g_usb_device_libusb_error_to_gerror(self, rc, error)) return 0x00; @@ -601,6 +790,15 @@ g_usb_device_get_interfaces(GUsbDevice *self, GError **error) gint rc; struct libusb_config_descriptor *config; + /* sanity check */ + if (priv->device == NULL) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "not supported for emulated device"); + return NULL; + } + rc = libusb_get_active_config_descriptor(priv->device, &config); if (!g_usb_device_libusb_error_to_gerror(self, rc, error)) return NULL; @@ -706,6 +904,15 @@ g_usb_device_get_bos_descriptors(GUsbDevice *self, GError **error) guint8 num_device_caps; struct libusb_bos_descriptor *bos = NULL; + /* sanity check */ + if (priv->device == NULL) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "not supported for emulated device"); + return NULL; + } + rc = libusb_get_bos_descriptor(priv->handle, &bos); if (!g_usb_device_libusb_error_to_gerror(self, rc, error)) return NULL; @@ -1783,6 +1990,10 @@ g_usb_device_get_parent(GUsbDevice *self) GUsbDevicePrivate *priv = GET_PRIVATE(self); libusb_device *parent; + /* sanity check */ + if (priv->device == NULL) + return NULL; + parent = libusb_get_parent(priv->device); if (parent == NULL) return NULL; @@ -1816,6 +2027,8 @@ g_usb_device_get_children(GUsbDevice *self) for (guint i = 0; i < devices->len; i++) { GUsbDevice *device_tmp = g_ptr_array_index(devices, i); GUsbDevicePrivate *priv_tmp = GET_PRIVATE(device_tmp); + if (priv->device == NULL) + continue; if (priv->device == libusb_get_parent(priv_tmp->device)) g_ptr_array_add(children, g_object_ref(device_tmp)); } @@ -1838,6 +2051,9 @@ g_usb_device_get_bus(GUsbDevice *self) { GUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(G_USB_IS_DEVICE(self), 0); + /* sanity check */ + if (priv->device == NULL) + return 0x0; return libusb_get_bus_number(priv->device); } @@ -1856,6 +2072,9 @@ g_usb_device_get_address(GUsbDevice *self) { GUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(G_USB_IS_DEVICE(self), 0); + /* sanity check */ + if (priv->device == NULL) + return 0x0; return libusb_get_device_address(priv->device); } @@ -1874,6 +2093,9 @@ g_usb_device_get_port_number(GUsbDevice *self) { GUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(G_USB_IS_DEVICE(self), 0); + /* sanity check */ + if (priv->device == NULL) + return 0x0; return libusb_get_port_number(priv->device); } @@ -2010,6 +2232,10 @@ g_usb_device_get_configuration_index(GUsbDevice *self) g_return_val_if_fail(G_USB_IS_DEVICE(self), 0); + /* sanity check */ + if (priv->device == NULL) + return 0x0; + rc = libusb_get_active_config_descriptor(priv->device, &config); g_return_val_if_fail(rc == 0, 0); diff --git a/gusb/gusb-device.h b/gusb/gusb-device.h index c19dd3a..39d778c 100644 --- a/gusb/gusb-device.h +++ b/gusb/gusb-device.h @@ -12,6 +12,7 @@ #include #include #include +#include G_BEGIN_DECLS diff --git a/gusb/gusb-endpoint-private.h b/gusb/gusb-endpoint-private.h index 05bf860..88c11f6 100644 --- a/gusb/gusb-endpoint-private.h +++ b/gusb/gusb-endpoint-private.h @@ -1,6 +1,7 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2020 Emmanuel Pacaud + * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ @@ -8,6 +9,7 @@ #pragma once #include +#include #include G_BEGIN_DECLS @@ -15,4 +17,9 @@ G_BEGIN_DECLS GUsbEndpoint * _g_usb_endpoint_new(const struct libusb_endpoint_descriptor *endpoint); +gboolean +_g_usb_endpoint_load(GUsbEndpoint *self, JsonObject *json_object, GError **error); +gboolean +_g_usb_endpoint_save(GUsbEndpoint *self, JsonBuilder *json_builder, GError **error); + G_END_DECLS diff --git a/gusb/gusb-endpoint.c b/gusb/gusb-endpoint.c index fcf86cf..338fb57 100644 --- a/gusb/gusb-endpoint.c +++ b/gusb/gusb-endpoint.c @@ -53,6 +53,101 @@ g_usb_endpoint_init(GUsbEndpoint *self) { } +gboolean +_g_usb_endpoint_load(GUsbEndpoint *self, JsonObject *json_object, GError **error) +{ + const gchar *str; + + g_return_val_if_fail(G_USB_IS_ENDPOINT(self), FALSE); + g_return_val_if_fail(json_object != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + +#if JSON_CHECK_VERSION(1, 6, 0) + /* optional properties */ + self->endpoint_descriptor.bDescriptorType = + json_object_get_int_member_with_default(json_object, "DescriptorType", 0x0); + self->endpoint_descriptor.bEndpointAddress = + json_object_get_int_member_with_default(json_object, "EndpointAddress", 0x0); + self->endpoint_descriptor.bRefresh = + json_object_get_int_member_with_default(json_object, "Refresh", 0x0); + self->endpoint_descriptor.bInterval = + json_object_get_int_member_with_default(json_object, "Interval", 0x0); + self->endpoint_descriptor.bSynchAddress = + json_object_get_int_member_with_default(json_object, "SynchAddress", 0x0); + self->endpoint_descriptor.wMaxPacketSize = + json_object_get_int_member_with_default(json_object, "MaxPacketSize", 0x0); + + /* extra data */ + str = json_object_get_string_member_with_default(json_object, "ExtraData", NULL); + if (str != NULL) { + gsize bufsz = 0; + g_autofree guchar *buf = g_base64_decode(str, &bufsz); + if (self->extra != NULL) + g_bytes_unref(self->extra); + self->extra = 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_endpoint_save(GUsbEndpoint *self, JsonBuilder *json_builder, GError **error) +{ + g_return_val_if_fail(G_USB_IS_ENDPOINT(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); + + /* optional properties */ + if (self->endpoint_descriptor.bDescriptorType != 0) { + json_builder_set_member_name(json_builder, "DescriptorType"); + json_builder_add_int_value(json_builder, self->endpoint_descriptor.bDescriptorType); + } + if (self->endpoint_descriptor.bEndpointAddress != 0) { + json_builder_set_member_name(json_builder, "EndpointAddress"); + json_builder_add_int_value(json_builder, + self->endpoint_descriptor.bEndpointAddress); + } + if (self->endpoint_descriptor.bRefresh != 0) { + json_builder_set_member_name(json_builder, "Refresh"); + json_builder_add_int_value(json_builder, self->endpoint_descriptor.bRefresh); + } + if (self->endpoint_descriptor.bInterval != 0) { + json_builder_set_member_name(json_builder, "Interval"); + json_builder_add_int_value(json_builder, self->endpoint_descriptor.bInterval); + } + if (self->endpoint_descriptor.bSynchAddress != 0) { + json_builder_set_member_name(json_builder, "SynchAddress"); + json_builder_add_int_value(json_builder, self->endpoint_descriptor.bSynchAddress); + } + if (self->endpoint_descriptor.wMaxPacketSize != 0) { + json_builder_set_member_name(json_builder, "MaxPacketSize"); + json_builder_add_int_value(json_builder, self->endpoint_descriptor.wMaxPacketSize); + } + + /* extra data */ + if (self->extra != NULL && g_bytes_get_size(self->extra) > 0) { + g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->extra, NULL), + g_bytes_get_size(self->extra)); + json_builder_set_member_name(json_builder, "ExtraData"); + json_builder_add_string_value(json_builder, str); + } + + /* success */ + json_builder_end_object(json_builder); + return TRUE; +} + /** * _g_usb_endpoint_new: * diff --git a/gusb/gusb-interface-private.h b/gusb/gusb-interface-private.h index 7efd171..dcdfde1 100644 --- a/gusb/gusb-interface-private.h +++ b/gusb/gusb-interface-private.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include G_BEGIN_DECLS @@ -15,4 +16,9 @@ G_BEGIN_DECLS GUsbInterface * _g_usb_interface_new(const struct libusb_interface_descriptor *iface); +gboolean +_g_usb_interface_load(GUsbInterface *self, JsonObject *json_object, GError **error); +gboolean +_g_usb_interface_save(GUsbInterface *self, JsonBuilder *json_builder, GError **error); + G_END_DECLS diff --git a/gusb/gusb-interface.c b/gusb/gusb-interface.c index b08defa..e08409c 100644 --- a/gusb/gusb-interface.c +++ b/gusb/gusb-interface.c @@ -58,6 +58,123 @@ g_usb_interface_init(GUsbInterface *self) { } +gboolean +_g_usb_interface_load(GUsbInterface *self, JsonObject *json_object, GError **error) +{ + const gchar *str; + + g_return_val_if_fail(G_USB_IS_INTERFACE(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->iface.bLength = json_object_get_int_member_with_default(json_object, "Length", 0x0); + self->iface.bDescriptorType = + json_object_get_int_member_with_default(json_object, "DescriptorType", 0x0); + self->iface.bInterfaceNumber = + json_object_get_int_member_with_default(json_object, "InterfaceNumber", 0x0); + self->iface.bAlternateSetting = + json_object_get_int_member_with_default(json_object, "AlternateSetting", 0x0); + self->iface.bInterfaceClass = + json_object_get_int_member_with_default(json_object, "InterfaceClass", 0x0); + self->iface.bInterfaceSubClass = + json_object_get_int_member_with_default(json_object, "InterfaceSubClass", 0x0); + self->iface.bInterfaceProtocol = + json_object_get_int_member_with_default(json_object, "InterfaceProtocol", 0x0); + self->iface.iInterface = + json_object_get_int_member_with_default(json_object, "Interface", 0x0); + + /* extra data */ + str = json_object_get_string_member_with_default(json_object, "ExtraData", NULL); + if (str != NULL) { + gsize bufsz = 0; + g_autofree guchar *buf = g_base64_decode(str, &bufsz); + if (self->extra != NULL) + g_bytes_unref(self->extra); + self->extra = 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_interface_save(GUsbInterface *self, JsonBuilder *json_builder, GError **error) +{ + g_return_val_if_fail(G_USB_IS_INTERFACE(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); + + /* optional properties */ + if (self->iface.bLength != 0) { + json_builder_set_member_name(json_builder, "Length"); + json_builder_add_int_value(json_builder, self->iface.bLength); + } + if (self->iface.bDescriptorType != 0) { + json_builder_set_member_name(json_builder, "DescriptorType"); + json_builder_add_int_value(json_builder, self->iface.bDescriptorType); + } + if (self->iface.bInterfaceNumber != 0) { + json_builder_set_member_name(json_builder, "InterfaceNumber"); + json_builder_add_int_value(json_builder, self->iface.bInterfaceNumber); + } + if (self->iface.bAlternateSetting != 0) { + json_builder_set_member_name(json_builder, "AlternateSetting"); + json_builder_add_int_value(json_builder, self->iface.bAlternateSetting); + } + if (self->iface.bInterfaceClass != 0) { + json_builder_set_member_name(json_builder, "InterfaceClass"); + json_builder_add_int_value(json_builder, self->iface.bInterfaceClass); + } + if (self->iface.bInterfaceSubClass != 0) { + json_builder_set_member_name(json_builder, "InterfaceSubClass"); + json_builder_add_int_value(json_builder, self->iface.bInterfaceSubClass); + } + if (self->iface.bInterfaceProtocol != 0) { + json_builder_set_member_name(json_builder, "InterfaceProtocol"); + json_builder_add_int_value(json_builder, self->iface.bInterfaceProtocol); + } + if (self->iface.iInterface != 0) { + json_builder_set_member_name(json_builder, "Interface"); + json_builder_add_int_value(json_builder, self->iface.iInterface); + } + + /* array of endpoints */ + if (self->endpoints->len > 0) { + json_builder_set_member_name(json_builder, "UsbEndpoints"); + json_builder_begin_array(json_builder); + for (guint i = 0; i < self->endpoints->len; i++) { + GUsbEndpoint *endpoint = g_ptr_array_index(self->endpoints, i); + if (!_g_usb_endpoint_save(endpoint, json_builder, error)) + return FALSE; + } + json_builder_end_array(json_builder); + } + + /* extra data */ + if (self->extra != NULL && g_bytes_get_size(self->extra) > 0) { + g_autofree gchar *str = g_base64_encode(g_bytes_get_data(self->extra, NULL), + g_bytes_get_size(self->extra)); + json_builder_set_member_name(json_builder, "ExtraData"); + json_builder_add_string_value(json_builder, str); + } + + /* success */ + json_builder_end_object(json_builder); + return TRUE; +} + /** * _g_usb_interface_new: * diff --git a/gusb/gusb-self-test.c b/gusb/gusb-self-test.c index e9dd20a..81ca246 100644 --- a/gusb/gusb-self-test.c +++ b/gusb/gusb-self-test.c @@ -401,6 +401,12 @@ gusb_device_munki_func(void) g_assert(ret); } +static void +gusb_device_json_func(void) +{ + // xxxxx +} + static void gusb_device_ch2_func(void) { @@ -467,6 +473,7 @@ main(int argc, char **argv) g_test_add_func("/gusb/device[huey]", gusb_device_huey_func); g_test_add_func("/gusb/device[munki]", gusb_device_munki_func); g_test_add_func("/gusb/device[colorhug2]", gusb_device_ch2_func); + g_test_add_func("/gusb/device[json]", gusb_device_json_func); return g_test_run(); } diff --git a/gusb/libgusb.ver b/gusb/libgusb.ver index 041a3a8..d189ec4 100644 --- a/gusb/libgusb.ver +++ b/gusb/libgusb.ver @@ -172,6 +172,8 @@ LIBGUSB_0.4.0 { g_usb_bos_descriptor_get_capability; g_usb_bos_descriptor_get_extra; g_usb_bos_descriptor_get_type; + g_usb_context_load; + g_usb_context_save; g_usb_device_get_bos_descriptor; g_usb_device_get_bos_descriptors; g_usb_device_invalidate; diff --git a/gusb/meson.build b/gusb/meson.build index 285c776..eeef369 100644 --- a/gusb/meson.build +++ b/gusb/meson.build @@ -69,6 +69,7 @@ gusb = library( dependencies : [ libgio, libusb, + libjsonglib, ], c_args : [ cargs, @@ -93,7 +94,7 @@ gusb_dep = declare_dependency( pkgg = import('pkgconfig') pkgg.generate(gusb, - requires : [ 'gio-2.0', 'gobject-2.0', 'libusb-1.0' ], + requires : [ 'gio-2.0', 'gobject-2.0', 'libusb-1.0', 'json-glib-1.0' ], subdirs : 'gusb-1', version : meson.project_version(), name : 'gusb', @@ -141,6 +142,7 @@ libgusb_girtarget = gnome.generate_gir(gusb, dependencies : [ libgio, libusb, + libjsonglib, ], includes : [ 'Gio-2.0', @@ -213,6 +215,7 @@ if get_option('tests') dependencies : [ libgio, libusb, + libjsonglib, ], c_args : [ cargs, diff --git a/meson.build b/meson.build index 1faca6a..2839e8c 100644 --- a/meson.build +++ b/meson.build @@ -115,10 +115,12 @@ endif if cc.has_header_symbol('libusb.h', 'libusb_get_port_number', dependencies: libusb) conf.set('HAVE_LIBUSB_GET_PORT_NUMBER', '1') endif +libjsonglib = dependency('json-glib-1.0', version: '>= 1.1.1') gusb_deps = [ libgio, libusb, + libjsonglib, ] gnome = import('gnome') diff --git a/tools/gusb-main.c b/tools/gusb-main.c index 8cfd4d1..406a945 100644 --- a/tools/gusb-main.c +++ b/tools/gusb-main.c @@ -349,6 +349,79 @@ gusb_cmd_replug(GUsbCmdPrivate *priv, gchar **values, GError **error) return device_new != NULL; } +static gboolean +gusb_cmd_load(GUsbCmdPrivate *priv, gchar **values, GError **error) +{ + JsonObject *json_obj; + JsonNode *json_node; + g_autoptr(JsonParser) parser = json_parser_new(); + + /* check args */ + if (g_strv_length(values) != 1) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "no filename specified"); + return FALSE; + } + + /* parse */ + if (!json_parser_load_from_file(parser, values[0], error)) + return FALSE; + + /* sanity check */ + json_node = json_parser_get_root(parser); + if (!JSON_NODE_HOLDS_OBJECT(json_node)) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "not a JSON object"); + return FALSE; + } + + /* not supplied */ + json_obj = json_node_get_object(json_node); + if (!g_usb_context_load(priv->usb_ctx, json_obj, error)) + return FALSE; + + /* success */ + return gusb_cmd_show(priv, NULL, error); +} + +static gboolean +gusb_cmd_save(GUsbCmdPrivate *priv, gchar **values, GError **error) +{ + g_autoptr(JsonBuilder) json_builder = json_builder_new(); + g_autofree gchar *data = NULL; + g_autoptr(JsonGenerator) json_generator = NULL; + g_autoptr(JsonNode) json_root = NULL; + + if (!g_usb_context_save(priv->usb_ctx, json_builder, error)) + return FALSE; + + /* 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); + if (data == NULL) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Failed to convert to JSON string"); + return FALSE; + } + + /* save to file */ + if (g_strv_length(values) == 1) + return g_file_set_contents(values[0], data, -1, error); + + /* just print */ + g_print("%s\n", data); + return TRUE; +} + static gboolean gusb_cmd_run(GUsbCmdPrivate *priv, const gchar *command, gchar **values, GError **error) { @@ -437,6 +510,8 @@ main(int argc, char *argv[]) gusb_cmd_add(priv->cmd_array, "show", "Show currently connected devices", gusb_cmd_show); gusb_cmd_add(priv->cmd_array, "watch", "Watch devices as they come and go", gusb_cmd_watch); gusb_cmd_add(priv->cmd_array, "replug", "Watch a device as it reconnects", gusb_cmd_replug); + gusb_cmd_add(priv->cmd_array, "load", "Load a set of devices from JSON", gusb_cmd_load); + gusb_cmd_add(priv->cmd_array, "save", "Save a set of devices to JSON", gusb_cmd_save); /* sort by command name */ g_ptr_array_sort(priv->cmd_array, (GCompareFunc)gusb_sort_command_name_cb); diff --git a/tools/meson.build b/tools/meson.build index 3bd2c6c..c0e47db 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -12,6 +12,7 @@ executable( dependencies : [ libgio, libusb, + libjsonglib, ], link_with : gusb, c_args : [ -- cgit v1.2.1