Coverage Report

Created: 2025-08-29 06:48

/src/fwupd/libfwupdplugin/fu-srec-firmware.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2019 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
3.19M
#define G_LOG_DOMAIN "FuFirmware"
8
9
#include "config.h"
10
11
#include <string.h>
12
13
#include "fu-byte-array.h"
14
#include "fu-chunk-array.h"
15
#include "fu-firmware-common.h"
16
#include "fu-srec-firmware.h"
17
#include "fu-string.h"
18
19
/**
20
 * FuSrecFirmware:
21
 *
22
 * A SREC firmware image.
23
 *
24
 * See also: [class@FuFirmware]
25
 */
26
27
typedef struct {
28
  GPtrArray *records;
29
  guint32 addr_min;
30
  guint32 addr_max;
31
} FuSrecFirmwarePrivate;
32
33
G_DEFINE_TYPE_WITH_PRIVATE(FuSrecFirmware, fu_srec_firmware, FU_TYPE_FIRMWARE)
34
5.66M
#define GET_PRIVATE(o) (fu_srec_firmware_get_instance_private(o))
35
36
5.65M
#define FU_SREC_FIRMWARE_TOKENS_MAX 100000 /* lines */
37
38
/**
39
 * fu_srec_firmware_get_records:
40
 * @self: A #FuSrecFirmware
41
 *
42
 * Returns the raw records from SREC tokenization.
43
 *
44
 * This might be useful if the plugin is expecting the SREC file to be a list
45
 * of operations, rather than a simple linear image with filled holes.
46
 *
47
 * Returns: (transfer none) (element-type FuSrecFirmwareRecord): records
48
 *
49
 * Since: 1.3.2
50
 **/
51
GPtrArray *
52
fu_srec_firmware_get_records(FuSrecFirmware *self)
53
0
{
54
0
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(self);
55
0
  g_return_val_if_fail(FU_IS_SREC_FIRMWARE(self), NULL);
56
0
  return priv->records;
57
0
}
58
59
/**
60
 * fu_srec_firmware_set_addr_min:
61
 * @self: A #FuSrecFirmware
62
 * @addr_min: address, or 0x0 to disable
63
 *
64
 * Sets the minimum address allowed. This may be useful to ignore a bootloader section.
65
 *
66
 * Since: 1.9.3
67
 **/
68
void
69
fu_srec_firmware_set_addr_min(FuSrecFirmware *self, guint32 addr_min)
70
1.97k
{
71
1.97k
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(self);
72
1.97k
  g_return_if_fail(FU_IS_SREC_FIRMWARE(self));
73
1.97k
  priv->addr_min = addr_min;
74
1.97k
}
75
76
/**
77
 * fu_srec_firmware_set_addr_max:
78
 * @self: A #FuSrecFirmware
79
 * @addr_max: address, or 0x0 to disable
80
 *
81
 * Sets the maximum address allowed. This may be useful to ignore a signature.
82
 *
83
 * Since: 1.9.3
84
 **/
85
void
86
fu_srec_firmware_set_addr_max(FuSrecFirmware *self, guint32 addr_max)
87
0
{
88
0
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(self);
89
0
  g_return_if_fail(FU_IS_SREC_FIRMWARE(self));
90
0
  priv->addr_max = addr_max;
91
0
}
92
93
static void
94
fu_srec_firmware_record_free(FuSrecFirmwareRecord *rcd)
95
1.87M
{
96
1.87M
  g_byte_array_unref(rcd->buf);
97
1.87M
  g_free(rcd);
98
1.87M
}
99
100
#pragma clang diagnostic push
101
#pragma clang diagnostic ignored "-Wunused-function"
102
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuSrecFirmwareRecord, fu_srec_firmware_record_free);
103
#pragma clang diagnostic pop
104
105
/**
106
 * fu_srec_firmware_record_new: (skip):
107
 * @ln: unsigned integer
108
 * @kind: a record kind, e.g. #FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32
109
 * @addr: unsigned integer
110
 *
111
 * Returns a single firmware record
112
 *
113
 * Returns: (transfer full): record
114
 *
115
 * Since: 1.3.2
116
 **/
