blob: 8416af706e59e1693243b51a12e3cc35fb4d36ab [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
Tao Baoea6cbd02018-09-05 13:06:37 -0700258 def test_vendor_fingerprint(self):
259 target_info = BuildInfo(self.TEST_INFO_DICT, None)
260 self.assertEqual('vendor-build-fingerprint',
261 target_info.vendor_fingerprint)
262
263 def test_vendor_fingerprint_blacklisted(self):
264 target_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
265 del target_info_dict['vendor.build.prop']['ro.vendor.build.fingerprint']
266 target_info = BuildInfo(target_info_dict, self.TEST_OEM_DICTS)
267 self.assertIsNone(target_info.vendor_fingerprint)
268
269 def test_vendor_fingerprint_without_vendor_build_prop(self):
270 target_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
271 del target_info_dict['vendor.build.prop']
272 target_info = BuildInfo(target_info_dict, self.TEST_OEM_DICTS)
273 self.assertIsNone(target_info.vendor_fingerprint)
274
Tao Bao481bab82017-12-21 11:23:09 -0800275 def test_WriteMountOemScript(self):
276 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
277 self.TEST_OEM_DICTS)
278 script_writer = MockScriptWriter()
279 target_info.WriteMountOemScript(script_writer)
280 self.assertEqual([('Mount', '/oem', None)], script_writer.script)
281
282 def test_WriteDeviceAssertions(self):
283 target_info = BuildInfo(self.TEST_INFO_DICT, None)
284 script_writer = MockScriptWriter()
285 target_info.WriteDeviceAssertions(script_writer, False)
286 self.assertEqual([('AssertDevice', 'product-device')], script_writer.script)
287
288 def test_WriteDeviceAssertions_with_oem_props(self):
289 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
290 self.TEST_OEM_DICTS)
291 script_writer = MockScriptWriter()
292 target_info.WriteDeviceAssertions(script_writer, False)
293 self.assertEqual(
294 [
295 ('AssertOemProperty', 'ro.product.device',
296 ['device1', 'device2', 'device3'], False),
297 ('AssertOemProperty', 'ro.product.brand',
298 ['brand1', 'brand2', 'brand3'], False),
299 ],
300 script_writer.script)
301
302 def test_WriteFingerprintAssertion_without_oem_props(self):
303 target_info = BuildInfo(self.TEST_INFO_DICT, None)
304 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT)
305 source_info_dict['build.prop']['ro.build.fingerprint'] = (
306 'source-build-fingerprint')
307 source_info = BuildInfo(source_info_dict, None)
308
309 script_writer = MockScriptWriter()
310 WriteFingerprintAssertion(script_writer, target_info, source_info)
311 self.assertEqual(
312 [('AssertSomeFingerprint', 'source-build-fingerprint',
313 'build-fingerprint')],
314 script_writer.script)
315
316 def test_WriteFingerprintAssertion_with_source_oem_props(self):
317 target_info = BuildInfo(self.TEST_INFO_DICT, None)
318 source_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
319 self.TEST_OEM_DICTS)
320
321 script_writer = MockScriptWriter()
322 WriteFingerprintAssertion(script_writer, target_info, source_info)
323 self.assertEqual(
324 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
325 'build-thumbprint')],
326 script_writer.script)
327
328 def test_WriteFingerprintAssertion_with_target_oem_props(self):
329 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
330 self.TEST_OEM_DICTS)
331 source_info = BuildInfo(self.TEST_INFO_DICT, None)
332
333 script_writer = MockScriptWriter()
334 WriteFingerprintAssertion(script_writer, target_info, source_info)
335 self.assertEqual(
336 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
337 'build-thumbprint')],
338 script_writer.script)
339
340 def test_WriteFingerprintAssertion_with_both_oem_props(self):
341 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
342 self.TEST_OEM_DICTS)
343 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
344 source_info_dict['build.prop']['ro.build.thumbprint'] = (
345 'source-build-thumbprint')
346 source_info = BuildInfo(source_info_dict, self.TEST_OEM_DICTS)
347
348 script_writer = MockScriptWriter()
349 WriteFingerprintAssertion(script_writer, target_info, source_info)
350 self.assertEqual(
351 [('AssertSomeThumbprint', 'build-thumbprint',
352 'source-build-thumbprint')],
353 script_writer.script)
354
355
356class LoadOemDictsTest(unittest.TestCase):
357
358 def tearDown(self):
359 common.Cleanup()
360
361 def test_NoneDict(self):
362 self.assertIsNone(_LoadOemDicts(None))
363
364 def test_SingleDict(self):
365 dict_file = common.MakeTempFile()
366 with open(dict_file, 'w') as dict_fp:
367 dict_fp.write('abc=1\ndef=2\nxyz=foo\na.b.c=bar\n')
368
369 oem_dicts = _LoadOemDicts([dict_file])
370 self.assertEqual(1, len(oem_dicts))
371 self.assertEqual('foo', oem_dicts[0]['xyz'])
372 self.assertEqual('bar', oem_dicts[0]['a.b.c'])
373
374 def test_MultipleDicts(self):
375 oem_source = []
376 for i in range(3):
377 dict_file = common.MakeTempFile()
378 with open(dict_file, 'w') as dict_fp:
379 dict_fp.write(
380 'ro.build.index={}\ndef=2\nxyz=foo\na.b.c=bar\n'.format(i))
381 oem_source.append(dict_file)
382
383 oem_dicts = _LoadOemDicts(oem_source)
384 self.assertEqual(3, len(oem_dicts))
385 for i, oem_dict in enumerate(oem_dicts):
386 self.assertEqual('2', oem_dict['def'])
387 self.assertEqual('foo', oem_dict['xyz'])
388 self.assertEqual('bar', oem_dict['a.b.c'])
389 self.assertEqual('{}'.format(i), oem_dict['ro.build.index'])
Tao Baodf3a48b2018-01-10 16:30:43 -0800390
391
392class OtaFromTargetFilesTest(unittest.TestCase):
393
394 TEST_TARGET_INFO_DICT = {
395 'build.prop' : {
396 'ro.product.device' : 'product-device',
397 'ro.build.fingerprint' : 'build-fingerprint-target',
398 'ro.build.version.incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800399 'ro.build.version.sdk' : '27',
400 'ro.build.version.security_patch' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800401 'ro.build.date.utc' : '1500000000',
402 },
403 }
404
405 TEST_SOURCE_INFO_DICT = {
406 'build.prop' : {
407 'ro.product.device' : 'product-device',
408 'ro.build.fingerprint' : 'build-fingerprint-source',
409 'ro.build.version.incremental' : 'build-version-incremental-source',
Tao Bao35dc2552018-02-01 13:18:00 -0800410 'ro.build.version.sdk' : '25',
411 'ro.build.version.security_patch' : '2016-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800412 'ro.build.date.utc' : '1400000000',
413 },
414 }
415
416 def setUp(self):
Tao Bao3bf8c652018-03-16 12:59:42 -0700417 self.testdata_dir = test_utils.get_testdata_dir()
418 self.assertTrue(os.path.exists(self.testdata_dir))
419
Tao Baodf3a48b2018-01-10 16:30:43 -0800420 # Reset the global options as in ota_from_target_files.py.
421 common.OPTIONS.incremental_source = None
422 common.OPTIONS.downgrade = False
423 common.OPTIONS.timestamp = False
424 common.OPTIONS.wipe_user_data = False
Tao Bao3bf8c652018-03-16 12:59:42 -0700425 common.OPTIONS.no_signing = False
426 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
427 common.OPTIONS.key_passwords = {
428 common.OPTIONS.package_key : None,
429 }
430
431 common.OPTIONS.search_path = test_utils.get_search_path()
432 self.assertIsNotNone(common.OPTIONS.search_path)
Tao Baodf3a48b2018-01-10 16:30:43 -0800433
Tao Baof5110492018-03-02 09:47:43 -0800434 def tearDown(self):
435 common.Cleanup()
436
Tao Baodf3a48b2018-01-10 16:30:43 -0800437 def test_GetPackageMetadata_abOta_full(self):
438 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
439 target_info_dict['ab_update'] = 'true'
440 target_info = BuildInfo(target_info_dict, None)
441 metadata = GetPackageMetadata(target_info)
442 self.assertDictEqual(
443 {
444 'ota-type' : 'AB',
445 'ota-required-cache' : '0',
446 'post-build' : 'build-fingerprint-target',
447 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800448 'post-sdk-level' : '27',
449 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800450 'post-timestamp' : '1500000000',
451 'pre-device' : 'product-device',
452 },
453 metadata)
454
455 def test_GetPackageMetadata_abOta_incremental(self):
456 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
457 target_info_dict['ab_update'] = 'true'
458 target_info = BuildInfo(target_info_dict, None)
459 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
460 common.OPTIONS.incremental_source = ''
461 metadata = GetPackageMetadata(target_info, source_info)
462 self.assertDictEqual(
463 {
464 'ota-type' : 'AB',
465 'ota-required-cache' : '0',
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 'pre-build' : 'build-fingerprint-source',
473 'pre-build-incremental' : 'build-version-incremental-source',
474 },
475 metadata)
476
477 def test_GetPackageMetadata_nonAbOta_full(self):
478 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
479 metadata = GetPackageMetadata(target_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 },
490 metadata)
491
492 def test_GetPackageMetadata_nonAbOta_incremental(self):
493 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
494 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
495 common.OPTIONS.incremental_source = ''
496 metadata = GetPackageMetadata(target_info, source_info)
497 self.assertDictEqual(
498 {
499 'ota-type' : 'BLOCK',
500 'post-build' : 'build-fingerprint-target',
501 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800502 'post-sdk-level' : '27',
503 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800504 'post-timestamp' : '1500000000',
505 'pre-device' : 'product-device',
506 'pre-build' : 'build-fingerprint-source',
507 'pre-build-incremental' : 'build-version-incremental-source',
508 },
509 metadata)
510
511 def test_GetPackageMetadata_wipe(self):
512 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
513 common.OPTIONS.wipe_user_data = True
514 metadata = GetPackageMetadata(target_info)
515 self.assertDictEqual(
516 {
517 'ota-type' : 'BLOCK',
518 'ota-wipe' : 'yes',
519 'post-build' : 'build-fingerprint-target',
520 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800521 'post-sdk-level' : '27',
522 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800523 'post-timestamp' : '1500000000',
524 'pre-device' : 'product-device',
525 },
526 metadata)
527
528 @staticmethod
529 def _test_GetPackageMetadata_swapBuildTimestamps(target_info, source_info):
530 (target_info['build.prop']['ro.build.date.utc'],
531 source_info['build.prop']['ro.build.date.utc']) = (
532 source_info['build.prop']['ro.build.date.utc'],
533 target_info['build.prop']['ro.build.date.utc'])
534
535 def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
536 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
537 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
538 self._test_GetPackageMetadata_swapBuildTimestamps(
539 target_info_dict, source_info_dict)
540
541 target_info = BuildInfo(target_info_dict, None)
542 source_info = BuildInfo(source_info_dict, None)
543 common.OPTIONS.incremental_source = ''
544 self.assertRaises(RuntimeError, GetPackageMetadata, target_info,
545 source_info)
546
547 def test_GetPackageMetadata_downgrade(self):
548 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
549 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
550 self._test_GetPackageMetadata_swapBuildTimestamps(
551 target_info_dict, source_info_dict)
552
553 target_info = BuildInfo(target_info_dict, None)
554 source_info = BuildInfo(source_info_dict, None)
555 common.OPTIONS.incremental_source = ''
556 common.OPTIONS.downgrade = True
557 common.OPTIONS.wipe_user_data = True
558 metadata = GetPackageMetadata(target_info, source_info)
559 self.assertDictEqual(
560 {
561 'ota-downgrade' : 'yes',
562 'ota-type' : 'BLOCK',
563 'ota-wipe' : 'yes',
564 'post-build' : 'build-fingerprint-target',
565 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800566 'post-sdk-level' : '27',
567 'post-security-patch-level' : '2017-12-01',
Tao Baofaa8e0b2018-04-12 14:31:43 -0700568 'post-timestamp' : '1400000000',
Tao Baodf3a48b2018-01-10 16:30:43 -0800569 'pre-device' : 'product-device',
570 'pre-build' : 'build-fingerprint-source',
571 'pre-build-incremental' : 'build-version-incremental-source',
572 },
573 metadata)
Tao Baofabe0832018-01-17 15:52:28 -0800574
Tao Baof7140c02018-01-30 17:09:24 -0800575 def test_GetTargetFilesZipForSecondaryImages(self):
576 input_file = construct_target_files(secondary=True)
577 target_file = GetTargetFilesZipForSecondaryImages(input_file)
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)
Tao Bao12489802018-07-12 14:47:38 -0700586 self.assertIn('RADIO/bootloader.img', namelist)
587 self.assertIn('RADIO/modem.img', namelist)
Tao Bao15a146a2018-02-21 16:06:59 -0800588 self.assertIn(POSTINSTALL_CONFIG, namelist)
Tao Baof7140c02018-01-30 17:09:24 -0800589
590 self.assertNotIn('IMAGES/system_other.img', namelist)
591 self.assertNotIn('IMAGES/system.map', namelist)
592
Tao Bao15a146a2018-02-21 16:06:59 -0800593 def test_GetTargetFilesZipForSecondaryImages_skipPostinstall(self):
594 input_file = construct_target_files(secondary=True)
595 target_file = GetTargetFilesZipForSecondaryImages(
596 input_file, skip_postinstall=True)
597
598 with zipfile.ZipFile(target_file) as verify_zip:
599 namelist = verify_zip.namelist()
600
601 self.assertIn('META/ab_partitions.txt', namelist)
602 self.assertIn('IMAGES/boot.img', namelist)
603 self.assertIn('IMAGES/system.img', namelist)
604 self.assertIn('IMAGES/vendor.img', namelist)
Tao Bao12489802018-07-12 14:47:38 -0700605 self.assertIn('RADIO/bootloader.img', namelist)
606 self.assertIn('RADIO/modem.img', namelist)
Tao Bao15a146a2018-02-21 16:06:59 -0800607
608 self.assertNotIn('IMAGES/system_other.img', namelist)
609 self.assertNotIn('IMAGES/system.map', namelist)
610 self.assertNotIn(POSTINSTALL_CONFIG, namelist)
611
Tao Bao12489802018-07-12 14:47:38 -0700612 def test_GetTargetFilesZipForSecondaryImages_withoutRadioImages(self):
613 input_file = construct_target_files(secondary=True)
614 common.ZipDelete(input_file, 'RADIO/bootloader.img')
615 common.ZipDelete(input_file, 'RADIO/modem.img')
616 target_file = GetTargetFilesZipForSecondaryImages(input_file)
617
618 with zipfile.ZipFile(target_file) as verify_zip:
619 namelist = verify_zip.namelist()
620
621 self.assertIn('META/ab_partitions.txt', namelist)
622 self.assertIn('IMAGES/boot.img', namelist)
623 self.assertIn('IMAGES/system.img', namelist)
624 self.assertIn('IMAGES/vendor.img', namelist)
625 self.assertIn(POSTINSTALL_CONFIG, namelist)
626
627 self.assertNotIn('IMAGES/system_other.img', namelist)
628 self.assertNotIn('IMAGES/system.map', namelist)
629 self.assertNotIn('RADIO/bootloader.img', namelist)
630 self.assertNotIn('RADIO/modem.img', namelist)
631
Tao Bao15a146a2018-02-21 16:06:59 -0800632 def test_GetTargetFilesZipWithoutPostinstallConfig(self):
633 input_file = construct_target_files()
634 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
635 with zipfile.ZipFile(target_file) as verify_zip:
636 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
637
638 def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self):
639 input_file = construct_target_files()
640 common.ZipDelete(input_file, POSTINSTALL_CONFIG)
641 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
642 with zipfile.ZipFile(target_file) as verify_zip:
643 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
644
Tao Bao3bf8c652018-03-16 12:59:42 -0700645 def _test_FinalizeMetadata(self, large_entry=False):
646 entries = [
647 'required-entry1',
648 'required-entry2',
649 ]
650 zip_file = PropertyFilesTest.construct_zip_package(entries)
651 # Add a large entry of 1 GiB if requested.
652 if large_entry:
653 with zipfile.ZipFile(zip_file, 'a') as zip_fp:
654 zip_fp.writestr(
655 # Using 'zoo' so that the entry stays behind others after signing.
656 'zoo',
657 'A' * 1024 * 1024 * 1024,
658 zipfile.ZIP_STORED)
659
660 metadata = {}
661 output_file = common.MakeTempFile(suffix='.zip')
662 needed_property_files = (
663 TestPropertyFiles(),
664 )
665 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
666 self.assertIn('ota-test-property-files', metadata)
667
668 def test_FinalizeMetadata(self):
669 self._test_FinalizeMetadata()
670
671 def test_FinalizeMetadata_withNoSigning(self):
672 common.OPTIONS.no_signing = True
673 self._test_FinalizeMetadata()
674
675 def test_FinalizeMetadata_largeEntry(self):
676 self._test_FinalizeMetadata(large_entry=True)
677
678 def test_FinalizeMetadata_largeEntry_withNoSigning(self):
679 common.OPTIONS.no_signing = True
680 self._test_FinalizeMetadata(large_entry=True)
681
682 def test_FinalizeMetadata_insufficientSpace(self):
683 entries = [
684 'required-entry1',
685 'required-entry2',
686 'optional-entry1',
687 'optional-entry2',
688 ]
689 zip_file = PropertyFilesTest.construct_zip_package(entries)
690 with zipfile.ZipFile(zip_file, 'a') as zip_fp:
691 zip_fp.writestr(
692 # 'foo-entry1' will appear ahead of all other entries (in alphabetical
693 # order) after the signing, which will in turn trigger the
694 # InsufficientSpaceException and an automatic retry.
695 'foo-entry1',
696 'A' * 1024 * 1024,
697 zipfile.ZIP_STORED)
698
699 metadata = {}
700 needed_property_files = (
701 TestPropertyFiles(),
702 )
703 output_file = common.MakeTempFile(suffix='.zip')
704 FinalizeMetadata(metadata, zip_file, output_file, needed_property_files)
705 self.assertIn('ota-test-property-files', metadata)
706
Tao Baoae5e4c32018-03-01 19:30:00 -0800707
Tao Bao69203522018-03-08 16:09:01 -0800708class TestPropertyFiles(PropertyFiles):
709 """A class that extends PropertyFiles for testing purpose."""
710
711 def __init__(self):
712 super(TestPropertyFiles, self).__init__()
713 self.name = 'ota-test-property-files'
714 self.required = (
715 'required-entry1',
716 'required-entry2',
717 )
718 self.optional = (
719 'optional-entry1',
720 'optional-entry2',
721 )
722
723
724class PropertyFilesTest(unittest.TestCase):
Tao Baoae5e4c32018-03-01 19:30:00 -0800725
Tao Bao3bf8c652018-03-16 12:59:42 -0700726 def setUp(self):
727 common.OPTIONS.no_signing = False
728
Tao Baoae5e4c32018-03-01 19:30:00 -0800729 def tearDown(self):
730 common.Cleanup()
731
Tao Baof5110492018-03-02 09:47:43 -0800732 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -0700733 def construct_zip_package(entries):
Tao Baof5110492018-03-02 09:47:43 -0800734 zip_file = common.MakeTempFile(suffix='.zip')
735 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
736 for entry in entries:
737 zip_fp.writestr(
738 entry,
739 entry.replace('.', '-').upper(),
740 zipfile.ZIP_STORED)
741 return zip_file
742
743 @staticmethod
Tao Bao69203522018-03-08 16:09:01 -0800744 def _parse_property_files_string(data):
Tao Baof5110492018-03-02 09:47:43 -0800745 result = {}
746 for token in data.split(','):
747 name, info = token.split(':', 1)
748 result[name] = info
749 return result
750
751 def _verify_entries(self, input_file, tokens, entries):
752 for entry in entries:
753 offset, size = map(int, tokens[entry].split(':'))
754 with open(input_file, 'rb') as input_fp:
755 input_fp.seek(offset)
756 if entry == 'metadata':
757 expected = b'META-INF/COM/ANDROID/METADATA'
758 else:
759 expected = entry.replace('.', '-').upper().encode()
760 self.assertEqual(expected, input_fp.read(size))
761
Tao Baoae5e4c32018-03-01 19:30:00 -0800762 def test_Compute(self):
Tao Baof5110492018-03-02 09:47:43 -0800763 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800764 'required-entry1',
765 'required-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800766 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700767 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800768 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800769 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Bao69203522018-03-08 16:09:01 -0800770 property_files_string = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800771
Tao Bao69203522018-03-08 16:09:01 -0800772 tokens = self._parse_property_files_string(property_files_string)
Tao Baof5110492018-03-02 09:47:43 -0800773 self.assertEqual(3, len(tokens))
774 self._verify_entries(zip_file, tokens, entries)
775
Tao Bao69203522018-03-08 16:09:01 -0800776 def test_Compute_withOptionalEntries(self):
Tao Baof5110492018-03-02 09:47:43 -0800777 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800778 'required-entry1',
779 'required-entry2',
780 'optional-entry1',
781 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800782 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700783 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800784 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800785 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Bao69203522018-03-08 16:09:01 -0800786 property_files_string = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800787
Tao Bao69203522018-03-08 16:09:01 -0800788 tokens = self._parse_property_files_string(property_files_string)
Tao Baof5110492018-03-02 09:47:43 -0800789 self.assertEqual(5, len(tokens))
790 self._verify_entries(zip_file, tokens, entries)
791
Tao Bao69203522018-03-08 16:09:01 -0800792 def test_Compute_missingRequiredEntry(self):
793 entries = (
794 'required-entry2',
795 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700796 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800797 property_files = TestPropertyFiles()
798 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
799 self.assertRaises(KeyError, property_files.Compute, zip_fp)
800
Tao Baoae5e4c32018-03-01 19:30:00 -0800801 def test_Finalize(self):
Tao Baof5110492018-03-02 09:47:43 -0800802 entries = [
Tao Bao69203522018-03-08 16:09:01 -0800803 'required-entry1',
804 'required-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800805 'META-INF/com/android/metadata',
806 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700807 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800808 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800809 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700810 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800811 zip_fp, reserve_space=False)
812 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
Tao Bao69203522018-03-08 16:09:01 -0800813 tokens = self._parse_property_files_string(streaming_metadata)
Tao Baof5110492018-03-02 09:47:43 -0800814
815 self.assertEqual(3, len(tokens))
816 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
817 # streaming metadata.
818 entries[2] = 'metadata'
819 self._verify_entries(zip_file, tokens, entries)
820
Tao Baoae5e4c32018-03-01 19:30:00 -0800821 def test_Finalize_assertReservedLength(self):
Tao Baof5110492018-03-02 09:47:43 -0800822 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800823 'required-entry1',
824 'required-entry2',
825 'optional-entry1',
826 'optional-entry2',
Tao Baof5110492018-03-02 09:47:43 -0800827 'META-INF/com/android/metadata',
828 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700829 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800830 property_files = TestPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800831 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
832 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700833 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800834 zip_fp, reserve_space=False)
Tao Baof5110492018-03-02 09:47:43 -0800835 raw_length = len(raw_metadata)
836
837 # Now pass in the exact expected length.
Tao Baoae5e4c32018-03-01 19:30:00 -0800838 streaming_metadata = property_files.Finalize(zip_fp, raw_length)
Tao Baof5110492018-03-02 09:47:43 -0800839 self.assertEqual(raw_length, len(streaming_metadata))
840
841 # Or pass in insufficient length.
842 self.assertRaises(
Tao Bao3bf8c652018-03-16 12:59:42 -0700843 PropertyFiles.InsufficientSpaceException,
Tao Baoae5e4c32018-03-01 19:30:00 -0800844 property_files.Finalize,
Tao Baof5110492018-03-02 09:47:43 -0800845 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800846 raw_length - 1)
Tao Baof5110492018-03-02 09:47:43 -0800847
848 # Or pass in a much larger size.
Tao Baoae5e4c32018-03-01 19:30:00 -0800849 streaming_metadata = property_files.Finalize(
Tao Baof5110492018-03-02 09:47:43 -0800850 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800851 raw_length + 20)
Tao Baof5110492018-03-02 09:47:43 -0800852 self.assertEqual(raw_length + 20, len(streaming_metadata))
853 self.assertEqual(' ' * 20, streaming_metadata[raw_length:])
854
Tao Baoae5e4c32018-03-01 19:30:00 -0800855 def test_Verify(self):
856 entries = (
Tao Bao69203522018-03-08 16:09:01 -0800857 'required-entry1',
858 'required-entry2',
859 'optional-entry1',
860 'optional-entry2',
861 'META-INF/com/android/metadata',
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 = TestPropertyFiles()
865 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
866 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700867 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800868 zip_fp, reserve_space=False)
869
870 # Should pass the test if verification passes.
871 property_files.Verify(zip_fp, raw_metadata)
872
873 # Or raise on verification failure.
874 self.assertRaises(
875 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
876
877
878class StreamingPropertyFilesTest(PropertyFilesTest):
879 """Additional sanity checks specialized for StreamingPropertyFiles."""
880
881 def test_init(self):
882 property_files = StreamingPropertyFiles()
883 self.assertEqual('ota-streaming-property-files', property_files.name)
884 self.assertEqual(
885 (
886 'payload.bin',
887 'payload_properties.txt',
888 ),
889 property_files.required)
890 self.assertEqual(
891 (
892 'care_map.txt',
893 'compatibility.zip',
894 ),
895 property_files.optional)
896
897 def test_Compute(self):
898 entries = (
Tao Baoae5e4c32018-03-01 19:30:00 -0800899 'payload.bin',
900 'payload_properties.txt',
901 'care_map.txt',
Tao Bao69203522018-03-08 16:09:01 -0800902 'compatibility.zip',
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:
907 property_files_string = property_files.Compute(zip_fp)
908
909 tokens = self._parse_property_files_string(property_files_string)
910 self.assertEqual(5, len(tokens))
911 self._verify_entries(zip_file, tokens, entries)
912
913 def test_Finalize(self):
914 entries = [
915 'payload.bin',
916 'payload_properties.txt',
917 'care_map.txt',
918 'compatibility.zip',
919 'META-INF/com/android/metadata',
920 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700921 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800922 property_files = StreamingPropertyFiles()
923 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700924 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800925 zip_fp, reserve_space=False)
926 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
927 tokens = self._parse_property_files_string(streaming_metadata)
928
929 self.assertEqual(5, len(tokens))
930 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
931 # streaming metadata.
932 entries[4] = 'metadata'
933 self._verify_entries(zip_file, tokens, entries)
934
935 def test_Verify(self):
936 entries = (
937 'payload.bin',
938 'payload_properties.txt',
939 'care_map.txt',
940 'compatibility.zip',
Tao Baoae5e4c32018-03-01 19:30:00 -0800941 'META-INF/com/android/metadata',
942 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700943 zip_file = self.construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800944 property_files = StreamingPropertyFiles()
945 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
946 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700947 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800948 zip_fp, reserve_space=False)
949
950 # Should pass the test if verification passes.
951 property_files.Verify(zip_fp, raw_metadata)
952
953 # Or raise on verification failure.
954 self.assertRaises(
955 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
956
Tao Baofabe0832018-01-17 15:52:28 -0800957
Tao Baob6304672018-03-08 16:28:33 -0800958class AbOtaPropertyFilesTest(PropertyFilesTest):
959 """Additional sanity checks specialized for AbOtaPropertyFiles."""
960
961 # The size for payload and metadata signature size.
962 SIGNATURE_SIZE = 256
963
964 def setUp(self):
965 self.testdata_dir = test_utils.get_testdata_dir()
966 self.assertTrue(os.path.exists(self.testdata_dir))
967
968 common.OPTIONS.wipe_user_data = False
969 common.OPTIONS.payload_signer = None
970 common.OPTIONS.payload_signer_args = None
971 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
972 common.OPTIONS.key_passwords = {
973 common.OPTIONS.package_key : None,
974 }
975
976 def test_init(self):
977 property_files = AbOtaPropertyFiles()
978 self.assertEqual('ota-property-files', property_files.name)
979 self.assertEqual(
980 (
981 'payload.bin',
982 'payload_properties.txt',
983 ),
984 property_files.required)
985 self.assertEqual(
986 (
987 'care_map.txt',
988 'compatibility.zip',
989 ),
990 property_files.optional)
991
992 def test_GetPayloadMetadataOffsetAndSize(self):
993 target_file = construct_target_files()
994 payload = Payload()
995 payload.Generate(target_file)
996
997 payload_signer = PayloadSigner()
998 payload.Sign(payload_signer)
999
1000 output_file = common.MakeTempFile(suffix='.zip')
1001 with zipfile.ZipFile(output_file, 'w') as output_zip:
1002 payload.WriteToZip(output_zip)
1003
1004 # Find out the payload metadata offset and size.
1005 property_files = AbOtaPropertyFiles()
1006 with zipfile.ZipFile(output_file) as input_zip:
1007 # pylint: disable=protected-access
1008 payload_offset, metadata_total = (
1009 property_files._GetPayloadMetadataOffsetAndSize(input_zip))
1010
1011 # Read in the metadata signature directly.
1012 with open(output_file, 'rb') as verify_fp:
1013 verify_fp.seek(payload_offset + metadata_total - self.SIGNATURE_SIZE)
1014 metadata_signature = verify_fp.read(self.SIGNATURE_SIZE)
1015
1016 # Now we extract the metadata hash via brillo_update_payload script, which
1017 # will serve as the oracle result.
1018 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1019 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1020 cmd = ['brillo_update_payload', 'hash',
1021 '--unsigned_payload', payload.payload_file,
1022 '--signature_size', str(self.SIGNATURE_SIZE),
1023 '--metadata_hash_file', metadata_sig_file,
1024 '--payload_hash_file', payload_sig_file]
1025 proc = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1026 stdoutdata, _ = proc.communicate()
1027 self.assertEqual(
1028 0, proc.returncode,
1029 'Failed to run brillo_update_payload: {}'.format(stdoutdata))
1030
1031 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
1032
1033 # Finally we can compare the two signatures.
1034 with open(signed_metadata_sig_file, 'rb') as verify_fp:
1035 self.assertEqual(verify_fp.read(), metadata_signature)
1036
1037 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -07001038 def construct_zip_package_withValidPayload(with_metadata=False):
1039 # Cannot use construct_zip_package() since we need a "valid" payload.bin.
Tao Baob6304672018-03-08 16:28:33 -08001040 target_file = construct_target_files()
1041 payload = Payload()
1042 payload.Generate(target_file)
1043
1044 payload_signer = PayloadSigner()
1045 payload.Sign(payload_signer)
1046
1047 zip_file = common.MakeTempFile(suffix='.zip')
1048 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
1049 # 'payload.bin',
1050 payload.WriteToZip(zip_fp)
1051
1052 # Other entries.
1053 entries = ['care_map.txt', 'compatibility.zip']
1054
1055 # Put META-INF/com/android/metadata if needed.
1056 if with_metadata:
1057 entries.append('META-INF/com/android/metadata')
1058
1059 for entry in entries:
1060 zip_fp.writestr(
1061 entry, entry.replace('.', '-').upper(), zipfile.ZIP_STORED)
1062
1063 return zip_file
1064
1065 def test_Compute(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001066 zip_file = self.construct_zip_package_withValidPayload()
Tao Baob6304672018-03-08 16:28:33 -08001067 property_files = AbOtaPropertyFiles()
1068 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1069 property_files_string = property_files.Compute(zip_fp)
1070
1071 tokens = self._parse_property_files_string(property_files_string)
1072 # "6" indcludes the four entries above, one metadata entry, and one entry
1073 # for payload-metadata.bin.
1074 self.assertEqual(6, len(tokens))
1075 self._verify_entries(
1076 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1077
1078 def test_Finalize(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001079 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001080 property_files = AbOtaPropertyFiles()
1081 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001082 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001083 zip_fp, reserve_space=False)
1084 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1085
1086 tokens = self._parse_property_files_string(property_files_string)
1087 # "6" indcludes the four entries above, one metadata entry, and one entry
1088 # for payload-metadata.bin.
1089 self.assertEqual(6, len(tokens))
1090 self._verify_entries(
1091 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1092
1093 def test_Verify(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001094 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001095 property_files = AbOtaPropertyFiles()
1096 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001097 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001098 zip_fp, reserve_space=False)
1099
1100 property_files.Verify(zip_fp, raw_metadata)
1101
1102
Tao Baoc0746f42018-02-21 13:17:22 -08001103class NonAbOtaPropertyFilesTest(PropertyFilesTest):
1104 """Additional sanity checks specialized for NonAbOtaPropertyFiles."""
1105
1106 def test_init(self):
1107 property_files = NonAbOtaPropertyFiles()
1108 self.assertEqual('ota-property-files', property_files.name)
1109 self.assertEqual((), property_files.required)
1110 self.assertEqual((), property_files.optional)
1111
1112 def test_Compute(self):
1113 entries = ()
Tao Bao3bf8c652018-03-16 12:59:42 -07001114 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001115 property_files = NonAbOtaPropertyFiles()
1116 with zipfile.ZipFile(zip_file) as zip_fp:
1117 property_files_string = property_files.Compute(zip_fp)
1118
1119 tokens = self._parse_property_files_string(property_files_string)
1120 self.assertEqual(1, len(tokens))
1121 self._verify_entries(zip_file, tokens, entries)
1122
1123 def test_Finalize(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 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1133 tokens = self._parse_property_files_string(property_files_string)
1134
1135 self.assertEqual(1, len(tokens))
1136 # 'META-INF/com/android/metadata' will be key'd as 'metadata'.
1137 entries[0] = 'metadata'
1138 self._verify_entries(zip_file, tokens, entries)
1139
1140 def test_Verify(self):
1141 entries = (
1142 'META-INF/com/android/metadata',
1143 )
Tao Bao3bf8c652018-03-16 12:59:42 -07001144 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001145 property_files = NonAbOtaPropertyFiles()
1146 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001147 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001148 zip_fp, reserve_space=False)
1149
1150 property_files.Verify(zip_fp, raw_metadata)
1151
1152
Tao Baofabe0832018-01-17 15:52:28 -08001153class PayloadSignerTest(unittest.TestCase):
1154
1155 SIGFILE = 'sigfile.bin'
1156 SIGNED_SIGFILE = 'signed-sigfile.bin'
1157
1158 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001159 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baofabe0832018-01-17 15:52:28 -08001160 self.assertTrue(os.path.exists(self.testdata_dir))
1161
1162 common.OPTIONS.payload_signer = None
1163 common.OPTIONS.payload_signer_args = []
1164 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1165 common.OPTIONS.key_passwords = {
1166 common.OPTIONS.package_key : None,
1167 }
1168
1169 def tearDown(self):
1170 common.Cleanup()
1171
1172 def _assertFilesEqual(self, file1, file2):
1173 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2:
1174 self.assertEqual(fp1.read(), fp2.read())
1175
1176 def test_init(self):
1177 payload_signer = PayloadSigner()
1178 self.assertEqual('openssl', payload_signer.signer)
1179
1180 def test_init_withPassword(self):
1181 common.OPTIONS.package_key = os.path.join(
1182 self.testdata_dir, 'testkey_with_passwd')
1183 common.OPTIONS.key_passwords = {
1184 common.OPTIONS.package_key : 'foo',
1185 }
1186 payload_signer = PayloadSigner()
1187 self.assertEqual('openssl', payload_signer.signer)
1188
1189 def test_init_withExternalSigner(self):
1190 common.OPTIONS.payload_signer = 'abc'
1191 common.OPTIONS.payload_signer_args = ['arg1', 'arg2']
1192 payload_signer = PayloadSigner()
1193 self.assertEqual('abc', payload_signer.signer)
1194 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args)
1195
1196 def test_Sign(self):
1197 payload_signer = PayloadSigner()
1198 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1199 signed_file = payload_signer.Sign(input_file)
1200
1201 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1202 self._assertFilesEqual(verify_file, signed_file)
1203
1204 def test_Sign_withExternalSigner_openssl(self):
1205 """Uses openssl as the external payload signer."""
1206 common.OPTIONS.payload_signer = 'openssl'
1207 common.OPTIONS.payload_signer_args = [
1208 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey',
1209 os.path.join(self.testdata_dir, 'testkey.pk8'),
1210 '-pkeyopt', 'digest:sha256']
1211 payload_signer = PayloadSigner()
1212 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1213 signed_file = payload_signer.Sign(input_file)
1214
1215 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1216 self._assertFilesEqual(verify_file, signed_file)
1217
1218 def test_Sign_withExternalSigner_script(self):
1219 """Uses testdata/payload_signer.sh as the external payload signer."""
1220 common.OPTIONS.payload_signer = os.path.join(
1221 self.testdata_dir, 'payload_signer.sh')
1222 common.OPTIONS.payload_signer_args = [
1223 os.path.join(self.testdata_dir, 'testkey.pk8')]
1224 payload_signer = PayloadSigner()
1225 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1226 signed_file = payload_signer.Sign(input_file)
1227
1228 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1229 self._assertFilesEqual(verify_file, signed_file)
Tao Baoc7b403a2018-01-30 18:19:04 -08001230
1231
1232class PayloadTest(unittest.TestCase):
1233
1234 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001235 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baoc7b403a2018-01-30 18:19:04 -08001236 self.assertTrue(os.path.exists(self.testdata_dir))
1237
1238 common.OPTIONS.wipe_user_data = False
1239 common.OPTIONS.payload_signer = None
1240 common.OPTIONS.payload_signer_args = None
1241 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1242 common.OPTIONS.key_passwords = {
1243 common.OPTIONS.package_key : None,
1244 }
1245
1246 def tearDown(self):
1247 common.Cleanup()
1248
1249 @staticmethod
Tao Baof7140c02018-01-30 17:09:24 -08001250 def _create_payload_full(secondary=False):
1251 target_file = construct_target_files(secondary)
Tao Bao667ff572018-02-10 00:02:40 -08001252 payload = Payload(secondary)
Tao Baoc7b403a2018-01-30 18:19:04 -08001253 payload.Generate(target_file)
1254 return payload
1255
Tao Baof7140c02018-01-30 17:09:24 -08001256 @staticmethod
1257 def _create_payload_incremental():
1258 target_file = construct_target_files()
1259 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001260 payload = Payload()
1261 payload.Generate(target_file, source_file)
1262 return payload
1263
1264 def test_Generate_full(self):
1265 payload = self._create_payload_full()
1266 self.assertTrue(os.path.exists(payload.payload_file))
1267
1268 def test_Generate_incremental(self):
1269 payload = self._create_payload_incremental()
1270 self.assertTrue(os.path.exists(payload.payload_file))
1271
1272 def test_Generate_additionalArgs(self):
Tao Baof7140c02018-01-30 17:09:24 -08001273 target_file = construct_target_files()
1274 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001275 payload = Payload()
1276 # This should work the same as calling payload.Generate(target_file,
1277 # source_file).
1278 payload.Generate(
1279 target_file, additional_args=["--source_image", source_file])
1280 self.assertTrue(os.path.exists(payload.payload_file))
1281
1282 def test_Generate_invalidInput(self):
Tao Baof7140c02018-01-30 17:09:24 -08001283 target_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001284 common.ZipDelete(target_file, 'IMAGES/vendor.img')
1285 payload = Payload()
1286 self.assertRaises(AssertionError, payload.Generate, target_file)
1287
1288 def test_Sign_full(self):
1289 payload = self._create_payload_full()
1290 payload.Sign(PayloadSigner())
1291
1292 output_file = common.MakeTempFile(suffix='.zip')
1293 with zipfile.ZipFile(output_file, 'w') as output_zip:
1294 payload.WriteToZip(output_zip)
1295
1296 import check_ota_package_signature
1297 check_ota_package_signature.VerifyAbOtaPayload(
1298 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1299 output_file)
1300
1301 def test_Sign_incremental(self):
1302 payload = self._create_payload_incremental()
1303 payload.Sign(PayloadSigner())
1304
1305 output_file = common.MakeTempFile(suffix='.zip')
1306 with zipfile.ZipFile(output_file, 'w') as output_zip:
1307 payload.WriteToZip(output_zip)
1308
1309 import check_ota_package_signature
1310 check_ota_package_signature.VerifyAbOtaPayload(
1311 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1312 output_file)
1313
1314 def test_Sign_withDataWipe(self):
1315 common.OPTIONS.wipe_user_data = True
1316 payload = self._create_payload_full()
1317 payload.Sign(PayloadSigner())
1318
1319 with open(payload.payload_properties) as properties_fp:
1320 self.assertIn("POWERWASH=1", properties_fp.read())
1321
Tao Bao667ff572018-02-10 00:02:40 -08001322 def test_Sign_secondary(self):
1323 payload = self._create_payload_full(secondary=True)
1324 payload.Sign(PayloadSigner())
1325
1326 with open(payload.payload_properties) as properties_fp:
1327 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read())
1328
Tao Baoc7b403a2018-01-30 18:19:04 -08001329 def test_Sign_badSigner(self):
1330 """Tests that signing failure can be captured."""
1331 payload = self._create_payload_full()
1332 payload_signer = PayloadSigner()
1333 payload_signer.signer_args.append('bad-option')
1334 self.assertRaises(AssertionError, payload.Sign, payload_signer)
1335
1336 def test_WriteToZip(self):
1337 payload = self._create_payload_full()
1338 payload.Sign(PayloadSigner())
1339
1340 output_file = common.MakeTempFile(suffix='.zip')
1341 with zipfile.ZipFile(output_file, 'w') as output_zip:
1342 payload.WriteToZip(output_zip)
1343
1344 with zipfile.ZipFile(output_file) as verify_zip:
1345 # First make sure we have the essential entries.
1346 namelist = verify_zip.namelist()
1347 self.assertIn(Payload.PAYLOAD_BIN, namelist)
1348 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
1349
1350 # Then assert these entries are stored.
1351 for entry_info in verify_zip.infolist():
1352 if entry_info.filename not in (Payload.PAYLOAD_BIN,
1353 Payload.PAYLOAD_PROPERTIES_TXT):
1354 continue
1355 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
1356
1357 def test_WriteToZip_unsignedPayload(self):
1358 """Unsigned payloads should not be allowed to be written to zip."""
1359 payload = self._create_payload_full()
1360
1361 output_file = common.MakeTempFile(suffix='.zip')
1362 with zipfile.ZipFile(output_file, 'w') as output_zip:
1363 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
1364
1365 # Also test with incremental payload.
1366 payload = self._create_payload_incremental()
1367
1368 output_file = common.MakeTempFile(suffix='.zip')
1369 with zipfile.ZipFile(output_file, 'w') as output_zip:
1370 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001371
1372 def test_WriteToZip_secondary(self):
1373 payload = self._create_payload_full(secondary=True)
1374 payload.Sign(PayloadSigner())
1375
1376 output_file = common.MakeTempFile(suffix='.zip')
1377 with zipfile.ZipFile(output_file, 'w') as output_zip:
Tao Bao667ff572018-02-10 00:02:40 -08001378 payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001379
1380 with zipfile.ZipFile(output_file) as verify_zip:
1381 # First make sure we have the essential entries.
1382 namelist = verify_zip.namelist()
1383 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist)
1384 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist)
1385
1386 # Then assert these entries are stored.
1387 for entry_info in verify_zip.infolist():
1388 if entry_info.filename not in (
1389 Payload.SECONDARY_PAYLOAD_BIN,
1390 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
1391 continue
1392 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)