/src/fwupd/libfwupdplugin/fu-bytes.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2017 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuCommon" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fwupd-error.h" |
12 | | |
13 | | #include "fu-byte-array.h" |
14 | | #include "fu-bytes.h" |
15 | | #include "fu-mem.h" |
16 | | |
17 | | /** |
18 | | * fu_bytes_set_contents: |
19 | | * @filename: a filename |
20 | | * @bytes: data to write |
21 | | * @error: (nullable): optional return location for an error |
22 | | * |
23 | | * Writes a blob of data to a filename, creating the parent directories as |
24 | | * required. |
25 | | * |
26 | | * Returns: %TRUE for success |
27 | | * |
28 | | * Since: 1.8.2 |
29 | | **/ |
30 | | gboolean |
31 | | fu_bytes_set_contents(const gchar *filename, GBytes *bytes, GError **error) |
32 | 0 | { |
33 | 0 | const gchar *data; |
34 | 0 | gsize size; |
35 | 0 | g_autoptr(GFile) file = NULL; |
36 | 0 | g_autoptr(GFile) file_parent = NULL; |
37 | |
|
38 | 0 | g_return_val_if_fail(filename != NULL, FALSE); |
39 | 0 | g_return_val_if_fail(bytes != NULL, FALSE); |
40 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
41 | | |
42 | 0 | file = g_file_new_for_path(filename); |
43 | 0 | file_parent = g_file_get_parent(file); |
44 | 0 | if (!g_file_query_exists(file_parent, NULL)) { |
45 | 0 | if (!g_file_make_directory_with_parents(file_parent, NULL, error)) |
46 | 0 | return FALSE; |
47 | 0 | } |
48 | 0 | data = g_bytes_get_data(bytes, &size); |
49 | 0 | g_debug("writing %s with 0x%x bytes", filename, (guint)size); |
50 | 0 | return g_file_set_contents(filename, data, size, error); |
51 | 0 | } |
52 | | |
53 | | /** |
54 | | * fu_bytes_get_contents: |
55 | | * @filename: a filename |
56 | | * @error: (nullable): optional return location for an error |
57 | | * |
58 | | * Reads a blob of data from a file. |
59 | | * |
60 | | * Returns: a #GBytes, or %NULL for failure |
61 | | * |
62 | | * Since: 1.8.2 |
63 | | **/ |
64 | | GBytes * |
65 | | fu_bytes_get_contents(const gchar *filename, GError **error) |
66 | 0 | { |
67 | 0 | g_autoptr(GError) error_local = NULL; |
68 | 0 | g_autoptr(GMappedFile) mapped_file = NULL; |
69 | |
|
70 | 0 | g_return_val_if_fail(filename != NULL, NULL); |
71 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
72 | | |
73 | | /* try as a mapped file, falling back to reading it as a blob instead */ |
74 | 0 | mapped_file = g_mapped_file_new(filename, FALSE, &error_local); |
75 | 0 | if (mapped_file == NULL || g_mapped_file_get_length(mapped_file) == 0) { |
76 | 0 | gchar *data = NULL; |
77 | 0 | gsize len = 0; |
78 | 0 | if (!g_file_get_contents(filename, &data, &len, error)) { |
79 | 0 | fwupd_error_convert(error); |
80 | 0 | return NULL; |
81 | 0 | } |
82 | 0 | g_debug("failed to read as mapped file, " |
83 | 0 | "so reading %s of size 0x%x: %s", |
84 | 0 | filename, |
85 | 0 | (guint)len, |
86 | 0 | error_local != NULL ? error_local->message : "zero size"); |
87 | 0 | return g_bytes_new_take(data, len); |
88 | 0 | } |
89 | 0 | g_debug("mapped file %s of size 0x%x", |
90 | 0 | filename, |
91 | 0 | (guint)g_mapped_file_get_length(mapped_file)); |
92 | 0 | return g_mapped_file_get_bytes(mapped_file); |
93 | 0 | } |
94 | | |
95 | | /** |
96 | | * fu_bytes_align: |
97 | | * @bytes: data blob |
98 | | * @blksz: block size in bytes |
99 | | * @padval: the byte used to pad the byte buffer |
100 | | * |
101 | | * Aligns a block of memory to @blksize using the @padval value; if |
102 | | * the block is already aligned then the original @bytes is returned. |
103 | | * |
104 | | * Returns: (transfer full): a #GBytes, possibly @bytes |
105 | | * |
106 | | * Since: 1.8.2 |
107 | | **/ |
108 | | GBytes * |
109 | | fu_bytes_align(GBytes *bytes, gsize blksz, gchar padval) |
110 | 0 | { |
111 | 0 | const guint8 *data; |
112 | 0 | gsize sz; |
113 | |
|
114 | 0 | g_return_val_if_fail(bytes != NULL, NULL); |
115 | 0 | g_return_val_if_fail(blksz > 0, NULL); |
116 | | |
117 | | /* pad */ |
118 | 0 | data = g_bytes_get_data(bytes, &sz); |
119 | 0 | if (sz % blksz != 0) { |
120 | 0 | gsize sz_align = ((sz / blksz) + 1) * blksz; |
121 | 0 | guint8 *data_align = g_malloc(sz_align); |
122 | 0 | memcpy(data_align, data, sz); /* nocheck:blocked */ |
123 | 0 | memset(data_align + sz, padval, sz_align - sz); |
124 | 0 | g_debug("aligning 0x%x bytes to 0x%x", (guint)sz, (guint)sz_align); |
125 | 0 | return g_bytes_new_take(data_align, sz_align); |
126 | 0 | } |
127 | | |
128 | | /* perfectly aligned */ |
129 | 0 | return g_bytes_ref(bytes); |
130 | 0 | } |
131 | | |
132 | | /** |
133 | | * fu_bytes_is_empty: |
134 | | * @bytes: data blob |
135 | | * |
136 | | * Checks if a byte array are just empty (0xff) bytes. |
137 | | * |
138 | | * Returns: %TRUE if @bytes is empty |
139 | | * |
140 | | * Since: 1.8.2 |
141 | | **/ |
142 | | gboolean |
143 | | fu_bytes_is_empty(GBytes *bytes) |
144 | 0 | { |
145 | 0 | gsize sz = 0; |
146 | 0 | const guint8 *buf = g_bytes_get_data(bytes, &sz); |
147 | 0 | for (gsize i = 0; i < sz; i++) { |
148 | 0 | if (buf[i] != 0xff) |
149 | 0 | return FALSE; |
150 | 0 | } |
151 | 0 | return TRUE; |
152 | 0 | } |
153 | | |
154 | | /** |
155 | | * fu_bytes_compare: |
156 | | * @bytes1: a data blob |
157 | | * @bytes2: another #GBytes |
158 | | * @error: (nullable): optional return location for an error |
159 | | * |
160 | | * Compares the buffers for equality. |
161 | | * |
162 | | * Returns: %TRUE if @bytes1 and @bytes2 are identical |
163 | | * |
164 | | * Since: 1.8.2 |
165 | | **/ |
166 | | gboolean |
167 | | fu_bytes_compare(GBytes *bytes1, GBytes *bytes2, GError **error) |
168 | 0 | { |
169 | 0 | const guint8 *buf1; |
170 | 0 | const guint8 *buf2; |
171 | 0 | gsize bufsz1; |
172 | 0 | gsize bufsz2; |
173 | |
|
174 | 0 | g_return_val_if_fail(bytes1 != NULL, FALSE); |
175 | 0 | g_return_val_if_fail(bytes2 != NULL, FALSE); |
176 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
177 | | |
178 | 0 | buf1 = g_bytes_get_data(bytes1, &bufsz1); |
179 | 0 | buf2 = g_bytes_get_data(bytes2, &bufsz2); |
180 | 0 | return fu_memcmp_safe(buf1, bufsz1, 0x0, buf2, bufsz2, 0x0, MAX(bufsz1, bufsz2), error); |
181 | 0 | } |
182 | | |
183 | | /** |
184 | | * fu_bytes_pad: |
185 | | * @bytes: data blob |
186 | | * @sz: the desired size in bytes |
187 | | * @data: the byte used to pad the array |
188 | | * |
189 | | * Pads a GBytes to a minimum @sz with `0xff`. |
190 | | * |
191 | | * Returns: (transfer full): a data blob |
192 | | * |
193 | | * Since: 2.0.7 |
194 | | **/ |
195 | | GBytes * |
196 | | fu_bytes_pad(GBytes *bytes, gsize sz, guint8 data) |
197 | 896 | { |
198 | 896 | gsize bytes_sz; |
199 | | |
200 | 896 | g_return_val_if_fail(bytes != NULL, NULL); |
201 | 896 | g_return_val_if_fail(sz != 0, NULL); |
202 | | |
203 | | /* pad */ |
204 | 896 | bytes_sz = g_bytes_get_size(bytes); |
205 | 896 | if (bytes_sz < sz) { |
206 | 858 | const guint8 *data_old = g_bytes_get_data(bytes, NULL); |
207 | 858 | guint8 *data_new = g_malloc(sz); |
208 | 858 | if (data_old != NULL) |
209 | 331 | memcpy(data_new, data_old, bytes_sz); /* nocheck:blocked */ |
210 | 858 | memset(data_new + bytes_sz, data, sz - bytes_sz); |
211 | 858 | return g_bytes_new_take(data_new, sz); |
212 | 858 | } |
213 | | |
214 | | /* not required */ |
215 | 38 | return g_bytes_ref(bytes); |
216 | 896 | } |
217 | | |
218 | | /** |
219 | | * fu_bytes_new_offset: |
220 | | * @bytes: data blob |
221 | | * @offset: where subsection starts at |
222 | | * @length: length of subsection |
223 | | * @error: (nullable): optional return location for an error |
224 | | * |
225 | | * Creates a #GBytes which is a subsection of another #GBytes. |
226 | | * |
227 | | * Returns: (transfer full): a #GBytes, or #NULL if range is invalid |
228 | | * |
229 | | * Since: 1.8.2 |
230 | | **/ |
231 | | GBytes * |
232 | | fu_bytes_new_offset(GBytes *bytes, gsize offset, gsize length, GError **error) |
233 | 772k | { |
234 | 772k | g_return_val_if_fail(bytes != NULL, NULL); |
235 | 772k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
236 | | |
237 | | /* optimize */ |
238 | 772k | if (offset == 0x0 && g_bytes_get_size(bytes) == length) |
239 | 25.1k | return g_bytes_ref(bytes); |
240 | | |
241 | | /* sanity check */ |
242 | 747k | if (offset + length < length || offset + length > g_bytes_get_size(bytes)) { |
243 | 138 | g_set_error(error, |
244 | 138 | FWUPD_ERROR, |
245 | 138 | FWUPD_ERROR_INVALID_DATA, |
246 | 138 | "cannot create bytes @0x%02x for 0x%02x " |
247 | 138 | "as buffer only 0x%04x bytes in size", |
248 | 138 | (guint)offset, |
249 | 138 | (guint)length, |
250 | 138 | (guint)g_bytes_get_size(bytes)); |
251 | 138 | return NULL; |
252 | 138 | } |
253 | 747k | return g_bytes_new_from_bytes(bytes, offset, length); |
254 | 747k | } |
255 | | |
256 | | /** |
257 | | * fu_bytes_get_data_safe: |
258 | | * @bytes: data blob |
259 | | * @bufsz: (out) (optional): location to return size of byte data |
260 | | * @error: (nullable): optional return location for an error |
261 | | * |
262 | | * Get the byte data in the #GBytes. This data should not be modified. |
263 | | * This function will always return the same pointer for a given #GBytes. |
264 | | * |
265 | | * If the size of @bytes is zero, then %NULL is returned and the @error is set, |
266 | | * which differs in behavior to that of g_bytes_get_data(). |
267 | | * |
268 | | * This may be useful when calling g_mapped_file_new() on a zero-length file. |
269 | | * |
270 | | * Returns: a pointer to the byte data, or %NULL. |
271 | | * |
272 | | * Since: 1.6.0 |
273 | | **/ |
274 | | const guint8 * |
275 | | fu_bytes_get_data_safe(GBytes *bytes, gsize *bufsz, GError **error) |
276 | 8.04M | { |
277 | 8.04M | const guint8 *buf = g_bytes_get_data(bytes, bufsz); |
278 | 8.04M | if (buf == NULL) { |
279 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "invalid data"); |
280 | 0 | return NULL; |
281 | 0 | } |
282 | 8.04M | return buf; |
283 | 8.04M | } |
284 | | |
285 | | /** |
286 | | * fu_bytes_to_string: |
287 | | * @bytes: data blob |
288 | | * |
289 | | * Converts @bytes to a lowercase hex string. |
290 | | * |
291 | | * Returns: (transfer full): a string, which may be zero length |
292 | | * |
293 | | * Since: 2.0.4 |
294 | | **/ |
295 | | gchar * |
296 | | fu_bytes_to_string(GBytes *bytes) |
297 | 0 | { |
298 | 0 | const guint8 *buf; |
299 | 0 | gsize bufsz = 0; |
300 | 0 | g_autoptr(GString) str = g_string_new(NULL); |
301 | |
|
302 | 0 | g_return_val_if_fail(bytes != NULL, NULL); |
303 | | |
304 | 0 | buf = g_bytes_get_data(bytes, &bufsz); |
305 | 0 | for (gsize i = 0; i < bufsz; i++) |
306 | 0 | g_string_append_printf(str, "%02x", buf[i]); |
307 | 0 | return g_string_free(g_steal_pointer(&str), FALSE); |
308 | 0 | } |
309 | | |
310 | | /** |
311 | | * fu_bytes_from_string: |
312 | | * @str: a hex string |
313 | | * @error: (nullable): optional return location for an error |
314 | | * |
315 | | * Converts a lowercase hex string to a #GBytes. |
316 | | * |
317 | | * Returns: (transfer full): a #GBytes, or %NULL on error |
318 | | * |
319 | | * Since: 2.0.5 |
320 | | **/ |
321 | | GBytes * |
322 | | fu_bytes_from_string(const gchar *str, GError **error) |
323 | 0 | { |
324 | 0 | g_autoptr(GByteArray) buf = NULL; |
325 | |
|
326 | 0 | g_return_val_if_fail(str != NULL, NULL); |
327 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
328 | | |
329 | 0 | buf = fu_byte_array_from_string(str, error); |
330 | 0 | if (buf == NULL) |
331 | 0 | return NULL; |
332 | 0 | return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); /* nocheck:blocked */ |
333 | 0 | } |