117
FuSrecFirmwareRecord *
118
fu_srec_firmware_record_new(guint ln, FuFirmareSrecRecordKind kind, guint32 addr)
119
1.87M
{
120
1.87M
  FuSrecFirmwareRecord *rcd = g_new0(FuSrecFirmwareRecord, 1);
121
1.87M
  rcd->ln = ln;
122
1.87M
  rcd->kind = kind;
123
1.87M
  rcd->addr = addr;
124
1.87M
  rcd->buf = g_byte_array_new();
125
1.87M
  return rcd;
126
1.87M
}
127
128
static FuSrecFirmwareRecord *
129
fu_srec_firmware_record_dup(const FuSrecFirmwareRecord *rcd)
130
0
{
131
0
  FuSrecFirmwareRecord *dest;
132
0
  g_return_val_if_fail(rcd != NULL, NULL);
133
0
  dest = fu_srec_firmware_record_new(rcd->ln, rcd->kind, rcd->addr);
134
0
  dest->buf = g_byte_array_ref(rcd->buf);
135
0
  return dest;
136
0
}
137
138
/**
139
 * fu_srec_firmware_record_get_type:
140
 *
141
 * Gets a specific type.
142
 *
143
 * Return value: a #GType
144
 *
145
 * Since: 1.6.1
146
 **/
147
GType
148
fu_srec_firmware_record_get_type(void)
149
0
{
150
0
  static GType type_id = 0;
151
0
  if (!type_id) {
152
0
    type_id =
153
0
        g_boxed_type_register_static("FuSrecFirmwareRecord",
154
0
             (GBoxedCopyFunc)fu_srec_firmware_record_dup,
155
0
             (GBoxedFreeFunc)fu_srec_firmware_record_free);
156
0
  }
157
0
  return type_id;
158
0
}
159
160
typedef struct {
161
  FuSrecFirmware *self;
162
  FuFirmwareParseFlags flags;
163
  gboolean got_eof;
164
} FuSrecFirmwareTokenHelper;
165
166
static gboolean
167
fu_srec_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
168
5.65M
{
169
5.65M
  FuSrecFirmwareTokenHelper *helper = (FuSrecFirmwareTokenHelper *)user_data;
170
5.65M
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(helper->self);
171
5.65M
  g_autoptr(FuSrecFirmwareRecord) rcd = NULL;
172
5.65M
  gboolean require_data = FALSE;
173
5.65M
  guint32 rec_addr32;
174
5.65M
  guint16 rec_addr16;
175
5.65M
  guint8 addrsz = 0; /* bytes */
176
5.65M
  guint8 rec_count;  /* words */
177
5.65M
  guint8 rec_kind;
178
179
  /* sanity check */
180
5.65M
  if (token_idx > FU_SREC_FIRMWARE_TOKENS_MAX) {
181
3
    g_set_error_literal(error,
182
3
            FWUPD_ERROR,
183
3
            FWUPD_ERROR_INVALID_DATA,
184
3
            "file has too many lines");
185
3
    return FALSE;
186
3
  }
187
188
  /* remove WIN32 line endings */
189
5.65M
  g_strdelimit(token->str, "\r\x1a", '\0');
190
5.65M
  token->len = strlen(token->str);
191
192
  /* ignore blank lines */
193
5.65M
  if (token->len == 0)
194
3.77M
    return TRUE;
195
196
  /* check starting token */
197
1.87M
  if (token->str[0] != 'S' || token->len < 3) {
198
256
    g_autofree gchar *strsafe = fu_strsafe(token->str, 3);
199
256
    if (strsafe != NULL) {
200
172
      g_set_error(error,
201
172
            FWUPD_ERROR,
202
172
            FWUPD_ERROR_INVALID_FILE,
203
172
            "invalid starting token, got '%s' at line %u",
204
172
            strsafe,
205
172
            token_idx + 1);
206
172
      return FALSE;
207
172
    }
208
84
    g_set_error(error,
209
84
          FWUPD_ERROR,
210
84
          FWUPD_ERROR_INVALID_FILE,
211
84
          "invalid starting token at line %u",
212
84
          token_idx + 1);
213
84
    return FALSE;
214
256
  }
215
216
  /* kind, count, address, (data), checksum, linefeed */
217
1.87M
  rec_kind = token->str[1] - '0';
218
1.87M
  if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 2, &rec_count, error))
