/src/fwupd/libfwupdplugin/fu-dfu-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 | 0 | #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-common.h" |
15 | | #include "fu-crc.h" |
16 | | #include "fu-dfu-firmware-private.h" |
17 | | #include "fu-dfu-firmware-struct.h" |
18 | | #include "fu-input-stream.h" |
19 | | |
20 | | /** |
21 | | * FuDfuFirmware: |
22 | | * |
23 | | * A DFU firmware image. |
24 | | * |
25 | | * See also: [class@FuFirmware] |
26 | | */ |
27 | | |
28 | | typedef struct { |
29 | | guint16 vid; |
30 | | guint16 pid; |
31 | | guint16 release; |
32 | | guint16 dfu_version; |
33 | | guint8 footer_len; |
34 | | } FuDfuFirmwarePrivate; |
35 | | |
36 | 2.62k | G_DEFINE_TYPE_WITH_PRIVATE(FuDfuFirmware, fu_dfu_firmware, FU_TYPE_FIRMWARE) |
37 | 2.62k | #define GET_PRIVATE(o) (fu_dfu_firmware_get_instance_private(o)) |
38 | | |
39 | | static void |
40 | | fu_dfu_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
41 | 0 | { |
42 | 0 | FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); |
43 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
44 | 0 | fu_xmlb_builder_insert_kx(bn, "vendor", priv->vid); |
45 | 0 | fu_xmlb_builder_insert_kx(bn, "product", priv->pid); |
46 | 0 | fu_xmlb_builder_insert_kx(bn, "release", priv->release); |
47 | 0 | fu_xmlb_builder_insert_kx(bn, "dfu_version", priv->dfu_version); |
48 | 0 | } |
49 | | |
50 | | /* private */ |
51 | | guint8 |
52 | | fu_dfu_firmware_get_footer_len(FuDfuFirmware *self) |
53 | 616 | { |
54 | 616 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
55 | 616 | g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); |
56 | 616 | return priv->footer_len; |
57 | 616 | } |
58 | | |
59 | | /** |
60 | | * fu_dfu_firmware_get_vid: |
61 | | * @self: a #FuDfuFirmware |
62 | | * |
63 | | * Gets the vendor ID, or 0xffff for no restriction. |
64 | | * |
65 | | * Returns: integer |
66 | | * |
67 | | * Since: 1.3.3 |
68 | | **/ |
69 | | guint16 |
70 | | fu_dfu_firmware_get_vid(FuDfuFirmware *self) |
71 | 0 | { |
72 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
73 | 0 | g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); |
74 | 0 | return priv->vid; |
75 | 0 | } |
76 | | |
77 | | /** |
78 | | * fu_dfu_firmware_get_pid: |
79 | | * @self: a #FuDfuFirmware |
80 | | * |
81 | | * Gets the product ID, or 0xffff for no restriction. |
82 | | * |
83 | | * Returns: integer |
84 | | * |
85 | | * Since: 1.3.3 |
86 | | **/ |
87 | | guint16 |
88 | | fu_dfu_firmware_get_pid(FuDfuFirmware *self) |
89 | 0 | { |
90 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
91 | 0 | g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); |
92 | 0 | return priv->pid; |
93 | 0 | } |
94 | | |
95 | | /** |
96 | | * fu_dfu_firmware_get_release: |
97 | | * @self: a #FuDfuFirmware |
98 | | * |
99 | | * Gets the device ID, or 0xffff for no restriction. |
100 | | * |
101 | | * Returns: integer |
102 | | * |
103 | | * Since: 1.3.3 |
104 | | **/ |
105 | | guint16 |
106 | | fu_dfu_firmware_get_release(FuDfuFirmware *self) |
107 | 0 | { |
108 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
109 | 0 | g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); |
110 | 0 | return priv->release; |
111 | 0 | } |
112 | | |
113 | | /** |
114 | | * fu_dfu_firmware_get_version: |
115 | | * @self: a #FuDfuFirmware |
116 | | * |
117 | | * Gets the file format version with is 0x0100 by default. |
118 | | * |
119 | | * Returns: integer |
120 | | * |
121 | | * Since: 1.3.3 |
122 | | **/ |
123 | | guint16 |
124 | | fu_dfu_firmware_get_version(FuDfuFirmware *self) |
125 | 0 | { |
126 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
127 | 0 | g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); |
128 | 0 | return priv->dfu_version; |
129 | 0 | } |
130 | | |
131 | | /** |
132 | | * fu_dfu_firmware_set_vid: |
133 | | * @self: a #FuDfuFirmware |
134 | | * @vid: vendor ID, or 0xffff if the firmware should match any vendor |
135 | | * |
136 | | * Sets the vendor ID. |
137 | | * |
138 | | * Since: 1.3.3 |
139 | | **/ |
140 | | void |
141 | | fu_dfu_firmware_set_vid(FuDfuFirmware *self, guint16 vid) |
142 | 0 | { |
143 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
144 | 0 | g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); |
145 | 0 | priv->vid = vid; |
146 | 0 | } |
147 | | |
148 | | /** |
149 | | * fu_dfu_firmware_set_pid: |
150 | | * @self: a #FuDfuFirmware |
151 | | * @pid: product ID, or 0xffff if the firmware should match any product |
152 | | * |
153 | | * Sets the product ID. |
154 | | * |
155 | | * Since: 1.3.3 |
156 | | **/ |
157 | | void |
158 | | fu_dfu_firmware_set_pid(FuDfuFirmware *self, guint16 pid) |
159 | 0 | { |
160 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
161 | 0 | g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); |
162 | 0 | priv->pid = pid; |
163 | 0 | } |
164 | | |
165 | | /** |
166 | | * fu_dfu_firmware_set_release: |
167 | | * @self: a #FuDfuFirmware |
168 | | * @release: release, or 0xffff if the firmware should match any release |
169 | | * |
170 | | * Sets the release for the dfu firmware. |
171 | | * |
172 | | * Since: 1.3.3 |
173 | | **/ |
174 | | void |
175 | | fu_dfu_firmware_set_release(FuDfuFirmware *self, guint16 release) |
176 | 0 | { |
177 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
178 | 0 | g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); |
179 | 0 | priv->release = release; |
180 | 0 | } |
181 | | |
182 | | /** |
183 | | * fu_dfu_firmware_set_version: |
184 | | * @self: a #FuDfuFirmware |
185 | | * @version: integer |
186 | | * |
187 | | * Sets the file format version. |
188 | | * |
189 | | * Since: 1.3.3 |
190 | | **/ |
191 | | void |
192 | | fu_dfu_firmware_set_version(FuDfuFirmware *self, guint16 version) |
193 | 640 | { |
194 | 640 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
195 | 640 | g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); |
196 | 640 | priv->dfu_version = version; |
197 | 640 | } |
198 | | |
199 | | static gboolean |
200 | | fu_dfu_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error) |
201 | 0 | { |
202 | 0 | gsize streamsz = 0; |
203 | 0 | if (!fu_input_stream_size(stream, &streamsz, error)) |
204 | 0 | return FALSE; |
205 | 0 | if (streamsz < FU_STRUCT_DFU_FTR_SIZE) { |
206 | 0 | g_set_error_literal(error, |
207 | 0 | FWUPD_ERROR, |
208 | 0 | FWUPD_ERROR_INVALID_FILE, |
209 | 0 | "stream was too small"); |
210 | 0 | return FALSE; |
211 | 0 | } |
212 | 0 | return fu_struct_dfu_ftr_validate_stream(stream, streamsz - FU_STRUCT_DFU_FTR_SIZE, error); |
213 | 0 | } |
214 | | |
215 | | gboolean |
216 | | fu_dfu_firmware_parse_footer(FuDfuFirmware *self, |
217 | | GInputStream *stream, |
218 | | FuFirmwareParseFlags flags, |
219 | | GError **error) |
220 | 569 | { |
221 | 569 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
222 | 569 | gsize bufsz; |
223 | 569 | const guint8 *buf; |
224 | 569 | g_autoptr(FuStructDfuFtr) st = NULL; |
225 | 569 | g_autoptr(GBytes) fw = NULL; |
226 | | |
227 | 569 | fw = fu_input_stream_read_bytes(stream, 0, G_MAXSIZE, NULL, error); |
228 | 569 | if (fw == NULL) |
229 | 0 | return FALSE; |
230 | 569 | buf = g_bytes_get_data(fw, &bufsz); |
231 | | |
232 | | /* parse */ |
233 | 569 | st = fu_struct_dfu_ftr_parse_stream(stream, bufsz - FU_STRUCT_DFU_FTR_SIZE, error); |
234 | 569 | if (st == NULL) |
235 | 42 | return FALSE; |
236 | 527 | priv->vid = fu_struct_dfu_ftr_get_vid(st); |
237 | 527 | priv->pid = fu_struct_dfu_ftr_get_pid(st); |
238 | 527 | priv->release = fu_struct_dfu_ftr_get_release(st); |
239 | 527 | priv->dfu_version = fu_struct_dfu_ftr_get_ver(st); |
240 | 527 | priv->footer_len = fu_struct_dfu_ftr_get_len(st); |
241 | | |
242 | | /* verify the checksum */ |
243 | 527 | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
244 | 0 | guint32 crc_new = 0; |
245 | 0 | if (!fu_crc32_safe(FU_CRC_KIND_B32_JAMCRC, |
246 | 0 | buf, |
247 | 0 | bufsz, |
248 | 0 | 0x0, |
249 | 0 | bufsz - 4, |
250 | 0 | &crc_new, |
251 | 0 | error)) |
252 | 0 | return FALSE; |
253 | 0 | if (fu_struct_dfu_ftr_get_crc(st) != crc_new) { |
254 | 0 | g_set_error(error, |
255 | 0 | FWUPD_ERROR, |
256 | 0 | FWUPD_ERROR_INTERNAL, |
257 | 0 | "CRC failed, expected 0x%04x, got 0x%04x", |
258 | 0 | crc_new, |
259 | 0 | fu_struct_dfu_ftr_get_crc(st)); |
260 | 0 | return FALSE; |
261 | 0 | } |
262 | 0 | } |
263 | | |
264 | | /* check reported length */ |
265 | 527 | if (priv->footer_len > bufsz) { |
266 | 3 | g_set_error(error, |
267 | 3 | FWUPD_ERROR, |
268 | 3 | FWUPD_ERROR_INTERNAL, |
269 | 3 | "reported footer size 0x%04x larger than file 0x%04x", |
270 | 3 | (guint)priv->footer_len, |
271 | 3 | (guint)bufsz); |
272 | 3 | return FALSE; |
273 | 3 | } |
274 | | |
275 | | /* success */ |
276 | 524 | return TRUE; |
277 | 527 | } |
278 | | |
279 | | static gboolean |
280 | | fu_dfu_firmware_parse(FuFirmware *firmware, |
281 | | GInputStream *stream, |
282 | | FuFirmwareParseFlags flags, |
283 | | GError **error) |
284 | 0 | { |
285 | 0 | FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); |
286 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
287 | 0 | gsize streamsz = 0; |
288 | 0 | g_autoptr(GBytes) contents = NULL; |
289 | | |
290 | | /* parse footer */ |
291 | 0 | if (!fu_dfu_firmware_parse_footer(self, stream, flags, error)) |
292 | 0 | return FALSE; |
293 | | |
294 | | /* trim footer off */ |
295 | 0 | if (!fu_input_stream_size(stream, &streamsz, error)) |
296 | 0 | return FALSE; |
297 | 0 | contents = fu_input_stream_read_bytes(stream, 0, streamsz - priv->footer_len, NULL, error); |
298 | 0 | if (contents == NULL) |
299 | 0 | return FALSE; |
300 | 0 | fu_firmware_set_bytes(firmware, contents); |
301 | 0 | return TRUE; |
302 | 0 | } |
303 | | |
304 | | GByteArray * |
305 | | fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error) |
306 | 156 | { |
307 | 156 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
308 | 156 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
309 | 156 | g_autoptr(FuStructDfuFtr) st = fu_struct_dfu_ftr_new(); |
310 | | |
311 | | /* add the raw firmware data, the footer-less-CRC, and only then the CRC */ |
312 | 156 | fu_byte_array_append_bytes(buf, contents); |
313 | 156 | fu_struct_dfu_ftr_set_release(st, priv->release); |
314 | 156 | fu_struct_dfu_ftr_set_pid(st, priv->pid); |
315 | 156 | fu_struct_dfu_ftr_set_vid(st, priv->vid); |
316 | 156 | fu_struct_dfu_ftr_set_ver(st, priv->dfu_version); |
317 | 156 | g_byte_array_append(buf, st->buf->data, st->buf->len - sizeof(guint32)); |
318 | 156 | fu_byte_array_append_uint32(buf, |
319 | 156 | fu_crc32(FU_CRC_KIND_B32_JAMCRC, buf->data, buf->len), |
320 | 156 | G_LITTLE_ENDIAN); |
321 | 156 | return g_steal_pointer(&buf); |
322 | 156 | } |
323 | | |
324 | | static GByteArray * |
325 | | fu_dfu_firmware_write(FuFirmware *firmware, GError **error) |
326 | 0 | { |
327 | 0 | FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); |
328 | 0 | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
329 | 0 | g_autoptr(GBytes) fw = NULL; |
330 | | |
331 | | /* can only contain one image */ |
332 | 0 | if (images->len > 1) { |
333 | 0 | g_set_error_literal(error, |
334 | 0 | FWUPD_ERROR, |
335 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
336 | 0 | "DFU only supports writing one image"); |
337 | 0 | return NULL; |
338 | 0 | } |
339 | | |
340 | | /* add footer */ |
341 | 0 | fw = fu_firmware_get_bytes_with_patches(firmware, error); |
342 | 0 | if (fw == NULL) |
343 | 0 | return NULL; |
344 | 0 | return fu_dfu_firmware_append_footer(self, fw, error); |
345 | 0 | } |
346 | | |
347 | | static gboolean |
348 | | fu_dfu_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
349 | 0 | { |
350 | 0 | FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); |
351 | 0 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
352 | 0 | guint64 tmp; |
353 | | |
354 | | /* optional properties */ |
355 | 0 | tmp = xb_node_query_text_as_uint(n, "vendor", NULL); |
356 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
357 | 0 | priv->vid = tmp; |
358 | 0 | tmp = xb_node_query_text_as_uint(n, "product", NULL); |
359 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
360 | 0 | priv->pid = tmp; |
361 | 0 | tmp = xb_node_query_text_as_uint(n, "release", NULL); |
362 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
363 | 0 | priv->release = tmp; |
364 | 0 | tmp = xb_node_query_text_as_uint(n, "dfu_version", NULL); |
365 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
366 | 0 | priv->dfu_version = tmp; |
367 | | |
368 | | /* success */ |
369 | 0 | return TRUE; |
370 | 0 | } |
371 | | |
372 | | static void |
373 | | fu_dfu_firmware_init(FuDfuFirmware *self) |
374 | 640 | { |
375 | 640 | FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); |
376 | 640 | priv->vid = 0xffff; |
377 | 640 | priv->pid = 0xffff; |
378 | 640 | priv->release = 0xffff; |
379 | 640 | priv->dfu_version = FU_DFU_FIRMARE_VERSION_DFU_1_0; |
380 | 640 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
381 | 640 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); |
382 | 640 | } |
383 | | |
384 | | static void |
385 | | fu_dfu_firmware_class_init(FuDfuFirmwareClass *klass) |
386 | 1 | { |
387 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
388 | 1 | firmware_class->validate = fu_dfu_firmware_validate; |
389 | 1 | firmware_class->export = fu_dfu_firmware_export; |
390 | 1 | firmware_class->parse = fu_dfu_firmware_parse; |
391 | 1 | firmware_class->write = fu_dfu_firmware_write; |
392 | 1 | firmware_class->build = fu_dfu_firmware_build; |
393 | 1 | } |
394 | | |
395 | | /** |
396 | | * fu_dfu_firmware_new: |
397 | | * |
398 | | * Creates a new #FuFirmware of sub type Dfu |
399 | | * |
400 | | * Since: 1.3.3 |
401 | | **/ |
402 | | FuFirmware * |
403 | | fu_dfu_firmware_new(void) |
404 | 0 | { |
405 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_DFU_FIRMWARE, NULL)); |
406 | 0 | } |