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