Coverage Report

Created: 2026-01-09 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/plugins/ccgx-dmc/fu-ccgx-dmc-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2020 Cypress Semiconductor Corporation.
3
 * Copyright 2020 Richard Hughes <richard@hughsie.com>
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 */
7
8
#include "config.h"
9
10
#include <string.h>
11
12
#include "fu-ccgx-dmc-firmware.h"
13
#include "fu-ccgx-dmc-struct.h"
14
15
struct _FuCcgxDmcFirmware {
16
  FuFirmware parent_instance;
17
  GPtrArray *image_records;
18
  GBytes *fwct_blob;
19
  GBytes *custom_meta_blob;
20
  guint32 row_data_offset_start;
21
  guint32 fw_data_size;
22
};
23
24
4.20k
G_DEFINE_TYPE(FuCcgxDmcFirmware, fu_ccgx_dmc_firmware, FU_TYPE_FIRMWARE)
25
4.20k
26
4.20k
#define DMC_FWCT_MAX_SIZE     2048
27
464
#define DMC_HASH_SIZE       32
28
957
#define DMC_CUSTOM_META_LENGTH_FIELD_SIZE 2
29
30
static void
31
fu_ccgx_dmc_firmware_record_free(FuCcgxDmcFirmwareRecord *rcd)
32
2.48k
{
33
2.48k
  if (rcd->seg_records != NULL)
34
1.88k
    g_ptr_array_unref(rcd->seg_records);
35
2.48k
  g_free(rcd);
36
2.48k
}
37
38
static void
39
fu_ccgx_dmc_firmware_segment_record_free(FuCcgxDmcFirmwareSegmentRecord *rcd)
40
23.2k
{
41
23.2k
  if (rcd->data_records != NULL)
42
23.0k
    g_ptr_array_unref(rcd->data_records);
43
23.2k
  g_free(rcd);
44
23.2k
}
45
46
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxDmcFirmwareRecord, fu_ccgx_dmc_firmware_record_free)
47
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxDmcFirmwareSegmentRecord,
48
            fu_ccgx_dmc_firmware_segment_record_free)
49
50
GPtrArray *
51
fu_ccgx_dmc_firmware_get_image_records(FuCcgxDmcFirmware *self)
52
0
{
53
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL);
54
0
  return self->image_records;
55
0
}
56
57
GBytes *
58
fu_ccgx_dmc_firmware_get_fwct_record(FuCcgxDmcFirmware *self)
59
0
{
60
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL);
61
0
  return self->fwct_blob;
62
0
}
63
64
GBytes *
65
fu_ccgx_dmc_firmware_get_custom_meta_record(FuCcgxDmcFirmware *self)
66
0
{
67
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL);
68
0
  return self->custom_meta_blob;
69
0
}
70
71
guint32
72
fu_ccgx_dmc_firmware_get_fw_data_size(FuCcgxDmcFirmware *self)
73
0
{
74
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), 0);
75
0
  return self->fw_data_size;
76
0
}
77
78
static void
79
fu_ccgx_dmc_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
80
0
{
81
0
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
82
0
  if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) {
83
0
    fu_xmlb_builder_insert_kx(bn, "fw_data_size", self->fw_data_size);
84
0
    fu_xmlb_builder_insert_kx(bn, "image_records", self->image_records->len);
85
0
  }
86
0
}
87
88
static gboolean
89
fu_ccgx_dmc_firmware_parse_segment(FuCcgxDmcFirmware *self,
90
           GInputStream *stream,
91
           FuCcgxDmcFirmwareRecord *img_rcd,
92
           gsize *seg_off,
93
           FuFirmwareParseFlags flags,
94
           GError **error)
95
1.88k
{
96
1.88k
  gsize row_off;
97
1.88k
  g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256);
98
99
  /* set row data offset in current image */
100
1.88k
  row_off = self->row_data_offset_start + img_rcd->img_offset;
101
102
  /* parse segment in image */
103
1.88k
  img_rcd->seg_records =
104
1.88k
      g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_segment_record_free);
