Coverage Report

Created: 2026-05-30 06:50

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
  GInputStream *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.76M
G_DEFINE_TYPE(FuChunkArray, fu_chunk_array, G_TYPE_OBJECT)
34
2.76M
35
2.76M
/**
36
2.76M
 * fu_chunk_array_length:
37
2.76M
 * @self: a #FuChunkArray
38
2.76M
 *
39
2.76M
 * Gets the number of chunks.
40
2.76M
 *
41
2.76M
 * Returns: integer
42
2.76M
 *
43
2.76M
 * Since: 1.9.6
44
2.76M
 **/
45
2.76M
guint
46
2.76M
fu_chunk_array_length(FuChunkArray *self)
47
2.76M
{
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.86M
{
59
1.86M
  gsize chunksz_tmp = MIN(self->packet_sz, self->total_size - offset);
60
1.86M
  gsize page_tmp = 0;
61
1.86M
  gsize address_tmp = self->addr_offset + offset;
62
63
  /* if page_sz is not specified then all the pages are 0 */
64
1.86M
  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.86M
  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.86M
  if (address != NULL)
78
894k
    *address = address_tmp;
79
1.86M
  if (page != NULL)
80
894k
    *page = page_tmp;
81
1.86M
  if (chunksz != NULL)
82
1.86M
    *chunksz = chunksz_tmp;
83
1.86M
}
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
894k
{
100
894k
  gsize address = 0;
101
894k
  gsize chunksz = 0;
102
894k
  gsize offset;
103
894k
  gsize page = 0;
104
894k
  g_autoptr(FuChunk) chk = NULL;
105
106
894k
  g_return_val_if_fail(FU_IS_CHUNK_ARRAY(self), NULL);
107
894k
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
108
109
894k
  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
894k
  offset = g_array_index(self->offsets, gsize, idx);
116
894k
  fu_chunk_array_calculate_chunk_for_offset(self, offset, &address, &page, &chunksz);
117
894k
  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
894k
  if (self->blob != NULL) {
124
639k
    g_autoptr(GBytes) blob_chk = NULL;
125
126
639k
    blob_chk = fu_bytes_new_offset(self->blob, offset, chunksz, error);
127
639k
    if (blob_chk == NULL)
128
0
      return NULL;
129
639k
    chk = fu_chunk_bytes_new(blob_chk);
130
639k
  } else if (self->stream != NULL) {
131
254k
    g_autoptr(GBytes) blob_chk =
132
254k
        fu_input_stream_read_bytes(self->stream, offset, chunksz, NULL, error);
133
254k
    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
254k
    chk = fu_chunk_bytes_new(blob_chk);
141
254k
  } else {
142
0
    chk = fu_chunk_bytes_new(NULL);
143
0
    fu_chunk_set_data_sz(chk, chunksz);
144
0
  }
145
894k
  fu_chunk_set_idx(chk, idx);
146
894k
  fu_chunk_set_page(chk, page);
147
894k
  fu_chunk_set_address(chk, address);
148
894k
  return g_steal_pointer(&chk);
149
894k
}
150
151
static void
152
fu_chunk_array_ensure_offsets(FuChunkArray *self)
153
242k
{
154
242k
  gsize offset = 0;
155
1.21M
  while (offset < self->total_size) {
156
968k
    gsize chunksz = 0;
157
968k
    fu_chunk_array_calculate_chunk_for_offset(self, offset, NULL, NULL, &chunksz);
158
968k
    g_array_append_val(self->offsets, offset);
159
968k
    if (chunksz > G_MAXSIZE - offset) {
160
0
      g_critical("offset overflow at offset %" G_GSIZE_FORMAT
161
0
           " with chunk size %" G_GSIZE_FORMAT,
162
0
           offset,
163
0
           chunksz);
164
0
      return;
165
0
    }
166
968k
    offset += chunksz;
167
968k
  }
168
242k
}
169
170
/**
171
 * fu_chunk_array_new_from_bytes:
172
 * @blob: data
173
 * @addr_offset: the hardware address offset, or %FU_CHUNK_ADDR_OFFSET_NONE
174
 * @page_sz: the hardware page size, typically %FU_CHUNK_PAGESZ_NONE
175
 * @packet_sz: the packet size, or 0x0
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
181
 *
182
 * Since: 1.9.6
183
 **/
184
FuChunkArray *
185
fu_chunk_array_new_from_bytes(GBytes *blob, gsize addr_offset, gsize page_sz, gsize packet_sz)
186
182
{
187
182
  g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL);
188
189
182
  g_return_val_if_fail(blob != NULL, NULL);
190
182
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
191
192
182
  self->addr_offset = addr_offset;
193
182
  self->page_sz = page_sz;
194
182
  self->packet_sz = packet_sz;
195
182
  self->blob = g_bytes_ref(blob);
196
182
  self->total_size = g_bytes_get_size(self->blob);
197
198
  /* success */
199
182
  fu_chunk_array_ensure_offsets(self);
200
182
  return g_steal_pointer(&self);
201
182
}
202
203
/**
204
 * fu_chunk_array_new_virtual:
205
 * @bufsz: size of the buffer
206
 * @addr_offset: the hardware address offset, or %FU_CHUNK_ADDR_OFFSET_NONE
207
 * @page_sz: the hardware page size, typically %FU_CHUNK_PAGESZ_NONE
208
 * @packet_sz: the packet size, or 0x0
209
 *
210
 * Chunks a virtual buffer memory into packets, ensuring each packet is less that a specific
211
 * transfer size.
212
 *
213
 * Returns: (transfer full): a #FuChunkArray
214
 *
215
 * Since: 2.0.2
216
 **/
