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