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