Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # SPDX-License-Identifier: GPL-2.0+ |
| 3 | # Copyright 2019 Google LLC |
| 4 | # Written by Simon Glass <sjg@chromium.org> |
| 5 | |
| 6 | """Tests for cbfs_util |
| 7 | |
| 8 | These create and read various CBFSs and compare the results with expected |
| 9 | values and with cbfstool |
| 10 | """ |
| 11 | |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 12 | import io |
| 13 | import os |
| 14 | import shutil |
| 15 | import struct |
| 16 | import tempfile |
| 17 | import unittest |
| 18 | |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 19 | from binman import bintool |
Simon Glass | c585dd4 | 2020-04-17 18:09:03 -0600 | [diff] [blame] | 20 | from binman import cbfs_util |
| 21 | from binman.cbfs_util import CbfsWriter |
| 22 | from binman import elf |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 23 | from u_boot_pylib import terminal |
Simon Glass | 131444f | 2023-02-23 18:18:04 -0700 | [diff] [blame] | 24 | from u_boot_pylib import test_util |
| 25 | from u_boot_pylib import tools |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 26 | |
| 27 | U_BOOT_DATA = b'1234' |
| 28 | U_BOOT_DTB_DATA = b'udtb' |
| 29 | COMPRESS_DATA = b'compress xxxxxxxxxxxxxxxxxxxxxx data' |
| 30 | |
| 31 | |
| 32 | class TestCbfs(unittest.TestCase): |
| 33 | """Test of cbfs_util classes""" |
| 34 | #pylint: disable=W0212 |
| 35 | @classmethod |
| 36 | def setUpClass(cls): |
| 37 | # Create a temporary directory for test files |
| 38 | cls._indir = tempfile.mkdtemp(prefix='cbfs_util.') |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 39 | tools.set_input_dirs([cls._indir]) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 40 | |
| 41 | # Set up some useful data files |
| 42 | TestCbfs._make_input_file('u-boot.bin', U_BOOT_DATA) |
| 43 | TestCbfs._make_input_file('u-boot.dtb', U_BOOT_DTB_DATA) |
| 44 | TestCbfs._make_input_file('compress', COMPRESS_DATA) |
| 45 | |
| 46 | # Set up a temporary output directory, used by the tools library when |
| 47 | # compressing files |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 48 | tools.prepare_output_dir(None) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 49 | |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 50 | cls.cbfstool = bintool.Bintool.create('cbfstool') |
| 51 | cls.have_cbfstool = cls.cbfstool.is_present() |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 52 | |
Stefan Herbrechtsmeier | 80c6277 | 2022-08-19 16:25:29 +0200 | [diff] [blame] | 53 | lz4 = bintool.Bintool.create('lz4') |
| 54 | cls.have_lz4 = lz4.is_present() |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 55 | |
| 56 | @classmethod |
| 57 | def tearDownClass(cls): |
| 58 | """Remove the temporary input directory and its contents""" |
| 59 | if cls._indir: |
| 60 | shutil.rmtree(cls._indir) |
| 61 | cls._indir = None |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 62 | tools.finalise_output_dir() |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 63 | |
| 64 | @classmethod |
| 65 | def _make_input_file(cls, fname, contents): |
| 66 | """Create a new test input file, creating directories as needed |
| 67 | |
| 68 | Args: |
| 69 | fname: Filename to create |
| 70 | contents: File contents to write in to the file |
| 71 | Returns: |
| 72 | Full pathname of file created |
| 73 | """ |
| 74 | pathname = os.path.join(cls._indir, fname) |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 75 | tools.write_file(pathname, contents) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 76 | return pathname |
| 77 | |
| 78 | def _check_hdr(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86): |
| 79 | """Check that the CBFS has the expected header |
| 80 | |
| 81 | Args: |
| 82 | data: Data to check |
| 83 | size: Expected ROM size |
| 84 | offset: Expected offset to first CBFS file |
| 85 | arch: Expected architecture |
| 86 | |
| 87 | Returns: |
| 88 | CbfsReader object containing the CBFS |
| 89 | """ |
| 90 | cbfs = cbfs_util.CbfsReader(data) |
| 91 | self.assertEqual(cbfs_util.HEADER_MAGIC, cbfs.magic) |
| 92 | self.assertEqual(cbfs_util.HEADER_VERSION2, cbfs.version) |
| 93 | self.assertEqual(size, cbfs.rom_size) |
| 94 | self.assertEqual(0, cbfs.boot_block_size) |
| 95 | self.assertEqual(cbfs_util.ENTRY_ALIGN, cbfs.align) |
| 96 | self.assertEqual(offset, cbfs.cbfs_offset) |
| 97 | self.assertEqual(arch, cbfs.arch) |
| 98 | return cbfs |
| 99 | |
Simon Glass | fa14422 | 2023-10-14 14:40:28 -0600 | [diff] [blame] | 100 | def _check_uboot(self, cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x20, |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 101 | data=U_BOOT_DATA, cbfs_offset=None): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 102 | """Check that the U-Boot file is as expected |
| 103 | |
| 104 | Args: |
| 105 | cbfs: CbfsReader object to check |
| 106 | ftype: Expected file type |
| 107 | offset: Expected offset of file |
| 108 | data: Expected data in file |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 109 | cbfs_offset: Expected CBFS offset for file's data |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 110 | |
| 111 | Returns: |
| 112 | CbfsFile object containing the file |
| 113 | """ |
| 114 | self.assertIn('u-boot', cbfs.files) |
| 115 | cfile = cbfs.files['u-boot'] |
| 116 | self.assertEqual('u-boot', cfile.name) |
| 117 | self.assertEqual(offset, cfile.offset) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 118 | if cbfs_offset is not None: |
| 119 | self.assertEqual(cbfs_offset, cfile.cbfs_offset) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 120 | self.assertEqual(data, cfile.data) |
| 121 | self.assertEqual(ftype, cfile.ftype) |
| 122 | self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress) |
| 123 | self.assertEqual(len(data), cfile.memlen) |
| 124 | return cfile |
| 125 | |
Simon Glass | fa14422 | 2023-10-14 14:40:28 -0600 | [diff] [blame] | 126 | def _check_dtb(self, cbfs, offset=0x24, data=U_BOOT_DTB_DATA, |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 127 | cbfs_offset=None): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 128 | """Check that the U-Boot dtb file is as expected |
| 129 | |
| 130 | Args: |
| 131 | cbfs: CbfsReader object to check |
| 132 | offset: Expected offset of file |
| 133 | data: Expected data in file |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 134 | cbfs_offset: Expected CBFS offset for file's data |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 135 | """ |
| 136 | self.assertIn('u-boot-dtb', cbfs.files) |
| 137 | cfile = cbfs.files['u-boot-dtb'] |
| 138 | self.assertEqual('u-boot-dtb', cfile.name) |
| 139 | self.assertEqual(offset, cfile.offset) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 140 | if cbfs_offset is not None: |
| 141 | self.assertEqual(cbfs_offset, cfile.cbfs_offset) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 142 | self.assertEqual(U_BOOT_DTB_DATA, cfile.data) |
| 143 | self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype) |
| 144 | self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress) |
| 145 | self.assertEqual(len(U_BOOT_DTB_DATA), cfile.memlen) |
| 146 | |
| 147 | def _check_raw(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86): |
| 148 | """Check that two raw files are added as expected |
| 149 | |
| 150 | Args: |
| 151 | data: Data to check |
| 152 | size: Expected ROM size |
| 153 | offset: Expected offset to first CBFS file |
| 154 | arch: Expected architecture |
| 155 | """ |
| 156 | cbfs = self._check_hdr(data, size, offset=offset, arch=arch) |
| 157 | self._check_uboot(cbfs) |
| 158 | self._check_dtb(cbfs) |
| 159 | |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 160 | def _get_expected_cbfs(self, size, arch='x86', compress=None, base=None): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 161 | """Get the file created by cbfstool for a particular scenario |
| 162 | |
| 163 | Args: |
| 164 | size: Size of the CBFS in bytes |
| 165 | arch: Architecture of the CBFS, as a string |
| 166 | compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 167 | base: Base address of file, or None to put it anywhere |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 168 | |
| 169 | Returns: |
| 170 | Resulting CBFS file, or None if cbfstool is not available |
| 171 | """ |
| 172 | if not self.have_cbfstool or not self.have_lz4: |
| 173 | return None |
| 174 | cbfs_fname = os.path.join(self._indir, 'test.cbfs') |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 175 | self.cbfstool.create_new(cbfs_fname, size, arch) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 176 | if base: |
| 177 | base = [(1 << 32) - size + b for b in base] |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 178 | self.cbfstool.add_raw( |
| 179 | cbfs_fname, 'u-boot', |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 180 | tools.get_input_filename(compress and 'compress' or 'u-boot.bin'), |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 181 | compress[0] if compress else None, |
| 182 | base[0] if base else None) |
| 183 | self.cbfstool.add_raw( |
| 184 | cbfs_fname, 'u-boot-dtb', |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 185 | tools.get_input_filename(compress and 'compress' or 'u-boot.dtb'), |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 186 | compress[1] if compress else None, |
| 187 | base[1] if base else None) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 188 | return cbfs_fname |
| 189 | |
| 190 | def _compare_expected_cbfs(self, data, cbfstool_fname): |
| 191 | """Compare against what cbfstool creates |
| 192 | |
| 193 | This compares what binman creates with what cbfstool creates for what |
| 194 | is proportedly the same thing. |
| 195 | |
| 196 | Args: |
| 197 | data: CBFS created by binman |
| 198 | cbfstool_fname: CBFS created by cbfstool |
| 199 | """ |
| 200 | if not self.have_cbfstool or not self.have_lz4: |
| 201 | return |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 202 | expect = tools.read_file(cbfstool_fname) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 203 | if expect != data: |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 204 | tools.write_file('/tmp/expect', expect) |
| 205 | tools.write_file('/tmp/actual', data) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 206 | print('diff -y <(xxd -g1 /tmp/expect) <(xxd -g1 /tmp/actual) | colordiff') |
| 207 | self.fail('cbfstool produced a different result') |
| 208 | |
| 209 | def test_cbfs_functions(self): |
| 210 | """Test global functions of cbfs_util""" |
| 211 | self.assertEqual(cbfs_util.ARCHITECTURE_X86, cbfs_util.find_arch('x86')) |
| 212 | self.assertIsNone(cbfs_util.find_arch('bad-arch')) |
| 213 | |
| 214 | self.assertEqual(cbfs_util.COMPRESS_LZMA, cbfs_util.find_compress('lzma')) |
| 215 | self.assertIsNone(cbfs_util.find_compress('bad-comp')) |
| 216 | |
| 217 | def test_cbfstool_failure(self): |
| 218 | """Test failure to run cbfstool""" |
| 219 | if not self.have_cbfstool: |
| 220 | self.skipTest('No cbfstool available') |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 221 | with self.assertRaises(ValueError) as exc: |
| 222 | out = self.cbfstool.fail() |
| 223 | self.assertIn('cbfstool missing-file bad-command', str(exc.exception)) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 224 | |
| 225 | def test_cbfs_raw(self): |
| 226 | """Test base handling of a Coreboot Filesystem (CBFS)""" |
| 227 | size = 0xb0 |
| 228 | cbw = CbfsWriter(size) |
| 229 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 230 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 231 | data = cbw.get_data() |
| 232 | self._check_raw(data, size) |
| 233 | cbfs_fname = self._get_expected_cbfs(size=size) |
| 234 | self._compare_expected_cbfs(data, cbfs_fname) |
| 235 | |
| 236 | def test_cbfs_invalid_file_type(self): |
| 237 | """Check handling of an invalid file type when outputiing a CBFS""" |
| 238 | size = 0xb0 |
| 239 | cbw = CbfsWriter(size) |
| 240 | cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 241 | |
| 242 | # Change the type manually before generating the CBFS, and make sure |
| 243 | # that the generator complains |
| 244 | cfile.ftype = 0xff |
| 245 | with self.assertRaises(ValueError) as e: |
| 246 | cbw.get_data() |
| 247 | self.assertIn('Unknown type 0xff when writing', str(e.exception)) |
| 248 | |
| 249 | def test_cbfs_invalid_file_type_on_read(self): |
| 250 | """Check handling of an invalid file type when reading the CBFS""" |
| 251 | size = 0xb0 |
| 252 | cbw = CbfsWriter(size) |
| 253 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 254 | |
| 255 | data = cbw.get_data() |
| 256 | |
| 257 | # Read in the first file header |
| 258 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 259 | with io.BytesIO(data) as fd: |
| 260 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 261 | pos = fd.tell() |
| 262 | hdr_data = fd.read(cbfs_util.FILE_HEADER_LEN) |
| 263 | magic, size, ftype, attr, offset = struct.unpack( |
| 264 | cbfs_util.FILE_HEADER_FORMAT, hdr_data) |
| 265 | |
| 266 | # Create a new CBFS with a change to the file type |
| 267 | ftype = 0xff |
| 268 | newdata = data[:pos] |
| 269 | newdata += struct.pack(cbfs_util.FILE_HEADER_FORMAT, magic, size, ftype, |
| 270 | attr, offset) |
| 271 | newdata += data[pos + cbfs_util.FILE_HEADER_LEN:] |
| 272 | |
| 273 | # Read in this CBFS and make sure that the reader complains |
| 274 | with self.assertRaises(ValueError) as e: |
| 275 | cbfs_util.CbfsReader(newdata) |
| 276 | self.assertIn('Unknown type 0xff when reading', str(e.exception)) |
| 277 | |
| 278 | def test_cbfs_no_space(self): |
| 279 | """Check handling of running out of space in the CBFS""" |
| 280 | size = 0x60 |
| 281 | cbw = CbfsWriter(size) |
| 282 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 283 | with self.assertRaises(ValueError) as e: |
| 284 | cbw.get_data() |
| 285 | self.assertIn('No space for header', str(e.exception)) |
| 286 | |
| 287 | def test_cbfs_no_space_skip(self): |
| 288 | """Check handling of running out of space in CBFS with file header""" |
Simon Glass | a61e6fe | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 289 | size = 0x5c |
| 290 | cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64) |
| 291 | cbw._add_fileheader = True |
| 292 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 293 | with self.assertRaises(ValueError) as e: |
| 294 | cbw.get_data() |
| 295 | self.assertIn('No space for data before offset', str(e.exception)) |
| 296 | |
| 297 | def test_cbfs_no_space_pad(self): |
| 298 | """Check handling of running out of space in CBFS with file header""" |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 299 | size = 0x70 |
| 300 | cbw = CbfsWriter(size) |
| 301 | cbw._add_fileheader = True |
| 302 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 303 | with self.assertRaises(ValueError) as e: |
| 304 | cbw.get_data() |
Simon Glass | a61e6fe | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 305 | self.assertIn('No space for data before pad offset', str(e.exception)) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 306 | |
| 307 | def test_cbfs_bad_header_ptr(self): |
| 308 | """Check handling of a bad master-header pointer""" |
| 309 | size = 0x70 |
| 310 | cbw = CbfsWriter(size) |
| 311 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 312 | data = cbw.get_data() |
| 313 | |
| 314 | # Add one to the pointer to make it invalid |
| 315 | newdata = data[:-4] + struct.pack('<I', cbw._header_offset + 1) |
| 316 | |
| 317 | # We should still be able to find the master header by searching |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 318 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 319 | cbfs = cbfs_util.CbfsReader(newdata) |
| 320 | self.assertIn('Relative offset seems wrong', stdout.getvalue()) |
| 321 | self.assertIn('u-boot', cbfs.files) |
| 322 | self.assertEqual(size, cbfs.rom_size) |
| 323 | |
| 324 | def test_cbfs_bad_header(self): |
| 325 | """Check handling of a bad master header""" |
| 326 | size = 0x70 |
| 327 | cbw = CbfsWriter(size) |
| 328 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 329 | data = cbw.get_data() |
| 330 | |
| 331 | # Drop most of the header and try reading the modified CBFS |
| 332 | newdata = data[:cbw._header_offset + 4] |
| 333 | |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 334 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 335 | with self.assertRaises(ValueError) as e: |
| 336 | cbfs_util.CbfsReader(newdata) |
| 337 | self.assertIn('Relative offset seems wrong', stdout.getvalue()) |
| 338 | self.assertIn('Cannot find master header', str(e.exception)) |
| 339 | |
| 340 | def test_cbfs_bad_file_header(self): |
| 341 | """Check handling of a bad file header""" |
| 342 | size = 0x70 |
| 343 | cbw = CbfsWriter(size) |
| 344 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 345 | data = cbw.get_data() |
| 346 | |
| 347 | # Read in the CBFS master header (only), then stop |
| 348 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 349 | with io.BytesIO(data) as fd: |
| 350 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 351 | pos = fd.tell() |
| 352 | |
| 353 | # Remove all but 4 bytes of the file headerm and try to read the file |
| 354 | newdata = data[:pos + 4] |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 355 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 356 | with io.BytesIO(newdata) as fd: |
| 357 | fd.seek(pos) |
| 358 | self.assertEqual(False, cbr._read_next_file(fd)) |
Simon Glass | d4ed3b0 | 2019-07-20 12:24:03 -0600 | [diff] [blame] | 359 | self.assertIn('File header at 0x0 ran out of data', stdout.getvalue()) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 360 | |
| 361 | def test_cbfs_bad_file_string(self): |
| 362 | """Check handling of an incomplete filename string""" |
| 363 | size = 0x70 |
| 364 | cbw = CbfsWriter(size) |
| 365 | cbw.add_file_raw('16-characters xx', U_BOOT_DATA) |
| 366 | data = cbw.get_data() |
| 367 | |
| 368 | # Read in the CBFS master header (only), then stop |
| 369 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 370 | with io.BytesIO(data) as fd: |
| 371 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 372 | pos = fd.tell() |
| 373 | |
| 374 | # Create a new CBFS with only the first 16 bytes of the file name, then |
| 375 | # try to read the file |
| 376 | newdata = data[:pos + cbfs_util.FILE_HEADER_LEN + 16] |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 377 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 378 | with io.BytesIO(newdata) as fd: |
| 379 | fd.seek(pos) |
| 380 | self.assertEqual(False, cbr._read_next_file(fd)) |
Simon Glass | d4ed3b0 | 2019-07-20 12:24:03 -0600 | [diff] [blame] | 381 | self.assertIn('String at %#x ran out of data' % |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 382 | cbfs_util.FILE_HEADER_LEN, stdout.getvalue()) |
| 383 | |
| 384 | def test_cbfs_debug(self): |
| 385 | """Check debug output""" |
| 386 | size = 0x70 |
| 387 | cbw = CbfsWriter(size) |
| 388 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 389 | data = cbw.get_data() |
| 390 | |
| 391 | try: |
| 392 | cbfs_util.DEBUG = True |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 393 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 394 | cbfs_util.CbfsReader(data) |
Simon Glass | 1362dbf | 2023-10-14 14:40:30 -0600 | [diff] [blame] | 395 | self.assertEqual('name u-boot\nftype 50\ndata %s\n' % U_BOOT_DATA, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 396 | stdout.getvalue()) |
| 397 | finally: |
| 398 | cbfs_util.DEBUG = False |
| 399 | |
| 400 | def test_cbfs_bad_attribute(self): |
| 401 | """Check handling of bad attribute tag""" |
| 402 | if not self.have_lz4: |
| 403 | self.skipTest('lz4 --no-frame-crc not available') |
| 404 | size = 0x140 |
| 405 | cbw = CbfsWriter(size) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 406 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 407 | compress=cbfs_util.COMPRESS_LZ4) |
| 408 | data = cbw.get_data() |
| 409 | |
| 410 | # Search the CBFS for the expected compression tag |
| 411 | with io.BytesIO(data) as fd: |
| 412 | while True: |
| 413 | pos = fd.tell() |
| 414 | tag, = struct.unpack('>I', fd.read(4)) |
| 415 | if tag == cbfs_util.FILE_ATTR_TAG_COMPRESSION: |
| 416 | break |
| 417 | |
| 418 | # Create a new CBFS with the tag changed to something invalid |
| 419 | newdata = data[:pos] + struct.pack('>I', 0x123) + data[pos + 4:] |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 420 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 421 | cbfs_util.CbfsReader(newdata) |
| 422 | self.assertEqual('Unknown attribute tag 123\n', stdout.getvalue()) |
| 423 | |
| 424 | def test_cbfs_missing_attribute(self): |
| 425 | """Check handling of an incomplete attribute tag""" |
| 426 | if not self.have_lz4: |
| 427 | self.skipTest('lz4 --no-frame-crc not available') |
| 428 | size = 0x140 |
| 429 | cbw = CbfsWriter(size) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 430 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 431 | compress=cbfs_util.COMPRESS_LZ4) |
| 432 | data = cbw.get_data() |
| 433 | |
| 434 | # Read in the CBFS master header (only), then stop |
| 435 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 436 | with io.BytesIO(data) as fd: |
| 437 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 438 | pos = fd.tell() |
| 439 | |
| 440 | # Create a new CBFS with only the first 4 bytes of the compression tag, |
Simon Glass | fa14422 | 2023-10-14 14:40:28 -0600 | [diff] [blame] | 441 | # then try to read the file. Note that the tag gets pushed out 4 bytes |
| 442 | tag_pos = (4 + pos + cbfs_util.FILE_HEADER_LEN + |
| 443 | cbfs_util.ATTRIBUTE_ALIGN) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 444 | newdata = data[:tag_pos + 4] |
Simon Glass | 14d64e3 | 2025-04-29 07:21:59 -0600 | [diff] [blame^] | 445 | with terminal.capture() as (stdout, _stderr): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 446 | with io.BytesIO(newdata) as fd: |
| 447 | fd.seek(pos) |
| 448 | self.assertEqual(False, cbr._read_next_file(fd)) |
| 449 | self.assertIn('Attribute tag at %x ran out of data' % tag_pos, |
| 450 | stdout.getvalue()) |
| 451 | |
| 452 | def test_cbfs_file_master_header(self): |
| 453 | """Check handling of a file containing a master header""" |
| 454 | size = 0x100 |
| 455 | cbw = CbfsWriter(size) |
| 456 | cbw._add_fileheader = True |
| 457 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 458 | data = cbw.get_data() |
| 459 | |
| 460 | cbr = cbfs_util.CbfsReader(data) |
| 461 | self.assertIn('u-boot', cbr.files) |
| 462 | self.assertEqual(size, cbr.rom_size) |
| 463 | |
| 464 | def test_cbfs_arch(self): |
| 465 | """Test on non-x86 architecture""" |
| 466 | size = 0x100 |
| 467 | cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64) |
| 468 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 469 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 470 | data = cbw.get_data() |
| 471 | self._check_raw(data, size, offset=0x40, |
| 472 | arch=cbfs_util.ARCHITECTURE_PPC64) |
| 473 | |
| 474 | # Compare against what cbfstool creates |
| 475 | cbfs_fname = self._get_expected_cbfs(size=size, arch='ppc64') |
| 476 | self._compare_expected_cbfs(data, cbfs_fname) |
| 477 | |
| 478 | def test_cbfs_stage(self): |
Simon Glass | 1362dbf | 2023-10-14 14:40:30 -0600 | [diff] [blame] | 479 | """Tests handling of a CBFS stage""" |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 480 | if not elf.ELF_TOOLS: |
| 481 | self.skipTest('Python elftools not available') |
| 482 | elf_fname = os.path.join(self._indir, 'cbfs-stage.elf') |
| 483 | elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA) |
| 484 | |
| 485 | size = 0xb0 |
| 486 | cbw = CbfsWriter(size) |
Simon Glass | 8002552 | 2022-01-29 14:14:04 -0700 | [diff] [blame] | 487 | cbw.add_file_stage('u-boot', tools.read_file(elf_fname)) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 488 | |
| 489 | data = cbw.get_data() |
| 490 | cbfs = self._check_hdr(data, size) |
| 491 | load = 0xfef20000 |
| 492 | entry = load + 2 |
| 493 | |
Simon Glass | 1362dbf | 2023-10-14 14:40:30 -0600 | [diff] [blame] | 494 | cfile = self._check_uboot(cbfs, cbfs_util.TYPE_STAGE, offset=0x38, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 495 | data=U_BOOT_DATA + U_BOOT_DTB_DATA) |
| 496 | |
| 497 | self.assertEqual(entry, cfile.entry) |
| 498 | self.assertEqual(load, cfile.load) |
| 499 | self.assertEqual(len(U_BOOT_DATA) + len(U_BOOT_DTB_DATA), |
| 500 | cfile.data_len) |
| 501 | |
| 502 | # Compare against what cbfstool creates |
| 503 | if self.have_cbfstool: |
| 504 | cbfs_fname = os.path.join(self._indir, 'test.cbfs') |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame] | 505 | self.cbfstool.create_new(cbfs_fname, size) |
| 506 | self.cbfstool.add_stage(cbfs_fname, 'u-boot', elf_fname) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 507 | self._compare_expected_cbfs(data, cbfs_fname) |
| 508 | |
| 509 | def test_cbfs_raw_compress(self): |
| 510 | """Test base handling of compressing raw files""" |
| 511 | if not self.have_lz4: |
| 512 | self.skipTest('lz4 --no-frame-crc not available') |
| 513 | size = 0x140 |
| 514 | cbw = CbfsWriter(size) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 515 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 516 | compress=cbfs_util.COMPRESS_LZ4) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 517 | cbw.add_file_raw('u-boot-dtb', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 518 | compress=cbfs_util.COMPRESS_LZMA) |
| 519 | data = cbw.get_data() |
| 520 | |
| 521 | cbfs = self._check_hdr(data, size) |
| 522 | self.assertIn('u-boot', cbfs.files) |
| 523 | cfile = cbfs.files['u-boot'] |
| 524 | self.assertEqual(cfile.name, 'u-boot') |
Simon Glass | fa14422 | 2023-10-14 14:40:28 -0600 | [diff] [blame] | 525 | self.assertEqual(cfile.offset, 0x30) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 526 | self.assertEqual(cfile.data, COMPRESS_DATA) |
| 527 | self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW) |
| 528 | self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZ4) |
| 529 | self.assertEqual(cfile.memlen, len(COMPRESS_DATA)) |
| 530 | |
| 531 | self.assertIn('u-boot-dtb', cbfs.files) |
| 532 | cfile = cbfs.files['u-boot-dtb'] |
| 533 | self.assertEqual(cfile.name, 'u-boot-dtb') |
Simon Glass | fa14422 | 2023-10-14 14:40:28 -0600 | [diff] [blame] | 534 | self.assertEqual(cfile.offset, 0x34) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 535 | self.assertEqual(cfile.data, COMPRESS_DATA) |
| 536 | self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW) |
| 537 | self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZMA) |
| 538 | self.assertEqual(cfile.memlen, len(COMPRESS_DATA)) |
| 539 | |
| 540 | cbfs_fname = self._get_expected_cbfs(size=size, compress=['lz4', 'lzma']) |
| 541 | self._compare_expected_cbfs(data, cbfs_fname) |
| 542 | |
Simon Glass | a61e6fe | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 543 | def test_cbfs_raw_space(self): |
| 544 | """Test files with unused space in the CBFS""" |
| 545 | size = 0xf0 |
| 546 | cbw = CbfsWriter(size) |
| 547 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 548 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 549 | data = cbw.get_data() |
| 550 | self._check_raw(data, size) |
| 551 | cbfs_fname = self._get_expected_cbfs(size=size) |
| 552 | self._compare_expected_cbfs(data, cbfs_fname) |
| 553 | |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 554 | def test_cbfs_offset(self): |
| 555 | """Test a CBFS with files at particular offsets""" |
| 556 | size = 0x200 |
| 557 | cbw = CbfsWriter(size) |
| 558 | cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40) |
| 559 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x140) |
| 560 | |
| 561 | data = cbw.get_data() |
| 562 | cbfs = self._check_hdr(data, size) |
| 563 | self._check_uboot(cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x40, |
| 564 | cbfs_offset=0x40) |
| 565 | self._check_dtb(cbfs, offset=0x40, cbfs_offset=0x140) |
| 566 | |
| 567 | cbfs_fname = self._get_expected_cbfs(size=size, base=(0x40, 0x140)) |
| 568 | self._compare_expected_cbfs(data, cbfs_fname) |
| 569 | |
| 570 | def test_cbfs_invalid_file_type_header(self): |
| 571 | """Check handling of an invalid file type when outputting a header""" |
| 572 | size = 0xb0 |
| 573 | cbw = CbfsWriter(size) |
| 574 | cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA, 0) |
| 575 | |
| 576 | # Change the type manually before generating the CBFS, and make sure |
| 577 | # that the generator complains |
| 578 | cfile.ftype = 0xff |
| 579 | with self.assertRaises(ValueError) as e: |
| 580 | cbw.get_data() |
| 581 | self.assertIn('Unknown file type 0xff', str(e.exception)) |
| 582 | |
| 583 | def test_cbfs_offset_conflict(self): |
| 584 | """Test a CBFS with files that want to overlap""" |
| 585 | size = 0x200 |
| 586 | cbw = CbfsWriter(size) |
| 587 | cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40) |
| 588 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x80) |
| 589 | |
| 590 | with self.assertRaises(ValueError) as e: |
| 591 | cbw.get_data() |
| 592 | self.assertIn('No space for data before pad offset', str(e.exception)) |
| 593 | |
| 594 | def test_cbfs_check_offset(self): |
| 595 | """Test that we can discover the offset of a file after writing it""" |
| 596 | size = 0xb0 |
| 597 | cbw = CbfsWriter(size) |
| 598 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 599 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 600 | data = cbw.get_data() |
| 601 | |
| 602 | cbfs = cbfs_util.CbfsReader(data) |
Simon Glass | fa14422 | 2023-10-14 14:40:28 -0600 | [diff] [blame] | 603 | self.assertEqual(0x20, cbfs.files['u-boot'].cbfs_offset) |
| 604 | self.assertEqual(0x64, cbfs.files['u-boot-dtb'].cbfs_offset) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 605 | |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 606 | |
| 607 | if __name__ == '__main__': |
| 608 | unittest.main() |