1# 2# Copyright (C) 2018 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import copy 18import os 19import os.path 20import zipfile 21 22import common 23import ota_metadata_pb2 24import test_utils 25from ota_utils import ( 26 BuildLegacyOtaMetadata, CalculateRuntimeDevicesAndFingerprints, 27 FinalizeMetadata, GetPackageMetadata, PropertyFiles) 28from ota_from_target_files import ( 29 _LoadOemDicts, AbOtaPropertyFiles, 30 GetTargetFilesZipForCustomImagesUpdates, 31 GetTargetFilesZipForPartialUpdates, 32 GetTargetFilesZipForSecondaryImages, 33 GetTargetFilesZipWithoutPostinstallConfig, 34 Payload, PayloadSigner, POSTINSTALL_CONFIG, 35 StreamingPropertyFiles, AB_PARTITIONS) 36from apex_utils import GetApexInfoFromTargetFiles 37from test_utils import PropertyFilesTestCase 38 39 40def construct_target_files(secondary=False, compressedApex=False): 41 """Returns a target-files.zip file for generating OTA packages.""" 42 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip') 43 with zipfile.ZipFile(target_files, 'w', allowZip64=True) as target_files_zip: 44 # META/update_engine_config.txt 45 target_files_zip.writestr( 46 'META/update_engine_config.txt', 47 "PAYLOAD_MAJOR_VERSION=2\nPAYLOAD_MINOR_VERSION=4\n") 48 49 # META/postinstall_config.txt 50 target_files_zip.writestr( 51 POSTINSTALL_CONFIG, 52 '\n'.join([ 53 "RUN_POSTINSTALL_system=true", 54 "POSTINSTALL_PATH_system=system/bin/otapreopt_script", 55 "FILESYSTEM_TYPE_system=ext4", 56 "POSTINSTALL_OPTIONAL_system=true", 57 ])) 58 59 ab_partitions = [ 60 ('IMAGES', 'boot'), 61 ('IMAGES', 'system'), 62 ('IMAGES', 'vendor'), 63 ('RADIO', 'bootloader'), 64 ('RADIO', 'modem'), 65 ] 66 # META/ab_partitions.txt 67 target_files_zip.writestr( 68 'META/ab_partitions.txt', 69 '\n'.join([partition[1] for partition in ab_partitions])) 70 71 # Create fake images for each of them. 72 for path, partition in ab_partitions: 73 target_files_zip.writestr( 74 '{}/{}.img'.format(path, partition), 75 os.urandom(len(partition))) 76 77 # system_other shouldn't appear in META/ab_partitions.txt. 78 if secondary: 79 target_files_zip.writestr('IMAGES/system_other.img', 80 os.urandom(len("system_other"))) 81 82 if compressedApex: 83 apex_file_name = 'com.android.apex.compressed.v1.capex' 84 apex_file = os.path.join(test_utils.get_current_dir(), apex_file_name) 85 target_files_zip.write(apex_file, 'SYSTEM/apex/' + apex_file_name) 86 87 return target_files 88 89 90class LoadOemDictsTest(test_utils.ReleaseToolsTestCase): 91 92 def test_NoneDict(self): 93 self.assertIsNone(_LoadOemDicts(None)) 94 95 def test_SingleDict(self): 96 dict_file = common.MakeTempFile() 97 with open(dict_file, 'w') as dict_fp: 98 dict_fp.write('abc=1\ndef=2\nxyz=foo\na.b.c=bar\n') 99 100 oem_dicts = _LoadOemDicts([dict_file]) 101 self.assertEqual(1, len(oem_dicts)) 102 self.assertEqual('foo', oem_dicts[0]['xyz']) 103 self.assertEqual('bar', oem_dicts[0]['a.b.c']) 104 105 def test_MultipleDicts(self): 106 oem_source = [] 107 for i in range(3): 108 dict_file = common.MakeTempFile() 109 with open(dict_file, 'w') as dict_fp: 110 dict_fp.write( 111 'ro.build.index={}\ndef=2\nxyz=foo\na.b.c=bar\n'.format(i)) 112 oem_source.append(dict_file) 113 114 oem_dicts = _LoadOemDicts(oem_source) 115 self.assertEqual(3, len(oem_dicts)) 116 for i, oem_dict in enumerate(oem_dicts): 117 self.assertEqual('2', oem_dict['def']) 118 self.assertEqual('foo', oem_dict['xyz']) 119 self.assertEqual('bar', oem_dict['a.b.c']) 120 self.assertEqual('{}'.format(i), oem_dict['ro.build.index']) 121 122 123class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): 124 TEST_TARGET_INFO_DICT = { 125 'build.prop': common.PartitionBuildProps.FromDictionary( 126 'system', { 127 'ro.product.device': 'product-device', 128 'ro.build.fingerprint': 'build-fingerprint-target', 129 'ro.build.version.incremental': 'build-version-incremental-target', 130 'ro.build.version.sdk': '27', 131 'ro.build.version.security_patch': '2017-12-01', 132 'ro.build.date.utc': '1500000000'} 133 ) 134 } 135 136 TEST_SOURCE_INFO_DICT = { 137 'build.prop': common.PartitionBuildProps.FromDictionary( 138 'system', { 139 'ro.product.device': 'product-device', 140 'ro.build.fingerprint': 'build-fingerprint-source', 141 'ro.build.version.incremental': 'build-version-incremental-source', 142 'ro.build.version.sdk': '25', 143 'ro.build.version.security_patch': '2016-12-01', 144 'ro.build.date.utc': '1400000000'} 145 ) 146 } 147 148 TEST_INFO_DICT_USES_OEM_PROPS = { 149 'build.prop': common.PartitionBuildProps.FromDictionary( 150 'system', { 151 'ro.product.name': 'product-name', 152 'ro.build.thumbprint': 'build-thumbprint', 153 'ro.build.bar': 'build-bar'} 154 ), 155 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( 156 'vendor', { 157 'ro.vendor.build.fingerprint': 'vendor-build-fingerprint'} 158 ), 159 'property1': 'value1', 160 'property2': 4096, 161 'oem_fingerprint_properties': 'ro.product.device ro.product.brand', 162 } 163 164 def setUp(self): 165 self.testdata_dir = test_utils.get_testdata_dir() 166 self.assertTrue(os.path.exists(self.testdata_dir)) 167 168 # Reset the global options as in ota_from_target_files.py. 169 common.OPTIONS.incremental_source = None 170 common.OPTIONS.downgrade = False 171 common.OPTIONS.retrofit_dynamic_partitions = False 172 common.OPTIONS.timestamp = False 173 common.OPTIONS.wipe_user_data = False 174 common.OPTIONS.no_signing = False 175 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') 176 common.OPTIONS.key_passwords = { 177 common.OPTIONS.package_key: None, 178 } 179 180 common.OPTIONS.search_path = test_utils.get_search_path() 181 182 @staticmethod 183 def GetLegacyOtaMetadata(target_info, source_info=None): 184 metadata_proto = GetPackageMetadata(target_info, source_info) 185 return BuildLegacyOtaMetadata(metadata_proto) 186 187 def test_GetPackageMetadata_abOta_full(self): 188 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) 189 target_info_dict['ab_update'] = 'true' 190 target_info_dict['ab_partitions'] = [] 191 target_info = common.BuildInfo(target_info_dict, None) 192 metadata = self.GetLegacyOtaMetadata(target_info) 193 self.assertDictEqual( 194 { 195 'ota-type': 'AB', 196 'ota-required-cache': '0', 197 'post-build': 'build-fingerprint-target', 198 'post-build-incremental': 'build-version-incremental-target', 199 'post-sdk-level': '27', 200 'post-security-patch-level': '2017-12-01', 201 'post-timestamp': '1500000000', 202 'pre-device': 'product-device', 203 }, 204 metadata) 205 206 def test_GetPackageMetadata_abOta_incremental(self): 207 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) 208 target_info_dict['ab_update'] = 'true' 209 target_info_dict['ab_partitions'] = [] 210 target_info = common.BuildInfo(target_info_dict, None) 211 source_info = common.BuildInfo(self.TEST_SOURCE_INFO_DICT, None) 212 common.OPTIONS.incremental_source = '' 213 metadata = self.GetLegacyOtaMetadata(target_info, source_info) 214 self.assertDictEqual( 215 { 216 'ota-type': 'AB', 217 'ota-required-cache': '0', 218 'post-build': 'build-fingerprint-target', 219 'post-build-incremental': 'build-version-incremental-target', 220 'post-sdk-level': '27', 221 'post-security-patch-level': '2017-12-01', 222 'post-timestamp': '1500000000', 223 'pre-device': 'product-device', 224 'pre-build': 'build-fingerprint-source', 225 'pre-build-incremental': 'build-version-incremental-source', 226 }, 227 metadata) 228 229 def test_GetPackageMetadata_nonAbOta_full(self): 230 target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) 231 metadata = self.GetLegacyOtaMetadata(target_info) 232 self.assertDictEqual( 233 { 234 'ota-type': 'BLOCK', 235 'ota-required-cache': '0', 236 'post-build': 'build-fingerprint-target', 237 'post-build-incremental': 'build-version-incremental-target', 238 'post-sdk-level': '27', 239 'post-security-patch-level': '2017-12-01', 240 'post-timestamp': '1500000000', 241 'pre-device': 'product-device', 242 }, 243 metadata) 244 245 def test_GetPackageMetadata_nonAbOta_incremental(self): 246 target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) 247 source_info = common.BuildInfo(self.TEST_SOURCE_INFO_DICT, None) 248 common.OPTIONS.incremental_source = '' 249 metadata = self.GetLegacyOtaMetadata(target_info, source_info) 250 self.assertDictEqual( 251 { 252 'ota-type': 'BLOCK', 253 'ota-required-cache': '0', 254 'post-build': 'build-fingerprint-target', 255 'post-build-incremental': 'build-version-incremental-target', 256 'post-sdk-level': '27', 257 'post-security-patch-level': '2017-12-01', 258 'post-timestamp': '1500000000', 259 'pre-device': 'product-device', 260 'pre-build': 'build-fingerprint-source', 261 'pre-build-incremental': 'build-version-incremental-source', 262 }, 263 metadata) 264 265 def test_GetPackageMetadata_wipe(self): 266 target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) 267 common.OPTIONS.wipe_user_data = True 268 metadata = self.GetLegacyOtaMetadata(target_info) 269 self.assertDictEqual( 270 { 271 'ota-type': 'BLOCK', 272 'ota-required-cache': '0', 273 'ota-wipe': 'yes', 274 'post-build': 'build-fingerprint-target', 275 'post-build-incremental': 'build-version-incremental-target', 276 'post-sdk-level': '27', 277 'post-security-patch-level': '2017-12-01', 278 'post-timestamp': '1500000000', 279 'pre-device': 'product-device', 280 }, 281 metadata) 282 283 @test_utils.SkipIfExternalToolsUnavailable() 284 def test_GetApexInfoFromTargetFiles(self): 285 target_files = construct_target_files(compressedApex=True) 286 apex_infos = GetApexInfoFromTargetFiles(target_files, 'system') 287 self.assertEqual(len(apex_infos), 1) 288 self.assertEqual(apex_infos[0].package_name, "com.android.apex.compressed") 289 self.assertEqual(apex_infos[0].version, 1) 290 self.assertEqual(apex_infos[0].is_compressed, True) 291 # Compare the decompressed APEX size with the original uncompressed APEX 292 original_apex_name = 'com.android.apex.compressed.v1_original.apex' 293 original_apex_filepath = os.path.join( 294 test_utils.get_current_dir(), original_apex_name) 295 uncompressed_apex_size = os.path.getsize(original_apex_filepath) 296 self.assertEqual(apex_infos[0].decompressed_size, uncompressed_apex_size) 297 298 def test_GetPackageMetadata_retrofitDynamicPartitions(self): 299 target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) 300 common.OPTIONS.retrofit_dynamic_partitions = True 301 metadata = self.GetLegacyOtaMetadata(target_info) 302 self.assertDictEqual( 303 { 304 'ota-retrofit-dynamic-partitions': 'yes', 305 'ota-type': 'BLOCK', 306 'ota-required-cache': '0', 307 'post-build': 'build-fingerprint-target', 308 'post-build-incremental': 'build-version-incremental-target', 309 'post-sdk-level': '27', 310 'post-security-patch-level': '2017-12-01', 311 'post-timestamp': '1500000000', 312 'pre-device': 'product-device', 313 }, 314 metadata) 315 316 @staticmethod 317 def _test_GetPackageMetadata_swapBuildTimestamps(target_info, source_info): 318 (target_info['build.prop'].build_props['ro.build.date.utc'], 319 source_info['build.prop'].build_props['ro.build.date.utc']) = ( 320 source_info['build.prop'].build_props['ro.build.date.utc'], 321 target_info['build.prop'].build_props['ro.build.date.utc']) 322 323 def test_GetPackageMetadata_unintentionalDowngradeDetected(self): 324 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) 325 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT) 326 self._test_GetPackageMetadata_swapBuildTimestamps( 327 target_info_dict, source_info_dict) 328 329 target_info = common.BuildInfo(target_info_dict, None) 330 source_info = common.BuildInfo(source_info_dict, None) 331 common.OPTIONS.incremental_source = '' 332 self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info, 333 source_info) 334 335 def test_GetPackageMetadata_downgrade(self): 336 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) 337 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT) 338 self._test_GetPackageMetadata_swapBuildTimestamps( 339 target_info_dict, source_info_dict) 340 341 target_info = common.BuildInfo(target_info_dict, None) 342 source_info = common.BuildInfo(source_info_dict, None) 343 common.OPTIONS.incremental_source = '' 344 common.OPTIONS.downgrade = True 345 common.OPTIONS.wipe_user_data = True 346 common.OPTIONS.spl_downgrade = True 347 metadata = self.GetLegacyOtaMetadata(target_info, source_info) 348 # Reset spl_downgrade so other tests are unaffected 349 common.OPTIONS.spl_downgrade = False 350 351 self.assertDictEqual( 352 { 353 'ota-downgrade': 'yes', 354 'ota-type': 'BLOCK', 355 'ota-required-cache': '0', 356 'ota-wipe': 'yes', 357 'post-build': 'build-fingerprint-target', 358 'post-build-incremental': 'build-version-incremental-target', 359 'post-sdk-level': '27', 360 'post-security-patch-level': '2017-12-01', 361 'post-timestamp': '1400000000', 362 'pre-device': 'product-device', 363 'pre-build': 'build-fingerprint-source', 364 'pre-build-incremental': 'build-version-incremental-source', 365 'spl-downgrade': 'yes', 366 }, 367 metadata) 368 369 @test_utils.SkipIfExternalToolsUnavailable() 370 def test_GetTargetFilesZipForSecondaryImages(self): 371 input_file = construct_target_files(secondary=True) 372 target_file = GetTargetFilesZipForSecondaryImages(input_file) 373 374 with zipfile.ZipFile(target_file) as verify_zip: 375 namelist = verify_zip.namelist() 376 ab_partitions = verify_zip.read('META/ab_partitions.txt').decode() 377 378 self.assertIn('META/ab_partitions.txt', namelist) 379 self.assertIn('IMAGES/system.img', namelist) 380 self.assertIn('RADIO/bootloader.img', namelist) 381 self.assertIn(POSTINSTALL_CONFIG, namelist) 382 383 self.assertNotIn('IMAGES/boot.img', namelist) 384 self.assertNotIn('IMAGES/system_other.img', namelist) 385 self.assertNotIn('IMAGES/system.map', namelist) 386 self.assertNotIn('RADIO/modem.img', namelist) 387 388 expected_ab_partitions = ['system', 'bootloader'] 389 self.assertEqual('\n'.join(expected_ab_partitions), ab_partitions) 390 391 @test_utils.SkipIfExternalToolsUnavailable() 392 def test_GetTargetFilesZipForSecondaryImages_skipPostinstall(self): 393 input_file = construct_target_files(secondary=True) 394 target_file = GetTargetFilesZipForSecondaryImages( 395 input_file, skip_postinstall=True) 396 397 with zipfile.ZipFile(target_file) as verify_zip: 398 namelist = verify_zip.namelist() 399 400 self.assertIn('META/ab_partitions.txt', namelist) 401 self.assertIn('IMAGES/system.img', namelist) 402 self.assertIn('RADIO/bootloader.img', namelist) 403 404 self.assertNotIn('IMAGES/boot.img', namelist) 405 self.assertNotIn('IMAGES/system_other.img', namelist) 406 self.assertNotIn('IMAGES/system.map', namelist) 407 self.assertNotIn('RADIO/modem.img', namelist) 408 self.assertNotIn(POSTINSTALL_CONFIG, namelist) 409 410 @test_utils.SkipIfExternalToolsUnavailable() 411 def test_GetTargetFilesZipForSecondaryImages_withoutRadioImages(self): 412 input_file = construct_target_files(secondary=True) 413 common.ZipDelete(input_file, 'RADIO/bootloader.img') 414 common.ZipDelete(input_file, 'RADIO/modem.img') 415 target_file = GetTargetFilesZipForSecondaryImages(input_file) 416 417 with zipfile.ZipFile(target_file) as verify_zip: 418 namelist = verify_zip.namelist() 419 420 self.assertIn('META/ab_partitions.txt', namelist) 421 self.assertIn('IMAGES/system.img', namelist) 422 self.assertIn(POSTINSTALL_CONFIG, namelist) 423 424 self.assertNotIn('IMAGES/boot.img', namelist) 425 self.assertNotIn('IMAGES/system_other.img', namelist) 426 self.assertNotIn('IMAGES/system.map', namelist) 427 self.assertNotIn('RADIO/bootloader.img', namelist) 428 self.assertNotIn('RADIO/modem.img', namelist) 429 430 @test_utils.SkipIfExternalToolsUnavailable() 431 def test_GetTargetFilesZipForSecondaryImages_dynamicPartitions(self): 432 input_file = construct_target_files(secondary=True) 433 misc_info = '\n'.join([ 434 'use_dynamic_partition_size=true', 435 'use_dynamic_partitions=true', 436 'dynamic_partition_list=system vendor product', 437 'super_partition_groups=google_dynamic_partitions', 438 'super_google_dynamic_partitions_group_size=4873781248', 439 'super_google_dynamic_partitions_partition_list=system vendor product', 440 ]) 441 dynamic_partitions_info = '\n'.join([ 442 'super_partition_groups=google_dynamic_partitions', 443 'super_google_dynamic_partitions_group_size=4873781248', 444 'super_google_dynamic_partitions_partition_list=system vendor product', 445 ]) 446 447 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip: 448 common.ZipWriteStr(append_zip, 'META/misc_info.txt', misc_info) 449 common.ZipWriteStr(append_zip, 'META/dynamic_partitions_info.txt', 450 dynamic_partitions_info) 451 452 target_file = GetTargetFilesZipForSecondaryImages(input_file) 453 454 with zipfile.ZipFile(target_file) as verify_zip: 455 namelist = verify_zip.namelist() 456 updated_misc_info = verify_zip.read('META/misc_info.txt').decode() 457 updated_dynamic_partitions_info = verify_zip.read( 458 'META/dynamic_partitions_info.txt').decode() 459 460 self.assertIn('META/ab_partitions.txt', namelist) 461 self.assertIn('IMAGES/system.img', namelist) 462 self.assertIn(POSTINSTALL_CONFIG, namelist) 463 self.assertIn('META/misc_info.txt', namelist) 464 self.assertIn('META/dynamic_partitions_info.txt', namelist) 465 466 self.assertNotIn('IMAGES/boot.img', namelist) 467 self.assertNotIn('IMAGES/system_other.img', namelist) 468 self.assertNotIn('IMAGES/system.map', namelist) 469 470 # Check the vendor & product are removed from the partitions list. 471 expected_misc_info = misc_info.replace('system vendor product', 472 'system') 473 expected_dynamic_partitions_info = dynamic_partitions_info.replace( 474 'system vendor product', 'system') 475 self.assertEqual(expected_misc_info, updated_misc_info) 476 self.assertEqual(expected_dynamic_partitions_info, 477 updated_dynamic_partitions_info) 478 479 @test_utils.SkipIfExternalToolsUnavailable() 480 def test_GetTargetFilesZipForPartialUpdates_singlePartition(self): 481 input_file = construct_target_files() 482 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip: 483 common.ZipWriteStr(append_zip, 'IMAGES/system.map', 'fake map') 484 485 target_file = GetTargetFilesZipForPartialUpdates(input_file, ['system']) 486 with zipfile.ZipFile(target_file) as verify_zip: 487 namelist = verify_zip.namelist() 488 ab_partitions = verify_zip.read('META/ab_partitions.txt').decode() 489 490 self.assertIn('META/ab_partitions.txt', namelist) 491 self.assertIn('META/update_engine_config.txt', namelist) 492 self.assertIn('IMAGES/system.img', namelist) 493 self.assertIn('IMAGES/system.map', namelist) 494 495 self.assertNotIn('IMAGES/boot.img', namelist) 496 self.assertNotIn('IMAGES/system_other.img', namelist) 497 self.assertNotIn('RADIO/bootloader.img', namelist) 498 self.assertNotIn('RADIO/modem.img', namelist) 499 500 self.assertEqual('system', ab_partitions) 501 502 @test_utils.SkipIfExternalToolsUnavailable() 503 def test_GetTargetFilesZipForPartialUpdates_unrecognizedPartition(self): 504 input_file = construct_target_files() 505 self.assertRaises(ValueError, GetTargetFilesZipForPartialUpdates, 506 input_file, ['product']) 507 508 @test_utils.SkipIfExternalToolsUnavailable() 509 def test_GetTargetFilesZipForPartialUpdates_dynamicPartitions(self): 510 input_file = construct_target_files(secondary=True) 511 misc_info = '\n'.join([ 512 'use_dynamic_partition_size=true', 513 'use_dynamic_partitions=true', 514 'dynamic_partition_list=system vendor product', 515 'super_partition_groups=google_dynamic_partitions', 516 'super_google_dynamic_partitions_group_size=4873781248', 517 'super_google_dynamic_partitions_partition_list=system vendor product', 518 ]) 519 dynamic_partitions_info = '\n'.join([ 520 'super_partition_groups=google_dynamic_partitions', 521 'super_google_dynamic_partitions_group_size=4873781248', 522 'super_google_dynamic_partitions_partition_list=system vendor product', 523 ]) 524 525 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip: 526 common.ZipWriteStr(append_zip, 'META/misc_info.txt', misc_info) 527 common.ZipWriteStr(append_zip, 'META/dynamic_partitions_info.txt', 528 dynamic_partitions_info) 529 530 target_file = GetTargetFilesZipForPartialUpdates(input_file, 531 ['boot', 'system']) 532 with zipfile.ZipFile(target_file) as verify_zip: 533 namelist = verify_zip.namelist() 534 ab_partitions = verify_zip.read('META/ab_partitions.txt').decode() 535 updated_misc_info = verify_zip.read('META/misc_info.txt').decode() 536 updated_dynamic_partitions_info = verify_zip.read( 537 'META/dynamic_partitions_info.txt').decode() 538 539 self.assertIn('META/ab_partitions.txt', namelist) 540 self.assertIn('IMAGES/boot.img', namelist) 541 self.assertIn('IMAGES/system.img', namelist) 542 self.assertIn('META/misc_info.txt', namelist) 543 self.assertIn('META/dynamic_partitions_info.txt', namelist) 544 545 self.assertNotIn('IMAGES/system_other.img', namelist) 546 self.assertNotIn('RADIO/bootloader.img', namelist) 547 self.assertNotIn('RADIO/modem.img', namelist) 548 549 # Check the vendor & product are removed from the partitions list. 550 expected_misc_info = misc_info.replace('system vendor product', 551 'system') 552 expected_dynamic_partitions_info = dynamic_partitions_info.replace( 553 'system vendor product', 'system') 554 self.assertEqual(expected_misc_info, updated_misc_info) 555 self.assertEqual(expected_dynamic_partitions_info, 556 updated_dynamic_partitions_info) 557 self.assertEqual('boot\nsystem', ab_partitions) 558 559 @test_utils.SkipIfExternalToolsUnavailable() 560 def test_GetTargetFilesZipWithoutPostinstallConfig(self): 561 input_file = construct_target_files() 562 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file) 563 with zipfile.ZipFile(target_file) as verify_zip: 564 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist()) 565 566 @test_utils.SkipIfExternalToolsUnavailable() 567 def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self): 568 input_file = construct_target_files() 569 common.ZipDelete(input_file, POSTINSTALL_CONFIG) 570 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file) 571 with zipfile.ZipFile(target_file) as verify_zip: 572 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist()) 573 574 @test_utils.SkipIfExternalToolsUnavailable() 575 def test_GetTargetFilesZipForCustomImagesUpdates_oemDefaultImage(self): 576 input_file = construct_target_files() 577 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip: 578 common.ZipWriteStr(append_zip, 'IMAGES/oem.img', 'oem') 579 common.ZipWriteStr(append_zip, 'IMAGES/oem_test.img', 'oem_test') 580 581 target_file = GetTargetFilesZipForCustomImagesUpdates( 582 input_file, {'oem': 'oem.img'}) 583 584 with zipfile.ZipFile(target_file) as verify_zip: 585 namelist = verify_zip.namelist() 586 ab_partitions = verify_zip.read('META/ab_partitions.txt').decode() 587 oem_image = verify_zip.read('IMAGES/oem.img').decode() 588 589 self.assertIn('META/ab_partitions.txt', namelist) 590 self.assertEqual('boot\nsystem\nvendor\nbootloader\nmodem', ab_partitions) 591 self.assertIn('IMAGES/oem.img', namelist) 592 self.assertEqual('oem', oem_image) 593 594 @test_utils.SkipIfExternalToolsUnavailable() 595 def test_GetTargetFilesZipForCustomImagesUpdates_oemTestImage(self): 596 input_file = construct_target_files() 597 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as append_zip: 598 common.ZipWriteStr(append_zip, 'IMAGES/oem.img', 'oem') 599 common.ZipWriteStr(append_zip, 'IMAGES/oem_test.img', 'oem_test') 600 601 target_file = GetTargetFilesZipForCustomImagesUpdates( 602 input_file, {'oem': 'oem_test.img'}) 603 604 with zipfile.ZipFile(target_file) as verify_zip: 605 namelist = verify_zip.namelist() 606 ab_partitions = verify_zip.read('META/ab_partitions.txt').decode() 607 oem_image = verify_zip.read('IMAGES/oem.img').decode() 608 609 self.assertIn('META/ab_partitions.txt', namelist) 610 self.assertEqual('boot\nsystem\nvendor\nbootloader\nmodem', ab_partitions) 611 self.assertIn('IMAGES/oem.img', namelist) 612 self.assertEqual('oem_test', oem_image) 613 614 def _test_FinalizeMetadata(self, large_entry=False): 615 entries = [ 616 'required-entry1', 617 'required-entry2', 618 ] 619 zip_file = PropertyFilesTest.construct_zip_package(entries) 620 # Add a large entry of 1 GiB if requested. 621 if large_entry: 622 with zipfile.ZipFile(zip_file, 'a', allowZip64=True) as zip_fp: 623 zip_fp.writestr( 624 # Using 'zoo' so that the entry stays behind others after signing. 625 'zoo', 626 'A' * 1024 * 1024 * 1024, 627 zipfile.ZIP_STORED) 628 629 metadata = ota_metadata_pb2.OtaMetadata() 630 output_file = common.MakeTempFile(suffix='.zip') 631 needed_property_files = ( 632 TestPropertyFiles(), 633 ) 634 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files) 635 self.assertIn('ota-test-property-files', metadata.property_files) 636 637 @test_utils.SkipIfExternalToolsUnavailable() 638 def test_FinalizeMetadata(self): 639 self._test_FinalizeMetadata() 640 641 @test_utils.SkipIfExternalToolsUnavailable() 642 def test_FinalizeMetadata_withNoSigning(self): 643 common.OPTIONS.no_signing = True 644 self._test_FinalizeMetadata() 645 646 @test_utils.SkipIfExternalToolsUnavailable() 647 def test_FinalizeMetadata_largeEntry(self): 648 self._test_FinalizeMetadata(large_entry=True) 649 650 @test_utils.SkipIfExternalToolsUnavailable() 651 def test_FinalizeMetadata_largeEntry_withNoSigning(self): 652 common.OPTIONS.no_signing = True 653 self._test_FinalizeMetadata(large_entry=True) 654 655 @test_utils.SkipIfExternalToolsUnavailable() 656 def test_FinalizeMetadata_insufficientSpace(self): 657 entries = [ 658 'required-entry1', 659 'required-entry2', 660 'optional-entry1', 661 'optional-entry2', 662 ] 663 zip_file = PropertyFilesTest.construct_zip_package(entries) 664 with zipfile.ZipFile(zip_file, 'a', allowZip64=True) as zip_fp: 665 zip_fp.writestr( 666 # 'foo-entry1' will appear ahead of all other entries (in alphabetical 667 # order) after the signing, which will in turn trigger the 668 # InsufficientSpaceException and an automatic retry. 669 'foo-entry1', 670 'A' * 1024 * 1024, 671 zipfile.ZIP_STORED) 672 673 metadata = ota_metadata_pb2.OtaMetadata() 674 needed_property_files = ( 675 TestPropertyFiles(), 676 ) 677 output_file = common.MakeTempFile(suffix='.zip') 678 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files) 679 self.assertIn('ota-test-property-files', metadata.property_files) 680 681 682class TestPropertyFiles(PropertyFiles): 683 """A class that extends PropertyFiles for testing purpose.""" 684 685 def __init__(self): 686 super(TestPropertyFiles, self).__init__() 687 self.name = 'ota-test-property-files' 688 self.required = ( 689 'required-entry1', 690 'required-entry2', 691 ) 692 self.optional = ( 693 'optional-entry1', 694 'optional-entry2', 695 ) 696 697 698class PropertyFilesTest(PropertyFilesTestCase): 699 700 @test_utils.SkipIfExternalToolsUnavailable() 701 def test_Compute(self): 702 entries = ( 703 'required-entry1', 704 'required-entry2', 705 ) 706 zip_file = self.construct_zip_package(entries) 707 property_files = TestPropertyFiles() 708 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 709 property_files_string = property_files.Compute(zip_fp) 710 711 tokens = self._parse_property_files_string(property_files_string) 712 self.assertEqual(4, len(tokens)) 713 self._verify_entries(zip_file, tokens, entries) 714 715 def test_Compute_withOptionalEntries(self): 716 entries = ( 717 'required-entry1', 718 'required-entry2', 719 'optional-entry1', 720 'optional-entry2', 721 ) 722 zip_file = self.construct_zip_package(entries) 723 property_files = TestPropertyFiles() 724 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 725 property_files_string = property_files.Compute(zip_fp) 726 727 tokens = self._parse_property_files_string(property_files_string) 728 self.assertEqual(6, len(tokens)) 729 self._verify_entries(zip_file, tokens, entries) 730 731 def test_Compute_missingRequiredEntry(self): 732 entries = ( 733 'required-entry2', 734 ) 735 zip_file = self.construct_zip_package(entries) 736 property_files = TestPropertyFiles() 737 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 738 self.assertRaises(KeyError, property_files.Compute, zip_fp) 739 740 @test_utils.SkipIfExternalToolsUnavailable() 741 def test_Finalize(self): 742 entries = [ 743 'required-entry1', 744 'required-entry2', 745 'META-INF/com/android/metadata', 746 'META-INF/com/android/metadata.pb', 747 ] 748 zip_file = self.construct_zip_package(entries) 749 property_files = TestPropertyFiles() 750 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 751 raw_metadata = property_files.GetPropertyFilesString( 752 zip_fp, reserve_space=False) 753 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata)) 754 tokens = self._parse_property_files_string(streaming_metadata) 755 756 self.assertEqual(4, len(tokens)) 757 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the 758 # streaming metadata. 759 entries[2] = 'metadata' 760 entries[3] = 'metadata.pb' 761 self._verify_entries(zip_file, tokens, entries) 762 763 @test_utils.SkipIfExternalToolsUnavailable() 764 def test_Finalize_assertReservedLength(self): 765 entries = ( 766 'required-entry1', 767 'required-entry2', 768 'optional-entry1', 769 'optional-entry2', 770 'META-INF/com/android/metadata', 771 'META-INF/com/android/metadata.pb', 772 ) 773 zip_file = self.construct_zip_package(entries) 774 property_files = TestPropertyFiles() 775 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 776 # First get the raw metadata string (i.e. without padding space). 777 raw_metadata = property_files.GetPropertyFilesString( 778 zip_fp, reserve_space=False) 779 raw_length = len(raw_metadata) 780 781 # Now pass in the exact expected length. 782 streaming_metadata = property_files.Finalize(zip_fp, raw_length) 783 self.assertEqual(raw_length, len(streaming_metadata)) 784 785 # Or pass in insufficient length. 786 self.assertRaises( 787 PropertyFiles.InsufficientSpaceException, 788 property_files.Finalize, 789 zip_fp, 790 raw_length - 1) 791 792 # Or pass in a much larger size. 793 streaming_metadata = property_files.Finalize( 794 zip_fp, 795 raw_length + 20) 796 self.assertEqual(raw_length + 20, len(streaming_metadata)) 797 self.assertEqual(' ' * 20, streaming_metadata[raw_length:]) 798 799 def test_Verify(self): 800 entries = ( 801 'required-entry1', 802 'required-entry2', 803 'optional-entry1', 804 'optional-entry2', 805 'META-INF/com/android/metadata', 806 'META-INF/com/android/metadata.pb', 807 ) 808 zip_file = self.construct_zip_package(entries) 809 property_files = TestPropertyFiles() 810 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 811 # First get the raw metadata string (i.e. without padding space). 812 raw_metadata = property_files.GetPropertyFilesString( 813 zip_fp, reserve_space=False) 814 815 # Should pass the test if verification passes. 816 property_files.Verify(zip_fp, raw_metadata) 817 818 # Or raise on verification failure. 819 self.assertRaises( 820 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x') 821 822 823class StreamingPropertyFilesTest(PropertyFilesTestCase): 824 """Additional validity checks specialized for StreamingPropertyFiles.""" 825 826 def test_init(self): 827 property_files = StreamingPropertyFiles() 828 self.assertEqual('ota-streaming-property-files', property_files.name) 829 self.assertEqual( 830 ( 831 'payload.bin', 832 'payload_properties.txt', 833 ), 834 property_files.required) 835 self.assertEqual( 836 ( 837 'care_map.pb', 838 'care_map.txt', 839 'compatibility.zip', 840 ), 841 property_files.optional) 842 843 def test_Compute(self): 844 entries = ( 845 'payload.bin', 846 'payload_properties.txt', 847 'care_map.txt', 848 'compatibility.zip', 849 ) 850 zip_file = self.construct_zip_package(entries) 851 property_files = StreamingPropertyFiles() 852 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 853 property_files_string = property_files.Compute(zip_fp) 854 855 tokens = self._parse_property_files_string(property_files_string) 856 self.assertEqual(6, len(tokens)) 857 self._verify_entries(zip_file, tokens, entries) 858 859 def test_Finalize(self): 860 entries = [ 861 'payload.bin', 862 'payload_properties.txt', 863 'care_map.txt', 864 'compatibility.zip', 865 'META-INF/com/android/metadata', 866 'META-INF/com/android/metadata.pb', 867 ] 868 zip_file = self.construct_zip_package(entries) 869 property_files = StreamingPropertyFiles() 870 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 871 raw_metadata = property_files.GetPropertyFilesString( 872 zip_fp, reserve_space=False) 873 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata)) 874 tokens = self._parse_property_files_string(streaming_metadata) 875 876 self.assertEqual(6, len(tokens)) 877 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the 878 # streaming metadata. 879 entries[4] = 'metadata' 880 entries[5] = 'metadata.pb' 881 self._verify_entries(zip_file, tokens, entries) 882 883 def test_Verify(self): 884 entries = ( 885 'payload.bin', 886 'payload_properties.txt', 887 'care_map.txt', 888 'compatibility.zip', 889 'META-INF/com/android/metadata', 890 'META-INF/com/android/metadata.pb', 891 ) 892 zip_file = self.construct_zip_package(entries) 893 property_files = StreamingPropertyFiles() 894 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 895 # First get the raw metadata string (i.e. without padding space). 896 raw_metadata = property_files.GetPropertyFilesString( 897 zip_fp, reserve_space=False) 898 899 # Should pass the test if verification passes. 900 property_files.Verify(zip_fp, raw_metadata) 901 902 # Or raise on verification failure. 903 self.assertRaises( 904 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x') 905 906 907class AbOtaPropertyFilesTest(PropertyFilesTestCase): 908 """Additional validity checks specialized for AbOtaPropertyFiles.""" 909 910 # The size for payload and metadata signature size. 911 SIGNATURE_SIZE = 256 912 913 def setUp(self): 914 self.testdata_dir = test_utils.get_testdata_dir() 915 self.assertTrue(os.path.exists(self.testdata_dir)) 916 917 common.OPTIONS.wipe_user_data = False 918 common.OPTIONS.payload_signer = None 919 common.OPTIONS.payload_signer_args = None 920 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') 921 common.OPTIONS.key_passwords = { 922 common.OPTIONS.package_key: None, 923 } 924 925 def test_init(self): 926 property_files = AbOtaPropertyFiles() 927 self.assertEqual('ota-property-files', property_files.name) 928 self.assertEqual( 929 ( 930 'payload.bin', 931 'payload_properties.txt', 932 ), 933 property_files.required) 934 self.assertEqual( 935 ( 936 'care_map.pb', 937 'care_map.txt', 938 'compatibility.zip', 939 ), 940 property_files.optional) 941 942 @test_utils.SkipIfExternalToolsUnavailable() 943 def test_GetPayloadMetadataOffsetAndSize(self): 944 target_file = construct_target_files() 945 payload = Payload() 946 payload.Generate(target_file) 947 948 payload_signer = PayloadSigner() 949 payload.Sign(payload_signer) 950 951 output_file = common.MakeTempFile(suffix='.zip') 952 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 953 payload.WriteToZip(output_zip) 954 955 # Find out the payload metadata offset and size. 956 property_files = AbOtaPropertyFiles() 957 with zipfile.ZipFile(output_file) as input_zip: 958 # pylint: disable=protected-access 959 payload_offset, metadata_total = ( 960 property_files._GetPayloadMetadataOffsetAndSize(input_zip)) 961 962 # The signature proto has the following format (details in 963 # /platform/system/update_engine/update_metadata.proto): 964 # message Signature { 965 # optional uint32 version = 1; 966 # optional bytes data = 2; 967 # optional fixed32 unpadded_signature_size = 3; 968 # } 969 # 970 # According to the protobuf encoding, the tail of the signature message will 971 # be [signature string(256 bytes) + encoding of the fixed32 number 256]. And 972 # 256 is encoded as 'x1d\x00\x01\x00\x00': 973 # [3 (field number) << 3 | 5 (type) + byte reverse of 0x100 (256)]. 974 # Details in (https://developers.google.com/protocol-buffers/docs/encoding) 975 signature_tail_length = self.SIGNATURE_SIZE + 5 976 self.assertGreater(metadata_total, signature_tail_length) 977 with open(output_file, 'rb') as verify_fp: 978 verify_fp.seek(payload_offset + metadata_total - signature_tail_length) 979 metadata_signature_proto_tail = verify_fp.read(signature_tail_length) 980 981 self.assertEqual(b'\x1d\x00\x01\x00\x00', 982 metadata_signature_proto_tail[-5:]) 983 metadata_signature = metadata_signature_proto_tail[:-5] 984 985 # Now we extract the metadata hash via brillo_update_payload script, which 986 # will serve as the oracle result. 987 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin") 988 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin") 989 cmd = ['brillo_update_payload', 'hash', 990 '--unsigned_payload', payload.payload_file, 991 '--signature_size', str(self.SIGNATURE_SIZE), 992 '--metadata_hash_file', metadata_sig_file, 993 '--payload_hash_file', payload_sig_file] 994 proc = common.Run(cmd) 995 stdoutdata, _ = proc.communicate() 996 self.assertEqual( 997 0, proc.returncode, 998 'Failed to run brillo_update_payload:\n{}'.format(stdoutdata)) 999 1000 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file) 1001 1002 # Finally we can compare the two signatures. 1003 with open(signed_metadata_sig_file, 'rb') as verify_fp: 1004 self.assertEqual(verify_fp.read(), metadata_signature) 1005 1006 @staticmethod 1007 def construct_zip_package_withValidPayload(with_metadata=False): 1008 # Cannot use construct_zip_package() since we need a "valid" payload.bin. 1009 target_file = construct_target_files() 1010 payload = Payload() 1011 payload.Generate(target_file) 1012 1013 payload_signer = PayloadSigner() 1014 payload.Sign(payload_signer) 1015 1016 zip_file = common.MakeTempFile(suffix='.zip') 1017 with zipfile.ZipFile(zip_file, 'w', allowZip64=True) as zip_fp: 1018 # 'payload.bin', 1019 payload.WriteToZip(zip_fp) 1020 1021 # Other entries. 1022 entries = ['care_map.txt', 'compatibility.zip'] 1023 1024 # Put META-INF/com/android/metadata if needed. 1025 if with_metadata: 1026 entries.append('META-INF/com/android/metadata') 1027 entries.append('META-INF/com/android/metadata.pb') 1028 1029 for entry in entries: 1030 zip_fp.writestr( 1031 entry, entry.replace('.', '-').upper(), zipfile.ZIP_STORED) 1032 1033 return zip_file 1034 1035 @test_utils.SkipIfExternalToolsUnavailable() 1036 def test_Compute(self): 1037 zip_file = self.construct_zip_package_withValidPayload() 1038 property_files = AbOtaPropertyFiles() 1039 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 1040 property_files_string = property_files.Compute(zip_fp) 1041 1042 tokens = self._parse_property_files_string(property_files_string) 1043 # "7" indcludes the four entries above, two metadata entries, and one entry 1044 # for payload-metadata.bin. 1045 self.assertEqual(7, len(tokens)) 1046 self._verify_entries( 1047 zip_file, tokens, ('care_map.txt', 'compatibility.zip')) 1048 1049 @test_utils.SkipIfExternalToolsUnavailable() 1050 def test_Finalize(self): 1051 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True) 1052 property_files = AbOtaPropertyFiles() 1053 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 1054 raw_metadata = property_files.GetPropertyFilesString( 1055 zip_fp, reserve_space=False) 1056 property_files_string = property_files.Finalize( 1057 zip_fp, len(raw_metadata)) 1058 1059 tokens = self._parse_property_files_string(property_files_string) 1060 # "7" includes the four entries above, two metadata entries, and one entry 1061 # for payload-metadata.bin. 1062 self.assertEqual(7, len(tokens)) 1063 self._verify_entries( 1064 zip_file, tokens, ('care_map.txt', 'compatibility.zip')) 1065 1066 @test_utils.SkipIfExternalToolsUnavailable() 1067 def test_Verify(self): 1068 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True) 1069 property_files = AbOtaPropertyFiles() 1070 with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_fp: 1071 raw_metadata = property_files.GetPropertyFilesString( 1072 zip_fp, reserve_space=False) 1073 1074 property_files.Verify(zip_fp, raw_metadata) 1075 1076 1077class PayloadSignerTest(test_utils.ReleaseToolsTestCase): 1078 1079 SIGFILE = 'sigfile.bin' 1080 SIGNED_SIGFILE = 'signed-sigfile.bin' 1081 1082 def setUp(self): 1083 self.testdata_dir = test_utils.get_testdata_dir() 1084 self.assertTrue(os.path.exists(self.testdata_dir)) 1085 1086 common.OPTIONS.payload_signer = None 1087 common.OPTIONS.payload_signer_args = [] 1088 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') 1089 common.OPTIONS.key_passwords = { 1090 common.OPTIONS.package_key: None, 1091 } 1092 1093 def _assertFilesEqual(self, file1, file2): 1094 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2: 1095 self.assertEqual(fp1.read(), fp2.read()) 1096 1097 @test_utils.SkipIfExternalToolsUnavailable() 1098 def test_init(self): 1099 payload_signer = PayloadSigner() 1100 self.assertEqual('openssl', payload_signer.signer) 1101 self.assertEqual(256, payload_signer.maximum_signature_size) 1102 1103 @test_utils.SkipIfExternalToolsUnavailable() 1104 def test_init_withPassword(self): 1105 common.OPTIONS.package_key = os.path.join( 1106 self.testdata_dir, 'testkey_with_passwd') 1107 common.OPTIONS.key_passwords = { 1108 common.OPTIONS.package_key: 'foo', 1109 } 1110 payload_signer = PayloadSigner() 1111 self.assertEqual('openssl', payload_signer.signer) 1112 1113 def test_init_withExternalSigner(self): 1114 common.OPTIONS.payload_signer = 'abc' 1115 common.OPTIONS.payload_signer_args = ['arg1', 'arg2'] 1116 common.OPTIONS.payload_signer_maximum_signature_size = '512' 1117 payload_signer = PayloadSigner() 1118 self.assertEqual('abc', payload_signer.signer) 1119 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args) 1120 self.assertEqual(512, payload_signer.maximum_signature_size) 1121 1122 @test_utils.SkipIfExternalToolsUnavailable() 1123 def test_GetMaximumSignatureSizeInBytes_512Bytes(self): 1124 signing_key = os.path.join(self.testdata_dir, 'testkey_RSA4096.key') 1125 # pylint: disable=protected-access 1126 signature_size = PayloadSigner._GetMaximumSignatureSizeInBytes(signing_key) 1127 self.assertEqual(512, signature_size) 1128 1129 @test_utils.SkipIfExternalToolsUnavailable() 1130 def test_GetMaximumSignatureSizeInBytes_ECKey(self): 1131 signing_key = os.path.join(self.testdata_dir, 'testkey_EC.key') 1132 # pylint: disable=protected-access 1133 signature_size = PayloadSigner._GetMaximumSignatureSizeInBytes(signing_key) 1134 self.assertEqual(72, signature_size) 1135 1136 @test_utils.SkipIfExternalToolsUnavailable() 1137 def test_Sign(self): 1138 payload_signer = PayloadSigner() 1139 input_file = os.path.join(self.testdata_dir, self.SIGFILE) 1140 signed_file = payload_signer.Sign(input_file) 1141 1142 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) 1143 self._assertFilesEqual(verify_file, signed_file) 1144 1145 def test_Sign_withExternalSigner_openssl(self): 1146 """Uses openssl as the external payload signer.""" 1147 common.OPTIONS.payload_signer = 'openssl' 1148 common.OPTIONS.payload_signer_args = [ 1149 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey', 1150 os.path.join(self.testdata_dir, 'testkey.pk8'), 1151 '-pkeyopt', 'digest:sha256'] 1152 payload_signer = PayloadSigner() 1153 input_file = os.path.join(self.testdata_dir, self.SIGFILE) 1154 signed_file = payload_signer.Sign(input_file) 1155 1156 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) 1157 self._assertFilesEqual(verify_file, signed_file) 1158 1159 def test_Sign_withExternalSigner_script(self): 1160 """Uses testdata/payload_signer.sh as the external payload signer.""" 1161 common.OPTIONS.payload_signer = os.path.join( 1162 self.testdata_dir, 'payload_signer.sh') 1163 os.chmod(common.OPTIONS.payload_signer, 0o700) 1164 common.OPTIONS.payload_signer_args = [ 1165 os.path.join(self.testdata_dir, 'testkey.pk8')] 1166 payload_signer = PayloadSigner() 1167 input_file = os.path.join(self.testdata_dir, self.SIGFILE) 1168 signed_file = payload_signer.Sign(input_file) 1169 1170 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) 1171 self._assertFilesEqual(verify_file, signed_file) 1172 1173 1174class PayloadTest(test_utils.ReleaseToolsTestCase): 1175 1176 def setUp(self): 1177 self.testdata_dir = test_utils.get_testdata_dir() 1178 self.assertTrue(os.path.exists(self.testdata_dir)) 1179 1180 common.OPTIONS.wipe_user_data = False 1181 common.OPTIONS.payload_signer = None 1182 common.OPTIONS.payload_signer_args = None 1183 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') 1184 common.OPTIONS.key_passwords = { 1185 common.OPTIONS.package_key: None, 1186 } 1187 1188 @staticmethod 1189 def _create_payload_full(secondary=False): 1190 target_file = construct_target_files(secondary) 1191 payload = Payload(secondary) 1192 payload.Generate(target_file) 1193 return payload 1194 1195 @staticmethod 1196 def _create_payload_incremental(): 1197 target_file = construct_target_files() 1198 source_file = construct_target_files() 1199 payload = Payload() 1200 payload.Generate(target_file, source_file) 1201 return payload 1202 1203 @test_utils.SkipIfExternalToolsUnavailable() 1204 def test_Generate_full(self): 1205 payload = self._create_payload_full() 1206 self.assertTrue(os.path.exists(payload.payload_file)) 1207 1208 @test_utils.SkipIfExternalToolsUnavailable() 1209 def test_Generate_incremental(self): 1210 payload = self._create_payload_incremental() 1211 self.assertTrue(os.path.exists(payload.payload_file)) 1212 1213 @test_utils.SkipIfExternalToolsUnavailable() 1214 def test_Generate_additionalArgs(self): 1215 target_file = construct_target_files() 1216 source_file = construct_target_files() 1217 payload = Payload() 1218 # This should work the same as calling payload.Generate(target_file, 1219 # source_file). 1220 payload.Generate( 1221 target_file, additional_args=["--source_image", source_file]) 1222 self.assertTrue(os.path.exists(payload.payload_file)) 1223 1224 @test_utils.SkipIfExternalToolsUnavailable() 1225 def test_Generate_invalidInput(self): 1226 target_file = construct_target_files() 1227 common.ZipDelete(target_file, 'IMAGES/vendor.img') 1228 payload = Payload() 1229 self.assertRaises(common.ExternalError, payload.Generate, target_file) 1230 1231 @test_utils.SkipIfExternalToolsUnavailable() 1232 def test_Sign_full(self): 1233 payload = self._create_payload_full() 1234 payload.Sign(PayloadSigner()) 1235 1236 output_file = common.MakeTempFile(suffix='.zip') 1237 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 1238 payload.WriteToZip(output_zip) 1239 1240 import check_ota_package_signature 1241 check_ota_package_signature.VerifyAbOtaPayload( 1242 os.path.join(self.testdata_dir, 'testkey.x509.pem'), 1243 output_file) 1244 1245 @test_utils.SkipIfExternalToolsUnavailable() 1246 def test_Sign_incremental(self): 1247 payload = self._create_payload_incremental() 1248 payload.Sign(PayloadSigner()) 1249 1250 output_file = common.MakeTempFile(suffix='.zip') 1251 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 1252 payload.WriteToZip(output_zip) 1253 1254 import check_ota_package_signature 1255 check_ota_package_signature.VerifyAbOtaPayload( 1256 os.path.join(self.testdata_dir, 'testkey.x509.pem'), 1257 output_file) 1258 1259 @test_utils.SkipIfExternalToolsUnavailable() 1260 def test_Sign_withDataWipe(self): 1261 common.OPTIONS.wipe_user_data = True 1262 payload = self._create_payload_full() 1263 payload.Sign(PayloadSigner()) 1264 1265 with open(payload.payload_properties) as properties_fp: 1266 self.assertIn("POWERWASH=1", properties_fp.read()) 1267 1268 @test_utils.SkipIfExternalToolsUnavailable() 1269 def test_Sign_secondary(self): 1270 payload = self._create_payload_full(secondary=True) 1271 payload.Sign(PayloadSigner()) 1272 1273 with open(payload.payload_properties) as properties_fp: 1274 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read()) 1275 1276 @test_utils.SkipIfExternalToolsUnavailable() 1277 def test_Sign_badSigner(self): 1278 """Tests that signing failure can be captured.""" 1279 payload = self._create_payload_full() 1280 payload_signer = PayloadSigner() 1281 payload_signer.signer_args.append('bad-option') 1282 self.assertRaises(common.ExternalError, payload.Sign, payload_signer) 1283 1284 @test_utils.SkipIfExternalToolsUnavailable() 1285 def test_WriteToZip(self): 1286 payload = self._create_payload_full() 1287 payload.Sign(PayloadSigner()) 1288 1289 output_file = common.MakeTempFile(suffix='.zip') 1290 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 1291 payload.WriteToZip(output_zip) 1292 1293 with zipfile.ZipFile(output_file) as verify_zip: 1294 # First make sure we have the essential entries. 1295 namelist = verify_zip.namelist() 1296 self.assertIn(Payload.PAYLOAD_BIN, namelist) 1297 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist) 1298 1299 # Then assert these entries are stored. 1300 for entry_info in verify_zip.infolist(): 1301 if entry_info.filename not in (Payload.PAYLOAD_BIN, 1302 Payload.PAYLOAD_PROPERTIES_TXT): 1303 continue 1304 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type) 1305 1306 @test_utils.SkipIfExternalToolsUnavailable() 1307 def test_WriteToZip_unsignedPayload(self): 1308 """Unsigned payloads should not be allowed to be written to zip.""" 1309 payload = self._create_payload_full() 1310 1311 output_file = common.MakeTempFile(suffix='.zip') 1312 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 1313 self.assertRaises(AssertionError, payload.WriteToZip, output_zip) 1314 1315 # Also test with incremental payload. 1316 payload = self._create_payload_incremental() 1317 1318 output_file = common.MakeTempFile(suffix='.zip') 1319 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 1320 self.assertRaises(AssertionError, payload.WriteToZip, output_zip) 1321 1322 @test_utils.SkipIfExternalToolsUnavailable() 1323 def test_WriteToZip_secondary(self): 1324 payload = self._create_payload_full(secondary=True) 1325 payload.Sign(PayloadSigner()) 1326 1327 output_file = common.MakeTempFile(suffix='.zip') 1328 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 1329 payload.WriteToZip(output_zip) 1330 1331 with zipfile.ZipFile(output_file) as verify_zip: 1332 # First make sure we have the essential entries. 1333 namelist = verify_zip.namelist() 1334 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist) 1335 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist) 1336 1337 # Then assert these entries are stored. 1338 for entry_info in verify_zip.infolist(): 1339 if entry_info.filename not in ( 1340 Payload.SECONDARY_PAYLOAD_BIN, 1341 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT): 1342 continue 1343 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type) 1344 1345 1346class RuntimeFingerprintTest(test_utils.ReleaseToolsTestCase): 1347 MISC_INFO = [ 1348 'recovery_api_version=3', 1349 'fstab_version=2', 1350 'recovery_as_boot=true', 1351 'ab_update=true', 1352 ] 1353 1354 BUILD_PROP = [ 1355 'ro.build.id=build-id', 1356 'ro.build.version.incremental=version-incremental', 1357 'ro.build.type=build-type', 1358 'ro.build.tags=build-tags', 1359 'ro.build.version.release=version-release', 1360 'ro.build.version.release_or_codename=version-release', 1361 'ro.build.version.sdk=30', 1362 'ro.build.version.security_patch=2020', 1363 'ro.build.date.utc=12345678', 1364 'ro.system.build.version.release=version-release', 1365 'ro.system.build.id=build-id', 1366 'ro.system.build.version.incremental=version-incremental', 1367 'ro.system.build.type=build-type', 1368 'ro.system.build.tags=build-tags', 1369 'ro.system.build.version.sdk=30', 1370 'ro.system.build.version.security_patch=2020', 1371 'ro.system.build.date.utc=12345678', 1372 'ro.product.system.brand=generic', 1373 'ro.product.system.name=generic', 1374 'ro.product.system.device=generic', 1375 ] 1376 1377 VENDOR_BUILD_PROP = [ 1378 'ro.vendor.build.version.release=version-release', 1379 'ro.vendor.build.id=build-id', 1380 'ro.vendor.build.version.incremental=version-incremental', 1381 'ro.vendor.build.type=build-type', 1382 'ro.vendor.build.tags=build-tags', 1383 'ro.vendor.build.version.sdk=30', 1384 'ro.vendor.build.version.security_patch=2020', 1385 'ro.vendor.build.date.utc=12345678', 1386 'ro.product.vendor.brand=vendor-product-brand', 1387 'ro.product.vendor.name=vendor-product-name', 1388 'ro.product.vendor.device=vendor-product-device' 1389 ] 1390 1391 def setUp(self): 1392 common.OPTIONS.oem_dicts = None 1393 self.test_dir = common.MakeTempDir() 1394 self.writeFiles({'META/misc_info.txt': '\n'.join(self.MISC_INFO)}, 1395 self.test_dir) 1396 1397 def writeFiles(self, contents_dict, out_dir): 1398 for path, content in contents_dict.items(): 1399 abs_path = os.path.join(out_dir, path) 1400 dir_name = os.path.dirname(abs_path) 1401 if not os.path.exists(dir_name): 1402 os.makedirs(dir_name) 1403 with open(abs_path, 'w') as f: 1404 f.write(content) 1405 1406 @staticmethod 1407 def constructFingerprint(prefix): 1408 return '{}:version-release/build-id/version-incremental:' \ 1409 'build-type/build-tags'.format(prefix) 1410 1411 def test_CalculatePossibleFingerprints_no_dynamic_fingerprint(self): 1412 build_prop = copy.deepcopy(self.BUILD_PROP) 1413 build_prop.extend([ 1414 'ro.product.brand=product-brand', 1415 'ro.product.name=product-name', 1416 'ro.product.device=product-device', 1417 ]) 1418 self.writeFiles({ 1419 'SYSTEM/build.prop': '\n'.join(build_prop), 1420 'VENDOR/build.prop': '\n'.join(self.VENDOR_BUILD_PROP), 1421 }, self.test_dir) 1422 1423 build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir)) 1424 expected = ({'product-device'}, 1425 {self.constructFingerprint( 1426 'product-brand/product-name/product-device')}) 1427 self.assertEqual(expected, 1428 CalculateRuntimeDevicesAndFingerprints(build_info, {})) 1429 1430 def test_CalculatePossibleFingerprints_single_override(self): 1431 vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP) 1432 vendor_build_prop.extend([ 1433 'import /vendor/etc/build_${ro.boot.sku_name}.prop', 1434 ]) 1435 self.writeFiles({ 1436 'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP), 1437 'VENDOR/build.prop': '\n'.join(vendor_build_prop), 1438 'VENDOR/etc/build_std.prop': 1439 'ro.product.vendor.name=vendor-product-std', 1440 'VENDOR/etc/build_pro.prop': 1441 'ro.product.vendor.name=vendor-product-pro', 1442 }, self.test_dir) 1443 1444 build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir)) 1445 boot_variable_values = {'ro.boot.sku_name': ['std', 'pro']} 1446 1447 expected = ({'vendor-product-device'}, { 1448 self.constructFingerprint( 1449 'vendor-product-brand/vendor-product-name/vendor-product-device'), 1450 self.constructFingerprint( 1451 'vendor-product-brand/vendor-product-std/vendor-product-device'), 1452 self.constructFingerprint( 1453 'vendor-product-brand/vendor-product-pro/vendor-product-device'), 1454 }) 1455 self.assertEqual( 1456 expected, CalculateRuntimeDevicesAndFingerprints( 1457 build_info, boot_variable_values)) 1458 1459 def test_CalculatePossibleFingerprints_multiple_overrides(self): 1460 vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP) 1461 vendor_build_prop.extend([ 1462 'import /vendor/etc/build_${ro.boot.sku_name}.prop', 1463 'import /vendor/etc/build_${ro.boot.device_name}.prop', 1464 ]) 1465 self.writeFiles({ 1466 'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP), 1467 'VENDOR/build.prop': '\n'.join(vendor_build_prop), 1468 'VENDOR/etc/build_std.prop': 1469 'ro.product.vendor.name=vendor-product-std', 1470 'VENDOR/etc/build_product1.prop': 1471 'ro.product.vendor.device=vendor-device-product1', 1472 'VENDOR/etc/build_pro.prop': 1473 'ro.product.vendor.name=vendor-product-pro', 1474 'VENDOR/etc/build_product2.prop': 1475 'ro.product.vendor.device=vendor-device-product2', 1476 }, self.test_dir) 1477 1478 build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir)) 1479 boot_variable_values = { 1480 'ro.boot.sku_name': ['std', 'pro'], 1481 'ro.boot.device_name': ['product1', 'product2'], 1482 } 1483 1484 expected_devices = {'vendor-product-device', 'vendor-device-product1', 1485 'vendor-device-product2'} 1486 expected_fingerprints = { 1487 self.constructFingerprint( 1488 'vendor-product-brand/vendor-product-name/vendor-product-device'), 1489 self.constructFingerprint( 1490 'vendor-product-brand/vendor-product-std/vendor-device-product1'), 1491 self.constructFingerprint( 1492 'vendor-product-brand/vendor-product-pro/vendor-device-product1'), 1493 self.constructFingerprint( 1494 'vendor-product-brand/vendor-product-std/vendor-device-product2'), 1495 self.constructFingerprint( 1496 'vendor-product-brand/vendor-product-pro/vendor-device-product2') 1497 } 1498 self.assertEqual((expected_devices, expected_fingerprints), 1499 CalculateRuntimeDevicesAndFingerprints( 1500 build_info, boot_variable_values)) 1501 1502 def test_GetPackageMetadata_full_package(self): 1503 vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP) 1504 vendor_build_prop.extend([ 1505 'import /vendor/etc/build_${ro.boot.sku_name}.prop', 1506 ]) 1507 self.writeFiles({ 1508 'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP), 1509 'VENDOR/build.prop': '\n'.join(vendor_build_prop), 1510 'VENDOR/etc/build_std.prop': 1511 'ro.product.vendor.name=vendor-product-std', 1512 'VENDOR/etc/build_pro.prop': 1513 'ro.product.vendor.name=vendor-product-pro', 1514 AB_PARTITIONS: '\n'.join(['system', 'vendor']), 1515 }, self.test_dir) 1516 1517 common.OPTIONS.boot_variable_file = common.MakeTempFile() 1518 with open(common.OPTIONS.boot_variable_file, 'w') as f: 1519 f.write('ro.boot.sku_name=std,pro') 1520 1521 build_info = common.BuildInfo(common.LoadInfoDict(self.test_dir)) 1522 metadata_dict = BuildLegacyOtaMetadata(GetPackageMetadata(build_info)) 1523 self.assertEqual('vendor-product-device', metadata_dict['pre-device']) 1524 fingerprints = [ 1525 self.constructFingerprint( 1526 'vendor-product-brand/vendor-product-name/vendor-product-device'), 1527 self.constructFingerprint( 1528 'vendor-product-brand/vendor-product-pro/vendor-product-device'), 1529 self.constructFingerprint( 1530 'vendor-product-brand/vendor-product-std/vendor-product-device'), 1531 ] 1532 self.assertEqual('|'.join(fingerprints), metadata_dict['post-build']) 1533 1534 def CheckMetadataEqual(self, metadata_dict, metadata_proto): 1535 post_build = metadata_proto.postcondition 1536 self.assertEqual('|'.join(post_build.build), 1537 metadata_dict['post-build']) 1538 self.assertEqual(post_build.build_incremental, 1539 metadata_dict['post-build-incremental']) 1540 self.assertEqual(post_build.sdk_level, 1541 metadata_dict['post-sdk-level']) 1542 self.assertEqual(post_build.security_patch_level, 1543 metadata_dict['post-security-patch-level']) 1544 1545 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB: 1546 ota_type = 'AB' 1547 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK: 1548 ota_type = 'BLOCK' 1549 else: 1550 ota_type = '' 1551 self.assertEqual(ota_type, metadata_dict['ota-type']) 1552 self.assertEqual(metadata_proto.wipe, 1553 metadata_dict.get('ota-wipe') == 'yes') 1554 self.assertEqual(metadata_proto.required_cache, 1555 int(metadata_dict.get('ota-required-cache', 0))) 1556 self.assertEqual(metadata_proto.retrofit_dynamic_partitions, 1557 metadata_dict.get( 1558 'ota-retrofit-dynamic-partitions') == 'yes') 1559 1560 def test_GetPackageMetadata_incremental_package(self): 1561 vendor_build_prop = copy.deepcopy(self.VENDOR_BUILD_PROP) 1562 vendor_build_prop.extend([ 1563 'import /vendor/etc/build_${ro.boot.sku_name}.prop', 1564 ]) 1565 self.writeFiles({ 1566 'META/misc_info.txt': '\n'.join(self.MISC_INFO), 1567 'META/ab_partitions.txt': '\n'.join(['system', 'vendor', 'product']), 1568 'SYSTEM/build.prop': '\n'.join(self.BUILD_PROP), 1569 'VENDOR/build.prop': '\n'.join(vendor_build_prop), 1570 'VENDOR/etc/build_std.prop': 1571 'ro.product.vendor.device=vendor-device-std', 1572 'VENDOR/etc/build_pro.prop': 1573 'ro.product.vendor.device=vendor-device-pro', 1574 }, self.test_dir) 1575 1576 common.OPTIONS.boot_variable_file = common.MakeTempFile() 1577 with open(common.OPTIONS.boot_variable_file, 'w') as f: 1578 f.write('ro.boot.sku_name=std,pro') 1579 1580 source_dir = common.MakeTempDir() 1581 source_build_prop = [ 1582 'ro.build.version.release=source-version-release', 1583 'ro.build.id=source-build-id', 1584 'ro.build.version.incremental=source-version-incremental', 1585 'ro.build.type=build-type', 1586 'ro.build.tags=build-tags', 1587 'ro.build.version.sdk=29', 1588 'ro.build.version.security_patch=2020', 1589 'ro.build.date.utc=12340000', 1590 'ro.system.build.version.release=source-version-release', 1591 'ro.system.build.id=source-build-id', 1592 'ro.system.build.version.incremental=source-version-incremental', 1593 'ro.system.build.type=build-type', 1594 'ro.system.build.tags=build-tags', 1595 'ro.system.build.version.sdk=29', 1596 'ro.system.build.version.security_patch=2020', 1597 'ro.system.build.date.utc=12340000', 1598 'ro.product.system.brand=generic', 1599 'ro.product.system.name=generic', 1600 'ro.product.system.device=generic', 1601 ] 1602 self.writeFiles({ 1603 'META/misc_info.txt': '\n'.join(self.MISC_INFO), 1604 'META/ab_partitions.txt': '\n'.join(['system', 'vendor', 'product']), 1605 'SYSTEM/build.prop': '\n'.join(source_build_prop), 1606 'VENDOR/build.prop': '\n'.join(vendor_build_prop), 1607 'VENDOR/etc/build_std.prop': 1608 'ro.product.vendor.device=vendor-device-std', 1609 'VENDOR/etc/build_pro.prop': 1610 'ro.product.vendor.device=vendor-device-pro', 1611 }, source_dir) 1612 common.OPTIONS.incremental_source = source_dir 1613 1614 target_info = common.BuildInfo(common.LoadInfoDict(self.test_dir)) 1615 source_info = common.BuildInfo(common.LoadInfoDict(source_dir)) 1616 1617 metadata_proto = GetPackageMetadata(target_info, source_info) 1618 metadata_dict = BuildLegacyOtaMetadata(metadata_proto) 1619 self.assertEqual( 1620 'vendor-device-pro|vendor-device-std|vendor-product-device', 1621 metadata_dict['pre-device']) 1622 source_suffix = ':source-version-release/source-build-id/' \ 1623 'source-version-incremental:build-type/build-tags' 1624 pre_fingerprints = [ 1625 'vendor-product-brand/vendor-product-name/vendor-device-pro' 1626 '{}'.format(source_suffix), 1627 'vendor-product-brand/vendor-product-name/vendor-device-std' 1628 '{}'.format(source_suffix), 1629 'vendor-product-brand/vendor-product-name/vendor-product-device' 1630 '{}'.format(source_suffix), 1631 ] 1632 self.assertEqual('|'.join(pre_fingerprints), metadata_dict['pre-build']) 1633 1634 post_fingerprints = [ 1635 self.constructFingerprint( 1636 'vendor-product-brand/vendor-product-name/vendor-device-pro'), 1637 self.constructFingerprint( 1638 'vendor-product-brand/vendor-product-name/vendor-device-std'), 1639 self.constructFingerprint( 1640 'vendor-product-brand/vendor-product-name/vendor-product-device'), 1641 ] 1642 self.assertEqual('|'.join(post_fingerprints), metadata_dict['post-build']) 1643 1644 self.CheckMetadataEqual(metadata_dict, metadata_proto) 1645 1646 pre_partition_states = metadata_proto.precondition.partition_state 1647 self.assertEqual(2, len(pre_partition_states)) 1648 self.assertEqual('system', pre_partition_states[0].partition_name) 1649 self.assertEqual(['generic'], pre_partition_states[0].device) 1650 self.assertEqual(['generic/generic/generic{}'.format(source_suffix)], 1651 pre_partition_states[0].build) 1652 1653 self.assertEqual('vendor', pre_partition_states[1].partition_name) 1654 self.assertEqual(['vendor-device-pro', 'vendor-device-std', 1655 'vendor-product-device'], pre_partition_states[1].device) 1656 vendor_fingerprints = post_fingerprints 1657 self.assertEqual(vendor_fingerprints, pre_partition_states[1].build) 1658 1659 post_partition_states = metadata_proto.postcondition.partition_state 1660 self.assertEqual(2, len(post_partition_states)) 1661 self.assertEqual('system', post_partition_states[0].partition_name) 1662 self.assertEqual(['generic'], post_partition_states[0].device) 1663 self.assertEqual([self.constructFingerprint('generic/generic/generic')], 1664 post_partition_states[0].build) 1665 1666 self.assertEqual('vendor', post_partition_states[1].partition_name) 1667 self.assertEqual(['vendor-device-pro', 'vendor-device-std', 1668 'vendor-product-device'], post_partition_states[1].device) 1669 self.assertEqual(vendor_fingerprints, post_partition_states[1].build) 1670