Coverage Report

Created: 2026-06-15 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-usb-device-ds20.c
Line
Count
Source
1
/*
2
 * Copyright 2022 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuUsbDeviceDs20"
8
9
#include "config.h"
10
11
#include "fu-dump.h"
12
#include "fu-input-stream.h"
13
#include "fu-usb-device-ds20-struct.h"
14
#include "fu-usb-device-ds20.h"
15
16
/**
17
 * FuUsbDeviceDs20:
18
 *
19
 * A USB DS20 BOS descriptor.
20
 *
21
 * See also: [class@FuUsbDevice]
22
 */
23
24
typedef struct {
25
  guint32 version_lowest;
26
} FuUsbDeviceDs20Private;
27
28
0
G_DEFINE_TYPE_WITH_PRIVATE(FuUsbDeviceDs20, fu_usb_device_ds20, FU_TYPE_FIRMWARE)
29
0
#define GET_PRIVATE(o) (fu_usb_device_ds20_get_instance_private(o))
30
31
typedef struct {
32
  guint32 platform_ver;
33
  guint16 total_length;
34
  guint8 vendor_code;
35
  guint8 alt_code;
36
} FuUsbDeviceDs20Item;
37
38
/**
39
 * fu_usb_device_ds20_set_version_lowest:
40
 * @self: a #FuUsbDeviceDs20
41
 * @version_lowest: version number
42
 *
43
 * Sets the lowest possible `platform_ver` for a DS20 descriptor.
44
 *
45
 * Since: 1.8.5
46
 **/
47
void
48
fu_usb_device_ds20_set_version_lowest(FuUsbDeviceDs20 *self, guint32 version_lowest)
49
0
{
50
0
  FuUsbDeviceDs20Private *priv = GET_PRIVATE(self);
51
0
  g_return_if_fail(FU_IS_USB_DEVICE_DS20(self));
52
0
  priv->version_lowest = version_lowest;
53
0
}
54
55
/**
56
 * fu_usb_device_ds20_apply_to_device:
57
 * @self: a #FuUsbDeviceDs20
58
 * @device: a #FuUsbDevice
59
 * @error: (nullable): optional return location for an error
60
 *
61
 * Sets the DS20 descriptor onto @device.
62
 *
63
 * Returns: %TRUE for success
64
 *
65
 * Since: 1.8.5
66
 **/
67
gboolean
68
fu_usb_device_ds20_apply_to_device(FuUsbDeviceDs20 *self, FuUsbDevice *device, GError **error)
69
0
{
70
0
  FuUsbDeviceDs20Class *klass = FU_USB_DEVICE_DS20_GET_CLASS(self);
71
0
  gsize actual_length = 0;
72
0
  gsize total_length = fu_firmware_get_size(FU_FIRMWARE(self));
73
0
  guint8 vendor_code = fu_firmware_get_idx(FU_FIRMWARE(self));
74
0
  g_autofree guint8 *buf = g_malloc0(total_length);
75
0
  g_autoptr(GInputStream) stream = NULL;
76
77
0
  g_return_val_if_fail(FU_IS_USB_DEVICE_DS20(self), FALSE);
78
0
  g_return_val_if_fail(FU_IS_USB_DEVICE(device), FALSE);
79
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
80
81
0
  if (!fu_usb_device_control_transfer(device,
82
0
              FU_USB_DIRECTION_DEVICE_TO_HOST,
83
0
              FU_USB_REQUEST_TYPE_VENDOR,
84
0
              FU_USB_RECIPIENT_DEVICE,
85
0
              vendor_code, /* bRequest */
86
0
              0x0,   /* wValue */
87
0
              0x07,  /* wIndex */
88
0
              buf,
89
0
              total_length,
90
0
              &actual_length,
91
0
              500,
92
0
              NULL, /* cancellable */
93
0
              error)) {
94
0
    g_prefix_error(error, "requested vendor code 0x%02x: ", vendor_code);
95
0
    return FALSE;
96
0
  }
97
0
  if (total_length != actual_length) {
98
0
    g_set_error(error,
99
0
          FWUPD_ERROR,
100
0
          FWUPD_ERROR_INVALID_DATA,
101
0
          "expected 0x%x bytes from vendor code 0x%02x, but got 0x%x",
102
0
          (guint)total_length,
103
0
          vendor_code,
104
0
          (guint)actual_length);
105
0
    return FALSE;
106
0
  }
107
108
  /* debug */
109
0
  fu_dump_raw(G_LOG_DOMAIN, "PlatformCapabilityOs20", buf, actual_length);
110
111
  /* FuUsbDeviceDs20->parse */
112
0
  stream = g_memory_input_stream_new_from_data(buf, actual_length, NULL);
113
0
  return klass->parse(self, stream, device, error);
114
0
}
115
116
static gboolean
117
fu_usb_device_ds20_validate(FuFirmware *firmware,
118
          GInputStream *stream,
119
          gsize offset,
120
          GError **error)
