dfu: Add DfuChunked

This allows us to segment a buffer taking into account page borders and maximum
transfer sizes.
This commit is contained in:
Richard Hughes 2017-10-26 08:52:51 +01:00
parent 9acf8cd16f
commit c314b89316
4 changed files with 277 additions and 0 deletions

176
plugins/dfu/dfu-chunked.c Normal file
View File

@ -0,0 +1,176 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 20157 Richard Hughes <richard@hughsie.com>
*
* 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 <string.h>
#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;
}

54
plugins/dfu/dfu-chunked.h Normal file
View File

@ -0,0 +1,54 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* 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 <glib.h>
#include <gusb.h>
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 */

View File

@ -26,6 +26,7 @@
#include <string.h>
#include <fnmatch.h>
#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);

View File

@ -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',