1#!/usr/bin/env python3
2#
3# Copyright (c) 2021 Nordic Semiconductor ASA
4#
5# SPDX-License-Identifier: Apache-2.0
6
7from unittest import TestCase, main, skipIf
8from subprocess import Popen, PIPE
9from re import sub
10from pathlib import Path
11from pprint import pprint
12from ecdsa import VerifyingKey
13from hashlib import sha256
14import cbor2
15from platform import python_version_tuple
16from sys import platform, exit
17from yaml import safe_load
18from tempfile import mkdtemp
19from shutil import rmtree
20from os import linesep
21
22
23try:
24    import zcbor
25except ImportError:
26    print("""
27The zcbor package must be installed to run these tests.
28During development, install with `pip3 install -e .` to install in a way
29that picks up changes in the files without having to reinstall.
30""")
31    exit(1)
32
33
34p_root = Path(__file__).absolute().parents[2]
35p_tests = Path(p_root, 'tests')
36p_cases = Path(p_tests, 'cases')
37p_manifest12 = Path(p_cases, 'manifest12.cddl')
38p_manifest14 = Path(p_cases, 'manifest14.cddl')
39p_manifest16 = Path(p_cases, 'manifest16.cddl')
40p_manifest20 = Path(p_cases, 'manifest20.cddl')
41p_test_vectors12 = tuple(Path(p_cases, f'manifest12_example{i}.cborhex') for i in range(6))
42p_test_vectors14 = tuple(Path(p_cases, f'manifest14_example{i}.cborhex') for i in range(6))
43p_test_vectors16 = tuple(Path(p_cases, f'manifest14_example{i}.cborhex') for i in range(6))  # Identical to manifest14.
44p_test_vectors20 = tuple(Path(p_cases, f'manifest20_example{i}.cborhex') for i in range(6))
45p_optional = Path(p_cases, 'optional.cddl')
46p_corner_cases = Path(p_cases, 'corner_cases.cddl')
47p_cose = Path(p_cases, 'cose.cddl')
48p_manifest14_priv = Path(p_cases, 'manifest14.priv')
49p_manifest14_pub = Path(p_cases, 'manifest14.pub')
50p_map_bstr_cddl = Path(p_cases, 'map_bstr.cddl')
51p_map_bstr_yaml = Path(p_cases, 'map_bstr.yaml')
52p_yaml_compat_cddl = Path(p_cases, 'yaml_compatibility.cddl')
53p_yaml_compat_yaml = Path(p_cases, 'yaml_compatibility.yaml')
54p_pet_cddl = Path(p_cases, 'pet.cddl')
55p_README = Path(p_root, 'README.md')
56p_prelude = Path(p_root, 'zcbor', 'prelude.cddl')
57p_VERSION = Path(p_root, 'zcbor', 'VERSION')
58
59
60class TestManifest(TestCase):
61    """Class for testing examples against CDDL for various versions of the SUIT manifest spec."""
62    def decode_file(self, data_path, *cddl_paths):
63        data = bytes.fromhex(data_path.read_text(encoding="utf-8").replace("\n", ""))
64        self.decode_string(data, *cddl_paths)
65
66    def decode_string(self, data_string, *cddl_paths):
67        cddl_str = " ".join((Path(p).read_text(encoding="utf-8") for p in cddl_paths))
68        self.my_types = zcbor.DataTranslator.from_cddl(cddl_str, 16).my_types
69        cddl = self.my_types["SUIT_Envelope_Tagged"]
70        self.decoded = cddl.decode_str(data_string)
71
72
73class TestEx0Manifest12(TestManifest):
74    def __init__(self, *args, **kwargs):
75        super().__init__(*args, **kwargs)
76        self.decode_file(p_test_vectors12[0], p_manifest12)
77
78    def test_manifest_digest(self):
79        self.assertEqual(
80            bytes.fromhex("5c097ef64bf3bb9b494e71e1f2418eef8d466cc902f639a855ec9af3e9eddb99"),
81            self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr.suit_digest_bytes)
82
83    def test_signature(self):
84        self.assertEqual(
85            1,
86            self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.protected.uintint[0].uintint_key)
87        self.assertEqual(
88            -7,
89            self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.protected.uintint[0].uintint)
90        self.assertEqual(
91            bytes.fromhex("a19fd1f23b17beed321cece7423dfb48c457b8f1f6ac83577a3c10c6773f6f3a7902376b59540920b6c5f57bac5fc8543d8f5d3d974faa2e6d03daa534b443a7"),
92            self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.signature)
93
94    def test_validate_run(self):
95        self.assertEqual(
96            "suit_condition_image_match_m_l",
97            self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_validate[0].suit_validate.union[0].SUIT_Condition_m.union_choice)
98        self.assertEqual(
99            "suit_directive_run_m_l",
100            self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_run[0].suit_run.union[0].SUIT_Directive_m.union_choice)
101
102    def test_image_size(self):
103        self.assertEqual(34768, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[3].suit_parameter_image_size)
104
105
106class TestEx0InvManifest12(TestManifest):
107    def __init__(self, *args, **kwargs):
108        super().__init__(*args, **kwargs)
109
110    def test_duplicate_type(self):
111        with self.assertRaises(ValueError, msg="Duplicate CDDL type found"):
112            self.decode_file(p_test_vectors12[0], p_manifest12, p_manifest12)
113
114
115class TestEx1Manifest12(TestManifest):
116    def __init__(self, *args, **kwargs):
117        super().__init__(*args, **kwargs)
118        self.decode_file(p_test_vectors12[1], p_manifest12)
119
120    def test_components(self):
121        self.assertEqual(
122            [b'\x00'],
123            self.decoded.suit_manifest.suit_common.suit_components[0][0].bstr)
124
125    def test_uri(self):
126        self.assertEqual(
127            "http://example.com/file.bin",
128            self.decoded.suit_manifest.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[0].SUIT_Directive_m.suit_directive_set_parameters_m_l.map[0].suit_parameter_uri)
129
130
131class TestEx2Manifest12(TestManifest):
132    def __init__(self, *args, **kwargs):
133        super().__init__(*args, **kwargs)
134        self.decode_file(p_test_vectors12[2], p_manifest12)
135
136    def test_severed_uri(self):
137        self.assertEqual(
138            "http://example.com/very/long/path/to/file/file.bin",
139            self.decoded.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[0].SUIT_Directive_m.suit_directive_set_parameters_m_l.map[0].suit_parameter_uri)
140
141    def test_severed_text(self):
142        self.assertIn(
143            "Example 2",
144            self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Text_Keys.suit_text_manifest_description[0])
145        self.assertEqual(
146            [b'\x00'],
147            self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier_key.bstr)
148        self.assertEqual(
149            "arm.com",
150            self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_vendor_domain[0])
151        self.assertEqual(
152            "This component is a demonstration. The digest is a sample pattern, not a real one.",
153            self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_component_description[0])
154
155
156class TestEx3Manifest12(TestManifest):
157    def __init__(self, *args, **kwargs):
158        super().__init__(*args, **kwargs)
159        self.decode_file(p_test_vectors12[3], p_manifest12)
160
161    def test_A_B_offset(self):
162        self.assertEqual(
163            33792,
164            self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[1].SUIT_Common_Commands_m.suit_directive_try_each_m_l.SUIT_Directive_Try_Each_Argument_m.SUIT_Command_Sequence_bstr[0].union[0].SUIT_Directive_m.suit_directive_override_parameters_m_l.map[0].suit_parameter_component_offset)
165        self.assertEqual(
166            541696,
167            self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[1].SUIT_Common_Commands_m.suit_directive_try_each_m_l.SUIT_Directive_Try_Each_Argument_m.SUIT_Command_Sequence_bstr[1].union[0].SUIT_Directive_m.suit_directive_override_parameters_m_l.map[0].suit_parameter_component_offset)
168
169
170class TestEx4Manifest12(TestManifest):
171    def __init__(self, *args, **kwargs):
172        super().__init__(*args, **kwargs)
173        self.decode_file(p_test_vectors12[4], p_manifest12)
174
175    def test_load_decompress(self):
176        self.assertEqual(
177            0,
178            self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_load[0].suit_load.union[1].SUIT_Directive_m.suit_directive_set_parameters_m_l.map[3].suit_parameter_source_component)
179        self.assertEqual(
180            "SUIT_Compression_Algorithm_zlib_m",
181            self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_load[0].suit_load.union[1].SUIT_Directive_m.suit_directive_set_parameters_m_l.map[2].suit_parameter_compression_info.suit_compression_algorithm)
182
183
184class TestEx5Manifest12(TestManifest):
185    def __init__(self, *args, **kwargs):
186        super().__init__(*args, **kwargs)
187        self.decode_file(p_test_vectors12[5], p_manifest12)
188
189    def test_two_image_match(self):
190        self.assertEqual(
191            "suit_condition_image_match_m_l",
192            self.decoded.suit_manifest.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[3].SUIT_Condition_m.union_choice)
193        self.assertEqual(
194            "suit_condition_image_match_m_l",
195            self.decoded.suit_manifest.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[7].SUIT_Condition_m.union_choice)
196
197
198def dumps(obj):
199    return cbor2.dumps(obj, canonical=True)
200
201
202def loads(string):
203    return cbor2.loads(string)
204
205
206class TestEx0Manifest14(TestManifest):
207    def __init__(self, *args, **kwargs):
208        super().__init__(*args, **kwargs)
209        self.key = VerifyingKey.from_pem(p_manifest14_pub.read_text(encoding="utf-8"))
210
211    def do_test_authentication(self):
212        self.assertEqual("COSE_Sign1_Tagged_m", self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].union_choice)
213        self.assertEqual(-7, self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.Headers_m.protected.header_map_bstr.Generic_Headers.uint1union[0].int)
214
215        manifest_signature = self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.signature
216        signature_header = self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.Headers_m.protected.header_map_bstr_bstr
217        manifest_suit_digest = self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr_bstr
218
219        sig_struct = dumps(["Signature1", signature_header, b'', manifest_suit_digest])
220
221        self.key.verify(manifest_signature, sig_struct, hashfunc=sha256)
222
223    def test_auth_0(self):
224        self.decode_file(p_test_vectors14[0], p_manifest14, p_cose)
225        self.do_test_authentication()
226
227    def test_auth_1(self):
228        self.decode_file(p_test_vectors14[1], p_manifest14, p_cose)
229        self.do_test_authentication()
230
231    def test_auth_2(self):
232        self.decode_file(p_test_vectors14[2], p_manifest14, p_cose)
233        self.do_test_authentication()
234
235    def test_auth_3(self):
236        self.decode_file(p_test_vectors14[3], p_manifest14, p_cose)
237        self.do_test_authentication()
238
239    def test_auth_4(self):
240        self.decode_file(p_test_vectors14[4], p_manifest14, p_cose)
241        self.do_test_authentication()
242
243    def test_auth_5(self):
244        self.decode_file(p_test_vectors14[5], p_manifest14, p_cose)
245        self.do_test_authentication()
246
247
248class TestEx1Manifest14(TestManifest):
249    def __init__(self, *args, **kwargs):
250        super().__init__(*args, **kwargs)
251        self.decode_file(p_test_vectors14[1], p_manifest14, p_cose)
252        self.manifest_digest = bytes.fromhex("60c61d6eb7a1aaeddc49ce8157a55cff0821537eeee77a4ded44155b03045132")
253
254    def test_structure(self):
255        self.assertEqual("COSE_Sign1_Tagged_m", self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].union_choice)
256        self.assertEqual(-7, self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.Headers_m.protected.header_map_bstr.Generic_Headers.uint1union[0].int)
257        self.assertEqual(self.manifest_digest, self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr.suit_digest_bytes)
258        self.assertEqual(1, self.decoded.suit_manifest.suit_manifest_sequence_number)
259        self.assertEqual(bytes.fromhex("fa6b4a53d5ad5fdfbe9de663e4d41ffe"), self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[0].suit_parameter_vendor_identifier.RFC4122_UUID_m)
260        self.assertEqual(bytes.fromhex("1492af1425695e48bf429b2d51f2ab45"), self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[1].suit_parameter_class_identifier)
261        self.assertEqual(bytes.fromhex("00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210"), self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[2].suit_parameter_image_digest.suit_digest_bytes)
262        self.assertEqual('cose_alg_sha_256_m', self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[2].suit_parameter_image_digest.suit_digest_algorithm_id.union_choice)
263        self.assertEqual(34768, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[3].suit_parameter_image_size)
264        self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map))
265        self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[1].SUIT_Condition_m.suit_condition_vendor_identifier_m_l.SUIT_Rep_Policy_m)
266        self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[2].SUIT_Condition_m.suit_condition_class_identifier_m_l.SUIT_Rep_Policy_m)
267        self.assertEqual(3, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union))
268        self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0]))
269        self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m))
270        self.assertEqual(1, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l))
271        self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map))
272        self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands_m.suit_directive_override_parameters_m_l.map[0]))
273
274    def test_cbor_pen(self):
275        data = bytes.fromhex(p_test_vectors14[1].read_text(encoding="utf-8").replace("\n", ""))
276        struct = loads(data)
277        struct2 = loads(struct.value[3])  # manifest
278        struct3 = loads(struct2[3])  # common sequence
279        struct4 = loads(struct3[4])  # override params
280        self.assertEqual(struct4[0], 20)
281        self.assertTrue(isinstance(struct4[1][1], bytes))
282        struct4[1][1] = cbor2.CBORTag(112, struct4[1][1])  # Add the tag for cbor-pen
283        struct3[4] = dumps(struct4)
284        struct2[3] = dumps(struct3)
285        struct.value[3] = dumps(struct2)
286        data = dumps(struct)
287        self.decode_string(data, p_manifest14, p_cose)
288
289
290class TestEx1InvManifest14(TestManifest):
291    def test_inv0(self):
292        data = bytes.fromhex(p_test_vectors14[1].read_text(encoding="utf-8").replace("\n", ""))
293        struct = loads(data)
294        struct2 = loads(struct.value[2])  # authentication
295        struct3 = loads(struct2[1])
296        struct3.tag = 99999  # invalid tag for COSE_Sign1
297        struct2[1] = dumps(struct3)
298        struct.value[2] = dumps(struct2)
299        data = dumps(struct)
300        try:
301            self.decode_string(data, p_manifest14, p_cose)
302        except zcbor.CddlValidationError as e:
303            return
304        else:
305            assert False, "Should have failed validation"
306
307    def test_inv1(self):
308        data = bytes.fromhex(p_test_vectors14[1].read_text(encoding="utf-8").replace("\n", ""))
309        struct = loads(data)
310        struct2 = loads(struct.value[3])  # manifest
311        struct2[1] += 1  # invalid manifest version
312        struct.value[3] = dumps(struct2)
313        data = dumps(struct)
314        try:
315            self.decode_string(data, p_manifest14, p_cose)
316        except zcbor.CddlValidationError as e:
317            return
318        else:
319            assert False, "Should have failed validation"
320
321    def test_inv2(self):
322        data = bytes.fromhex(p_test_vectors14[1].read_text(encoding="utf-8").replace("\n", ""))
323        struct = loads(data)
324        struct.value[23] = b''  # Invalid integrated payload key
325        data = dumps(struct)
326        try:
327            self.decode_string(data, p_manifest14, p_cose)
328        except (zcbor.CddlValidationError, cbor2.CBORDecodeEOF) as e:
329            return
330        else:
331            assert False, "Should have failed validation"
332
333    def test_inv3(self):
334        data = bytes.fromhex(p_test_vectors14[1].read_text(encoding="utf-8").replace("\n", ""))
335        struct = loads(data)
336        struct2 = loads(struct.value[3])  # manifest
337        struct3 = loads(struct2[3])  # common sequence
338        struct4 = loads(struct3[4])  # override params
339        self.assertEqual(struct4[0], 20)
340        self.assertTrue(isinstance(struct4[1][1], bytes))
341        struct4[1][1] += b'x'  # vendor ID: wrong length
342        struct3[4] = dumps(struct4)
343        struct2[3] = dumps(struct3)
344        struct.value[3] = dumps(struct2)
345        data = dumps(struct)
346        try:
347            self.decode_string(data, p_manifest14, p_cose)
348        except zcbor.CddlValidationError as e:
349            return
350        else:
351            assert False, "Should have failed validation"
352
353
354class TestEx2Manifest14(TestManifest):
355    def __init__(self, *args, **kwargs):
356        super().__init__(*args, **kwargs)
357        self.decode_file(p_test_vectors14[2], p_manifest14, p_cose)
358
359    def test_text(self):
360        self.assertEqual(
361            bytes.fromhex('2bfc4d0cc6680be7dd9f5ca30aa2bb5d1998145de33d54101b80e2ca49faf918'),
362            self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_text[0].SUIT_Digest_m.suit_digest_bytes)
363        self.assertEqual(
364            bytes.fromhex('2bfc4d0cc6680be7dd9f5ca30aa2bb5d1998145de33d54101b80e2ca49faf918'),
365            sha256(dumps(self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text_bstr)).digest())
366        self.assertEqual('arm.com', self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_vendor_domain[0])
367        self.assertEqual('This component is a demonstration. The digest is a sample pattern, not a real one.', self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_component_description[0])
368
369        # Check manifest description. The concatenation and .replace() call are there to add
370        # trailing whitespace to all blank lines except the first.
371        # This is done in this way to avoid editors automatically removing the whitespace.
372        self.assertEqual('''## Example 2: Simultaneous Download, Installation, Secure Boot, Severed Fields
373''' + '''
374    This example covers the following templates:
375
376    * Compatibility Check ({{template-compatibility-check}})
377    * Secure Boot ({{template-secure-boot}})
378    * Firmware Download ({{firmware-download-template}})
379
380    This example also demonstrates severable elements ({{ovr-severable}}), and text ({{manifest-digest-text}}).'''.replace("\n\n", "\n    \n"), self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Text_Keys.suit_text_manifest_description[0])
381
382
383class TestEx3Manifest14(TestManifest):
384    def __init__(self, *args, **kwargs):
385        super().__init__(*args, **kwargs)
386        self.decode_file(p_test_vectors14[3], p_manifest14, p_cose)
387        self.slots = (33792, 541696)
388
389    def test_try_each(self):
390        self.assertEqual(2, len(self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_install[0].SUIT_Command_Sequence_bstr.union[0].SUIT_Directive_m.suit_directive_try_each_m_l.SUIT_Directive_Try_Each_Argument_m.SUIT_Command_Sequence_bstr))
391        self.assertEqual(self.slots[0], self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_install[0].SUIT_Command_Sequence_bstr.union[0].SUIT_Directive_m.suit_directive_try_each_m_l.SUIT_Directive_Try_Each_Argument_m.SUIT_Command_Sequence_bstr[0].union[0].SUIT_Directive_m.suit_directive_override_parameters_m_l.map[0].suit_parameter_component_slot)
392        self.assertEqual(self.slots[1], self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_install[0].SUIT_Command_Sequence_bstr.union[0].SUIT_Directive_m.suit_directive_try_each_m_l.SUIT_Directive_Try_Each_Argument_m.SUIT_Command_Sequence_bstr[1].union[0].SUIT_Directive_m.suit_directive_override_parameters_m_l.map[0].suit_parameter_component_slot)
393
394
395class TestEx4Manifest14(TestManifest):
396    def __init__(self, *args, **kwargs):
397        super().__init__(*args, **kwargs)
398        self.decode_file(p_test_vectors14[4], p_manifest14, p_cose)
399
400    def test_components(self):
401        self.assertEqual(3, len(self.decoded.suit_manifest.suit_common.suit_components[0]))
402        self.assertEqual(b'\x00', self.decoded.suit_manifest.suit_common.suit_components[0][0].bstr[0])
403        self.assertEqual(b'\x02', self.decoded.suit_manifest.suit_common.suit_components[0][1].bstr[0])
404        self.assertEqual(b'\x01', self.decoded.suit_manifest.suit_common.suit_components[0][2].bstr[0])
405
406
407class TestEx5Manifest14(TestManifest):
408    def __init__(self, *args, **kwargs):
409        super().__init__(*args, **kwargs)
410        self.decode_file(p_test_vectors14[5], p_manifest14, p_cose)
411
412    def test_validate(self):
413        self.assertEqual(4, len(self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_validate[0].suit_validate.union))
414        self.assertEqual(15, self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_validate[0].suit_validate.union[1].SUIT_Condition_m.suit_condition_image_match_m_l.SUIT_Rep_Policy_m)
415
416
417class TestEx5InvManifest14(TestManifest):
418    def test_invalid_rep_policy(self):
419        data = bytes.fromhex(p_test_vectors14[5].read_text(encoding="utf-8").replace("\n", ""))
420        struct = loads(data)
421        struct2 = loads(struct.value[3])  # manifest
422        struct3 = loads(struct2[10])  # suit_validate
423        struct3[3] += 16  # invalid Rep_Policy
424        struct2[10] = dumps(struct3)
425        struct.value[3] = dumps(struct2)
426        data = dumps(struct)
427        try:
428            self.decode_string(data, p_manifest14, p_cose)
429        except zcbor.CddlValidationError as e:
430            return
431        else:
432            assert False, "Should have failed validation"
433
434
435class TestEx0Manifest16(TestEx0Manifest14):
436    def __init__(self, *args, **kwargs):
437        super().__init__(*args, **kwargs)
438        self.decode_file(p_test_vectors16[0], p_manifest16, p_cose)
439
440
441class TestEx1Manifest16(TestEx1Manifest14):
442    def __init__(self, *args, **kwargs):
443        super().__init__(*args, **kwargs)
444        self.decode_file(p_test_vectors16[1], p_manifest16, p_cose)
445
446
447class TestEx1InvManifest16(TestEx1InvManifest14):
448    def __init__(self, *args, **kwargs):
449        super().__init__(*args, **kwargs)
450        self.decode_file(p_test_vectors16[1], p_manifest16, p_cose)
451
452
453class TestEx2Manifest16(TestEx2Manifest14):
454    def __init__(self, *args, **kwargs):
455        super().__init__(*args, **kwargs)
456        self.decode_file(p_test_vectors16[2], p_manifest16, p_cose)
457
458
459class TestEx3Manifest16(TestEx3Manifest14):
460    def __init__(self, *args, **kwargs):
461        super().__init__(*args, **kwargs)
462        self.decode_file(p_test_vectors16[3], p_manifest16, p_cose)
463
464
465# Comment out because example 4 uses compression which is unsupported in manifest16
466# class TestEx4Manifest16(TestEx4Manifest14):
467#     def __init__(self, *args, **kwargs):
468#         super().__init__(*args, **kwargs)
469#         self.decode_file(p_test_vectors16[4], p_manifest16, p_cose)
470
471
472class TestEx5Manifest16(TestEx5Manifest14):
473    def __init__(self, *args, **kwargs):
474        super().__init__(*args, **kwargs)
475        self.decode_file(p_test_vectors16[5], p_manifest16, p_cose)
476
477
478class TestEx5InvManifest16(TestEx5InvManifest14):
479    def __init__(self, *args, **kwargs):
480        super().__init__(*args, **kwargs)
481        self.decode_file(p_test_vectors16[5], p_manifest16, p_cose)
482
483
484class TestEx0Manifest20(TestEx0Manifest16):
485    def __init__(self, *args, **kwargs):
486        super().__init__(*args, **kwargs)
487        self.decode_file(p_test_vectors20[0], p_manifest20, p_cose)
488
489
490class TestEx1Manifest20(TestEx1Manifest16):
491    def __init__(self, *args, **kwargs):
492        super().__init__(*args, **kwargs)
493        self.decode_file(p_test_vectors20[1], p_manifest20, p_cose)
494        self.manifest_digest = bytes.fromhex("ef14b7091e8adae8aa3bb6fca1d64fb37e19dcf8b35714cfdddc5968c80ff50e")
495
496    def test_structure(self):
497        self.assertEqual("COSE_Sign1_Tagged_m", self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].union_choice)
498        self.assertEqual(-7, self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged_m.Headers_m.protected.header_map_bstr.Generic_Headers.uint1union[0].int)
499        self.assertEqual(self.manifest_digest, self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr.suit_digest_bytes)
500        self.assertEqual(1, self.decoded.suit_manifest.suit_manifest_sequence_number)
501        self.assertEqual(bytes.fromhex("fa6b4a53d5ad5fdfbe9de663e4d41ffe"), self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map[0].suit_parameter_vendor_identifier.RFC4122_UUID_m)
502        self.assertEqual(bytes.fromhex("1492af1425695e48bf429b2d51f2ab45"), self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map[1].suit_parameter_class_identifier)
503        self.assertEqual(bytes.fromhex("00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210"), self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map[2].suit_parameter_image_digest.suit_digest_bytes)
504        self.assertEqual('cose_alg_sha_256_m', self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map[2].suit_parameter_image_digest.suit_digest_algorithm_id.union_choice)
505        self.assertEqual(34768, self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map[3].suit_parameter_image_size)
506        self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map))
507        self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[1].SUIT_Condition_m.suit_condition_vendor_identifier_m_l.SUIT_Rep_Policy_m)
508        self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[2].SUIT_Condition_m.suit_condition_class_identifier_m_l.SUIT_Rep_Policy_m)
509        self.assertEqual(3, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union))
510        self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0]))
511        self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m))
512        self.assertEqual(1, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l))
513        self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map))
514        self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands_m.suit_directive_override_parameters_m_l.map[0]))
515
516
517class TestEx1InvManifest20(TestEx1InvManifest16):
518    def __init__(self, *args, **kwargs):
519        super().__init__(*args, **kwargs)
520        self.decode_file(p_test_vectors20[1], p_manifest20, p_cose)
521
522
523class TestEx2Manifest20(TestEx2Manifest16):
524    def __init__(self, *args, **kwargs):
525        super().__init__(*args, **kwargs)
526        self.decode_file(p_test_vectors20[2], p_manifest20, p_cose)
527
528
529class TestEx3Manifest20(TestEx3Manifest16):
530    def __init__(self, *args, **kwargs):
531        super().__init__(*args, **kwargs)
532        self.decode_file(p_test_vectors20[3], p_manifest20, p_cose)
533        self.slots = (0, 1)
534
535
536class TestEx4Manifest20(TestEx4Manifest14):
537    def __init__(self, *args, **kwargs):
538        super().__init__(*args, **kwargs)
539        self.decode_file(p_test_vectors20[4], p_manifest20, p_cose)
540
541
542class TestEx5Manifest20(TestEx5Manifest16):
543    def __init__(self, *args, **kwargs):
544        super().__init__(*args, **kwargs)
545        self.decode_file(p_test_vectors20[5], p_manifest20, p_cose)
546
547
548class TestEx5InvManifest20(TestEx5InvManifest16):
549    def __init__(self, *args, **kwargs):
550        super().__init__(*args, **kwargs)
551        self.decode_file(p_test_vectors20[5], p_manifest20, p_cose)
552
553
554class PopenTest(TestCase):
555    def popen_test(self, args, input="", exp_retcode=0):
556        call0 = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
557        stdout0, stderr0 = call0.communicate(input)
558        self.assertEqual(exp_retcode, call0.returncode, stderr0.decode('utf-8'))
559        return stdout0, stderr0
560
561
562class TestCLI(PopenTest):
563    def get_std_args(self, input, cmd="convert"):
564        return ["zcbor", cmd, "--cddl", str(p_manifest12), "--input", str(input), "-t", "SUIT_Envelope_Tagged", "--yaml-compatibility"]
565
566    def do_testManifest(self, n):
567        self.popen_test(self.get_std_args(p_test_vectors12[n], cmd="validate"), "")
568        stdout0, _ = self.popen_test(self.get_std_args(p_test_vectors12[n]) + ["--output", "-", "--output-as", "cbor"], "")
569
570        self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "cbor"], stdout0)
571        stdout1, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "cbor", "--output", "-", "--output-as", "json"], stdout0)
572
573        self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "json"], stdout1)
574        stdout2, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "json", "--output", "-", "--output-as", "yaml"], stdout1)
575
576        self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "yaml"], stdout2)
577        stdout3, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "yaml", "--output", "-", "--output-as", "cbor"], stdout2)
578
579        self.assertEqual(stdout0, stdout3)
580
581        self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "cbor"], stdout3)
582        stdout4, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "cbor", "--output", "-", "--output-as", "cborhex"], stdout3)
583
584        self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "cborhex"], stdout4)
585        stdout5, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "cborhex", "--output", "-", "--output-as", "json"], stdout4)
586
587        self.assertEqual(stdout1, stdout5)
588
589        self.maxDiff = None
590
591        with open(p_test_vectors12[n], 'r', encoding="utf-8") as f:
592            self.assertEqual(sub(r"\W+", "", f.read()), sub(r"\W+", "", stdout4.decode("utf-8")))
593
594    def test_0(self):
595        self.do_testManifest(0)
596
597    def test_1(self):
598        self.do_testManifest(1)
599
600    def test_2(self):
601        self.do_testManifest(2)
602
603    def test_3(self):
604        self.do_testManifest(3)
605
606    def test_4(self):
607        self.do_testManifest(4)
608
609    def test_5(self):
610        self.do_testManifest(5)
611
612    def test_map_bstr(self):
613        stdout1, _ = self.popen_test(["zcbor", "convert", "--cddl", str(p_map_bstr_cddl), "--input", str(p_map_bstr_yaml), "-t", "map", "--yaml-compatibility", "--output", "-"], "")
614        self.assertEqual(dumps({"test": bytes.fromhex("1234abcd"), "test2": cbor2.CBORTag(1234, bytes.fromhex("1a2b3c4d")), ("test3",): dumps(1234)}), stdout1)
615
616    def test_decode_encode(self):
617        _, stderr1 = self.popen_test(["zcbor", "code", "--cddl", str(p_map_bstr_cddl), "-t", "map"], "", exp_retcode=2)
618        self.assertIn(b"error: Please specify at least one of --decode or --encode", stderr1)
619
620    def test_output_present(self):
621        args = ["zcbor", "code", "--cddl", str(p_map_bstr_cddl), "-t", "map", "-d"]
622        _, stderr1 = self.popen_test(args, "", exp_retcode=2)
623        self.assertIn(
624            b"error: Please specify both --output-c and --output-h "
625            b"unless --output-cmake is specified.",
626            stderr1)
627
628        _, stderr2 = self.popen_test(args + ["--output-c", "/tmp/map.c"], "", exp_retcode=2)
629        self.assertIn(
630            b"error: Please specify both --output-c and --output-h "
631            b"unless --output-cmake is specified.",
632            stderr2)
633
634    def do_test_file_header(self, from_file=False):
635        tempd = Path(mkdtemp())
636        file_header = """Sample
637
638file header"""
639        if from_file:
640            (tempd / "file_header.txt").write_text(file_header, encoding="utf-8")
641            file_header_input = str(tempd / "file_header.txt")
642        else:
643            file_header_input = file_header
644
645        _, __ = self.popen_test(["zcbor", "code", "--cddl", str(p_pet_cddl), "-t", "Pet", "--output-cmake", str(tempd / "pet.cmake"), "-d", "-e", "--file-header", (file_header_input), "--dq", "5"], "")
646        exp_cmake_header = f"""#
647# Sample
648#
649# file header
650#
651# Generated using zcbor version {p_VERSION.read_text(encoding="utf-8")}
652# https://github.com/NordicSemiconductor/zcbor
653# Generated with a --default-max-qty of 5
654#""".splitlines()
655        exp_c_header = f"""/*
656 * Sample
657 *
658 * file header
659 *
660 * Generated using zcbor version {p_VERSION.read_text(encoding="utf-8")}
661 * https://github.com/NordicSemiconductor/zcbor
662 * Generated with a --default-max-qty of 5
663 */""".splitlines()
664        self.assertEqual(exp_cmake_header, (tempd / "pet.cmake").read_text(encoding="utf-8").splitlines()[:9])
665        for p in (tempd / "src" / "pet_decode.c", tempd / "src" / "pet_encode.c",
666                  tempd / "include" / "pet_decode.h", tempd / "include" / "pet_encode.h",
667                  tempd / "include" / "pet_types.h"):
668            self.assertEqual(exp_c_header, p.read_text(encoding="utf-8").splitlines()[:9])
669        rmtree(tempd)
670
671    def test_file_header(self):
672        self.do_test_file_header()
673        self.do_test_file_header(from_file=True)
674
675
676class TestOptional(TestCase):
677    def test_optional_0(self):
678        with open(p_optional, 'r', encoding="utf-8") as f:
679            cddl_res = zcbor.DataTranslator.from_cddl(f.read(), 16)
680        cddl = cddl_res.my_types['cfg']
681        test_yaml = """
682            mem_config:
683                - 0
684                - 5"""
685        decoded = cddl.decode_str_yaml(test_yaml)
686        self.assertEqual(decoded.mem_config[0].READ.union_choice, "uint0")
687        self.assertEqual(decoded.mem_config[0].N, [5])
688
689
690class TestUndefined(TestCase):
691    def test_undefined_0(self):
692        cddl_res = zcbor.DataTranslator.from_cddl(
693            p_prelude.read_text(encoding="utf-8") + '\n' + p_corner_cases.read_text(encoding="utf-8"), 16)
694        cddl = cddl_res.my_types['Simples']
695        test_yaml = "[true, false, true, null, [zcbor_undefined]]"
696
697        decoded = cddl.decode_str_yaml(test_yaml, yaml_compat=True)
698        self.assertEqual(True, decoded.boolval)
699
700        encoded = cddl.str_to_yaml(cddl.from_yaml(test_yaml, yaml_compat=True), yaml_compat=True)
701        self.assertEqual(safe_load(encoded), safe_load(test_yaml))
702
703
704class TestFloat(TestCase):
705    def test_float_0(self):
706        cddl_res = zcbor.DataTranslator.from_cddl(
707            p_prelude.read_text(encoding="utf-8") + '\n' + p_corner_cases.read_text(encoding="utf-8"), 16)
708        cddl = cddl_res.my_types['Floats']
709        test_yaml = f"[3.1415, 1234567.89, 0.000123, 3.1415, 2.71828, 5.0, {1 / 3}]"
710
711        decoded = cddl.decode_str_yaml(test_yaml)
712        self.assertEqual(3.1415, decoded.float_16)
713        self.assertEqual(1234567.89, decoded.float_32)
714        self.assertEqual(0.000123, decoded.float_64)
715        self.assertEqual(2, len(decoded.floats))
716        self.assertEqual(5, decoded.floats[0])
717        self.assertEqual(1 / 3, decoded.floats[1])
718
719        encoded = cddl.str_to_yaml(cddl.from_yaml(test_yaml))
720        self.assertEqual(safe_load(encoded), safe_load(test_yaml))
721
722
723class TestYamlCompatibility(PopenTest):
724    def test_yaml_compatibility(self):
725        self.popen_test(["zcbor", "validate", "-c", p_yaml_compat_cddl, "-i", p_yaml_compat_yaml, "-t", "Yaml_compatibility_example"], exp_retcode=1)
726        self.popen_test(["zcbor", "validate", "-c", p_yaml_compat_cddl, "-i", p_yaml_compat_yaml, "-t", "Yaml_compatibility_example", "--yaml-compatibility"])
727        stdout1, _ = self.popen_test(["zcbor", "convert", "-c", p_yaml_compat_cddl, "-i", p_yaml_compat_yaml, "-o", "-", "-t", "Yaml_compatibility_example", "--yaml-compatibility"])
728        stdout2, _ = self.popen_test(["zcbor", "convert", "-c", p_yaml_compat_cddl, "-i", "-", "-o", "-", "--output-as", "yaml", "-t", "Yaml_compatibility_example", "--yaml-compatibility"], stdout1)
729        self.assertEqual(safe_load(stdout2), safe_load(p_yaml_compat_yaml.read_text(encoding="utf-8")))
730
731
732class TestIntmax(TestCase):
733    def test_intmax1(self):
734        cddl_res = zcbor.DataTranslator.from_cddl(
735            p_prelude.read_text(encoding="utf-8") + '\n' + p_corner_cases.read_text(encoding="utf-8"), 16)
736        cddl = cddl_res.my_types['Intmax1']
737        test_yaml = f"[-128, 127, 255, -32768, 32767, 65535, -2147483648, 2147483647, 4294967295, -9223372036854775808, 9223372036854775807, 18446744073709551615]"
738        decoded = cddl.decode_str_yaml(test_yaml)
739
740    def test_intmax2(self):
741        cddl_res = zcbor.DataTranslator.from_cddl(
742            p_prelude.read_text(encoding="utf-8") + '\n' + p_corner_cases.read_text(encoding="utf-8"), 16)
743        cddl = cddl_res.my_types['Intmax2']
744        test_yaml1 = f"[-128, 0, -32768, 0, -2147483648, 0, -9223372036854775808, 0]"
745        decoded = cddl.decode_str_yaml(test_yaml1)
746        self.assertEqual(decoded.INT_8, -128)
747        self.assertEqual(decoded.UINT_8, 0)
748        self.assertEqual(decoded.INT_16, -32768)
749        self.assertEqual(decoded.UINT_16, 0)
750        self.assertEqual(decoded.INT_32, -2147483648)
751        self.assertEqual(decoded.UINT_32, 0)
752        self.assertEqual(decoded.INT_64, -9223372036854775808)
753        self.assertEqual(decoded.UINT_64, 0)
754
755        test_yaml2 = f"[127, 255, 32767, 65535, 2147483647, 4294967295, 9223372036854775807, 18446744073709551615]"
756        decoded = cddl.decode_str_yaml(test_yaml2)
757        self.assertEqual(decoded.INT_8, 127)
758        self.assertEqual(decoded.UINT_8, 255)
759        self.assertEqual(decoded.INT_16, 32767)
760        self.assertEqual(decoded.UINT_16, 65535)
761        self.assertEqual(decoded.INT_32, 2147483647)
762        self.assertEqual(decoded.UINT_32, 4294967295)
763        self.assertEqual(decoded.INT_64, 9223372036854775807)
764        self.assertEqual(decoded.UINT_64, 18446744073709551615)
765
766
767class TestInvalidIdentifiers(TestCase):
768    def test_invalid_identifiers0(self):
769        cddl_res = zcbor.DataTranslator.from_cddl(
770            p_prelude.read_text(encoding="utf-8") + '\n' + p_corner_cases.read_text(encoding="utf-8"), 16)
771        cddl = cddl_res.my_types['InvalidIdentifiers']
772        test_yaml = "['1one', 2, '{[a-z]}']"
773        decoded = cddl.decode_str_yaml(test_yaml)
774        self.assertTrue(decoded.f_1one_tstr)
775        self.assertTrue(decoded.f_)
776        self.assertTrue(decoded.a_z_tstr)
777
778
779if __name__ == "__main__":
780    main()
781