121
0
{
122
0
  g_autoptr(FuStructDs20) st = NULL;
123
0
  g_autofree gchar *guid_str = NULL;
124
125
  /* matches the correct UUID */
126
0
  st = fu_struct_ds20_parse_stream(stream, offset, error);
127
0
  if (st == NULL)
128
0
    return FALSE;
129
0
  guid_str = fwupd_guid_to_string(fu_struct_ds20_get_guid(st), FWUPD_GUID_FLAG_MIXED_ENDIAN);
130
0
  if (g_strcmp0(guid_str, fu_firmware_get_id(firmware)) != 0) {
131
0
    g_set_error(error,
132
0
          FWUPD_ERROR,
133
0
          FWUPD_ERROR_INVALID_FILE,
134
0
          "invalid UUID for DS20, expected %s",
135
0
          fu_firmware_get_id(firmware));
136
0
    return FALSE;
137
0
  }
138
139
  /* success */
140
0
  return TRUE;
141
0
}
142
143
static gint
144
fu_usb_device_ds20_sort_by_platform_ver_cb(gconstpointer a, gconstpointer b)
145
0
{
146
0
  FuUsbDeviceDs20Item *ds1 = *((FuUsbDeviceDs20Item **)a);
147
0
  FuUsbDeviceDs20Item *ds2 = *((FuUsbDeviceDs20Item **)b);
148
0
  if (ds1->platform_ver < ds2->platform_ver)
149
0
    return -1;
150
0
  if (ds1->platform_ver > ds2->platform_ver)
151
0
    return 1;
152
0
  return 0;
153
0
}
154
155
static gboolean
156
fu_usb_device_ds20_parse(FuFirmware *firmware,
157
       GInputStream *stream,
158
       FuFirmwareParseFlags flags,
159
       GError **error)
