Coverage Report

Created: 2026-01-17 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-ihex-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2019 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
84.4k
#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-firmware-common.h"
15
#include "fu-ihex-firmware.h"
16
#include "fu-mem.h"
17
#include "fu-string.h"
18
19
/**
20
 * FuIhexFirmware:
21
 *
22
 * A Intel hex (ihex) firmware image.
23
 *
24
 * See also: [class@FuFirmware]
25
 */
26
27
typedef struct {
28
  GPtrArray *records;
29
  guint8 padding_value;
30
} FuIhexFirmwarePrivate;
31
32
10.7k
G_DEFINE_TYPE_WITH_PRIVATE(FuIhexFirmware, fu_ihex_firmware, FU_TYPE_FIRMWARE)
33
3.67M
#define GET_PRIVATE(o) (fu_ihex_firmware_get_instance_private(o))
34
35
3.66M
#define FU_IHEX_FIRMWARE_TOKENS_MAX 100000 /* lines */
36
37
/**
38
 * fu_ihex_firmware_get_records:
39
 * @self: A #FuIhexFirmware
40
 *
41
 * Returns the raw lines from tokenization.
42
 *
43
 * This might be useful if the plugin is expecting the hex file to be a list
44
 * of operations, rather than a simple linear image with filled holes.
45
 *
46
 * Returns: (transfer none) (element-type FuIhexFirmwareRecord): records
47
 *
48
 * Since: 1.3.4
49
 **/
50
GPtrArray *
51
fu_ihex_firmware_get_records(FuIhexFirmware *self)
52
0
{
53
0
  FuIhexFirmwarePrivate *priv = GET_PRIVATE(self);
54
0
  g_return_val_if_fail(FU_IS_IHEX_FIRMWARE(self), NULL);
55
0
  return priv->records;
56
0
}
57
58
/**
59
 * fu_ihex_firmware_set_padding_value:
60
 * @self: A #FuIhexFirmware
61
 * @padding_value: the byte used to pad the image
62
 *
63
 * Set the padding value to fill incomplete address ranges.
64
 *
65
 * The default value of zero can be changed to `0xff` if functions like
66
 * fu_bytes_is_empty() are going to be used on subsections of the data.
67
 *
68
 * Since: 1.6.0
69
 **/
70
void
71
fu_ihex_firmware_set_padding_value(FuIhexFirmware *self, guint8 padding_value)
72
0
{
73
0
  FuIhexFirmwarePrivate *priv = GET_PRIVATE(self);
74
0
  g_return_if_fail(FU_IS_IHEX_FIRMWARE(self));
75
0
  priv->padding_value = padding_value;
76
0
}
77
78
static void
79
fu_ihex_firmware_record_free(FuIhexFirmwareRecord *rcd)
80
436k
{
81
436k
  g_string_free(rcd->buf, TRUE);
82
436k
  g_byte_array_unref(rcd->data);
83
436k
  g_free(rcd);
84
436k
}
85
86
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuIhexFirmwareRecord, fu_ihex_firmware_record_free)
87
88
static FuIhexFirmwareRecord *
89
fu_ihex_firmware_record_new(guint ln, const gchar *line, FuFirmwareParseFlags flags, GError **error)
90
436k
{
91
436k
  g_autoptr(FuIhexFirmwareRecord) rcd = NULL;
92
436k
  gsize linesz = strlen(line);
93
436k
  guint line_end;
94
436k
  guint16 addr16 = 0;
95
96
  /* check starting token */
97
436k
  if (line[0] != ':') {
98
171
    g_autofree gchar *strsafe = fu_strsafe(line, 5);
99
171
    if (strsafe != NULL) {
100
65
      g_set_error(error,
101
65
            FWUPD_ERROR,
102
65
            FWUPD_ERROR_INVALID_FILE,
103
65
            "invalid starting token: %s",
104
65
            strsafe);
105
65
      return NULL;
106
65
    }
107
106
    g_set_error_literal(error,
108
106
            FWUPD_ERROR,
109
106
            FWUPD_ERROR_INVALID_FILE,
110
106
            "invalid starting token");
111
106
    return NULL;
112
171
  }
113
114
  /* length, 16-bit address, type */
115
436k
  rcd = g_new0(FuIhexFirmwareRecord, 1);
116
436k
  rcd->ln = ln;
117
436k
  rcd->data = g_byte_array_new();
118
436k
  rcd->buf = g_string_new(line);
119
436k
  if (!fu_firmware_strparse_uint8_safe(line, linesz, 1, &rcd->byte_cnt, error))
120
78
    return NULL;
121
436k
  if (!fu_firmware_strparse_uint16_safe(line, linesz, 3, &addr16, error))
122
127
    return NULL;
123
435k
  rcd->addr = addr16;
124
435k
  if (!fu_firmware_strparse_uint8_safe(line, linesz, 7, &rcd->record_type, error))
125
59
    return NULL;
126
127
  /* position of checksum */
128
435k
  line_end = 9 + rcd->byte_cnt * 2;
129
435k
  if (line_end > (guint)rcd->buf->len) {
130
42
    g_set_error(error,
131
42
          FWUPD_ERROR,
132
42
          FWUPD_ERROR_INVALID_FILE,
133
42
          "line malformed, length: %u",
134
42
          line_end);
135
42
    return NULL;
136
42
  }
137
138
  /* verify checksum */
139
435k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
140
39.4k
    guint8 checksum = 0;
141
334k
    for (guint i = 1; i < line_end + 2; i += 2) {
142
295k
      guint8 data_tmp = 0;
143
295k
      if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &data_tmp, error))