105
24.6k
  for (guint32 i = 0; i < img_rcd->num_img_segments; i++) {
106
23.2k
    guint16 row_size_bytes = 0;
107
23.2k
    g_autoptr(FuCcgxDmcFirmwareSegmentRecord) seg_rcd = NULL;
108
23.2k
    g_autoptr(FuStructCcgxDmcFwctSegmentationInfo) st_info = NULL;
109
110
    /* read segment info  */
111
23.2k
    seg_rcd = g_new0(FuCcgxDmcFirmwareSegmentRecord, 1);
112
23.2k
    st_info =
113
23.2k
        fu_struct_ccgx_dmc_fwct_segmentation_info_parse_stream(stream, *seg_off, error);
114
23.2k
    if (st_info == NULL)
115
162
      return FALSE;
116
23.0k
    seg_rcd->start_row =
117
23.0k
        fu_struct_ccgx_dmc_fwct_segmentation_info_get_start_row(st_info);
118
23.0k
    seg_rcd->num_rows = fu_struct_ccgx_dmc_fwct_segmentation_info_get_num_rows(st_info);
119
120
    /* calculate actual row size */
121
23.0k
    row_size_bytes = img_rcd->row_size * 64;
122
123
    /* create data record array in segment record */
124
23.0k
    seg_rcd->data_records =
125
23.0k
        g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref);
126
127
    /* read row data in segment */
128
593k
    for (int row = 0; row < seg_rcd->num_rows; row++) {
129
571k
      g_autoptr(GBytes) data_rcd = NULL;
130
131
571k
      data_rcd = fu_input_stream_read_bytes(stream,
132
571k
                    row_off,
133
571k
                    row_size_bytes,
134
571k
                    NULL,
135
571k
                    error);
136
571k
      if (data_rcd == NULL)
137
267
        return FALSE;
138
139
      /* update hash */
140
570k
      g_checksum_update(csum,
141
570k
            (guchar *)g_bytes_get_data(data_rcd, NULL),
142
570k
            g_bytes_get_size(data_rcd));
143
144
      /* add row data to data record */
145
570k
      g_ptr_array_add(seg_rcd->data_records, g_steal_pointer(&data_rcd));
146
147
      /* increment row data offset */
148
570k
      row_off += row_size_bytes;
149
570k
    }
150
151
    /* add segment record to segment array */
152
22.7k
    g_ptr_array_add(img_rcd->seg_records, g_steal_pointer(&seg_rcd));
153
154
    /* increment segment info offset */
155
22.7k
    *seg_off += st_info->buf->len;
156
22.7k
  }
157
158
  /* check checksum */
159
1.45k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
160
464
    guint8 csumbuf[DMC_HASH_SIZE] = {0x0};
161
464
    gsize csumbufsz = sizeof(csumbuf);
162
464
    g_checksum_get_digest(csum, csumbuf, &csumbufsz);
163
464
    if (memcmp(csumbuf, img_rcd->img_digest, DMC_HASH_SIZE) != 0) {
164
192
      g_set_error_literal(error,
165
192
              FWUPD_ERROR,
166
192
              FWUPD_ERROR_NOT_SUPPORTED,
167
192
              "invalid hash");
168
192
      return FALSE;
169
192
    }
170
464
  }
171
172
  /* success */
173
1.26k
  return TRUE;
174
1.45k
}
175
176
static gboolean
177
fu_ccgx_dmc_firmware_parse_image(FuCcgxDmcFirmware *self,
178
         guint8 image_count,
179
         GInputStream *stream,
180
         FuFirmwareParseFlags flags,
181
         GError **error)
