Coverage Report

Created: 2026-08-15 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-chunk.c
Line
Count
Source
1
/*
2
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuChunk"
8
9
#include "config.h"
10
11
#include "fu-chunk-private.h"
12
#include "fu-common.h"
13
#include "fu-mem.h"
14
15
/**
16
 * FuChunk:
17
 *
18
 * A optionally mutable packet of chunked data with address, page and index.
19
 */
20
21
struct _FuChunk {
22
  GObject parent_instance;
23
  guint idx;
24
  guint page;
25
  gsize address;
26
  const guint8 *data;
27
  gsize data_sz;
28
  gboolean is_mutable;
29
  GBytes *bytes;
30
};
31
32
16.7M
G_DEFINE_TYPE(FuChunk, fu_chunk, G_TYPE_OBJECT)
33
16.7M
34
16.7M
/**
35
16.7M
 * fu_chunk_set_idx:
36
16.7M
 * @self: a #FuChunk
37
16.7M
 * @idx: index, starting at 0
38
16.7M
 *
39
16.7M
 * Sets the index of the chunk.
40
16.7M
 *
41
16.7M
 * Since: 1.5.6
42
16.7M
 **/
43
16.7M
void
44
16.7M
fu_chunk_set_idx(FuChunk *self, guint idx)
45
16.7M
{
46
828k
  g_return_if_fail(FU_IS_CHUNK(self));
47
828k
  self->idx = idx;
48
828k
}
49
50
/**
51
 * fu_chunk_get_idx:
52
 * @self: a #FuChunk
53
 *
54
 * Gets the index of the chunk.
55
 *
56
 * Returns: index
57
 *
58
 * Since: 1.5.6
59
 **/
60
guint
61
fu_chunk_get_idx(FuChunk *self)
62
3.26k
{
63
3.26k
  g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXUINT);
64
3.26k
  return self->idx;
65
3.26k
}
66
67
/**
68
 * fu_chunk_set_page:
69
 * @self: a #FuChunk
70
 * @page: page number, starting at 0
71
 *
72
 * Sets the page of the chunk.
73
 *
74
 * Since: 1.5.6
75
 **/
76
void
77
fu_chunk_set_page(FuChunk *self, guint page)
78
828k
{
79
828k
  g_return_if_fail(FU_IS_CHUNK(self));
80
828k
  self->page = page;
81
828k
}
82
83
/**
84
 * fu_chunk_get_page:
85
 * @self: a #FuChunk
86
 *
87
 * Gets the page of the chunk.
88
 *
89
 * Returns: page
90
 *
91
 * Since: 1.5.6
92
 **/
93
guint
94
fu_chunk_get_page(FuChunk *self)
95
0
{
96
0
  g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXUINT);
97
0
  return self->page;
98
0
}
99
100
/**
101
 * fu_chunk_set_address:
102
 * @self: a #FuChunk
103
 * @address: memory address
104
 *
105
 * Sets the address of the chunk.
106
 *
107
 * Since: 1.5.6
108
 **/
109
void
110
fu_chunk_set_address(FuChunk *self, gsize address)
111
1.77M
{
112
1.77M
  g_return_if_fail(FU_IS_CHUNK(self));
113
1.77M
  self->address = address;
114
1.77M
}
115
116
/**
117
 * fu_chunk_get_address:
118
 * @self: a #FuChunk
119
 *
120
 * Gets the address of the chunk.
121
 *
122
 * Returns: address
123
 *
124
 * Since: 1.5.6
125
 **/
126
gsize
127
fu_chunk_get_address(FuChunk *self)
128
1.77M
{
129
1.77M
  g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXSIZE);
130
1.77M
  return self->address;
131
1.77M
}
132
133
/**
134
 * fu_chunk_get_data:
135
 * @self: a #FuChunk
136
 *
137
 * Gets the data of the chunk.
138
 *
139
 * Returns: bytes
140
 *
141
 * Since: 1.5.6
142
 **/