160
0
{
161
0
  FuUsbDeviceDs20 *self = FU_USB_DEVICE_DS20(firmware);
162
0
  FuUsbDeviceDs20Private *priv = GET_PRIVATE(self);
163
0
  gsize streamsz = 0;
164
0
  guint version_lowest = fu_firmware_get_version_raw(firmware);
165
0
  g_autoptr(GPtrArray) dsinfos = g_ptr_array_new_with_free_func(g_free);
166
167
0
  if (!fu_input_stream_size(stream, &streamsz, error))
168
0
    return FALSE;
169
0
  for (gsize off = 0; off < streamsz; off += FU_STRUCT_DS20_SIZE) {
170
0
    g_autofree FuUsbDeviceDs20Item *dsinfo = g_new0(FuUsbDeviceDs20Item, 1);
171
0
    g_autoptr(FuStructDs20) st = NULL;
172
173
    /* parse */
174
0
    st = fu_struct_ds20_parse_stream(stream, off, error);
175
0
    if (st == NULL)
176
0
      return FALSE;
177
0
    dsinfo->platform_ver = fu_struct_ds20_get_platform_ver(st);
178
0
    dsinfo->total_length = fu_struct_ds20_get_total_length(st);
179
0
    dsinfo->vendor_code = fu_struct_ds20_get_vendor_code(st);
180
0
    dsinfo->alt_code = fu_struct_ds20_get_alt_code(st);
181
0
    g_debug("PlatformVersion=0x%08x, TotalLength=0x%04x, VendorCode=0x%02x, "
182
0
      "AltCode=0x%02x",
183
0
      dsinfo->platform_ver,
184
0
      dsinfo->total_length,
185
0
      dsinfo->vendor_code,
186
0
      dsinfo->alt_code);
187
0
    g_ptr_array_add(dsinfos, g_steal_pointer(&dsinfo));
188
0
  }
189
190
  /* sort by platform_ver, highest first */
191
0
  g_ptr_array_sort(dsinfos, fu_usb_device_ds20_sort_by_platform_ver_cb);
192
193
  /* find the newest info that's not newer than the lowest version */
194
0
  for (guint i = 0; i < dsinfos->len; i++) {
195
0
    FuUsbDeviceDs20Item *dsinfo = g_ptr_array_index(dsinfos, i);
196
197
    /* not valid */
198
0
    if (dsinfo->platform_ver == 0x0) {
199
0
      g_set_error(error,
200
0
            FWUPD_ERROR,
201
0
            FWUPD_ERROR_NOT_SUPPORTED,
202
0
            "invalid platform version 0x%08x",
203
0
            dsinfo->platform_ver);
204
0
      return FALSE;
205
0
    }
206
0
    if (dsinfo->platform_ver < priv->version_lowest) {
207
0
      g_set_error(error,
208
0
            FWUPD_ERROR,
209
0
            FWUPD_ERROR_NOT_SUPPORTED,
210
0
            "invalid platform version 0x%08x, expected >= 0x%08x",
211
0
            dsinfo->platform_ver,
212
0
            priv->version_lowest);
213
0
      return FALSE;
214
0
    }
215
216
    /* dwVersion is effectively the minimum version */
217
0
    if (dsinfo->platform_ver <= version_lowest) {
218
0
      fu_firmware_set_size(firmware, dsinfo->total_length);
219
0
      fu_firmware_set_idx(firmware, dsinfo->vendor_code);
220
0
      return TRUE;
221
0
    }
222
0
  }
223
224
  /* failed */
225
0
  g_set_error_literal(error,
226
0
          FWUPD_ERROR,
227
0
          FWUPD_ERROR_NOT_SUPPORTED,
228
0
          "no supported platform version");
229
0
  return FALSE;
230
0
}
231
232
static GByteArray *
233
fu_usb_device_ds20_write(FuFirmware *firmware, GError **error)
234
0
{
235
0
  g_autoptr(FuStructDs20) st = fu_struct_ds20_new();
236
0
  fwupd_guid_t guid = {0x0};
237
238
  /* pack */
239
0
  if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
240
0
            &guid,
241
0
            FWUPD_GUID_FLAG_MIXED_ENDIAN,
242
0
            error))
243
0
    return NULL;
244
0
  fu_struct_ds20_set_guid(st, &guid);
245
0
  fu_struct_ds20_set_platform_ver(st, fu_firmware_get_version_raw(firmware));
246
0
  fu_struct_ds20_set_total_length(st, fu_firmware_get_size(firmware));
247
0
  fu_struct_ds20_set_vendor_code(st, fu_firmware_get_idx(firmware));
248
0
  return g_steal_pointer(&st->buf);
249
0
}
250
251
static void
252
fu_usb_device_ds20_init(FuUsbDeviceDs20 *self)
253
0
{
254
0
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
255
0
  fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB);
256
0
}
257
258
static void
259
fu_usb_device_ds20_class_init(FuUsbDeviceDs20Class *klass)
260
0
{
261
0
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
262
0
  firmware_class->validate = fu_usb_device_ds20_validate;
263
0
  firmware_class->parse = fu_usb_device_ds20_parse;
264
0
  firmware_class->write = fu_usb_device_ds20_write;
265
0
}