diff --git a/libdfu/Makefile.am b/libdfu/Makefile.am index 81433da1d..dc327cf89 100644 --- a/libdfu/Makefile.am +++ b/libdfu/Makefile.am @@ -48,14 +48,22 @@ libdfu_la_SOURCES = \ dfu-device-private.h \ dfu-element.c \ dfu-element.h \ - dfu-element-private.h \ dfu-error.c \ dfu-error.h \ dfu-firmware.c \ dfu-firmware.h \ + dfu-format-dfu.c \ + dfu-format-dfu.h \ + dfu-format-dfuse.c \ + dfu-format-dfuse.h \ + dfu-format-ihex.c \ + dfu-format-ihex.h \ + dfu-format-metadata.c \ + dfu-format-metadata.h \ + dfu-format-raw.c \ + dfu-format-raw.h \ dfu-image.c \ dfu-image.h \ - dfu-image-private.h \ dfu-sector.c \ dfu-sector.h \ dfu-sector-private.h \ diff --git a/libdfu/dfu-element.c b/libdfu/dfu-element.c index 671810eb0..cd0d84693 100644 --- a/libdfu/dfu-element.c +++ b/libdfu/dfu-element.c @@ -37,7 +37,7 @@ #include #include "dfu-common.h" -#include "dfu-element-private.h" +#include "dfu-element.h" #include "dfu-error.h" static void dfu_element_finalize (GObject *object); @@ -240,95 +240,3 @@ dfu_element_set_target_size (DfuElement *element, guint32 target_size) g_bytes_unref (priv->contents); priv->contents = g_bytes_new_take (buf, target_size); } - -/* DfuSe element header */ -typedef struct __attribute__((packed)) { - guint32 address; - guint32 size; -} DfuSeElementPrefix; - -/** - * dfu_element_from_dfuse: (skip) - * @data: data buffer - * @length: length of @data we can access - * @consumed: (out): the number of bytes we consued - * @error: a #GError, or %NULL - * - * Unpacks an element from DfuSe data. - * - * Returns: a #DfuElement, or %NULL for error - **/ -DfuElement * -dfu_element_from_dfuse (const guint8 *data, - guint32 length, - guint32 *consumed, - GError **error) -{ - DfuElement *element = NULL; - DfuElementPrivate *priv; - DfuSeElementPrefix *el = (DfuSeElementPrefix *) data; - guint32 size; - - g_assert_cmpint(sizeof(DfuSeElementPrefix), ==, 8); - - /* check input buffer size */ - if (length < sizeof(DfuSeElementPrefix)) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "invalid element data size %u", - (guint32) length); - return NULL; - } - - /* check size */ - size = GUINT32_FROM_LE (el->size); - if (size + sizeof(DfuSeElementPrefix) > length) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "invalid element size %u, only %u bytes left", - size, - (guint32) (length - sizeof(DfuSeElementPrefix))); - return NULL; - } - - /* create new element */ - element = dfu_element_new (); - priv = GET_PRIVATE (element); - priv->address = GUINT32_FROM_LE (el->address); - priv->contents = g_bytes_new (data + sizeof(DfuSeElementPrefix), size); - - /* return size */ - if (consumed != NULL) - *consumed = (guint32) sizeof(DfuSeElementPrefix) + size; - - return element; -} - -/** - * dfu_element_to_dfuse: (skip) - * @element: a #DfuElement - * - * Packs a DfuSe element. - * - * Returns: (transfer full): the packed data - **/ -GBytes * -dfu_element_to_dfuse (DfuElement *element) -{ - DfuElementPrivate *priv = GET_PRIVATE (element); - DfuSeElementPrefix *el; - const guint8 *data; - gsize length; - guint8 *buf; - - data = g_bytes_get_data (priv->contents, &length); - buf = g_malloc0 (length + sizeof (DfuSeElementPrefix)); - el = (DfuSeElementPrefix *) buf; - el->address = GUINT32_TO_LE (priv->address); - el->size = GUINT32_TO_LE (length); - - memcpy (buf + sizeof (DfuSeElementPrefix), data, length); - return g_bytes_new_take (buf, length + sizeof (DfuSeElementPrefix)); -} diff --git a/libdfu/dfu-firmware.c b/libdfu/dfu-firmware.c index 1a22e1ea6..d992318fe 100644 --- a/libdfu/dfu-firmware.c +++ b/libdfu/dfu-firmware.c @@ -40,7 +40,10 @@ #include "dfu-common.h" #include "dfu-error.h" #include "dfu-firmware.h" -#include "dfu-image-private.h" +#include "dfu-format-dfu.h" +#include "dfu-format-ihex.h" +#include "dfu-format-raw.h" +#include "dfu-image.h" static void dfu_firmware_finalize (GObject *object); @@ -50,7 +53,6 @@ typedef struct { guint16 vid; guint16 pid; guint16 release; - guint32 crc; DfuCipherKind cipher_kind; DfuFirmwareFormat format; } DfuFirmwarePrivate; @@ -386,528 +388,6 @@ dfu_firmware_set_format (DfuFirmware *firmware, DfuFirmwareFormat format) priv->format = format; } -typedef struct __attribute__((packed)) { - guint16 release; - guint16 pid; - guint16 vid; - guint16 ver; - guint8 sig[3]; - guint8 len; - guint32 crc; -} DfuFirmwareFooter; - -static guint32 _crctbl[] = { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, - 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, - 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, - 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, - 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, - 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, - 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, - 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, - 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, - 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, - 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, - 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, - 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, - 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, - 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, - 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, - 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, - 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; - -static guint32 -dfu_firmware_generate_crc32 (const guint8 *data, gsize length) -{ - guint i; - guint32 accum = 0xffffffff; - for (i = 0; i < length; i++) - accum = _crctbl[(accum^data[i]) & 0xff] ^ (accum >> 8); - return accum; -} - -static guint8 -dfu_firmware_ihex_parse_uint8 (const gchar *data, guint pos) -{ - gchar buffer[3]; - memcpy (buffer, data + pos, 2); - buffer[2] = '\0'; - return (guint8) g_ascii_strtoull (buffer, NULL, 16); -} - -static guint16 -dfu_firmware_ihex_parse_uint16 (const gchar *data, guint pos) -{ - gchar buffer[5]; - memcpy (buffer, data + pos, 4); - buffer[4] = '\0'; - return (guint16) g_ascii_strtoull (buffer, NULL, 16); -} - -#define DFU_INHX32_RECORD_TYPE_DATA 0 -#define DFU_INHX32_RECORD_TYPE_EOF 1 -#define DFU_INHX32_RECORD_TYPE_EXTENDED 4 - -static gboolean -dfu_firmware_add_ihex (DfuFirmware *firmware, GBytes *bytes, - DfuFirmwareParseFlags flags, GError **error) -{ - const gchar *in_buffer; - gsize len_in; - guint16 addr_high = 0; - guint16 addr_low = 0; - guint32 addr32 = 0; - guint32 addr32_last = 0; - guint8 checksum; - guint8 data_tmp; - guint8 len_tmp; - guint8 type; - guint end; - guint i; - guint j; - guint offset = 0; - g_autoptr(DfuElement) element = NULL; - g_autoptr(DfuImage) image = NULL; - g_autoptr(GBytes) contents = NULL; - g_autoptr(GString) string = NULL; - - g_return_val_if_fail (bytes != NULL, FALSE); - - /* create element */ - image = dfu_image_new (); - dfu_image_set_name (image, "ihex"); - element = dfu_element_new (); - - /* parse records */ - in_buffer = g_bytes_get_data (bytes, &len_in); - string = g_string_new (""); - while (offset < len_in) { - - /* check starting token */ - if (in_buffer[offset] != ':') { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INVALID_FILE, - "invalid starting token, got %c at %x", - in_buffer[offset], offset); - return FALSE; - } - - /* check there's enough data for the smallest possible record */ - if (offset + 12 > (guint) len_in) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INVALID_FILE, - "record incomplete at %u, length %u", - offset, (guint) len_in); - return FALSE; - } - - /* length, 16-bit address, type */ - len_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+1); - addr_low = dfu_firmware_ihex_parse_uint16 (in_buffer, offset+3); - type = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+7); - - /* position of checksum */ - end = offset + 9 + len_tmp * 2; - if (end > (guint) len_in) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INVALID_FILE, - "checksum > file length: %u", - end); - return FALSE; - } - - /* verify checksum */ - if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { - checksum = 0; - for (i = offset + 1; i < end + 2; i += 2) { - data_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, i); - checksum += data_tmp; - } - if (checksum != 0) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INVALID_FILE, - "invalid record checksum"); - return FALSE; - } - } - - /* process different record types */ - switch (type) { - case DFU_INHX32_RECORD_TYPE_DATA: - /* if not contiguous with previous record */ - if ((addr_high + addr_low) != addr32) { - if (addr32 == 0x0) { - g_debug ("base address %04x", addr_low); - dfu_element_set_address (element, addr_low); - } -// addr32 = addr_high + addr_low; - addr32 = ((guint32) addr_high << 16) + addr_low; - } - - /* parse bytes from line */ - for (i = offset + 9; i < end; i += 2) { - /* any holes in the hex record */ - guint32 len_hole = addr32 - addr32_last; - if (addr32_last > 0x0 && len_hole > 1) { - for (j = 1; j < len_hole; j++) { - g_debug ("filling address 0x%04x", - addr32_last + j); - /* although 0xff might be clearer, - * we can't write 0xffff to pic14 */ - g_string_append_c (string, 0x00); - } - } - /* write into buf */ - data_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, i); - g_string_append_c (string, (gchar) data_tmp); - g_debug ("writing address 0x%04x", addr32); - addr32_last = addr32++; - } - break; - case DFU_INHX32_RECORD_TYPE_EOF: - break; - case DFU_INHX32_RECORD_TYPE_EXTENDED: - addr_high = dfu_firmware_ihex_parse_uint16 (in_buffer, offset+9); - g_error ("set base address %x", addr_high); - addr32 = ((guint32) addr_high << 16) + addr_low; - break; - default: - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INVALID_FILE, - "invalid ihex record type %i", - type); - return FALSE; - } - - /* ignore any line return */ - offset = end + 2; - for (; offset < len_in; offset++) { - if (in_buffer[offset] != '\n' && - in_buffer[offset] != '\r') - break; - } - } - - /* add single image */ - contents = g_bytes_new (string->str, string->len); - dfu_element_set_contents (element, contents); - dfu_image_add_element (image, element); - dfu_firmware_add_image (firmware, image); - dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_INTEL_HEX); - return TRUE; -} - -static gboolean -dfu_firmware_write_data_ihex_element (DfuElement *element, - GString *str, - GError **error) -{ - GBytes *contents; - const guint8 *data; - const guint chunk_size = 16; - gsize len; - - /* get number of chunks */ - contents = dfu_element_get_contents (element); - data = g_bytes_get_data (contents, &len); - for (gsize i = 0; i < len; i += chunk_size) { - guint8 checksum = 0; - - /* length, 16-bit address, type */ - gsize chunk_len = MIN (len - i, 16); - g_string_append_printf (str, ":%02X%04X%02X", - (guint) chunk_len, - (guint) (dfu_element_get_address (element) + i), - (guint) DFU_INHX32_RECORD_TYPE_DATA); - for (gsize j = 0; j < chunk_len; j++) - g_string_append_printf (str, "%02X", data[i+j]); - - /* add checksum */ - for (gsize j = 0; j < (chunk_len * 2) + 8; j++) - checksum += (guint8) str->str[str->len - (j + 1)]; - g_string_append_printf (str, "%02X\n", checksum); - } - return TRUE; -} - -static GBytes * -dfu_firmware_write_data_ihex (DfuFirmware *firmware, GError **error) -{ - DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); - DfuElement *element; - DfuImage *image; - GPtrArray *elements; - guint i; - guint j; - g_autoptr(GString) str = NULL; - - /* write all the element data */ - str = g_string_new (""); - for (i = 0; i < priv->images->len; i++) { - image = g_ptr_array_index (priv->images, i); - elements = dfu_image_get_elements (image); - for (j = 0; j < elements->len; j++) { - element = g_ptr_array_index (elements, j); - if (!dfu_firmware_write_data_ihex_element (element, - str, - error)) - return NULL; - } - } - - /* add EOF */ - g_string_append_printf (str, ":000000%02XFF\n", - (guint) DFU_INHX32_RECORD_TYPE_EOF); - return g_bytes_new (str->str, str->len); -} - -static gboolean -dfu_firmware_add_binary (DfuFirmware *firmware, GBytes *bytes, GError **error) -{ - g_autoptr(DfuElement) element = NULL; - g_autoptr(DfuImage) image = NULL; - image = dfu_image_new (); - element = dfu_element_new (); - dfu_element_set_contents (element, bytes); - dfu_image_add_element (image, element); - dfu_firmware_add_image (firmware, image); - return TRUE; -} - -/* DfuSe header */ -typedef struct __attribute__((packed)) { - guint8 sig[5]; - guint8 ver; - guint32 image_size; - guint8 targets; -} DfuSePrefix; - -static gboolean -dfu_firmware_add_dfuse (DfuFirmware *firmware, GBytes *bytes, GError **error) -{ - DfuSePrefix *prefix; - gsize len; - guint32 offset = sizeof(DfuSePrefix); - guint8 *data; - guint i; - - /* check the prefix (BE) */ - data = (guint8 *) g_bytes_get_data (bytes, &len); - prefix = (DfuSePrefix *) data; - if (memcmp (prefix->sig, "DfuSe", 5) != 0) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "invalid DfuSe prefix"); - return FALSE; - } - - /* check the version */ - if (prefix->ver != 0x01) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "invalid DfuSe version, got %02x", - prefix->ver); - return FALSE; - } - - /* check image size */ - if (GUINT32_FROM_LE (prefix->image_size) != len) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "invalid DfuSe image size, " - "got %" G_GUINT32_FORMAT ", " - "expected %" G_GSIZE_FORMAT, - GUINT32_FROM_LE (prefix->image_size), - len); - return FALSE; - } - - /* parse the image targets */ - len -= sizeof(DfuSePrefix); - for (i = 0; i < prefix->targets; i++) { - guint consumed; - g_autoptr(DfuImage) image = NULL; - image = dfu_image_from_dfuse (data + offset, (guint32) len, - &consumed, error); - if (image == NULL) - return FALSE; - dfu_firmware_add_image (firmware, image); - offset += consumed; - len -= consumed; - } - return TRUE; -} - -static GBytes * -dfu_firmware_write_data_dfuse (DfuFirmware *firmware, GError **error) -{ - DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); - DfuSePrefix *prefix; - guint i; - guint32 image_size_total = 0; - guint32 offset = sizeof (DfuSePrefix); - guint8 *buf; - g_autoptr(GPtrArray) dfuse_images = NULL; - - /* get all the image data */ - dfuse_images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); - for (i = 0; i < priv->images->len; i++) { - DfuImage *im = g_ptr_array_index (priv->images, i); - GBytes *contents; - contents = dfu_image_to_dfuse (im); - image_size_total += (guint32) g_bytes_get_size (contents); - g_ptr_array_add (dfuse_images, contents); - } - g_debug ("image_size_total: %" G_GUINT32_FORMAT, image_size_total); - - buf = g_malloc0 (sizeof (DfuSePrefix) + image_size_total); - - /* DfuSe header */ - prefix = (DfuSePrefix *) buf; - memcpy (prefix->sig, "DfuSe", 5); - prefix->ver = 0x01; - prefix->image_size = GUINT32_TO_LE (offset + image_size_total); - if (priv->images->len > G_MAXUINT8) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "too many (%u) images to write DfuSe file", - priv->images->len); - return FALSE; - } - prefix->targets = (guint8) priv->images->len; - - /* copy images */ - for (i = 0; i < dfuse_images->len; i++) { - GBytes *contents = g_ptr_array_index (dfuse_images, i); - gsize length; - const guint8 *data; - data = g_bytes_get_data (contents, &length); - memcpy (buf + offset, data, length); - offset += (guint32) length; - } - - /* return blob */ - return g_bytes_new_take (buf, sizeof (DfuSePrefix) + image_size_total); -} - -/** - * dfu_firmware_parse_metadata: - * - * The representation in memory is as follows: - * - * uint16 signature='MD' - * uint8 number_of_keys - * uint8 number_of_keys - * uint8 key(n)_length - * ... key(n) (no NUL) - * uint8 value(n)_length - * ... value(n) (no NUL) - * - **/ -static gboolean -dfu_firmware_parse_metadata (DfuFirmware *firmware, - const guint8 *data, - guint data_length, - guint8 footer_size, - GError **error) -{ - guint i; - guint idx = data_length - footer_size + 2; - guint kvlen; - guint number_keys; - - /* not big enough */ - if (footer_size <= 0x10) - return TRUE; - - /* signature invalid */ - if (memcmp (&data[data_length - footer_size], "MD", 2) != 0) - return TRUE; - - /* parse key=value store */ - number_keys = data[idx++]; - for (i = 0; i < number_keys; i++) { - g_autofree gchar *key = NULL; - g_autofree gchar *value = NULL; - - /* parse key */ - kvlen = data[idx++]; - if (kvlen > 233) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "metadata table corrupt, key=%u", - kvlen); - return FALSE; - } - if (idx + kvlen + 0x10 > data_length) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "metadata table corrupt"); - return FALSE; - } - key = g_strndup ((const gchar *) data + idx, kvlen); - idx += kvlen; - - /* parse value */ - kvlen = data[idx++]; - if (kvlen > 233) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "metadata table corrupt, value=%u", - kvlen); - return FALSE; - } - if (idx + kvlen + 0x10 > data_length) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "metadata table corrupt"); - return FALSE; - } - value = g_strndup ((const gchar *) data + idx, kvlen); - idx += kvlen; - dfu_firmware_set_metadata (firmware, key, value); - } - return TRUE; -} - /** * dfu_firmware_parse_data: * @firmware: a #DfuFirmware @@ -925,112 +405,32 @@ gboolean dfu_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, DfuFirmwareParseFlags flags, GError **error) { - DfuFirmwareFooter *ftr; DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); - const gchar *cipher_str; - gsize len; - guint32 crc_new; - guint8 *data; - g_autoptr(GBytes) contents = NULL; g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), FALSE); g_return_val_if_fail (bytes != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - /* sanity check */ - g_assert_cmpint (sizeof(DfuFirmwareFooter), ==, 16); - g_assert_cmpint (sizeof(DfuSePrefix), ==, 11); - /* set defaults */ priv->vid = 0xffff; priv->pid = 0xffff; priv->release = 0xffff; - /* this is ihex */ - data = (guint8 *) g_bytes_get_data (bytes, &len); - if (data[0] == ':') - return dfu_firmware_add_ihex (firmware, bytes, flags, error); + /* try to get format if not already set */ + if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) + priv->format = dfu_firmware_detect_ihex (bytes); + if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) + priv->format = dfu_firmware_detect_dfu (bytes); + if (priv->format == DFU_FIRMWARE_FORMAT_UNKNOWN) + priv->format = dfu_firmware_detect_raw (bytes); - /* too small to be a DFU file */ - if (len < 16) { - priv->format = DFU_FIRMWARE_FORMAT_RAW; - return dfu_firmware_add_binary (firmware, bytes, error); - } - - /* check for DFU signature */ - ftr = (DfuFirmwareFooter *) &data[len - sizeof(DfuFirmwareFooter)]; - if (memcmp (ftr->sig, "UFD", 3) != 0) { - priv->format = DFU_FIRMWARE_FORMAT_RAW; - return dfu_firmware_add_binary (firmware, bytes, error); - } - - /* check version */ - priv->format = GUINT16_FROM_LE (ftr->ver); - if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST) == 0) { - if (priv->format != DFU_FIRMWARE_FORMAT_DFU_1_0 && - priv->format != DFU_FIRMWARE_FORMAT_DFUSE) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "version check failed, got %04x", - priv->format); - return FALSE; - } - } - - /* verify the checksum */ - priv->crc = GUINT32_FROM_LE (ftr->crc); - if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { - crc_new = dfu_firmware_generate_crc32 (data, len - 4); - if (priv->crc != crc_new) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "CRC failed, expected %04x, got %04x", - crc_new, GUINT32_FROM_LE (ftr->crc)); - return FALSE; - } - } - - /* set from footer */ - dfu_firmware_set_vid (firmware, GUINT16_FROM_LE (ftr->vid)); - dfu_firmware_set_pid (firmware, GUINT16_FROM_LE (ftr->pid)); - dfu_firmware_set_release (firmware, GUINT16_FROM_LE (ftr->release)); - - /* check reported length */ - if (ftr->len > len) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "reported firmware size %04x larger than file %04x", - (guint) ftr->len, (guint) len); - return FALSE; - } - - /* parse the optional metadata segment */ - if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_METADATA) == 0) { - if (!dfu_firmware_parse_metadata (firmware, data, - (guint) len, - (guint) ftr->len, error)) - return FALSE; - } - - /* set this automatically */ - cipher_str = dfu_firmware_get_metadata (firmware, DFU_METADATA_KEY_CIPHER_KIND); - if (cipher_str != NULL) { - if (g_strcmp0 (cipher_str, "XTEA") == 0) - priv->cipher_kind = DFU_CIPHER_KIND_XTEA; - else - g_warning ("Unknown CipherKind: %s", cipher_str); - } - - /* parse DfuSe prefix */ - contents = g_bytes_new_from_bytes (bytes, 0, len - ftr->len); - if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE) - return dfu_firmware_add_dfuse (firmware, contents, error); - - /* just copy old-plain DFU file */ - return dfu_firmware_add_binary (firmware, contents, error); + /* handled easily */ + if (priv->format == DFU_FIRMWARE_FORMAT_INTEL_HEX) + return dfu_firmware_from_ihex (firmware, bytes, flags, error); + if (priv->format == DFU_FIRMWARE_FORMAT_DFU_1_0 || + priv->format == DFU_FIRMWARE_FORMAT_DFUSE) + return dfu_firmware_from_dfu (firmware, bytes, flags, error); + return dfu_firmware_from_raw (firmware, bytes, flags, error); } /** @@ -1092,6 +492,23 @@ dfu_firmware_get_metadata (DfuFirmware *firmware, const gchar *key) return g_hash_table_lookup (priv->metadata, key); } +/** + * dfu_firmware_get_metadata_table: + * @firmware: a #DfuFirmware + * + * Gets all metadata from the store. + * + * Return value: (transfer none): the metadata hash table + * + * Since: 0.6.3 + **/ +GHashTable * +dfu_firmware_get_metadata_table (DfuFirmware *firmware) +{ + DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); + return priv->metadata; +} + /** * dfu_firmware_set_metadata: * @firmware: a #DfuFirmware @@ -1127,131 +544,6 @@ dfu_firmware_remove_metadata (DfuFirmware *firmware, const gchar *key) g_hash_table_remove (priv->metadata, key); } -static GBytes * -dfu_firmware_build_metadata_table (DfuFirmware *firmware, GError **error) -{ - DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); - GList *l; - guint8 mdbuf[239]; - guint idx = 0; - guint number_keys; - g_autoptr(GList) keys = NULL; - - /* no metadata */ - if (g_hash_table_size (priv->metadata) == 0) - return g_bytes_new (NULL, 0); - - /* check the number of keys */ - keys = g_hash_table_get_keys (priv->metadata); - number_keys = g_list_length (keys); - if (number_keys > 59) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "too many metadata keys (%u)", - number_keys); - return NULL; - } - - /* write the signature */ - mdbuf[idx++] = 'M'; - mdbuf[idx++] = 'D'; - mdbuf[idx++] = (guint8) number_keys; - for (l = keys; l != NULL; l = l->next) { - const gchar *key; - const gchar *value; - guint key_len; - guint value_len; - - /* check key and value length */ - key = l->data; - key_len = (guint) strlen (key); - if (key_len > 233) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "metdata key too long: %s", - key); - return NULL; - } - value = g_hash_table_lookup (priv->metadata, key); - value_len = (guint) strlen (value); - if (value_len > 233) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "value too long: %s", - value); - return NULL; - } - - /* do we still have space? */ - if (idx + key_len + value_len + 2 > sizeof(mdbuf)) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_SUPPORTED, - "not enough space in metadata table, " - "already used %u bytes", idx); - return NULL; - } - - /* write the key */ - mdbuf[idx++] = (guint8) key_len; - memcpy(mdbuf + idx, key, key_len); - idx += key_len; - - /* write the value */ - mdbuf[idx++] = (guint8) value_len; - memcpy(mdbuf + idx, value, value_len); - idx += value_len; - } - g_debug ("metadata table was %u/%" G_GSIZE_FORMAT " bytes", - idx, sizeof(mdbuf)); - return g_bytes_new (mdbuf, idx); -} - -static GBytes * -dfu_firmware_add_footer (DfuFirmware *firmware, GBytes *contents, GError **error) -{ - DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); - DfuFirmwareFooter *ftr; - const guint8 *data_bin; - const guint8 *data_md; - gsize length_bin = 0; - gsize length_md = 0; - guint32 crc_new; - guint8 *buf; - g_autoptr(GBytes) metadata_table = NULL; - - /* get any file metadata */ - metadata_table = dfu_firmware_build_metadata_table (firmware, error); - if (metadata_table == NULL) - return NULL; - data_md = g_bytes_get_data (metadata_table, &length_md); - - /* add the raw firmware data */ - data_bin = g_bytes_get_data (contents, &length_bin); - buf = g_malloc0 (length_bin + length_md + 0x10); - memcpy (buf + 0, data_bin, length_bin); - - /* add the metadata table */ - memcpy (buf + length_bin, data_md, length_md); - - /* set up LE footer */ - ftr = (DfuFirmwareFooter *) (buf + length_bin + length_md); - ftr->release = GUINT16_TO_LE (priv->release); - ftr->pid = GUINT16_TO_LE (priv->pid); - ftr->vid = GUINT16_TO_LE (priv->vid); - ftr->ver = GUINT16_TO_LE (priv->format); - ftr->len = (guint8) (sizeof (DfuFirmwareFooter) + length_md); - memcpy(ftr->sig, "UFD", 3); - crc_new = dfu_firmware_generate_crc32 (buf, length_bin + length_md + 12); - ftr->crc = GUINT32_TO_LE (crc_new); - - /* return all data */ - return g_bytes_new_take (buf, length_bin + length_md + 0x10); -} - /** * dfu_firmware_write_data: * @firmware: a #DfuFirmware @@ -1267,7 +559,6 @@ GBytes * dfu_firmware_write_data (DfuFirmware *firmware, GError **error) { DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); - DfuImage *image; g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); @@ -1293,54 +584,17 @@ dfu_firmware_write_data (DfuFirmware *firmware, GError **error) } /* raw */ - if (priv->format == DFU_FIRMWARE_FORMAT_RAW) { - GBytes *contents; - DfuElement *element; - image = dfu_firmware_get_image_default (firmware); - g_assert (image != NULL); - element = dfu_image_get_element (image, 0); - if (element == NULL) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_FOUND, - "no firmware element data to write"); - return NULL; - } - contents = dfu_element_get_contents (element); - return g_bytes_ref (contents); - } + if (priv->format == DFU_FIRMWARE_FORMAT_RAW) + return dfu_firmware_to_raw (firmware, error); - /* plain-old DFU */ - if (priv->format == DFU_FIRMWARE_FORMAT_DFU_1_0) { - GBytes *contents; - DfuElement *element; - image = dfu_firmware_get_image_default (firmware); - g_assert (image != NULL); - element = dfu_image_get_element (image, 0); - if (element == NULL) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_NOT_FOUND, - "no firmware element data to write"); - return NULL; - } - contents = dfu_element_get_contents (element); - g_assert (contents != NULL); - return dfu_firmware_add_footer (firmware, contents, error); - } - - /* DfuSe */ - if (priv->format == DFU_FIRMWARE_FORMAT_DFUSE) { - g_autoptr(GBytes) contents = NULL; - contents = dfu_firmware_write_data_dfuse (firmware, error); - if (contents == NULL) - return NULL; - return dfu_firmware_add_footer (firmware, contents, error); - } + /* DFU or DfuSe*/ + if (priv->format == DFU_FIRMWARE_FORMAT_DFU_1_0 || + priv->format == DFU_FIRMWARE_FORMAT_DFUSE) + return dfu_firmware_to_dfu (firmware, error); /* Intel HEX */ if (priv->format == DFU_FIRMWARE_FORMAT_INTEL_HEX) - return dfu_firmware_write_data_ihex (firmware, error); + return dfu_firmware_to_ihex (firmware, error); /* invalid */ g_set_error (error, @@ -1420,7 +674,6 @@ dfu_firmware_to_string (DfuFirmware *firmware) g_string_append_printf (str, "vid: 0x%04x\n", priv->vid); g_string_append_printf (str, "pid: 0x%04x\n", priv->pid); g_string_append_printf (str, "release: 0x%04x\n", priv->release); - g_string_append_printf (str, "crc: 0x%08x\n", priv->crc); g_string_append_printf (str, "format: %s [0x%04x]\n", dfu_firmware_format_to_string (priv->format), priv->format); @@ -1495,3 +748,19 @@ dfu_firmware_get_cipher_kind (DfuFirmware *firmware) g_return_val_if_fail (DFU_IS_FIRMWARE (firmware), 0); return priv->cipher_kind; } + +/** + * dfu_firmware_set_cipher_kind: + * @firmware: a #DfuFirmware + * + * Sets the kind of cipher used by the firmware file. + * + * Since: 0.6.3 + **/ +void +dfu_firmware_set_cipher_kind (DfuFirmware *firmware, DfuCipherKind cipher_kind) +{ + DfuFirmwarePrivate *priv = GET_PRIVATE (firmware); + g_return_if_fail (DFU_IS_FIRMWARE (firmware)); + priv->cipher_kind = cipher_kind; +} diff --git a/libdfu/dfu-firmware.h b/libdfu/dfu-firmware.h index 6b90628c8..46e4542f0 100644 --- a/libdfu/dfu-firmware.h +++ b/libdfu/dfu-firmware.h @@ -114,6 +114,8 @@ void dfu_firmware_set_release (DfuFirmware *firmware, guint16 release); void dfu_firmware_set_format (DfuFirmware *firmware, DfuFirmwareFormat format); +void dfu_firmware_set_cipher_kind (DfuFirmware *firmware, + DfuCipherKind cipher_kind); gboolean dfu_firmware_parse_data (DfuFirmware *firmware, GBytes *bytes, @@ -133,6 +135,7 @@ gboolean dfu_firmware_write_file (DfuFirmware *firmware, GError **error); gchar *dfu_firmware_to_string (DfuFirmware *firmware); +GHashTable *dfu_firmware_get_metadata_table(DfuFirmware *firmware); const gchar *dfu_firmware_get_metadata (DfuFirmware *firmware, const gchar *key); void dfu_firmware_set_metadata (DfuFirmware *firmware, diff --git a/libdfu/dfu-format-dfu.c b/libdfu/dfu-format-dfu.c new file mode 100644 index 000000000..58a999f26 --- /dev/null +++ b/libdfu/dfu-format-dfu.c @@ -0,0 +1,323 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include + +#include "dfu-element.h" +#include "dfu-format-dfu.h" +#include "dfu-format-metadata.h" +#include "dfu-format-dfuse.h" +#include "dfu-format-raw.h" +#include "dfu-image.h" +#include "dfu-error.h" + +typedef struct __attribute__((packed)) { + guint16 release; + guint16 pid; + guint16 vid; + guint16 ver; + guint8 sig[3]; + guint8 len; + guint32 crc; +} DfuFirmwareFooter; + +/** + * dfu_firmware_detect_dfu: (skip) + * @bytes: data to parse + * + * Attempts to sniff the data and work out the firmware format + * + * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW + **/ +DfuFirmwareFormat +dfu_firmware_detect_dfu (GBytes *bytes) +{ + DfuFirmwareFooter *ftr; + guint8 *data; + gsize len; + + /* check data size */ + data = (guint8 *) g_bytes_get_data (bytes, &len); + if (len < 16) + return DFU_FIRMWARE_FORMAT_UNKNOWN; + + /* check for DFU signature */ + ftr = (DfuFirmwareFooter *) &data[len - sizeof(DfuFirmwareFooter)]; + if (memcmp (ftr->sig, "UFD", 3) != 0) + return DFU_FIRMWARE_FORMAT_RAW; + + return GUINT16_FROM_LE (ftr->ver); +} + +static guint32 _crctbl[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; + +static guint32 +dfu_firmware_generate_crc32 (const guint8 *data, gsize length) +{ + guint i; + guint32 accum = 0xffffffff; + for (i = 0; i < length; i++) + accum = _crctbl[(accum^data[i]) & 0xff] ^ (accum >> 8); + return accum; +} + +/** + * dfu_firmware_from_dfu: (skip) + * @firmware: a #DfuFirmware + * @bytes: data to parse + * @flags: some #DfuFirmwareParseFlags + * @error: a #GError, or %NULL + * + * Unpacks into a firmware object from dfu data. + * + * Returns: %TRUE for success + **/ +gboolean +dfu_firmware_from_dfu (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error) +{ + DfuFirmwareFooter *ftr; + const gchar *cipher_str; + gsize len; + guint32 crc; + guint32 crc_new; + guint8 *data; + g_autoptr(GBytes) contents = NULL; + + /* check data size */ + data = (guint8 *) g_bytes_get_data (bytes, &len); + if (len < 16) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "size check failed, too small"); + return FALSE; + } + + /* check for DFU signature */ + ftr = (DfuFirmwareFooter *) &data[len - sizeof(DfuFirmwareFooter)]; + if (memcmp (ftr->sig, "UFD", 3) != 0) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "no DFU signature"); + return FALSE; + } + + /* check version */ + if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_VERSION_TEST) == 0) { + if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_DFU_1_0 && + dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_DFUSE) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "version check failed, got %04x", + dfu_firmware_get_format (firmware)); + return FALSE; + } + } + + /* verify the checksum */ + crc = GUINT32_FROM_LE (ftr->crc); + if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { + crc_new = dfu_firmware_generate_crc32 (data, len - 4); + if (crc != crc_new) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "CRC failed, expected %04x, got %04x", + crc_new, GUINT32_FROM_LE (ftr->crc)); + return FALSE; + } + } + + /* set from footer */ + dfu_firmware_set_vid (firmware, GUINT16_FROM_LE (ftr->vid)); + dfu_firmware_set_pid (firmware, GUINT16_FROM_LE (ftr->pid)); + dfu_firmware_set_release (firmware, GUINT16_FROM_LE (ftr->release)); + + /* check reported length */ + if (ftr->len > len) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "reported firmware size %04x larger than file %04x", + (guint) ftr->len, (guint) len); + return FALSE; + } + + /* parse the optional metadata segment */ + if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_METADATA) == 0) { + gsize offset = len - ftr->len; + g_autoptr(GBytes) md = g_bytes_new (&data[offset], ftr->len); + if (!dfu_firmware_from_metadata (firmware, md, flags, error)) + return FALSE; + } + + /* set this automatically */ + cipher_str = dfu_firmware_get_metadata (firmware, DFU_METADATA_KEY_CIPHER_KIND); + if (cipher_str != NULL) { + if (g_strcmp0 (cipher_str, "XTEA") == 0) + dfu_firmware_set_cipher_kind (firmware, DFU_CIPHER_KIND_XTEA); + else + g_warning ("Unknown CipherKind: %s", cipher_str); + } + + /* parse DfuSe prefix */ + contents = g_bytes_new_from_bytes (bytes, 0, len - ftr->len); + if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFUSE) + return dfu_firmware_from_dfuse (firmware, contents, flags, error); + + /* just copy old-plain DFU file */ + return dfu_firmware_from_raw (firmware, contents, flags, error); +} + +static GBytes * +dfu_firmware_add_footer (DfuFirmware *firmware, GBytes *contents, GError **error) +{ + DfuFirmwareFooter *ftr; + const guint8 *data_bin; + const guint8 *data_md; + gsize length_bin = 0; + gsize length_md = 0; + guint32 crc_new; + guint8 *buf; + g_autoptr(GBytes) metadata_table = NULL; + + /* get any file metadata */ + metadata_table = dfu_firmware_to_metadata (firmware, error); + if (metadata_table == NULL) + return NULL; + data_md = g_bytes_get_data (metadata_table, &length_md); + + /* add the raw firmware data */ + data_bin = g_bytes_get_data (contents, &length_bin); + buf = g_malloc0 (length_bin + length_md + 0x10); + memcpy (buf + 0, data_bin, length_bin); + + /* add the metadata table */ + memcpy (buf + length_bin, data_md, length_md); + + /* set up LE footer */ + ftr = (DfuFirmwareFooter *) (buf + length_bin + length_md); + ftr->release = GUINT16_TO_LE (dfu_firmware_get_release (firmware)); + ftr->pid = GUINT16_TO_LE (dfu_firmware_get_pid (firmware)); + ftr->vid = GUINT16_TO_LE (dfu_firmware_get_vid (firmware)); + ftr->ver = GUINT16_TO_LE (dfu_firmware_get_format (firmware)); + ftr->len = (guint8) (sizeof (DfuFirmwareFooter) + length_md); + memcpy(ftr->sig, "UFD", 3); + crc_new = dfu_firmware_generate_crc32 (buf, length_bin + length_md + 12); + ftr->crc = GUINT32_TO_LE (crc_new); + + /* return all data */ + return g_bytes_new_take (buf, length_bin + length_md + 0x10); +} + +/** + * dfu_firmware_to_dfu: (skip) + * @firmware: a #DfuFirmware + * @error: a #GError, or %NULL + * + * Packs dfu firmware + * + * Returns: (transfer full): the packed data + **/ +GBytes * +dfu_firmware_to_dfu (DfuFirmware *firmware, GError **error) +{ + /* plain DFU */ + if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFU_1_0) { + GBytes *contents; + DfuElement *element; + DfuImage *image; + image = dfu_firmware_get_image_default (firmware); + g_assert (image != NULL); + element = dfu_image_get_element (image, 0); + if (element == NULL) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no firmware element data to write"); + return NULL; + } + contents = dfu_element_get_contents (element); + return dfu_firmware_add_footer (firmware, contents, error); + } + + /* DfuSe */ + if (dfu_firmware_get_format (firmware) == DFU_FIRMWARE_FORMAT_DFUSE) { + g_autoptr(GBytes) contents = NULL; + contents = dfu_firmware_to_dfuse (firmware, error); + if (contents == NULL) + return NULL; + return dfu_firmware_add_footer (firmware, contents, error); + } + + g_assert_not_reached (); + return NULL; +} diff --git a/libdfu/dfu-image-private.h b/libdfu/dfu-format-dfu.h similarity index 64% rename from libdfu/dfu-image-private.h rename to libdfu/dfu-format-dfu.h index 930648ace..7602bc09e 100644 --- a/libdfu/dfu-image-private.h +++ b/libdfu/dfu-format-dfu.h @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * - * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2015-2016 Richard Hughes * * Licensed under the GNU Lesser General Public License Version 2.1 * @@ -19,19 +19,24 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef __DFU_IMAGE_PRIVATE_H -#define __DFU_IMAGE_PRIVATE_H +#ifndef __DFU_FORMAT_DFU_H +#define __DFU_FORMAT_DFU_H -#include "dfu-image.h" +#include +#include + +#include "dfu-firmware.h" G_BEGIN_DECLS -DfuImage *dfu_image_from_dfuse (const guint8 *data, - guint32 length, - guint32 *consumed, - GError **error); -GBytes *dfu_image_to_dfuse (DfuImage *image); +DfuFirmwareFormat dfu_firmware_detect_dfu (GBytes *bytes); +GBytes *dfu_firmware_to_dfu (DfuFirmware *firmware, + GError **error); +gboolean dfu_firmware_from_dfu (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error); G_END_DECLS -#endif /* __DFU_IMAGE_PRIVATE_H */ +#endif /* __DFU_FORMAT_DFU_H */ diff --git a/libdfu/dfu-format-dfuse.c b/libdfu/dfu-format-dfuse.c new file mode 100644 index 000000000..7306a9ce9 --- /dev/null +++ b/libdfu/dfu-format-dfuse.c @@ -0,0 +1,402 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include + +#include "dfu-element.h" +#include "dfu-format-dfuse.h" +#include "dfu-image.h" +#include "dfu-error.h" + +/* DfuSe element header */ +typedef struct __attribute__((packed)) { + guint32 address; + guint32 size; +} DfuSeElementPrefix; + +/** + * dfu_element_from_dfuse: (skip) + * @data: data buffer + * @length: length of @data we can access + * @consumed: (out): the number of bytes we consued + * @error: a #GError, or %NULL + * + * Unpacks an element from DfuSe data. + * + * Returns: a #DfuElement, or %NULL for error + **/ +static DfuElement * +dfu_element_from_dfuse (const guint8 *data, + guint32 length, + guint32 *consumed, + GError **error) +{ + DfuElement *element = NULL; + DfuSeElementPrefix *el = (DfuSeElementPrefix *) data; + guint32 size; + g_autoptr(GBytes) contents = NULL; + + g_assert_cmpint(sizeof(DfuSeElementPrefix), ==, 8); + + /* check input buffer size */ + if (length < sizeof(DfuSeElementPrefix)) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "invalid element data size %u", + (guint32) length); + return NULL; + } + + /* check size */ + size = GUINT32_FROM_LE (el->size); + if (size + sizeof(DfuSeElementPrefix) > length) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "invalid element size %u, only %u bytes left", + size, + (guint32) (length - sizeof(DfuSeElementPrefix))); + return NULL; + } + + /* create new element */ + element = dfu_element_new (); + dfu_element_set_address (element, GUINT32_FROM_LE (el->address)); + contents = g_bytes_new (data + sizeof(DfuSeElementPrefix), size); + dfu_element_set_contents (element, contents); + + /* return size */ + if (consumed != NULL) + *consumed = (guint32) sizeof(DfuSeElementPrefix) + size; + + return element; +} + +/** + * dfu_element_to_dfuse: (skip) + * @element: a #DfuElement + * + * Packs a DfuSe element. + * + * Returns: (transfer full): the packed data + **/ +static GBytes * +dfu_element_to_dfuse (DfuElement *element) +{ + DfuSeElementPrefix *el; + const guint8 *data; + gsize length; + guint8 *buf; + + data = g_bytes_get_data (dfu_element_get_contents (element), &length); + buf = g_malloc0 (length + sizeof (DfuSeElementPrefix)); + el = (DfuSeElementPrefix *) buf; + el->address = GUINT32_TO_LE (dfu_element_get_address (element)); + el->size = GUINT32_TO_LE (length); + + memcpy (buf + sizeof (DfuSeElementPrefix), data, length); + return g_bytes_new_take (buf, length + sizeof (DfuSeElementPrefix)); +} + +/* DfuSe image header */ +typedef struct __attribute__((packed)) { + guint8 sig[6]; + guint8 alt_setting; + guint32 target_named; + gchar target_name[255]; + guint32 target_size; + guint32 elements; +} DfuSeImagePrefix; + +/** + * dfu_image_from_dfuse: (skip) + * @data: data buffer + * @length: length of @data we can access + * @consumed: (out): the number of bytes we consued + * @error: a #GError, or %NULL + * + * Unpacks an image from DfuSe data. + * + * Returns: a #DfuImage, or %NULL for error + **/ +static DfuImage * +dfu_image_from_dfuse (const guint8 *data, + guint32 length, + guint32 *consumed, + GError **error) +{ + DfuSeImagePrefix *im; + guint32 elements; + guint32 offset = sizeof(DfuSeImagePrefix); + guint j; + g_autoptr(DfuImage) image = NULL; + + g_assert_cmpint(sizeof(DfuSeImagePrefix), ==, 274); + + /* check input buffer size */ + if (length < sizeof(DfuSeImagePrefix)) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "invalid image data size %u", + (guint32) length); + return NULL; + } + + /* verify image signature */ + im = (DfuSeImagePrefix *) data; + if (memcmp (im->sig, "Target", 6) != 0) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INVALID_FILE, + "invalid DfuSe target signature"); + return NULL; + } + + /* create new image */ + image = dfu_image_new (); + dfu_image_set_alt_setting (image, im->alt_setting); + if (GUINT32_FROM_LE (im->target_named) == 0x01) + dfu_image_set_name (image, im->target_name); + + /* parse elements */ + length -= offset; + elements = GUINT32_FROM_LE (im->elements); + for (j = 0; j < elements; j++) { + guint32 consumed_local; + g_autoptr(DfuElement) element = NULL; + element = dfu_element_from_dfuse (data + offset, length, + &consumed_local, error); + if (element == NULL) + return NULL; + dfu_image_add_element (image, element); + offset += consumed_local; + length -= consumed_local; + } + + /* return size */ + if (consumed != NULL) + *consumed = offset; + + return g_object_ref (image); +} + +/** + * dfu_image_to_dfuse: (skip) + * @image: a #DfuImage + * + * Packs a DfuSe image + * + * Returns: (transfer full): the packed data + **/ +static GBytes * +dfu_image_to_dfuse (DfuImage *image) +{ + DfuElement *element; + DfuSeImagePrefix *im; + GBytes *bytes; + GPtrArray *elements; + guint32 length_total = 0; + guint32 offset = sizeof (DfuSeImagePrefix); + guint8 *buf; + guint i; + g_autoptr(GPtrArray) element_array = NULL; + + /* get total size */ + element_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); + elements = dfu_image_get_elements (image); + for (i = 0; i < elements->len; i++) { + element = g_ptr_array_index (elements, i); + bytes = dfu_element_to_dfuse (element); + g_ptr_array_add (element_array, bytes); + length_total += (guint32) g_bytes_get_size (bytes); + } + + /* add prefix */ + buf = g_malloc0 (length_total + sizeof (DfuSeImagePrefix)); + im = (DfuSeImagePrefix *) buf; + memcpy (im->sig, "Target", 6); + im->alt_setting = dfu_image_get_alt_setting (image); + if (dfu_image_get_name (image) != NULL) { + im->target_named = GUINT32_TO_LE (0x01); + memcpy (im->target_name, dfu_image_get_name (image), 255); + } + im->target_size = GUINT32_TO_LE (length_total); + im->elements = GUINT32_TO_LE (elements->len); + + /* copy data */ + for (i = 0; i < element_array->len; i++) { + const guint8 *data; + gsize length; + bytes = g_ptr_array_index (element_array, i); + data = g_bytes_get_data (bytes, &length); + memcpy (buf + offset, data, length); + offset += (guint32) length; + } + return g_bytes_new_take (buf, length_total + sizeof (DfuSeImagePrefix)); +} + +/* DfuSe header */ +typedef struct __attribute__((packed)) { + guint8 sig[5]; + guint8 ver; + guint32 image_size; + guint8 targets; +} DfuSePrefix; + +/** + * dfu_firmware_to_dfuse: (skip) + * @firmware: a #DfuFirmware + * @error: a #GError, or %NULL + * + * Packs a DfuSe firmware + * + * Returns: (transfer full): the packed data + **/ +GBytes * +dfu_firmware_to_dfuse (DfuFirmware *firmware, GError **error) +{ + DfuSePrefix *prefix; + GPtrArray *images; + guint i; + guint32 image_size_total = 0; + guint32 offset = sizeof (DfuSePrefix); + guint8 *buf; + g_autoptr(GPtrArray) dfuse_images = NULL; + + /* get all the image data */ + dfuse_images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); + images = dfu_firmware_get_images (firmware); + for (i = 0; i < images->len; i++) { + DfuImage *im = g_ptr_array_index (images, i); + GBytes *contents; + contents = dfu_image_to_dfuse (im); + image_size_total += (guint32) g_bytes_get_size (contents); + g_ptr_array_add (dfuse_images, contents); + } + g_debug ("image_size_total: %" G_GUINT32_FORMAT, image_size_total); + + buf = g_malloc0 (sizeof (DfuSePrefix) + image_size_total); + + /* DfuSe header */ + prefix = (DfuSePrefix *) buf; + memcpy (prefix->sig, "DfuSe", 5); + prefix->ver = 0x01; + prefix->image_size = GUINT32_TO_LE (offset + image_size_total); + if (images->len > G_MAXUINT8) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "too many (%u) images to write DfuSe file", + images->len); + return FALSE; + } + prefix->targets = (guint8) images->len; + + /* copy images */ + for (i = 0; i < dfuse_images->len; i++) { + GBytes *contents = g_ptr_array_index (dfuse_images, i); + gsize length; + const guint8 *data; + data = g_bytes_get_data (contents, &length); + memcpy (buf + offset, data, length); + offset += (guint32) length; + } + + /* return blob */ + return g_bytes_new_take (buf, sizeof (DfuSePrefix) + image_size_total); +} + +/** + * dfu_firmware_from_dfuse: (skip) + * @firmware: a #DfuFirmware + * @bytes: data to parse + * @flags: some #DfuFirmwareParseFlags + * @error: a #GError, or %NULL + * + * Unpacks into a firmware object from DfuSe data. + * + * Returns: %TRUE for success + **/ +gboolean +dfu_firmware_from_dfuse (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error) +{ + DfuSePrefix *prefix; + gsize len; + guint32 offset = sizeof(DfuSePrefix); + guint8 *data; + guint i; + + /* check the prefix (BE) */ + data = (guint8 *) g_bytes_get_data (bytes, &len); + prefix = (DfuSePrefix *) data; + if (memcmp (prefix->sig, "DfuSe", 5) != 0) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "invalid DfuSe prefix"); + return FALSE; + } + + /* check the version */ + if (prefix->ver != 0x01) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "invalid DfuSe version, got %02x", + prefix->ver); + return FALSE; + } + + /* check image size */ + if (GUINT32_FROM_LE (prefix->image_size) != len) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "invalid DfuSe image size, " + "got %" G_GUINT32_FORMAT ", " + "expected %" G_GSIZE_FORMAT, + GUINT32_FROM_LE (prefix->image_size), + len); + return FALSE; + } + + /* parse the image targets */ + len -= sizeof(DfuSePrefix); + for (i = 0; i < prefix->targets; i++) { + guint consumed; + g_autoptr(DfuImage) image = NULL; + image = dfu_image_from_dfuse (data + offset, (guint32) len, + &consumed, error); + if (image == NULL) + return FALSE; + dfu_firmware_add_image (firmware, image); + offset += consumed; + len -= consumed; + } + return TRUE; +} diff --git a/libdfu/dfu-format-dfuse.h b/libdfu/dfu-format-dfuse.h new file mode 100644 index 000000000..a96a577d3 --- /dev/null +++ b/libdfu/dfu-format-dfuse.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DFU_FORMAT_DFUSE_H +#define __DFU_FORMAT_DFUSE_H + +#include +#include + +#include "dfu-firmware.h" + +G_BEGIN_DECLS + +DfuFirmwareFormat dfu_firmware_detect_dfuse (GBytes *bytes); +GBytes *dfu_firmware_to_dfuse (DfuFirmware *firmware, + GError **error); +gboolean dfu_firmware_from_dfuse (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error); + +G_END_DECLS + +#endif /* __DFU_FORMAT_DFUSE_H */ diff --git a/libdfu/dfu-format-ihex.c b/libdfu/dfu-format-ihex.c new file mode 100644 index 000000000..492e31cf6 --- /dev/null +++ b/libdfu/dfu-format-ihex.c @@ -0,0 +1,311 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include + +#include "dfu-element.h" +#include "dfu-error.h" +#include "dfu-format-ihex.h" +#include "dfu-image.h" + +/** + * dfu_firmware_detect_ihex: (skip) + * @bytes: data to parse + * + * Attempts to sniff the data and work out the firmware format + * + * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW + **/ +DfuFirmwareFormat +dfu_firmware_detect_ihex (GBytes *bytes) +{ + guint8 *data; + gsize len; + data = (guint8 *) g_bytes_get_data (bytes, &len); + if (len < 12) + return DFU_FIRMWARE_FORMAT_UNKNOWN; + if (data[0] != ':') + return DFU_FIRMWARE_FORMAT_UNKNOWN; + return DFU_FIRMWARE_FORMAT_INTEL_HEX; +} + +static guint8 +dfu_firmware_ihex_parse_uint8 (const gchar *data, guint pos) +{ + gchar buffer[3]; + memcpy (buffer, data + pos, 2); + buffer[2] = '\0'; + return (guint8) g_ascii_strtoull (buffer, NULL, 16); +} + +static guint16 +dfu_firmware_ihex_parse_uint16 (const gchar *data, guint pos) +{ + gchar buffer[5]; + memcpy (buffer, data + pos, 4); + buffer[4] = '\0'; + return (guint16) g_ascii_strtoull (buffer, NULL, 16); +} + +#define DFU_INHX32_RECORD_TYPE_DATA 0 +#define DFU_INHX32_RECORD_TYPE_EOF 1 +#define DFU_INHX32_RECORD_TYPE_EXTENDED 4 + +/** + * dfu_firmware_from_ihex: (skip) + * @firmware: a #DfuFirmware + * @bytes: data to parse + * @flags: some #DfuFirmwareParseFlags + * @error: a #GError, or %NULL + * + * Unpacks into a firmware object from raw data. + * + * Returns: %TRUE for success + **/ +gboolean +dfu_firmware_from_ihex (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error) +{ + const gchar *in_buffer; + gsize len_in; + guint16 addr_high = 0; + guint16 addr_low = 0; + guint32 addr32 = 0; + guint32 addr32_last = 0; + guint8 checksum; + guint8 data_tmp; + guint8 len_tmp; + guint8 type; + guint end; + guint i; + guint j; + guint offset = 0; + g_autoptr(DfuElement) element = NULL; + g_autoptr(DfuImage) image = NULL; + g_autoptr(GBytes) contents = NULL; + g_autoptr(GString) string = NULL; + + g_return_val_if_fail (bytes != NULL, FALSE); + + /* create element */ + image = dfu_image_new (); + dfu_image_set_name (image, "ihex"); + element = dfu_element_new (); + + /* parse records */ + in_buffer = g_bytes_get_data (bytes, &len_in); + string = g_string_new (""); + while (offset < len_in) { + + /* check starting token */ + if (in_buffer[offset] != ':') { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INVALID_FILE, + "invalid starting token, got %c at %x", + in_buffer[offset], offset); + return FALSE; + } + + /* check there's enough data for the smallest possible record */ + if (offset + 12 > (guint) len_in) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INVALID_FILE, + "record incomplete at %u, length %u", + offset, (guint) len_in); + return FALSE; + } + + /* length, 16-bit address, type */ + len_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+1); + addr_low = dfu_firmware_ihex_parse_uint16 (in_buffer, offset+3); + type = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+7); + + /* position of checksum */ + end = offset + 9 + len_tmp * 2; + if (end > (guint) len_in) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INVALID_FILE, + "checksum > file length: %u", + end); + return FALSE; + } + + /* verify checksum */ + if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) { + checksum = 0; + for (i = offset + 1; i < end + 2; i += 2) { + data_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, i); + checksum += data_tmp; + } + if (checksum != 0) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INVALID_FILE, + "invalid record checksum"); + return FALSE; + } + } + + /* process different record types */ + switch (type) { + case DFU_INHX32_RECORD_TYPE_DATA: + /* if not contiguous with previous record */ + if ((addr_high + addr_low) != addr32) { + if (addr32 == 0x0) { + g_debug ("base address %04x", addr_low); + dfu_element_set_address (element, addr_low); + } +// addr32 = addr_high + addr_low; + addr32 = ((guint32) addr_high << 16) + addr_low; + } + + /* parse bytes from line */ + for (i = offset + 9; i < end; i += 2) { + /* any holes in the hex record */ + guint32 len_hole = addr32 - addr32_last; + if (addr32_last > 0x0 && len_hole > 1) { + for (j = 1; j < len_hole; j++) { + g_debug ("filling address 0x%04x", + addr32_last + j); + /* although 0xff might be clearer, + * we can't write 0xffff to pic14 */ + g_string_append_c (string, 0x00); + } + } + /* write into buf */ + data_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, i); + g_string_append_c (string, (gchar) data_tmp); + g_debug ("writing address 0x%04x", addr32); + addr32_last = addr32++; + } + break; + case DFU_INHX32_RECORD_TYPE_EOF: + break; + case DFU_INHX32_RECORD_TYPE_EXTENDED: + addr_high = dfu_firmware_ihex_parse_uint16 (in_buffer, offset+9); + g_error ("set base address %x", addr_high); + addr32 = ((guint32) addr_high << 16) + addr_low; + break; + default: + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INVALID_FILE, + "invalid ihex record type %i", + type); + return FALSE; + } + + /* ignore any line return */ + offset = end + 2; + for (; offset < len_in; offset++) { + if (in_buffer[offset] != '\n' && + in_buffer[offset] != '\r') + break; + } + } + + /* add single image */ + contents = g_bytes_new (string->str, string->len); + dfu_element_set_contents (element, contents); + dfu_image_add_element (image, element); + dfu_firmware_add_image (firmware, image); + dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_INTEL_HEX); + return TRUE; +} + +static gboolean +dfu_firmware_to_ihex_element (DfuElement *element, GString *str, GError **error) +{ + GBytes *contents; + const guint8 *data; + const guint chunk_size = 16; + gsize len; + + /* get number of chunks */ + contents = dfu_element_get_contents (element); + data = g_bytes_get_data (contents, &len); + for (gsize i = 0; i < len; i += chunk_size) { + guint8 checksum = 0; + + /* length, 16-bit address, type */ + gsize chunk_len = MIN (len - i, 16); + g_string_append_printf (str, ":%02X%04X%02X", + (guint) chunk_len, + (guint) (dfu_element_get_address (element) + i), + (guint) DFU_INHX32_RECORD_TYPE_DATA); + for (gsize j = 0; j < chunk_len; j++) + g_string_append_printf (str, "%02X", data[i+j]); + + /* add checksum */ + for (gsize j = 0; j < (chunk_len * 2) + 8; j++) + checksum += (guint8) str->str[str->len - (j + 1)]; + g_string_append_printf (str, "%02X\n", checksum); + } + return TRUE; +} + +/** + * dfu_firmware_to_ihex: (skip) + * @firmware: a #DfuFirmware + * @error: a #GError, or %NULL + * + * Packs a IHEX firmware + * + * Returns: (transfer full): the packed data + **/ +GBytes * +dfu_firmware_to_ihex (DfuFirmware *firmware, GError **error) +{ + DfuElement *element; + DfuImage *image; + GPtrArray *images; + GPtrArray *elements; + guint i; + guint j; + g_autoptr(GString) str = NULL; + + /* write all the element data */ + str = g_string_new (""); + images = dfu_firmware_get_images (firmware); + for (i = 0; i < images->len; i++) { + image = g_ptr_array_index (images, i); + elements = dfu_image_get_elements (image); + for (j = 0; j < elements->len; j++) { + element = g_ptr_array_index (elements, j); + if (!dfu_firmware_to_ihex_element (element, + str, + error)) + return NULL; + } + } + + /* add EOF */ + g_string_append_printf (str, ":000000%02XFF\n", + (guint) DFU_INHX32_RECORD_TYPE_EOF); + return g_bytes_new (str->str, str->len); +} diff --git a/libdfu/dfu-format-ihex.h b/libdfu/dfu-format-ihex.h new file mode 100644 index 000000000..88edcf146 --- /dev/null +++ b/libdfu/dfu-format-ihex.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DFU_FORMAT_IHEX_H +#define __DFU_FORMAT_IHEX_H + +#include +#include + +#include "dfu-firmware.h" + +G_BEGIN_DECLS + +DfuFirmwareFormat dfu_firmware_detect_ihex (GBytes *bytes); +GBytes *dfu_firmware_to_ihex (DfuFirmware *firmware, + GError **error); +gboolean dfu_firmware_from_ihex (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error); + +G_END_DECLS + +#endif /* __DFU_FORMAT_IHEX_H */ diff --git a/libdfu/dfu-format-metadata.c b/libdfu/dfu-format-metadata.c new file mode 100644 index 000000000..d046674d5 --- /dev/null +++ b/libdfu/dfu-format-metadata.c @@ -0,0 +1,216 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include + +#include "dfu-element.h" +#include "dfu-format-metadata.h" +#include "dfu-image.h" +#include "dfu-error.h" + +/** + * dfu_firmware_from_metadata: (skip) + * @firmware: a #DfuFirmware + * @bytes: data to parse + * @flags: some #DfuFirmwareParseFlags + * @error: a #GError, or %NULL + * + * Unpacks into a firmware object from metadata data. + * + * The representation in memory is as follows: + * + * uint16 signature='MD' + * uint8 number_of_keys + * uint8 number_of_keys + * uint8 key(n)_length + * ... key(n) (no NUL) + * uint8 value(n)_length + * ... value(n) (no NUL) + * + + * Returns: %TRUE for success + **/ +gboolean +dfu_firmware_from_metadata (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error) +{ + gsize data_length; + guint8 *data; + guint i; + guint idx = 2; + guint kvlen; + guint number_keys; + + /* not big enough */ + data = g_bytes_get_data (bytes, &data_length); + if (data_length <= 0x10) + return TRUE; + + /* signature invalid */ + if (memcmp (data, "MD", 2) != 0) + return TRUE; + + /* parse key=value store */ + number_keys = data[idx++]; + for (i = 0; i < number_keys; i++) { + g_autofree gchar *key = NULL; + g_autofree gchar *value = NULL; + + /* parse key */ + kvlen = data[idx++]; + if (kvlen > 233) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "metadata table corrupt, key=%u", + kvlen); + return FALSE; + } + if (idx + kvlen + 0x10 > data_length) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "metadata table corrupt"); + return FALSE; + } + key = g_strndup ((const gchar *) data + idx, kvlen); + idx += kvlen; + + /* parse value */ + kvlen = data[idx++]; + if (kvlen > 233) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "metadata table corrupt, value=%u", + kvlen); + return FALSE; + } + if (idx + kvlen + 0x10 > data_length) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_INTERNAL, + "metadata table corrupt"); + return FALSE; + } + value = g_strndup ((const gchar *) data + idx, kvlen); + idx += kvlen; + dfu_firmware_set_metadata (firmware, key, value); + } + return TRUE; +} + +/** + * dfu_firmware_to_metadata: (skip) + * @firmware: a #DfuFirmware + * @error: a #GError, or %NULL + * + * Packs metadata firmware + * + * Returns: (transfer full): the packed data + **/ +GBytes * +dfu_firmware_to_metadata (DfuFirmware *firmware, GError **error) +{ + GList *l; + GHashTable *metadata; + guint8 mdbuf[239]; + guint idx = 0; + guint number_keys; + g_autoptr(GList) keys = NULL; + + /* no metadata */ + metadata = dfu_firmware_get_metadata_table (firmware); + if (g_hash_table_size (metadata) == 0) + return g_bytes_new (NULL, 0); + + /* check the number of keys */ + keys = g_hash_table_get_keys (metadata); + number_keys = g_list_length (keys); + if (number_keys > 59) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "too many metadata keys (%u)", + number_keys); + return NULL; + } + + /* write the signature */ + mdbuf[idx++] = 'M'; + mdbuf[idx++] = 'D'; + mdbuf[idx++] = (guint8) number_keys; + for (l = keys; l != NULL; l = l->next) { + const gchar *key; + const gchar *value; + guint key_len; + guint value_len; + + /* check key and value length */ + key = l->data; + key_len = (guint) strlen (key); + if (key_len > 233) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "metdata key too long: %s", + key); + return NULL; + } + value = g_hash_table_lookup (metadata, key); + value_len = (guint) strlen (value); + if (value_len > 233) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "value too long: %s", + value); + return NULL; + } + + /* do we still have space? */ + if (idx + key_len + value_len + 2 > sizeof(mdbuf)) { + g_set_error (error, + DFU_ERROR, + DFU_ERROR_NOT_SUPPORTED, + "not enough space in metadata table, " + "already used %u bytes", idx); + return NULL; + } + + /* write the key */ + mdbuf[idx++] = (guint8) key_len; + memcpy(mdbuf + idx, key, key_len); + idx += key_len; + + /* write the value */ + mdbuf[idx++] = (guint8) value_len; + memcpy(mdbuf + idx, value, value_len); + idx += value_len; + } + g_debug ("metadata table was %u/%" G_GSIZE_FORMAT " bytes", + idx, sizeof(mdbuf)); + return g_bytes_new (mdbuf, idx); +} diff --git a/libdfu/dfu-element-private.h b/libdfu/dfu-format-metadata.h similarity index 66% rename from libdfu/dfu-element-private.h rename to libdfu/dfu-format-metadata.h index a08b4e3a7..6f26d3321 100644 --- a/libdfu/dfu-element-private.h +++ b/libdfu/dfu-format-metadata.h @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * - * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2015-2016 Richard Hughes * * Licensed under the GNU Lesser General Public License Version 2.1 * @@ -19,19 +19,23 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef __DFU_ELEMENT_PRIVATE_H -#define __DFU_ELEMENT_PRIVATE_H +#ifndef __DFU_FORMAT_METADATA_H +#define __DFU_FORMAT_METADATA_H -#include "dfu-element.h" +#include +#include + +#include "dfu-firmware.h" G_BEGIN_DECLS -DfuElement *dfu_element_from_dfuse (const guint8 *data, - guint32 length, - guint32 *consumed, - GError **error); -GBytes *dfu_element_to_dfuse (DfuElement *element); +GBytes *dfu_firmware_to_metadata (DfuFirmware *firmware, + GError **error); +gboolean dfu_firmware_from_metadata (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error); G_END_DECLS -#endif /* __DFU_ELEMENT_PRIVATE_H */ +#endif /* __DFU_FORMAT_METADATA_H */ diff --git a/libdfu/dfu-format-raw.c b/libdfu/dfu-format-raw.c new file mode 100644 index 000000000..9737920d9 --- /dev/null +++ b/libdfu/dfu-format-raw.c @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include + +#include "dfu-element.h" +#include "dfu-format-raw.h" +#include "dfu-image.h" +#include "dfu-error.h" + +/** + * dfu_firmware_detect_raw: (skip) + * @bytes: data to parse + * + * Attempts to sniff the data and work out the firmware format + * + * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW + **/ +DfuFirmwareFormat +dfu_firmware_detect_raw (GBytes *bytes) +{ + return DFU_FIRMWARE_FORMAT_RAW; +} + +/** + * dfu_firmware_from_raw: (skip) + * @firmware: a #DfuFirmware + * @bytes: data to parse + * @flags: some #DfuFirmwareParseFlags + * @error: a #GError, or %NULL + * + * Unpacks into a firmware object from raw data. + * + * Returns: %TRUE for success + **/ +gboolean +dfu_firmware_from_raw (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error) +{ + g_autoptr(DfuElement) element = NULL; + g_autoptr(DfuImage) image = NULL; + image = dfu_image_new (); + element = dfu_element_new (); + dfu_element_set_contents (element, bytes); + dfu_image_add_element (image, element); + dfu_firmware_add_image (firmware, image); + return TRUE; +} + +/** + * dfu_firmware_to_raw: (skip) + * @firmware: a #DfuFirmware + * @error: a #GError, or %NULL + * + * Packs raw firmware + * + * Returns: (transfer full): the packed data + **/ +GBytes * +dfu_firmware_to_raw (DfuFirmware *firmware, GError **error) +{ + DfuElement *element; + DfuImage *image; + GBytes *contents; + + image = dfu_firmware_get_image_default (firmware); + if (image == NULL) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no firmware image data to write"); + return NULL; + } + element = dfu_image_get_element (image, 0); + if (element == NULL) { + g_set_error_literal (error, + DFU_ERROR, + DFU_ERROR_NOT_FOUND, + "no firmware element data to write"); + return NULL; + } + contents = dfu_element_get_contents (element); + return g_bytes_ref (contents); +} diff --git a/libdfu/dfu-format-raw.h b/libdfu/dfu-format-raw.h new file mode 100644 index 000000000..61fdf44f5 --- /dev/null +++ b/libdfu/dfu-format-raw.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015-2016 Richard Hughes + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DFU_FORMAT_RAW_H +#define __DFU_FORMAT_RAW_H + +#include +#include + +#include "dfu-firmware.h" + +G_BEGIN_DECLS + +DfuFirmwareFormat dfu_firmware_detect_raw (GBytes *bytes); +GBytes *dfu_firmware_to_raw (DfuFirmware *firmware, + GError **error); +gboolean dfu_firmware_from_raw (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error); + +G_END_DECLS + +#endif /* __DFU_FORMAT_RAW_H */ diff --git a/libdfu/dfu-image.c b/libdfu/dfu-image.c index 07b978ba4..9ad756cb6 100644 --- a/libdfu/dfu-image.c +++ b/libdfu/dfu-image.c @@ -35,9 +35,9 @@ #include #include "dfu-common.h" -#include "dfu-element-private.h" +#include "dfu-element.h" #include "dfu-error.h" -#include "dfu-image-private.h" +#include "dfu-image.h" static void dfu_image_finalize (GObject *object); @@ -272,6 +272,10 @@ dfu_image_set_name (DfuImage *image, const gchar *name) sz = MIN ((guint16) strlen (name), 0xff - 1); memcpy (priv->name, name, sz); } + + /* copy junk data in self tests for 1:1 copies */ + if (name != NULL && G_UNLIKELY (g_getenv ("DFU_SELF_TEST") != NULL)) + memcpy (priv->name, name, 0xff); } /** @@ -312,142 +316,3 @@ dfu_image_to_string (DfuImage *image) g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } - -/* DfuSe image header */ -typedef struct __attribute__((packed)) { - guint8 sig[6]; - guint8 alt_setting; - guint32 target_named; - gchar target_name[255]; - guint32 target_size; - guint32 elements; -} DfuSeImagePrefix; - -/** - * dfu_image_from_dfuse: (skip) - * @data: data buffer - * @length: length of @data we can access - * @consumed: (out): the number of bytes we consued - * @error: a #GError, or %NULL - * - * Unpacks an image from DfuSe data. - * - * Returns: a #DfuImage, or %NULL for error - **/ -DfuImage * -dfu_image_from_dfuse (const guint8 *data, - guint32 length, - guint32 *consumed, - GError **error) -{ - DfuImagePrivate *priv; - DfuSeImagePrefix *im; - guint32 elements; - guint32 offset = sizeof(DfuSeImagePrefix); - guint j; - g_autoptr(DfuImage) image = NULL; - - g_assert_cmpint(sizeof(DfuSeImagePrefix), ==, 274); - - /* check input buffer size */ - if (length < sizeof(DfuSeImagePrefix)) { - g_set_error (error, - DFU_ERROR, - DFU_ERROR_INTERNAL, - "invalid image data size %u", - (guint32) length); - return NULL; - } - - /* verify image signature */ - im = (DfuSeImagePrefix *) data; - if (memcmp (im->sig, "Target", 6) != 0) { - g_set_error_literal (error, - DFU_ERROR, - DFU_ERROR_INVALID_FILE, - "invalid DfuSe target signature"); - return NULL; - } - - /* create new image */ - image = dfu_image_new (); - priv = GET_PRIVATE (image); - priv->alt_setting = im->alt_setting; - if (GUINT32_FROM_LE (im->target_named) == 0x01) - memcpy (priv->name, im->target_name, 255); - - /* parse elements */ - length -= offset; - elements = GUINT32_FROM_LE (im->elements); - for (j = 0; j < elements; j++) { - guint32 consumed_local; - g_autoptr(DfuElement) element = NULL; - element = dfu_element_from_dfuse (data + offset, length, - &consumed_local, error); - if (element == NULL) - return NULL; - dfu_image_add_element (image, element); - offset += consumed_local; - length -= consumed_local; - } - - /* return size */ - if (consumed != NULL) - *consumed = offset; - - return g_object_ref (image); -} - -/** - * dfu_image_to_dfuse: (skip) - * @image: a #DfuImage - * - * Packs a DfuSe image - * - * Returns: (transfer full): the packed data - **/ -GBytes * -dfu_image_to_dfuse (DfuImage *image) -{ - DfuImagePrivate *priv = GET_PRIVATE (image); - DfuElement *element; - DfuSeImagePrefix *im; - GBytes *bytes; - guint32 length_total = 0; - guint32 offset = sizeof (DfuSeImagePrefix); - guint8 *buf; - guint i; - g_autoptr(GPtrArray) element_array = NULL; - - /* get total size */ - element_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref); - for (i = 0; i < priv->elements->len; i++) { - element = g_ptr_array_index (priv->elements, i); - bytes = dfu_element_to_dfuse (element); - g_ptr_array_add (element_array, bytes); - length_total += (guint32) g_bytes_get_size (bytes); - } - - /* add prefix */ - buf = g_malloc0 (length_total + sizeof (DfuSeImagePrefix)); - im = (DfuSeImagePrefix *) buf; - memcpy (im->sig, "Target", 6); - im->alt_setting = priv->alt_setting; - if (priv->name != NULL) { - im->target_named = GUINT32_TO_LE (0x01); - memcpy (im->target_name, priv->name, 255); - } - im->target_size = GUINT32_TO_LE (length_total); - im->elements = GUINT32_TO_LE (priv->elements->len); - - /* copy data */ - for (i = 0; i < element_array->len; i++) { - const guint8 *data; - gsize length; - bytes = g_ptr_array_index (element_array, i); - data = g_bytes_get_data (bytes, &length); - memcpy (buf + offset, data, length); - offset += (guint32) length; - } - return g_bytes_new_take (buf, length_total + sizeof (DfuSeImagePrefix)); -} diff --git a/libdfu/dfu-self-test.c b/libdfu/dfu-self-test.c index 683d9af02..339053e13 100644 --- a/libdfu/dfu-self-test.c +++ b/libdfu/dfu-self-test.c @@ -675,6 +675,7 @@ main (int argc, char **argv) /* log everything */ g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); + g_setenv ("DFU_SELF_TEST", "", FALSE); /* tests go here */ g_test_add_func ("/libdfu/enums", dfu_enums_func);