Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 Richard Hughes <richard@hughsie.com> |
| 3 | * |
| 4 | * SPDX-License-Identifier: LGPL-2.1+ |
| 5 | */ |
| 6 | |
| 7 | #define G_LOG_DOMAIN "FuFirmware" |
| 8 | |
| 9 | #include "config.h" |
| 10 | |
| 11 | #include <string.h> |
| 12 | |
| 13 | #include "fu-chunk-private.h" |
| 14 | #include "fu-common.h" |
| 15 | #include "fu-dfu-firmware-private.h" |
| 16 | #include "fu-dfuse-firmware.h" |
| 17 | |
| 18 | /** |
| 19 | * SECTION:fu-dfuse-firmware |
| 20 | * @short_description: DfuSe firmware image |
| 21 | * |
| 22 | * An object that represents a DfuSe firmware image. |
| 23 | * |
| 24 | * See also: #FuDfuFirmware |
| 25 | */ |
| 26 | |
| 27 | G_DEFINE_TYPE (FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE) |
| 28 | |
| 29 | /* firmware: LE */ |
| 30 | typedef struct __attribute__((packed)) { |
| 31 | guint8 sig[5]; |
| 32 | guint8 ver; |
| 33 | guint32 image_size; |
| 34 | guint8 targets; |
| 35 | } DfuSeHdr; |
| 36 | |
| 37 | /* image: LE */ |
| 38 | typedef struct __attribute__((packed)) { |
| 39 | guint8 sig[6]; |
| 40 | guint8 alt_setting; |
| 41 | guint32 target_named; |
| 42 | gchar target_name[255]; |
| 43 | guint32 target_size; |
| 44 | guint32 chunks; |
| 45 | } DfuSeImageHdr; |
| 46 | |
| 47 | /* element: LE */ |
| 48 | typedef struct __attribute__((packed)) { |
| 49 | guint32 address; |
| 50 | guint32 size; |
| 51 | } DfuSeElementHdr; |
| 52 | |
| 53 | G_STATIC_ASSERT(sizeof(DfuSeHdr) == 11); |
| 54 | G_STATIC_ASSERT(sizeof(DfuSeImageHdr) == 274); |
| 55 | G_STATIC_ASSERT(sizeof(DfuSeElementHdr) == 8); |
| 56 | |
| 57 | static FuChunk * |
Richard Hughes | 7a23330 | 2021-02-11 08:11:40 +0000 | [diff] [blame] | 58 | fu_firmware_image_chunk_parse (FuDfuseFirmware *self, |
| 59 | GBytes *bytes, |
| 60 | gsize *offset, |
| 61 | GError **error) |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 62 | { |
| 63 | DfuSeElementHdr hdr = { 0x0 }; |
| 64 | gsize bufsz = 0; |
Richard Hughes | 7a23330 | 2021-02-11 08:11:40 +0000 | [diff] [blame] | 65 | gsize ftrlen = fu_dfu_firmware_get_footer_len (FU_DFU_FIRMWARE (self)); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 66 | const guint8 *buf = g_bytes_get_data (bytes, &bufsz); |
| 67 | g_autoptr(FuChunk) chk = NULL; |
| 68 | g_autoptr(GBytes) blob = NULL; |
| 69 | |
| 70 | /* check size */ |
| 71 | if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */ |
Richard Hughes | 7a23330 | 2021-02-11 08:11:40 +0000 | [diff] [blame] | 72 | buf, bufsz - ftrlen, *offset, /* src */ |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 73 | sizeof(hdr), error)) |
| 74 | return NULL; |
| 75 | |
| 76 | /* create new chunk */ |
| 77 | *offset += sizeof(hdr); |
| 78 | blob = fu_common_bytes_new_offset (bytes, *offset, |
| 79 | GUINT32_FROM_LE (hdr.size), |
| 80 | error); |
| 81 | if (blob == NULL) |
| 82 | return NULL; |
| 83 | chk = fu_chunk_bytes_new (blob); |
| 84 | fu_chunk_set_address (chk, GUINT32_FROM_LE (hdr.address)); |
| 85 | *offset += fu_chunk_get_data_sz (chk); |
| 86 | |
| 87 | /* success */ |
| 88 | return g_steal_pointer (&chk); |
| 89 | } |
| 90 | |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 91 | static FuFirmware * |
Richard Hughes | 7a23330 | 2021-02-11 08:11:40 +0000 | [diff] [blame] | 92 | fu_dfuse_firmware_image_parse (FuDfuseFirmware *self, |
| 93 | GBytes *bytes, |
| 94 | gsize *offset, |
| 95 | GError **error) |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 96 | { |
| 97 | DfuSeImageHdr hdr = { 0x0 }; |
| 98 | gsize bufsz = 0; |
| 99 | const guint8 *buf = g_bytes_get_data (bytes, &bufsz); |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 100 | g_autoptr(FuFirmware) image = fu_firmware_new (); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 101 | |
| 102 | /* verify image signature */ |
| 103 | if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */ |
| 104 | buf, bufsz, *offset, /* src */ |
| 105 | sizeof(hdr), error)) |
| 106 | return NULL; |
| 107 | if (memcmp (hdr.sig, "Target", sizeof(hdr.sig)) != 0) { |
| 108 | g_set_error_literal (error, |
| 109 | FWUPD_ERROR, |
| 110 | FWUPD_ERROR_INVALID_FILE, |
| 111 | "invalid DfuSe target signature"); |
| 112 | return NULL; |
| 113 | } |
| 114 | |
| 115 | /* set properties */ |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 116 | fu_firmware_set_idx (image, hdr.alt_setting); |
Richard Hughes | abdc82b | 2021-02-08 17:40:11 +0000 | [diff] [blame] | 117 | if (GUINT32_FROM_LE (hdr.target_named) == 0x01) { |
| 118 | g_autofree gchar *img_id = NULL; |
| 119 | img_id = g_strndup (hdr.target_name, sizeof(hdr.target_name)); |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 120 | fu_firmware_set_id (image, img_id); |
Richard Hughes | abdc82b | 2021-02-08 17:40:11 +0000 | [diff] [blame] | 121 | } |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 122 | |
Richard Hughes | 9bb5d27 | 2021-02-11 12:52:47 +0000 | [diff] [blame] | 123 | /* no chunks */ |
| 124 | if (hdr.chunks == 0) { |
| 125 | g_set_error_literal (error, |
| 126 | FWUPD_ERROR, |
| 127 | FWUPD_ERROR_INVALID_FILE, |
| 128 | "DfuSe image has no chunks"); |
| 129 | return NULL; |
| 130 | } |
| 131 | |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 132 | /* parse chunks */ |
| 133 | *offset += sizeof(hdr); |
| 134 | for (guint j = 0; j < GUINT32_FROM_LE (hdr.chunks); j++) { |
| 135 | g_autoptr(FuChunk) chk = NULL; |
Richard Hughes | 7a23330 | 2021-02-11 08:11:40 +0000 | [diff] [blame] | 136 | chk = fu_firmware_image_chunk_parse (self, |
| 137 | bytes, |
| 138 | offset, |
| 139 | error); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 140 | if (chk == NULL) |
| 141 | return NULL; |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 142 | fu_firmware_add_chunk (image, chk); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 143 | } |
| 144 | |
| 145 | /* success */ |
| 146 | return g_steal_pointer (&image); |
| 147 | } |
| 148 | |
| 149 | static gboolean |
| 150 | fu_dfuse_firmware_parse (FuFirmware *firmware, |
| 151 | GBytes *fw, |
| 152 | guint64 addr_start, |
| 153 | guint64 addr_end, |
| 154 | FwupdInstallFlags flags, |
| 155 | GError **error) |
| 156 | { |
| 157 | FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE (firmware); |
| 158 | DfuSeHdr hdr = { 0x0 }; |
| 159 | gsize bufsz = 0; |
| 160 | gsize offset = 0; |
| 161 | const guint8 *buf; |
| 162 | |
| 163 | /* DFU footer first */ |
| 164 | if (!fu_dfu_firmware_parse_footer (dfu_firmware, fw, flags, error)) |
| 165 | return FALSE; |
| 166 | |
| 167 | /* check the prefix */ |
| 168 | buf = (const guint8 *) g_bytes_get_data (fw, &bufsz); |
| 169 | if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */ |
| 170 | buf, bufsz, offset, /* src */ |
| 171 | sizeof(hdr), error)) |
| 172 | return FALSE; |
| 173 | if (memcmp (hdr.sig, "DfuSe", sizeof(hdr.sig)) != 0) { |
| 174 | g_set_error_literal (error, |
| 175 | FWUPD_ERROR, |
| 176 | FWUPD_ERROR_INTERNAL, |
| 177 | "invalid DfuSe prefix"); |
| 178 | return FALSE; |
| 179 | } |
| 180 | |
| 181 | /* check the version */ |
| 182 | if (hdr.ver != 0x01) { |
| 183 | g_set_error (error, |
| 184 | FWUPD_ERROR, |
| 185 | FWUPD_ERROR_INTERNAL, |
| 186 | "invalid DfuSe version, got %02x", |
| 187 | hdr.ver); |
| 188 | return FALSE; |
| 189 | } |
| 190 | |
| 191 | /* check image size */ |
| 192 | if (GUINT32_FROM_LE (hdr.image_size) != |
| 193 | bufsz - fu_dfu_firmware_get_footer_len (dfu_firmware)) { |
| 194 | g_set_error (error, |
| 195 | FWUPD_ERROR, |
| 196 | FWUPD_ERROR_INTERNAL, |
| 197 | "invalid DfuSe image size, " |
| 198 | "got %" G_GUINT32_FORMAT ", " |
| 199 | "expected %" G_GSIZE_FORMAT, |
| 200 | GUINT32_FROM_LE (hdr.image_size), |
| 201 | bufsz - fu_dfu_firmware_get_footer_len (dfu_firmware)); |
| 202 | return FALSE; |
| 203 | } |
| 204 | |
| 205 | /* parse the image targets */ |
| 206 | offset += sizeof(hdr); |
| 207 | for (guint i = 0; i < hdr.targets; i++) { |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 208 | g_autoptr(FuFirmware) image = NULL; |
Richard Hughes | 7a23330 | 2021-02-11 08:11:40 +0000 | [diff] [blame] | 209 | image = fu_dfuse_firmware_image_parse (FU_DFUSE_FIRMWARE (firmware), |
| 210 | fw, &offset, |
| 211 | error); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 212 | if (image == NULL) |
| 213 | return FALSE; |
| 214 | fu_firmware_add_image (firmware, image); |
| 215 | } |
| 216 | return TRUE; |
| 217 | } |
| 218 | |
| 219 | static GBytes * |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 220 | fu_firmware_chunk_write (FuChunk *chk) |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 221 | { |
| 222 | DfuSeElementHdr hdr = { 0x0 }; |
| 223 | const guint8 *data = fu_chunk_get_data (chk); |
| 224 | gsize length = fu_chunk_get_data_sz (chk); |
| 225 | g_autoptr(GByteArray) buf = NULL; |
| 226 | |
| 227 | buf = g_byte_array_sized_new (sizeof(DfuSeElementHdr) + length); |
| 228 | hdr.address = GUINT32_TO_LE (fu_chunk_get_address (chk)); |
| 229 | hdr.size = GUINT32_TO_LE (length); |
| 230 | g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr)); |
| 231 | g_byte_array_append (buf, data, length); |
| 232 | return g_byte_array_free_to_bytes (g_steal_pointer (&buf)); |
| 233 | } |
| 234 | |
| 235 | static GBytes * |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 236 | fu_dfuse_firmware_write_image (FuFirmware *image, GError **error) |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 237 | { |
| 238 | DfuSeImageHdr hdr = { 0x0 }; |
| 239 | gsize totalsz = 0; |
| 240 | g_autoptr(GByteArray) buf = NULL; |
| 241 | g_autoptr(GPtrArray) blobs = NULL; |
| 242 | g_autoptr(GPtrArray) chunks = NULL; |
| 243 | |
| 244 | /* get total size */ |
| 245 | blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 246 | chunks = fu_firmware_get_chunks (image, error); |
Richard Hughes | 87a8091 | 2021-02-11 12:54:15 +0000 | [diff] [blame] | 247 | if (chunks == NULL) |
| 248 | return NULL; |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 249 | for (guint i = 0; i < chunks->len; i++) { |
| 250 | FuChunk *chk = g_ptr_array_index (chunks, i); |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 251 | GBytes *bytes = fu_firmware_chunk_write (chk); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 252 | g_ptr_array_add (blobs, bytes); |
| 253 | totalsz += g_bytes_get_size (bytes); |
| 254 | } |
| 255 | |
| 256 | /* mutable output buffer */ |
| 257 | buf = g_byte_array_sized_new (sizeof(DfuSeImageHdr) + totalsz); |
| 258 | |
| 259 | /* add prefix */ |
| 260 | memcpy (hdr.sig, "Target", 6); |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 261 | hdr.alt_setting = fu_firmware_get_idx (image); |
| 262 | if (fu_firmware_get_id (image) != NULL) { |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 263 | hdr.target_named = GUINT32_TO_LE (0x01); |
| 264 | g_strlcpy ((gchar *) &hdr.target_name, |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 265 | fu_firmware_get_id (image), |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 266 | sizeof(hdr.target_name)); |
| 267 | } |
| 268 | hdr.target_size = GUINT32_TO_LE (totalsz); |
| 269 | hdr.chunks = GUINT32_TO_LE (chunks->len); |
| 270 | g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr)); |
| 271 | |
| 272 | /* copy data */ |
| 273 | for (guint i = 0; i < blobs->len; i++) { |
| 274 | GBytes *blob = g_ptr_array_index (blobs, i); |
| 275 | g_byte_array_append (buf, |
| 276 | g_bytes_get_data (blob, NULL), |
| 277 | g_bytes_get_size (blob)); |
| 278 | } |
| 279 | return g_byte_array_free_to_bytes (g_steal_pointer (&buf)); |
| 280 | } |
| 281 | |
| 282 | static GBytes * |
| 283 | fu_dfuse_firmware_write (FuFirmware *firmware, GError **error) |
| 284 | { |
| 285 | DfuSeHdr hdr = { 0x0 }; |
| 286 | gsize totalsz = 0; |
| 287 | g_autoptr(GByteArray) buf = NULL; |
| 288 | g_autoptr(GBytes) blob_noftr = NULL; |
| 289 | g_autoptr(GPtrArray) blobs = NULL; |
| 290 | g_autoptr(GPtrArray) images = NULL; |
| 291 | |
| 292 | /* create mutable output buffer */ |
| 293 | blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); |
| 294 | images = fu_firmware_get_images (FU_FIRMWARE (firmware)); |
| 295 | for (guint i = 0; i < images->len; i++) { |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 296 | FuFirmware *img = g_ptr_array_index (images, i); |
Richard Hughes | 87a8091 | 2021-02-11 12:54:15 +0000 | [diff] [blame] | 297 | g_autoptr(GBytes) blob = NULL; |
Richard Hughes | 1981c63 | 2021-03-09 09:44:12 +0000 | [diff] [blame] | 298 | blob = fu_dfuse_firmware_write_image (img, error); |
Richard Hughes | 87a8091 | 2021-02-11 12:54:15 +0000 | [diff] [blame] | 299 | if (blob == NULL) |
| 300 | return NULL; |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 301 | totalsz += g_bytes_get_size (blob); |
Richard Hughes | 87a8091 | 2021-02-11 12:54:15 +0000 | [diff] [blame] | 302 | g_ptr_array_add (blobs, g_steal_pointer (&blob)); |
Richard Hughes | 98972f4 | 2021-01-29 11:35:02 +0000 | [diff] [blame] | 303 | } |
| 304 | buf = g_byte_array_sized_new (sizeof(DfuSeHdr) + totalsz); |
| 305 | |
| 306 | /* DfuSe header */ |
| 307 | memcpy (hdr.sig, "DfuSe", 5); |
| 308 | hdr.ver = 0x01; |
| 309 | hdr.image_size = GUINT32_TO_LE (sizeof(hdr) + totalsz); |
| 310 | if (images->len > G_MAXUINT8) { |
| 311 | g_set_error (error, |
| 312 | FWUPD_ERROR, |
| 313 | FWUPD_ERROR_INTERNAL, |
| 314 | "too many (%u) images to write DfuSe file", |
| 315 | images->len); |
| 316 | return NULL; |
| 317 | } |
| 318 | hdr.targets = (guint8) images->len; |
| 319 | g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr)); |
| 320 | |
| 321 | /* copy images */ |
| 322 | for (guint i = 0; i < blobs->len; i++) { |
| 323 | GBytes *blob = g_ptr_array_index (blobs, i); |
| 324 | g_byte_array_append (buf, |
| 325 | g_bytes_get_data (blob, NULL), |
| 326 | g_bytes_get_size (blob)); |
| 327 | } |
| 328 | |
| 329 | /* return blob */ |
| 330 | blob_noftr = g_byte_array_free_to_bytes (g_steal_pointer (&buf)); |
| 331 | return fu_dfu_firmware_append_footer (FU_DFU_FIRMWARE (firmware), |
| 332 | blob_noftr, error); |
| 333 | } |
| 334 | |
| 335 | static void |
| 336 | fu_dfuse_firmware_init (FuDfuseFirmware *self) |
| 337 | { |
| 338 | fu_dfu_firmware_set_version (FU_DFU_FIRMWARE (self), DFU_VERSION_DFUSE); |
| 339 | } |
| 340 | |
| 341 | static void |
| 342 | fu_dfuse_firmware_class_init (FuDfuseFirmwareClass *klass) |
| 343 | { |
| 344 | FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); |
| 345 | klass_firmware->parse = fu_dfuse_firmware_parse; |
| 346 | klass_firmware->write = fu_dfuse_firmware_write; |
| 347 | } |
| 348 | |
| 349 | /** |
| 350 | * fu_dfuse_firmware_new: |
| 351 | * |
| 352 | * Creates a new #FuFirmware of sub type Dfuse |
| 353 | * |
| 354 | * Since: 1.5.6 |
| 355 | **/ |
| 356 | FuFirmware * |
| 357 | fu_dfuse_firmware_new (void) |
| 358 | { |
| 359 | return FU_FIRMWARE (g_object_new (FU_TYPE_DFUSE_FIRMWARE, NULL)); |
| 360 | } |