Coverage Report

Created: 2026-07-30 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-chunk-array.c
Line
Count
Source
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 "FuChunkArray"
8
9
#include "config.h"
10
11
#include "fu-bytes.h"
12
#include "fu-chunk-array.h"
13
#include "fu-chunk-private.h"
14
#include "fu-input-stream.h"
15
16
/**
17
 * FuChunkArray:
18
 *
19
 * Create chunked data with address and index as required.
20
 */
21
22
struct _FuChunkArray {
23
  GObject parent_instance;
24
  GBytes *blob;
25
  FuInputStream *stream;
26
  gsize addr_offset;
27
  gsize page_sz;
28
  gsize packet_sz;
29
  GArray *offsets; /* of gsize */
30
  gsize total_size;
31
};
32
33
2.75M
G_DEFINE_TYPE(FuChunkArray, fu_chunk_array, G_TYPE_OBJECT)
34
2.75M
35
2.75M
/**
36
2.75M
 * fu_chunk_array_length:
37
2.75M
 * @self: a #FuChunkArray
38
2.75M
 *
39
2.75M
 * Gets the number of chunks.
40
2.75M
 *
41
2.75M
 * Returns: integer
42
2.75M
 *
43
2.75M
 * Since: 1.9.6
44
2.75M
 **/
45
2.75M
guint
46
2.75M
fu_chunk_array_length(FuChunkArray *self)
47
2.75M
{
48
1.38M
  g_return_val_if_fail(FU_IS_CHUNK_ARRAY(self), G_MAXUINT);
49
1.38M
  return self->offsets->len;
50
1.38M
}
51
52
static void
53
fu_chunk_array_calculate_chunk_for_offset(FuChunkArray *self,
54
            gsize offset,
55
            gsize *address,
56
            gsize *page,
57
            gsize *chunksz)
58
1.90M
{
59
1.90M
  gsize chunksz_tmp = MIN(self->packet_sz, self->total_size - offset);
60
1.90M
  gsize page_tmp = 0;
61
1.90M
  gsize address_tmp = self->addr_offset + offset;
62
63
  /* if page_sz is not specified then all the pages are 0 */
64
1.90M
  if (self->page_sz > 0) {
65
0
    address_tmp %= self->page_sz;
66
0
    page_tmp = (offset + self->addr_offset) / self->page_sz;
67
0
  }
68
69
  /* cut the packet so it does not straddle multiple blocks */
70
1.90M
  if (self->page_sz != self->packet_sz && self->page_sz > 0) {
71
0
    gsize remainder = (offset + self->addr_offset) % self->page_sz;
72
0
    if (remainder != 0)
73
0
      chunksz_tmp = MIN(chunksz_tmp, self->page_sz - remainder);
74
0
  }
75
76
  /* all optional */
77
1.90M
  if (address != NULL)
78
875k
    *address = address_tmp;
79
1.90M
  if (page != NULL)
80
875k
    *page = page_tmp;
81
1.90M
  if (chunksz != NULL)
82
1.90M
    *chunksz = chunksz_tmp;
83
1.90M
}
84
85
/**
86
 * fu_chunk_array_index:
87
 * @self: a #FuChunkArray
88
 * @idx: the chunk index
89
 * @error: (nullable): optional return location for an error
90
 *
91
 * Gets the next chunk.
92
 *
93
 * Returns: (transfer full): a #FuChunk or %NULL if not valid
94
 *
95
 * Since: 1.9.6
96
 **/
97
FuChunk *
98
fu_chunk_array_index(FuChunkArray *self, guint idx, GError **error)
99
875k
{
100
875k
  gsize address = 0;
101
875k
  gsize chunksz = 0;
102
875k
  gsize offset;
103
875k
  gsize page = 0;
104
875k
  g_autoptr(FuChunk) chk = NULL;
105
106
875k
  g_return_val_if_fail(FU_IS_CHUNK_ARRAY(self), NULL);
107
875k
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
108
109
875k
  if (idx >= self->offsets->len) {
110
0
    g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "idx %u invalid", idx);
111
0
    return NULL;
112
0
  }
113
114
  /* calculate address, page and chunk size from the offset */
115
875k
  offset = g_array_index(self->offsets, gsize, idx);