219
123
    return FALSE;
220
1.87M
  if (rec_count * 2 != token->len - 4) {
221
298
    g_set_error(error,
222
298
          FWUPD_ERROR,
223
298
          FWUPD_ERROR_INVALID_FILE,
224
298
          "count incomplete at line %u, "
225
298
          "length %u, expected %u",
226
298
          token_idx + 1,
227
298
          (guint)token->len - 4,
228
298
          (guint)rec_count * 2);
229
298
    return FALSE;
230
298
  }
231
232
  /* checksum check */
233
1.87M
  if ((helper->flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
234
1.55M
    guint8 rec_csum = 0;
235
1.55M
    guint8 rec_csum_expected;
236
6.23M
    for (guint8 i = 0; i < rec_count; i++) {
237
4.67M
      guint8 csum_tmp = 0;
238
4.67M
      if (!fu_firmware_strparse_uint8_safe(token->str,
239
4.67M
                   token->len,
240
4.67M
                   (i * 2) + 2,
241
4.67M
                   &csum_tmp,
242
4.67M
                   error))
243
45
        return FALSE;
244
4.67M
      rec_csum += csum_tmp;
245
4.67M
    }
246
1.55M
    rec_csum ^= 0xff;
247
1.55M
    if (!fu_firmware_strparse_uint8_safe(token->str,
248
1.55M
                 token->len,
249
1.55M
                 (rec_count * 2) + 2,
250
1.55M
                 &rec_csum_expected,
251
1.55M
                 error))
252
134
      return FALSE;
253
1.55M
    if (rec_csum != rec_csum_expected) {
254
500
      g_set_error(error,
255
500
            FWUPD_ERROR,
256
500
            FWUPD_ERROR_INVALID_FILE,
257
500
            "checksum incorrect line %u, "
258
500
            "expected %02x, got %02x",
259
500
            token_idx + 1,
260
500
            rec_csum_expected,
261
500
            rec_csum);
262
500
      return FALSE;
263
500
    }
264
1.55M
  }
265
266
  /* set each command settings */
267
1.87M
  switch (rec_kind) {
268
2.56k
  case FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER:
269
1.53M
  case FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16:
270
1.53M
    addrsz = 2;
271
1.53M
    require_data = TRUE;
272
1.53M
    break;
273
987
  case FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24:
274
987
    addrsz = 3;
275
987
    require_data = TRUE;
276
987
    break;
277
1.69k
  case FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32:
278
1.69k
    addrsz = 4;
279
1.69k
    require_data = TRUE;
280
1.69k
    break;
281
256k
  case FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16:
282
256k
    addrsz = 2;
283
256k
    helper->got_eof = TRUE;
284
256k
    break;
285
330
  case FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24:
286
330
    addrsz = 3;
287
330
    break;
288
1.88k
  case FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32:
289
1.88k
    addrsz = 4;
290
1.88k
    helper->got_eof = TRUE;
291
1.88k
    break;
292
8.69k
  case FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24:
293
8.69k
    addrsz = 3;
294
8.69k
    helper->got_eof = TRUE;
295
8.69k
    break;
296
76.2k
  case FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16:
297
76.2k
    addrsz = 2;
298
76.2k
    helper->got_eof = TRUE;
299
76.2k
    break;
300
6
  default:
301
6
    g_set_error(error,
302
6
          FWUPD_ERROR,
303
6
          FWUPD_ERROR_INVALID_FILE,
304
6
          "invalid srec record type S%c at line %u",
305
6
          token->str[1],
306
6
          token_idx + 1);
307
6
    return FALSE;
308
1.87M
  }
309
310
  /* parse address */
311
1.87M
  switch (addrsz) {
312
1.86M
  case 2:
313
1.86M
    if (!fu_firmware_strparse_uint16_safe(token->str,
314
1.86M
                  token->len,
315
1.86M
                  4,
316
1.86M
                  &rec_addr16,
317
1.86M
                  error))
318
70
      return FALSE;
319
1.86M
    rec_addr32 = rec_addr16;
320
1.86M
    break;
321
10.0k
  case 3:
322
10.0k
    if (!fu_firmware_strparse_uint24_safe(token->str,
323
10.0k
                  token->len,
324
10.0k
                  4,
325
10.0k
                  &rec_addr32,
326
10.0k
                  error))
327
59
      return FALSE;
328
9.94k
    break;
329
9.94k
  case 4:
330
3.57k
    if (!fu_firmware_strparse_uint32_safe(token->str,
331
3.57k
                  token->len,
332
3.57k
                  4,
333
3.57k
                  &rec_addr32,
334
3.57k
                  error))
335
44
      return FALSE;
336
3.52k
    break;
337
3.52k
  default:
338
0
    g_assert_not_reached();
339
1.87M
  }
340
1.87M
  g_debug("line %03u S%u addr:0x%04x datalen:0x%02x",
341
1.87M
    token_idx + 1,
342
1.87M
    rec_kind,
343
1.87M
    rec_addr32,
344
1.87M
    (guint)rec_count - addrsz - 1);
345
1.87M
  if (require_data && rec_count == addrsz) {
346
48
    g_set_error(error,
347
48
          FWUPD_ERROR,
348
48
          FWUPD_ERROR_INVALID_FILE,
349
48
          "S%u required data but not provided",
350
48
          rec_kind);
351
48
    return FALSE;
352
48
  }
353
354
  /* data */
355
1.87M
  rcd = fu_srec_firmware_record_new(token_idx + 1, rec_kind, rec_addr32);
356
1.87M
  if (rec_kind == 1 || rec_kind == 2 || rec_kind == 3) {
357
1.54M
    for (gsize i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) {
358
11.5k
      guint8 tmp = 0;
359
11.5k
      if (!fu_firmware_strparse_uint8_safe(token->str,
360
11.5k
                   token->len,
361
11.5k
                   i,
362
11.5k
                   &tmp,
363
11.5k
                   error))
364
11
        return FALSE;
365
11.4k
      fu_byte_array_append_uint8(rcd->buf, tmp);
366
11.4k
    }
367
1.53M
  }
368
1.87M
  g_ptr_array_add(priv->records, g_steal_pointer(&rcd));
369
1.87M
  return TRUE;
370
1.87M
}
371
372
static gboolean
373
fu_srec_firmware_tokenize(FuFirmware *firmware,
374
        GInputStream *stream,
375
        FuFirmwareParseFlags flags,
376
        GError **error)
