diff --git a/docs/tools/transfer-list-compiler.rst b/docs/tools/transfer-list-compiler.rst index c8ef7ac02..e9710d745 100644 --- a/docs/tools/transfer-list-compiler.rst +++ b/docs/tools/transfer-list-compiler.rst @@ -59,6 +59,12 @@ through the ``--entry`` option. provided tag ID. It only checks that the tags provided as input are within range and that there is sufficient memory to include their TE's. +You can also create a TL from a YAML config file. + +.. code :: + + tlc create --from-yaml config.yaml tl.bin + Printing the contents of a TL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -186,6 +192,81 @@ performs the following checks: #. Ensures that the specified version is greater than or equal to the tool’s current version. #. Verifies alignment criteria for all TE’s. +YAML Config File Format +~~~~~~~~~~~~~~~~~~~~~~~ + +Example YAML config file: + +.. code:: + + execution_state: aarch32 + has_checksum: true + max_size: 4096 + entries: + - tag_id: 258 # entry point info + ep_info: + args: + - 67112968 + - 67112960 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + h: + attr: 8 + type: 1 + version: 2 + pc: 67239936 + spsr: 467 + - tag_id: 3 # memory layout + addr: 8 + size: 8 + - tag_id: 1, # fdt + blob_file_path: "fdt.bin", + +`max_size` defaults to `0x1000`, `execution_state` defaults to `aarch64`, and `has_checksum` +defaults to `true`. + +The fields of the YAML file should match the fields in the specification for the transfer list. You +don't need to give the hdr_size or data_size fields. For example, a memory layout entry would have +an entry like: + +.. code:: + + tag_id: 3 + addr: 8 + size: 8 + +You can input blob files by giving paths to the current working directory. You can do this for any +TE type. For example, an FDT layout would have an entry like: + +.. code:: + + tag_id: 1, + blob_file_path: "fdt.bin", + +You can input C-types by giving its fields. For example, an entry point +info entry would have an entry like: + +.. code:: + + tag_id: 258 + ep_info: + args: + - 67112968 + - 67112960 + - 0 + - 0 + h: + attr: 8 + type: 1 + version: 2 + lr_svc: 0 + pc: 67239936 + spsr: 467 + -------------- *Copyright (c) 2024, Arm Limited. All rights reserved.* diff --git a/poetry.lock b/poetry.lock index 31590deff..169bd4056 100644 --- a/poetry.lock +++ b/poetry.lock @@ -251,13 +251,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "8.1.0" +version = "8.2.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.1.0-py3-none-any.whl", hash = "sha256:3cd29f739ed65973840b068e3132135ce954c254d48b5b640484467ef7ab3c8c"}, - {file = "importlib_metadata-8.1.0.tar.gz", hash = "sha256:fcdcb1d5ead7bdf3dd32657bb94ebe9d2aabfe89a19782ddc32da5041d6ebfb4"}, + {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, + {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, ] [package.dependencies] @@ -447,13 +447,13 @@ files = [ [[package]] name = "pip" -version = "24.1.2" +version = "24.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.1.2-py3-none-any.whl", hash = "sha256:7cd207eed4c60b0f411b444cd1464198fe186671c323b6cd6d433ed80fc9d247"}, - {file = "pip-24.1.2.tar.gz", hash = "sha256:e5458a0b89f2755e0ee8c0c77613fe5273e05f337907874d64f13171a898a7ff"}, + {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, + {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, ] [[package]] @@ -645,13 +645,13 @@ jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] [[package]] name = "setuptools" -version = "71.1.0" +version = "72.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, - {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, ] [package.extras] @@ -893,6 +893,7 @@ develop = false [package.dependencies] click = "^8.1.7" +pyyaml = "^6.0.1" rich = "^10.14.0" typer = {version = "^0.4.0", extras = ["all"]} diff --git a/tools/tlc/assets/images/coverage.svg b/tools/tlc/assets/images/coverage.svg index 34387324e..b6c4e361f 100644 --- a/tools/tlc/assets/images/coverage.svg +++ b/tools/tlc/assets/images/coverage.svg @@ -15,7 +15,7 @@ coverage coverage - 97% - 97% + 95% + 95% diff --git a/tools/tlc/poetry.lock b/tools/tlc/poetry.lock index 5a39322c5..839f2364c 100644 --- a/tools/tlc/poetry.lock +++ b/tools/tlc/poetry.lock @@ -386,13 +386,13 @@ pipenv = ["pipenv (<=2022.12.19)"] [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -1107,18 +1107,19 @@ gitlab = ["python-gitlab (>=1.3.0)"] [[package]] name = "setuptools" -version = "70.2.0" +version = "72.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"}, - {file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"}, + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, ] [package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shellingham" @@ -1191,13 +1192,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.5" +version = "0.13.0" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, - {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, ] [[package]] @@ -1352,4 +1353,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "60bdb4a8b67815f01b4e7089d9f7664afcb9041fa8adf5aa92d977f4e2d5b4b2" +content-hash = "cfcb196cda412f6139302937640455aa8154d7979c69017fe45ddd528e4a1bf2" diff --git a/tools/tlc/pyproject.toml b/tools/tlc/pyproject.toml index 30875a522..5661abfd5 100644 --- a/tools/tlc/pyproject.toml +++ b/tools/tlc/pyproject.toml @@ -37,6 +37,7 @@ python = "^3.8" typer = {extras = ["all"], version = "^0.4.0"} rich = "^10.14.0" click = "^8.1.7" +pyyaml = "^6.0.1" [tool.poetry.dev-dependencies] bandit = "^1.7.1" diff --git a/tools/tlc/tests/conftest.py b/tools/tlc/tests/conftest.py index 6b28e432b..b8f88b50c 100644 --- a/tools/tlc/tests/conftest.py +++ b/tools/tlc/tests/conftest.py @@ -10,6 +10,7 @@ """ Common configurations and fixtures for test environment.""" import pytest +import yaml from click.testing import CliRunner from tlc.cli import cli @@ -20,6 +21,11 @@ def tmptlstr(tmpdir): return tmpdir.join("tl.bin").strpath +@pytest.fixture +def tmpyamlconfig(tmpdir): + return tmpdir.join("config.yaml").strpath + + @pytest.fixture def tmpfdt(tmpdir): fdt = tmpdir.join("fdt.dtb") @@ -27,6 +33,32 @@ def tmpfdt(tmpdir): return fdt +@pytest.fixture(params=[1, 2, 3, 4, 5, 0x100, 0x101, 0x102, 0x104]) +def non_empty_tag_id(request): + return request.param + + +@pytest.fixture +def tmpyamlconfig_blob_file(tmpdir, tmpfdt, non_empty_tag_id): + config_path = tmpdir.join("config.yaml") + + config = { + "has_checksum": True, + "max_size": 0x1000, + "entries": [ + { + "tag_id": non_empty_tag_id, + "blob_file_path": tmpfdt.strpath, + }, + ], + } + + with open(config_path, "w") as f: + yaml.safe_dump(config, f) + + return config_path + + @pytest.fixture def tlcrunner(tmptlstr): runner = CliRunner() diff --git a/tools/tlc/tests/test_cli.py b/tools/tlc/tests/test_cli.py index d79773b07..5c1035cd8 100644 --- a/tools/tlc/tests/test_cli.py +++ b/tools/tlc/tests/test_cli.py @@ -11,8 +11,11 @@ from pathlib import Path from unittest import mock +from math import log2, ceil import pytest +import pytest +import yaml from click.testing import CliRunner from tlc.cli import cli @@ -203,3 +206,180 @@ def test_validate_unsupported_version(version, tmptlstr, tlcrunner, monkeypatch) assert result.exit_code == 0 else: assert result.exit_code == 1 + + +def test_create_entry_from_yaml_and_blob_file( + tlcrunner, tmpyamlconfig_blob_file, tmptlstr, non_empty_tag_id +): + tlcrunner.invoke( + cli, + [ + "create", + "--from-yaml", + tmpyamlconfig_blob_file.strpath, + tmptlstr, + ], + ) + + tl = TransferList.fromfile(tmptlstr) + assert tl is not None + assert len(tl.entries) == 1 + assert tl.entries[0].id == non_empty_tag_id + + +@pytest.mark.parametrize( + "entry", + [ + {"tag_id": 0}, + { + "tag_id": 0x104, + "addr": 0x0400100000000010, + "size": 0x0003300000000000, + }, + { + "tag_id": 0x100, + "pp_addr": 100, + }, + ], +) +def test_create_from_yaml_check_sum_bytes(tlcrunner, tmpyamlconfig, tmptlstr, entry): + """Test creating a TL from a yaml file, but only check that the sum of the + data in the yaml file matches the sum of the data in the TL. This means + you don't have to type the exact sequence of expected bytes. All the data + in the yaml file must be integers. + """ + # create yaml config file + config = { + "has_checksum": True, + "max_size": 0x1000, + "entries": [entry], + } + with open(tmpyamlconfig, "w") as f: + yaml.safe_dump(config, f) + + # invoke TLC + tlcrunner.invoke( + cli, + [ + "create", + "--from-yaml", + tmpyamlconfig, + tmptlstr, + ], + ) + + # open created TL, and check + tl = TransferList.fromfile(tmptlstr) + assert tl is not None + assert len(tl.entries) == 1 + + # Check that the sum of all the data in the transfer entry in the yaml file + # is the same as the sum of all the data in the transfer list. Don't count + # the tag id or the TE headers. + + # every item in the entry dict must be an integer + yaml_total = 0 + for key, data in iter_nested_dict(entry): + if key != "tag_id": + num_bytes = ceil(log2(data + 1) / 8) + yaml_total += sum(data.to_bytes(num_bytes, "little")) + + tl_total = sum(tl.entries[0].data) + + assert tl_total == yaml_total + + +@pytest.mark.parametrize( + "entry,expected", + [ + ( + { + "tag_id": 0x102, + "ep_info": { + "h": { + "type": 0x01, + "version": 0x02, + "attr": 8, + }, + "pc": 67239936, + "spsr": 965, + "args": [67112976, 67112960, 0, 0, 0, 0, 0, 0], + }, + }, + ( + "0x00580201 0x00000008 0x04020000 0x00000000 " + "0x000003C5 0x00000000 0x04001010 0x00000000 " + "0x04001000 0x00000000 0x00000000 0x00000000 " + "0x00000000 0x00000000 0x00000000 0x00000000 " + "0x00000000 0x00000000 0x00000000 0x00000000 " + "0x00000000 0x00000000" + ), + ), + ], +) +def test_create_from_yaml_check_exact_data( + tlcrunner, tmpyamlconfig, tmptlstr, entry, expected +): + """Test creating a TL from a yaml file, checking the exact sequence of + bytes. This is useful for checking that the alignment is correct. You can + get the expected sequence of bytes by copying it from the ArmDS debugger. + """ + # create yaml config file + config = { + "has_checksum": True, + "max_size": 0x1000, + "entries": [entry], + } + with open(tmpyamlconfig, "w") as f: + yaml.safe_dump(config, f) + + # invoke TLC + tlcrunner.invoke( + cli, + [ + "create", + "--from-yaml", + tmpyamlconfig, + tmptlstr, + ], + ) + + # open TL and check + tl = TransferList.fromfile(tmptlstr) + assert tl is not None + assert len(tl.entries) == 1 + + # check expected and actual data + actual = tl.entries[0].data + actual = bytes_to_hex(actual) + + assert actual == expected + + +def bytes_to_hex(data: bytes) -> str: + """Convert bytes to a hex string in the same format as the debugger in + ArmDS + + You can copy data from the debugger in Arm Development Studio and put it + into a unit test. You can then run this function on the output from tlc, + and compare it to the data you copied. + + The format is groups of 4 bytes with 0x prefixes separated by spaces. + Little endian is used. + """ + words_hex = [] + for i in range(0, len(data), 4): + word = data[i : i + 4] + word_int = int.from_bytes(word, "little") + word_hex = "0x" + f"{word_int:0>8x}".upper() + words_hex.append(word_hex) + + return " ".join(words_hex) + + +def iter_nested_dict(dictionary: dict): + for key, value in dictionary.items(): + if isinstance(value, dict): + yield from iter_nested_dict(value) + else: + yield key, value diff --git a/tools/tlc/tlc/cli.py b/tools/tlc/tlc/cli.py index e574e59da..1d4949d67 100644 --- a/tools/tlc/tlc/cli.py +++ b/tools/tlc/tlc/cli.py @@ -12,6 +12,7 @@ from pathlib import Path import click +import yaml from tlc.tl import * @@ -44,15 +45,26 @@ def cli(): show_default=True, help="Settings for the TL's properties.", ) -def create(filename, size, fdt, entry, flags): +@click.option( + "--from-yaml", + type=click.Path(exists=True), + help="Create the transfer list from a YAML config file.", +) +def create(filename, size, fdt, entry, flags, from_yaml): """Create a new Transfer List.""" - tl = TransferList(size) + try: + if from_yaml: + with open(from_yaml, "r") as f: + config = yaml.safe_load(f) - entry = (*entry, (1, fdt)) if fdt else entry + tl = TransferList.from_dict(config) + else: + tl = TransferList(size) - try: - for id, path in entry: - tl.add_transfer_entry_from_file(id, path) + entry = (*entry, (1, fdt)) if fdt else entry + + for id, path in entry: + tl.add_transfer_entry_from_file(id, path) except MemoryError as mem_excp: raise MemoryError( "TL max size exceeded, consider increasing with the option -s" diff --git a/tools/tlc/tlc/tl.py b/tools/tlc/tlc/tl.py index a409651f1..dae7e14f6 100644 --- a/tools/tlc/tlc/tl.py +++ b/tools/tlc/tlc/tl.py @@ -19,6 +19,60 @@ from tlc.te import TransferEntry TRANSFER_LIST_ENABLE_CHECKSUM = 0b1 +# Description of each TE type. For each TE, there is a tag ID, a format (to be +# used in struct.pack to encode the TE), and a list of field names that can +# appear in the yaml file for that TE. Some fields are missing, if that TE has +# to be processed differently, or if it can only be added with a blob file. +transfer_entry_formats = { + 0: { + "tag_name": "empty", + "format": "4x", + "fields": [], + }, + 1: { + "tag_name": "fdt", + }, + 2: { + "tag_name": "hob_block", + }, + 3: { + "tag_name": "hob_list", + }, + 4: { + "tag_name": "acpi_table_aggregate", + }, + 5: { + "tag_name": "tpm_event_log_table", + "fields": ["event_log", "flags"], + }, + 6: { + "tag_name": "tpm_crb_base_address_table", + "format": "QI", + "fields": ["crb_base_address", "crb_size"], + }, + 0x100: { + "tag_name": "optee_pageable_part", + "format": "Q", + "fields": ["pp_addr"], + }, + 0x101: { + "tag_name": "dt_spmc_manifest", + }, + 0x102: { + "tag_name": "exec_ep_info", + "format": "2BHIQI4x8Q", + "fields": ["ep_info"], + }, + 0x104: { + "tag_name": "sram_layout", + "format": "2Q", + "fields": ["addr", "size"], + }, +} +tag_name_to_tag_id = { + te["tag_name"]: tag_id for tag_id, te in transfer_entry_formats.items() +} + class TransferList: """Class representing a Transfer List based on version 1.0 of the Firmware Handoff specification.""" @@ -96,6 +150,28 @@ class TransferList: return tl + @classmethod + def from_dict(cls, config: dict): + """Create a TL from data in a dictionary + + The dictionary should have the same format as the yaml config files. + See the readme for more detail. + + :param config: Dictionary containing the data described above. + """ + # get settings from config and set defaults + max_size = config.get("max_size", 0x1000) + has_checksum = config.get("has_checksum", True) + + flags = TRANSFER_LIST_ENABLE_CHECKSUM if has_checksum else 0 + + tl = cls(max_size, flags) + + for entry in config["entries"]: + tl.add_transfer_entry_from_dict(entry) + + return tl + def header_to_bytes(self) -> bytes: return struct.pack( self.encoding, @@ -141,6 +217,72 @@ class TransferList: self.update_checksum() return te + def add_transfer_entry_from_struct_format( + self, tag_id: int, struct_format: str, *args + ): + struct_format = "<" + struct_format + data = struct.pack(struct_format, *args) + return self.add_transfer_entry(tag_id, data) + + def add_entry_point_info_transfer_entry(self, entry: dict) -> "TransferEntry": + """Add entry_point_info transfer entry + + :param entry: Dictionary of the transfer entry, in the same format as + the YAML file. + """ + ep_info = entry["ep_info"] + header = ep_info["h"] + + # size of the entry_point_info struct + entry_point_size = 88 + + return self.add_transfer_entry_from_struct_format( + 0x102, + transfer_entry_formats[0x102]["format"], + header["type"], + header["version"], + entry_point_size, + header["attr"], + ep_info["pc"], + ep_info["spsr"], + *ep_info["args"], + ) + + def add_transfer_entry_from_dict( + self, + entry: dict, + ) -> "TransferEntry": + """Add a transfer entry from data in a dictionary + + The dictionary should have the same format as the entries in the yaml + config files. See the readme for more detail. + + :param entry: Dictionary containing the data described above. + """ + tag_id = entry["tag_id"] + te_format = transfer_entry_formats[tag_id] + tag_name = te_format["tag_name"] + + if "blob_file_path" in entry: + return self.add_transfer_entry_from_file(tag_id, entry["blob_file_path"]) + elif tag_name == "tpm_event_log_table": + with open(entry["event_log"], "rb") as f: + event_log_data = f.read() + + flags_bytes = entry["flags"].to_bytes(4, "little") + data = flags_bytes + event_log_data + + return self.add_transfer_entry(tag_id, data) + elif tag_name == "exec_ep_info": + return self.add_entry_point_info_transfer_entry(entry) + elif "format" in te_format and "fields" in te_format: + fields = [entry[field] for field in te_format["fields"]] + return self.add_transfer_entry_from_struct_format( + tag_id, te_format["format"], *fields + ) + else: + raise ValueError(f"Invalid transfer entry {entry}.") + def add_transfer_entry_from_file(self, tag_id: int, path: Path) -> "TransferEntry": with open(path, "rb") as f: return self.add_transfer_entry(tag_id, f.read())