feat: import Transfer List Compiler tool source
Adds the Transfer List Compiler (tlc) as a standalone Python CLI tool
under the LibTL project. This includes CLI interface, YAML parsing,
test coverage, pyproject config, and coverage tooling for generating
and manipulating TL blobs on host machines.
Change-Id: I9896f771ecc413193b9b65ed86c9be837defb598
Signed-off-by: Harrison Mutai <harrison.mutai@arm.com>
diff --git a/tlc/tests/test_transfer_list.py b/tlc/tests/test_transfer_list.py
new file mode 100644
index 0000000..6900b41
--- /dev/null
+++ b/tlc/tests/test_transfer_list.py
@@ -0,0 +1,280 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2024, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+"""Contains unit tests for the types TransferEntry and TransferList."""
+
+import math
+from random import randint
+
+import pytest
+
+from tlc.te import TransferEntry
+from tlc.tl import TransferList
+
+large_data = 0xDEADBEEF.to_bytes(4, "big")
+small_data = 0x1234.to_bytes(3, "big")
+test_entries = [
+ (0, b""),
+ (1, small_data),
+ (1, large_data),
+ (0xFFFFFF, small_data),
+ (0xFFFFFF, large_data),
+]
+
+
+@pytest.mark.parametrize(
+ "size,csum",
+ [
+ (-1, None),
+ (0x18, 0x9E),
+ (0x1000, 0xA6),
+ (0x2000, 0x96),
+ (0x4000, 0x76),
+ ],
+)
+def test_make_transfer_list(size, csum):
+ if size < 8:
+ with pytest.raises(AssertionError):
+ tl = TransferList(size)
+ else:
+ tl = TransferList(size)
+
+ assert tl.signature == 0x4A0FB10B
+ assert not tl.entries
+ assert tl.sum_of_bytes() == 0
+ assert tl.checksum == csum
+
+
+def test_add_transfer_entry(random_entries):
+ tl = TransferList(0x1000)
+
+ # Add a single entry and check it's in the list of entries
+ te = tl.add_transfer_entry(1, bytes(100))
+ assert te in tl.entries
+ assert tl.size % 8 == 0
+
+ # Add a range of tag id's
+ for id, data in random_entries(50, 1):
+ te = tl.add_transfer_entry(id, data)
+ assert te in tl.entries
+ assert tl.size % 8 == 0
+
+
+@pytest.mark.parametrize("align", [4, 6, 12, 13])
+def test_add_transfer_entry_with_align(align, random_entries, random_entry):
+ tl = TransferList(0xF00000)
+ id, data = random_entry(4)
+
+ tl.add_transfer_entry(id, data)
+
+ # Add an entry with a larger alignment requirement
+ _, data = random_entry(4)
+ te = tl.add_transfer_entry(1, data, data_align=align)
+ assert (te.offset + te.hdr_size) % (1 << align) == 0
+ assert tl.alignment == align
+
+ # Add some more entries and ensure the alignment is preserved
+ for id, data in random_entries(5, 0x200):
+ te = tl.add_transfer_entry(id, data, data_align=align)
+ assert (te.offset + te.hdr_size) % (1 << align) == 0
+ assert tl.alignment == align
+
+
+@pytest.mark.parametrize(
+ ("tag_id", "data"),
+ [
+ (-1, None), # tag out of range
+ (1, None), # no data provided
+ (1, bytes(8000)), # very large data > total size
+ (0x100000, b"0dd0edfe"), # tag out of range
+ ],
+)
+def test_add_out_of_range_transfer_entry(tag_id, data):
+ tl = TransferList()
+
+ with pytest.raises(Exception):
+ tl.add_transfer_entry(tag_id, data)
+
+
+@pytest.mark.parametrize(("tag_id", "data"), test_entries)
+def test_calculate_te_sum_of_bytes(tag_id, data):
+ te = TransferEntry(tag_id, len(data), data)
+ csum = (
+ sum(data)
+ + sum(len(data).to_bytes(4, "big"))
+ + te.hdr_size
+ + sum(tag_id.to_bytes(4, "big"))
+ ) % 256
+ assert te.sum_of_bytes == csum
+
+
+def test_calc_tl_checksum(tmpdir, random_entries):
+ tl_file = tmpdir.join("tl.bin")
+
+ tl = TransferList(0x1000)
+
+ for id, data in random_entries(10):
+ tl.add_transfer_entry(id, data)
+
+ assert sum(tl.to_bytes()) % 256 == 0
+
+ # Write the transfer list to a file and check that the sum of bytes is 0
+ tl.write_to_file(tl_file)
+ assert sum(tl_file.read_binary()) % 256 == 0
+
+
+def test_empty_transfer_list_blob(tmpdir):
+ """Check that we can correctly create a transfer list header."""
+ test_file = tmpdir.join("test_tl_blob.bin")
+ tl = TransferList()
+ tl.write_to_file(test_file)
+
+ with open(test_file, "rb") as f:
+ assert f.read(tl.hdr_size) == tl.header_to_bytes()
+
+
+@pytest.mark.parametrize(("tag_id", "data"), test_entries)
+def test_single_te_transfer_list(tag_id, data, tmpdir):
+ """Check that we can create a complete TL with a single TE."""
+ test_file = tmpdir.join("test_tl_blob.bin")
+ tl = TransferList(0x1000)
+
+ tl.add_transfer_entry(tag_id, data)
+ tl.write_to_file(test_file)
+
+ te = tl.entries[0]
+
+ with open(test_file, "rb") as f:
+ assert f.read(tl.hdr_size) == tl.header_to_bytes()
+ assert int.from_bytes(f.read(3), "little") == te.id
+ assert int.from_bytes(f.read(1), "little") == te.hdr_size
+ assert int.from_bytes(f.read(4), "little") == te.data_size
+ assert f.read(te.data_size) == te.data
+
+
+def test_write_multiple_tes_to_file(tmpdir, random_entries, random_entry):
+ """Check that we can create a TL with multiple TE's."""
+ test_file = tmpdir.join("test_tl_blob.bin")
+ tl = TransferList(0x4000)
+ _test_entries = list(random_entries())
+
+ for tag_id, data in _test_entries:
+ tl.add_transfer_entry(tag_id, data)
+
+ # Add a few entries with special alignment requirements
+ blob_id, blob = random_entry(0x200)
+ tl.add_transfer_entry(blob_id, blob, data_align=12)
+
+ tl.write_to_file(test_file)
+
+ with open(test_file, "rb") as f:
+ assert f.read(tl.hdr_size) == tl.header_to_bytes()
+ # Ensure that TE's have the correct alignment
+ for tag_id, data in _test_entries:
+ f.seek(int(math.ceil(f.tell() / 8) * 8))
+
+ assert int.from_bytes(f.read(3), "little") == tag_id
+ assert int.from_bytes(f.read(1), "little") == TransferEntry.hdr_size
+ # Make sure the data in the TE matches the data in the original case
+ data_size = int.from_bytes(f.read(4), "little")
+ assert f.read(data_size) == data
+
+ f.seek(int(math.ceil(f.tell() / (1 << 12)) * (1 << 12)) - 8)
+ assert int.from_bytes(f.read(3), "little") == blob_id
+ assert int.from_bytes(f.read(1), "little") == TransferEntry.hdr_size
+ # Make sure the data in the TE matches the data in the original case
+ data_size = int.from_bytes(f.read(4), "little")
+ assert f.read(data_size) == blob
+
+ # padding is added to align TE's, make sure padding is added to the size of
+ # the TL by checking we don't overflow.
+ assert f.tell() <= tl.size
+
+
+def test_read_empty_transfer_list_from_file(tmpdir):
+ test_file = tmpdir.join("test_tl_blob.bin")
+ original_tl = TransferList(0x1000)
+ original_tl.write_to_file(test_file)
+
+ # Read the contents of the file we just wrote
+ tl = TransferList.fromfile(test_file)
+ assert tl.header_to_bytes() == original_tl.header_to_bytes()
+ assert tl.sum_of_bytes() == 0
+
+
+def test_read_single_transfer_list_from_file(tmpdir):
+ test_file = tmpdir.join("test_tl_blob.bin")
+ original_tl = TransferList(0x1000)
+
+ original_tl.add_transfer_entry(test_entries[0][0], test_entries[0][1])
+ original_tl.write_to_file(test_file)
+
+ # Read the contents of the file we just wrote
+ tl = TransferList.fromfile(test_file)
+ assert tl.entries
+
+ te = tl.entries[0]
+ assert te.id == test_entries[0][0]
+ assert te.data == test_entries[0][1]
+ assert tl.sum_of_bytes() == 0
+
+
+def test_read_multiple_transfer_list_from_file(tmpdir):
+ test_file = tmpdir.join("test_tl_blob.bin")
+ original_tl = TransferList(0x1000)
+
+ for tag_id, data in test_entries:
+ original_tl.add_transfer_entry(tag_id, data)
+
+ original_tl.write_to_file(test_file)
+
+ # Read the contents of the file we just wrote
+ tl = TransferList.fromfile(test_file)
+
+ # The TE we derive from the file might have a an associated offset, compare
+ # the TE's based on the header in bytes, which doesn't account for this.
+ for te0, te1 in zip(tl.entries, original_tl.entries):
+ assert te0.header_to_bytes() == te1.header_to_bytes()
+
+ assert tl.sum_of_bytes() == 0
+
+
+def test_remove_tag(random_entry):
+ """Adds a transfer entry and remove it, size == transfer list header."""
+ tl = TransferList(0x100)
+ id, data = random_entry(tl.total_size // 2)
+
+ te = tl.add_transfer_entry(id, data)
+ assert te in tl.entries
+
+ tl.remove_tag(id)
+ assert not tl.get_entry(id) and te not in tl.entries
+ assert tl.size == tl.hdr_size
+
+
+def test_get_fdt_offset(tmpdir):
+ tl = TransferList(0x1000)
+ tl.add_transfer_entry(1, 0xEDFE0DD0.to_bytes(4, "big"))
+ f = tmpdir.join("blob.bin")
+
+ tl.write_to_file(f)
+
+ blob_tl = TransferList.fromfile(f)
+
+ assert blob_tl.hdr_size + TransferEntry.hdr_size == blob_tl.get_entry_data_offset(1)
+
+
+def test_get_missing_fdt_offset(tmpdir):
+ tl = TransferList(0x1000)
+ f = tmpdir.join("blob.bin")
+
+ tl.write_to_file(f)
+ blob_tl = TransferList.fromfile(f)
+
+ with pytest.raises(ValueError):
+ blob_tl.get_entry_data_offset(1)