377
4.93k
{
378
4.93k
  FuSrecFirmware *self = FU_SREC_FIRMWARE(firmware);
379
4.93k
  FuSrecFirmwareTokenHelper helper = {.self = self, .flags = flags, .got_eof = FALSE};
380
381
  /* parse records */
382
4.93k
  if (!fu_strsplit_stream(stream, 0x0, "\n", fu_srec_firmware_tokenize_cb, &helper, error))
383
1.59k
    return FALSE;
384
385
  /* no EOF */
386
3.33k
  if (!helper.got_eof) {
387
1.05k
    g_set_error_literal(error,
388
1.05k
            FWUPD_ERROR,
389
1.05k
            FWUPD_ERROR_INVALID_FILE,
390
1.05k
            "no EOF, perhaps truncated file");
391
1.05k
    return FALSE;
392
1.05k
  }
393
2.27k
  return TRUE;
394
3.33k
}
395
396
static gboolean
397
fu_srec_firmware_parse(FuFirmware *firmware,
398
           GInputStream *stream,
399
           FuFirmwareParseFlags flags,
400
           GError **error)
401
2.27k
{
402
2.27k
  FuSrecFirmware *self = FU_SREC_FIRMWARE(firmware);
403
2.27k
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(self);
404
2.27k
  gboolean got_hdr = FALSE;
405
2.27k
  guint16 data_cnt = 0;
406
2.27k
  guint32 addr32_last = 0;
407
2.27k
  guint32 img_address = 0;
408
2.27k
  g_autoptr(GBytes) img_bytes = NULL;
409
2.27k
  g_autoptr(GByteArray) outbuf = g_byte_array_new();
410
411
  /* parse records */
412
1.41M
  for (guint j = 0; j < priv->records->len; j++) {
413
1.40M
    FuSrecFirmwareRecord *rcd = g_ptr_array_index(priv->records, j);
414
415
    /* header */
416
1.40M
    if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER) {
417
2.07k
      g_autoptr(GString) modname = g_string_new(NULL);
418
419
      /* check for duplicate */
420
2.07k
      if (got_hdr) {
421
8
        g_set_error(error,
422
8
              FWUPD_ERROR,
423
8
              FWUPD_ERROR_INVALID_FILE,
424
8
              "duplicate header record at line %u",
425
8
              rcd->ln);
426
8
        return FALSE;
427
8
      }
428
429
      /* could be anything, lets assume text */
430
2.06k
      for (guint i = 0; i < rcd->buf->len; i++) {
431
0
        gchar tmp = rcd->buf->data[i];
432
0
        if (!g_ascii_isgraph(tmp))
433
0
          break;
434
0
        g_string_append_c(modname, tmp);
435
0
      }
436
2.06k
      if (modname->len != 0)
437
0
        fu_firmware_set_id(firmware, modname->str);
438
2.06k
      got_hdr = TRUE;
439
2.06k
      continue;
440
2.07k
    }
441
442
    /* verify we got all records */
443
1.40M
    if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16) {
444
3.05k
      if (rcd->addr != data_cnt) {
445
189
        g_set_error(error,
446
189
              FWUPD_ERROR,
447
189
              FWUPD_ERROR_INVALID_FILE,
448
189
              "count record was not valid, got 0x%02x expected "
449
189
              "0x%02x at line %u",
450
189
              (guint)rcd->addr,
451
189
              (guint)data_cnt,
452
189
              rcd->ln);
453
189
        return FALSE;
454
189
      }
455
2.86k
      continue;
456
3.05k
    }
