blob: 7c34b7e9988c54dc93850c2730d51506e74ed3b8 [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 Bao5277d102018-04-17 23:47:21 -070053 ab_partitions = [
54 ('IMAGES', 'boot'),
55 ('IMAGES', 'system'),
56 ('IMAGES', 'vendor'),
57 ('RADIO', 'bootloader'),
58 ('RADIO', 'modem'),
59 ]
Tao Baof7140c02018-01-30 17:09:24 -080060 # META/ab_partitions.txt
Tao Baof7140c02018-01-30 17:09:24 -080061 target_files_zip.writestr(
62 'META/ab_partitions.txt',
Tao Bao5277d102018-04-17 23:47:21 -070063 '\n'.join([partition[1] for partition in ab_partitions]))
Tao Baof7140c02018-01-30 17:09:24 -080064
65 # Create dummy images for each of them.
Tao Bao5277d102018-04-17 23:47:21 -070066 for path, partition in ab_partitions:
67 target_files_zip.writestr(
68 '{}/{}.img'.format(path, partition),
69 os.urandom(len(partition)))
Tao Baof7140c02018-01-30 17:09:24 -080070
Tao Bao5277d102018-04-17 23:47:21 -070071 # system_other shouldn't appear in META/ab_partitions.txt.
Tao Baof7140c02018-01-30 17:09:24 -080072 if secondary:
73 target_files_zip.writestr('IMAGES/system_other.img',
74 os.urandom(len("system_other")))
75
76 return target_files
77
78
Tao Bao481bab82017-12-21 11:23:09 -080079class MockScriptWriter(object):
80 """A class that mocks edify_generator.EdifyGenerator.
81
82 It simply pushes the incoming arguments onto script stack, which is to assert
83 the calls to EdifyGenerator functions.
84 """
85
86 def __init__(self):
87 self.script = []
88
89 def Mount(self, *args):
90 self.script.append(('Mount',) + args)
91
92 def AssertDevice(self, *args):
93 self.script.append(('AssertDevice',) + args)
94
95 def AssertOemProperty(self, *args):
96 self.script.append(('AssertOemProperty',) + args)
97
98 def AssertFingerprintOrThumbprint(self, *args):
99 self.script.append(('AssertFingerprintOrThumbprint',) + args)
100
101 def AssertSomeFingerprint(self, *args):
102 self.script.append(('AssertSomeFingerprint',) + args)
103
104 def AssertSomeThumbprint(self, *args):
105 self.script.append(('AssertSomeThumbprint',) + args)
106
107
108class BuildInfoTest(unittest.TestCase):
109
110 TEST_INFO_DICT = {
111 'build.prop' : {
112 'ro.product.device' : 'product-device',
113 'ro.product.name' : 'product-name',
114 'ro.build.fingerprint' : 'build-fingerprint',
115 'ro.build.foo' : 'build-foo',
116 },
117 'vendor.build.prop' : {
118 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
119 },
120 'property1' : 'value1',
121 'property2' : 4096,
122 }
123
124 TEST_INFO_DICT_USES_OEM_PROPS = {
125 'build.prop' : {
126 'ro.product.name' : 'product-name',
127 'ro.build.thumbprint' : 'build-thumbprint',
128 'ro.build.bar' : 'build-bar',
129 },
130 'vendor.build.prop' : {
131 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
132 },
133 'property1' : 'value1',
134 'property2' : 4096,
135 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand',
136 }
137
138 TEST_OEM_DICTS = [
139 {
140 'ro.product.brand' : 'brand1',
141 'ro.product.device' : 'device1',
142 },
143 {
144 'ro.product.brand' : 'brand2',
145 'ro.product.device' : 'device2',
146 },
147 {
148 'ro.product.brand' : 'brand3',
149 'ro.product.device' : 'device3',
150 },
151 ]
152
153 def test_init(self):
154 target_info = BuildInfo(self.TEST_INFO_DICT, None)
155 self.assertEqual('product-device', target_info.device)
156 self.assertEqual('build-fingerprint', target_info.fingerprint)
157 self.assertFalse(target_info.is_ab)
158 self.assertIsNone(target_info.oem_props)
159
160 def test_init_with_oem_props(self):
161 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
162 self.TEST_OEM_DICTS)
163 self.assertEqual('device1', target_info.device)
164 self.assertEqual('brand1/product-name/device1:build-thumbprint',
165 target_info.fingerprint)
166
167 # Swap the order in oem_dicts, which would lead to different BuildInfo.
168 oem_dicts = copy.copy(self.TEST_OEM_DICTS)
169 oem_dicts[0], oem_dicts[2] = oem_dicts[2], oem_dicts[0]
170 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, oem_dicts)
171 self.assertEqual('device3', target_info.device)
172 self.assertEqual('brand3/product-name/device3:build-thumbprint',
173 target_info.fingerprint)
174
175 # Missing oem_dict should be rejected.
176 self.assertRaises(AssertionError, BuildInfo,
177 self.TEST_INFO_DICT_USES_OEM_PROPS, None)
178
179 def test___getitem__(self):
180 target_info = BuildInfo(self.TEST_INFO_DICT, None)
181 self.assertEqual('value1', target_info['property1'])
182 self.assertEqual(4096, target_info['property2'])
183 self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo'])
184
185 def test___getitem__with_oem_props(self):
186 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
187 self.TEST_OEM_DICTS)
188 self.assertEqual('value1', target_info['property1'])
189 self.assertEqual(4096, target_info['property2'])
190 self.assertRaises(KeyError,
191 lambda: target_info['build.prop']['ro.build.foo'])
192
Tao Bao667c7532018-07-06 10:13:59 -0700193 def test___setitem__(self):
194 target_info = BuildInfo(copy.deepcopy(self.TEST_INFO_DICT), None)
195 self.assertEqual('value1', target_info['property1'])
196 target_info['property1'] = 'value2'
197 self.assertEqual('value2', target_info['property1'])
198
199 self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo'])
200 target_info['build.prop']['ro.build.foo'] = 'build-bar'
201 self.assertEqual('build-bar', target_info['build.prop']['ro.build.foo'])
202
Tao Bao481bab82017-12-21 11:23:09 -0800203 def test_get(self):
204 target_info = BuildInfo(self.TEST_INFO_DICT, None)
205 self.assertEqual('value1', target_info.get('property1'))
206 self.assertEqual(4096, target_info.get('property2'))
207 self.assertEqual(4096, target_info.get('property2', 1024))
208 self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
209 self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo'])
210
211 def test_get_with_oem_props(self):
212 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
213 self.TEST_OEM_DICTS)
214 self.assertEqual('value1', target_info.get('property1'))
215 self.assertEqual(4096, target_info.get('property2'))
216 self.assertEqual(4096, target_info.get('property2', 1024))
217 self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
218 self.assertIsNone(target_info.get('build.prop').get('ro.build.foo'))
219 self.assertRaises(KeyError,
220 lambda: target_info.get('build.prop')['ro.build.foo'])
221
Tao Bao667c7532018-07-06 10:13:59 -0700222 def test_items(self):
223 target_info = BuildInfo(self.TEST_INFO_DICT, None)
224 items = target_info.items()
225 self.assertIn(('property1', 'value1'), items)
226 self.assertIn(('property2', 4096), items)
227
Tao Bao481bab82017-12-21 11:23:09 -0800228 def test_GetBuildProp(self):
229 target_info = BuildInfo(self.TEST_INFO_DICT, None)
230 self.assertEqual('build-foo', target_info.GetBuildProp('ro.build.foo'))
231 self.assertRaises(common.ExternalError, target_info.GetBuildProp,
232 'ro.build.nonexistent')
233
234 def test_GetBuildProp_with_oem_props(self):
235 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
236 self.TEST_OEM_DICTS)
237 self.assertEqual('build-bar', target_info.GetBuildProp('ro.build.bar'))
238 self.assertRaises(common.ExternalError, target_info.GetBuildProp,
239 'ro.build.nonexistent')
240
241 def test_GetVendorBuildProp(self):
242 target_info = BuildInfo(self.TEST_INFO_DICT, None)
243 self.assertEqual('vendor-build-fingerprint',
244 target_info.GetVendorBuildProp(
245 'ro.vendor.build.fingerprint'))
246 self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
247 'ro.build.nonexistent')
248
249 def test_GetVendorBuildProp_with_oem_props(self):
250 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
251 self.TEST_OEM_DICTS)
252 self.assertEqual('vendor-build-fingerprint',
253 target_info.GetVendorBuildProp(
254 'ro.vendor.build.fingerprint'))
255 self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
256 'ro.build.nonexistent')
257
258 def test_WriteMountOemScript(self):
259 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
260 self.TEST_OEM_DICTS)
261 script_writer = MockScriptWriter()
262 target_info.WriteMountOemScript(script_writer)
263 self.assertEqual([('Mount', '/oem', None)], script_writer.script)
264
265 def test_WriteDeviceAssertions(self):
266 target_info = BuildInfo(self.TEST_INFO_DICT, None)
267 script_writer = MockScriptWriter()
268 target_info.WriteDeviceAssertions(script_writer, False)
269 self.assertEqual([('AssertDevice', 'product-device')], script_writer.script)
270
271 def test_WriteDeviceAssertions_with_oem_props(self):
272 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
273 self.TEST_OEM_DICTS)
274 script_writer = MockScriptWriter()
275 target_info.WriteDeviceAssertions(script_writer, False)
276 self.assertEqual(
277 [
278 ('AssertOemProperty', 'ro.product.device',
279 ['device1', 'device2', 'device3'], False),
280 ('AssertOemProperty', 'ro.product.brand',
281 ['brand1', 'brand2', 'brand3'], False),
282 ],
283 script_writer.script)
284
285 def test_WriteFingerprintAssertion_without_oem_props(self):
286 target_info = BuildInfo(self.TEST_INFO_DICT, None)
287 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT)
288 source_info_dict['build.prop']['ro.build.fingerprint'] = (
289 'source-build-fingerprint')
290 source_info = BuildInfo(source_info_dict, None)
291
292 script_writer = MockScriptWriter()
293 WriteFingerprintAssertion(script_writer, target_info, source_info)
294 self.assertEqual(
295 [('AssertSomeFingerprint', 'source-build-fingerprint',
296 'build-fingerprint')],
297 script_writer.script)
298
299 def test_WriteFingerprintAssertion_with_source_oem_props(self):
300 target_info = BuildInfo(self.TEST_INFO_DICT, None)
301 source_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
302 self.TEST_OEM_DICTS)
303
304 script_writer = MockScriptWriter()
305 WriteFingerprintAssertion(script_writer, target_info, source_info)
306 self.assertEqual(
307 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
308 'build-thumbprint')],
309 script_writer.script)
310
311 def test_WriteFingerprintAssertion_with_target_oem_props(self):
312 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
313 self.TEST_OEM_DICTS)
314 source_info = BuildInfo(self.TEST_INFO_DICT, None)
315
316 script_writer = MockScriptWriter()
317 WriteFingerprintAssertion(script_writer, target_info, source_info)
318 self.assertEqual(
319 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
320 'build-thumbprint')],
321 script_writer.script)
322
323 def test_WriteFingerprintAssertion_with_both_oem_props(self):
324 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
325 self.TEST_OEM_DICTS)
326 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
327 source_info_dict['build.prop']['ro.build.thumbprint'] = (
328 'source-build-thumbprint')
329 source_info = BuildInfo(source_info_dict, self.TEST_OEM_DICTS)
330
331 script_writer = MockScriptWriter()
332 WriteFingerprintAssertion(script_writer, target_info, source_info)
333 self.assertEqual(
334 [('AssertSomeThumbprint', 'build-thumbprint',
335 'source-build-thumbprint')],
336 script_writer.script)
337
338
339class LoadOemDictsTest(unittest.TestCase):
340
341 def tearDown(self):
342 common.Cleanup()
343
344 def test_NoneDict(self):
345 self.assertIsNone(_LoadOemDicts(None))
346
347 def test_SingleDict(self):
348 dict_file = common.MakeTempFile()
349 with open(dict_file, 'w') as dict_fp:
350 dict_fp.write('abc=1\ndef=2\nxyz=foo\na.b.c=bar\n')
351
352 oem_dicts = _LoadOemDicts([dict_file])
353 self.assertEqual(1, len(oem_dicts))
354 self.assertEqual('foo', oem_dicts[0]['xyz'])
355 self.assertEqual('bar', oem_dicts[0]['a.b.c'])
356
357 def test_MultipleDicts(self):
358 oem_source = []
359 for i in range(3):
360 dict_file = common.MakeTempFile()
361 with open(dict_file, 'w') as dict_fp:
362 dict_fp.write(
363 'ro.build.index={}\ndef=2\nxyz=foo\na.b.c=bar\n'.format(i))
364 oem_source.append(dict_file)
365
366 oem_dicts = _LoadOemDicts(oem_source)
367 self.assertEqual(3, len(oem_dicts))
368 for i, oem_dict in enumerate(oem_dicts):
369 self.assertEqual('2', oem_dict['def'])
370 self.assertEqual('foo', oem_dict['xyz'])
371 self.assertEqual('bar', oem_dict['a.b.c'])
372 self.assertEqual('{}'.format(i), oem_dict['ro.build.index'])
Tao Baodf3a48b2018-01-10 16:30:43 -0800373
374
375class OtaFromTargetFilesTest(unittest.TestCase):
376
377 TEST_TARGET_INFO_DICT = {
378 'build.prop' : {
379 'ro.product.device' : 'product-device',
380 'ro.build.fingerprint' : 'build-fingerprint-target',
381 'ro.build.version.incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800382 'ro.build.version.sdk' : '27',
383 'ro.build.version.security_patch' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800384 'ro.build.date.utc' : '1500000000',
385 },
386 }
387
388 TEST_SOURCE_INFO_DICT = {
389 'build.prop' : {
390 'ro.product.device' : 'product-device',
391 'ro.build.fingerprint' : 'build-fingerprint-source',
392 'ro.build.version.incremental' : 'build-version-incremental-source',
Tao Bao35dc2552018-02-01 13:18:00 -0800393 'ro.build.version.sdk' : '25',
394 'ro.build.version.security_patch' : '2016-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800395 'ro.build.date.utc' : '1400000000',
396 },
397 }
398
399 def setUp(self):
Tao Bao3bf8c652018-03-16 12:59:42 -0700400 self.testdata_dir = test_utils.get_testdata_dir()
401 self.assertTrue(os.path.exists(self.testdata_dir))
402
Tao Baodf3a48b2018-01-10 16:30:43 -0800403 # Reset the global options as in ota_from_target_files.py.
404 common.OPTIONS.incremental_source = None
405 common.OPTIONS.downgrade = False
406 common.OPTIONS.timestamp = False
407 common.OPTIONS.wipe_user_data = False
Tao Bao3bf8c652018-03-16 12:59:42 -0700408 common.OPTIONS.no_signing = False
409 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
410 common.OPTIONS.key_passwords = {
411 common.OPTIONS.package_key : None,
412 }
413
414 common.OPTIONS.search_path = test_utils.get_search_path()
415 self.assertIsNotNone(common.OPTIONS.search_path)
Tao Baodf3a48b2018-01-10 16:30:43 -0800416
Tao Baof5110492018-03-02 09:47:43 -0800417 def tearDown(self):
418 common.Cleanup()
419
Tao Baodf3a48b2018-01-10 16:30:43 -0800420 def test_GetPackageMetadata_abOta_full(self):
421 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
422 target_info_dict['ab_update'] = 'true'
423 target_info = BuildInfo(target_info_dict, None)
424 metadata = GetPackageMetadata(target_info)
425 self.assertDictEqual(
426 {
427 'ota-type' : 'AB',
428 'ota-required-cache' : '0',
429 'post-build' : 'build-fingerprint-target',
430 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800431 'post-sdk-level' : '27',
432 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800433 'post-timestamp' : '1500000000',
434 'pre-device' : 'product-device',
435 },
436 metadata)
437
438 def test_GetPackageMetadata_abOta_incremental(self):
439 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
440 target_info_dict['ab_update'] = 'true'
441 target_info = BuildInfo(target_info_dict, None)
442 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
443 common.OPTIONS.incremental_source = ''
444 metadata = GetPackageMetadata(target_info, source_info)
445 self.assertDictEqual(
446 {
447 'ota-type' : 'AB',
448 'ota-required-cache' : '0',
449 'post-build' : 'build-fingerprint-target',
450 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800451 'post-sdk-level' : '27',
452 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800453 'post-timestamp' : '1500000000',
454 'pre-device' : 'product-device',
455 'pre-build' : 'build-fingerprint-source',
456 'pre-build-incremental' : 'build-version-incremental-source',
457 },
458 metadata)
459
460 def test_GetPackageMetadata_nonAbOta_full(self):
461 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
462 metadata = GetPackageMetadata(target_info)
463 self.assertDictEqual(
464 {
465 'ota-type' : 'BLOCK',
466 'post-build' : 'build-fingerprint-target',
467 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800468 'post-sdk-level' : '27',
469 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800470 'post-timestamp' : '1500000000',
471 'pre-device' : 'product-device',
472 },
473 metadata)
474
475 def test_GetPackageMetadata_nonAbOta_incremental(self):
476 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
477 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
478 common.OPTIONS.incremental_source = ''
479 metadata = GetPackageMetadata(target_info, source_info)
480 self.assertDictEqual(
481 {
482 'ota-type' : 'BLOCK',
483 'post-build' : 'build-fingerprint-target',
484 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800485 'post-sdk-level' : '27',
486 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800487 'post-timestamp' : '1500000000',
488 'pre-device' : 'product-device',
489 'pre-build' : 'build-fingerprint-source',
490 'pre-build-incremental' : 'build-version-incremental-source',
491 },
492 metadata)
493
494 def test_GetPackageMetadata_wipe(self):
495 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
496 common.OPTIONS.wipe_user_data = True
497 metadata = GetPackageMetadata(target_info)
498 self.assertDictEqual(
499 {
500 'ota-type' : 'BLOCK',
501 'ota-wipe' : 'yes',
502 'post-build' : 'build-fingerprint-target',
503 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800504 'post-sdk-level' : '27',
505 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800506 'post-timestamp' : '1500000000',
507 'pre-device' : 'product-device',
508 },
509 metadata)
510
511 @staticmethod
512 def _test_GetPackageMetadata_swapBuildTimestamps(target_info, source_info):
513 (target_info['build.prop']['ro.build.date.utc'],
514 source_info['build.prop']['ro.build.date.utc']) = (
515 source_info['build.prop']['ro.build.date.utc'],
516 target_info['build.prop']['ro.build.date.utc'])
517
518 def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
519 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
520 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
521 self._test_GetPackageMetadata_swapBuildTimestamps(
522 target_info_dict, source_info_dict)
523
524 target_info = BuildInfo(target_info_dict, None)
525 source_info = BuildInfo(source_info_dict, None)
526 common.OPTIONS.incremental_source = ''
527 self.assertRaises(RuntimeError, GetPackageMetadata, target_info,
528 source_info)
529
530 def test_GetPackageMetadata_downgrade(self):
531 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
532 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
533 self._test_GetPackageMetadata_swapBuildTimestamps(
534 target_info_dict, source_info_dict)
535
536 target_info = BuildInfo(target_info_dict, None)
537 source_info = BuildInfo(source_info_dict, None)
538 common.OPTIONS.incremental_source = ''
539 common.OPTIONS.downgrade = True
540 common.OPTIONS.wipe_user_data = True
541 metadata = GetPackageMetadata(target_info, source_info)
542 self.assertDictEqual(
543 {
544 'ota-downgrade' : 'yes',
545 'ota-type' : 'BLOCK',
546 'ota-wipe' : 'yes',
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 Baofaa8e0b2018-04-12 14:31:43 -0700551 'post-timestamp' : '1400000000',
Tao Baodf3a48b2018-01-10 16:30:43 -0800552 '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:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700769 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800770 zip_fp, reserve_space=False)
771 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
Tao Bao69203522018-03-08 16:09:01 -0800772 tokens = self._parse_property_files_string(streaming_metadata)
Tao Baof5110492018-03-02 09:47:43 -0800773
774 self.assertEqual(3, len(tokens))
775 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
776 # streaming metadata.
777 entries[2] = 'metadata'
778 self._verify_entries(zip_file, tokens, entries)
779
Tao Baoae5e4c32018-03-01 19:30:00 -0800780 def test_Finalize_assertReservedLength(self):
Tao Baof5110492018-03-02 09:47:43 -0800781 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800782 'required-entry1',
783 'required-entry2',
784 'optional-entry1',
785 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800786 'META-INF/com/android/metadata',
787 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700788 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800789 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800790 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
791 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700792 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800793 zip_fp, reserve_space=False)
Tao Baof5110492018-03-02 09:47:43 -0800794 raw_length = len(raw_metadata)
795
796 # Now pass in the exact expected length.
Tao Baoae5e4c32018-03-01 19:30:00 -0800797 streaming_metadata = property_files.Finalize(zip_fp, raw_length)
Tao Baof5110492018-03-02 09:47:43 -0800798 self.assertEqual(raw_length, len(streaming_metadata))
799
800 # Or pass in insufficient length.
801 self.assertRaises(
Tao Bao3bf8c652018-03-16 12:59:42 -0700802 PropertyFiles.InsufficientSpaceException,
Tao Baoae5e4c32018-03-01 19:30:00 -0800803 property_files.Finalize,
Tao Baof5110492018-03-02 09:47:43 -0800804 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800805 raw_length - 1)
Tao Baof5110492018-03-02 09:47:43 -0800806
807 # Or pass in a much larger size.
Tao Baoae5e4c32018-03-01 19:30:00 -0800808 streaming_metadata = property_files.Finalize(
Tao Baof5110492018-03-02 09:47:43 -0800809 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800810 raw_length + 20)
Tao Baof5110492018-03-02 09:47:43 -0800811 self.assertEqual(raw_length + 20, len(streaming_metadata))
812 self.assertEqual(' ' * 20, streaming_metadata[raw_length:])
813
Tao Baoae5e4c32018-03-01 19:30:00 -0800814 def test_Verify(self):
815 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800816 'required-entry1',
817 'required-entry2',
818 'optional-entry1',
819 'optional-entry2',
820 'META-INF/com/android/metadata',
821 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700822 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800823 property_files = TestPropertyFiles()
824 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
825 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700826 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800827 zip_fp, reserve_space=False)
828
829 # Should pass the test if verification passes.
830 property_files.Verify(zip_fp, raw_metadata)
831
832 # Or raise on verification failure.
833 self.assertRaises(
834 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
835
836
837class StreamingPropertyFilesTest(PropertyFilesTest):
838 """Additional sanity checks specialized for StreamingPropertyFiles."""
839
840 def test_init(self):
841 property_files = StreamingPropertyFiles()
842 self.assertEqual('ota-streaming-property-files', property_files.name)
843 self.assertEqual(
844 (
845 'payload.bin',
846 'payload_properties.txt',
847 ),
848 property_files.required)
849 self.assertEqual(
850 (
851 'care_map.txt',
852 'compatibility.zip',
853 ),
854 property_files.optional)
855
856 def test_Compute(self):
857 entries = (
Tao Baoae5e4c32018-03-01 19:30:00 -0800858 'payload.bin',
859 'payload_properties.txt',
860 'care_map.txt',
Tao Bao69203522018-03-08 16:09:01 -0800861 'compatibility.zip',
862 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700863 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800864 property_files = StreamingPropertyFiles()
865 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
866 property_files_string = property_files.Compute(zip_fp)
867
868 tokens = self._parse_property_files_string(property_files_string)
869 self.assertEqual(5, len(tokens))
870 self._verify_entries(zip_file, tokens, entries)
871
872 def test_Finalize(self):
873 entries = [
874 'payload.bin',
875 'payload_properties.txt',
876 'care_map.txt',
877 'compatibility.zip',
878 'META-INF/com/android/metadata',
879 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700880 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800881 property_files = StreamingPropertyFiles()
882 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700883 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800884 zip_fp, reserve_space=False)
885 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
886 tokens = self._parse_property_files_string(streaming_metadata)
887
888 self.assertEqual(5, len(tokens))
889 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
890 # streaming metadata.
891 entries[4] = 'metadata'
892 self._verify_entries(zip_file, tokens, entries)
893
894 def test_Verify(self):
895 entries = (
896 'payload.bin',
897 'payload_properties.txt',
898 'care_map.txt',
899 'compatibility.zip',
Tao Baoae5e4c32018-03-01 19:30:00 -0800900 'META-INF/com/android/metadata',
901 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700902 zip_file = self.construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800903 property_files = StreamingPropertyFiles()
904 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
905 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700906 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800907 zip_fp, reserve_space=False)
908
909 # Should pass the test if verification passes.
910 property_files.Verify(zip_fp, raw_metadata)
911
912 # Or raise on verification failure.
913 self.assertRaises(
914 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
915
Tao Baofabe0832018-01-17 15:52:28 -0800916
Tao Baob6304672018-03-08 16:28:33 -0800917class AbOtaPropertyFilesTest(PropertyFilesTest):
918 """Additional sanity checks specialized for AbOtaPropertyFiles."""
919
920 # The size for payload and metadata signature size.
921 SIGNATURE_SIZE = 256
922
923 def setUp(self):
924 self.testdata_dir = test_utils.get_testdata_dir()
925 self.assertTrue(os.path.exists(self.testdata_dir))
926
927 common.OPTIONS.wipe_user_data = False
928 common.OPTIONS.payload_signer = None
929 common.OPTIONS.payload_signer_args = None
930 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
931 common.OPTIONS.key_passwords = {
932 common.OPTIONS.package_key : None,
933 }
934
935 def test_init(self):
936 property_files = AbOtaPropertyFiles()
937 self.assertEqual('ota-property-files', property_files.name)
938 self.assertEqual(
939 (
940 'payload.bin',
941 'payload_properties.txt',
942 ),
943 property_files.required)
944 self.assertEqual(
945 (
946 'care_map.txt',
947 'compatibility.zip',
948 ),
949 property_files.optional)
950
951 def test_GetPayloadMetadataOffsetAndSize(self):
952 target_file = construct_target_files()
953 payload = Payload()
954 payload.Generate(target_file)
955
956 payload_signer = PayloadSigner()
957 payload.Sign(payload_signer)
958
959 output_file = common.MakeTempFile(suffix='.zip')
960 with zipfile.ZipFile(output_file, 'w') as output_zip:
961 payload.WriteToZip(output_zip)
962
963 # Find out the payload metadata offset and size.
964 property_files = AbOtaPropertyFiles()
965 with zipfile.ZipFile(output_file) as input_zip:
966 # pylint: disable=protected-access
967 payload_offset, metadata_total = (
968 property_files._GetPayloadMetadataOffsetAndSize(input_zip))
969
970 # Read in the metadata signature directly.
971 with open(output_file, 'rb') as verify_fp:
972 verify_fp.seek(payload_offset + metadata_total - self.SIGNATURE_SIZE)
973 metadata_signature = verify_fp.read(self.SIGNATURE_SIZE)
974
975 # Now we extract the metadata hash via brillo_update_payload script, which
976 # will serve as the oracle result.
977 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
978 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
979 cmd = ['brillo_update_payload', 'hash',
980 '--unsigned_payload', payload.payload_file,
981 '--signature_size', str(self.SIGNATURE_SIZE),
982 '--metadata_hash_file', metadata_sig_file,
983 '--payload_hash_file', payload_sig_file]
984 proc = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
985 stdoutdata, _ = proc.communicate()
986 self.assertEqual(
987 0, proc.returncode,
988 'Failed to run brillo_update_payload: {}'.format(stdoutdata))
989
990 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
991
992 # Finally we can compare the two signatures.
993 with open(signed_metadata_sig_file, 'rb') as verify_fp:
994 self.assertEqual(verify_fp.read(), metadata_signature)
995
996 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -0700997 def construct_zip_package_withValidPayload(with_metadata=False):
998 # Cannot use construct_zip_package() since we need a "valid" payload.bin.
Tao Baob6304672018-03-08 16:28:33 -0800999 target_file = construct_target_files()
1000 payload = Payload()
1001 payload.Generate(target_file)
1002
1003 payload_signer = PayloadSigner()
1004 payload.Sign(payload_signer)
1005
1006 zip_file = common.MakeTempFile(suffix='.zip')
1007 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
1008 # 'payload.bin',
1009 payload.WriteToZip(zip_fp)
1010
1011 # Other entries.
1012 entries = ['care_map.txt', 'compatibility.zip']
1013
1014 # Put META-INF/com/android/metadata if needed.
1015 if with_metadata:
1016 entries.append('META-INF/com/android/metadata')
1017
1018 for entry in entries:
1019 zip_fp.writestr(
1020 entry, entry.replace('.', '-').upper(), zipfile.ZIP_STORED)
1021
1022 return zip_file
1023
1024 def test_Compute(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001025 zip_file = self.construct_zip_package_withValidPayload()
Tao Baob6304672018-03-08 16:28:33 -08001026 property_files = AbOtaPropertyFiles()
1027 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1028 property_files_string = property_files.Compute(zip_fp)
1029
1030 tokens = self._parse_property_files_string(property_files_string)
1031 # "6" indcludes the four entries above, one metadata entry, and one entry
1032 # for payload-metadata.bin.
1033 self.assertEqual(6, len(tokens))
1034 self._verify_entries(
1035 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1036
1037 def test_Finalize(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001038 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001039 property_files = AbOtaPropertyFiles()
1040 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001041 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001042 zip_fp, reserve_space=False)
1043 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1044
1045 tokens = self._parse_property_files_string(property_files_string)
1046 # "6" indcludes the four entries above, one metadata entry, and one entry
1047 # for payload-metadata.bin.
1048 self.assertEqual(6, len(tokens))
1049 self._verify_entries(
1050 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1051
1052 def test_Verify(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001053 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001054 property_files = AbOtaPropertyFiles()
1055 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001056 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001057 zip_fp, reserve_space=False)
1058
1059 property_files.Verify(zip_fp, raw_metadata)
1060
1061
Tao Baoc0746f42018-02-21 13:17:22 -08001062class NonAbOtaPropertyFilesTest(PropertyFilesTest):
1063 """Additional sanity checks specialized for NonAbOtaPropertyFiles."""
1064
1065 def test_init(self):
1066 property_files = NonAbOtaPropertyFiles()
1067 self.assertEqual('ota-property-files', property_files.name)
1068 self.assertEqual((), property_files.required)
1069 self.assertEqual((), property_files.optional)
1070
1071 def test_Compute(self):
1072 entries = ()
Tao Bao3bf8c652018-03-16 12:59:42 -07001073 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001074 property_files = NonAbOtaPropertyFiles()
1075 with zipfile.ZipFile(zip_file) as zip_fp:
1076 property_files_string = property_files.Compute(zip_fp)
1077
1078 tokens = self._parse_property_files_string(property_files_string)
1079 self.assertEqual(1, len(tokens))
1080 self._verify_entries(zip_file, tokens, entries)
1081
1082 def test_Finalize(self):
1083 entries = [
1084 'META-INF/com/android/metadata',
1085 ]
Tao Bao3bf8c652018-03-16 12:59:42 -07001086 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001087 property_files = NonAbOtaPropertyFiles()
1088 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001089 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001090 zip_fp, reserve_space=False)
1091 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1092 tokens = self._parse_property_files_string(property_files_string)
1093
1094 self.assertEqual(1, len(tokens))
1095 # 'META-INF/com/android/metadata' will be key'd as 'metadata'.
1096 entries[0] = 'metadata'
1097 self._verify_entries(zip_file, tokens, entries)
1098
1099 def test_Verify(self):
1100 entries = (
1101 'META-INF/com/android/metadata',
1102 )
Tao Bao3bf8c652018-03-16 12:59:42 -07001103 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001104 property_files = NonAbOtaPropertyFiles()
1105 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001106 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001107 zip_fp, reserve_space=False)
1108
1109 property_files.Verify(zip_fp, raw_metadata)
1110
1111
Tao Baofabe0832018-01-17 15:52:28 -08001112class PayloadSignerTest(unittest.TestCase):
1113
1114 SIGFILE = 'sigfile.bin'
1115 SIGNED_SIGFILE = 'signed-sigfile.bin'
1116
1117 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001118 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baofabe0832018-01-17 15:52:28 -08001119 self.assertTrue(os.path.exists(self.testdata_dir))
1120
1121 common.OPTIONS.payload_signer = None
1122 common.OPTIONS.payload_signer_args = []
1123 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1124 common.OPTIONS.key_passwords = {
1125 common.OPTIONS.package_key : None,
1126 }
1127
1128 def tearDown(self):
1129 common.Cleanup()
1130
1131 def _assertFilesEqual(self, file1, file2):
1132 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2:
1133 self.assertEqual(fp1.read(), fp2.read())
1134
1135 def test_init(self):
1136 payload_signer = PayloadSigner()
1137 self.assertEqual('openssl', payload_signer.signer)
1138
1139 def test_init_withPassword(self):
1140 common.OPTIONS.package_key = os.path.join(
1141 self.testdata_dir, 'testkey_with_passwd')
1142 common.OPTIONS.key_passwords = {
1143 common.OPTIONS.package_key : 'foo',
1144 }
1145 payload_signer = PayloadSigner()
1146 self.assertEqual('openssl', payload_signer.signer)
1147
1148 def test_init_withExternalSigner(self):
1149 common.OPTIONS.payload_signer = 'abc'
1150 common.OPTIONS.payload_signer_args = ['arg1', 'arg2']
1151 payload_signer = PayloadSigner()
1152 self.assertEqual('abc', payload_signer.signer)
1153 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args)
1154
1155 def test_Sign(self):
1156 payload_signer = PayloadSigner()
1157 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1158 signed_file = payload_signer.Sign(input_file)
1159
1160 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1161 self._assertFilesEqual(verify_file, signed_file)
1162
1163 def test_Sign_withExternalSigner_openssl(self):
1164 """Uses openssl as the external payload signer."""
1165 common.OPTIONS.payload_signer = 'openssl'
1166 common.OPTIONS.payload_signer_args = [
1167 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey',
1168 os.path.join(self.testdata_dir, 'testkey.pk8'),
1169 '-pkeyopt', 'digest:sha256']
1170 payload_signer = PayloadSigner()
1171 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1172 signed_file = payload_signer.Sign(input_file)
1173
1174 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1175 self._assertFilesEqual(verify_file, signed_file)
1176
1177 def test_Sign_withExternalSigner_script(self):
1178 """Uses testdata/payload_signer.sh as the external payload signer."""
1179 common.OPTIONS.payload_signer = os.path.join(
1180 self.testdata_dir, 'payload_signer.sh')
1181 common.OPTIONS.payload_signer_args = [
1182 os.path.join(self.testdata_dir, 'testkey.pk8')]
1183 payload_signer = PayloadSigner()
1184 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1185 signed_file = payload_signer.Sign(input_file)
1186
1187 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1188 self._assertFilesEqual(verify_file, signed_file)
Tao Baoc7b403a2018-01-30 18:19:04 -08001189
1190
1191class PayloadTest(unittest.TestCase):
1192
1193 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001194 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baoc7b403a2018-01-30 18:19:04 -08001195 self.assertTrue(os.path.exists(self.testdata_dir))
1196
1197 common.OPTIONS.wipe_user_data = False
1198 common.OPTIONS.payload_signer = None
1199 common.OPTIONS.payload_signer_args = None
1200 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1201 common.OPTIONS.key_passwords = {
1202 common.OPTIONS.package_key : None,
1203 }
1204
1205 def tearDown(self):
1206 common.Cleanup()
1207
1208 @staticmethod
Tao Baof7140c02018-01-30 17:09:24 -08001209 def _create_payload_full(secondary=False):
1210 target_file = construct_target_files(secondary)
Tao Bao667ff572018-02-10 00:02:40 -08001211 payload = Payload(secondary)
Tao Baoc7b403a2018-01-30 18:19:04 -08001212 payload.Generate(target_file)
1213 return payload
1214
Tao Baof7140c02018-01-30 17:09:24 -08001215 @staticmethod
1216 def _create_payload_incremental():
1217 target_file = construct_target_files()
1218 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001219 payload = Payload()
1220 payload.Generate(target_file, source_file)
1221 return payload
1222
1223 def test_Generate_full(self):
1224 payload = self._create_payload_full()
1225 self.assertTrue(os.path.exists(payload.payload_file))
1226
1227 def test_Generate_incremental(self):
1228 payload = self._create_payload_incremental()
1229 self.assertTrue(os.path.exists(payload.payload_file))
1230
1231 def test_Generate_additionalArgs(self):
Tao Baof7140c02018-01-30 17:09:24 -08001232 target_file = construct_target_files()
1233 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001234 payload = Payload()
1235 # This should work the same as calling payload.Generate(target_file,
1236 # source_file).
1237 payload.Generate(
1238 target_file, additional_args=["--source_image", source_file])
1239 self.assertTrue(os.path.exists(payload.payload_file))
1240
1241 def test_Generate_invalidInput(self):
Tao Baof7140c02018-01-30 17:09:24 -08001242 target_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001243 common.ZipDelete(target_file, 'IMAGES/vendor.img')
1244 payload = Payload()
1245 self.assertRaises(AssertionError, payload.Generate, target_file)
1246
1247 def test_Sign_full(self):
1248 payload = self._create_payload_full()
1249 payload.Sign(PayloadSigner())
1250
1251 output_file = common.MakeTempFile(suffix='.zip')
1252 with zipfile.ZipFile(output_file, 'w') as output_zip:
1253 payload.WriteToZip(output_zip)
1254
1255 import check_ota_package_signature
1256 check_ota_package_signature.VerifyAbOtaPayload(
1257 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1258 output_file)
1259
1260 def test_Sign_incremental(self):
1261 payload = self._create_payload_incremental()
1262 payload.Sign(PayloadSigner())
1263
1264 output_file = common.MakeTempFile(suffix='.zip')
1265 with zipfile.ZipFile(output_file, 'w') as output_zip:
1266 payload.WriteToZip(output_zip)
1267
1268 import check_ota_package_signature
1269 check_ota_package_signature.VerifyAbOtaPayload(
1270 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1271 output_file)
1272
1273 def test_Sign_withDataWipe(self):
1274 common.OPTIONS.wipe_user_data = True
1275 payload = self._create_payload_full()
1276 payload.Sign(PayloadSigner())
1277
1278 with open(payload.payload_properties) as properties_fp:
1279 self.assertIn("POWERWASH=1", properties_fp.read())
1280
Tao Bao667ff572018-02-10 00:02:40 -08001281 def test_Sign_secondary(self):
1282 payload = self._create_payload_full(secondary=True)
1283 payload.Sign(PayloadSigner())
1284
1285 with open(payload.payload_properties) as properties_fp:
1286 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read())
1287
Tao Baoc7b403a2018-01-30 18:19:04 -08001288 def test_Sign_badSigner(self):
1289 """Tests that signing failure can be captured."""
1290 payload = self._create_payload_full()
1291 payload_signer = PayloadSigner()
1292 payload_signer.signer_args.append('bad-option')
1293 self.assertRaises(AssertionError, payload.Sign, payload_signer)
1294
1295 def test_WriteToZip(self):
1296 payload = self._create_payload_full()
1297 payload.Sign(PayloadSigner())
1298
1299 output_file = common.MakeTempFile(suffix='.zip')
1300 with zipfile.ZipFile(output_file, 'w') as output_zip:
1301 payload.WriteToZip(output_zip)
1302
1303 with zipfile.ZipFile(output_file) as verify_zip:
1304 # First make sure we have the essential entries.
1305 namelist = verify_zip.namelist()
1306 self.assertIn(Payload.PAYLOAD_BIN, namelist)
1307 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
1308
1309 # Then assert these entries are stored.
1310 for entry_info in verify_zip.infolist():
1311 if entry_info.filename not in (Payload.PAYLOAD_BIN,
1312 Payload.PAYLOAD_PROPERTIES_TXT):
1313 continue
1314 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
1315
1316 def test_WriteToZip_unsignedPayload(self):
1317 """Unsigned payloads should not be allowed to be written to zip."""
1318 payload = self._create_payload_full()
1319
1320 output_file = common.MakeTempFile(suffix='.zip')
1321 with zipfile.ZipFile(output_file, 'w') as output_zip:
1322 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
1323
1324 # Also test with incremental payload.
1325 payload = self._create_payload_incremental()
1326
1327 output_file = common.MakeTempFile(suffix='.zip')
1328 with zipfile.ZipFile(output_file, 'w') as output_zip:
1329 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001330
1331 def test_WriteToZip_secondary(self):
1332 payload = self._create_payload_full(secondary=True)
1333 payload.Sign(PayloadSigner())
1334
1335 output_file = common.MakeTempFile(suffix='.zip')
1336 with zipfile.ZipFile(output_file, 'w') as output_zip:
Tao Bao667ff572018-02-10 00:02:40 -08001337 payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001338
1339 with zipfile.ZipFile(output_file) as verify_zip:
1340 # First make sure we have the essential entries.
1341 namelist = verify_zip.namelist()
1342 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist)
1343 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist)
1344
1345 # Then assert these entries are stored.
1346 for entry_info in verify_zip.infolist():
1347 if entry_info.filename not in (
1348 Payload.SECONDARY_PAYLOAD_BIN,
1349 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
1350 continue
1351 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)