blob: d7cace8c2223de847c90d9b7d827d5df690ca1e5 [file] [log] [blame]
Tao Bao481bab82017-12-21 11:23:09 -08001#
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
Tao Baoc7b403a2018-01-30 18:19:04 -080018import os
Tao Baofabe0832018-01-17 15:52:28 -080019import os.path
Tao Baob6304672018-03-08 16:28:33 -080020import subprocess
Tao Bao481bab82017-12-21 11:23:09 -080021import unittest
Tao Baoc7b403a2018-01-30 18:19:04 -080022import zipfile
Tao Bao481bab82017-12-21 11:23:09 -080023
24import common
Tao Bao04e1f012018-02-04 12:13:35 -080025import test_utils
Tao Bao481bab82017-12-21 11:23:09 -080026from ota_from_target_files import (
Tao Bao3bf8c652018-03-16 12:59:42 -070027 _LoadOemDicts, AbOtaPropertyFiles, BuildInfo, FinalizeMetadata,
28 GetPackageMetadata, GetTargetFilesZipForSecondaryImages,
Tao Baoc0746f42018-02-21 13:17:22 -080029 GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles,
Tao Bao69203522018-03-08 16:09:01 -080030 Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles,
31 StreamingPropertyFiles, WriteFingerprintAssertion)
Tao Baofabe0832018-01-17 15:52:28 -080032
33
Tao Baof7140c02018-01-30 17:09:24 -080034def construct_target_files(secondary=False):
35 """Returns a target-files.zip file for generating OTA packages."""
36 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
37 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
38 # META/update_engine_config.txt
39 target_files_zip.writestr(
40 'META/update_engine_config.txt',
41 "PAYLOAD_MAJOR_VERSION=2\nPAYLOAD_MINOR_VERSION=4\n")
42
Tao Bao15a146a2018-02-21 16:06:59 -080043 # META/postinstall_config.txt
44 target_files_zip.writestr(
45 POSTINSTALL_CONFIG,
46 '\n'.join([
47 "RUN_POSTINSTALL_system=true",
48 "POSTINSTALL_PATH_system=system/bin/otapreopt_script",
49 "FILESYSTEM_TYPE_system=ext4",
50 "POSTINSTALL_OPTIONAL_system=true",
51 ]))
52
Tao Baof7140c02018-01-30 17:09:24 -080053 # META/ab_partitions.txt
54 ab_partitions = ['boot', 'system', 'vendor']
55 target_files_zip.writestr(
56 'META/ab_partitions.txt',
57 '\n'.join(ab_partitions))
58
59 # Create dummy images for each of them.
60 for partition in ab_partitions:
61 target_files_zip.writestr('IMAGES/' + partition + '.img',
62 os.urandom(len(partition)))
63
64 if secondary:
65 target_files_zip.writestr('IMAGES/system_other.img',
66 os.urandom(len("system_other")))
67
68 return target_files
69
70
Tao Bao481bab82017-12-21 11:23:09 -080071class MockScriptWriter(object):
72 """A class that mocks edify_generator.EdifyGenerator.
73
74 It simply pushes the incoming arguments onto script stack, which is to assert
75 the calls to EdifyGenerator functions.
76 """
77
78 def __init__(self):
79 self.script = []
80
81 def Mount(self, *args):
82 self.script.append(('Mount',) + args)
83
84 def AssertDevice(self, *args):
85 self.script.append(('AssertDevice',) + args)
86
87 def AssertOemProperty(self, *args):
88 self.script.append(('AssertOemProperty',) + args)
89
90 def AssertFingerprintOrThumbprint(self, *args):
91 self.script.append(('AssertFingerprintOrThumbprint',) + args)
92
93 def AssertSomeFingerprint(self, *args):
94 self.script.append(('AssertSomeFingerprint',) + args)
95
96 def AssertSomeThumbprint(self, *args):
97 self.script.append(('AssertSomeThumbprint',) + args)
98
99
100class BuildInfoTest(unittest.TestCase):
101
102 TEST_INFO_DICT = {
103 'build.prop' : {
104 'ro.product.device' : 'product-device',
105 'ro.product.name' : 'product-name',
106 'ro.build.fingerprint' : 'build-fingerprint',
107 'ro.build.foo' : 'build-foo',
108 },
109 'vendor.build.prop' : {
110 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
111 },
112 'property1' : 'value1',
113 'property2' : 4096,
114 }
115
116 TEST_INFO_DICT_USES_OEM_PROPS = {
117 'build.prop' : {
118 'ro.product.name' : 'product-name',
119 'ro.build.thumbprint' : 'build-thumbprint',
120 'ro.build.bar' : 'build-bar',
121 },
122 'vendor.build.prop' : {
123 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
124 },
125 'property1' : 'value1',
126 'property2' : 4096,
127 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand',
128 }
129
130 TEST_OEM_DICTS = [
131 {
132 'ro.product.brand' : 'brand1',
133 'ro.product.device' : 'device1',
134 },
135 {
136 'ro.product.brand' : 'brand2',
137 'ro.product.device' : 'device2',
138 },
139 {
140 'ro.product.brand' : 'brand3',
141 'ro.product.device' : 'device3',
142 },
143 ]
144
145 def test_init(self):
146 target_info = BuildInfo(self.TEST_INFO_DICT, None)
147 self.assertEqual('product-device', target_info.device)
148 self.assertEqual('build-fingerprint', target_info.fingerprint)
149 self.assertFalse(target_info.is_ab)
150 self.assertIsNone(target_info.oem_props)
151
152 def test_init_with_oem_props(self):
153 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
154 self.TEST_OEM_DICTS)
155 self.assertEqual('device1', target_info.device)
156 self.assertEqual('brand1/product-name/device1:build-thumbprint',
157 target_info.fingerprint)
158
159 # Swap the order in oem_dicts, which would lead to different BuildInfo.
160 oem_dicts = copy.copy(self.TEST_OEM_DICTS)
161 oem_dicts[0], oem_dicts[2] = oem_dicts[2], oem_dicts[0]
162 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, oem_dicts)
163 self.assertEqual('device3', target_info.device)
164 self.assertEqual('brand3/product-name/device3:build-thumbprint',
165 target_info.fingerprint)
166
167 # Missing oem_dict should be rejected.
168 self.assertRaises(AssertionError, BuildInfo,
169 self.TEST_INFO_DICT_USES_OEM_PROPS, None)
170
171 def test___getitem__(self):
172 target_info = BuildInfo(self.TEST_INFO_DICT, None)
173 self.assertEqual('value1', target_info['property1'])
174 self.assertEqual(4096, target_info['property2'])
175 self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo'])
176
177 def test___getitem__with_oem_props(self):
178 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
179 self.TEST_OEM_DICTS)
180 self.assertEqual('value1', target_info['property1'])
181 self.assertEqual(4096, target_info['property2'])
182 self.assertRaises(KeyError,
183 lambda: target_info['build.prop']['ro.build.foo'])
184
185 def test_get(self):
186 target_info = BuildInfo(self.TEST_INFO_DICT, None)
187 self.assertEqual('value1', target_info.get('property1'))
188 self.assertEqual(4096, target_info.get('property2'))
189 self.assertEqual(4096, target_info.get('property2', 1024))
190 self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
191 self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo'])
192
193 def test_get_with_oem_props(self):
194 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
195 self.TEST_OEM_DICTS)
196 self.assertEqual('value1', target_info.get('property1'))
197 self.assertEqual(4096, target_info.get('property2'))
198 self.assertEqual(4096, target_info.get('property2', 1024))
199 self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
200 self.assertIsNone(target_info.get('build.prop').get('ro.build.foo'))
201 self.assertRaises(KeyError,
202 lambda: target_info.get('build.prop')['ro.build.foo'])
203
204 def test_GetBuildProp(self):
205 target_info = BuildInfo(self.TEST_INFO_DICT, None)
206 self.assertEqual('build-foo', target_info.GetBuildProp('ro.build.foo'))
207 self.assertRaises(common.ExternalError, target_info.GetBuildProp,
208 'ro.build.nonexistent')
209
210 def test_GetBuildProp_with_oem_props(self):
211 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
212 self.TEST_OEM_DICTS)
213 self.assertEqual('build-bar', target_info.GetBuildProp('ro.build.bar'))
214 self.assertRaises(common.ExternalError, target_info.GetBuildProp,
215 'ro.build.nonexistent')
216
217 def test_GetVendorBuildProp(self):
218 target_info = BuildInfo(self.TEST_INFO_DICT, None)
219 self.assertEqual('vendor-build-fingerprint',
220 target_info.GetVendorBuildProp(
221 'ro.vendor.build.fingerprint'))
222 self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
223 'ro.build.nonexistent')
224
225 def test_GetVendorBuildProp_with_oem_props(self):
226 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
227 self.TEST_OEM_DICTS)
228 self.assertEqual('vendor-build-fingerprint',
229 target_info.GetVendorBuildProp(
230 'ro.vendor.build.fingerprint'))
231 self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
232 'ro.build.nonexistent')
233
234 def test_WriteMountOemScript(self):
235 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
236 self.TEST_OEM_DICTS)
237 script_writer = MockScriptWriter()
238 target_info.WriteMountOemScript(script_writer)
239 self.assertEqual([('Mount', '/oem', None)], script_writer.script)
240
241 def test_WriteDeviceAssertions(self):
242 target_info = BuildInfo(self.TEST_INFO_DICT, None)
243 script_writer = MockScriptWriter()
244 target_info.WriteDeviceAssertions(script_writer, False)
245 self.assertEqual([('AssertDevice', 'product-device')], script_writer.script)
246
247 def test_WriteDeviceAssertions_with_oem_props(self):
248 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
249 self.TEST_OEM_DICTS)
250 script_writer = MockScriptWriter()
251 target_info.WriteDeviceAssertions(script_writer, False)
252 self.assertEqual(
253 [
254 ('AssertOemProperty', 'ro.product.device',
255 ['device1', 'device2', 'device3'], False),
256 ('AssertOemProperty', 'ro.product.brand',
257 ['brand1', 'brand2', 'brand3'], False),
258 ],
259 script_writer.script)
260
261 def test_WriteFingerprintAssertion_without_oem_props(self):
262 target_info = BuildInfo(self.TEST_INFO_DICT, None)
263 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT)
264 source_info_dict['build.prop']['ro.build.fingerprint'] = (
265 'source-build-fingerprint')
266 source_info = BuildInfo(source_info_dict, None)
267
268 script_writer = MockScriptWriter()
269 WriteFingerprintAssertion(script_writer, target_info, source_info)
270 self.assertEqual(
271 [('AssertSomeFingerprint', 'source-build-fingerprint',
272 'build-fingerprint')],
273 script_writer.script)
274
275 def test_WriteFingerprintAssertion_with_source_oem_props(self):
276 target_info = BuildInfo(self.TEST_INFO_DICT, None)
277 source_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
278 self.TEST_OEM_DICTS)
279
280 script_writer = MockScriptWriter()
281 WriteFingerprintAssertion(script_writer, target_info, source_info)
282 self.assertEqual(
283 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
284 'build-thumbprint')],
285 script_writer.script)
286
287 def test_WriteFingerprintAssertion_with_target_oem_props(self):
288 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
289 self.TEST_OEM_DICTS)
290 source_info = BuildInfo(self.TEST_INFO_DICT, None)
291
292 script_writer = MockScriptWriter()
293 WriteFingerprintAssertion(script_writer, target_info, source_info)
294 self.assertEqual(
295 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
296 'build-thumbprint')],
297 script_writer.script)
298
299 def test_WriteFingerprintAssertion_with_both_oem_props(self):
300 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
301 self.TEST_OEM_DICTS)
302 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
303 source_info_dict['build.prop']['ro.build.thumbprint'] = (
304 'source-build-thumbprint')
305 source_info = BuildInfo(source_info_dict, self.TEST_OEM_DICTS)
306
307 script_writer = MockScriptWriter()
308 WriteFingerprintAssertion(script_writer, target_info, source_info)
309 self.assertEqual(
310 [('AssertSomeThumbprint', 'build-thumbprint',
311 'source-build-thumbprint')],
312 script_writer.script)
313
314
315class LoadOemDictsTest(unittest.TestCase):
316
317 def tearDown(self):
318 common.Cleanup()
319
320 def test_NoneDict(self):
321 self.assertIsNone(_LoadOemDicts(None))
322
323 def test_SingleDict(self):
324 dict_file = common.MakeTempFile()
325 with open(dict_file, 'w') as dict_fp:
326 dict_fp.write('abc=1\ndef=2\nxyz=foo\na.b.c=bar\n')
327
328 oem_dicts = _LoadOemDicts([dict_file])
329 self.assertEqual(1, len(oem_dicts))
330 self.assertEqual('foo', oem_dicts[0]['xyz'])
331 self.assertEqual('bar', oem_dicts[0]['a.b.c'])
332
333 def test_MultipleDicts(self):
334 oem_source = []
335 for i in range(3):
336 dict_file = common.MakeTempFile()
337 with open(dict_file, 'w') as dict_fp:
338 dict_fp.write(
339 'ro.build.index={}\ndef=2\nxyz=foo\na.b.c=bar\n'.format(i))
340 oem_source.append(dict_file)
341
342 oem_dicts = _LoadOemDicts(oem_source)
343 self.assertEqual(3, len(oem_dicts))
344 for i, oem_dict in enumerate(oem_dicts):
345 self.assertEqual('2', oem_dict['def'])
346 self.assertEqual('foo', oem_dict['xyz'])
347 self.assertEqual('bar', oem_dict['a.b.c'])
348 self.assertEqual('{}'.format(i), oem_dict['ro.build.index'])
Tao Baodf3a48b2018-01-10 16:30:43 -0800349
350
351class OtaFromTargetFilesTest(unittest.TestCase):
352
353 TEST_TARGET_INFO_DICT = {
354 'build.prop' : {
355 'ro.product.device' : 'product-device',
356 'ro.build.fingerprint' : 'build-fingerprint-target',
357 'ro.build.version.incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800358 'ro.build.version.sdk' : '27',
359 'ro.build.version.security_patch' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800360 'ro.build.date.utc' : '1500000000',
361 },
362 }
363
364 TEST_SOURCE_INFO_DICT = {
365 'build.prop' : {
366 'ro.product.device' : 'product-device',
367 'ro.build.fingerprint' : 'build-fingerprint-source',
368 'ro.build.version.incremental' : 'build-version-incremental-source',
Tao Bao35dc2552018-02-01 13:18:00 -0800369 'ro.build.version.sdk' : '25',
370 'ro.build.version.security_patch' : '2016-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800371 'ro.build.date.utc' : '1400000000',
372 },
373 }
374
375 def setUp(self):
Tao Bao3bf8c652018-03-16 12:59:42 -0700376 self.testdata_dir = test_utils.get_testdata_dir()
377 self.assertTrue(os.path.exists(self.testdata_dir))
378
Tao Baodf3a48b2018-01-10 16:30:43 -0800379 # Reset the global options as in ota_from_target_files.py.
380 common.OPTIONS.incremental_source = None
381 common.OPTIONS.downgrade = False
382 common.OPTIONS.timestamp = False
383 common.OPTIONS.wipe_user_data = False
Tao Bao3bf8c652018-03-16 12:59:42 -0700384 common.OPTIONS.no_signing = False
385 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
386 common.OPTIONS.key_passwords = {
387 common.OPTIONS.package_key : None,
388 }
389
390 common.OPTIONS.search_path = test_utils.get_search_path()
391 self.assertIsNotNone(common.OPTIONS.search_path)
Tao Baodf3a48b2018-01-10 16:30:43 -0800392
Tao Baof5110492018-03-02 09:47:43 -0800393 def tearDown(self):
394 common.Cleanup()
395
Tao Baodf3a48b2018-01-10 16:30:43 -0800396 def test_GetPackageMetadata_abOta_full(self):
397 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
398 target_info_dict['ab_update'] = 'true'
399 target_info = BuildInfo(target_info_dict, None)
400 metadata = GetPackageMetadata(target_info)
401 self.assertDictEqual(
402 {
403 'ota-type' : 'AB',
404 'ota-required-cache' : '0',
405 'post-build' : 'build-fingerprint-target',
406 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800407 'post-sdk-level' : '27',
408 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800409 'post-timestamp' : '1500000000',
410 'pre-device' : 'product-device',
411 },
412 metadata)
413
414 def test_GetPackageMetadata_abOta_incremental(self):
415 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
416 target_info_dict['ab_update'] = 'true'
417 target_info = BuildInfo(target_info_dict, None)
418 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
419 common.OPTIONS.incremental_source = ''
420 metadata = GetPackageMetadata(target_info, source_info)
421 self.assertDictEqual(
422 {
423 'ota-type' : 'AB',
424 'ota-required-cache' : '0',
425 'post-build' : 'build-fingerprint-target',
426 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800427 'post-sdk-level' : '27',
428 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800429 'post-timestamp' : '1500000000',
430 'pre-device' : 'product-device',
431 'pre-build' : 'build-fingerprint-source',
432 'pre-build-incremental' : 'build-version-incremental-source',
433 },
434 metadata)
435
436 def test_GetPackageMetadata_nonAbOta_full(self):
437 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
438 metadata = GetPackageMetadata(target_info)
439 self.assertDictEqual(
440 {
441 'ota-type' : 'BLOCK',
442 'post-build' : 'build-fingerprint-target',
443 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800444 'post-sdk-level' : '27',
445 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800446 'post-timestamp' : '1500000000',
447 'pre-device' : 'product-device',
448 },
449 metadata)
450
451 def test_GetPackageMetadata_nonAbOta_incremental(self):
452 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
453 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
454 common.OPTIONS.incremental_source = ''
455 metadata = GetPackageMetadata(target_info, source_info)
456 self.assertDictEqual(
457 {
458 'ota-type' : 'BLOCK',
459 'post-build' : 'build-fingerprint-target',
460 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800461 'post-sdk-level' : '27',
462 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800463 'post-timestamp' : '1500000000',
464 'pre-device' : 'product-device',
465 'pre-build' : 'build-fingerprint-source',
466 'pre-build-incremental' : 'build-version-incremental-source',
467 },
468 metadata)
469
470 def test_GetPackageMetadata_wipe(self):
471 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
472 common.OPTIONS.wipe_user_data = True
473 metadata = GetPackageMetadata(target_info)
474 self.assertDictEqual(
475 {
476 'ota-type' : 'BLOCK',
477 'ota-wipe' : 'yes',
478 'post-build' : 'build-fingerprint-target',
479 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800480 'post-sdk-level' : '27',
481 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800482 'post-timestamp' : '1500000000',
483 'pre-device' : 'product-device',
484 },
485 metadata)
486
487 @staticmethod
488 def _test_GetPackageMetadata_swapBuildTimestamps(target_info, source_info):
489 (target_info['build.prop']['ro.build.date.utc'],
490 source_info['build.prop']['ro.build.date.utc']) = (
491 source_info['build.prop']['ro.build.date.utc'],
492 target_info['build.prop']['ro.build.date.utc'])
493
494 def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
495 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
496 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
497 self._test_GetPackageMetadata_swapBuildTimestamps(
498 target_info_dict, source_info_dict)
499
500 target_info = BuildInfo(target_info_dict, None)
501 source_info = BuildInfo(source_info_dict, None)
502 common.OPTIONS.incremental_source = ''
503 self.assertRaises(RuntimeError, GetPackageMetadata, target_info,
504 source_info)
505
506 def test_GetPackageMetadata_downgrade(self):
507 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
508 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
509 self._test_GetPackageMetadata_swapBuildTimestamps(
510 target_info_dict, source_info_dict)
511
512 target_info = BuildInfo(target_info_dict, None)
513 source_info = BuildInfo(source_info_dict, None)
514 common.OPTIONS.incremental_source = ''
515 common.OPTIONS.downgrade = True
516 common.OPTIONS.wipe_user_data = True
517 metadata = GetPackageMetadata(target_info, source_info)
518 self.assertDictEqual(
519 {
520 'ota-downgrade' : 'yes',
521 'ota-type' : 'BLOCK',
522 'ota-wipe' : 'yes',
523 'post-build' : 'build-fingerprint-target',
524 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800525 'post-sdk-level' : '27',
526 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800527 'pre-device' : 'product-device',
528 'pre-build' : 'build-fingerprint-source',
529 'pre-build-incremental' : 'build-version-incremental-source',
530 },
531 metadata)
532
533 def test_GetPackageMetadata_overrideTimestamp(self):
534 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
535 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
536 self._test_GetPackageMetadata_swapBuildTimestamps(
537 target_info_dict, source_info_dict)
538
539 target_info = BuildInfo(target_info_dict, None)
540 source_info = BuildInfo(source_info_dict, None)
541 common.OPTIONS.incremental_source = ''
542 common.OPTIONS.timestamp = True
543 metadata = GetPackageMetadata(target_info, source_info)
544 self.assertDictEqual(
545 {
546 'ota-type' : 'BLOCK',
547 'post-build' : 'build-fingerprint-target',
548 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800549 'post-sdk-level' : '27',
550 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800551 'post-timestamp' : '1500000001',
552 'pre-device' : 'product-device',
553 'pre-build' : 'build-fingerprint-source',
554 'pre-build-incremental' : 'build-version-incremental-source',
555 },
556 metadata)
Tao Baofabe0832018-01-17 15:52:28 -0800557
Tao Baof7140c02018-01-30 17:09:24 -0800558 def test_GetTargetFilesZipForSecondaryImages(self):
559 input_file = construct_target_files(secondary=True)
560 target_file = GetTargetFilesZipForSecondaryImages(input_file)
561
562 with zipfile.ZipFile(target_file) as verify_zip:
563 namelist = verify_zip.namelist()
564
565 self.assertIn('META/ab_partitions.txt', namelist)
566 self.assertIn('IMAGES/boot.img', namelist)
567 self.assertIn('IMAGES/system.img', namelist)
568 self.assertIn('IMAGES/vendor.img', namelist)
Tao Bao15a146a2018-02-21 16:06:59 -0800569 self.assertIn(POSTINSTALL_CONFIG, namelist)
Tao Baof7140c02018-01-30 17:09:24 -0800570
571 self.assertNotIn('IMAGES/system_other.img', namelist)
572 self.assertNotIn('IMAGES/system.map', namelist)
573
Tao Bao15a146a2018-02-21 16:06:59 -0800574 def test_GetTargetFilesZipForSecondaryImages_skipPostinstall(self):
575 input_file = construct_target_files(secondary=True)
576 target_file = GetTargetFilesZipForSecondaryImages(
577 input_file, skip_postinstall=True)
578
579 with zipfile.ZipFile(target_file) as verify_zip:
580 namelist = verify_zip.namelist()
581
582 self.assertIn('META/ab_partitions.txt', namelist)
583 self.assertIn('IMAGES/boot.img', namelist)
584 self.assertIn('IMAGES/system.img', namelist)
585 self.assertIn('IMAGES/vendor.img', namelist)
586
587 self.assertNotIn('IMAGES/system_other.img', namelist)
588 self.assertNotIn('IMAGES/system.map', namelist)
589 self.assertNotIn(POSTINSTALL_CONFIG, namelist)
590
591 def test_GetTargetFilesZipWithoutPostinstallConfig(self):
592 input_file = construct_target_files()
593 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
594 with zipfile.ZipFile(target_file) as verify_zip:
595 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
596
597 def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self):
598 input_file = construct_target_files()
599 common.ZipDelete(input_file, POSTINSTALL_CONFIG)
600 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
601 with zipfile.ZipFile(target_file) as verify_zip:
602 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
603
Tao Bao3bf8c652018-03-16 12:59:42 -0700604 def _test_FinalizeMetadata(self, large_entry=False):
605 entries = [
606 'required-entry1',
607 'required-entry2',
608 ]
609 zip_file = PropertyFilesTest.construct_zip_package(entries)
610 # Add a large entry of 1 GiB if requested.
611 if large_entry:
612 with zipfile.ZipFile(zip_file, 'a') as zip_fp:
613 zip_fp.writestr(
614 # Using 'zoo' so that the entry stays behind others after signing.
615 'zoo',
616 'A' * 1024 * 1024 * 1024,
617 zipfile.ZIP_STORED)
618
619 metadata = {}
620 output_file = common.MakeTempFile(suffix='.zip')
621 needed_property_files = (
622 TestPropertyFiles(),
623 )
624 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
625 self.assertIn('ota-test-property-files', metadata)
626
627 def test_FinalizeMetadata(self):
628 self._test_FinalizeMetadata()
629
630 def test_FinalizeMetadata_withNoSigning(self):
631 common.OPTIONS.no_signing = True
632 self._test_FinalizeMetadata()
633
634 def test_FinalizeMetadata_largeEntry(self):
635 self._test_FinalizeMetadata(large_entry=True)
636
637 def test_FinalizeMetadata_largeEntry_withNoSigning(self):
638 common.OPTIONS.no_signing = True
639 self._test_FinalizeMetadata(large_entry=True)
640
641 def test_FinalizeMetadata_insufficientSpace(self):
642 entries = [
643 'required-entry1',
644 'required-entry2',
645 'optional-entry1',
646 'optional-entry2',
647 ]
648 zip_file = PropertyFilesTest.construct_zip_package(entries)
649 with zipfile.ZipFile(zip_file, 'a') as zip_fp:
650 zip_fp.writestr(
651 # 'foo-entry1' will appear ahead of all other entries (in alphabetical
652 # order) after the signing, which will in turn trigger the
653 # InsufficientSpaceException and an automatic retry.
654 'foo-entry1',
655 'A' * 1024 * 1024,
656 zipfile.ZIP_STORED)
657
658 metadata = {}
659 needed_property_files = (
660 TestPropertyFiles(),
661 )
662 output_file = common.MakeTempFile(suffix='.zip')
663 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
664 self.assertIn('ota-test-property-files', metadata)
665
Tao Baoae5e4c32018-03-01 19:30:00 -0800666
Tao Bao69203522018-03-08 16:09:01 -0800667class TestPropertyFiles(PropertyFiles):
668 """A class that extends PropertyFiles for testing purpose."""
669
670 def __init__(self):
671 super(TestPropertyFiles, self).__init__()
672 self.name = 'ota-test-property-files'
673 self.required = (
674 'required-entry1',
675 'required-entry2',
676 )
677 self.optional = (
678 'optional-entry1',
679 'optional-entry2',
680 )
681
682
683class PropertyFilesTest(unittest.TestCase):
Tao Baoae5e4c32018-03-01 19:30:00 -0800684
Tao Bao3bf8c652018-03-16 12:59:42 -0700685 def setUp(self):
686 common.OPTIONS.no_signing = False
687
Tao Baoae5e4c32018-03-01 19:30:00 -0800688 def tearDown(self):
689 common.Cleanup()
690
Tao Baof5110492018-03-02 09:47:43 -0800691 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -0700692 def construct_zip_package(entries):
Tao Baof5110492018-03-02 09:47:43 -0800693 zip_file = common.MakeTempFile(suffix='.zip')
694 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
695 for entry in entries:
696 zip_fp.writestr(
697 entry,
698 entry.replace('.', '-').upper(),
699 zipfile.ZIP_STORED)
700 return zip_file
701
702 @staticmethod
Tao Bao69203522018-03-08 16:09:01 -0800703 def _parse_property_files_string(data):
Tao Baof5110492018-03-02 09:47:43 -0800704 result = {}
705 for token in data.split(','):
706 name, info = token.split(':', 1)
707 result[name] = info
708 return result
709
710 def _verify_entries(self, input_file, tokens, entries):
711 for entry in entries:
712 offset, size = map(int, tokens[entry].split(':'))
713 with open(input_file, 'rb') as input_fp:
714 input_fp.seek(offset)
715 if entry == 'metadata':
716 expected = b'META-INF/COM/ANDROID/METADATA'
717 else:
718 expected = entry.replace('.', '-').upper().encode()
719 self.assertEqual(expected, input_fp.read(size))
720
Tao Baoae5e4c32018-03-01 19:30:00 -0800721 def test_Compute(self):
Tao Baof5110492018-03-02 09:47:43 -0800722 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800723 'required-entry1',
724 'required-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800725 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700726 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800727 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800728 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Bao69203522018-03-08 16:09:01 -0800729 property_files_string = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800730
Tao Bao69203522018-03-08 16:09:01 -0800731 tokens = self._parse_property_files_string(property_files_string)
Tao Baof5110492018-03-02 09:47:43 -0800732 self.assertEqual(3, len(tokens))
733 self._verify_entries(zip_file, tokens, entries)
734
Tao Bao69203522018-03-08 16:09:01 -0800735 def test_Compute_withOptionalEntries(self):
Tao Baof5110492018-03-02 09:47:43 -0800736 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800737 'required-entry1',
738 'required-entry2',
739 'optional-entry1',
740 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800741 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700742 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800743 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800744 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Bao69203522018-03-08 16:09:01 -0800745 property_files_string = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800746
Tao Bao69203522018-03-08 16:09:01 -0800747 tokens = self._parse_property_files_string(property_files_string)
Tao Baof5110492018-03-02 09:47:43 -0800748 self.assertEqual(5, len(tokens))
749 self._verify_entries(zip_file, tokens, entries)
750
Tao Bao69203522018-03-08 16:09:01 -0800751 def test_Compute_missingRequiredEntry(self):
752 entries = (
753 'required-entry2',
754 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700755 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800756 property_files = TestPropertyFiles()
757 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
758 self.assertRaises(KeyError, property_files.Compute, zip_fp)
759
Tao Baoae5e4c32018-03-01 19:30:00 -0800760 def test_Finalize(self):
Tao Baof5110492018-03-02 09:47:43 -0800761 entries = [
Tao Bao69203522018-03-08 16:09:01 -0800762 'required-entry1',
763 'required-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800764 'META-INF/com/android/metadata',
765 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700766 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800767 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800768 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Bao69203522018-03-08 16:09:01 -0800769 # pylint: disable=protected-access
Tao Baoae5e4c32018-03-01 19:30:00 -0800770 raw_metadata = property_files._GetPropertyFilesString(
771 zip_fp, reserve_space=False)
772 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
Tao Bao69203522018-03-08 16:09:01 -0800773 tokens = self._parse_property_files_string(streaming_metadata)
Tao Baof5110492018-03-02 09:47:43 -0800774
775 self.assertEqual(3, len(tokens))
776 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
777 # streaming metadata.
778 entries[2] = 'metadata'
779 self._verify_entries(zip_file, tokens, entries)
780
Tao Baoae5e4c32018-03-01 19:30:00 -0800781 def test_Finalize_assertReservedLength(self):
Tao Baof5110492018-03-02 09:47:43 -0800782 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800783 'required-entry1',
784 'required-entry2',
785 'optional-entry1',
786 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800787 'META-INF/com/android/metadata',
788 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700789 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800790 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800791 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
792 # First get the raw metadata string (i.e. without padding space).
Tao Bao69203522018-03-08 16:09:01 -0800793 # pylint: disable=protected-access
Tao Baoae5e4c32018-03-01 19:30:00 -0800794 raw_metadata = property_files._GetPropertyFilesString(
795 zip_fp, reserve_space=False)
Tao Baof5110492018-03-02 09:47:43 -0800796 raw_length = len(raw_metadata)
797
798 # Now pass in the exact expected length.
Tao Baoae5e4c32018-03-01 19:30:00 -0800799 streaming_metadata = property_files.Finalize(zip_fp, raw_length)
Tao Baof5110492018-03-02 09:47:43 -0800800 self.assertEqual(raw_length, len(streaming_metadata))
801
802 # Or pass in insufficient length.
803 self.assertRaises(
Tao Bao3bf8c652018-03-16 12:59:42 -0700804 PropertyFiles.InsufficientSpaceException,
Tao Baoae5e4c32018-03-01 19:30:00 -0800805 property_files.Finalize,
Tao Baof5110492018-03-02 09:47:43 -0800806 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800807 raw_length - 1)
Tao Baof5110492018-03-02 09:47:43 -0800808
809 # Or pass in a much larger size.
Tao Baoae5e4c32018-03-01 19:30:00 -0800810 streaming_metadata = property_files.Finalize(
Tao Baof5110492018-03-02 09:47:43 -0800811 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800812 raw_length + 20)
Tao Baof5110492018-03-02 09:47:43 -0800813 self.assertEqual(raw_length + 20, len(streaming_metadata))
814 self.assertEqual(' ' * 20, streaming_metadata[raw_length:])
815
Tao Baoae5e4c32018-03-01 19:30:00 -0800816 def test_Verify(self):
817 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800818 'required-entry1',
819 'required-entry2',
820 'optional-entry1',
821 'optional-entry2',
822 'META-INF/com/android/metadata',
823 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700824 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800825 property_files = TestPropertyFiles()
826 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
827 # First get the raw metadata string (i.e. without padding space).
828 # pylint: disable=protected-access
829 raw_metadata = property_files._GetPropertyFilesString(
830 zip_fp, reserve_space=False)
831
832 # Should pass the test if verification passes.
833 property_files.Verify(zip_fp, raw_metadata)
834
835 # Or raise on verification failure.
836 self.assertRaises(
837 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
838
839
840class StreamingPropertyFilesTest(PropertyFilesTest):
841 """Additional sanity checks specialized for StreamingPropertyFiles."""
842
843 def test_init(self):
844 property_files = StreamingPropertyFiles()
845 self.assertEqual('ota-streaming-property-files', property_files.name)
846 self.assertEqual(
847 (
848 'payload.bin',
849 'payload_properties.txt',
850 ),
851 property_files.required)
852 self.assertEqual(
853 (
854 'care_map.txt',
855 'compatibility.zip',
856 ),
857 property_files.optional)
858
859 def test_Compute(self):
860 entries = (
Tao Baoae5e4c32018-03-01 19:30:00 -0800861 'payload.bin',
862 'payload_properties.txt',
863 'care_map.txt',
Tao Bao69203522018-03-08 16:09:01 -0800864 'compatibility.zip',
865 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700866 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800867 property_files = StreamingPropertyFiles()
868 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
869 property_files_string = property_files.Compute(zip_fp)
870
871 tokens = self._parse_property_files_string(property_files_string)
872 self.assertEqual(5, len(tokens))
873 self._verify_entries(zip_file, tokens, entries)
874
875 def test_Finalize(self):
876 entries = [
877 'payload.bin',
878 'payload_properties.txt',
879 'care_map.txt',
880 'compatibility.zip',
881 'META-INF/com/android/metadata',
882 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700883 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800884 property_files = StreamingPropertyFiles()
885 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
886 # pylint: disable=protected-access
887 raw_metadata = property_files._GetPropertyFilesString(
888 zip_fp, reserve_space=False)
889 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
890 tokens = self._parse_property_files_string(streaming_metadata)
891
892 self.assertEqual(5, len(tokens))
893 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
894 # streaming metadata.
895 entries[4] = 'metadata'
896 self._verify_entries(zip_file, tokens, entries)
897
898 def test_Verify(self):
899 entries = (
900 'payload.bin',
901 'payload_properties.txt',
902 'care_map.txt',
903 'compatibility.zip',
Tao Baoae5e4c32018-03-01 19:30:00 -0800904 'META-INF/com/android/metadata',
905 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700906 zip_file = self.construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800907 property_files = StreamingPropertyFiles()
908 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
909 # First get the raw metadata string (i.e. without padding space).
Tao Bao69203522018-03-08 16:09:01 -0800910 # pylint: disable=protected-access
Tao Baoae5e4c32018-03-01 19:30:00 -0800911 raw_metadata = property_files._GetPropertyFilesString(
912 zip_fp, reserve_space=False)
913
914 # Should pass the test if verification passes.
915 property_files.Verify(zip_fp, raw_metadata)
916
917 # Or raise on verification failure.
918 self.assertRaises(
919 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
920
Tao Baofabe0832018-01-17 15:52:28 -0800921
Tao Baob6304672018-03-08 16:28:33 -0800922class AbOtaPropertyFilesTest(PropertyFilesTest):
923 """Additional sanity checks specialized for AbOtaPropertyFiles."""
924
925 # The size for payload and metadata signature size.
926 SIGNATURE_SIZE = 256
927
928 def setUp(self):
929 self.testdata_dir = test_utils.get_testdata_dir()
930 self.assertTrue(os.path.exists(self.testdata_dir))
931
932 common.OPTIONS.wipe_user_data = False
933 common.OPTIONS.payload_signer = None
934 common.OPTIONS.payload_signer_args = None
935 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
936 common.OPTIONS.key_passwords = {
937 common.OPTIONS.package_key : None,
938 }
939
940 def test_init(self):
941 property_files = AbOtaPropertyFiles()
942 self.assertEqual('ota-property-files', property_files.name)
943 self.assertEqual(
944 (
945 'payload.bin',
946 'payload_properties.txt',
947 ),
948 property_files.required)
949 self.assertEqual(
950 (
951 'care_map.txt',
952 'compatibility.zip',
953 ),
954 property_files.optional)
955
956 def test_GetPayloadMetadataOffsetAndSize(self):
957 target_file = construct_target_files()
958 payload = Payload()
959 payload.Generate(target_file)
960
961 payload_signer = PayloadSigner()
962 payload.Sign(payload_signer)
963
964 output_file = common.MakeTempFile(suffix='.zip')
965 with zipfile.ZipFile(output_file, 'w') as output_zip:
966 payload.WriteToZip(output_zip)
967
968 # Find out the payload metadata offset and size.
969 property_files = AbOtaPropertyFiles()
970 with zipfile.ZipFile(output_file) as input_zip:
971 # pylint: disable=protected-access
972 payload_offset, metadata_total = (
973 property_files._GetPayloadMetadataOffsetAndSize(input_zip))
974
975 # Read in the metadata signature directly.
976 with open(output_file, 'rb') as verify_fp:
977 verify_fp.seek(payload_offset + metadata_total - self.SIGNATURE_SIZE)
978 metadata_signature = verify_fp.read(self.SIGNATURE_SIZE)
979
980 # Now we extract the metadata hash via brillo_update_payload script, which
981 # will serve as the oracle result.
982 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
983 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
984 cmd = ['brillo_update_payload', 'hash',
985 '--unsigned_payload', payload.payload_file,
986 '--signature_size', str(self.SIGNATURE_SIZE),
987 '--metadata_hash_file', metadata_sig_file,
988 '--payload_hash_file', payload_sig_file]
989 proc = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
990 stdoutdata, _ = proc.communicate()
991 self.assertEqual(
992 0, proc.returncode,
993 'Failed to run brillo_update_payload: {}'.format(stdoutdata))
994
995 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
996
997 # Finally we can compare the two signatures.
998 with open(signed_metadata_sig_file, 'rb') as verify_fp:
999 self.assertEqual(verify_fp.read(), metadata_signature)
1000
1001 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -07001002 def construct_zip_package_withValidPayload(with_metadata=False):
1003 # Cannot use construct_zip_package() since we need a "valid" payload.bin.
Tao Baob6304672018-03-08 16:28:33 -08001004 target_file = construct_target_files()
1005 payload = Payload()
1006 payload.Generate(target_file)
1007
1008 payload_signer = PayloadSigner()
1009 payload.Sign(payload_signer)
1010
1011 zip_file = common.MakeTempFile(suffix='.zip')
1012 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
1013 # 'payload.bin',
1014 payload.WriteToZip(zip_fp)
1015
1016 # Other entries.
1017 entries = ['care_map.txt', 'compatibility.zip']
1018
1019 # Put META-INF/com/android/metadata if needed.
1020 if with_metadata:
1021 entries.append('META-INF/com/android/metadata')
1022
1023 for entry in entries:
1024 zip_fp.writestr(
1025 entry, entry.replace('.', '-').upper(), zipfile.ZIP_STORED)
1026
1027 return zip_file
1028
1029 def test_Compute(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001030 zip_file = self.construct_zip_package_withValidPayload()
Tao Baob6304672018-03-08 16:28:33 -08001031 property_files = AbOtaPropertyFiles()
1032 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1033 property_files_string = property_files.Compute(zip_fp)
1034
1035 tokens = self._parse_property_files_string(property_files_string)
1036 # "6" indcludes the four entries above, one metadata entry, and one entry
1037 # for payload-metadata.bin.
1038 self.assertEqual(6, len(tokens))
1039 self._verify_entries(
1040 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1041
1042 def test_Finalize(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001043 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001044 property_files = AbOtaPropertyFiles()
1045 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1046 # pylint: disable=protected-access
1047 raw_metadata = property_files._GetPropertyFilesString(
1048 zip_fp, reserve_space=False)
1049 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1050
1051 tokens = self._parse_property_files_string(property_files_string)
1052 # "6" indcludes the four entries above, one metadata entry, and one entry
1053 # for payload-metadata.bin.
1054 self.assertEqual(6, len(tokens))
1055 self._verify_entries(
1056 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1057
1058 def test_Verify(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001059 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001060 property_files = AbOtaPropertyFiles()
1061 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1062 # pylint: disable=protected-access
1063 raw_metadata = property_files._GetPropertyFilesString(
1064 zip_fp, reserve_space=False)
1065
1066 property_files.Verify(zip_fp, raw_metadata)
1067
1068
Tao Baoc0746f42018-02-21 13:17:22 -08001069class NonAbOtaPropertyFilesTest(PropertyFilesTest):
1070 """Additional sanity checks specialized for NonAbOtaPropertyFiles."""
1071
1072 def test_init(self):
1073 property_files = NonAbOtaPropertyFiles()
1074 self.assertEqual('ota-property-files', property_files.name)
1075 self.assertEqual((), property_files.required)
1076 self.assertEqual((), property_files.optional)
1077
1078 def test_Compute(self):
1079 entries = ()
Tao Bao3bf8c652018-03-16 12:59:42 -07001080 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001081 property_files = NonAbOtaPropertyFiles()
1082 with zipfile.ZipFile(zip_file) as zip_fp:
1083 property_files_string = property_files.Compute(zip_fp)
1084
1085 tokens = self._parse_property_files_string(property_files_string)
1086 self.assertEqual(1, len(tokens))
1087 self._verify_entries(zip_file, tokens, entries)
1088
1089 def test_Finalize(self):
1090 entries = [
1091 'META-INF/com/android/metadata',
1092 ]
Tao Bao3bf8c652018-03-16 12:59:42 -07001093 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001094 property_files = NonAbOtaPropertyFiles()
1095 with zipfile.ZipFile(zip_file) as zip_fp:
1096 # pylint: disable=protected-access
1097 raw_metadata = property_files._GetPropertyFilesString(
1098 zip_fp, reserve_space=False)
1099 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1100 tokens = self._parse_property_files_string(property_files_string)
1101
1102 self.assertEqual(1, len(tokens))
1103 # 'META-INF/com/android/metadata' will be key'd as 'metadata'.
1104 entries[0] = 'metadata'
1105 self._verify_entries(zip_file, tokens, entries)
1106
1107 def test_Verify(self):
1108 entries = (
1109 'META-INF/com/android/metadata',
1110 )
Tao Bao3bf8c652018-03-16 12:59:42 -07001111 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001112 property_files = NonAbOtaPropertyFiles()
1113 with zipfile.ZipFile(zip_file) as zip_fp:
1114 # pylint: disable=protected-access
1115 raw_metadata = property_files._GetPropertyFilesString(
1116 zip_fp, reserve_space=False)
1117
1118 property_files.Verify(zip_fp, raw_metadata)
1119
1120
Tao Baofabe0832018-01-17 15:52:28 -08001121class PayloadSignerTest(unittest.TestCase):
1122
1123 SIGFILE = 'sigfile.bin'
1124 SIGNED_SIGFILE = 'signed-sigfile.bin'
1125
1126 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001127 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baofabe0832018-01-17 15:52:28 -08001128 self.assertTrue(os.path.exists(self.testdata_dir))
1129
1130 common.OPTIONS.payload_signer = None
1131 common.OPTIONS.payload_signer_args = []
1132 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1133 common.OPTIONS.key_passwords = {
1134 common.OPTIONS.package_key : None,
1135 }
1136
1137 def tearDown(self):
1138 common.Cleanup()
1139
1140 def _assertFilesEqual(self, file1, file2):
1141 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2:
1142 self.assertEqual(fp1.read(), fp2.read())
1143
1144 def test_init(self):
1145 payload_signer = PayloadSigner()
1146 self.assertEqual('openssl', payload_signer.signer)
1147
1148 def test_init_withPassword(self):
1149 common.OPTIONS.package_key = os.path.join(
1150 self.testdata_dir, 'testkey_with_passwd')
1151 common.OPTIONS.key_passwords = {
1152 common.OPTIONS.package_key : 'foo',
1153 }
1154 payload_signer = PayloadSigner()
1155 self.assertEqual('openssl', payload_signer.signer)
1156
1157 def test_init_withExternalSigner(self):
1158 common.OPTIONS.payload_signer = 'abc'
1159 common.OPTIONS.payload_signer_args = ['arg1', 'arg2']
1160 payload_signer = PayloadSigner()
1161 self.assertEqual('abc', payload_signer.signer)
1162 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args)
1163
1164 def test_Sign(self):
1165 payload_signer = PayloadSigner()
1166 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1167 signed_file = payload_signer.Sign(input_file)
1168
1169 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1170 self._assertFilesEqual(verify_file, signed_file)
1171
1172 def test_Sign_withExternalSigner_openssl(self):
1173 """Uses openssl as the external payload signer."""
1174 common.OPTIONS.payload_signer = 'openssl'
1175 common.OPTIONS.payload_signer_args = [
1176 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey',
1177 os.path.join(self.testdata_dir, 'testkey.pk8'),
1178 '-pkeyopt', 'digest:sha256']
1179 payload_signer = PayloadSigner()
1180 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1181 signed_file = payload_signer.Sign(input_file)
1182
1183 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1184 self._assertFilesEqual(verify_file, signed_file)
1185
1186 def test_Sign_withExternalSigner_script(self):
1187 """Uses testdata/payload_signer.sh as the external payload signer."""
1188 common.OPTIONS.payload_signer = os.path.join(
1189 self.testdata_dir, 'payload_signer.sh')
1190 common.OPTIONS.payload_signer_args = [
1191 os.path.join(self.testdata_dir, 'testkey.pk8')]
1192 payload_signer = PayloadSigner()
1193 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1194 signed_file = payload_signer.Sign(input_file)
1195
1196 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1197 self._assertFilesEqual(verify_file, signed_file)
Tao Baoc7b403a2018-01-30 18:19:04 -08001198
1199
1200class PayloadTest(unittest.TestCase):
1201
1202 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001203 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baoc7b403a2018-01-30 18:19:04 -08001204 self.assertTrue(os.path.exists(self.testdata_dir))
1205
1206 common.OPTIONS.wipe_user_data = False
1207 common.OPTIONS.payload_signer = None
1208 common.OPTIONS.payload_signer_args = None
1209 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1210 common.OPTIONS.key_passwords = {
1211 common.OPTIONS.package_key : None,
1212 }
1213
1214 def tearDown(self):
1215 common.Cleanup()
1216
1217 @staticmethod
Tao Baof7140c02018-01-30 17:09:24 -08001218 def _create_payload_full(secondary=False):
1219 target_file = construct_target_files(secondary)
Tao Bao667ff572018-02-10 00:02:40 -08001220 payload = Payload(secondary)
Tao Baoc7b403a2018-01-30 18:19:04 -08001221 payload.Generate(target_file)
1222 return payload
1223
Tao Baof7140c02018-01-30 17:09:24 -08001224 @staticmethod
1225 def _create_payload_incremental():
1226 target_file = construct_target_files()
1227 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001228 payload = Payload()
1229 payload.Generate(target_file, source_file)
1230 return payload
1231
1232 def test_Generate_full(self):
1233 payload = self._create_payload_full()
1234 self.assertTrue(os.path.exists(payload.payload_file))
1235
1236 def test_Generate_incremental(self):
1237 payload = self._create_payload_incremental()
1238 self.assertTrue(os.path.exists(payload.payload_file))
1239
1240 def test_Generate_additionalArgs(self):
Tao Baof7140c02018-01-30 17:09:24 -08001241 target_file = construct_target_files()
1242 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001243 payload = Payload()
1244 # This should work the same as calling payload.Generate(target_file,
1245 # source_file).
1246 payload.Generate(
1247 target_file, additional_args=["--source_image", source_file])
1248 self.assertTrue(os.path.exists(payload.payload_file))
1249
1250 def test_Generate_invalidInput(self):
Tao Baof7140c02018-01-30 17:09:24 -08001251 target_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001252 common.ZipDelete(target_file, 'IMAGES/vendor.img')
1253 payload = Payload()
1254 self.assertRaises(AssertionError, payload.Generate, target_file)
1255
1256 def test_Sign_full(self):
1257 payload = self._create_payload_full()
1258 payload.Sign(PayloadSigner())
1259
1260 output_file = common.MakeTempFile(suffix='.zip')
1261 with zipfile.ZipFile(output_file, 'w') as output_zip:
1262 payload.WriteToZip(output_zip)
1263
1264 import check_ota_package_signature
1265 check_ota_package_signature.VerifyAbOtaPayload(
1266 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1267 output_file)
1268
1269 def test_Sign_incremental(self):
1270 payload = self._create_payload_incremental()
1271 payload.Sign(PayloadSigner())
1272
1273 output_file = common.MakeTempFile(suffix='.zip')
1274 with zipfile.ZipFile(output_file, 'w') as output_zip:
1275 payload.WriteToZip(output_zip)
1276
1277 import check_ota_package_signature
1278 check_ota_package_signature.VerifyAbOtaPayload(
1279 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1280 output_file)
1281
1282 def test_Sign_withDataWipe(self):
1283 common.OPTIONS.wipe_user_data = True
1284 payload = self._create_payload_full()
1285 payload.Sign(PayloadSigner())
1286
1287 with open(payload.payload_properties) as properties_fp:
1288 self.assertIn("POWERWASH=1", properties_fp.read())
1289
Tao Bao667ff572018-02-10 00:02:40 -08001290 def test_Sign_secondary(self):
1291 payload = self._create_payload_full(secondary=True)
1292 payload.Sign(PayloadSigner())
1293
1294 with open(payload.payload_properties) as properties_fp:
1295 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read())
1296
Tao Baoc7b403a2018-01-30 18:19:04 -08001297 def test_Sign_badSigner(self):
1298 """Tests that signing failure can be captured."""
1299 payload = self._create_payload_full()
1300 payload_signer = PayloadSigner()
1301 payload_signer.signer_args.append('bad-option')
1302 self.assertRaises(AssertionError, payload.Sign, payload_signer)
1303
1304 def test_WriteToZip(self):
1305 payload = self._create_payload_full()
1306 payload.Sign(PayloadSigner())
1307
1308 output_file = common.MakeTempFile(suffix='.zip')
1309 with zipfile.ZipFile(output_file, 'w') as output_zip:
1310 payload.WriteToZip(output_zip)
1311
1312 with zipfile.ZipFile(output_file) as verify_zip:
1313 # First make sure we have the essential entries.
1314 namelist = verify_zip.namelist()
1315 self.assertIn(Payload.PAYLOAD_BIN, namelist)
1316 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
1317
1318 # Then assert these entries are stored.
1319 for entry_info in verify_zip.infolist():
1320 if entry_info.filename not in (Payload.PAYLOAD_BIN,
1321 Payload.PAYLOAD_PROPERTIES_TXT):
1322 continue
1323 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
1324
1325 def test_WriteToZip_unsignedPayload(self):
1326 """Unsigned payloads should not be allowed to be written to zip."""
1327 payload = self._create_payload_full()
1328
1329 output_file = common.MakeTempFile(suffix='.zip')
1330 with zipfile.ZipFile(output_file, 'w') as output_zip:
1331 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
1332
1333 # Also test with incremental payload.
1334 payload = self._create_payload_incremental()
1335
1336 output_file = common.MakeTempFile(suffix='.zip')
1337 with zipfile.ZipFile(output_file, 'w') as output_zip:
1338 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001339
1340 def test_WriteToZip_secondary(self):
1341 payload = self._create_payload_full(secondary=True)
1342 payload.Sign(PayloadSigner())
1343
1344 output_file = common.MakeTempFile(suffix='.zip')
1345 with zipfile.ZipFile(output_file, 'w') as output_zip:
Tao Bao667ff572018-02-10 00:02:40 -08001346 payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001347
1348 with zipfile.ZipFile(output_file) as verify_zip:
1349 # First make sure we have the essential entries.
1350 namelist = verify_zip.namelist()
1351 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist)
1352 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist)
1353
1354 # Then assert these entries are stored.
1355 for entry_info in verify_zip.infolist():
1356 if entry_info.filename not in (
1357 Payload.SECONDARY_PAYLOAD_BIN,
1358 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
1359 continue
1360 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)