457
458
    /* data */
459
1.40M
    if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16 ||
460
1.40M
        rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24 ||
461
1.40M
        rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) {
462
      /* invalid */
463
1.32M
      if (!got_hdr) {
464
13
        g_set_error(error,
465
13
              FWUPD_ERROR,
466
13
              FWUPD_ERROR_INVALID_FILE,
467
13
              "missing header record at line %u",
468
13
              rcd->ln);
469
13
        return FALSE;
470
13
      }
471
472
      /* does not make sense */
473
1.32M
      if (rcd->addr < addr32_last) {
474
26
        g_set_error(error,
475
26
              FWUPD_ERROR,
476
26
              FWUPD_ERROR_INVALID_FILE,
477
26
              "invalid address 0x%x, last was 0x%x at line %u",
478
26
              (guint)rcd->addr,
479
26
              (guint)addr32_last,
480
26
              rcd->ln);
481
26
        return FALSE;
482
26
      }
483
1.32M
      if (rcd->addr < priv->addr_min) {
484
1.32M
        g_debug(
485
1.32M
            "ignoring data at 0x%x as before start address 0x%x at line %u",
486
1.32M
            (guint)rcd->addr,
487
1.32M
            priv->addr_min,
488
1.32M
            rcd->ln);
489
1.32M
      } else if (priv->addr_max > 0 && rcd->addr < priv->addr_max) {
490
0
        g_debug(
491
0
            "ignoring data at 0x%x as after end address 0x%x at line %u",
492
0
            (guint)rcd->addr,
493
0
            priv->addr_max,
494
0
            rcd->ln);
495
3.74k
      } else {
496
3.74k
        guint32 len_hole = rcd->addr - addr32_last;
497
498
        /* fill any holes, but only up to 1Mb to avoid a DoS */
499
3.74k
        if (addr32_last > 0 && len_hole > 0x100000) {
500
25
          g_set_error(
501
25
              error,
502
25
              FWUPD_ERROR,
503
25
              FWUPD_ERROR_INVALID_FILE,
504
25
              "hole of 0x%x bytes too large to fill at line %u",
505
25
              (guint)len_hole,
506
25
              rcd->ln);
507
25
          return FALSE;
508
25
        }
509
3.71k
        if (addr32_last > 0x0 && len_hole > 1) {
510
434
          g_debug("filling address 0x%08x to 0x%08x at line %u",
511
434
            addr32_last + 1,
512
434
            addr32_last + len_hole - 1,
513
434
            rcd->ln);
514
128M
          for (guint i = 0; i < len_hole; i++)
515
128M
            fu_byte_array_append_uint8(outbuf, 0xff);
516
434
        }
517
518
        /* add data */
519
3.71k
        g_byte_array_append(outbuf, rcd->buf->data, rcd->buf->len);
520
3.71k
        if (img_address == 0x0)
521
1.09k
          img_address = rcd->addr;
522
3.71k
        addr32_last = rcd->addr + rcd->buf->len;
523
3.71k
        if (addr32_last < rcd->addr) {
524
3
          g_set_error(error,
525
3
                FWUPD_ERROR,
526
3
                FWUPD_ERROR_INVALID_FILE,
527
3
                "overflow from address 0x%x at line %u",
528
3
                (guint)rcd->addr,
529
3
                rcd->ln);
530
3
          return FALSE;
531
3
        }
532
3.71k
      }
533
1.32M
      data_cnt++;
534
1.32M
    }
