/src/fwupd/libfwupdplugin/fu-ihex-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 | 663k | #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 | | G_DEFINE_TYPE_WITH_PRIVATE(FuIhexFirmware, fu_ihex_firmware, FU_TYPE_FIRMWARE) |
33 | 1.48M | #define GET_PRIVATE(o) (fu_ihex_firmware_get_instance_private(o)) |
34 | | |
35 | 1.48M | #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 | 620k | { |
81 | 620k | g_string_free(rcd->buf, TRUE); |
82 | 620k | g_byte_array_unref(rcd->data); |
83 | 620k | g_free(rcd); |
84 | 620k | } |
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 | 620k | { |
91 | 620k | g_autoptr(FuIhexFirmwareRecord) rcd = NULL; |
92 | 620k | gsize linesz = strlen(line); |
93 | 620k | guint line_end; |
94 | 620k | guint16 addr16 = 0; |
95 | | |
96 | | /* check starting token */ |
97 | 620k | if (line[0] != ':') { |
98 | 163 | g_autofree gchar *strsafe = fu_strsafe(line, 5); |
99 | 163 | if (strsafe != NULL) { |
100 | 76 | g_set_error(error, |
101 | 76 | FWUPD_ERROR, |
102 | 76 | FWUPD_ERROR_INVALID_FILE, |
103 | 76 | "invalid starting token: %s", |
104 | 76 | strsafe); |
105 | 76 | return NULL; |
106 | 76 | } |
107 | 87 | g_set_error_literal(error, |
108 | 87 | FWUPD_ERROR, |
109 | 87 | FWUPD_ERROR_INVALID_FILE, |
110 | 87 | "invalid starting token"); |
111 | 87 | return NULL; |
112 | 163 | } |
113 | | |
114 | | /* length, 16-bit address, type */ |
115 | 620k | rcd = g_new0(FuIhexFirmwareRecord, 1); |
116 | 620k | rcd->ln = ln; |
117 | 620k | rcd->data = g_byte_array_new(); |
118 | 620k | rcd->buf = g_string_new(line); |
119 | 620k | if (!fu_firmware_strparse_uint8_safe(line, linesz, 1, &rcd->byte_cnt, error)) |
120 | 100 | return NULL; |
121 | 620k | if (!fu_firmware_strparse_uint16_safe(line, linesz, 3, &addr16, error)) |
122 | 122 | return NULL; |
123 | 620k | rcd->addr = addr16; |
124 | 620k | if (!fu_firmware_strparse_uint8_safe(line, linesz, 7, &rcd->record_type, error)) |
125 | 60 | return NULL; |
126 | | |
127 | | /* position of checksum */ |
128 | 620k | line_end = 9 + rcd->byte_cnt * 2; |
129 | 620k | if (line_end > (guint)rcd->buf->len) { |
130 | 46 | g_set_error(error, |
131 | 46 | FWUPD_ERROR, |
132 | 46 | FWUPD_ERROR_INVALID_FILE, |
133 | 46 | "line malformed, length: %u", |
134 | 46 | line_end); |
135 | 46 | return NULL; |
136 | 46 | } |
137 | | |
138 | | /* verify checksum */ |
139 | 619k | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
140 | 52.9k | guint8 checksum = 0; |
141 | 432k | for (guint i = 1; i < line_end + 2; i += 2) { |
142 | 380k | guint8 data_tmp = 0; |
143 | 380k | if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &data_tmp, error)) |
144 | 571 | return NULL; |
145 | 379k | checksum += data_tmp; |
146 | 379k | } |
147 | 52.3k | if (checksum != 0) { |
148 | 106 | g_set_error(error, |
149 | 106 | FWUPD_ERROR, |
150 | 106 | FWUPD_ERROR_INVALID_FILE, |
151 | 106 | "invalid checksum (0x%02x)", |
152 | 106 | checksum); |
153 | 106 | return NULL; |
154 | 106 | } |
155 | 52.3k | } |
156 | | |
157 | | /* add data */ |
158 | 1.09M | for (guint i = 9; i < line_end; i += 2) { |
159 | 476k | guint8 tmp_c = 0; |
160 | 476k | if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &tmp_c, error)) |
161 | 9 | return NULL; |
162 | 476k | fu_byte_array_append_uint8(rcd->data, tmp_c); |
163 | 476k | } |
164 | 619k | return g_steal_pointer(&rcd); |
165 | 619k | } |
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 | 1.48M | { |
175 | 1.48M | FuIhexFirmwareTokenHelper *helper = (FuIhexFirmwareTokenHelper *)user_data; |
176 | 1.48M | FuIhexFirmwarePrivate *priv = GET_PRIVATE(helper->self); |
177 | 1.48M | g_autoptr(FuIhexFirmwareRecord) rcd = NULL; |
178 | | |
179 | | /* sanity check */ |
180 | 1.48M | 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 | 1.48M | g_strdelimit(token->str, "\r\x1a", '\0'); |
190 | 1.48M | token->len = strlen(token->str); |
191 | | |
192 | | /* ignore blank lines */ |
193 | 1.48M | if (token->len == 0) |
194 | 859k | return TRUE; |
195 | | |
196 | | /* ignore comments */ |
197 | 621k | if (g_str_has_prefix(token->str, ";")) |
198 | 735 | return TRUE; |
199 | | |
200 | | /* parse record */ |
201 | 620k | rcd = fu_ihex_firmware_record_new(token_idx + 1, token->str, helper->flags, error); |
202 | 620k | if (rcd == NULL) { |
203 | 1.17k | g_prefix_error(error, "invalid line %u: ", token_idx + 1); |
204 | 1.17k | return FALSE; |
205 | 1.17k | } |
206 | 619k | g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); |
207 | 619k | return TRUE; |
208 | 620k | } |
209 | | |
210 | | static gboolean |
211 | | fu_ihex_firmware_tokenize(FuFirmware *firmware, |
212 | | GInputStream *stream, |
213 | | FuFirmwareParseFlags flags, |
214 | | GError **error) |
215 | 3.03k | { |
216 | 3.03k | FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware); |
217 | 3.03k | FuIhexFirmwareTokenHelper helper = {.self = self, .flags = flags}; |
218 | 3.03k | return fu_strsplit_stream(stream, 0x0, "\n", fu_ihex_firmware_tokenize_cb, &helper, error); |
219 | 3.03k | } |
220 | | |
221 | | static gboolean |
222 | | fu_ihex_firmware_parse(FuFirmware *firmware, |
223 | | GInputStream *stream, |
224 | | FuFirmwareParseFlags flags, |
225 | | GError **error) |
226 | 1.85k | { |
227 | 1.85k | FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware); |
228 | 1.85k | FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); |
229 | 1.85k | gboolean got_eof = FALSE; |
230 | 1.85k | gboolean got_sig = FALSE; |
231 | 1.85k | guint32 abs_addr = 0x0; |
232 | 1.85k | guint32 addr_last = 0x0; |
233 | 1.85k | guint32 img_addr = G_MAXUINT32; |
234 | 1.85k | guint32 seg_addr = 0x0; |
235 | 1.85k | g_autoptr(GBytes) img_bytes = NULL; |
236 | 1.85k | g_autoptr(GByteArray) buf = g_byte_array_new(); |
237 | | |
238 | | /* parse records */ |
239 | 217k | for (guint k = 0; k < priv->records->len; k++) { |
240 | 216k | FuIhexFirmwareRecord *rcd = g_ptr_array_index(priv->records, k); |
241 | 216k | guint16 addr16 = 0; |
242 | 216k | guint32 addr = rcd->addr + seg_addr + abs_addr; |
243 | 216k | guint32 len_hole; |
244 | | |
245 | | /* debug */ |
246 | 216k | g_debug("%s:", fu_ihex_firmware_record_type_to_string(rcd->record_type)); |
247 | 216k | g_debug("length:\t0x%02x", rcd->data->len); |
248 | 216k | g_debug("addr:\t0x%08x", addr); |
249 | | |
250 | | /* sanity check */ |
251 | 216k | if (rcd->record_type != FU_IHEX_FIRMWARE_RECORD_TYPE_EOF && rcd->data->len == 0) { |
252 | 309 | g_set_error(error, |
253 | 309 | FWUPD_ERROR, |
254 | 309 | FWUPD_ERROR_NOT_SUPPORTED, |
255 | 309 | "record 0x%x had zero size", |
256 | 309 | k); |
257 | 309 | return FALSE; |
258 | 309 | } |
259 | | |
260 | | /* process different record types */ |
261 | 216k | switch (rcd->record_type) { |
262 | 201k | case FU_IHEX_FIRMWARE_RECORD_TYPE_DATA: |
263 | | |
264 | | /* does not make sense */ |
265 | 201k | if (got_eof) { |
266 | 5 | g_set_error_literal(error, |
267 | 5 | FWUPD_ERROR, |
268 | 5 | FWUPD_ERROR_INVALID_FILE, |
269 | 5 | "cannot process data after EOF"); |
270 | 5 | return FALSE; |
271 | 5 | } |
272 | 201k | 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 | 201k | if (img_addr == G_MAXUINT32) |
282 | 643 | img_addr = addr; |
283 | | |
284 | | /* does not make sense */ |
285 | 201k | if (addr < addr_last) { |
286 | 23 | g_set_error(error, |
287 | 23 | FWUPD_ERROR, |
288 | 23 | FWUPD_ERROR_INVALID_FILE, |
289 | 23 | "invalid address 0x%x, last was 0x%x on line %u", |
290 | 23 | (guint)addr, |
291 | 23 | (guint)addr_last, |
292 | 23 | rcd->ln); |
293 | 23 | return FALSE; |
294 | 23 | } |
295 | | |
296 | | /* any holes in the hex record */ |
297 | 201k | len_hole = addr - addr_last; |
298 | 201k | if (addr_last > 0 && len_hole > 0x100000) { |
299 | 20 | g_set_error(error, |
300 | 20 | FWUPD_ERROR, |
301 | 20 | FWUPD_ERROR_INVALID_FILE, |
302 | 20 | "hole of 0x%x bytes too large to fill on line %u", |
303 | 20 | (guint)len_hole, |
304 | 20 | rcd->ln); |
305 | 20 | return FALSE; |
306 | 20 | } |
307 | 201k | if (addr_last > 0x0 && len_hole > 1) { |
308 | 173 | g_debug("filling address 0x%08x to 0x%08x on line %u", |
309 | 173 | addr_last + 1, |
310 | 173 | addr_last + len_hole - 1, |
311 | 173 | rcd->ln); |
312 | 43.3M | for (guint j = 1; j < len_hole; j++) |
313 | 43.3M | fu_byte_array_append_uint8(buf, priv->padding_value); |
314 | 173 | } |
315 | 201k | addr_last = addr + rcd->data->len - 1; |
316 | 201k | 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 | 201k | g_byte_array_append(buf, rcd->data->data, rcd->data->len); |
328 | 201k | break; |
329 | 227 | case FU_IHEX_FIRMWARE_RECORD_TYPE_EOF: |
330 | 227 | if (got_eof) { |
331 | 18 | g_set_error_literal(error, |
332 | 18 | FWUPD_ERROR, |
333 | 18 | FWUPD_ERROR_INVALID_FILE, |
334 | 18 | "duplicate EOF, perhaps " |
335 | 18 | "corrupt file"); |
336 | 18 | return FALSE; |
337 | 18 | } |
338 | 209 | got_eof = TRUE; |
339 | 209 | break; |
340 | 517 | case FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR: |
341 | 517 | if (!fu_memread_uint16_safe(rcd->data->data, |
342 | 517 | rcd->data->len, |
343 | 517 | 0x0, |
344 | 517 | &addr16, |
345 | 517 | G_BIG_ENDIAN, |
346 | 517 | error)) |
347 | 4 | return FALSE; |
348 | 513 | abs_addr = (guint32)addr16 << 16; |
349 | 513 | g_debug("abs_addr:\t0x%02x on line %u", abs_addr, rcd->ln); |
350 | 513 | break; |
351 | 12.4k | case FU_IHEX_FIRMWARE_RECORD_TYPE_START_LINEAR: |
352 | 12.4k | if (!fu_memread_uint32_safe(rcd->data->data, |
353 | 12.4k | rcd->data->len, |
354 | 12.4k | 0x0, |
355 | 12.4k | &abs_addr, |
356 | 12.4k | G_BIG_ENDIAN, |
357 | 12.4k | error)) |
358 | 5 | return FALSE; |
359 | 12.4k | g_debug("abs_addr:\t0x%08x on line %u", abs_addr, rcd->ln); |
360 | 12.4k | break; |
361 | 350 | case FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_SEGMENT: |
362 | 350 | if (!fu_memread_uint16_safe(rcd->data->data, |
363 | 350 | rcd->data->len, |
364 | 350 | 0x0, |
365 | 350 | &addr16, |
366 | 350 | G_BIG_ENDIAN, |
367 | 350 | error)) |
368 | 8 | return FALSE; |
369 | | /* segment base address, so ~1Mb addressable */ |
370 | 342 | seg_addr = (guint32)addr16 * 16; |
371 | 342 | g_debug("seg_addr:\t0x%08x on line %u", seg_addr, rcd->ln); |
372 | 342 | break; |
373 | 274 | case FU_IHEX_FIRMWARE_RECORD_TYPE_START_SEGMENT: |
374 | | /* initial content of the CS:IP registers */ |
375 | 274 | if (!fu_memread_uint32_safe(rcd->data->data, |
376 | 274 | rcd->data->len, |
377 | 274 | 0x0, |
378 | 274 | &seg_addr, |
379 | 274 | G_BIG_ENDIAN, |
380 | 274 | error)) |
381 | 5 | return FALSE; |
382 | 269 | g_debug("seg_addr:\t0x%02x on line %u", seg_addr, rcd->ln); |
383 | 269 | break; |
384 | 155 | case FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE: |
385 | 155 | if (got_sig) { |
386 | 9 | g_set_error_literal(error, |
387 | 9 | FWUPD_ERROR, |
388 | 9 | FWUPD_ERROR_INVALID_FILE, |
389 | 9 | "duplicate signature, perhaps " |
390 | 9 | "corrupt file"); |
391 | 9 | return FALSE; |
392 | 9 | } |
393 | 146 | if (rcd->data->len > 0) { |
394 | 146 | g_autoptr(GBytes) data_sig = |
395 | 146 | g_bytes_new(rcd->data->data, rcd->data->len); |
396 | 146 | g_autoptr(FuFirmware) img_sig = |
397 | 146 | fu_firmware_new_from_bytes(data_sig); |
398 | 146 | fu_firmware_set_id(img_sig, FU_FIRMWARE_ID_SIGNATURE); |
399 | 146 | if (!fu_firmware_add_image_full(firmware, img_sig, error)) |
400 | 0 | return FALSE; |
401 | 146 | } |
402 | 146 | got_sig = TRUE; |
403 | 146 | break; |
404 | 269 | default: |
405 | | /* vendors sneak in nonstandard sections past the EOF */ |
406 | 269 | if (got_eof) |
407 | 227 | 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 | 216k | } |
416 | 216k | } |
417 | | |
418 | | /* no EOF */ |
419 | 1.40k | if (!got_eof) { |
420 | 1.22k | g_set_error_literal(error, |
421 | 1.22k | FWUPD_ERROR, |
422 | 1.22k | FWUPD_ERROR_INVALID_FILE, |
423 | 1.22k | "no EOF, perhaps truncated file"); |
424 | 1.22k | return FALSE; |
425 | 1.22k | } |
426 | | |
427 | | /* add single image */ |
428 | 180 | img_bytes = g_bytes_new(buf->data, buf->len); |
429 | 180 | if (img_addr != G_MAXUINT32) |
430 | 133 | fu_firmware_set_addr(firmware, img_addr); |
431 | 180 | fu_firmware_set_bytes(firmware, img_bytes); |
432 | 180 | 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 | 722k | { |
442 | 722k | guint8 checksum = 0x00; |
443 | 722k | g_string_append_printf(str, ":%02X%04X%02X", (guint)sz, (guint)address, (guint)record_type); |
444 | 12.2M | for (gsize j = 0; j < sz; j++) |
445 | 11.5M | g_string_append_printf(str, "%02X", data[j]); |
446 | 722k | checksum = (guint8)sz; |
447 | 722k | checksum += (guint8)((address & 0xff00) >> 8); |
448 | 722k | checksum += (guint8)(address & 0xff); |
449 | 722k | checksum += record_type; |
450 | 12.2M | for (gsize j = 0; j < sz; j++) |
451 | 11.5M | checksum += data[j]; |
452 | 722k | g_string_append_printf(str, "%02X\n", (guint)(((~checksum) + 0x01) & 0xff)); |
453 | 722k | } |
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 | 200 | { |
462 | 200 | const guint8 *data; |
463 | 200 | const guint chunk_size = 16; |
464 | 200 | gsize len; |
465 | 200 | guint32 address_offset_last = 0x0; |
466 | | |
467 | | /* get number of chunks */ |
468 | 200 | data = g_bytes_get_data(bytes, &len); |
469 | 721k | for (gsize i = 0; i < len; i += chunk_size) { |
470 | 721k | guint32 address_tmp = addr + i; |
471 | 721k | guint32 address_offset = (address_tmp >> 16) & 0xffff; |
472 | 721k | gsize chunk_len = MIN(len - i, 16); |
473 | | |
474 | | /* need to offset */ |
475 | 721k | if (address_offset != address_offset_last) { |
476 | 217 | guint8 buf[2]; /* nocheck:zero-init */ |
477 | 217 | fu_memwrite_uint16(buf, address_offset, G_BIG_ENDIAN); |
478 | 217 | fu_ihex_firmware_emit_chunk(str, |
479 | 217 | 0x0, |
480 | 217 | FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR, |
481 | 217 | buf, |
482 | 217 | 2); |
483 | 217 | address_offset_last = address_offset; |
484 | 217 | } |
485 | 721k | address_tmp &= 0xffff; |
486 | 721k | fu_ihex_firmware_emit_chunk(str, address_tmp, record_type, data + i, chunk_len); |
487 | 721k | } |
488 | 200 | return TRUE; |
489 | 200 | } |
490 | | |
491 | | static GByteArray * |
492 | | fu_ihex_firmware_write(FuFirmware *firmware, GError **error) |
493 | 180 | { |
494 | 180 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
495 | 180 | g_autoptr(GBytes) fw = NULL; |
496 | 180 | g_autoptr(FuFirmware) img_sig = NULL; |
497 | 180 | g_autoptr(GString) str = g_string_new(""); |
498 | | |
499 | | /* payload */ |
500 | 180 | fw = fu_firmware_get_bytes_with_patches(firmware, error); |
501 | 180 | if (fw == NULL) |
502 | 0 | return NULL; |
503 | 180 | if (!fu_ihex_firmware_image_to_string(fw, |
504 | 180 | fu_firmware_get_addr(firmware), |
505 | 180 | FU_IHEX_FIRMWARE_RECORD_TYPE_DATA, |
506 | 180 | str, |
507 | 180 | error)) |
508 | 0 | return NULL; |
509 | | |
510 | | /* signature */ |
511 | 180 | img_sig = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_SIGNATURE, NULL); |
512 | 180 | if (img_sig != NULL) { |
513 | 20 | g_autoptr(GBytes) img_fw = fu_firmware_get_bytes(img_sig, error); |
514 | 20 | if (img_fw == NULL) |
515 | 0 | return NULL; |
516 | 20 | if (!fu_ihex_firmware_image_to_string(img_fw, |
517 | 20 | 0, |
518 | 20 | FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE, |
519 | 20 | str, |
520 | 20 | error)) |
521 | 0 | return NULL; |
522 | 20 | } |
523 | | |
524 | | /* add EOF */ |
525 | 180 | fu_ihex_firmware_emit_chunk(str, 0x0, FU_IHEX_FIRMWARE_RECORD_TYPE_EOF, NULL, 0); |
526 | 180 | g_byte_array_append(buf, (const guint8 *)str->str, str->len); |
527 | 180 | return g_steal_pointer(&buf); |
528 | 180 | } |
529 | | |
530 | | static void |
531 | | fu_ihex_firmware_finalize(GObject *object) |
532 | 3.03k | { |
533 | 3.03k | FuIhexFirmware *self = FU_IHEX_FIRMWARE(object); |
534 | 3.03k | FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); |
535 | 3.03k | g_ptr_array_unref(priv->records); |
536 | 3.03k | G_OBJECT_CLASS(fu_ihex_firmware_parent_class)->finalize(object); |
537 | 3.03k | } |
538 | | |
539 | | static void |
540 | | fu_ihex_firmware_init(FuIhexFirmware *self) |
541 | 3.03k | { |
542 | 3.03k | FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); |
543 | 3.03k | priv->padding_value = 0x00; /* chosen as we can't write 0xffff to PIC14 */ |
544 | 3.03k | priv->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ihex_firmware_record_free); |
545 | 3.03k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
546 | 3.03k | fu_firmware_set_images_max(FU_FIRMWARE(self), 10); |
547 | 3.03k | } |
548 | | |
549 | | static void |
550 | | fu_ihex_firmware_class_init(FuIhexFirmwareClass *klass) |
551 | 1 | { |
552 | 1 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
553 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
554 | 1 | object_class->finalize = fu_ihex_firmware_finalize; |
555 | 1 | firmware_class->parse = fu_ihex_firmware_parse; |
556 | 1 | firmware_class->tokenize = fu_ihex_firmware_tokenize; |
557 | 1 | firmware_class->write = fu_ihex_firmware_write; |
558 | 1 | } |
559 | | |
560 | | /** |
561 | | * fu_ihex_firmware_new: |
562 | | * |
563 | | * Creates a new #FuFirmware of sub type Ihex |
564 | | * |
565 | | * Since: 1.3.1 |
566 | | **/ |
567 | | FuFirmware * |
568 | | fu_ihex_firmware_new(void) |
569 | 0 | { |
570 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_IHEX_FIRMWARE, NULL)); |
571 | 0 | } |