182
957
{
183
957
  gsize img_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE;
184
957
  gsize seg_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
185
957
      image_count * FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE;
186
187
  /* set initial segment info offset */
188
2.66k
  for (guint32 i = 0; i < image_count; i++) {
189
2.48k
    g_autoptr(FuCcgxDmcFirmwareRecord) img_rcd = NULL;
190
2.48k
    g_autoptr(FuStructCcgxDmcFwctImageInfo) st_img = NULL;
191
192
    /* read image info */
193
2.48k
    img_rcd = g_new0(FuCcgxDmcFirmwareRecord, 1);
194
2.48k
    st_img = fu_struct_ccgx_dmc_fwct_image_info_parse_stream(stream, img_off, error);
195
2.48k
    if (st_img == NULL)
196
139
      return FALSE;
197
2.34k
    img_rcd->row_size = fu_struct_ccgx_dmc_fwct_image_info_get_row_size(st_img);
198
2.34k
    if (img_rcd->row_size == 0) {
199
19
      g_set_error(error,
200
19
            FWUPD_ERROR,
201
19
            FWUPD_ERROR_NOT_SUPPORTED,
202
19
            "invalid row size 0x%x",
203
19
            img_rcd->row_size);
204
19
      return FALSE;
205
19
    }
206
2.32k
    img_rcd->img_offset = fu_struct_ccgx_dmc_fwct_image_info_get_img_offset(st_img);
207
2.32k
    img_rcd->num_img_segments =
208
2.32k
        fu_struct_ccgx_dmc_fwct_image_info_get_num_img_segments(st_img);
209
210
    /* segments are optional */
211
2.32k
    if (img_rcd->num_img_segments > 0) {
212
1.88k
      gsize img_digestsz = 0;
213
1.88k
      const guint8 *img_digest;
214
215
1.88k
      img_digest =
216
1.88k
          fu_struct_ccgx_dmc_fwct_image_info_get_img_digest(st_img,
217
1.88k
                        &img_digestsz);
218
1.88k
      if (!fu_memcpy_safe((guint8 *)&img_rcd->img_digest,
219
1.88k
              sizeof(img_rcd->img_digest),
220
1.88k
              0x0, /* dst */
221
1.88k
              img_digest,
222
1.88k
              img_digestsz,
223
1.88k
              0, /* src */
224
1.88k
              img_digestsz,
225
1.88k
              error))
226
0
        return FALSE;
227
228
      /* parse segment */
229
1.88k
      if (!fu_ccgx_dmc_firmware_parse_segment(self,
230
1.88k
                stream,
231
1.88k
                img_rcd,
232
1.88k
                &seg_off,
233
1.88k
                flags,
234
1.88k
                error))
235
621
        return FALSE;
236
237
      /* add image record to image record array */
238
1.26k
      g_ptr_array_add(self->image_records, g_steal_pointer(&img_rcd));
239
1.26k
    }
240
241
    /* increment image offset */
242
1.70k
    img_off += FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE;
243
1.70k
  }
244
245
178
  return TRUE;
246
957
}
247
248
static gboolean
249
fu_ccgx_dmc_firmware_validate(FuFirmware *firmware,
250
            GInputStream *stream,
251
            gsize offset,
252
            GError **error)
253
293k
{
254
293k
  return fu_struct_ccgx_dmc_fwct_info_validate_stream(stream, offset, error);
255
293k
}
256
257
static gboolean
258
fu_ccgx_dmc_firmware_parse(FuFirmware *firmware,
259
         GInputStream *stream,
260
         FuFirmwareParseFlags flags,
261
         GError **error)
