Coverage Report

Created: 2025-12-14 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/plugins/wacom-usb/fu-wac-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2018 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include <string.h>
10
11
#include "fu-wac-firmware.h"
12
#include "fu-wac-struct.h"
13
14
struct _FuWacFirmware {
15
  FuFirmware parent_instance;
16
};
17
18
2.01k
G_DEFINE_TYPE(FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE)
19
2.01k
20
867k
#define FU_WAC_FIRMWARE_TOKENS_MAX   100000 /* lines */
21
1.57k
#define FU_WAC_FIRMWARE_SECTIONS_MAX 10
22
23
typedef struct {
24
  guint32 addr;
25
  guint32 sz;
26
  guint32 prog_start_addr;
27
} FuFirmwareWacHeaderRecord;
28
29
typedef struct {
30
  FuFirmware *firmware;
31
  FuFirmwareParseFlags flags;
32
  GPtrArray *header_infos;
33
  GString *image_buffer;
34
  guint8 images_cnt;
35
} FuWacFirmwareTokenHelper;
36
37
static gboolean
38
fu_wac_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
39
867k
{
40
867k
  FuWacFirmwareTokenHelper *helper = (FuWacFirmwareTokenHelper *)user_data;
41
867k
  g_autofree gchar *cmd = NULL;
42
43
  /* sanity check */
44
867k
  if (token_idx > FU_WAC_FIRMWARE_TOKENS_MAX) {
45
1
    g_set_error_literal(error,
46
1
            FWUPD_ERROR,
47
1
            FWUPD_ERROR_INVALID_DATA,
48
1
            "file has too many lines");
49
1
    return FALSE;
50
1
  }
51
52
  /* remove WIN32 line endings */
53
867k
  g_strdelimit(token->str, "\r\x1a", '\0');
54
867k
  token->len = strlen(token->str);
55
56
  /* ignore blank lines */
57
867k
  cmd = g_strndup(token->str, 2);
58
867k
  if (g_strcmp0(cmd, "") == 0)
59
355k
    return TRUE;
60
61
  /* custom metadata */
62
512k
  if (g_strcmp0(cmd, "WA") == 0) {
63
    /* header info record */
64
4.29k
    if (token->len > 3 && memcmp(token->str + 2, "COM", 3) == 0) {
65
1.71k
      guint8 header_image_cnt = 0;
66
1.71k
      if (token->len != 40) {
67
134
        g_set_error(error,
68
134
              FWUPD_ERROR,
69
134
              FWUPD_ERROR_INTERNAL,
70
134
              "invalid header, got %" G_GSIZE_FORMAT " bytes",
71
134
              token->len);
72
134
        return FALSE;
73
134
      }
74
75
      /* sanity check */
76
1.57k
      if (helper->header_infos->len > FU_WAC_FIRMWARE_SECTIONS_MAX) {
77
2
        g_set_error(error,
78
2
              FWUPD_ERROR,
79
2
              FWUPD_ERROR_INTERNAL,
80
2
              "too many metadata sections: %u",
81
2
              helper->header_infos->len);
82
2
        return FALSE;
83
2
      }
84
1.57k
      if (!fu_firmware_strparse_uint4_safe(token->str,
85
1.57k
                   token->len,
86
1.57k
                   5,
87
1.57k
                   &header_image_cnt,
88
1.57k
                   error))
89
1
        return FALSE;
90
3.63k
      for (guint j = 0; j < header_image_cnt; j++) {
91
2.11k
        g_autofree FuFirmwareWacHeaderRecord *hdr = NULL;
92
2.11k
        hdr = g_new0(FuFirmwareWacHeaderRecord, 1);
93
2.11k
        if (!fu_firmware_strparse_uint32_safe(token->str,
94
2.11k
                      token->len,
95
2.11k
                      (j * 16) + 6,
96
2.11k
                      &hdr->addr,
97
2.11k
                      error))
98
43
          return FALSE;
99
2.07k
        if (!fu_firmware_strparse_uint32_safe(token->str,
100
2.07k
                      token->len,
101
2.07k
                      (j * 16) + 14,
102
2.07k
                      &hdr->sz,
103
2.07k
                      error))
104
20
          return FALSE;
105
2.05k
        g_debug("header_fw%u_addr: 0x%x", j, hdr->addr);
106
2.05k
        g_debug("header_fw%u_sz:   0x%x", j, hdr->sz);
107
2.05k
        g_ptr_array_add(helper->header_infos, g_steal_pointer(&hdr));
108
2.05k
      }
109
1.51k
      return TRUE;
110
1.57k
    }
111
112
    /* firmware headline record */
113
2.57k
    if (token->len == 13) {
114
566
      FuFirmwareWacHeaderRecord *hdr;
115
566
      guint8 idx = 0;
116
566
      if (!fu_firmware_strparse_uint4_safe(token->str,
117
566
                   token->len,
118
566
                   2,
119
566
                   &idx,
120
566
                   error))
121
2
        return FALSE;
122
564
      if (idx == 0) {
123
1
        g_set_error(error,
124
1
              FWUPD_ERROR,
125
1
              FWUPD_ERROR_INTERNAL,
126
1
              "headline %u invalid",
127
1
              idx);
128
1
        return FALSE;
129
1
      }
130
563
      if (idx > helper->header_infos->len) {
131
6
        g_set_error(error,
132
6
              FWUPD_ERROR,
133
6
              FWUPD_ERROR_INTERNAL,
134
6
              "headline %u exceeds header count %u",
135
6
              idx,
136
6
              helper->header_infos->len);
137
6
        return FALSE;
138
6
      }
139
557
      if (idx - 1 != helper->images_cnt) {
140
4
        g_set_error(error,
141
4
              FWUPD_ERROR,
142
4
              FWUPD_ERROR_INTERNAL,
143
4
              "headline %u is not in sorted order",
144
4
              idx);
145
4
        return FALSE;
146
4
      }
147
553
      hdr = g_ptr_array_index(helper->header_infos, idx - 1);
148
553
      if (!fu_firmware_strparse_uint32_safe(token->str,
149
553
                    token->len,
150
553
                    3,
151
553
                    &hdr->prog_start_addr,
152
553
                    error))
153
1
        return FALSE;
154
552
      if (hdr->prog_start_addr != hdr->addr) {
155
46
        g_set_error(error,
156
46
              FWUPD_ERROR,
157
46
              FWUPD_ERROR_INTERNAL,
158
46
              "programming address 0x%x != "
159
46
              "base address 0x%0x for idx %u",
160
46
              hdr->prog_start_addr,
161
46
              hdr->addr,
162
46
              idx);
163
46
        return FALSE;
164
46
      }
165
506
      g_debug("programing-start-address: 0x%x", hdr->prog_start_addr);
166
506
      return TRUE;
167
552
    }
168
169
2.01k
    g_debug("unknown Wacom-specific metadata");
170
2.01k
    return TRUE;
171
2.57k
  }
172
173
  /* start */
174
507k
  if (g_strcmp0(cmd, "S0") == 0) {
175
1.90k
    if (helper->image_buffer->len > 0) {
176
19
      g_set_error_literal(error,
177
19
              FWUPD_ERROR,
178
19
              FWUPD_ERROR_INTERNAL,
179
19
              "duplicate S0 without S7");
180
19
      return FALSE;
181
19
    }
182
1.88k
    g_string_append_printf(helper->image_buffer, "%s\n", token->str);
183
1.88k
    return TRUE;
184
1.90k
  }
185
186
  /* these are things we want to include in the image */
187
505k
  if (g_strcmp0(cmd, "S1") == 0 || g_strcmp0(cmd, "S2") == 0 || g_strcmp0(cmd, "S3") == 0 ||
188
7.18k
      g_strcmp0(cmd, "S5") == 0 || g_strcmp0(cmd, "S7") == 0 || g_strcmp0(cmd, "S8") == 0 ||
189
505k
      g_strcmp0(cmd, "S9") == 0) {
190
505k
    if (helper->image_buffer->len == 0) {
191
22
      g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without S0", cmd);
192
22
      return FALSE;
193
22
    }
194
505k
    g_string_append_printf(helper->image_buffer, "%s\n", token->str);
195
505k
  } else {
196
68
    g_set_error(error,
197
68
          FWUPD_ERROR,
198
68
          FWUPD_ERROR_INTERNAL,
199
68
          "invalid SREC command on line %u: %s",
200
68
          token_idx + 1,
201
68
          cmd);
202
68
    return FALSE;
203
68
  }
204
205
  /* end */
206
505k
  if (g_strcmp0(cmd, "S7") == 0) {
207
1.60k
    g_autoptr(GBytes) blob = NULL;
208
1.60k
    g_autoptr(GBytes) fw_srec = NULL;
209
1.60k
    g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new();
210
1.60k
    g_autoptr(FuFirmware) img = fu_firmware_new();
211
1.60k
    FuFirmwareWacHeaderRecord *hdr;
212
213
    /* get the correct relocated start address */
214
1.60k
    if (helper->images_cnt >= helper->header_infos->len) {
215
24
      g_set_error(error,
216
24
            FWUPD_ERROR,
217
24
            FWUPD_ERROR_INTERNAL,
218
24
            "%s without header",
219
24
            cmd);
220
24
      return FALSE;
221
24
    }
222
1.58k
    hdr = g_ptr_array_index(helper->header_infos, helper->images_cnt);
223
224
1.58k
    if (helper->image_buffer->len == 0) {
225
0
      g_set_error(error,
226
0
            FWUPD_ERROR,
227
0
            FWUPD_ERROR_INTERNAL,
228
0
            "%s with missing image buffer",
229
0
            cmd);
230
0
      return FALSE;
231
0
    }
232
233
    /* parse SREC file and add as image */
234
1.58k
    blob = g_bytes_new(helper->image_buffer->str, helper->image_buffer->len);
235
1.58k
    fu_srec_firmware_set_addr_min(FU_SREC_FIRMWARE(firmware_srec), hdr->addr);
236
1.58k
    if (!fu_firmware_parse_bytes(firmware_srec,
237
1.58k
               blob,
238
1.58k
               0x0,
239
1.58k
               helper->flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
240
1.58k
               error))
241
362
      return FALSE;
242
1.22k
    fw_srec = fu_firmware_get_bytes(firmware_srec, error);
243
1.22k
    if (fw_srec == NULL)
244
0
      return FALSE;
245
1.22k
    fu_firmware_set_bytes(img, fw_srec);
246
1.22k
    fu_firmware_set_addr(img, fu_firmware_get_addr(firmware_srec));
247
1.22k
    fu_firmware_set_idx(img, helper->images_cnt);
248
1.22k
    if (!fu_firmware_add_image(helper->firmware, img, error))
249
0
      return FALSE;
250
1.22k
    helper->images_cnt++;
251
252
    /* clear the image buffer */
253
1.22k
    g_string_set_size(helper->image_buffer, 0);
254
1.22k
  }
255
256
  /* success */
257
505k
  return TRUE;
258
505k
}
259
260
static gboolean
261
fu_wac_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
262
224k
{
263
224k
  return fu_struct_wac_firmware_hdr_validate_stream(stream, offset, error);
264
224k
}
265
266
static gboolean
267
fu_wac_firmware_parse(FuFirmware *firmware,
268
          GInputStream *stream,
269
          FuFirmwareParseFlags flags,
270
          GError **error)
