blob: c8e6750c59b50b0521dbec2d69586c284af39973 [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 Bao12489802018-07-12 14:47:38 -0700569 self.assertIn('RADIO/bootloader.img', namelist)
570 self.assertIn('RADIO/modem.img', namelist)
Tao Bao15a146a2018-02-21 16:06:59 -0800571 self.assertIn(POSTINSTALL_CONFIG, namelist)
Tao Baof7140c02018-01-30 17:09:24 -0800572
573 self.assertNotIn('IMAGES/system_other.img', namelist)
574 self.assertNotIn('IMAGES/system.map', namelist)
575
Tao Bao15a146a2018-02-21 16:06:59 -0800576 def test_GetTargetFilesZipForSecondaryImages_skipPostinstall(self):
577 input_file = construct_target_files(secondary=True)
578 target_file = GetTargetFilesZipForSecondaryImages(
579 input_file, skip_postinstall=True)
580
581 with zipfile.ZipFile(target_file) as verify_zip:
582 namelist = verify_zip.namelist()
583
584 self.assertIn('META/ab_partitions.txt', namelist)
585 self.assertIn('IMAGES/boot.img', namelist)
586 self.assertIn('IMAGES/system.img', namelist)
587 self.assertIn('IMAGES/vendor.img', namelist)
Tao Bao12489802018-07-12 14:47:38 -0700588 self.assertIn('RADIO/bootloader.img', namelist)
589 self.assertIn('RADIO/modem.img', namelist)
Tao Bao15a146a2018-02-21 16:06:59 -0800590
591 self.assertNotIn('IMAGES/system_other.img', namelist)
592 self.assertNotIn('IMAGES/system.map', namelist)
593 self.assertNotIn(POSTINSTALL_CONFIG, namelist)
594
Tao Bao12489802018-07-12 14:47:38 -0700595 def test_GetTargetFilesZipForSecondaryImages_withoutRadioImages(self):
596 input_file = construct_target_files(secondary=True)
597 common.ZipDelete(input_file, 'RADIO/bootloader.img')
598 common.ZipDelete(input_file, 'RADIO/modem.img')
599 target_file = GetTargetFilesZipForSecondaryImages(input_file)
600
601 with zipfile.ZipFile(target_file) as verify_zip:
602 namelist = verify_zip.namelist()
603
604 self.assertIn('META/ab_partitions.txt', namelist)
605 self.assertIn('IMAGES/boot.img', namelist)
606 self.assertIn('IMAGES/system.img', namelist)
607 self.assertIn('IMAGES/vendor.img', namelist)
608 self.assertIn(POSTINSTALL_CONFIG, namelist)
609
610 self.assertNotIn('IMAGES/system_other.img', namelist)
611 self.assertNotIn('IMAGES/system.map', namelist)
612 self.assertNotIn('RADIO/bootloader.img', namelist)
613 self.assertNotIn('RADIO/modem.img', namelist)
614
Tao Bao15a146a2018-02-21 16:06:59 -0800615 def test_GetTargetFilesZipWithoutPostinstallConfig(self):
616 input_file = construct_target_files()
617 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
618 with zipfile.ZipFile(target_file) as verify_zip:
619 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
620
621 def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self):
622 input_file = construct_target_files()
623 common.ZipDelete(input_file, POSTINSTALL_CONFIG)
624 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
625 with zipfile.ZipFile(target_file) as verify_zip:
626 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
627
Tao Bao3bf8c652018-03-16 12:59:42 -0700628 def _test_FinalizeMetadata(self, large_entry=False):
629 entries = [
630 'required-entry1',
631 'required-entry2',
632 ]
633 zip_file = PropertyFilesTest.construct_zip_package(entries)
634 # Add a large entry of 1 GiB if requested.
635 if large_entry:
636 with zipfile.ZipFile(zip_file, 'a') as zip_fp:
637 zip_fp.writestr(
638 # Using 'zoo' so that the entry stays behind others after signing.
639 'zoo',
640 'A' * 1024 * 1024 * 1024,
641 zipfile.ZIP_STORED)
642
643 metadata = {}
644 output_file = common.MakeTempFile(suffix='.zip')
645 needed_property_files = (
646 TestPropertyFiles(),
647 )
648 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
649 self.assertIn('ota-test-property-files', metadata)
650
651 def test_FinalizeMetadata(self):
652 self._test_FinalizeMetadata()
653
654 def test_FinalizeMetadata_withNoSigning(self):
655 common.OPTIONS.no_signing = True
656 self._test_FinalizeMetadata()
657
658 def test_FinalizeMetadata_largeEntry(self):
659 self._test_FinalizeMetadata(large_entry=True)
660
661 def test_FinalizeMetadata_largeEntry_withNoSigning(self):
662 common.OPTIONS.no_signing = True
663 self._test_FinalizeMetadata(large_entry=True)
664
665 def test_FinalizeMetadata_insufficientSpace(self):
666 entries = [
667 'required-entry1',
668 'required-entry2',
669 'optional-entry1',
670 'optional-entry2',
671 ]
672 zip_file = PropertyFilesTest.construct_zip_package(entries)
673 with zipfile.ZipFile(zip_file, 'a') as zip_fp:
674 zip_fp.writestr(
675 # 'foo-entry1' will appear ahead of all other entries (in alphabetical
676 # order) after the signing, which will in turn trigger the
677 # InsufficientSpaceException and an automatic retry.
678 'foo-entry1',
679 'A' * 1024 * 1024,
680 zipfile.ZIP_STORED)
681
682 metadata = {}
683 needed_property_files = (
684 TestPropertyFiles(),
685 )
686 output_file = common.MakeTempFile(suffix='.zip')
687 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
688 self.assertIn('ota-test-property-files', metadata)
689
Tao Baoae5e4c32018-03-01 19:30:00 -0800690
Tao Bao69203522018-03-08 16:09:01 -0800691class TestPropertyFiles(PropertyFiles):
692 """A class that extends PropertyFiles for testing purpose."""
693
694 def __init__(self):
695 super(TestPropertyFiles, self).__init__()
696 self.name = 'ota-test-property-files'
697 self.required = (
698 'required-entry1',
699 'required-entry2',
700 )
701 self.optional = (
702 'optional-entry1',
703 'optional-entry2',
704 )
705
706
707class PropertyFilesTest(unittest.TestCase):
Tao Baoae5e4c32018-03-01 19:30:00 -0800708
Tao Bao3bf8c652018-03-16 12:59:42 -0700709 def setUp(self):
710 common.OPTIONS.no_signing = False
711
Tao Baoae5e4c32018-03-01 19:30:00 -0800712 def tearDown(self):
713 common.Cleanup()
714
Tao Baof5110492018-03-02 09:47:43 -0800715 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -0700716 def construct_zip_package(entries):
Tao Baof5110492018-03-02 09:47:43 -0800717 zip_file = common.MakeTempFile(suffix='.zip')
718 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
719 for entry in entries:
720 zip_fp.writestr(
721 entry,
722 entry.replace('.', '-').upper(),
723 zipfile.ZIP_STORED)
724 return zip_file
725
726 @staticmethod
Tao Bao69203522018-03-08 16:09:01 -0800727 def _parse_property_files_string(data):
Tao Baof5110492018-03-02 09:47:43 -0800728 result = {}
729 for token in data.split(','):
730 name, info = token.split(':', 1)
731 result[name] = info
732 return result
733
734 def _verify_entries(self, input_file, tokens, entries):
735 for entry in entries:
736 offset, size = map(int, tokens[entry].split(':'))
737 with open(input_file, 'rb') as input_fp:
738 input_fp.seek(offset)
739 if entry == 'metadata':
740 expected = b'META-INF/COM/ANDROID/METADATA'
741 else:
742 expected = entry.replace('.', '-').upper().encode()
743 self.assertEqual(expected, input_fp.read(size))
744
Tao Baoae5e4c32018-03-01 19:30:00 -0800745 def test_Compute(self):
Tao Baof5110492018-03-02 09:47:43 -0800746 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800747 'required-entry1',
748 'required-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800749 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700750 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800751 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800752 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Bao69203522018-03-08 16:09:01 -0800753 property_files_string = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800754
Tao Bao69203522018-03-08 16:09:01 -0800755 tokens = self._parse_property_files_string(property_files_string)
Tao Baof5110492018-03-02 09:47:43 -0800756 self.assertEqual(3, len(tokens))
757 self._verify_entries(zip_file, tokens, entries)
758
Tao Bao69203522018-03-08 16:09:01 -0800759 def test_Compute_withOptionalEntries(self):
Tao Baof5110492018-03-02 09:47:43 -0800760 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800761 'required-entry1',
762 'required-entry2',
763 'optional-entry1',
764 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800765 )
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 property_files_string = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800770
Tao Bao69203522018-03-08 16:09:01 -0800771 tokens = self._parse_property_files_string(property_files_string)
Tao Baof5110492018-03-02 09:47:43 -0800772 self.assertEqual(5, len(tokens))
773 self._verify_entries(zip_file, tokens, entries)
774
Tao Bao69203522018-03-08 16:09:01 -0800775 def test_Compute_missingRequiredEntry(self):
776 entries = (
777 'required-entry2',
778 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700779 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800780 property_files = TestPropertyFiles()
781 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
782 self.assertRaises(KeyError, property_files.Compute, zip_fp)
783
Tao Baoae5e4c32018-03-01 19:30:00 -0800784 def test_Finalize(self):
Tao Baof5110492018-03-02 09:47:43 -0800785 entries = [
Tao Bao69203522018-03-08 16:09:01 -0800786 'required-entry1',
787 'required-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800788 'META-INF/com/android/metadata',
789 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700790 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800791 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800792 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700793 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800794 zip_fp, reserve_space=False)
795 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
Tao Bao69203522018-03-08 16:09:01 -0800796 tokens = self._parse_property_files_string(streaming_metadata)
Tao Baof5110492018-03-02 09:47:43 -0800797
798 self.assertEqual(3, len(tokens))
799 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
800 # streaming metadata.
801 entries[2] = 'metadata'
802 self._verify_entries(zip_file, tokens, entries)
803
Tao Baoae5e4c32018-03-01 19:30:00 -0800804 def test_Finalize_assertReservedLength(self):
Tao Baof5110492018-03-02 09:47:43 -0800805 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800806 'required-entry1',
807 'required-entry2',
808 'optional-entry1',
809 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800810 'META-INF/com/android/metadata',
811 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700812 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800813 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800814 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
815 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700816 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800817 zip_fp, reserve_space=False)
Tao Baof5110492018-03-02 09:47:43 -0800818 raw_length = len(raw_metadata)
819
820 # Now pass in the exact expected length.
Tao Baoae5e4c32018-03-01 19:30:00 -0800821 streaming_metadata = property_files.Finalize(zip_fp, raw_length)
Tao Baof5110492018-03-02 09:47:43 -0800822 self.assertEqual(raw_length, len(streaming_metadata))
823
824 # Or pass in insufficient length.
825 self.assertRaises(
Tao Bao3bf8c652018-03-16 12:59:42 -0700826 PropertyFiles.InsufficientSpaceException,
Tao Baoae5e4c32018-03-01 19:30:00 -0800827 property_files.Finalize,
Tao Baof5110492018-03-02 09:47:43 -0800828 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800829 raw_length - 1)
Tao Baof5110492018-03-02 09:47:43 -0800830
831 # Or pass in a much larger size.
Tao Baoae5e4c32018-03-01 19:30:00 -0800832 streaming_metadata = property_files.Finalize(
Tao Baof5110492018-03-02 09:47:43 -0800833 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800834 raw_length + 20)
Tao Baof5110492018-03-02 09:47:43 -0800835 self.assertEqual(raw_length + 20, len(streaming_metadata))
836 self.assertEqual(' ' * 20, streaming_metadata[raw_length:])
837
Tao Baoae5e4c32018-03-01 19:30:00 -0800838 def test_Verify(self):
839 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800840 'required-entry1',
841 'required-entry2',
842 'optional-entry1',
843 'optional-entry2',
844 'META-INF/com/android/metadata',
845 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700846 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800847 property_files = TestPropertyFiles()
848 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
849 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700850 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800851 zip_fp, reserve_space=False)
852
853 # Should pass the test if verification passes.
854 property_files.Verify(zip_fp, raw_metadata)
855
856 # Or raise on verification failure.
857 self.assertRaises(
858 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
859
860
861class StreamingPropertyFilesTest(PropertyFilesTest):
862 """Additional sanity checks specialized for StreamingPropertyFiles."""
863
864 def test_init(self):
865 property_files = StreamingPropertyFiles()
866 self.assertEqual('ota-streaming-property-files', property_files.name)
867 self.assertEqual(
868 (
869 'payload.bin',
870 'payload_properties.txt',
871 ),
872 property_files.required)
873 self.assertEqual(
874 (
875 'care_map.txt',
876 'compatibility.zip',
877 ),
878 property_files.optional)
879
880 def test_Compute(self):
881 entries = (
Tao Baoae5e4c32018-03-01 19:30:00 -0800882 'payload.bin',
883 'payload_properties.txt',
884 'care_map.txt',
Tao Bao69203522018-03-08 16:09:01 -0800885 'compatibility.zip',
886 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700887 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800888 property_files = StreamingPropertyFiles()
889 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
890 property_files_string = property_files.Compute(zip_fp)
891
892 tokens = self._parse_property_files_string(property_files_string)
893 self.assertEqual(5, len(tokens))
894 self._verify_entries(zip_file, tokens, entries)
895
896 def test_Finalize(self):
897 entries = [
898 'payload.bin',
899 'payload_properties.txt',
900 'care_map.txt',
901 'compatibility.zip',
902 'META-INF/com/android/metadata',
903 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700904 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800905 property_files = StreamingPropertyFiles()
906 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700907 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800908 zip_fp, reserve_space=False)
909 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
910 tokens = self._parse_property_files_string(streaming_metadata)
911
912 self.assertEqual(5, len(tokens))
913 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
914 # streaming metadata.
915 entries[4] = 'metadata'
916 self._verify_entries(zip_file, tokens, entries)
917
918 def test_Verify(self):
919 entries = (
920 'payload.bin',
921 'payload_properties.txt',
922 'care_map.txt',
923 'compatibility.zip',
Tao Baoae5e4c32018-03-01 19:30:00 -0800924 'META-INF/com/android/metadata',
925 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700926 zip_file = self.construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800927 property_files = StreamingPropertyFiles()
928 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
929 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700930 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800931 zip_fp, reserve_space=False)
932
933 # Should pass the test if verification passes.
934 property_files.Verify(zip_fp, raw_metadata)
935
936 # Or raise on verification failure.
937 self.assertRaises(
938 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
939
Tao Baofabe0832018-01-17 15:52:28 -0800940
Tao Baob6304672018-03-08 16:28:33 -0800941class AbOtaPropertyFilesTest(PropertyFilesTest):
942 """Additional sanity checks specialized for AbOtaPropertyFiles."""
943
944 # The size for payload and metadata signature size.
945 SIGNATURE_SIZE = 256
946
947 def setUp(self):
948 self.testdata_dir = test_utils.get_testdata_dir()
949 self.assertTrue(os.path.exists(self.testdata_dir))
950
951 common.OPTIONS.wipe_user_data = False
952 common.OPTIONS.payload_signer = None
953 common.OPTIONS.payload_signer_args = None
954 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
955 common.OPTIONS.key_passwords = {
956 common.OPTIONS.package_key : None,
957 }
958
959 def test_init(self):
960 property_files = AbOtaPropertyFiles()
961 self.assertEqual('ota-property-files', property_files.name)
962 self.assertEqual(
963 (
964 'payload.bin',
965 'payload_properties.txt',
966 ),
967 property_files.required)
968 self.assertEqual(
969 (
970 'care_map.txt',
971 'compatibility.zip',
972 ),
973 property_files.optional)
974
975 def test_GetPayloadMetadataOffsetAndSize(self):
976 target_file = construct_target_files()
977 payload = Payload()
978 payload.Generate(target_file)
979
980 payload_signer = PayloadSigner()
981 payload.Sign(payload_signer)
982
983 output_file = common.MakeTempFile(suffix='.zip')
984 with zipfile.ZipFile(output_file, 'w') as output_zip:
985 payload.WriteToZip(output_zip)
986
987 # Find out the payload metadata offset and size.
988 property_files = AbOtaPropertyFiles()
989 with zipfile.ZipFile(output_file) as input_zip:
990 # pylint: disable=protected-access
991 payload_offset, metadata_total = (
992 property_files._GetPayloadMetadataOffsetAndSize(input_zip))
993
994 # Read in the metadata signature directly.
995 with open(output_file, 'rb') as verify_fp:
996 verify_fp.seek(payload_offset + metadata_total - self.SIGNATURE_SIZE)
997 metadata_signature = verify_fp.read(self.SIGNATURE_SIZE)
998
999 # Now we extract the metadata hash via brillo_update_payload script, which
1000 # will serve as the oracle result.
1001 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1002 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1003 cmd = ['brillo_update_payload', 'hash',
1004 '--unsigned_payload', payload.payload_file,
1005 '--signature_size', str(self.SIGNATURE_SIZE),
1006 '--metadata_hash_file', metadata_sig_file,
1007 '--payload_hash_file', payload_sig_file]
1008 proc = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1009 stdoutdata, _ = proc.communicate()
1010 self.assertEqual(
1011 0, proc.returncode,
1012 'Failed to run brillo_update_payload: {}'.format(stdoutdata))
1013
1014 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
1015
1016 # Finally we can compare the two signatures.
1017 with open(signed_metadata_sig_file, 'rb') as verify_fp:
1018 self.assertEqual(verify_fp.read(), metadata_signature)
1019
1020 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -07001021 def construct_zip_package_withValidPayload(with_metadata=False):
1022 # Cannot use construct_zip_package() since we need a "valid" payload.bin.
Tao Baob6304672018-03-08 16:28:33 -08001023 target_file = construct_target_files()
1024 payload = Payload()
1025 payload.Generate(target_file)
1026
1027 payload_signer = PayloadSigner()
1028 payload.Sign(payload_signer)
1029
1030 zip_file = common.MakeTempFile(suffix='.zip')
1031 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
1032 # 'payload.bin',
1033 payload.WriteToZip(zip_fp)
1034
1035 # Other entries.
1036 entries = ['care_map.txt', 'compatibility.zip']
1037
1038 # Put META-INF/com/android/metadata if needed.
1039 if with_metadata:
1040 entries.append('META-INF/com/android/metadata')
1041
1042 for entry in entries:
1043 zip_fp.writestr(
1044 entry, entry.replace('.', '-').upper(), zipfile.ZIP_STORED)
1045
1046 return zip_file
1047
1048 def test_Compute(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001049 zip_file = self.construct_zip_package_withValidPayload()
Tao Baob6304672018-03-08 16:28:33 -08001050 property_files = AbOtaPropertyFiles()
1051 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1052 property_files_string = property_files.Compute(zip_fp)
1053
1054 tokens = self._parse_property_files_string(property_files_string)
1055 # "6" indcludes the four entries above, one metadata entry, and one entry
1056 # for payload-metadata.bin.
1057 self.assertEqual(6, len(tokens))
1058 self._verify_entries(
1059 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1060
1061 def test_Finalize(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001062 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001063 property_files = AbOtaPropertyFiles()
1064 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001065 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001066 zip_fp, reserve_space=False)
1067 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1068
1069 tokens = self._parse_property_files_string(property_files_string)
1070 # "6" indcludes the four entries above, one metadata entry, and one entry
1071 # for payload-metadata.bin.
1072 self.assertEqual(6, len(tokens))
1073 self._verify_entries(
1074 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1075
1076 def test_Verify(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001077 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001078 property_files = AbOtaPropertyFiles()
1079 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001080 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001081 zip_fp, reserve_space=False)
1082
1083 property_files.Verify(zip_fp, raw_metadata)
1084
1085
Tao Baoc0746f42018-02-21 13:17:22 -08001086class NonAbOtaPropertyFilesTest(PropertyFilesTest):
1087 """Additional sanity checks specialized for NonAbOtaPropertyFiles."""
1088
1089 def test_init(self):
1090 property_files = NonAbOtaPropertyFiles()
1091 self.assertEqual('ota-property-files', property_files.name)
1092 self.assertEqual((), property_files.required)
1093 self.assertEqual((), property_files.optional)
1094
1095 def test_Compute(self):
1096 entries = ()
Tao Bao3bf8c652018-03-16 12:59:42 -07001097 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001098 property_files = NonAbOtaPropertyFiles()
1099 with zipfile.ZipFile(zip_file) as zip_fp:
1100 property_files_string = property_files.Compute(zip_fp)
1101
1102 tokens = self._parse_property_files_string(property_files_string)
1103 self.assertEqual(1, len(tokens))
1104 self._verify_entries(zip_file, tokens, entries)
1105
1106 def test_Finalize(self):
1107 entries = [
1108 'META-INF/com/android/metadata',
1109 ]
Tao Bao3bf8c652018-03-16 12:59:42 -07001110 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001111 property_files = NonAbOtaPropertyFiles()
1112 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001113 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001114 zip_fp, reserve_space=False)
1115 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1116 tokens = self._parse_property_files_string(property_files_string)
1117
1118 self.assertEqual(1, len(tokens))
1119 # 'META-INF/com/android/metadata' will be key'd as 'metadata'.
1120 entries[0] = 'metadata'
1121 self._verify_entries(zip_file, tokens, entries)
1122
1123 def test_Verify(self):
1124 entries = (
1125 'META-INF/com/android/metadata',
1126 )
Tao Bao3bf8c652018-03-16 12:59:42 -07001127 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001128 property_files = NonAbOtaPropertyFiles()
1129 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001130 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001131 zip_fp, reserve_space=False)
1132
1133 property_files.Verify(zip_fp, raw_metadata)
1134
1135
Tao Baofabe0832018-01-17 15:52:28 -08001136class PayloadSignerTest(unittest.TestCase):
1137
1138 SIGFILE = 'sigfile.bin'
1139 SIGNED_SIGFILE = 'signed-sigfile.bin'
1140
1141 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001142 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baofabe0832018-01-17 15:52:28 -08001143 self.assertTrue(os.path.exists(self.testdata_dir))
1144
1145 common.OPTIONS.payload_signer = None
1146 common.OPTIONS.payload_signer_args = []
1147 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1148 common.OPTIONS.key_passwords = {
1149 common.OPTIONS.package_key : None,
1150 }
1151
1152 def tearDown(self):
1153 common.Cleanup()
1154
1155 def _assertFilesEqual(self, file1, file2):
1156 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2:
1157 self.assertEqual(fp1.read(), fp2.read())
1158
1159 def test_init(self):
1160 payload_signer = PayloadSigner()
1161 self.assertEqual('openssl', payload_signer.signer)
1162
1163 def test_init_withPassword(self):
1164 common.OPTIONS.package_key = os.path.join(
1165 self.testdata_dir, 'testkey_with_passwd')
1166 common.OPTIONS.key_passwords = {
1167 common.OPTIONS.package_key : 'foo',
1168 }
1169 payload_signer = PayloadSigner()
1170 self.assertEqual('openssl', payload_signer.signer)
1171
1172 def test_init_withExternalSigner(self):
1173 common.OPTIONS.payload_signer = 'abc'
1174 common.OPTIONS.payload_signer_args = ['arg1', 'arg2']
1175 payload_signer = PayloadSigner()
1176 self.assertEqual('abc', payload_signer.signer)
1177 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args)
1178
1179 def test_Sign(self):
1180 payload_signer = PayloadSigner()
1181 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1182 signed_file = payload_signer.Sign(input_file)
1183
1184 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1185 self._assertFilesEqual(verify_file, signed_file)
1186
1187 def test_Sign_withExternalSigner_openssl(self):
1188 """Uses openssl as the external payload signer."""
1189 common.OPTIONS.payload_signer = 'openssl'
1190 common.OPTIONS.payload_signer_args = [
1191 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey',
1192 os.path.join(self.testdata_dir, 'testkey.pk8'),
1193 '-pkeyopt', 'digest:sha256']
1194 payload_signer = PayloadSigner()
1195 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1196 signed_file = payload_signer.Sign(input_file)
1197
1198 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1199 self._assertFilesEqual(verify_file, signed_file)
1200
1201 def test_Sign_withExternalSigner_script(self):
1202 """Uses testdata/payload_signer.sh as the external payload signer."""
1203 common.OPTIONS.payload_signer = os.path.join(
1204 self.testdata_dir, 'payload_signer.sh')
1205 common.OPTIONS.payload_signer_args = [
1206 os.path.join(self.testdata_dir, 'testkey.pk8')]
1207 payload_signer = PayloadSigner()
1208 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1209 signed_file = payload_signer.Sign(input_file)
1210
1211 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1212 self._assertFilesEqual(verify_file, signed_file)
Tao Baoc7b403a2018-01-30 18:19:04 -08001213
1214
1215class PayloadTest(unittest.TestCase):
1216
1217 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001218 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baoc7b403a2018-01-30 18:19:04 -08001219 self.assertTrue(os.path.exists(self.testdata_dir))
1220
1221 common.OPTIONS.wipe_user_data = False
1222 common.OPTIONS.payload_signer = None
1223 common.OPTIONS.payload_signer_args = None
1224 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1225 common.OPTIONS.key_passwords = {
1226 common.OPTIONS.package_key : None,
1227 }
1228
1229 def tearDown(self):
1230 common.Cleanup()
1231
1232 @staticmethod
Tao Baof7140c02018-01-30 17:09:24 -08001233 def _create_payload_full(secondary=False):
1234 target_file = construct_target_files(secondary)
Tao Bao667ff572018-02-10 00:02:40 -08001235 payload = Payload(secondary)
Tao Baoc7b403a2018-01-30 18:19:04 -08001236 payload.Generate(target_file)
1237 return payload
1238
Tao Baof7140c02018-01-30 17:09:24 -08001239 @staticmethod
1240 def _create_payload_incremental():
1241 target_file = construct_target_files()
1242 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001243 payload = Payload()
1244 payload.Generate(target_file, source_file)
1245 return payload
1246
1247 def test_Generate_full(self):
1248 payload = self._create_payload_full()
1249 self.assertTrue(os.path.exists(payload.payload_file))
1250
1251 def test_Generate_incremental(self):
1252 payload = self._create_payload_incremental()
1253 self.assertTrue(os.path.exists(payload.payload_file))
1254
1255 def test_Generate_additionalArgs(self):
Tao Baof7140c02018-01-30 17:09:24 -08001256 target_file = construct_target_files()
1257 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001258 payload = Payload()
1259 # This should work the same as calling payload.Generate(target_file,
1260 # source_file).
1261 payload.Generate(
1262 target_file, additional_args=["--source_image", source_file])
1263 self.assertTrue(os.path.exists(payload.payload_file))
1264
1265 def test_Generate_invalidInput(self):
Tao Baof7140c02018-01-30 17:09:24 -08001266 target_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001267 common.ZipDelete(target_file, 'IMAGES/vendor.img')
1268 payload = Payload()
1269 self.assertRaises(AssertionError, payload.Generate, target_file)
1270
1271 def test_Sign_full(self):
1272 payload = self._create_payload_full()
1273 payload.Sign(PayloadSigner())
1274
1275 output_file = common.MakeTempFile(suffix='.zip')
1276 with zipfile.ZipFile(output_file, 'w') as output_zip:
1277 payload.WriteToZip(output_zip)
1278
1279 import check_ota_package_signature
1280 check_ota_package_signature.VerifyAbOtaPayload(
1281 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1282 output_file)
1283
1284 def test_Sign_incremental(self):
1285 payload = self._create_payload_incremental()
1286 payload.Sign(PayloadSigner())
1287
1288 output_file = common.MakeTempFile(suffix='.zip')
1289 with zipfile.ZipFile(output_file, 'w') as output_zip:
1290 payload.WriteToZip(output_zip)
1291
1292 import check_ota_package_signature
1293 check_ota_package_signature.VerifyAbOtaPayload(
1294 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1295 output_file)
1296
1297 def test_Sign_withDataWipe(self):
1298 common.OPTIONS.wipe_user_data = True
1299 payload = self._create_payload_full()
1300 payload.Sign(PayloadSigner())
1301
1302 with open(payload.payload_properties) as properties_fp:
1303 self.assertIn("POWERWASH=1", properties_fp.read())
1304
Tao Bao667ff572018-02-10 00:02:40 -08001305 def test_Sign_secondary(self):
1306 payload = self._create_payload_full(secondary=True)
1307 payload.Sign(PayloadSigner())
1308
1309 with open(payload.payload_properties) as properties_fp:
1310 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read())
1311
Tao Baoc7b403a2018-01-30 18:19:04 -08001312 def test_Sign_badSigner(self):
1313 """Tests that signing failure can be captured."""
1314 payload = self._create_payload_full()
1315 payload_signer = PayloadSigner()
1316 payload_signer.signer_args.append('bad-option')
1317 self.assertRaises(AssertionError, payload.Sign, payload_signer)
1318
1319 def test_WriteToZip(self):
1320 payload = self._create_payload_full()
1321 payload.Sign(PayloadSigner())
1322
1323 output_file = common.MakeTempFile(suffix='.zip')
1324 with zipfile.ZipFile(output_file, 'w') as output_zip:
1325 payload.WriteToZip(output_zip)
1326
1327 with zipfile.ZipFile(output_file) as verify_zip:
1328 # First make sure we have the essential entries.
1329 namelist = verify_zip.namelist()
1330 self.assertIn(Payload.PAYLOAD_BIN, namelist)
1331 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
1332
1333 # Then assert these entries are stored.
1334 for entry_info in verify_zip.infolist():
1335 if entry_info.filename not in (Payload.PAYLOAD_BIN,
1336 Payload.PAYLOAD_PROPERTIES_TXT):
1337 continue
1338 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
1339
1340 def test_WriteToZip_unsignedPayload(self):
1341 """Unsigned payloads should not be allowed to be written to zip."""
1342 payload = self._create_payload_full()
1343
1344 output_file = common.MakeTempFile(suffix='.zip')
1345 with zipfile.ZipFile(output_file, 'w') as output_zip:
1346 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
1347
1348 # Also test with incremental payload.
1349 payload = self._create_payload_incremental()
1350
1351 output_file = common.MakeTempFile(suffix='.zip')
1352 with zipfile.ZipFile(output_file, 'w') as output_zip:
1353 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001354
1355 def test_WriteToZip_secondary(self):
1356 payload = self._create_payload_full(secondary=True)
1357 payload.Sign(PayloadSigner())
1358
1359 output_file = common.MakeTempFile(suffix='.zip')
1360 with zipfile.ZipFile(output_file, 'w') as output_zip:
Tao Bao667ff572018-02-10 00:02:40 -08001361 payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001362
1363 with zipfile.ZipFile(output_file) as verify_zip:
1364 # First make sure we have the essential entries.
1365 namelist = verify_zip.namelist()
1366 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist)
1367 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist)
1368
1369 # Then assert these entries are stored.
1370 for entry_info in verify_zip.infolist():
1371 if entry_info.filename not in (
1372 Payload.SECONDARY_PAYLOAD_BIN,
1373 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
1374 continue
1375 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)