144
534
        return NULL;
145
294k
      checksum += data_tmp;
146
294k
    }
147
38.8k
    if (checksum != 0) {
148
93
      g_set_error(error,
149
93
            FWUPD_ERROR,
150
93
            FWUPD_ERROR_INVALID_FILE,
151
93
            "invalid checksum (0x%02x)",
152
93
            checksum);
153
93
      return NULL;
154
93
    }
155
38.8k
  }
156
157
  /* add data */
158
790k
  for (guint i = 9; i < line_end; i += 2) {
159
355k
    guint8 tmp_c = 0;
160
355k
    if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &tmp_c, error))
161
15
      return NULL;
162
355k
    fu_byte_array_append_uint8(rcd->data, tmp_c);
163
355k
  }
164
435k
  return g_steal_pointer(&rcd);
165
435k
}
166
167
typedef struct {
168
  FuIhexFirmware *self;
169
  FuFirmwareParseFlags flags;
170
} FuIhexFirmwareTokenHelper;
171
172
static gboolean
173
fu_ihex_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
174
3.66M
{
175
3.66M
  FuIhexFirmwareTokenHelper *helper = (FuIhexFirmwareTokenHelper *)user_data;
176
3.66M
  FuIhexFirmwarePrivate *priv = GET_PRIVATE(helper->self);
177
3.66M
  g_autoptr(FuIhexFirmwareRecord) rcd = NULL;
178
179
  /* sanity check */
180
3.66M
  if (token_idx > FU_IHEX_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
3.66M
  g_strdelimit(token->str, "\r\x1a", '\0');
190
3.66M
  token->len = strlen(token->str);
191
192
  /* ignore blank lines */
193
3.66M
  if (token->len == 0)
194
3.23M
    return TRUE;
195
196
  /* ignore comments */
197
436k
  if (g_str_has_prefix(token->str, ";"))
198
555
    return TRUE;
199
200
  /* parse record */
201
436k
  rcd = fu_ihex_firmware_record_new(token_idx + 1, token->str, helper->flags, error);
202
436k
  if (rcd == NULL) {
203
1.11k
    g_prefix_error(error, "invalid line %u: ", token_idx + 1);
204
1.11k
    return FALSE;
205
1.11k
  }
206
435k
  g_ptr_array_add(priv->records, g_steal_pointer(&rcd));
207
435k
  return TRUE;
208
436k
}
209
210
static gboolean
211
fu_ihex_firmware_tokenize(FuFirmware *firmware,
212
        GInputStream *stream,
213
        FuFirmwareParseFlags flags,
214
        GError **error)
215
2.96k
{
216
2.96k
  FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware);
217
2.96k
  FuIhexFirmwareTokenHelper helper = {.self = self, .flags = flags};
218
2.96k
  return fu_strsplit_stream(stream, 0x0, "\n", fu_ihex_firmware_tokenize_cb, &helper, error);
219
2.96k
}
220
221
static gboolean
222
fu_ihex_firmware_parse(FuFirmware *firmware,
223
           GInputStream *stream,
224
           FuFirmwareParseFlags flags,
225
           GError **error)