271
1.85k
{
272
1.85k
  g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func(g_free);
273
1.85k
  g_autoptr(GString) image_buffer = g_string_new(NULL);
274
1.85k
  FuWacFirmwareTokenHelper helper = {.firmware = firmware,
275
1.85k
             .flags = flags,
276
1.85k
             .header_infos = header_infos,
277
1.85k
             .image_buffer = image_buffer,
278
1.85k
             .images_cnt = 0};
279
280
  /* tokenize */
281
1.85k
  if (!fu_strsplit_stream(stream, 0x0, "\n", fu_wac_firmware_tokenize_cb, &helper, error))
282
756
    return FALSE;
283
284
  /* verify data is complete */
285
1.10k
  if (helper.image_buffer->len > 0) {
286
241
    g_set_error_literal(error,
287
241
            FWUPD_ERROR,
288
241
            FWUPD_ERROR_INTERNAL,
289
241
            "truncated data: no S7");
290
241
    return FALSE;
291
241
  }
292
293
  /* ensure this matched the header */
294
861
  if (helper.header_infos->len != helper.images_cnt) {
295
63
    g_set_error(error,
296
63
          FWUPD_ERROR,
297
63
          FWUPD_ERROR_INTERNAL,
298
63
          "not enough images %u for header count %u",
299
63
          helper.images_cnt,
300
63
          header_infos->len);
301
63
    return FALSE;
302
63
  }
303
304
  /* success */
305
798
  return TRUE;
306
861
}
307
308
static guint8
309
fu_wac_firmware_calc_checksum(GByteArray *buf)
310
906
{
311
906
  return fu_sum8(buf->data, buf->len) ^ 0xFF;
312
906
}
313
314
static GByteArray *
315
fu_wac_firmware_write(FuFirmware *firmware, GError **error)
316
798
{
317
798
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
318
798
  g_autoptr(GString) str = g_string_new(NULL);
319
798
  g_autoptr(GByteArray) buf = g_byte_array_new();
320
798
  g_autoptr(GByteArray) buf_hdr = g_byte_array_new();
321
322
  /* fw header */
323
798
  if (images->len == 0) {
324
520
    g_set_error_literal(error,
325
520
            FWUPD_ERROR,
326
520
            FWUPD_ERROR_NOT_SUPPORTED,
327
520
            "no firmware images found");
328
520
    return NULL;
329
520
  }
330
906
  for (guint i = 0; i < images->len; i++) {
331
628
    FuFirmware *img = g_ptr_array_index(images, i);
332
628
    fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_addr(img), G_BIG_ENDIAN);
333
628
    fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_size(img), G_BIG_ENDIAN);
