diff --git a/ncs/build.py b/ncs/build.py index 0d45607c..9fee9239 100755 --- a/ncs/build.py +++ b/ncs/build.py @@ -59,7 +59,6 @@ def get_absolute_address(node): parent_parser.add_argument( "--core", action="append", required=True, help="Configuration of sample name:location of binaries:location of edt" ) -parent_parser.add_argument("--output-envelope", required=True, help="Location of output envelope.") parent_parser.add_argument("--zephyr-base", required=True, help="Location of zephyr directory.") parser = ArgumentParser(add_help=False) @@ -76,6 +75,9 @@ def get_absolute_address(node): cmd_template_arg_parser.add_argument("--template-suit", required=True, help="Input SUIT jinja2 template.") cmd_template_arg_parser.add_argument("--output-suit", required=True, help="Output SUIT configuration.") +cmd_storage_arg_parser.add_argument( + "--input-envelope", required=True, action="append", help="Location of input envelope(s)." +) cmd_storage_arg_parser.add_argument("--storage-output-file", required=True, help="Input binary SUIT envelope.") arguments = parser.parse_args() @@ -83,10 +85,10 @@ def get_absolute_address(node): sys.path.insert(0, os.path.join(arguments.zephyr_base, "scripts", "dts", "python-devicetree", "src")) configuration = read_configurations(arguments.core) -configuration["output_envelope"] = arguments.output_envelope if arguments.command == TEMPLATE_CMD: configuration["version"] = arguments.version + configuration["output_envelope"] = arguments.output_suit output_suit_content = render_template(arguments.template_suit, configuration) with open(arguments.output_suit, "w") as output_file: output_file.write(output_suit_content) @@ -94,9 +96,10 @@ def get_absolute_address(node): elif arguments.command == STORAGE_CMD: # fixme: envelope_address, update_candidate_info_address and dfu_max_caches shall be extracted from DTS ImageCreator.create_files_for_boot( - input_file=arguments.output_envelope, + input_files=arguments.input_envelope, storage_output_file=arguments.storage_output_file, envelope_address=ImageCreator.default_envelope_address, + envelope_slot_size=ImageCreator.default_envelope_slot_size, update_candidate_info_address=ImageCreator.default_update_candidate_info_address, dfu_max_caches=ImageCreator.default_dfu_max_caches, ) diff --git a/suit_generator/cmd_image.py b/suit_generator/cmd_image.py index 8602e828..e77bd9ac 100644 --- a/suit_generator/cmd_image.py +++ b/suit_generator/cmd_image.py @@ -5,6 +5,8 @@ # """Use SUIT envelope to generate hex files allowing boot/update execution path.""" +from __future__ import annotations + import os import struct @@ -34,7 +36,7 @@ def add_arguments(parser): ImageCreator.IMAGE_CMD_UPDATE, help="Generate .hex files for update execution path" ) - cmd_image_boot.add_argument("--input-file", required=True, help="Input SUIT file; an envelope") + cmd_image_boot.add_argument("--input-file", required=True, action="append", help="Input SUIT file; an envelope") cmd_image_boot.add_argument( "--storage-output-file", required=True, help="Output hex file with SUIT storage contents" ) @@ -53,6 +55,13 @@ def add_arguments(parser): default=ImageCreator.default_envelope_address, help=f"Address of installed envelope in SUIT storage. Default: 0x{ImageCreator.default_envelope_address:08X}", ) + cmd_image_boot.add_argument( + "--envelope-slot-size", + required=False, + type=lambda x: int(x, 0), + default=ImageCreator.default_envelope_slot_size, + help=f"Envelope slot size in SUIT storage. Default: 0x{ImageCreator.default_envelope_slot_size:08X}", + ) cmd_image_boot.add_argument( "--dfu-max-caches", required=False, @@ -103,6 +112,7 @@ class ImageCreator: default_update_candidate_info_address = 0x0E1EEC00 default_envelope_address = 0x0E1EED80 + default_envelope_slot_size = 2048 default_dfu_partition_address = 0x0E100000 default_dfu_max_caches = 4 @@ -181,9 +191,10 @@ def _prepare_envelope_slot_binary(envelope: SuitEnvelope) -> bytes: @staticmethod def _create_suit_storage_file_for_boot( - envelope: SuitEnvelope, + envelopes: list[SuitEnvelope], update_candidate_info_address: int, installed_envelope_address: int, + envelope_slot_size: int, file_name: str, dfu_max_caches: int, ) -> None: @@ -193,13 +204,25 @@ def _create_suit_storage_file_for_boot( ImageCreator._prepare_update_candidate_info_for_boot(dfu_max_caches), update_candidate_info_address ) - # Installed envelope - envelope_hex = IntelHex() - envelope_hex.frombytes(ImageCreator._prepare_envelope_slot_binary(envelope), installed_envelope_address) - # The suit storage file for boot path combines update candidate info and installed envelope combined_hex = IntelHex(uci_hex) - combined_hex.merge(envelope_hex) + + # Installed envelopes + envelope_address = installed_envelope_address + + for envelope in envelopes: + envelope_bytes = ImageCreator._prepare_envelope_slot_binary(envelope) + if len(envelope_bytes) > envelope_slot_size: + raise GeneratorError( + f"Input envelope ({envelope}) exceeds slot size ({len(envelope_bytes)} > {envelope_slot_size})." + ) + + envelope_hex = IntelHex() + envelope_hex.frombytes(envelope_bytes, envelope_address) + + combined_hex.merge(envelope_hex) + envelope_address += envelope_slot_size + combined_hex.write_hex_file(file_name) @staticmethod @@ -227,10 +250,11 @@ def _create_dfu_partition_hex_file(input_file: str, dfu_partition_output_file: s @staticmethod def create_files_for_boot( - input_file: str, + input_files: list[str], storage_output_file: str, update_candidate_info_address: int, envelope_address: int, + envelope_slot_size: int, dfu_max_caches: int, ) -> None: """Create storage and payload hex files allowing boot execution path. @@ -241,13 +265,21 @@ def create_files_for_boot( :param envelope_address: address of installed envelope in SUIT storage """ try: - envelope = SuitEnvelope() - envelope.load(input_file, "suit") + envelopes = [] + for input_file in input_files: + envelope = SuitEnvelope() + envelope.load(input_file, "suit") - envelope.sever() + envelope.sever() + envelopes.append(envelope) ImageCreator._create_suit_storage_file_for_boot( - envelope, update_candidate_info_address, envelope_address, storage_output_file, dfu_max_caches + envelopes, + update_candidate_info_address, + envelope_address, + envelope_slot_size, + storage_output_file, + dfu_max_caches, ) except FileNotFoundError as error: raise GeneratorError(error) @@ -302,6 +334,7 @@ def main(**kwargs) -> None: kwargs["storage_output_file"], kwargs["update_candidate_info_address"], kwargs["envelope_address"], + kwargs["envelope_slot_size"], kwargs["dfu_max_caches"], ) elif kwargs["image"] == ImageCreator.IMAGE_CMD_UPDATE: diff --git a/suit_generator/envelope.py b/suit_generator/envelope.py index 3cbbe266..a9357def 100644 --- a/suit_generator/envelope.py +++ b/suit_generator/envelope.py @@ -61,7 +61,13 @@ def load(self, file_name: str, input_type: str = "AUTO") -> None: def sever(self) -> None: """Get rid of severable elements.""" - severable = ["suit-payload-fetch", "suit-install", "suit-text", "suit-integrated-payloads"] + severable = [ + "suit-payload-fetch", + "suit-install", + "suit-text", + "suit-integrated-payloads", + "suit-integrated-dependencies", + ] [ self._envelope["SUIT_Envelope_Tagged"].pop(k, None) for k in list(self._envelope["SUIT_Envelope_Tagged"].keys()) diff --git a/tests/test_cmd_image.py b/tests/test_cmd_image.py index 25d95bab..6294c4a9 100644 --- a/tests/test_cmd_image.py +++ b/tests/test_cmd_image.py @@ -211,10 +211,11 @@ def test_boot_subcommand_nonexisting_input_file(): with pytest.raises(GeneratorError): cmd_image_main( image="boot", - input_file="nonexisting", + input_file=["nonexisting"], storage_output_file="", update_candidate_info_address=0, envelope_address=0, + envelope_slot_size=2048, dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=0, @@ -228,10 +229,11 @@ def test_boot_subcommand_manifest_without_component_id(mocker): with pytest.raises(GeneratorError): cmd_image_main( image="boot", - input_file="some_input", + input_file=["some_input"], storage_output_file="some_output.hex", update_candidate_info_address=0x0E1EEC00, envelope_address=0x0E1EED80, + envelope_slot_size=2048, dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=4, @@ -244,10 +246,11 @@ def test_boot_subcommand_success(mocker): cmd_image_main( image="boot", - input_file="some_input", + input_file=["some_input"], storage_output_file="some_output.hex", update_candidate_info_address=0x0E1EEC00, envelope_address=0x0E1EED80, + envelope_slot_size=2048, dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=4, @@ -304,10 +307,11 @@ def test_malformed_envelope(mocker): with pytest.raises(SUITError): cmd_image_main( image="boot", - input_file="some_input", + input_file=["some_input"], storage_output_file="some_output.hex", update_candidate_info_address=0x0E1FE000, envelope_address=0x0E1FF000, + envelope_slot_size=2048, dfu_partition_output_file="", dfu_partition_address=0, dfu_max_caches=0,