From c314b89316243b7c626644c723b255b628e3035c Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Thu, 26 Oct 2017 08:52:51 +0100 Subject: [PATCH] dfu: Add DfuChunked This allows us to segment a buffer taking into account page borders and maximum transfer sizes. --- plugins/dfu/dfu-chunked.c | 176 ++++++++++++++++++++++++++++++++++++ plugins/dfu/dfu-chunked.h | 54 +++++++++++ plugins/dfu/dfu-self-test.c | 46 ++++++++++ plugins/dfu/meson.build | 1 + 4 files changed, 277 insertions(+) create mode 100644 plugins/dfu/dfu-chunked.c create mode 100644 plugins/dfu/dfu-chunked.h diff --git a/plugins/dfu/dfu-chunked.c b/plugins/dfu/dfu-chunked.c new file mode 100644 index 000000000..3c06998b3 --- /dev/null +++ b/plugins/dfu/dfu-chunked.c @@ -0,0 +1,176 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 20157 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-chunked.h" + +/** + * dfu_chunked_packet_new: + * @idx: the packet number + * @page: the hardware memory page + * @address: the address *within* the page + * @data: the data + * @data_sz: size of @data_sz + * + * Creates a new packet of chunked data. + * + * Return value: (transfer full): a #DfuChunkedPacket + **/ +DfuChunkedPacket * +dfu_chunked_packet_new (guint32 idx, + guint32 page, + guint32 address, + const guint8 *data, + guint32 data_sz) +{ + DfuChunkedPacket *item = g_new0 (DfuChunkedPacket, 1); + item->idx = idx; + item->page = page; + item->address = address; + item->data = data; + item->data_sz = data_sz; + return item; +} + +/** + * dfu_chunked_packet_to_string: + * @item: a #DfuChunkedPacket + * + * Converts the chunked packet to a string representation. + * + * Return value: (transfer full): A string + **/ +gchar * +dfu_chunked_packet_to_string (DfuChunkedPacket *item) +{ + g_autoptr(GString) str = g_string_new (NULL); + if (item->data != NULL) { + for (guint32 i = 0; i < item->data_sz; i++) { + gchar tmp = (gchar) item->data[i]; + if (tmp == 0x00) + break; + g_string_append_c (str, g_ascii_isalnum (tmp) ? tmp : '?'); + } + } + return g_strdup_printf ("#%02" G_GUINT32_FORMAT ": page:%02x " + "addr:%04x len:%02" G_GUINT32_FORMAT " %s", + item->idx, + (guint) item->page, + (guint) item->address, + item->data_sz, + str->str); +} + +/** + * dfu_chunked_to_string: + * @segments: (element-type DfuChunkedPacket): array of packets + * + * Converts all the chunked packets in an array to a string representation. + * + * Return value: (transfer full): A string + **/ +gchar * +dfu_chunked_to_string (GPtrArray *segments) +{ + GString *str = g_string_new (NULL); + for (guint i = 0; i < segments->len; i++) { + DfuChunkedPacket *item = g_ptr_array_index (segments, i); + g_autofree gchar *tmp = dfu_chunked_packet_to_string (item); + g_string_append_printf (str, "%s\n", tmp); + } + return g_string_free (str, FALSE); +} + +/** + * dfu_chunked_new: + * @data: a linear blob of memory, or %NULL + * @data_sz: size of @data_sz + * @addr_start: the hardware address offset, or 0 + * @page_sz: the hardware page size, or 0 + * @packet_sz: the transfer size, or 0 + * + * Chunks a linear blob of memory into packets, ensuring each packet does not + * cross a package boundary and is less that a specific transfer size. + * + * Return value: (element-type DfuChunkedPacket): array of packets + **/ +GPtrArray * +dfu_chunked_new (const guint8 *data, + guint32 data_sz, + guint32 addr_start, + guint32 page_sz, + guint32 packet_sz) +{ + GPtrArray *segments = NULL; + guint32 page_old = G_MAXUINT32; + guint32 idx; + guint32 last_flush = 0; + + g_return_val_if_fail (data_sz > 0, NULL); + + segments = g_ptr_array_new_with_free_func (g_free); + for (idx = 1; idx < data_sz; idx++) { + guint32 page = 0; + if (page_sz > 0) + page = (addr_start + idx) / page_sz; + if (page_old == G_MAXUINT32) { + page_old = page; + } else if (page != page_old) { + const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; + g_ptr_array_add (segments, + dfu_chunked_packet_new (segments->len, + page_old, + (addr_start + last_flush) % page_sz, + data_offset, + idx - last_flush)); + last_flush = idx; + page_old = page; + continue; + } + if (packet_sz > 0 && idx - last_flush >= packet_sz) { + const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; + g_ptr_array_add (segments, + dfu_chunked_packet_new (segments->len, + page, + (addr_start + last_flush) % page_sz, + data_offset, + idx - last_flush)); + last_flush = idx; + continue; + } + } + if (last_flush != idx) { + const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; + guint32 page = 0; + if (page_sz > 0) + page = (addr_start + (idx - 1)) / page_sz; + g_ptr_array_add (segments, + dfu_chunked_packet_new (segments->len, + page, + (addr_start + last_flush) % page_sz, + data_offset, + data_sz - last_flush)); + } + return segments; +} diff --git a/plugins/dfu/dfu-chunked.h b/plugins/dfu/dfu-chunked.h new file mode 100644 index 000000000..e99a9f68d --- /dev/null +++ b/plugins/dfu/dfu-chunked.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017 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_CHUNKED_H +#define __DFU_CHUNKED_H + +#include +#include + +G_BEGIN_DECLS + +typedef struct { + guint32 idx; + guint32 page; + guint32 address; + const guint8 *data; + guint32 data_sz; +} DfuChunkedPacket; + +DfuChunkedPacket *dfu_chunked_packet_new (guint32 idx, + guint32 page, + guint32 address, + const guint8 *data, + guint32 data_sz); +gchar *dfu_chunked_packet_to_string (DfuChunkedPacket *item); + +gchar *dfu_chunked_to_string (GPtrArray *chunked); +GPtrArray *dfu_chunked_new (const guint8 *data, + guint32 data_sz, + guint32 addr_start, + guint32 page_sz, + guint32 packet_sz); + +G_END_DECLS + +#endif /* __DFU_CHUNKED_H */ diff --git a/plugins/dfu/dfu-self-test.c b/plugins/dfu/dfu-self-test.c index 0364e4f8e..21efe81db 100644 --- a/plugins/dfu/dfu-self-test.c +++ b/plugins/dfu/dfu-self-test.c @@ -26,6 +26,7 @@ #include #include +#include "dfu-chunked.h" #include "dfu-cipher-xtea.h" #include "dfu-common.h" #include "dfu-context.h" @@ -926,6 +927,50 @@ dfu_patch_func (void) g_debug ("serialized blob %s", serialized_str); } +static void +dfu_chunked_func (void) +{ + g_autofree gchar *chunked1_str = NULL; + g_autofree gchar *chunked2_str = NULL; + g_autofree gchar *chunked3_str = NULL; + g_autofree gchar *chunked4_str = NULL; + g_autoptr(GPtrArray) chunked1 = NULL; + g_autoptr(GPtrArray) chunked2 = NULL; + g_autoptr(GPtrArray) chunked3 = NULL; + g_autoptr(GPtrArray) chunked4 = NULL; + + chunked3 = dfu_chunked_new ((const guint8 *) "123456", 6, 0x0, 3, 3); + chunked3_str = dfu_chunked_to_string (chunked3); + g_print ("\n%s", chunked3_str); + g_assert_cmpstr (chunked3_str, ==, "#00: page:00 addr:0000 len:03 123\n" + "#01: page:01 addr:0000 len:03 456\n"); + + chunked4 = dfu_chunked_new ((const guint8 *) "123456", 6, 0x4, 4, 4); + chunked4_str = dfu_chunked_to_string (chunked4); + g_print ("\n%s", chunked4_str); + g_assert_cmpstr (chunked4_str, ==, "#00: page:01 addr:0000 len:04 1234\n" + "#01: page:02 addr:0000 len:02 56\n"); + + chunked1 = dfu_chunked_new ((const guint8 *) "0123456789abcdef", 16, 0x0, 10, 4); + chunked1_str = dfu_chunked_to_string (chunked1); + g_print ("\n%s", chunked1_str); + g_assert_cmpstr (chunked1_str, ==, "#00: page:00 addr:0000 len:04 0123\n" + "#01: page:00 addr:0004 len:04 4567\n" + "#02: page:00 addr:0008 len:02 89\n" + "#03: page:01 addr:0000 len:04 abcd\n" + "#04: page:01 addr:0004 len:02 ef\n"); + + chunked2 = dfu_chunked_new ((const guint8 *) "XXXXXXYYYYYYZZZZZZ", 18, 0x0, 6, 4); + chunked2_str = dfu_chunked_to_string (chunked2); + g_print ("\n%s", chunked2_str); + g_assert_cmpstr (chunked2_str, ==, "#00: page:00 addr:0000 len:04 XXXX\n" + "#01: page:00 addr:0004 len:02 XX\n" + "#02: page:01 addr:0000 len:04 YYYY\n" + "#03: page:01 addr:0004 len:02 YY\n" + "#04: page:02 addr:0000 len:04 ZZZZ\n" + "#05: page:02 addr:0004 len:02 ZZ\n"); +} + int main (int argc, char **argv) { @@ -938,6 +983,7 @@ main (int argc, char **argv) g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); /* tests go here */ + g_test_add_func ("/dfu/chunked", dfu_chunked_func); g_test_add_func ("/dfu/patch", dfu_patch_func); g_test_add_func ("/dfu/patch{merges}", dfu_patch_merges_func); g_test_add_func ("/dfu/patch{apply}", dfu_patch_apply_func); diff --git a/plugins/dfu/meson.build b/plugins/dfu/meson.build index 37cea66ee..c33ac5442 100644 --- a/plugins/dfu/meson.build +++ b/plugins/dfu/meson.build @@ -3,6 +3,7 @@ cargs = ['-DG_LOG_DOMAIN="FuPluginDfu"'] dfu = static_library( 'dfu', sources : [ + 'dfu-chunked.c', 'dfu-cipher-xtea.c', 'dfu-common.c', 'dfu-context.c',