535
1.40M
  }
536
537
  /* add single image */
538
2.01k
  img_bytes = g_bytes_new(outbuf->data, outbuf->len);
539
2.01k
  fu_firmware_set_bytes(firmware, img_bytes);
540
2.01k
  fu_firmware_set_addr(firmware, img_address);
541
2.01k
  return TRUE;
542
2.27k
}
543
544
static void
545
fu_srec_firmware_write_line(GString *str,
546
          FuFirmareSrecRecordKind kind,
547
          guint32 addr,
548
          const guint8 *buf,
549
          gsize bufsz)
550
713k
{
551
713k
  guint8 csum = 0;
552
713k
  g_autoptr(GByteArray) buf_addr = g_byte_array_new();
553
554
713k
  if (kind == FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER ||
555
713k
      kind == FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16 ||
556
713k
      kind == FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16 ||
557
713k
      kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16) {
558
521k
    fu_byte_array_append_uint16(buf_addr, addr, G_BIG_ENDIAN);
559
521k
  } else if (kind == FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24 ||
560
191k
       kind == FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24 ||
561
191k
       kind == FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24) {
562
56.9k
    fu_byte_array_append_uint32(buf_addr, addr, G_BIG_ENDIAN);
563
56.9k
    g_byte_array_remove_index(buf_addr, 0);
564
134k
  } else if (kind == FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32 ||
565
134k
       kind == FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32) {
566
134k
    fu_byte_array_append_uint32(buf_addr, addr, G_BIG_ENDIAN);
567
134k
  }
568
569
  /* bytecount + address + data */
570
713k
  csum = buf_addr->len + bufsz + 1;
571
2.46M
  for (guint i = 0; i < buf_addr->len; i++)
572
1.75M
    csum += buf_addr->data[i];
573
46.2M
  for (guint i = 0; i < bufsz; i++)
574
45.5M
    csum += buf[i];
575
713k
  csum ^= 0xff;
576
577
  /* output record */
578
713k
  g_string_append_printf(str, "S%X", kind);
579
713k
  g_string_append_printf(str, "%02X", (guint)(buf_addr->len + bufsz + 1));
580
2.46M
  for (guint i = 0; i < buf_addr->len; i++)
581
1.75M
    g_string_append_printf(str, "%02X", buf_addr->data[i]);
582
46.2M
  for (guint i = 0; i < bufsz; i++)
583
45.5M
    g_string_append_printf(str, "%02X", buf[i]);
584
713k
  g_string_append_printf(str, "%02X\n", csum);
585
713k
}
586
587
static GByteArray *
588
fu_srec_firmware_write(FuFirmware *firmware, GError **error)
589
379
{
590
379
  g_autoptr(GByteArray) buf = g_byte_array_new();
591
379
  g_autoptr(GString) str = g_string_new(NULL);
592
379
  g_autoptr(GBytes) buf_blob = NULL;
593
379
  const gchar *id = fu_firmware_get_id(firmware);
594
379
  gsize id_strlen = id != NULL ? strlen(id) : 0;
595
379
  FuFirmareSrecRecordKind kind_data = FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16;
596
379
  FuFirmareSrecRecordKind kind_coun = FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16;
597
379
  FuFirmareSrecRecordKind kind_term = FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16;
598
599
  /* upgrade to longer addresses? */
600
379
  if (fu_firmware_get_addr(firmware) >= (1ull << 24)) {
601
73
    kind_data = FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32;
602
73
    kind_term = FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32; /* intentional... */
603
306
  } else if (fu_firmware_get_addr(firmware) >= (1ull << 16)) {
604
39
    kind_data = FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24;
605
39
    kind_term = FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24;
606
39
  }
607
608
  /* main blob */
609
379
  buf_blob = fu_firmware_get_bytes_with_patches(firmware, error);
610
379
  if (buf_blob == NULL)
611
0
    return NULL;
612
613
  /* header */
614
379
  fu_srec_firmware_write_line(str,
615
379
            FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER,
616
379
            0x0,
617
379
            (const guint8 *)id,
618
379
            id_strlen);
619
620
  /* payload */
621
379
  if (g_bytes_get_size(buf_blob) > 0) {
622
203
    g_autoptr(FuChunkArray) chunks =
623
203
        fu_chunk_array_new_from_bytes(buf_blob,
624
203
              fu_firmware_get_addr(firmware),
625
203
              FU_CHUNK_PAGESZ_NONE,
626
203
              64);
627
712k
    for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
628
712k
      g_autoptr(FuChunk) chk = NULL;
629
630
      /* prepare chunk */
631
712k
      chk = fu_chunk_array_index(chunks, i, error);
632
712k
      if (chk == NULL)
633
0
        return NULL;
634
712k
      fu_srec_firmware_write_line(str,
635
712k
                kind_data,
636
712k
                fu_chunk_get_address(chk),
637
712k
                fu_chunk_get_data(chk),
638
712k
                fu_chunk_get_data_sz(chk));
639
712k
    }
640
    /* upgrade to longer format */
641
203
    if (fu_chunk_array_length(chunks) > G_MAXUINT16)
642
0
      kind_coun = FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24;
643
203
    fu_srec_firmware_write_line(str, kind_coun, fu_chunk_array_length(chunks), NULL, 0);
644
203
  }
