summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2022-08-14 17:30:58 +0100
committerRichard Hughes <richard@hughsie.com>2022-09-05 16:03:23 +0100
commit88e7c32dea37a8db19256745a0b334ec2b1b80a6 (patch)
tree87ff3286dbd41bcb5c71092a8b01a449306f1a7b
parent28c2e67e8f91e357c264f97c4cc4201e45fcb880 (diff)
downloadgusb-88e7c32dea37a8db19256745a0b334ec2b1b80a6.tar.gz
Serialize the GUsbDevice to and from JSON format
This allows us to write tests that mock up entire devices.
-rw-r--r--.github/workflows/main.yml2
-rw-r--r--contrib/ci/Dockerfile-debian2
-rw-r--r--contrib/ci/Dockerfile-fedora1
-rw-r--r--contrib/libgusb.spec.in1
-rw-r--r--gusb/gusb-bos-descriptor-private.h6
-rw-r--r--gusb/gusb-bos-descriptor.c64
-rw-r--r--gusb/gusb-context.c86
-rw-r--r--gusb/gusb-context.h5
-rw-r--r--gusb/gusb-device-private.h4
-rw-r--r--gusb/gusb-device.c246
-rw-r--r--gusb/gusb-device.h1
-rw-r--r--gusb/gusb-endpoint-private.h7
-rw-r--r--gusb/gusb-endpoint.c95
-rw-r--r--gusb/gusb-interface-private.h6
-rw-r--r--gusb/gusb-interface.c117
-rw-r--r--gusb/gusb-self-test.c7
-rw-r--r--gusb/libgusb.ver2
-rw-r--r--gusb/meson.build5
-rw-r--r--meson.build2
-rw-r--r--tools/gusb-main.c75
-rw-r--r--tools/meson.build1
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 <gusb/gusb-bos-descriptor.h>
+#include <json-glib/json-glib.h>
#include <libusb.h>
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 <gusb/gusb-bos-descriptor.h>
#include <gusb/gusb-interface.h>
#include <gusb/gusb-util.h>
+#include <json-glib/json-glib.h>
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 <emmanuel@gnome.org>
+ * Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
@@ -8,6 +9,7 @@
#pragma once
#include <gusb/gusb-endpoint.h>
+#include <json-glib/json-glib.h>
#include <libusb.h>
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 <gusb/gusb-interface.h>
+#include <json-glib/json-glib.h>
#include <libusb.h>
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
@@ -402,6 +402,12 @@ gusb_device_munki_func(void)
}
static void
+gusb_device_json_func(void)
+{
+ // xxxxx
+}
+
+static void
gusb_device_ch2_func(void)
{
gboolean ret;
@@ -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
@@ -350,6 +350,79 @@ gusb_cmd_replug(GUsbCmdPrivate *priv, gchar **values, GError **error)
}
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)
{
g_autoptr(GString) string = g_string_new(NULL);
@@ -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 : [