116
875k
  fu_chunk_array_calculate_chunk_for_offset(self, offset, &address, &page, &chunksz);
117
875k
  if (chunksz == 0) {
118
0
    g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "idx %u zero sized", idx);
119
0
    return NULL;
120
0
  }
121
122
  /* create new chunk */
123
875k
  if (self->blob != NULL) {
124
612k
    g_autoptr(GBytes) blob_chk = NULL;
125
126
612k
    blob_chk = fu_bytes_new_offset(self->blob, offset, chunksz, error);
127
612k
    if (blob_chk == NULL)
128
0
      return NULL;
129
612k
    chk = fu_chunk_bytes_new(blob_chk);
130
612k
  } else if (self->stream != NULL) {
131
262k
    g_autoptr(GBytes) blob_chk =
132
262k
        fu_input_stream_read_bytes(self->stream, offset, chunksz, NULL, error);
133
262k
    if (blob_chk == NULL) {
134
0
      g_prefix_error(error,
135
0
               "failed to get stream at 0x%x for 0x%x: ",
136
0
               (guint)offset,
137
0
               (guint)chunksz);
138
0
      return NULL;
139
0
    }
140
262k
    chk = fu_chunk_bytes_new(blob_chk);
141
262k
  } else {
142
0
    chk = fu_chunk_bytes_new(NULL);
143
0
    fu_chunk_set_data_sz(chk, chunksz);
144
0
  }
145
875k
  fu_chunk_set_idx(chk, idx);
146
875k
  fu_chunk_set_page(chk, page);
147
875k
  fu_chunk_set_address(chk, address);
148
875k
  return g_steal_pointer(&chk);
149
875k
}
150
151
static void
152
fu_chunk_array_ensure_offsets(FuChunkArray *self)
153
251k
{
154
251k
  gsize offset = 0;
155
1.28M
  while (offset < self->total_size) {
156
1.03M
    gsize chunksz = 0;
157
1.03M
    fu_chunk_array_calculate_chunk_for_offset(self, offset, NULL, NULL, &chunksz);
158
1.03M
    g_array_append_val(self->offsets, offset);
159
1.03M
    if (chunksz > G_MAXSIZE - offset) {
160
0
      g_critical("offset overflow at offset %zu with chunk size %zu",
161
0
           offset,
162
0
           chunksz);
163
0
      return;
164
0
    }
165
1.03M
    offset += chunksz;
166
1.03M
  }
167
251k
}
168
169
/**
170
 * fu_chunk_array_new_from_bytes:
171
 * @blob: data
172
 * @addr_offset: the hardware address offset, or %FU_CHUNK_ADDR_OFFSET_NONE
173
 * @page_sz: the hardware page size, typically %FU_CHUNK_PAGESZ_NONE
174
 * @packet_sz: the packet size, or 0x0
175
 * @error: (nullable): optional return location for an error
176
 *
177
 * Chunks a linear blob of memory into packets, ensuring each packet is less that a specific
178
 * transfer size.
179
 *
180
 * Returns: (transfer full): a #FuChunkArray, or %NULL on error
181
 *
182
 * Since: 1.9.6
183
 **/
184
FuChunkArray *
185
fu_chunk_array_new_from_bytes(GBytes *blob,
186
            gsize addr_offset,
187
            gsize page_sz,
188
            gsize packet_sz,
189
            GError **error)
190
168
{
191
168
  g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL);
192
193
168
  g_return_val_if_fail(blob != NULL, NULL);
194
168
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
195
168
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
196
197
  /* sanity check */
198
168
  if (packet_sz == 0) {
199
0
    g_set_error_literal(error,
200
0
            FWUPD_ERROR,
201
0
            FWUPD_ERROR_INVALID_DATA,
202
0
            "packet size cannot be zero");
203
0
    return NULL;
204
0
  }
205
206
168
  self->addr_offset = addr_offset;
207
168
  self->page_sz = page_sz;
208
168
  self->packet_sz = packet_sz;
209
168
  self->blob = g_bytes_ref(blob);
210
168
  self->total_size = g_bytes_get_size(self->blob);
211
212
  /* success */
213
168
  fu_chunk_array_ensure_offsets(self);
214
168
  return g_steal_pointer(&self);