262
1.07k
{
263
1.07k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
264
1.07k
  gsize streamsz = 0;
265
1.07k
  guint16 hdr_size = 0;
266
1.07k
  guint16 mdbufsz = 0;
267
1.07k
  guint32 hdr_composite_version = 0;
268
1.07k
  guint8 hdr_image_count = 0;
269
1.07k
  g_autoptr(FuStructCcgxDmcFwctInfo) st_hdr = NULL;
270
271
  /* parse */
272
1.07k
  st_hdr = fu_struct_ccgx_dmc_fwct_info_parse_stream(stream, 0x0, error);
273
1.07k
  if (st_hdr == NULL)
274
0
    return FALSE;
275
276
  /* check fwct size */
277
1.07k
  hdr_size = fu_struct_ccgx_dmc_fwct_info_get_size(st_hdr);
278
1.07k
  if (hdr_size > DMC_FWCT_MAX_SIZE || hdr_size == 0) {
279
21
    g_set_error(error,
280
21
          FWUPD_ERROR,
281
21
          FWUPD_ERROR_NOT_SUPPORTED,
282
21
          "invalid dmc fwct size, expected <= 0x%x, got 0x%x",
283
21
          (guint)DMC_FWCT_MAX_SIZE,
284
21
          (guint)hdr_size);
285
21
    return FALSE;
286
21
  }
287
288
  /* set version */
289
1.05k
  hdr_composite_version = fu_struct_ccgx_dmc_fwct_info_get_composite_version(st_hdr);
290
1.05k
  if (hdr_composite_version != 0)
291
998
    fu_firmware_set_version_raw(firmware, hdr_composite_version);
292
293
  /* read fwct data */
294
1.05k
  self->fwct_blob = fu_input_stream_read_bytes(stream, 0x0, hdr_size, NULL, error);
295
1.05k
  if (self->fwct_blob == NULL)
296
0
    return FALSE;
297
298
  /* create custom meta binary */
299
1.05k
  if (!fu_input_stream_read_u16(stream, hdr_size, &mdbufsz, G_LITTLE_ENDIAN, error)) {
300
81
    g_prefix_error_literal(error, "failed to read metadata size: ");
301
81
    return FALSE;
302
81
  }
303
971
  if (mdbufsz > 0) {
304
608
    self->custom_meta_blob =
305
608
        fu_input_stream_read_bytes(stream, hdr_size + 2, mdbufsz, NULL, error);
306
608
    if (self->custom_meta_blob == NULL)
307
14
      return FALSE;
308
608
  }
309
310
  /* set row data start offset */
311
957
  self->row_data_offset_start = hdr_size + DMC_CUSTOM_META_LENGTH_FIELD_SIZE + mdbufsz;
312
957
  if (!fu_input_stream_size(stream, &streamsz, error))
313
0
    return FALSE;
314
957
  self->fw_data_size = streamsz - self->row_data_offset_start;
315
316
  /* parse image */
317
957
  hdr_image_count = fu_struct_ccgx_dmc_fwct_info_get_image_count(st_hdr);
318
957
  if (!fu_ccgx_dmc_firmware_parse_image(self, hdr_image_count, stream, flags, error))
319
779
    return FALSE;
320
321
  /* success */
322
178
  return TRUE;
323
957
}
324
325
static GByteArray *
326
fu_ccgx_dmc_firmware_write(FuFirmware *firmware, GError **error)
327
178
{
328
178
  g_autoptr(GByteArray) buf = g_byte_array_new();
329
178
  g_autoptr(FuStructCcgxDmcFwctInfo) st_hdr = fu_struct_ccgx_dmc_fwct_info_new();
330
178
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
331
332
  /* add header */
333
178
  fu_struct_ccgx_dmc_fwct_info_set_size(
334
178
      st_hdr,
335
178
      FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
336
178
    (images->len * (FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE +
337
178
        FU_STRUCT_CCGX_DMC_FWCT_SEGMENTATION_INFO_SIZE)));
338
178
  fu_struct_ccgx_dmc_fwct_info_set_version(st_hdr, 0x2);
339
178
  fu_struct_ccgx_dmc_fwct_info_set_custom_meta_type(st_hdr, 0x3);
340
178
  fu_struct_ccgx_dmc_fwct_info_set_cdtt_version(st_hdr, 0x1);
341
178
  fu_struct_ccgx_dmc_fwct_info_set_device_id(st_hdr, 0x1);
342
178
  fu_struct_ccgx_dmc_fwct_info_set_composite_version(st_hdr,
343
178
                 fu_firmware_get_version_raw(firmware));
344
178
  fu_struct_ccgx_dmc_fwct_info_set_image_count(st_hdr, images->len);
345
178
  g_byte_array_append(buf, st_hdr->buf->data, st_hdr->buf->len);
346
347
  /* add image headers */
348
178
  for (guint i = 0; i < images->len; i++) {
349
0
    g_autoptr(FuStructCcgxDmcFwctImageInfo) st_img =
350
0
        fu_struct_ccgx_dmc_fwct_image_info_new();
351
0
    fu_struct_ccgx_dmc_fwct_image_info_set_device_type(st_img, 0x2);
352
0
    fu_struct_ccgx_dmc_fwct_image_info_set_img_type(st_img, 0x1);
353
0
    fu_struct_ccgx_dmc_fwct_image_info_set_row_size(st_img, 0x1);
354
0
    fu_struct_ccgx_dmc_fwct_image_info_set_fw_version(st_img, 0x330006d2);
355
0
    fu_struct_ccgx_dmc_fwct_image_info_set_app_version(st_img, 0x14136161);
356
0
    fu_struct_ccgx_dmc_fwct_image_info_set_num_img_segments(st_img, 0x1);
357
0
    fu_byte_array_append_array(buf, st_img->buf);
358
0
  }
359
360
  /* add segments */
361
178
  for (guint i = 0; i < images->len; i++) {
362
0
    FuFirmware *img = g_ptr_array_index(images, i);
363
0
    g_autoptr(FuStructCcgxDmcFwctSegmentationInfo) st_info =
364
0
        fu_struct_ccgx_dmc_fwct_segmentation_info_new();
365
0
    g_autoptr(FuChunkArray) chunks = NULL;
366
0
    g_autoptr(GBytes) img_bytes = fu_firmware_get_bytes(img, error);
367
0
    if (img_bytes == NULL)
368
0
      return NULL;
369
0
    chunks = fu_chunk_array_new_from_bytes(img_bytes,
370
0
                   FU_CHUNK_ADDR_OFFSET_NONE,
371
0
                   FU_CHUNK_PAGESZ_NONE,
372
0
                   64);
373
0
    fu_struct_ccgx_dmc_fwct_segmentation_info_set_num_rows(
374
0
        st_info,
375
0
        MAX(fu_chunk_array_length(chunks), 1));
376
0
    fu_byte_array_append_array(buf, st_info->buf);
377
0
  }
378
379
  /* metadata */
380
178
  fu_byte_array_append_uint16(buf, 0x1, G_LITTLE_ENDIAN);
381
178
  fu_byte_array_append_uint8(buf, 0xff);
382
383
  /* add image headers */
384
178
  for (guint i = 0; i < images->len; i++) {
385
0
    FuFirmware *img = g_ptr_array_index(images, i);
386
0
    gsize csumbufsz = DMC_HASH_SIZE;
387
0
    gsize img_offset = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
388
0
           (i * FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE);
389
0
    guint8 csumbuf[DMC_HASH_SIZE] = {0x0};
390
0
    g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256);
391
0
    g_autoptr(GBytes) img_bytes = NULL;
392
0
    g_autoptr(GBytes) img_padded = NULL;
393
0
    g_autoptr(FuChunkArray) chunks = NULL;
394
395
0
    img_bytes = fu_firmware_get_bytes(img, error);
396
0
    if (img_bytes == NULL)
397
0
      return NULL;
398
0
    chunks = fu_chunk_array_new_from_bytes(img_bytes,
399
0
                   FU_CHUNK_ADDR_OFFSET_NONE,
400
0
                   FU_CHUNK_PAGESZ_NONE,
401
0
                   64);
402
0
    img_padded =
403
0
        fu_bytes_pad(img_bytes, MAX(fu_chunk_array_length(chunks), 1) * 64, 0xFF);
404
0
    fu_byte_array_append_bytes(buf, img_padded);
405
0
    g_checksum_update(csum,
406
0
          (const guchar *)g_bytes_get_data(img_padded, NULL),
407
0
          g_bytes_get_size(img_padded));
408
0
    g_checksum_get_digest(csum, csumbuf, &csumbufsz);
409
410
    /* update checksum */
411
0
    if (!fu_memcpy_safe(buf->data,
412
0
            buf->len, /* dst */
413
0
            img_offset +
414
0
          FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_OFFSET_IMG_DIGEST,
415
0
            csumbuf,
416
0
            sizeof(csumbuf),
417
0
            0x0, /* src */
418
0
            sizeof(csumbuf),
419
0
            error))
420
0
      return NULL;
421
0
  }
