/src/fwupd/libfwupdplugin/fu-input-stream.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2023 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuInputStream" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-chunk-array.h" |
12 | | #include "fu-crc-private.h" |
13 | | #include "fu-input-stream.h" |
14 | | #include "fu-mem-private.h" |
15 | | #include "fu-sum.h" |
16 | | |
17 | | /** |
18 | | * fu_input_stream_from_path: |
19 | | * @path: a filename |
20 | | * @error: (nullable): optional return location for an error |
21 | | * |
22 | | * Opens the file as n input stream. |
23 | | * |
24 | | * Returns: (transfer full): a #GInputStream, or %NULL on error |
25 | | * |
26 | | * Since: 2.0.0 |
27 | | **/ |
28 | | GInputStream * |
29 | | fu_input_stream_from_path(const gchar *path, GError **error) |
30 | 0 | { |
31 | 0 | g_autoptr(GFile) file = NULL; |
32 | 0 | g_autoptr(GFileInputStream) stream = NULL; |
33 | |
|
34 | 0 | g_return_val_if_fail(path != NULL, NULL); |
35 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
36 | | |
37 | 0 | file = g_file_new_for_path(path); |
38 | 0 | stream = g_file_read(file, NULL, error); |
39 | 0 | if (stream == NULL) { |
40 | 0 | fwupd_error_convert(error); |
41 | 0 | return NULL; |
42 | 0 | } |
43 | 0 | return G_INPUT_STREAM(g_steal_pointer(&stream)); |
44 | 0 | } |
45 | | |
46 | | /** |
47 | | * fu_input_stream_read_safe: |
48 | | * @stream: a #GInputStream |
49 | | * @buf (not nullable): a buffer to read data into |
50 | | * @bufsz: size of @buf |
51 | | * @offset: offset in bytes into @buf to copy from |
52 | | * @seek_set: given offset to seek to |
53 | | * @count: the number of bytes that will be read from the stream |
54 | | * @error: (nullable): optional return location for an error |
55 | | * |
56 | | * Tries to read count bytes from the stream into the buffer starting at @buf. |
57 | | * |
58 | | * Returns: %TRUE for success |
59 | | * |
60 | | * Since: 2.0.0 |
61 | | **/ |
62 | | gboolean |
63 | | fu_input_stream_read_safe(GInputStream *stream, |
64 | | guint8 *buf, |
65 | | gsize bufsz, |
66 | | gsize offset, |
67 | | gsize seek_set, |
68 | | gsize count, |
69 | | GError **error) |
70 | 16.7M | { |
71 | 16.7M | gssize rc; |
72 | | |
73 | 16.7M | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
74 | 16.7M | g_return_val_if_fail(buf != NULL, FALSE); |
75 | 16.7M | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
76 | | |
77 | 16.7M | if (!fu_memchk_write(bufsz, offset, count, error)) |
78 | 0 | return FALSE; |
79 | 16.7M | if (!g_seekable_seek(G_SEEKABLE(stream), seek_set, G_SEEK_SET, NULL, error)) { |
80 | 15.7k | g_prefix_error(error, "seek to 0x%x: ", (guint)seek_set); |
81 | 15.7k | return FALSE; |
82 | 15.7k | } |
83 | 16.7M | rc = g_input_stream_read(stream, buf + offset, count, NULL, error); |
84 | 16.7M | if (rc == -1) { |
85 | 0 | g_prefix_error(error, "failed read of 0x%x: ", (guint)count); |
86 | 0 | return FALSE; |
87 | 0 | } |
88 | 16.7M | if ((gsize)rc != count) { |
89 | 1.02k | g_set_error(error, |
90 | 1.02k | FWUPD_ERROR, |
91 | 1.02k | FWUPD_ERROR_READ, |
92 | 1.02k | "requested 0x%x and got 0x%x", |
93 | 1.02k | (guint)count, |
94 | 1.02k | (guint)rc); |
95 | 1.02k | return FALSE; |
96 | 1.02k | } |
97 | 16.7M | return TRUE; |
98 | 16.7M | } |
99 | | |
100 | | /** |
101 | | * fu_input_stream_read_u8: |
102 | | * @stream: a #GInputStream |
103 | | * @offset: offset in bytes into @stream to copy from |
104 | | * @value: (out) (not nullable): the parsed value |
105 | | * @error: (nullable): optional return location for an error |
106 | | * |
107 | | * Read a value from a stream using a specified endian in a safe way. |
108 | | * |
109 | | * Returns: %TRUE if @value was set, %FALSE otherwise |
110 | | * |
111 | | * Since: 2.0.0 |
112 | | **/ |
113 | | gboolean |
114 | | fu_input_stream_read_u8(GInputStream *stream, gsize offset, guint8 *value, GError **error) |
115 | 471k | { |
116 | 471k | guint8 buf = 0; |
117 | 471k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
118 | 471k | g_return_val_if_fail(value != NULL, FALSE); |
119 | 471k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
120 | 471k | if (!fu_input_stream_read_safe(stream, &buf, sizeof(buf), 0x0, offset, sizeof(buf), error)) |
121 | 368 | return FALSE; |
122 | 471k | *value = buf; |
123 | 471k | return TRUE; |
124 | 471k | } |
125 | | |
126 | | /** |
127 | | * fu_input_stream_read_u16: |
128 | | * @stream: a #GInputStream |
129 | | * @offset: offset in bytes into @stream to copy from |
130 | | * @value: (out) (not nullable): the parsed value |
131 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
132 | | * @error: (nullable): optional return location for an error |
133 | | * |
134 | | * Read a value from a stream using a specified endian in a safe way. |
135 | | * |
136 | | * Returns: %TRUE if @value was set, %FALSE otherwise |
137 | | * |
138 | | * Since: 2.0.0 |
139 | | **/ |
140 | | gboolean |
141 | | fu_input_stream_read_u16(GInputStream *stream, |
142 | | gsize offset, |
143 | | guint16 *value, |
144 | | FuEndianType endian, |
145 | | GError **error) |
146 | 130k | { |
147 | 130k | guint8 buf[2] = {0}; |
148 | 130k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
149 | 130k | g_return_val_if_fail(value != NULL, FALSE); |
150 | 130k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
151 | 130k | if (!fu_input_stream_read_safe(stream, buf, sizeof(buf), 0x0, offset, sizeof(buf), error)) |
152 | 446 | return FALSE; |
153 | 129k | *value = fu_memread_uint16(buf, endian); |
154 | 129k | return TRUE; |
155 | 130k | } |
156 | | |
157 | | /** |
158 | | * fu_input_stream_read_u24: |
159 | | * @stream: a #GInputStream |
160 | | * @offset: offset in bytes into @stream to copy from |
161 | | * @value: (out) (not nullable): the parsed value |
162 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
163 | | * @error: (nullable): optional return location for an error |
164 | | * |
165 | | * Read a value from a stream using a specified endian in a safe way. |
166 | | * |
167 | | * Returns: %TRUE if @value was set, %FALSE otherwise |
168 | | * |
169 | | * Since: 2.0.0 |
170 | | **/ |
171 | | gboolean |
172 | | fu_input_stream_read_u24(GInputStream *stream, |
173 | | gsize offset, |
174 | | guint32 *value, |
175 | | FuEndianType endian, |
176 | | GError **error) |
177 | 474 | { |
178 | 474 | guint8 buf[3] = {0}; |
179 | 474 | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
180 | 474 | g_return_val_if_fail(value != NULL, FALSE); |
181 | 474 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
182 | 474 | if (!fu_input_stream_read_safe(stream, buf, sizeof(buf), 0x0, offset, sizeof(buf), error)) |
183 | 12 | return FALSE; |
184 | 462 | *value = fu_memread_uint24(buf, endian); |
185 | 462 | return TRUE; |
186 | 474 | } |
187 | | |
188 | | /** |
189 | | * fu_input_stream_read_u32: |
190 | | * @stream: a #GInputStream |
191 | | * @offset: offset in bytes into @stream to copy from |
192 | | * @value: (out) (not nullable): the parsed value |
193 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
194 | | * @error: (nullable): optional return location for an error |
195 | | * |
196 | | * Read a value from a stream using a specified endian in a safe way. |
197 | | * |
198 | | * Returns: %TRUE if @value was set, %FALSE otherwise |
199 | | * |
200 | | * Since: 2.0.0 |
201 | | **/ |
202 | | gboolean |
203 | | fu_input_stream_read_u32(GInputStream *stream, |
204 | | gsize offset, |
205 | | guint32 *value, |
206 | | FuEndianType endian, |
207 | | GError **error) |
208 | 9.52M | { |
209 | 9.52M | guint8 buf[4] = {0}; |
210 | 9.52M | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
211 | 9.52M | g_return_val_if_fail(value != NULL, FALSE); |
212 | 9.52M | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
213 | 9.52M | if (!fu_input_stream_read_safe(stream, buf, sizeof(buf), 0x0, offset, sizeof(buf), error)) |
214 | 574 | return FALSE; |
215 | 9.52M | *value = fu_memread_uint32(buf, endian); |
216 | 9.52M | return TRUE; |
217 | 9.52M | } |
218 | | |
219 | | /** |
220 | | * fu_input_stream_read_u64: |
221 | | * @stream: a #GInputStream |
222 | | * @offset: offset in bytes into @stream to copy from |
223 | | * @value: (out) (not nullable): the parsed value |
224 | | * @endian: an endian type, e.g. %G_LITTLE_ENDIAN |
225 | | * @error: (nullable): optional return location for an error |
226 | | * |
227 | | * Read a value from a stream using a specified endian in a safe way. |
228 | | * |
229 | | * Returns: %TRUE if @value was set, %FALSE otherwise |
230 | | * |
231 | | * Since: 2.0.0 |
232 | | **/ |
233 | | gboolean |
234 | | fu_input_stream_read_u64(GInputStream *stream, |
235 | | gsize offset, |
236 | | guint64 *value, |
237 | | FuEndianType endian, |
238 | | GError **error) |
239 | 6.62M | { |
240 | 6.62M | guint8 buf[8] = {0}; |
241 | 6.62M | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
242 | 6.62M | g_return_val_if_fail(value != NULL, FALSE); |
243 | 6.62M | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
244 | 6.62M | if (!fu_input_stream_read_safe(stream, buf, sizeof(buf), 0x0, offset, sizeof(buf), error)) |
245 | 15.0k | return FALSE; |
246 | 6.60M | *value = fu_memread_uint64(buf, endian); |
247 | 6.60M | return TRUE; |
248 | 6.62M | } |
249 | | |
250 | | /** |
251 | | * fu_input_stream_read_byte_array: |
252 | | * @stream: a #GInputStream |
253 | | * @offset: offset in bytes into @stream to copy from |
254 | | * @count: maximum number of bytes to read |
255 | | * @progress: (nullable): an optional #FuProgress |
256 | | * @error: (nullable): optional return location for an error |
257 | | * |
258 | | * Read a byte array from a stream in a safe way. |
259 | | * |
260 | | * NOTE: The returned buffer may be smaller than @count! |
261 | | * |
262 | | * Returns: (transfer full): buffer |
263 | | * |
264 | | * Since: 2.0.0 |
265 | | **/ |
266 | | GByteArray * |
267 | | fu_input_stream_read_byte_array(GInputStream *stream, |
268 | | gsize offset, |
269 | | gsize count, |
270 | | FuProgress *progress, |
271 | | GError **error) |
272 | 133M | { |
273 | 133M | guint8 tmp[0x8000]; |
274 | 133M | g_autoptr(GByteArray) buf = g_byte_array_new(); |
275 | 133M | g_autoptr(GError) error_local = NULL; |
276 | | |
277 | 133M | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
278 | 133M | g_return_val_if_fail(progress == NULL || FU_IS_PROGRESS(progress), NULL); |
279 | 133M | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
280 | | |
281 | | /* this is invalid */ |
282 | 133M | if (count == 0) { |
283 | 92.3k | g_set_error_literal(error, |
284 | 92.3k | FWUPD_ERROR, |
285 | 92.3k | FWUPD_ERROR_NOT_SUPPORTED, |
286 | 92.3k | "read size must be non-zero"); |
287 | 92.3k | return NULL; |
288 | 92.3k | } |
289 | | |
290 | | /* seek back to start */ |
291 | 133M | if (G_IS_SEEKABLE(stream) && g_seekable_can_seek(G_SEEKABLE(stream))) { |
292 | 133M | if (!g_seekable_seek(G_SEEKABLE(stream), offset, G_SEEK_SET, NULL, error)) |
293 | 1.84k | return NULL; |
294 | 133M | } |
295 | | |
296 | | /* read from stream in 32kB chunks */ |
297 | 133M | while (TRUE) { |
298 | 133M | gssize sz; |
299 | 133M | sz = g_input_stream_read(stream, |
300 | 133M | tmp, |
301 | 133M | MIN(count - buf->len, sizeof(tmp)), |
302 | 133M | NULL, |
303 | 133M | &error_local); |
304 | 133M | if (sz == 0) |
305 | 161k | break; |
306 | 133M | if (sz < 0) { |
307 | 97 | g_set_error_literal(error, |
308 | 97 | FWUPD_ERROR, |
309 | 97 | FWUPD_ERROR_INVALID_FILE, |
310 | 97 | error_local->message); |
311 | 97 | return NULL; |
312 | 97 | } |
313 | | |
314 | | /* update progress */ |
315 | 133M | if (progress != NULL) |
316 | 0 | fu_progress_set_percentage_full(progress, buf->len, count); |
317 | | |
318 | 133M | g_byte_array_append(buf, tmp, sz); |
319 | 133M | if (buf->len >= count) |
320 | 133M | break; |
321 | 133M | } |
322 | | |
323 | | /* no data was read */ |
324 | 133M | if (buf->len == 0) { |
325 | 1.83k | g_set_error_literal(error, |
326 | 1.83k | FWUPD_ERROR, |
327 | 1.83k | FWUPD_ERROR_INVALID_FILE, |
328 | 1.83k | "no data could be read"); |
329 | 1.83k | return NULL; |
330 | 1.83k | } |
331 | | |
332 | | /* success */ |
333 | 133M | return g_steal_pointer(&buf); |
334 | 133M | } |
335 | | |
336 | | /** |
337 | | * fu_input_stream_read_bytes: |
338 | | * @stream: a #GInputStream |
339 | | * @offset: offset in bytes into @stream to copy from |
340 | | * @count: maximum number of bytes to read |
341 | | * @progress: (nullable): an optional #FuProgress |
342 | | * @error: (nullable): optional return location for an error |
343 | | * |
344 | | * Read a #GBytes from a stream in a safe way. |
345 | | * |
346 | | * NOTE: The returned buffer may be smaller than @count! |
347 | | * |
348 | | * Returns: (transfer full): buffer |
349 | | * |
350 | | * Since: 2.0.0 |
351 | | **/ |
352 | | GBytes * |
353 | | fu_input_stream_read_bytes(GInputStream *stream, |
354 | | gsize offset, |
355 | | gsize count, |
356 | | FuProgress *progress, |
357 | | GError **error) |
358 | 1.49M | { |
359 | 1.49M | g_autoptr(GByteArray) buf = NULL; |
360 | 1.49M | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
361 | 1.49M | g_return_val_if_fail(progress == NULL || FU_IS_PROGRESS(progress), NULL); |
362 | 1.49M | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
363 | 1.49M | buf = fu_input_stream_read_byte_array(stream, offset, count, progress, error); |
364 | 1.49M | if (buf == NULL) |
365 | 838 | return NULL; |
366 | 1.49M | return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); /* nocheck:blocked */ |
367 | 1.49M | } |
368 | | |
369 | | /** |
370 | | * fu_input_stream_read_string: |
371 | | * @stream: a #GInputStream |
372 | | * @offset: offset in bytes into @stream to copy from |
373 | | * @count: maximum number of bytes to read |
374 | | * @error: (nullable): optional return location for an error |
375 | | * |
376 | | * Read a UTF-8 string from a stream in a safe way. |
377 | | * |
378 | | * Returns: (transfer full): string |
379 | | * |
380 | | * Since: 2.0.0 |
381 | | **/ |
382 | | gchar * |
383 | | fu_input_stream_read_string(GInputStream *stream, gsize offset, gsize count, GError **error) |
384 | 6.22k | { |
385 | 6.22k | g_autoptr(GByteArray) buf = NULL; |
386 | | |
387 | 6.22k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
388 | 6.22k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
389 | | |
390 | 6.22k | buf = fu_input_stream_read_byte_array(stream, offset, count, NULL, error); |
391 | 6.22k | if (buf == NULL) |
392 | 54 | return NULL; |
393 | 6.16k | if (!g_utf8_validate_len((const gchar *)buf->data, buf->len, NULL)) { |
394 | 171 | g_set_error_literal(error, |
395 | 171 | FWUPD_ERROR, |
396 | 171 | FWUPD_ERROR_NOT_SUPPORTED, |
397 | 171 | "non UTF-8 string"); |
398 | 171 | return NULL; |
399 | 171 | } |
400 | 5.99k | return g_strndup((const gchar *)buf->data, buf->len); |
401 | 6.16k | } |
402 | | |
403 | | /** |
404 | | * fu_input_stream_size: |
405 | | * @stream: a #GInputStream |
406 | | * @val: (out): size in bytes |
407 | | * @error: (nullable): optional return location for an error |
408 | | * |
409 | | * Reads the total possible of the stream. |
410 | | * |
411 | | * If @stream is not seekable, %G_MAXSIZE is used as the size. |
412 | | * |
413 | | * Returns: %TRUE for success |
414 | | * |
415 | | * Since: 2.0.0 |
416 | | **/ |
417 | | gboolean |
418 | | fu_input_stream_size(GInputStream *stream, gsize *val, GError **error) |
419 | 9.58M | { |
420 | 9.58M | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
421 | 9.58M | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
422 | | |
423 | | /* streaming from unseekable stream */ |
424 | 9.58M | if (!G_IS_SEEKABLE(stream) || !g_seekable_can_seek(G_SEEKABLE(stream))) { |
425 | 0 | if (val != NULL) |
426 | 0 | *val = G_MAXSIZE; |
427 | 0 | return TRUE; |
428 | 0 | } |
429 | | |
430 | 9.58M | if (!g_seekable_seek(G_SEEKABLE(stream), 0, G_SEEK_END, NULL, error)) { |
431 | 103 | g_prefix_error(error, "seek to end: "); |
432 | 103 | return FALSE; |
433 | 103 | } |
434 | 9.58M | if (val != NULL) |
435 | 9.58M | *val = g_seekable_tell(G_SEEKABLE(stream)); |
436 | | |
437 | | /* success */ |
438 | 9.58M | return TRUE; |
439 | 9.58M | } |
440 | | |
441 | | static gboolean |
442 | | fu_input_stream_compute_checksum_cb(const guint8 *buf, |
443 | | gsize bufsz, |
444 | | gpointer user_data, |
445 | | GError **error) |
446 | 0 | { |
447 | 0 | GChecksum *csum = (GChecksum *)user_data; |
448 | 0 | g_checksum_update(csum, buf, bufsz); |
449 | 0 | return TRUE; |
450 | 0 | } |
451 | | |
452 | | /** |
453 | | * fu_input_stream_compute_checksum: |
454 | | * @stream: a #GInputStream |
455 | | * @checksum_type: a #GChecksumType |
456 | | * @error: (nullable): optional return location for an error |
457 | | * |
458 | | * Generates the checksum of the entire stream. |
459 | | * |
460 | | * Returns: the hexadecimal representation of the checksum, or %NULL on error |
461 | | * |
462 | | * Since: 2.0.0 |
463 | | **/ |
464 | | gchar * |
465 | | fu_input_stream_compute_checksum(GInputStream *stream, GChecksumType checksum_type, GError **error) |
466 | 0 | { |
467 | 0 | g_autoptr(GChecksum) csum = g_checksum_new(checksum_type); |
468 | |
|
469 | 0 | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
470 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
471 | | |
472 | 0 | if (!fu_input_stream_chunkify(stream, fu_input_stream_compute_checksum_cb, csum, error)) |
473 | 0 | return NULL; |
474 | 0 | return g_strdup(g_checksum_get_string(csum)); |
475 | 0 | } |
476 | | |
477 | | static gboolean |
478 | | fu_input_stream_compute_sum8_cb(const guint8 *buf, gsize bufsz, gpointer user_data, GError **error) |
479 | 2.18k | { |
480 | 2.18k | guint8 *value = (guint8 *)user_data; |
481 | 2.18k | *value += fu_sum8(buf, bufsz); |
482 | 2.18k | return TRUE; |
483 | 2.18k | } |
484 | | |
485 | | /** |
486 | | * fu_input_stream_compute_sum8: |
487 | | * @stream: a #GInputStream |
488 | | * @value: (out): value |
489 | | * @error: (nullable): optional return location for an error |
490 | | * |
491 | | * Returns the arithmetic sum of all bytes in the stream. |
492 | | * |
493 | | * Returns: %TRUE for success |
494 | | * |
495 | | * Since: 2.0.0 |
496 | | **/ |
497 | | gboolean |
498 | | fu_input_stream_compute_sum8(GInputStream *stream, guint8 *value, GError **error) |
499 | 1.77k | { |
500 | 1.77k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
501 | 1.77k | g_return_val_if_fail(value != NULL, FALSE); |
502 | 1.77k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
503 | 1.77k | return fu_input_stream_chunkify(stream, fu_input_stream_compute_sum8_cb, value, error); |
504 | 1.77k | } |
505 | | |
506 | | static gboolean |
507 | | fu_input_stream_compute_sum16_cb(const guint8 *buf, gsize bufsz, gpointer user_data, GError **error) |
508 | 8.56k | { |
509 | 8.56k | guint16 *value = (guint16 *)user_data; |
510 | 8.56k | if (bufsz % sizeof(*value) != 0) { |
511 | 0 | g_set_error(error, |
512 | 0 | FWUPD_ERROR, |
513 | 0 | FWUPD_ERROR_READ, |
514 | 0 | "not aligned to %u bytes, got 0x%x", |
515 | 0 | (guint)sizeof(*value), |
516 | 0 | (guint)bufsz); |
517 | 0 | return FALSE; |
518 | 0 | } |
519 | 8.56k | *value += fu_sum16(buf, bufsz); |
520 | 8.56k | return TRUE; |
521 | 8.56k | } |
522 | | |
523 | | /** |
524 | | * fu_input_stream_compute_sum16: |
525 | | * @stream: a #GInputStream |
526 | | * @value: (out): value |
527 | | * @error: (nullable): optional return location for an error |
528 | | * |
529 | | * Returns the arithmetic sum of all bytes in the stream. |
530 | | * |
531 | | * Returns: %TRUE for success |
532 | | * |
533 | | * Since: 2.0.0 |
534 | | **/ |
535 | | gboolean |
536 | | fu_input_stream_compute_sum16(GInputStream *stream, guint16 *value, GError **error) |
537 | 8.45k | { |
538 | 8.45k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
539 | 8.45k | g_return_val_if_fail(value != NULL, FALSE); |
540 | 8.45k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
541 | 8.45k | return fu_input_stream_chunkify(stream, fu_input_stream_compute_sum16_cb, value, error); |
542 | 8.45k | } |
543 | | |
544 | | static gboolean |
545 | | fu_input_stream_compute_sum32_cb(const guint8 *buf, gsize bufsz, gpointer user_data, GError **error) |
546 | 0 | { |
547 | 0 | guint32 *value = (guint32 *)user_data; |
548 | 0 | if (bufsz % sizeof(*value) != 0) { |
549 | 0 | g_set_error(error, |
550 | 0 | FWUPD_ERROR, |
551 | 0 | FWUPD_ERROR_READ, |
552 | 0 | "not aligned to %u bytes, got 0x%x", |
553 | 0 | (guint)sizeof(*value), |
554 | 0 | (guint)bufsz); |
555 | 0 | return FALSE; |
556 | 0 | } |
557 | 0 | *value += fu_sum32(buf, bufsz); |
558 | 0 | return TRUE; |
559 | 0 | } |
560 | | |
561 | | /** |
562 | | * fu_input_stream_compute_sum32: |
563 | | * @stream: a #GInputStream |
564 | | * @value: (out): value |
565 | | * @error: (nullable): optional return location for an error |
566 | | * |
567 | | * Returns the arithmetic sum of all bytes in the stream. |
568 | | * |
569 | | * Returns: %TRUE for success |
570 | | * |
571 | | * Since: 2.0.1 |
572 | | **/ |
573 | | gboolean |
574 | | fu_input_stream_compute_sum32(GInputStream *stream, guint32 *value, GError **error) |
575 | 0 | { |
576 | 0 | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
577 | 0 | g_return_val_if_fail(value != NULL, FALSE); |
578 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
579 | 0 | return fu_input_stream_chunkify(stream, fu_input_stream_compute_sum32_cb, value, error); |
580 | 0 | } |
581 | | |
582 | | typedef struct { |
583 | | FuCrcKind kind; |
584 | | guint32 crc; |
585 | | } FuInputStreamComputeCrc32Helper; |
586 | | |
587 | | static gboolean |
588 | | fu_input_stream_compute_crc32_cb(const guint8 *buf, gsize bufsz, gpointer user_data, GError **error) |
589 | 3.35k | { |
590 | 3.35k | FuInputStreamComputeCrc32Helper *helper = (FuInputStreamComputeCrc32Helper *)user_data; |
591 | 3.35k | helper->crc = fu_crc32_step(helper->kind, buf, bufsz, helper->crc); |
592 | 3.35k | return TRUE; |
593 | 3.35k | } |
594 | | |
595 | | /** |
596 | | * fu_input_stream_compute_crc32: |
597 | | * @stream: a #GInputStream |
598 | | * @kind: a #FuCrcKind, typically %FU_CRC_KIND_B32_STANDARD |
599 | | * @crc: (inout): initial and final CRC value |
600 | | * @error: (nullable): optional return location for an error |
601 | | * |
602 | | * Returns the cyclic redundancy check value for the given memory buffer. |
603 | | * |
604 | | * NOTE: The initial @crc differs from fu_crc32_step() in that it is inverted (to make it |
605 | | * symmetrical, and chainable), so for most uses you want to use the value of 0x0, not 0xFFFFFFFF. |
606 | | * |
607 | | * Returns: %TRUE for success |
608 | | * |
609 | | * Since: 2.0.0 |
610 | | **/ |
611 | | gboolean |
612 | | fu_input_stream_compute_crc32(GInputStream *stream, FuCrcKind kind, guint32 *crc, GError **error) |
613 | 3.10k | { |
614 | 3.10k | FuInputStreamComputeCrc32Helper helper = {.crc = *crc, .kind = kind}; |
615 | 3.10k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
616 | 3.10k | g_return_val_if_fail(crc != NULL, FALSE); |
617 | 3.10k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
618 | 3.10k | if (!fu_input_stream_chunkify(stream, fu_input_stream_compute_crc32_cb, &helper, error)) |
619 | 0 | return FALSE; |
620 | 3.10k | *crc = fu_crc32_done(kind, helper.crc); |
621 | 3.10k | return TRUE; |
622 | 3.10k | } |
623 | | |
624 | | typedef struct { |
625 | | FuCrcKind kind; |
626 | | guint16 crc; |
627 | | } FuInputStreamComputeCrc16Helper; |
628 | | |
629 | | static gboolean |
630 | | fu_input_stream_compute_crc16_cb(const guint8 *buf, gsize bufsz, gpointer user_data, GError **error) |
631 | 0 | { |
632 | 0 | FuInputStreamComputeCrc16Helper *helper = (FuInputStreamComputeCrc16Helper *)user_data; |
633 | 0 | helper->crc = fu_crc16_step(helper->kind, buf, bufsz, helper->crc); |
634 | 0 | return TRUE; |
635 | 0 | } |
636 | | |
637 | | /** |
638 | | * fu_input_stream_compute_crc16: |
639 | | * @stream: a #GInputStream |
640 | | * @kind: a #FuCrcKind, typically %FU_CRC_KIND_B16_XMODEM |
641 | | * @crc: (inout): initial and final CRC value |
642 | | * @error: (nullable): optional return location for an error |
643 | | * |
644 | | * Returns the cyclic redundancy check value for the given memory buffer. |
645 | | * |
646 | | * NOTE: The initial @crc differs from fu_crc16() in that it is inverted (to make it |
647 | | * symmetrical, and chainable), so for most uses you want to use the value of 0x0, not 0xFFFF. |
648 | | * |
649 | | * Returns: %TRUE for success |
650 | | * |
651 | | * Since: 2.0.0 |
652 | | **/ |
653 | | gboolean |
654 | | fu_input_stream_compute_crc16(GInputStream *stream, FuCrcKind kind, guint16 *crc, GError **error) |
655 | 0 | { |
656 | 0 | FuInputStreamComputeCrc16Helper helper = {.crc = *crc, .kind = kind}; |
657 | 0 | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
658 | 0 | g_return_val_if_fail(crc != NULL, FALSE); |
659 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
660 | 0 | if (!fu_input_stream_chunkify(stream, fu_input_stream_compute_crc16_cb, &helper, error)) |
661 | 0 | return FALSE; |
662 | 0 | *crc = fu_crc16_done(kind, helper.crc); |
663 | 0 | return TRUE; |
664 | 0 | } |
665 | | |
666 | | /** |
667 | | * fu_input_stream_chunkify: |
668 | | * @stream: a #GInputStream |
669 | | * @func_cb: (scope async): function to call with chunks |
670 | | * @user_data: user data to pass to @func_cb |
671 | | * @error: (nullable): optional return location for an error |
672 | | * |
673 | | * Split the stream into blocks and calls a function on each chunk. |
674 | | * |
675 | | * Returns: %TRUE for success |
676 | | * |
677 | | * Since: 2.0.0 |
678 | | **/ |
679 | | gboolean |
680 | | fu_input_stream_chunkify(GInputStream *stream, |
681 | | FuInputStreamChunkifyFunc func_cb, |
682 | | gpointer user_data, |
683 | | GError **error) |
684 | 15.5k | { |
685 | 15.5k | g_autoptr(FuChunkArray) chunks = NULL; |
686 | | |
687 | 15.5k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
688 | 15.5k | g_return_val_if_fail(func_cb != NULL, FALSE); |
689 | 15.5k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
690 | | |
691 | 15.5k | chunks = fu_chunk_array_new_from_stream(stream, |
692 | 15.5k | FU_CHUNK_ADDR_OFFSET_NONE, |
693 | 15.5k | FU_CHUNK_PAGESZ_NONE, |
694 | 15.5k | 0x8000, |
695 | 15.5k | error); |
696 | 15.5k | if (chunks == NULL) |
697 | 0 | return FALSE; |
698 | 32.1k | for (gsize i = 0; i < fu_chunk_array_length(chunks); i++) { |
699 | 16.5k | g_autoptr(FuChunk) chk = NULL; |
700 | 16.5k | chk = fu_chunk_array_index(chunks, i, error); |
701 | 16.5k | if (chk == NULL) |
702 | 0 | return FALSE; |
703 | 16.5k | if (!func_cb(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), user_data, error)) |
704 | 0 | return FALSE; |
705 | 16.5k | } |
706 | 15.5k | return TRUE; |
707 | 15.5k | } |
708 | | |
709 | | /** |
710 | | * fu_input_stream_find: |
711 | | * @stream: a #GInputStream |
712 | | * @buf: input buffer to look for |
713 | | * @bufsz: size of @buf |
714 | | * @offset: (nullable): found offset |
715 | | * @error: (nullable): optional return location for an error |
716 | | * |
717 | | * Find a memory buffer within an input stream, without loading the entire stream into a buffer. |
718 | | * |
719 | | * Returns: %TRUE if @buf was found |
720 | | * |
721 | | * Since: 2.0.0 |
722 | | **/ |
723 | | gboolean |
724 | | fu_input_stream_find(GInputStream *stream, |
725 | | const guint8 *buf, |
726 | | gsize bufsz, |
727 | | gsize *offset, |
728 | | GError **error) |
729 | 1.11k | { |
730 | 1.11k | g_autoptr(GByteArray) buf_acc = g_byte_array_new(); |
731 | 1.11k | const gsize blocksz = 0x10000; |
732 | 1.11k | gsize offset_add = 0; |
733 | 1.11k | gsize offset_cur = 0; |
734 | | |
735 | 1.11k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), FALSE); |
736 | 1.11k | g_return_val_if_fail(buf != NULL, FALSE); |
737 | 1.11k | g_return_val_if_fail(bufsz != 0, FALSE); |
738 | 1.11k | g_return_val_if_fail(bufsz < blocksz, FALSE); |
739 | 1.11k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
740 | | |
741 | 1.41k | while (offset_cur < bufsz) { |
742 | 1.13k | g_autoptr(GByteArray) buf_tmp = NULL; |
743 | 1.13k | g_autoptr(GError) error_local = NULL; |
744 | | |
745 | | /* read more data */ |
746 | 1.13k | buf_tmp = fu_input_stream_read_byte_array(stream, |
747 | 1.13k | offset_cur, |
748 | 1.13k | blocksz, |
749 | 1.13k | NULL, |
750 | 1.13k | &error_local); |
751 | 1.13k | if (buf_tmp == NULL) { |
752 | 24 | if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) |
753 | 24 | break; |
754 | 0 | g_propagate_error(error, g_steal_pointer(&error_local)); |
755 | 0 | return FALSE; |
756 | 24 | } |
757 | 1.11k | g_byte_array_append(buf_acc, buf_tmp->data, buf_tmp->len); |
758 | | |
759 | | /* we found something */ |
760 | 1.11k | if (fu_memmem_safe(buf_acc->data, buf_acc->len, buf, bufsz, offset, NULL)) { |
761 | 804 | if (offset != NULL) |
762 | 804 | *offset += offset_add; |
763 | 804 | return TRUE; |
764 | 804 | } |
765 | | |
766 | | /* truncate the buffer */ |
767 | 306 | if (buf_acc->len > bufsz) { |
768 | 280 | offset_add += buf_acc->len - bufsz; |
769 | 280 | g_byte_array_remove_range(buf_acc, 0, buf_acc->len - bufsz); |
770 | 280 | } |
771 | | |
772 | | /* move the offset */ |
773 | 306 | offset_cur += buf_tmp->len; |
774 | 306 | } |
775 | 306 | g_set_error(error, |
776 | 306 | FWUPD_ERROR, |
777 | 306 | FWUPD_ERROR_NOT_FOUND, |
778 | 306 | "failed to find buffer of size 0x%x", |
779 | 306 | (guint)bufsz); |
780 | 306 | return FALSE; |
781 | 1.11k | } |