334
628
  }
335
278
  g_string_append_printf(str, "WACOM%u", images->len);
336
5.30k
  for (guint i = 0; i < buf_hdr->len; i++)
337
5.02k
    g_string_append_printf(str, "%02X", buf_hdr->data[i]);
338
278
  g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_hdr));
339
340
  /* payload */
341
906
  for (guint i = 0; i < images->len; i++) {
342
628
    FuFirmware *img = g_ptr_array_index(images, i);
343
628
    g_autoptr(GBytes) img_blob = NULL;
344
628
    g_autoptr(GByteArray) buf_img = g_byte_array_new();
345
346
    /* img header */
347
628
    g_string_append_printf(str, "WA%u", (guint)fu_firmware_get_idx(img) + 1);
348
628
    fu_byte_array_append_uint32(buf_img, fu_firmware_get_addr(img), G_BIG_ENDIAN);
349
3.14k
    for (guint j = 0; j < buf_img->len; j++)
350
2.51k
      g_string_append_printf(str, "%02X", buf_img->data[j]);
351
628
    g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_img));
352
353
    /* srec */
354
628
    img_blob = fu_firmware_write(img, error);
355
628
    if (img_blob == NULL)
356
0
      return NULL;
357
628
    g_string_append_len(str,
358
628
            (const gchar *)g_bytes_get_data(img_blob, NULL),
359
628
            g_bytes_get_size(img_blob));
360
628
  }
361
362
  /* success */
363
278
  g_byte_array_append(buf, (const guint8 *)str->str, str->len);
364
278
  return g_steal_pointer(&buf);
365
278
}
366
367
static void
368
fu_wac_firmware_init(FuWacFirmware *self)
369
2.01k
{
370
2.01k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 1024);
371
2.01k
  g_type_ensure(FU_TYPE_SREC_FIRMWARE);
372
2.01k
}
373
374
static void
375
fu_wac_firmware_class_init(FuWacFirmwareClass *klass)
376
1
{
377
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
378
1
  firmware_class->validate = fu_wac_firmware_validate;
379
1
  firmware_class->parse = fu_wac_firmware_parse;
380
1
  firmware_class->write = fu_wac_firmware_write;
381
1
}
382
383
FuFirmware *
384
fu_wac_firmware_new(void)
385
0
{
386
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_WAC_FIRMWARE, NULL));
387
0
}