422
423
178
  return g_steal_pointer(&buf);
424
178
}
425
426
static gchar *
427
fu_ccgx_dmc_firmware_convert_version(FuFirmware *firmware, guint64 version_raw)
428
998
{
429
998
  return fu_version_from_uint32(version_raw, fu_firmware_get_version_format(firmware));
430
998
}
431
432
static void
433
fu_ccgx_dmc_firmware_init(FuCcgxDmcFirmware *self)
434
1.56k
{
435
1.56k
  self->image_records =
436
1.56k
      g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_record_free);
437
1.56k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
438
1.56k
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_QUAD);
439
1.56k
}
440
441
static void
442
fu_ccgx_dmc_firmware_finalize(GObject *object)
443
1.56k
{
444
1.56k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(object);
445
446
1.56k
  if (self->fwct_blob != NULL)
447
1.05k
    g_bytes_unref(self->fwct_blob);
448
1.56k
  if (self->custom_meta_blob != NULL)
449
594
    g_bytes_unref(self->custom_meta_blob);
450
1.56k
  if (self->image_records != NULL)
451
1.56k
    g_ptr_array_unref(self->image_records);
452
453
1.56k
  G_OBJECT_CLASS(fu_ccgx_dmc_firmware_parent_class)->finalize(object);
454
1.56k
}
455
456
static void
457
fu_ccgx_dmc_firmware_class_init(FuCcgxDmcFirmwareClass *klass)
458
1
{
459
1
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
460
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
461
1
  firmware_class->convert_version = fu_ccgx_dmc_firmware_convert_version;
462
1
  object_class->finalize = fu_ccgx_dmc_firmware_finalize;
463
1
  firmware_class->validate = fu_ccgx_dmc_firmware_validate;
464
1
  firmware_class->parse = fu_ccgx_dmc_firmware_parse;
465
1
  firmware_class->write = fu_ccgx_dmc_firmware_write;
466
1
  firmware_class->export = fu_ccgx_dmc_firmware_export;
467
1
}
468
469
FuFirmware *
470
fu_ccgx_dmc_firmware_new(void)
471
0
{
472
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_CCGX_DMC_FIRMWARE, NULL));
473
0
}