143
const guint8 *
144
fu_chunk_get_data(FuChunk *self)
145
2.03M
{
146
2.03M
  g_return_val_if_fail(FU_IS_CHUNK(self), NULL);
147
2.03M
  return self->data;
148
2.03M
}
149
150
/**
151
 * fu_chunk_get_data_out:
152
 * @self: a #FuChunk
153
 *
154
 * Gets the mutable data of the chunk.
155
 *
156
 * WARNING: At the moment fu_chunk_get_data_out() returns the same data as
157
 * fu_chunk_get_data() in all cases. The caller should verify the data passed to
158
 * fu_chunk_array_new() is also writable (i.e. not `const` or `mmap`) before
159
 * using this function.
160
 *
161
 * Returns: (transfer none): bytes
162
 *
163
 * Since: 1.5.6
164
 **/
165
guint8 *
166
fu_chunk_get_data_out(FuChunk *self)
167
0
{
168
0
  g_return_val_if_fail(FU_IS_CHUNK(self), NULL);
169
170
  /* warn, but allow to proceed */
171
0
  if (!self->is_mutable) {
172
0
    g_critical("calling fu_chunk_get_data_out() from immutable chunk");
173
0
    self->is_mutable = TRUE;
174
0
  }
175
0
  return (guint8 *)self->data;
176
0
}
177
178
/**
179
 * fu_chunk_get_data_sz:
180
 * @self: a #FuChunk
181
 *
182
 * Gets the data size of the chunk.
183
 *
184
 * Returns: size in bytes
185
 *
186
 * Since: 1.5.6
187
 **/
188
gsize
189
fu_chunk_get_data_sz(FuChunk *self)
190
3.24M
{
191
3.24M
  g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXSIZE);
192
3.24M
  return self->data_sz;
193
3.24M
}
194
195
/* private */
196
void
197
fu_chunk_set_data_sz(FuChunk *self, gsize data_sz)
198
0
{
199
0
  g_return_if_fail(FU_IS_CHUNK(self));
200
0
  self->data_sz = data_sz;
201
0
}
202
203
/**
204
 * fu_chunk_set_bytes:
205
 * @self: a #FuChunk
206
 * @bytes: (nullable): data
207
 *
208
 * Sets the data to use for the chunk.
209
 *
210
 * Since: 1.5.6
211
 **/
212
void
213
fu_chunk_set_bytes(FuChunk *self, GBytes *bytes)
214
1.77M
{
215
1.77M
  g_return_if_fail(FU_IS_CHUNK(self));
216
217
  /* not changed */
218
1.77M
  if (self->bytes == bytes)
219
0
    return;
220
221
1.77M
  g_clear_pointer(&self->bytes, g_bytes_unref);
222
1.77M
  if (bytes != NULL) {
223
1.77M
    self->bytes = g_bytes_ref(bytes);
224
1.77M
    self->data = g_bytes_get_data(bytes, NULL);
225
1.77M
    self->data_sz = g_bytes_get_size(bytes);
226
1.77M
  }
227
1.77M
}
228
229
/**
230
 * fu_chunk_get_bytes:
231
 * @self: a #FuChunk
232
 *
233
 * Gets the data of the chunk.
234
 *
235
 * Returns: (transfer full): data
236
 *
237
 * Since: 1.5.6
238
 **/
239
GBytes *
240
fu_chunk_get_bytes(FuChunk *self)
241
0
{
242
0
  g_return_val_if_fail(FU_IS_CHUNK(self), NULL);
243
0
  if (self->bytes != NULL)
244
0
    return g_bytes_ref(self->bytes);
245
0
  return g_bytes_new_static(self->data, self->data_sz);
246
0
}
247
248
/**
249
 * fu_chunk_new:
250
 * @idx: the packet number
251
 * @page: the hardware memory page
252
 * @address: the address *within* the page
253
 * @data: the data
254
 * @data_sz: size of @data_sz
255
 *
256
 * Creates a new packet of chunked data.
257
 *
258
 * Returns: (transfer full): a #FuChunk
259
 *
260
 * Since: 1.1.2
261
 **/