226
1.84k
{
227
1.84k
  FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware);
228
1.84k
  FuIhexFirmwarePrivate *priv = GET_PRIVATE(self);
229
1.84k
  gboolean got_eof = FALSE;
230
1.84k
  gboolean got_sig = FALSE;
231
1.84k
  guint32 abs_addr = 0x0;
232
1.84k
  guint32 addr_last = 0x0;
233
1.84k
  guint32 img_addr = G_MAXUINT32;
234
1.84k
  guint32 seg_addr = 0x0;
235
1.84k
  g_autoptr(GBytes) img_bytes = NULL;
236
1.84k
  g_autoptr(GByteArray) buf = g_byte_array_new();
237
238
  /* parse records */
239
24.5k
  for (guint k = 0; k < priv->records->len; k++) {
240
23.0k
    FuIhexFirmwareRecord *rcd = g_ptr_array_index(priv->records, k);
241
23.0k
    guint16 addr16 = 0;
242
23.0k
    guint32 addr = rcd->addr + seg_addr + abs_addr;
243
23.0k
    guint32 len_hole;
244
245
    /* debug */
246
23.0k
    g_debug("%s:", fu_ihex_firmware_record_type_to_string(rcd->record_type));
247
23.0k
    g_debug("length:\t0x%02x", rcd->data->len);
248
23.0k
    g_debug("addr:\t0x%08x", addr);
249
250
    /* sanity check */
251
23.0k
    if (rcd->record_type != FU_IHEX_FIRMWARE_RECORD_TYPE_EOF && rcd->data->len == 0) {
252
294
      g_set_error(error,
253
294
            FWUPD_ERROR,
254
294
            FWUPD_ERROR_NOT_SUPPORTED,
255
294
            "record 0x%x had zero size",
256
294
            k);
257
294
      return FALSE;
258
294
    }
259
260
    /* process different record types */
261
22.8k
    switch (rcd->record_type) {
262
7.10k
    case FU_IHEX_FIRMWARE_RECORD_TYPE_DATA:
263
264
      /* does not make sense */
265
7.10k
      if (got_eof) {
266
3
        g_set_error_literal(error,
267
3
                FWUPD_ERROR,
268
3
                FWUPD_ERROR_INVALID_FILE,
269
3
                "cannot process data after EOF");
270
3
        return FALSE;
271
3
      }
272
7.10k
      if (rcd->data->len == 0) {
273
0
        g_set_error_literal(error,
274
0
                FWUPD_ERROR,
275
0
                FWUPD_ERROR_INVALID_FILE,
276
0
                "cannot parse invalid data");
277
0
        return FALSE;
278
0
      }
279
280
      /* base address for element */
281
7.10k
      if (img_addr == G_MAXUINT32)
282
603
        img_addr = addr;
283
284
      /* does not make sense */
285
7.10k
      if (addr < addr_last) {
286
25
        g_set_error(error,
287
25
              FWUPD_ERROR,
288
25
              FWUPD_ERROR_INVALID_FILE,
289
25
              "invalid address 0x%x, last was 0x%x on line %u",
290
25
              (guint)addr,
291
25
              (guint)addr_last,
292
25
              rcd->ln);
293
25
        return FALSE;
294
25
      }
295
296
      /* any holes in the hex record */
297
7.07k
      len_hole = addr - addr_last;
298
7.07k
      if (addr_last > 0 && len_hole > 0x100000) {
299
15
        g_set_error(error,
300
15
              FWUPD_ERROR,
301
15
              FWUPD_ERROR_INVALID_FILE,
302
15
              "hole of 0x%x bytes too large to fill on line %u",
303
15
              (guint)len_hole,
304
15
              rcd->ln);
305
15
        return FALSE;
306
15
      }
307
7.06k
      if (addr_last > 0x0 && len_hole > 1) {
308
166
        g_debug("filling address 0x%08x to 0x%08x on line %u",
309
166
          addr_last + 1,
310
166
          addr_last + len_hole - 1,
311
166
          rcd->ln);
312
44.0M
        for (guint j = 1; j < len_hole; j++)
313
44.0M
          fu_byte_array_append_uint8(buf, priv->padding_value);
314
166
      }
315
7.06k
      addr_last = addr + rcd->data->len - 1;
316
7.06k
      if (addr_last < addr) {
317
3
        g_set_error(error,
318
3
              FWUPD_ERROR,
319
3
              FWUPD_ERROR_INVALID_FILE,
320
3
              "overflow of address 0x%x on line %u",
321
3
              (guint)addr,
322
3
              rcd->ln);
323
3
        return FALSE;
324
3
      }
325
326
      /* write into buf */
327
7.06k
      g_byte_array_append(buf, rcd->data->data, rcd->data->len);
328
7.06k
      break;
329
256
    case FU_IHEX_FIRMWARE_RECORD_TYPE_EOF:
330
256
      if (got_eof) {
331
28
        g_set_error_literal(error,
332
28
                FWUPD_ERROR,
333
28
                FWUPD_ERROR_INVALID_FILE,
334
28
                "duplicate EOF, perhaps "
335
28
                "corrupt file");
336
28
        return FALSE;
337
28
      }
338
228
      got_eof = TRUE;
339
228
      break;
340
558
    case FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR:
341
558
      if (!fu_memread_uint16_safe(rcd->data->data,
342
558
                rcd->data->len,
343
558
                0x0,
344
558
                &addr16,
345
558
                G_BIG_ENDIAN,
346
558
                error))
347
4
        return FALSE;
348
554
      abs_addr = (guint32)addr16 << 16;
349
554
      g_debug("abs_addr:\t0x%02x on line %u", abs_addr, rcd->ln);
350
554
      break;
351
13.9k
    case FU_IHEX_FIRMWARE_RECORD_TYPE_START_LINEAR:
352
13.9k
      if (!fu_memread_uint32_safe(rcd->data->data,
353
13.9k
                rcd->data->len,
354
13.9k
                0x0,
355
13.9k
                &abs_addr,
356
13.9k
                G_BIG_ENDIAN,
357
13.9k
                error))
358
4
        return FALSE;
359
13.9k
      g_debug("abs_addr:\t0x%08x on line %u", abs_addr, rcd->ln);
360
13.9k
      break;
361
269
    case FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_SEGMENT:
362
269
      if (!fu_memread_uint16_safe(rcd->data->data,
363
269
                rcd->data->len,
364
269
                0x0,
365
269
                &addr16,
366
269
                G_BIG_ENDIAN,
367
269
                error))
368
7
        return FALSE;
369
      /* segment base address, so ~1Mb addressable */
370
262
      seg_addr = (guint32)addr16 * 16;
371
262
      g_debug("seg_addr:\t0x%08x on line %u", seg_addr, rcd->ln);
372
262
      break;
373
205
    case FU_IHEX_FIRMWARE_RECORD_TYPE_START_SEGMENT:
374
      /* initial content of the CS:IP registers */
375
205
      if (!fu_memread_uint32_safe(rcd->data->data,
376
205
                rcd->data->len,
377
205
                0x0,
378
205
                &seg_addr,
379
205
                G_BIG_ENDIAN,
380
205
                error))
381
5
        return FALSE;
382
200
      g_debug("seg_addr:\t0x%02x on line %u", seg_addr, rcd->ln);
383
200
      break;
384
160
    case FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE:
385
160
      if (got_sig) {
386
10
        g_set_error_literal(error,
387
10
                FWUPD_ERROR,
388
10
                FWUPD_ERROR_INVALID_FILE,
389
10
                "duplicate signature, perhaps "
390
10
                "corrupt file");
391
10
        return FALSE;
392
10
      }
393
150
      if (rcd->data->len > 0) {
394
150
        g_autoptr(GBytes) data_sig =
395
150
            g_bytes_new(rcd->data->data, rcd->data->len);
396
150
        g_autoptr(FuFirmware) img_sig =
397
150
            fu_firmware_new_from_bytes(data_sig);
398
150
        fu_firmware_set_id(img_sig, FU_FIRMWARE_ID_SIGNATURE);
399
150
        if (!fu_firmware_add_image(firmware, img_sig, error))
400
0
          return FALSE;
401
150
      }
402
150
      got_sig = TRUE;
403
150
      break;
404
252
    default:
405
      /* vendors sneak in nonstandard sections past the EOF */
406
252
      if (got_eof)
407
210
        break;
408
42
      g_set_error(error,
409
42
            FWUPD_ERROR,
410
42
            FWUPD_ERROR_INVALID_FILE,
411
42
            "invalid ihex record type %i on line %u",
412
42
            rcd->record_type,
413
42
            rcd->ln);
414
42
      return FALSE;
415
22.8k
    }
416
22.8k
  }