645
646
  /* EOF */
647
379
  fu_srec_firmware_write_line(str, kind_term, 0x0, NULL, 0);
648
649
  /* success */
650
379
  g_byte_array_append(buf, (const guint8 *)str->str, str->len);
651
379
  return g_steal_pointer(&buf);
652
379
}
653
654
static void
655
fu_srec_firmware_finalize(GObject *object)
656
4.97k
{
657
4.97k
  FuSrecFirmware *self = FU_SREC_FIRMWARE(object);
658
4.97k
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(self);
659
4.97k
  g_ptr_array_unref(priv->records);
660
4.97k
  G_OBJECT_CLASS(fu_srec_firmware_parent_class)->finalize(object);
661
4.97k
}
662
663
static void
664
fu_srec_firmware_init(FuSrecFirmware *self)
665
4.97k
{
666
4.97k
  FuSrecFirmwarePrivate *priv = GET_PRIVATE(self);
667
4.97k
  priv->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_srec_firmware_record_free);
668
4.97k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
669
4.97k
}
670
671
static void
672
fu_srec_firmware_class_init(FuSrecFirmwareClass *klass)
673
2
{
674
2
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
675
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
676
2
  object_class->finalize = fu_srec_firmware_finalize;
677
2
  firmware_class->parse = fu_srec_firmware_parse;
678
2
  firmware_class->tokenize = fu_srec_firmware_tokenize;
679
2
  firmware_class->write = fu_srec_firmware_write;
680
2
}
681
682
/**
683
 * fu_srec_firmware_new:
684
 *
685
 * Creates a new #FuFirmware of type SREC
686
 *
687
 * Since: 1.3.2
688
 **/
689
FuFirmware *
690
fu_srec_firmware_new(void)
691
2.01k
{
692
2.01k
  return FU_FIRMWARE(g_object_new(FU_TYPE_SREC_FIRMWARE, NULL));
693
2.01k
}