Coverage Report

Created: 2025-08-29 06:48

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