262
FuChunk *
263
fu_chunk_new(guint idx, guint page, gsize address, const guint8 *data, gsize data_sz)
264
0
{
265
0
  FuChunk *self = g_object_new(FU_TYPE_CHUNK, NULL);
266
0
  self->idx = idx;
267
0
  self->page = page;
268
0
  self->address = address;
269
0
  self->data = data;
270
0
  self->data_sz = data_sz;
271
0
  return self;
272
0
}
273
274
/**
275
 * fu_chunk_bytes_new:
276
 * @bytes: (nullable): data
277
 *
278
 * Creates a new packet of data.
279
 *
280
 * Returns: (transfer full): a #FuChunk
281
 *
282
 * Since: 1.5.6
283
 **/
284
FuChunk *
285
fu_chunk_bytes_new(GBytes *bytes)
286
1.77M
{
287
1.77M
  FuChunk *self = g_object_new(FU_TYPE_CHUNK, NULL);
288
1.77M
  fu_chunk_set_bytes(self, bytes);
289
1.77M
  return self;
290
1.77M
}
291
292
void
293
fu_chunk_export(FuChunk *self, FuFirmwareExportFlags flags, XbBuilderNode *bn)
294
0
{
295
0
  fu_xmlb_builder_insert_kx(bn, "idx", self->idx);
296
0
  fu_xmlb_builder_insert_kx(bn, "page", self->page);
297
0
  fu_xmlb_builder_insert_kx(bn, "addr", self->address);
298
0
  if (self->data != NULL) {
299
0
    g_autofree gchar *datastr = NULL;
300
0
    g_autofree gchar *dataszstr = g_strdup_printf("0x%x", (guint)self->data_sz);
301
0
    if (flags & FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA) {
302
0
      datastr = fu_memstrsafe(self->data,
303
0
            self->data_sz,
304
0
            0x0,
305
0
            MIN(self->data_sz, 16),
306
0
            NULL);
307
0
    } else {
308
0
      datastr = fu_base64_encode(self->data, self->data_sz);
309
0
    }
310
0
    xb_builder_node_insert_text(bn, "data", datastr, "size", dataszstr, NULL);
311
0
  } else {
312
0
    fu_xmlb_builder_insert_kx(bn, "size", self->data_sz);
313
0
  }
314
0
}
315
316
/**
317
 * fu_chunk_to_string:
318
 * @self: a #FuChunk
319
 *
320
 * Converts the chunked packet to a string representation.
321
 *
322
 * Returns: (transfer full): a string
323
 *
324
 * Since: 1.1.2
325
 **/
326
gchar *
327
fu_chunk_to_string(FuChunk *self)
328
0
{
329
0
  g_autoptr(XbBuilderNode) bn = xb_builder_node_new("chunk");
330
0
  fu_chunk_export(self, FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA, bn);
331
0
  return xb_builder_node_export(bn,
332
0
              XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE |
333
0
            XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY |
334
0
            XB_NODE_EXPORT_FLAG_FORMAT_INDENT,
335
0
              NULL);
336
0
}
337
338
/**
339
 * fu_chunk_array_to_string:
340
 * @chunks: (element-type FuChunk): array of chunks
341
 *
342
 * Converts all the chunked packets in an array to a string representation.
343
 *
344
 * Returns: (transfer full): a string
345
 *
346
 * Since: 1.0.1
347
 **/
348
gchar *
349
fu_chunk_array_to_string(GPtrArray *chunks)
350
0
{
351
0
  g_autoptr(XbBuilderNode) bn = xb_builder_node_new("chunks");
352
0
  for (guint i = 0; i < chunks->len; i++) {
353
0
    FuChunk *chk = g_ptr_array_index(chunks, i);
354
0
    g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "chunk", NULL);
355
0
    fu_chunk_export(chk, FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA, bc);
356
0
  }