417
418
  /* no EOF */
419
1.40k
  if (!got_eof) {
420
1.21k
    g_set_error_literal(error,
421
1.21k
            FWUPD_ERROR,
422
1.21k
            FWUPD_ERROR_INVALID_FILE,
423
1.21k
            "no EOF, perhaps truncated file");
424
1.21k
    return FALSE;
425
1.21k
  }
426
427
  /* add single image */
428
186
  img_bytes = g_bytes_new(buf->data, buf->len);
429
186
  if (img_addr != G_MAXUINT32)
430
130
    fu_firmware_set_addr(firmware, img_addr);
431
186
  fu_firmware_set_bytes(firmware, img_bytes);
432
186
  return TRUE;
433
1.40k
}
434
435
static void
436
fu_ihex_firmware_emit_chunk(GString *str,
437
          guint16 address,
438
          guint8 record_type,
439
          const guint8 *data,
440
          gsize sz)
441
774k
{
442
774k
  guint8 checksum = 0x00;
443
774k
  g_string_append_printf(str, ":%02X%04X%02X", (guint)sz, (guint)address, (guint)record_type);
444
13.1M
  for (gsize j = 0; j < sz; j++)
445
12.3M
    g_string_append_printf(str, "%02X", data[j]);
446
774k
  checksum = (guint8)sz;
447
774k
  checksum += (guint8)((address & 0xff00) >> 8);
448
774k
  checksum += (guint8)(address & 0xff);
449
774k
  checksum += record_type;
450
13.1M
  for (gsize j = 0; j < sz; j++)
451
12.3M
    checksum += data[j];
452
774k
  g_string_append_printf(str, "%02X\n", (guint)(((~checksum) + 0x01) & 0xff));
453
774k
}
454
455
static gboolean
456
fu_ihex_firmware_image_to_string(GBytes *bytes,
457
         guint32 addr,
458
         guint8 record_type,
459
         GString *str,
460
         GError **error)