215
168
}
216
217
/**
218
 * fu_chunk_array_new_virtual:
219
 * @bufsz: size of the buffer
220
 * @addr_offset: the hardware address offset, or %FU_CHUNK_ADDR_OFFSET_NONE
221
 * @page_sz: the hardware page size, typically %FU_CHUNK_PAGESZ_NONE
222
 * @packet_sz: the packet size, or 0x0
223
 *
224
 * Chunks a virtual buffer memory into packets, ensuring each packet is less that a specific
225
 * transfer size.
226
 *
227
 * Returns: (transfer full): a #FuChunkArray
228
 *
229
 * Since: 2.0.2
230
 **/
231
FuChunkArray *
232
fu_chunk_array_new_virtual(gsize bufsz, gsize addr_offset, gsize page_sz, gsize packet_sz)
233
4.25k
{
234
4.25k
  g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL);
235
236
4.25k
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
237
238
4.25k
  self->addr_offset = addr_offset;
239
4.25k
  self->page_sz = page_sz;
240
4.25k
  self->packet_sz = packet_sz;
241
4.25k
  self->total_size = bufsz;
242
243
  /* success */
244
4.25k
  fu_chunk_array_ensure_offsets(self);
245
4.25k
  return g_steal_pointer(&self);
246
4.25k
}
247
248
/**
249
 * fu_chunk_array_new_from_stream:
250
 * @stream: a #FuInputStream
251
 * @addr_offset: the hardware address offset, or %FU_CHUNK_ADDR_OFFSET_NONE
252
 * @page_sz: the hardware page size, typically %FU_CHUNK_PAGESZ_NONE
253
 * @packet_sz: the packet size, or 0x0
254
 * @error: (nullable): optional return location for an error
255
 *
256
 * Chunks a linear stream into packets, ensuring each packet is less that a specific
257
 * transfer size.
258
 *
259
 * Returns: (transfer full): a #FuChunkArray, or #NULL on error
260
 *
261
 * Since: 2.0.2
262
 **/
263
FuChunkArray *
264
fu_chunk_array_new_from_stream(FuInputStream *stream,
265
             gsize addr_offset,
266
             gsize page_sz,
267
             gsize packet_sz,
268
             GError **error)
269
247k
{
270
247k
  g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL);
271
272
247k
  g_return_val_if_fail(FU_IS_INPUT_STREAM(stream), NULL);
273
247k
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
274
247k
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
275
276
  /* sanity check */
277
247k
  if (packet_sz == 0) {
278
0
    g_set_error_literal(error,
279
0
            FWUPD_ERROR,
280
0
            FWUPD_ERROR_INVALID_DATA,
281
0
            "packet size cannot be zero");
282
0
    return NULL;
283
0
  }
284
285
247k
  if (!fu_input_stream_size(stream, &self->total_size, error))
286
0
    return NULL;
287
247k
  if (!g_seekable_seek(G_SEEKABLE(stream), 0x0, G_SEEK_SET, NULL, error))
288
0
    return NULL;
289
247k
  self->addr_offset = addr_offset;
290
247k
  self->page_sz = page_sz;
291
247k
  self->packet_sz = packet_sz;
292
247k
  self->stream = g_object_ref(stream);
293
294
  /* success */
295
247k
  fu_chunk_array_ensure_offsets(self);
296
247k
  return g_steal_pointer(&self);
297
247k
}
298
299
static void
300
fu_chunk_array_finalize(GObject *object)
301
251k
{
302
251k
  FuChunkArray *self = FU_CHUNK_ARRAY(object);
303
251k
  g_array_unref(self->offsets);
304
251k
  if (self->blob != NULL)
305
168
    g_bytes_unref(self->blob);
306
251k
  if (self->stream != NULL)
307
247k
    g_object_unref(self->stream);
308
251k
  G_OBJECT_CLASS(fu_chunk_array_parent_class)->finalize(object);
309
251k
}
310
311
static void
312
fu_chunk_array_class_init(FuChunkArrayClass *klass)
313
11
{
314
11
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
315
11
  object_class->finalize = fu_chunk_array_finalize;
316
11
}
317
318
static void
319
fu_chunk_array_init(FuChunkArray *self)
320
251k
{
321
251k
  self->offsets = g_array_new(FALSE, FALSE, sizeof(gsize));
322
251k
}