357
0
  return xb_builder_node_export(bn,
358
0
              XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE |
359
0
            XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY |
360
0
            XB_NODE_EXPORT_FLAG_FORMAT_INDENT,
361
0
              NULL);
362
0
}
363
364
/**
365
 * fu_chunk_array_mutable_new:
366
 * @data: a mutable blob of memory
367
 * @data_sz: size of @data_sz
368
 * @addr_offset: the hardware address offset, or 0
369
 * @page_sz: the hardware page size, or 0
370
 * @packet_sz: the transfer size, or 0
371
 * @error: (nullable): optional return location for an error
372
 *
373
 * Chunks a mutable blob of memory into packets, ensuring each packet does not
374
 * cross a package boundary and is less that a specific transfer size.
375
 *
376
 * Returns: (transfer container) (element-type FuChunk): array of packets
377
 *
378
 * Since: 2.1.2
379
 **/
380
GPtrArray *
381
fu_chunk_array_mutable_new(guint8 *data,
382
         gsize data_sz,
383
         gsize addr_offset,
384
         gsize page_sz,
385
         gsize packet_sz,
386
         GError **error)
387
0
{
388
0
  GPtrArray *chunks;
389
390
0
  g_return_val_if_fail(data != NULL, NULL);
391
0
  g_return_val_if_fail(data_sz > 0, NULL);
392
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
393
394
0
  chunks = fu_chunk_array_new(data, data_sz, addr_offset, page_sz, packet_sz, error);
395
0
  if (chunks == NULL)
396
0
    return NULL;
397
0
  for (guint i = 0; i < chunks->len; i++) {
398
0
    FuChunk *chk = g_ptr_array_index(chunks, i);
399
0
    chk->is_mutable = TRUE;
400
0
  }
401
0
  return chunks;
402
0
}
403
404
/**
405
 * fu_chunk_array_new:
406
 * @data: (nullable): an optional linear blob of memory
407
 * @data_sz: size of @data_sz
408
 * @addr_offset: the hardware address offset, or 0
409
 * @page_sz: the hardware page size, or 0
410
 * @packet_sz: the transfer size, or 0
411
 * @error: (nullable): optional return location for an error
412
 *
413
 * Chunks a linear blob of memory into packets, ensuring each packet does not
414
 * cross a package boundary and is less that a specific transfer size.
415
 *
416
 * Returns: (transfer container) (element-type FuChunk): array of packets, or %NULL
417
 *
418
 * Since: 2.1.2
419
 **/
420
GPtrArray *
421
fu_chunk_array_new(const guint8 *data,
422
       gsize data_sz,
423
       gsize addr_offset,
424
       gsize page_sz,
425
       gsize packet_sz,
426
       GError **error)
