diff --git a/gp2040ce_bintools/builder.py b/gp2040ce_bintools/builder.py index d723c10..9a02815 100644 --- a/gp2040ce_bintools/builder.py +++ b/gp2040ce_bintools/builder.py @@ -31,19 +31,29 @@ class FirmwareLengthError(ValueError): """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, - replace_extra: bool = False) -> bytearray: - """Given firmware and config binaries, combine the two to one, with proper offsets for GP2040-CE. +def combine_firmware_and_config(firmware_binary: bytearray, board_config_binary: bytearray, + user_config_binary: bytearray, replace_extra: bool = False) -> bytearray: + """Given firmware and board and/or user config binaries, combine to one binary with proper offsets for GP2040-CE. Args: 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 Returns: the resulting correctly-offset binary suitable for a GP2040-CE board """ - return (pad_binary_up_to_user_config(firmware_binary, or_truncate=replace_extra) + - pad_config_to_storage_size(config_binary)) + if not board_config_binary and not user_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, @@ -62,13 +72,13 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, binary_user_c new_binary = None if binary_user_config_filename: 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) elif json_user_config_filename: with open(firmware_filename, 'rb') as firmware, open(json_user_config_filename, 'r') as json_file: config = get_config_from_json(json_file.read()) 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) 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: # 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: new_binary = bytearray(copy.copy(board_binary)) new_config = pad_config_to_storage_size(config_binary) diff --git a/gp2040ce_bintools/storage.py b/gp2040ce_bintools/storage.py index dc5887d..78fde59 100644 --- a/gp2040ce_bintools/storage.py +++ b/gp2040ce_bintools/storage.py @@ -194,6 +194,19 @@ def get_storage_section(content: bytes, address: int) -> bytes: 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: """Get the user storage area from what should be a whole board GP2040-CE dump. diff --git a/tests/test_builder.py b/tests/test_builder.py index 8cf33db..97434cd 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -15,8 +15,8 @@ from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and concatenate_firmware_and_storage_files, get_gp2040ce_from_usb, 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) -from gp2040ce_bintools.storage import (get_config, get_config_footer, get_user_storage_section, - serialize_config_with_footer) +from gp2040ce_bintools.storage import (get_board_storage_section, get_config, get_config_footer, + get_user_storage_section, serialize_config_with_footer) 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) -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.""" - 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 storage = get_user_storage_section(whole_board) footer_size, _, _ = get_config_footer(storage) assert footer_size == 3309 -def test_firmware_plus_config_binary(firmware_binary, config_binary): - """Test that combining firmware and storage produces a valid combined binary.""" - whole_board = combine_firmware_and_config(firmware_binary, config_binary) +def test_firmware_plus_user_config_binary(firmware_binary, config_binary): + """Test that combining firmware and user config produces a valid combined 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 storage = get_user_storage_section(whole_board) footer_size, _, _ = get_config_footer(storage) 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.""" - 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 storage = get_user_storage_section(whole_board) footer_size, _, _ = get_config_footer(storage) 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): """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)