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