/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 | } |