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