methods to add/retrieve board config to binary

with this, the combining methods can combine both a board and user
config into one binary, which is a step closer to having a command that
does it, and then we can start removing BoardConfig.h from the firmware
build and replace it with patched binaries.

to test it, another retrieval method was added too, and this will
probably be used for more dump commands or a better info or something
like that

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
Brian S. Stephan 2024-01-06 11:08:29 -06:00
parent bc64a6531b
commit ef71db6f5e
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
3 changed files with 71 additions and 18 deletions

View File

@ -31,19 +31,29 @@ class FirmwareLengthError(ValueError):
"""Exception raised when the firmware is too large to fit the known storage location.""" """Exception raised when the firmware is too large to fit the known storage location."""
def combine_firmware_and_config(firmware_binary: bytearray, config_binary: bytearray, def combine_firmware_and_config(firmware_binary: bytearray, board_config_binary: bytearray,
replace_extra: bool = False) -> bytearray: user_config_binary: bytearray, replace_extra: bool = False) -> bytearray:
"""Given firmware and config binaries, combine the two to one, with proper offsets for GP2040-CE. """Given firmware and board and/or user config binaries, combine to one binary with proper offsets for GP2040-CE.
Args: Args:
firmware_binary: binary data of the raw GP2040-CE firmware, probably but not necessarily unpadded firmware_binary: binary data of the raw GP2040-CE firmware, probably but not necessarily unpadded
config_binary: binary data of board config + footer, possibly padded to be a full storage section board_config_binary: binary data of board config + footer, possibly padded to be a full storage section
user_config_binary: binary data of user config + footer, possibly padded to be a full storage section
replace_extra: if larger than normal firmware files should have their overage replaced replace_extra: if larger than normal firmware files should have their overage replaced
Returns: Returns:
the resulting correctly-offset binary suitable for a GP2040-CE board the resulting correctly-offset binary suitable for a GP2040-CE board
""" """
return (pad_binary_up_to_user_config(firmware_binary, or_truncate=replace_extra) + if not board_config_binary and not user_config_binary:
pad_config_to_storage_size(config_binary)) raise ValueError("at least one config binary must be provided!")
combined = copy.copy(firmware_binary)
if board_config_binary:
combined = (pad_binary_up_to_board_config(combined, or_truncate=replace_extra) +
pad_config_to_storage_size(board_config_binary))
if user_config_binary:
combined = (pad_binary_up_to_user_config(combined, or_truncate=replace_extra) +
pad_config_to_storage_size(user_config_binary))
return combined
def concatenate_firmware_and_storage_files(firmware_filename: str, binary_user_config_filename: Optional[str] = None, def concatenate_firmware_and_storage_files(firmware_filename: str, binary_user_config_filename: Optional[str] = None,
@ -62,13 +72,13 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, binary_user_c
new_binary = None new_binary = None
if binary_user_config_filename: if binary_user_config_filename:
with open(firmware_filename, 'rb') as firmware, open(binary_user_config_filename, 'rb') as storage: with open(firmware_filename, 'rb') as firmware, open(binary_user_config_filename, 'rb') as storage:
new_binary = combine_firmware_and_config(bytearray(firmware.read()), bytearray(storage.read()), new_binary = combine_firmware_and_config(bytearray(firmware.read()), None, bytearray(storage.read()),
replace_extra=replace_extra) replace_extra=replace_extra)
elif json_user_config_filename: elif json_user_config_filename:
with open(firmware_filename, 'rb') as firmware, open(json_user_config_filename, 'r') as json_file: with open(firmware_filename, 'rb') as firmware, open(json_user_config_filename, 'r') as json_file:
config = get_config_from_json(json_file.read()) config = get_config_from_json(json_file.read())
serialized_config = serialize_config_with_footer(config) serialized_config = serialize_config_with_footer(config)
new_binary = combine_firmware_and_config(bytearray(firmware.read()), serialized_config, new_binary = combine_firmware_and_config(bytearray(firmware.read()), None, serialized_config,
replace_extra=replace_extra) replace_extra=replace_extra)
if not new_binary: if not new_binary:
@ -162,7 +172,7 @@ def replace_config_in_binary(board_binary: bytearray, config_binary: bytearray)
""" """
if len(board_binary) < USER_CONFIG_BINARY_LOCATION + STORAGE_SIZE: if len(board_binary) < USER_CONFIG_BINARY_LOCATION + STORAGE_SIZE:
# this is functionally the same, since this doesn't sanity check the firmware # this is functionally the same, since this doesn't sanity check the firmware
return combine_firmware_and_config(board_binary, config_binary) return combine_firmware_and_config(board_binary, None, config_binary)
else: else:
new_binary = bytearray(copy.copy(board_binary)) new_binary = bytearray(copy.copy(board_binary))
new_config = pad_config_to_storage_size(config_binary) new_config = pad_config_to_storage_size(config_binary)

View File

@ -194,6 +194,19 @@ def get_storage_section(content: bytes, address: int) -> bytes:
return content[address:(address + STORAGE_SIZE)] return content[address:(address + STORAGE_SIZE)]
def get_board_storage_section(content: bytes) -> bytes:
"""Get the board storage area from what should be a whole board GP2040-CE dump.
Args:
content: bytes of a GP2040-CE whole board dump
Returns:
the presumed storage section from the binary
Raises:
ConfigLengthError: if the provided bytes don't appear to have a storage section
"""
return get_storage_section(content, BOARD_CONFIG_BINARY_LOCATION)
def get_user_storage_section(content: bytes) -> bytes: def get_user_storage_section(content: bytes) -> bytes:
"""Get the user storage area from what should be a whole board GP2040-CE dump. """Get the user storage area from what should be a whole board GP2040-CE dump.

View File

@ -15,8 +15,8 @@ from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and
concatenate_firmware_and_storage_files, get_gp2040ce_from_usb, concatenate_firmware_and_storage_files, get_gp2040ce_from_usb,
pad_binary_up_to_board_config, pad_binary_up_to_user_config, pad_binary_up_to_board_config, pad_binary_up_to_user_config,
replace_config_in_binary, write_new_config_to_filename, write_new_config_to_usb) replace_config_in_binary, write_new_config_to_filename, write_new_config_to_usb)
from gp2040ce_bintools.storage import (get_config, get_config_footer, get_user_storage_section, from gp2040ce_bintools.storage import (get_board_storage_section, get_config, get_config_footer,
serialize_config_with_footer) get_user_storage_section, serialize_config_with_footer)
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
@ -96,33 +96,63 @@ def test_padding_firmware_to_board(firmware_binary):
assert len(padded) == 2080768 - (16 * 1024) assert len(padded) == 2080768 - (16 * 1024)
def test_firmware_plus_storage(firmware_binary, storage_dump): def test_firmware_plus_storage_section(firmware_binary, storage_dump):
"""Test that combining firmware and storage produces a valid combined binary.""" """Test that combining firmware and storage produces a valid combined binary."""
whole_board = combine_firmware_and_config(firmware_binary, storage_dump) whole_board = combine_firmware_and_config(firmware_binary, None, storage_dump)
# if this is valid, we should be able to find the storage and footer again # if this is valid, we should be able to find the storage and footer again
storage = get_user_storage_section(whole_board) storage = get_user_storage_section(whole_board)
footer_size, _, _ = get_config_footer(storage) footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309 assert footer_size == 3309
def test_firmware_plus_config_binary(firmware_binary, config_binary): def test_firmware_plus_user_config_binary(firmware_binary, config_binary):
"""Test that combining firmware and storage produces a valid combined binary.""" """Test that combining firmware and user config produces a valid combined binary."""
whole_board = combine_firmware_and_config(firmware_binary, config_binary) whole_board = combine_firmware_and_config(firmware_binary, None, config_binary)
# if this is valid, we should be able to find the storage and footer again # if this is valid, we should be able to find the storage and footer again
storage = get_user_storage_section(whole_board) storage = get_user_storage_section(whole_board)
footer_size, _, _ = get_config_footer(storage) footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309 assert footer_size == 3309
def test_chunky_firmware_plus_config_binary(config_binary): def test_chunky_firmware_plus_user_config_binary(config_binary):
"""Test that combining giant firmware and storage produces a valid combined binary.""" """Test that combining giant firmware and storage produces a valid combined binary."""
whole_board = combine_firmware_and_config(bytearray(b'\x00' * 4 * 1024 * 1024), config_binary, replace_extra=True) whole_board = combine_firmware_and_config(bytearray(b'\x00' * 4 * 1024 * 1024), None, config_binary,
replace_extra=True)
# if this is valid, we should be able to find the storage and footer again # if this is valid, we should be able to find the storage and footer again
storage = get_user_storage_section(whole_board) storage = get_user_storage_section(whole_board)
footer_size, _, _ = get_config_footer(storage) footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309 assert footer_size == 3309
def test_firmware_plus_board_config_binary(firmware_binary, config_binary):
"""Test that combining firmware and board config produces a valid combined binary."""
almost_whole_board = combine_firmware_and_config(firmware_binary, config_binary, None)
assert len(almost_whole_board) == (2 * 1024 * 1024) - (16 * 1024)
# if this is valid, we should be able to find the storage and footer again
storage = get_board_storage_section(almost_whole_board)
footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309
def test_firmware_plus_board_and_user_config_binary(firmware_binary, config_binary):
"""Test that combining firmware and both board and user configs produces a valid combined binary."""
whole_board = combine_firmware_and_config(firmware_binary, config_binary, config_binary)
assert len(whole_board) == 2 * 1024 * 1024
# if this is valid, we should be able to find the storage and footer again
storage = get_board_storage_section(whole_board)
footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309
storage = get_user_storage_section(whole_board)
footer_size, _, _ = get_config_footer(storage)
assert footer_size == 3309
def test_combine_must_get_at_least_one_config(firmware_binary):
"""Test that we error if we are asked to combine with nothing to combine."""
with pytest.raises(ValueError):
combine_firmware_and_config(firmware_binary, None, None)
def test_replace_config_in_binary(config_binary): def test_replace_config_in_binary(config_binary):
"""Test that a config binary is placed in the storage location of a source binary to overwrite.""" """Test that a config binary is placed in the storage location of a source binary to overwrite."""
whole_board = replace_config_in_binary(bytearray(b'\x00' * 3 * 1024 * 1024), config_binary) whole_board = replace_config_in_binary(bytearray(b'\x00' * 3 * 1024 * 1024), config_binary)