217
FuChunkArray *
218
fu_chunk_array_new_virtual(gsize bufsz, gsize addr_offset, gsize page_sz, gsize packet_sz)
219
3.39k
{
220
3.39k
  g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL);
221
222
3.39k
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
223
224
3.39k
  self->addr_offset = addr_offset;
225
3.39k
  self->page_sz = page_sz;
226
3.39k
  self->packet_sz = packet_sz;
227
3.39k
  self->total_size = bufsz;
228
229
  /* success */
230
3.39k
  fu_chunk_array_ensure_offsets(self);
231
3.39k
  return g_steal_pointer(&self);
232
3.39k
}
233
234
/**
235
 * fu_chunk_array_new_from_stream:
236
 * @stream: a #GInputStream
237
 * @addr_offset: the hardware address offset, or %FU_CHUNK_ADDR_OFFSET_NONE
238
 * @page_sz: the hardware page size, typically %FU_CHUNK_PAGESZ_NONE
239
 * @packet_sz: the packet size, or 0x0
240
 * @error: (nullable): optional return location for an error
241
 *
242
 * Chunks a linear stream into packets, ensuring each packet is less that a specific
243
 * transfer size.
244
 *
245
 * Returns: (transfer full): a #FuChunkArray, or #NULL on error
246
 *
247
 * Since: 2.0.2
248
 **/
249
FuChunkArray *
250
fu_chunk_array_new_from_stream(GInputStream *stream,
251
             gsize addr_offset,
252
             gsize page_sz,
253
             gsize packet_sz,
254
             GError **error)
255
238k
{
256
238k
  g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL);
257
258
238k
  g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL);
259
238k
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
260
238k
  g_return_val_if_fail(page_sz == 0 || page_sz >= packet_sz, NULL);
261
262
238k
  if (!fu_input_stream_size(stream, &self->total_size, error))
263
0
    return NULL;
264
238k
  if (!g_seekable_seek(G_SEEKABLE(stream), 0x0, G_SEEK_SET, NULL, error))
265
0
    return NULL;
266
238k
  self->addr_offset = addr_offset;
267
238k
  self->page_sz = page_sz;
268
238k
  self->packet_sz = packet_sz;
269
238k
  self->stream = g_object_ref(stream);
270
271
  /* success */
272
238k
  fu_chunk_array_ensure_offsets(self);
273
238k
  return g_steal_pointer(&self);
274
238k
}
275
276
static void
277
fu_chunk_array_finalize(GObject *object)
278
242k
{
279
242k
  FuChunkArray *self = FU_CHUNK_ARRAY(object);
280
242k
  g_array_unref(self->offsets);
281
242k
  if (self->blob != NULL)
282
182
    g_bytes_unref(self->blob);
283
242k
  if (self->stream != NULL)
284
238k
    g_object_unref(self->stream);
285
242k
  G_OBJECT_CLASS(fu_chunk_array_parent_class)->finalize(object);
286
242k
}
287
288
static void
289
fu_chunk_array_class_init(FuChunkArrayClass *klass)
290
10
{
291
10
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
292
10
  object_class->finalize = fu_chunk_array_finalize;
293
10
}
294
295
static void
296
fu_chunk_array_init(FuChunkArray *self)
297
242k
{
298
242k
  self->offsets = g_array_new(FALSE, FALSE, sizeof(gsize));
299
242k
}