461
214
{
462
214
  const guint8 *data;
463
214
  const guint chunk_size = 16;
464
214
  gsize len;
465
214
  guint32 address_offset_last = 0x0;
466
467
  /* get number of chunks */
468
214
  data = g_bytes_get_data(bytes, &len);
469
774k
  for (gsize i = 0; i < len; i += chunk_size) {
470
774k
    guint32 address_tmp = addr + i;
471
774k
    guint32 address_offset = (address_tmp >> 16) & 0xffff;
472
774k
    gsize chunk_len = MIN(len - i, 16);
473
474
    /* need to offset */
475
774k
    if (address_offset != address_offset_last) {
476
231
      guint8 buf[2]; /* nocheck:zero-init */
477
231
      fu_memwrite_uint16(buf, address_offset, G_BIG_ENDIAN);
478
231
      fu_ihex_firmware_emit_chunk(str,
479
231
                0x0,
480
231
                FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR,
481
231
                buf,
482
231
                2);
483
231
      address_offset_last = address_offset;
484
231
    }
485
774k
    address_tmp &= 0xffff;
486
774k
    fu_ihex_firmware_emit_chunk(str, address_tmp, record_type, data + i, chunk_len);
487
774k
  }
488
214
  return TRUE;
489
214
}
490
491
static GByteArray *
492
fu_ihex_firmware_write(FuFirmware *firmware, GError **error)
493
186
{
494
186
  g_autoptr(GByteArray) buf = g_byte_array_new();
495
186
  g_autoptr(GBytes) fw = NULL;
496
186
  g_autoptr(FuFirmware) img_sig = NULL;
497
186
  g_autoptr(GString) str = g_string_new("");
498
499
  /* payload */
500
186
  fw = fu_firmware_get_bytes_with_patches(firmware, error);
501
186
  if (fw == NULL)
502
0
    return NULL;
503
186
  if (!fu_ihex_firmware_image_to_string(fw,
504
186
                fu_firmware_get_addr(firmware),
505
186
                FU_IHEX_FIRMWARE_RECORD_TYPE_DATA,
506
186
                str,
507
186
                error))
508
0
    return NULL;
509
510
  /* signature */
511
186
  img_sig = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_SIGNATURE, NULL);