427
0
{
428
0
  gsize offset = 0;
429
0
  g_autoptr(GPtrArray) chunks =
430
0
      g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
431
432
0
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
433
0
  g_return_val_if_fail(packet_sz > 0, NULL);
434
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
435
436
0
  while (offset < data_sz) {
437
0
    gsize chunksz = MIN(packet_sz, data_sz - offset);
438
0
    gsize page = 0;
439
0
    gsize address_offset = addr_offset;
440
441
    /* if page_sz is not specified then all the pages are 0 */
442
0
    if (!fu_size_checked_inc(&address_offset, offset, error))
443
0
      return NULL;
444
0
    if (page_sz > 0) {
445
0
      gsize offset_next = offset;
446
447
0
      address_offset %= page_sz;
448
0
      if (!fu_size_checked_inc(&offset_next, addr_offset, error))
449
0
        return NULL;
450
0
      page = offset_next / page_sz;
451
0
    }
452
453
    /* cut the packet so it does not straddle multiple blocks */
454
0
    if (page_sz != packet_sz && page_sz > 0) {
455
0
      gsize chunksz_tmp;
456
0
      gsize offset_next = offset;
457
458
0
      if (!fu_size_checked_inc(&offset_next, packet_sz, error))
459
0
        return NULL;
460
0
      chunksz_tmp = MIN(chunksz, offset_next % page_sz);
461
0
      if (chunksz_tmp != 0)
462
0
        chunksz = chunksz_tmp;
463
0
    }
464
0
    g_ptr_array_add(chunks,
465
0
        fu_chunk_new(chunks->len,
466
0
               page,
467
0
               address_offset,
468
0
               data != NULL ? data + offset : NULL,
469
0
               chunksz));
470
0
    if (!fu_size_checked_inc(&offset, chunksz, error))
471
0
      return NULL;
472
0
  }
473
474
0
#ifndef SUPPORTED_BUILD
475
  /* show the programmer a warning */
476
0
  if (page_sz == 0x0 && chunks->len > 10000) {
477
0
    g_warning("fu_chunk_array_new() generated a lot of chunks (%u), "
478
0
        "maybe use FuChunkArray instead?",
479
0
        chunks->len);
480
0
  }
481
0
#endif
482
0
  return g_steal_pointer(&chunks);
483
0
}
484
485
/* private */
486
gboolean
487
fu_chunk_build(FuChunk *self, XbNode *n, GError **error)
488
0
{
489
0
  guint64 tmp;
490
0
  g_autoptr(XbNode) data = NULL;
491
492
0
  g_return_val_if_fail(FU_IS_CHUNK(self), FALSE);
493
0
  g_return_val_if_fail(XB_IS_NODE(n), FALSE);
494
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
495
496
  /* optional properties */
497
0
  tmp = xb_node_query_text_as_uint(n, "idx", NULL);
498
0
  if (tmp != G_MAXUINT64)
499
0
    self->idx = tmp;
500
0
  tmp = xb_node_query_text_as_uint(n, "page", NULL);
501
0
  if (tmp != G_MAXUINT64)
502
0
    self->page = tmp;
503
0
  tmp = xb_node_query_text_as_uint(n, "addr", NULL);
504
0
  if (tmp != G_MAXUINT64)
505
0
    self->address = tmp;
506
0
  data = xb_node_query_first(n, "data", NULL);
507
0
  if (data != NULL && xb_node_get_text(data) != NULL) {
508
0
    gsize bufsz = 0;
509
0
    g_autofree guchar *buf = NULL;
510
0
    g_autoptr(GBytes) blob = NULL;
511
0
    buf = g_base64_decode(xb_node_get_text(data), &bufsz);
512
0
    blob = g_bytes_new(buf, bufsz);
513
0
    fu_chunk_set_bytes(self, blob);
514
0
  } else if (data != NULL) {
515
0
    g_autoptr(GBytes) blob = NULL;
516
0
    blob = g_bytes_new(NULL, 0);
517
0
    fu_chunk_set_bytes(self, blob);
518
0
  }
519
520
  /* success */
521
0
  return TRUE;
522
0
}
523
524
static void
525
fu_chunk_finalize(GObject *object)
526
1.77M
{
527
1.77M
  FuChunk *self = FU_CHUNK(object);
528
1.77M
  if (self->bytes != NULL)
529
1.77M
    g_bytes_unref(self->bytes);
530
1.77M
  G_OBJECT_CLASS(fu_chunk_parent_class)->finalize(object);
531
1.77M
}
532
533
static void
534
fu_chunk_class_init(FuChunkClass *klass)
535
10
{
536
10
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
537
10
  object_class->finalize = fu_chunk_finalize;
538
10
}
539
540
static void
541
fu_chunk_init(FuChunk *self)
542
1.77M
{
543
1.77M
}