/src/fwupd/libfwupdplugin/fu-dfuse-firmware.c
Line | Count | Source (jump to first uncovered line) |
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-dfu-firmware-private.h" |
13 | | #include "fu-dfu-firmware-struct.h" |
14 | | #include "fu-dfuse-firmware.h" |
15 | | #include "fu-input-stream.h" |
16 | | |
17 | | /** |
18 | | * FuDfuseFirmware: |
19 | | * |
20 | | * A DfuSe firmware image. |
21 | | * |
22 | | * See also: [class@FuDfuFirmware] |
23 | | */ |
24 | | |
25 | | G_DEFINE_TYPE(FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE) |
26 | | |
27 | | static FuChunk * |
28 | | fu_dfuse_firmware_image_chunk_parse(FuDfuseFirmware *self, |
29 | | GInputStream *stream, |
30 | | gsize *offset, |
31 | | GError **error) |
32 | 1.57k | { |
33 | 1.57k | g_autoptr(FuChunk) chk = NULL; |
34 | 1.57k | g_autoptr(GByteArray) st_ele = NULL; |
35 | 1.57k | g_autoptr(GBytes) blob = NULL; |
36 | | |
37 | | /* create new chunk */ |
38 | 1.57k | st_ele = fu_struct_dfuse_element_parse_stream(stream, *offset, error); |
39 | 1.57k | if (st_ele == NULL) |
40 | 148 | return NULL; |
41 | 1.43k | *offset += st_ele->len; |
42 | 1.43k | blob = fu_input_stream_read_bytes(stream, |
43 | 1.43k | *offset, |
44 | 1.43k | fu_struct_dfuse_element_get_size(st_ele), |
45 | 1.43k | NULL, |
46 | 1.43k | error); |
47 | 1.43k | if (blob == NULL) |
48 | 38 | return NULL; |
49 | 1.39k | chk = fu_chunk_bytes_new(blob); |
50 | 1.39k | fu_chunk_set_address(chk, fu_struct_dfuse_element_get_address(st_ele)); |
51 | 1.39k | *offset += fu_chunk_get_data_sz(chk); |
52 | | |
53 | | /* success */ |
54 | 1.39k | return g_steal_pointer(&chk); |
55 | 1.43k | } |
56 | | |
57 | | static FuFirmware * |
58 | | fu_dfuse_firmware_image_parse_stream(FuDfuseFirmware *self, |
59 | | GInputStream *stream, |
60 | | gsize *offset, |
61 | | GError **error) |
62 | 385 | { |
63 | 385 | guint chunks; |
64 | 385 | g_autoptr(FuFirmware) image = fu_firmware_new(); |
65 | 385 | g_autoptr(GByteArray) st_img = NULL; |
66 | | |
67 | | /* verify image signature */ |
68 | 385 | st_img = fu_struct_dfuse_image_parse_stream(stream, *offset, error); |
69 | 385 | if (st_img == NULL) |
70 | 110 | return NULL; |
71 | | |
72 | | /* set properties */ |
73 | 275 | fu_firmware_set_idx(image, fu_struct_dfuse_image_get_alt_setting(st_img)); |
74 | 275 | if (fu_struct_dfuse_image_get_target_named(st_img) == 0x01) { |
75 | 70 | g_autofree gchar *target_name = fu_struct_dfuse_image_get_target_name(st_img); |
76 | 70 | fu_firmware_set_id(image, target_name); |
77 | 70 | } |
78 | | |
79 | | /* no chunks */ |
80 | 275 | chunks = fu_struct_dfuse_image_get_chunks(st_img); |
81 | 275 | if (chunks == 0) { |
82 | 3 | g_set_error_literal(error, |
83 | 3 | FWUPD_ERROR, |
84 | 3 | FWUPD_ERROR_INVALID_FILE, |
85 | 3 | "DfuSe image has no chunks"); |
86 | 3 | return NULL; |
87 | 3 | } |
88 | | |
89 | | /* parse chunks */ |
90 | 272 | *offset += st_img->len; |
91 | 1.66k | for (guint j = 0; j < chunks; j++) { |
92 | 1.57k | g_autoptr(FuChunk) chk = NULL; |
93 | 1.57k | chk = fu_dfuse_firmware_image_chunk_parse(self, stream, offset, error); |
94 | 1.57k | if (chk == NULL) |
95 | 186 | return NULL; |
96 | 1.39k | fu_firmware_add_chunk(image, chk); |
97 | 1.39k | } |
98 | | |
99 | | /* success */ |
100 | 86 | return g_steal_pointer(&image); |
101 | 272 | } |
102 | | |
103 | | static gboolean |
104 | | fu_dfuse_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error) |
105 | 5.47M | { |
106 | 5.47M | return fu_struct_dfuse_hdr_validate_stream(stream, offset, error); |
107 | 5.47M | } |
108 | | |
109 | | static gboolean |
110 | | fu_dfuse_firmware_parse(FuFirmware *firmware, |
111 | | GInputStream *stream, |
112 | | FuFirmwareParseFlags flags, |
113 | | GError **error) |
114 | 1.22k | { |
115 | 1.22k | FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE(firmware); |
116 | 1.22k | gsize offset = 0; |
117 | 1.22k | gsize streamsz = 0; |
118 | 1.22k | guint8 targets = 0; |
119 | 1.22k | g_autoptr(GByteArray) st_hdr = NULL; |
120 | | |
121 | | /* DFU footer first */ |
122 | 1.22k | if (!fu_dfu_firmware_parse_footer(dfu_firmware, stream, flags, error)) |
123 | 680 | return FALSE; |
124 | | |
125 | | /* parse */ |
126 | 549 | st_hdr = fu_struct_dfuse_hdr_parse_stream(stream, offset, error); |
127 | 549 | if (st_hdr == NULL) |
128 | 0 | return FALSE; |
129 | | |
130 | | /* check image size */ |
131 | 549 | if (!fu_input_stream_size(stream, &streamsz, error)) |
132 | 0 | return FALSE; |
133 | 549 | if (fu_struct_dfuse_hdr_get_image_size(st_hdr) != |
134 | 549 | streamsz - fu_dfu_firmware_get_footer_len(dfu_firmware)) { |
135 | 164 | g_set_error(error, |
136 | 164 | FWUPD_ERROR, |
137 | 164 | FWUPD_ERROR_INTERNAL, |
138 | 164 | "invalid DfuSe image size, " |
139 | 164 | "got %" G_GUINT32_FORMAT ", " |
140 | 164 | "expected %" G_GSIZE_FORMAT, |
141 | 164 | fu_struct_dfuse_hdr_get_image_size(st_hdr), |
142 | 164 | streamsz - fu_dfu_firmware_get_footer_len(dfu_firmware)); |
143 | 164 | return FALSE; |
144 | 164 | } |
145 | | |
146 | | /* parse the image targets */ |
147 | 385 | targets = fu_struct_dfuse_hdr_get_targets(st_hdr); |
148 | 385 | offset += st_hdr->len; |
149 | 471 | for (guint i = 0; i < targets; i++) { |
150 | 385 | g_autoptr(FuFirmware) image = NULL; |
151 | 385 | image = fu_dfuse_firmware_image_parse_stream(FU_DFUSE_FIRMWARE(firmware), |
152 | 385 | stream, |
153 | 385 | &offset, |
154 | 385 | error); |
155 | 385 | if (image == NULL) |
156 | 299 | return FALSE; |
157 | 86 | if (!fu_firmware_add_image_full(firmware, image, error)) |
158 | 0 | return FALSE; |
159 | 86 | } |
160 | 86 | return TRUE; |
161 | 385 | } |
162 | | |
163 | | static GBytes * |
164 | | fu_dfuse_firmware_chunk_write(FuDfuseFirmware *self, FuChunk *chk) |
165 | 503 | { |
166 | 503 | g_autoptr(GByteArray) st_ele = fu_struct_dfuse_element_new(); |
167 | 503 | fu_struct_dfuse_element_set_address(st_ele, fu_chunk_get_address(chk)); |
168 | 503 | fu_struct_dfuse_element_set_size(st_ele, fu_chunk_get_data_sz(chk)); |
169 | 503 | g_byte_array_append(st_ele, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); |
170 | 503 | return g_bytes_new(st_ele->data, st_ele->len); |
171 | 503 | } |
172 | | |
173 | | static GBytes * |
174 | | fu_dfuse_firmware_write_image(FuDfuseFirmware *self, FuFirmware *image, GError **error) |
175 | 61 | { |
176 | 61 | gsize totalsz = 0; |
177 | 61 | g_autoptr(GByteArray) st_img = fu_struct_dfuse_image_new(); |
178 | 61 | g_autoptr(GPtrArray) blobs = NULL; |
179 | 61 | g_autoptr(GPtrArray) chunks = NULL; |
180 | | |
181 | | /* get total size */ |
182 | 61 | blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); |
183 | 61 | chunks = fu_firmware_get_chunks(image, error); |
184 | 61 | if (chunks == NULL) |
185 | 0 | return NULL; |
186 | 564 | for (guint i = 0; i < chunks->len; i++) { |
187 | 503 | FuChunk *chk = g_ptr_array_index(chunks, i); |
188 | 503 | GBytes *bytes = fu_dfuse_firmware_chunk_write(self, chk); |
189 | 503 | g_ptr_array_add(blobs, bytes); |
190 | 503 | totalsz += g_bytes_get_size(bytes); |
191 | 503 | } |
192 | | |
193 | | /* add prefix */ |
194 | 61 | fu_struct_dfuse_image_set_alt_setting(st_img, fu_firmware_get_idx(image)); |
195 | 61 | if (fu_firmware_get_id(image) != NULL) { |
196 | 23 | fu_struct_dfuse_image_set_target_named(st_img, 0x01); |
197 | 23 | if (!fu_struct_dfuse_image_set_target_name(st_img, |
198 | 23 | fu_firmware_get_id(image), |
199 | 23 | error)) |
200 | 0 | return NULL; |
201 | 23 | } |
202 | 61 | fu_struct_dfuse_image_set_target_size(st_img, totalsz); |
203 | 61 | fu_struct_dfuse_image_set_chunks(st_img, chunks->len); |
204 | | |
205 | | /* copy data */ |
206 | 564 | for (guint i = 0; i < blobs->len; i++) { |
207 | 503 | GBytes *blob = g_ptr_array_index(blobs, i); |
208 | 503 | fu_byte_array_append_bytes(st_img, blob); |
209 | 503 | } |
210 | 61 | return g_bytes_new(st_img->data, st_img->len); |
211 | 61 | } |
212 | | |
213 | | static GByteArray * |
214 | | fu_dfuse_firmware_write(FuFirmware *firmware, GError **error) |
215 | 86 | { |
216 | 86 | FuDfuseFirmware *self = FU_DFUSE_FIRMWARE(firmware); |
217 | 86 | gsize totalsz = 0; |
218 | 86 | g_autoptr(GByteArray) st_hdr = fu_struct_dfuse_hdr_new(); |
219 | 86 | g_autoptr(GBytes) blob_noftr = NULL; |
220 | 86 | g_autoptr(GPtrArray) blobs = NULL; |
221 | 86 | g_autoptr(GPtrArray) images = NULL; |
222 | | |
223 | | /* create mutable output buffer */ |
224 | 86 | blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); |
225 | 86 | images = fu_firmware_get_images(FU_FIRMWARE(firmware)); |
226 | 147 | for (guint i = 0; i < images->len; i++) { |
227 | 61 | FuFirmware *img = g_ptr_array_index(images, i); |
228 | 61 | g_autoptr(GBytes) blob = NULL; |
229 | 61 | blob = fu_dfuse_firmware_write_image(self, img, error); |
230 | 61 | if (blob == NULL) |
231 | 0 | return NULL; |
232 | 61 | totalsz += g_bytes_get_size(blob); |
233 | 61 | g_ptr_array_add(blobs, g_steal_pointer(&blob)); |
234 | 61 | } |
235 | | |
236 | | /* DfuSe header */ |
237 | 86 | fu_struct_dfuse_hdr_set_image_size(st_hdr, st_hdr->len + totalsz); |
238 | 86 | if (images->len > G_MAXUINT8) { |
239 | 0 | g_set_error(error, |
240 | 0 | FWUPD_ERROR, |
241 | 0 | FWUPD_ERROR_INTERNAL, |
242 | 0 | "too many (%u) images to write DfuSe file", |
243 | 0 | images->len); |
244 | 0 | return NULL; |
245 | 0 | } |
246 | 86 | fu_struct_dfuse_hdr_set_targets(st_hdr, (guint8)images->len); |
247 | | |
248 | | /* copy images */ |
249 | 147 | for (guint i = 0; i < blobs->len; i++) { |
250 | 61 | GBytes *blob = g_ptr_array_index(blobs, i); |
251 | 61 | fu_byte_array_append_bytes(st_hdr, blob); |
252 | 61 | } |
253 | | |
254 | | /* return blob */ |
255 | 86 | blob_noftr = g_bytes_new(st_hdr->data, st_hdr->len); |
256 | 86 | return fu_dfu_firmware_append_footer(FU_DFU_FIRMWARE(firmware), blob_noftr, error); |
257 | 86 | } |
258 | | |
259 | | static void |
260 | | fu_dfuse_firmware_init(FuDfuseFirmware *self) |
261 | 1.76k | { |
262 | 1.76k | fu_dfu_firmware_set_version(FU_DFU_FIRMWARE(self), FU_DFU_FIRMARE_VERSION_DFUSE); |
263 | 1.76k | fu_firmware_set_images_max(FU_FIRMWARE(self), 255); |
264 | 1.76k | } |
265 | | |
266 | | static void |
267 | | fu_dfuse_firmware_class_init(FuDfuseFirmwareClass *klass) |
268 | 1 | { |
269 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
270 | 1 | firmware_class->validate = fu_dfuse_firmware_validate; |
271 | 1 | firmware_class->parse = fu_dfuse_firmware_parse; |
272 | 1 | firmware_class->write = fu_dfuse_firmware_write; |
273 | 1 | } |
274 | | |
275 | | /** |
276 | | * fu_dfuse_firmware_new: |
277 | | * |
278 | | * Creates a new #FuFirmware of sub type DfuSe |
279 | | * |
280 | | * Since: 1.5.6 |
281 | | **/ |
282 | | FuFirmware * |
283 | | fu_dfuse_firmware_new(void) |
284 | 0 | { |
285 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_DFUSE_FIRMWARE, NULL)); |
286 | 0 | } |