/src/fwupd/libfwupdplugin/fu-dfuse-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2015 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #define G_LOG_DOMAIN "FuFirmware" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-dfu-firmware-private.h" |
14 | | #include "fu-dfu-firmware-struct.h" |
15 | | #include "fu-dfuse-firmware.h" |
16 | | #include "fu-input-stream.h" |
17 | | |
18 | | /** |
19 | | * FuDfuseFirmware: |
20 | | * |
21 | | * A DfuSe firmware image. |
22 | | * |
23 | | * See also: [class@FuDfuFirmware] |
24 | | */ |
25 | | |
26 | 1.08k | G_DEFINE_TYPE(FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE) |
27 | 1.08k | |
28 | 1.08k | #define FU_DFUSE_FIRMWARE_CHUNKS_MAX 10000 |
29 | | |
30 | | static FuChunk * |
31 | | fu_dfuse_firmware_image_chunk_parse(FuDfuseFirmware *self, |
32 | | FuInputStream *stream, |
33 | | gsize *offset, |
34 | | GError **error) |
35 | 1.29k | { |
36 | 1.29k | g_autoptr(FuChunk) chk = NULL; |
37 | 1.29k | g_autoptr(FuStructDfuseElement) st_ele = NULL; |
38 | 1.29k | g_autoptr(GBytes) blob = NULL; |
39 | | |
40 | | /* create new chunk */ |
41 | 1.29k | st_ele = fu_struct_dfuse_element_parse_stream(stream, *offset, error); |
42 | 1.29k | if (st_ele == NULL) |
43 | 60 | return NULL; |
44 | 1.23k | if (!fu_size_checked_inc(offset, st_ele->buf->len, error)) { |
45 | 0 | g_prefix_error_literal(error, "DfuSe element offset overflow: "); |
46 | 0 | return NULL; |
47 | 0 | } |
48 | | |
49 | 1.23k | blob = fu_input_stream_read_bytes(stream, |
50 | 1.23k | *offset, |
51 | 1.23k | fu_struct_dfuse_element_get_size(st_ele), |
52 | 1.23k | NULL, |
53 | 1.23k | error); |
54 | 1.23k | if (blob == NULL) |
55 | 10 | return NULL; |
56 | 1.22k | chk = fu_chunk_bytes_new(blob); |
57 | 1.22k | fu_chunk_set_address(chk, fu_struct_dfuse_element_get_address(st_ele)); |
58 | 1.22k | if (!fu_size_checked_inc(offset, fu_chunk_get_data_sz(chk), error)) |
59 | 0 | return NULL; |
60 | | |
61 | | /* success */ |
62 | 1.22k | return g_steal_pointer(&chk); |
63 | 1.22k | } |
64 | | |
65 | | static FuFirmware * |
66 | | fu_dfuse_firmware_image_parse_stream(FuDfuseFirmware *self, |
67 | | FuInputStream *stream, |
68 | | gsize *offset, |
69 | | GError **error) |
70 | 354 | { |
71 | 354 | guint chunks; |
72 | 354 | g_autoptr(FuFirmware) image = fu_firmware_new(); |
73 | 354 | g_autoptr(FuStructDfuseImage) st_img = NULL; |
74 | | |
75 | | /* verify image signature */ |
76 | 354 | st_img = fu_struct_dfuse_image_parse_stream(stream, *offset, error); |
77 | 354 | if (st_img == NULL) |
78 | 87 | return NULL; |
79 | | |
80 | | /* set properties */ |
81 | 267 | fu_firmware_set_idx(image, fu_struct_dfuse_image_get_alt_setting(st_img)); |
82 | 267 | if (fu_struct_dfuse_image_get_target_named(st_img) == 0x01) { |
83 | 102 | g_autofree gchar *target_name = fu_struct_dfuse_image_get_target_name(st_img); |
84 | 102 | fu_firmware_set_id(image, target_name); |
85 | 102 | } |
86 | | |
87 | | /* no chunks */ |
88 | 267 | chunks = fu_struct_dfuse_image_get_chunks(st_img); |
89 | 267 | if (chunks == 0) { |
90 | 1 | g_set_error_literal(error, |
91 | 1 | FWUPD_ERROR, |
92 | 1 | FWUPD_ERROR_INVALID_FILE, |
93 | 1 | "DfuSe image has no chunks"); |
94 | 1 | return NULL; |
95 | 1 | } |
96 | 266 | if (chunks > FU_DFUSE_FIRMWARE_CHUNKS_MAX) { |
97 | 70 | g_set_error(error, |
98 | 70 | FWUPD_ERROR, |
99 | 70 | FWUPD_ERROR_INVALID_DATA, |
100 | 70 | "excessive chunk count: %u", |
101 | 70 | chunks); |
102 | 70 | return NULL; |
103 | 70 | } |
104 | | |
105 | | /* parse chunks */ |
106 | 196 | if (!fu_size_checked_inc(offset, st_img->buf->len, error)) { |
107 | 0 | g_prefix_error_literal(error, "DfuSe image offset overflow: "); |
108 | 0 | return NULL; |
109 | 0 | } |
110 | | |
111 | 1.42k | for (guint j = 0; j < chunks; j++) { |
112 | 1.29k | g_autoptr(FuChunk) chk = NULL; |
113 | 1.29k | chk = fu_dfuse_firmware_image_chunk_parse(self, stream, offset, error); |
114 | 1.29k | if (chk == NULL) |
115 | 70 | return NULL; |
116 | 1.22k | fu_firmware_add_chunk(image, chk); |
117 | 1.22k | } |
118 | | |
119 | | /* success */ |
120 | 126 | return g_steal_pointer(&image); |
121 | 196 | } |
122 | | |
123 | | static gboolean |
124 | | fu_dfuse_firmware_validate(FuFirmware *firmware, |
125 | | FuInputStream *stream, |
126 | | gsize offset, |
127 | | GError **error) |
128 | 600 | { |
129 | 600 | return fu_struct_dfuse_hdr_validate_stream(stream, offset, error); |
130 | 600 | } |
131 | | |
132 | | static gboolean |
133 | | fu_dfuse_firmware_parse(FuFirmware *firmware, |
134 | | FuInputStream *stream, |
135 | | FuFirmwareParseFlags flags, |
136 | | GError **error) |
137 | 529 | { |
138 | 529 | FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE(firmware); |
139 | 529 | gsize offset = 0; |
140 | 529 | gsize streamsz = 0; |
141 | 529 | guint8 targets = 0; |
142 | 529 | g_autoptr(FuStructDfuseHdr) st_hdr = NULL; |
143 | | |
144 | | /* DFU footer first */ |
145 | 529 | if (!fu_dfu_firmware_parse_footer(dfu_firmware, stream, flags, error)) |
146 | 83 | return FALSE; |
147 | | |
148 | | /* parse */ |
149 | 446 | st_hdr = fu_struct_dfuse_hdr_parse_stream(stream, offset, error); |
150 | 446 | if (st_hdr == NULL) |
151 | 0 | return FALSE; |
152 | | |
153 | | /* check image size */ |
154 | 446 | if (!fu_input_stream_size(stream, &streamsz, error)) |
155 | 0 | return FALSE; |
156 | 446 | if (fu_dfu_firmware_get_footer_len(dfu_firmware) > streamsz) { |
157 | 0 | g_set_error(error, |
158 | 0 | FWUPD_ERROR, |
159 | 0 | FWUPD_ERROR_INVALID_DATA, |
160 | 0 | "footer length 0x%x exceeds stream size 0x%x", |
161 | 0 | (guint)fu_dfu_firmware_get_footer_len(dfu_firmware), |
162 | 0 | (guint)streamsz); |
163 | 0 | return FALSE; |
164 | 0 | } |
165 | 446 | if (fu_struct_dfuse_hdr_get_image_size(st_hdr) != |
166 | 446 | streamsz - fu_dfu_firmware_get_footer_len(dfu_firmware)) { |
167 | 94 | g_set_error(error, |
168 | 94 | FWUPD_ERROR, |
169 | 94 | FWUPD_ERROR_INTERNAL, |
170 | 94 | "invalid DfuSe image size, " |
171 | 94 | "got %" G_GUINT32_FORMAT ", " |
172 | 94 | "expected %zu", |
173 | 94 | fu_struct_dfuse_hdr_get_image_size(st_hdr), |
174 | 94 | streamsz - fu_dfu_firmware_get_footer_len(dfu_firmware)); |
175 | 94 | return FALSE; |
176 | 94 | } |
177 | | |
178 | | /* parse the image targets */ |
179 | 352 | targets = fu_struct_dfuse_hdr_get_targets(st_hdr); |
180 | 352 | if (!fu_size_checked_inc(&offset, st_hdr->buf->len, error)) { |
181 | 0 | g_prefix_error_literal(error, "DfuSe header offset overflow: "); |
182 | 0 | return FALSE; |
183 | 0 | } |
184 | | |
185 | 478 | for (guint i = 0; i < targets; i++) { |
186 | 354 | g_autoptr(FuFirmware) image = NULL; |
187 | 354 | image = fu_dfuse_firmware_image_parse_stream(FU_DFUSE_FIRMWARE(firmware), |
188 | 354 | stream, |
189 | 354 | &offset, |
190 | 354 | error); |
191 | 354 | if (image == NULL) |
192 | 228 | return FALSE; |
193 | 126 | if (!fu_firmware_add_image(firmware, image, error)) |
194 | 0 | return FALSE; |
195 | 126 | } |
196 | 124 | return TRUE; |
197 | 352 | } |
198 | | |
199 | | static GBytes * |
200 | | fu_dfuse_firmware_chunk_write(FuDfuseFirmware *self, FuChunk *chk) |
201 | 713 | { |
202 | 713 | g_autoptr(FuStructDfuseElement) st_ele = fu_struct_dfuse_element_new(); |
203 | 713 | fu_struct_dfuse_element_set_address(st_ele, fu_chunk_get_address(chk)); |
204 | 713 | fu_struct_dfuse_element_set_size(st_ele, fu_chunk_get_data_sz(chk)); |
205 | 713 | g_byte_array_append(st_ele->buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); |
206 | 713 | return fu_struct_dfuse_element_to_bytes(st_ele); |
207 | 713 | } |
208 | | |
209 | | static GBytes * |
210 | | fu_dfuse_firmware_write_image(FuDfuseFirmware *self, FuFirmware *image, GError **error) |
211 | 108 | { |
212 | 108 | gsize totalsz = 0; |
213 | 108 | g_autoptr(FuStructDfuseImage) st_img = fu_struct_dfuse_image_new(); |
214 | 108 | g_autoptr(GPtrArray) blobs = NULL; |
215 | 108 | g_autoptr(GPtrArray) chunks = NULL; |
216 | | |
217 | | /* get total size */ |
218 | 108 | blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); |
219 | 108 | chunks = fu_firmware_get_chunks(image, error); |
220 | 108 | if (chunks == NULL) |
221 | 0 | return NULL; |
222 | 821 | for (guint i = 0; i < chunks->len; i++) { |
223 | 713 | FuChunk *chk = g_ptr_array_index(chunks, i); |
224 | 713 | GBytes *bytes = fu_dfuse_firmware_chunk_write(self, chk); |
225 | 713 | g_ptr_array_add(blobs, bytes); |
226 | 713 | if (!fu_size_checked_inc(&totalsz, g_bytes_get_size(bytes), error)) |
227 | 0 | return NULL; |
228 | 713 | } |
229 | | |
230 | | /* add prefix */ |
231 | 108 | fu_struct_dfuse_image_set_alt_setting(st_img, fu_firmware_get_idx(image)); |
232 | 108 | if (fu_firmware_get_id(image) != NULL) { |
233 | 56 | fu_struct_dfuse_image_set_target_named(st_img, 0x01); |
234 | 56 | if (!fu_struct_dfuse_image_set_target_name(st_img, |
235 | 56 | fu_firmware_get_id(image), |
236 | 56 | error)) |
237 | 0 | return NULL; |
238 | 56 | } |
239 | 108 | fu_struct_dfuse_image_set_target_size(st_img, totalsz); |
240 | 108 | fu_struct_dfuse_image_set_chunks(st_img, chunks->len); |
241 | | |
242 | | /* copy data */ |
243 | 821 | for (guint i = 0; i < blobs->len; i++) { |
244 | 713 | GBytes *blob = g_ptr_array_index(blobs, i); |
245 | 713 | fu_byte_array_append_bytes(st_img->buf, blob); |
246 | 713 | } |
247 | 108 | return fu_struct_dfuse_image_to_bytes(st_img); |
248 | 108 | } |
249 | | |
250 | | static GByteArray * |
251 | | fu_dfuse_firmware_write(FuFirmware *firmware, GError **error) |
252 | 124 | { |
253 | 124 | FuDfuseFirmware *self = FU_DFUSE_FIRMWARE(firmware); |
254 | 124 | gsize totalsz = 0; |
255 | 124 | g_autoptr(FuStructDfuseHdr) st_hdr = fu_struct_dfuse_hdr_new(); |
256 | 124 | g_autoptr(GBytes) blob_noftr = NULL; |
257 | 124 | g_autoptr(GPtrArray) blobs = NULL; |
258 | 124 | g_autoptr(GPtrArray) images = NULL; |
259 | | |
260 | | /* create mutable output buffer */ |
261 | 124 | blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); |
262 | 124 | images = fu_firmware_get_images(FU_FIRMWARE(firmware)); |
263 | 232 | for (guint i = 0; i < images->len; i++) { |
264 | 108 | FuFirmware *img = g_ptr_array_index(images, i); |
265 | 108 | g_autoptr(GBytes) blob = NULL; |
266 | 108 | blob = fu_dfuse_firmware_write_image(self, img, error); |
267 | 108 | if (blob == NULL) |
268 | 0 | return NULL; |
269 | 108 | if (!fu_size_checked_inc(&totalsz, g_bytes_get_size(blob), error)) |
270 | 0 | return NULL; |
271 | 108 | g_ptr_array_add(blobs, g_steal_pointer(&blob)); |
272 | 108 | } |
273 | | |
274 | | /* DfuSe header */ |
275 | 124 | fu_struct_dfuse_hdr_set_image_size(st_hdr, st_hdr->buf->len + totalsz); |
276 | 124 | if (images->len > G_MAXUINT8) { |
277 | 0 | g_set_error(error, |
278 | 0 | FWUPD_ERROR, |
279 | 0 | FWUPD_ERROR_INTERNAL, |
280 | 0 | "too many (%u) images to write DfuSe file", |
281 | 0 | images->len); |
282 | 0 | return NULL; |
283 | 0 | } |
284 | 124 | fu_struct_dfuse_hdr_set_targets(st_hdr, (guint8)images->len); |
285 | | |
286 | | /* copy images */ |
287 | 232 | for (guint i = 0; i < blobs->len; i++) { |
288 | 108 | GBytes *blob = g_ptr_array_index(blobs, i); |
289 | 108 | fu_byte_array_append_bytes(st_hdr->buf, blob); |
290 | 108 | } |
291 | | |
292 | | /* return blob */ |
293 | 124 | blob_noftr = fu_struct_dfuse_hdr_to_bytes(st_hdr); |
294 | 124 | return fu_dfu_firmware_append_footer(FU_DFU_FIRMWARE(firmware), blob_noftr, error); |
295 | 124 | } |
296 | | |
297 | | static void |
298 | | fu_dfuse_firmware_init(FuDfuseFirmware *self) |
299 | 600 | { |
300 | 600 | fu_dfu_firmware_set_version(FU_DFU_FIRMWARE(self), FU_DFU_FIRMWARE_VERSION_DFUSE); |
301 | 600 | } |
302 | | |
303 | | static void |
304 | | fu_dfuse_firmware_class_init(FuDfuseFirmwareClass *klass) |
305 | 1 | { |
306 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
307 | 1 | fu_firmware_add_image_gtype(firmware_class, FU_TYPE_FIRMWARE); |
308 | 1 | firmware_class->validate = fu_dfuse_firmware_validate; |
309 | 1 | firmware_class->parse = fu_dfuse_firmware_parse; |
310 | 1 | firmware_class->write = fu_dfuse_firmware_write; |
311 | 1 | fu_firmware_set_images_max(firmware_class, 255); |
312 | 1 | fu_firmware_set_size_max(firmware_class, 256 * FU_MB); |
313 | 1 | } |
314 | | |
315 | | /** |
316 | | * fu_dfuse_firmware_new: |
317 | | * |
318 | | * Creates a new #FuFirmware of sub type DfuSe |
319 | | * |
320 | | * Since: 1.5.6 |
321 | | **/ |
322 | | FuFirmware * |
323 | | fu_dfuse_firmware_new(void) |
324 | 0 | { |
325 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_DFUSE_FIRMWARE, NULL)); |
326 | 0 | } |