512
186
  if (img_sig != NULL) {
513
28
    g_autoptr(GBytes) img_fw = fu_firmware_get_bytes(img_sig, error);
514
28
    if (img_fw == NULL)
515
0
      return NULL;
516
28
    if (!fu_ihex_firmware_image_to_string(img_fw,
517
28
                  0,
518
28
                  FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE,
519
28
                  str,
520
28
                  error))
521
0
      return NULL;
522
28
  }
523
524
  /* add EOF */
525
186
  fu_ihex_firmware_emit_chunk(str, 0x0, FU_IHEX_FIRMWARE_RECORD_TYPE_EOF, NULL, 0);
526
186
  g_byte_array_append(buf, (const guint8 *)str->str, str->len);
527
186
  return g_steal_pointer(&buf);
528
186
}
529
530
static void
531
fu_ihex_firmware_finalize(GObject *object)
532
2.96k
{
533
2.96k
  FuIhexFirmware *self = FU_IHEX_FIRMWARE(object);
534
2.96k
  FuIhexFirmwarePrivate *priv = GET_PRIVATE(self);
535
2.96k
  g_ptr_array_unref(priv->records);
536
2.96k
  G_OBJECT_CLASS(fu_ihex_firmware_parent_class)->finalize(object);
537
2.96k
}
538
539
static void
540
fu_ihex_firmware_init(FuIhexFirmware *self)
541
2.96k
{
542
2.96k
  FuIhexFirmwarePrivate *priv = GET_PRIVATE(self);
543
2.96k
  priv->padding_value = 0x00; /* chosen as we can't write 0xffff to PIC14 */
544
2.96k
  priv->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ihex_firmware_record_free);
545
2.96k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
546
2.96k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE);
547
2.96k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 10);
548
2.96k
}
549
550
static void
551
fu_ihex_firmware_class_init(FuIhexFirmwareClass *klass)
552
1
{
553
1
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
554
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
555
1
  object_class->finalize = fu_ihex_firmware_finalize;
556
1
  firmware_class->parse = fu_ihex_firmware_parse;
557
1
  firmware_class->tokenize = fu_ihex_firmware_tokenize;
558
1
  firmware_class->write = fu_ihex_firmware_write;
559
1
}
560
561
/**
562
 * fu_ihex_firmware_new:
563
 *
564
 * Creates a new #FuFirmware of sub type Ihex
565
 *
566
 * Since: 1.3.1
567
 **/
568
FuFirmware *
569
fu_ihex_firmware_new(void)
570
0
{
571
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_IHEX_FIRMWARE, NULL));
572
0
}