blob: 1d8a786ba96d2cc0bd6f6b46e092187063d91e23 [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 (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -0700892 'care_map.pb',
Tao Bao69203522018-03-08 16:09:01 -0800893 'care_map.txt',
894 'compatibility.zip',
895 ),
896 property_files.optional)
897
898 def test_Compute(self):
899 entries = (
Tao Baoae5e4c32018-03-01 19:30:00 -0800900 'payload.bin',
901 'payload_properties.txt',
902 'care_map.txt',
Tao Bao69203522018-03-08 16:09:01 -0800903 'compatibility.zip',
904 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700905 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800906 property_files = StreamingPropertyFiles()
907 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
908 property_files_string = property_files.Compute(zip_fp)
909
910 tokens = self._parse_property_files_string(property_files_string)
911 self.assertEqual(5, len(tokens))
912 self._verify_entries(zip_file, tokens, entries)
913
914 def test_Finalize(self):
915 entries = [
916 'payload.bin',
917 'payload_properties.txt',
918 'care_map.txt',
919 'compatibility.zip',
920 'META-INF/com/android/metadata',
921 ]
Tao Bao3bf8c652018-03-16 12:59:42 -0700922 zip_file = self.construct_zip_package(entries)
Tao Bao69203522018-03-08 16:09:01 -0800923 property_files = StreamingPropertyFiles()
924 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700925 raw_metadata = property_files.GetPropertyFilesString(
Tao Bao69203522018-03-08 16:09:01 -0800926 zip_fp, reserve_space=False)
927 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
928 tokens = self._parse_property_files_string(streaming_metadata)
929
930 self.assertEqual(5, len(tokens))
931 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
932 # streaming metadata.
933 entries[4] = 'metadata'
934 self._verify_entries(zip_file, tokens, entries)
935
936 def test_Verify(self):
937 entries = (
938 'payload.bin',
939 'payload_properties.txt',
940 'care_map.txt',
941 'compatibility.zip',
Tao Baoae5e4c32018-03-01 19:30:00 -0800942 'META-INF/com/android/metadata',
943 )
Tao Bao3bf8c652018-03-16 12:59:42 -0700944 zip_file = self.construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800945 property_files = StreamingPropertyFiles()
946 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
947 # First get the raw metadata string (i.e. without padding space).
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -0700948 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoae5e4c32018-03-01 19:30:00 -0800949 zip_fp, reserve_space=False)
950
951 # Should pass the test if verification passes.
952 property_files.Verify(zip_fp, raw_metadata)
953
954 # Or raise on verification failure.
955 self.assertRaises(
956 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
957
Tao Baofabe0832018-01-17 15:52:28 -0800958
Tao Baob6304672018-03-08 16:28:33 -0800959class AbOtaPropertyFilesTest(PropertyFilesTest):
960 """Additional sanity checks specialized for AbOtaPropertyFiles."""
961
962 # The size for payload and metadata signature size.
963 SIGNATURE_SIZE = 256
964
965 def setUp(self):
966 self.testdata_dir = test_utils.get_testdata_dir()
967 self.assertTrue(os.path.exists(self.testdata_dir))
968
969 common.OPTIONS.wipe_user_data = False
970 common.OPTIONS.payload_signer = None
971 common.OPTIONS.payload_signer_args = None
972 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
973 common.OPTIONS.key_passwords = {
974 common.OPTIONS.package_key : None,
975 }
976
977 def test_init(self):
978 property_files = AbOtaPropertyFiles()
979 self.assertEqual('ota-property-files', property_files.name)
980 self.assertEqual(
981 (
982 'payload.bin',
983 'payload_properties.txt',
984 ),
985 property_files.required)
986 self.assertEqual(
987 (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -0700988 'care_map.pb',
Tao Baob6304672018-03-08 16:28:33 -0800989 'care_map.txt',
990 'compatibility.zip',
991 ),
992 property_files.optional)
993
994 def test_GetPayloadMetadataOffsetAndSize(self):
995 target_file = construct_target_files()
996 payload = Payload()
997 payload.Generate(target_file)
998
999 payload_signer = PayloadSigner()
1000 payload.Sign(payload_signer)
1001
1002 output_file = common.MakeTempFile(suffix='.zip')
1003 with zipfile.ZipFile(output_file, 'w') as output_zip:
1004 payload.WriteToZip(output_zip)
1005
1006 # Find out the payload metadata offset and size.
1007 property_files = AbOtaPropertyFiles()
1008 with zipfile.ZipFile(output_file) as input_zip:
1009 # pylint: disable=protected-access
1010 payload_offset, metadata_total = (
1011 property_files._GetPayloadMetadataOffsetAndSize(input_zip))
1012
1013 # Read in the metadata signature directly.
1014 with open(output_file, 'rb') as verify_fp:
1015 verify_fp.seek(payload_offset + metadata_total - self.SIGNATURE_SIZE)
1016 metadata_signature = verify_fp.read(self.SIGNATURE_SIZE)
1017
1018 # Now we extract the metadata hash via brillo_update_payload script, which
1019 # will serve as the oracle result.
1020 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1021 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1022 cmd = ['brillo_update_payload', 'hash',
1023 '--unsigned_payload', payload.payload_file,
1024 '--signature_size', str(self.SIGNATURE_SIZE),
1025 '--metadata_hash_file', metadata_sig_file,
1026 '--payload_hash_file', payload_sig_file]
1027 proc = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1028 stdoutdata, _ = proc.communicate()
1029 self.assertEqual(
1030 0, proc.returncode,
1031 'Failed to run brillo_update_payload: {}'.format(stdoutdata))
1032
1033 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
1034
1035 # Finally we can compare the two signatures.
1036 with open(signed_metadata_sig_file, 'rb') as verify_fp:
1037 self.assertEqual(verify_fp.read(), metadata_signature)
1038
1039 @staticmethod
Tao Bao3bf8c652018-03-16 12:59:42 -07001040 def construct_zip_package_withValidPayload(with_metadata=False):
1041 # Cannot use construct_zip_package() since we need a "valid" payload.bin.
Tao Baob6304672018-03-08 16:28:33 -08001042 target_file = construct_target_files()
1043 payload = Payload()
1044 payload.Generate(target_file)
1045
1046 payload_signer = PayloadSigner()
1047 payload.Sign(payload_signer)
1048
1049 zip_file = common.MakeTempFile(suffix='.zip')
1050 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
1051 # 'payload.bin',
1052 payload.WriteToZip(zip_fp)
1053
1054 # Other entries.
1055 entries = ['care_map.txt', 'compatibility.zip']
1056
1057 # Put META-INF/com/android/metadata if needed.
1058 if with_metadata:
1059 entries.append('META-INF/com/android/metadata')
1060
1061 for entry in entries:
1062 zip_fp.writestr(
1063 entry, entry.replace('.', '-').upper(), zipfile.ZIP_STORED)
1064
1065 return zip_file
1066
1067 def test_Compute(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001068 zip_file = self.construct_zip_package_withValidPayload()
Tao Baob6304672018-03-08 16:28:33 -08001069 property_files = AbOtaPropertyFiles()
1070 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
1071 property_files_string = property_files.Compute(zip_fp)
1072
1073 tokens = self._parse_property_files_string(property_files_string)
1074 # "6" indcludes the four entries above, one metadata entry, and one entry
1075 # for payload-metadata.bin.
1076 self.assertEqual(6, len(tokens))
1077 self._verify_entries(
1078 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1079
1080 def test_Finalize(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001081 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001082 property_files = AbOtaPropertyFiles()
1083 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001084 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001085 zip_fp, reserve_space=False)
1086 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1087
1088 tokens = self._parse_property_files_string(property_files_string)
1089 # "6" indcludes the four entries above, one metadata entry, and one entry
1090 # for payload-metadata.bin.
1091 self.assertEqual(6, len(tokens))
1092 self._verify_entries(
1093 zip_file, tokens, ('care_map.txt', 'compatibility.zip'))
1094
1095 def test_Verify(self):
Tao Bao3bf8c652018-03-16 12:59:42 -07001096 zip_file = self.construct_zip_package_withValidPayload(with_metadata=True)
Tao Baob6304672018-03-08 16:28:33 -08001097 property_files = AbOtaPropertyFiles()
1098 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001099 raw_metadata = property_files.GetPropertyFilesString(
Tao Baob6304672018-03-08 16:28:33 -08001100 zip_fp, reserve_space=False)
1101
1102 property_files.Verify(zip_fp, raw_metadata)
1103
1104
Tao Baoc0746f42018-02-21 13:17:22 -08001105class NonAbOtaPropertyFilesTest(PropertyFilesTest):
1106 """Additional sanity checks specialized for NonAbOtaPropertyFiles."""
1107
1108 def test_init(self):
1109 property_files = NonAbOtaPropertyFiles()
1110 self.assertEqual('ota-property-files', property_files.name)
1111 self.assertEqual((), property_files.required)
1112 self.assertEqual((), property_files.optional)
1113
1114 def test_Compute(self):
1115 entries = ()
Tao Bao3bf8c652018-03-16 12:59:42 -07001116 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001117 property_files = NonAbOtaPropertyFiles()
1118 with zipfile.ZipFile(zip_file) as zip_fp:
1119 property_files_string = property_files.Compute(zip_fp)
1120
1121 tokens = self._parse_property_files_string(property_files_string)
1122 self.assertEqual(1, len(tokens))
1123 self._verify_entries(zip_file, tokens, entries)
1124
1125 def test_Finalize(self):
1126 entries = [
1127 'META-INF/com/android/metadata',
1128 ]
Tao Bao3bf8c652018-03-16 12:59:42 -07001129 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001130 property_files = NonAbOtaPropertyFiles()
1131 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001132 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001133 zip_fp, reserve_space=False)
1134 property_files_string = property_files.Finalize(zip_fp, len(raw_metadata))
1135 tokens = self._parse_property_files_string(property_files_string)
1136
1137 self.assertEqual(1, len(tokens))
1138 # 'META-INF/com/android/metadata' will be key'd as 'metadata'.
1139 entries[0] = 'metadata'
1140 self._verify_entries(zip_file, tokens, entries)
1141
1142 def test_Verify(self):
1143 entries = (
1144 'META-INF/com/android/metadata',
1145 )
Tao Bao3bf8c652018-03-16 12:59:42 -07001146 zip_file = self.construct_zip_package(entries)
Tao Baoc0746f42018-02-21 13:17:22 -08001147 property_files = NonAbOtaPropertyFiles()
1148 with zipfile.ZipFile(zip_file) as zip_fp:
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001149 raw_metadata = property_files.GetPropertyFilesString(
Tao Baoc0746f42018-02-21 13:17:22 -08001150 zip_fp, reserve_space=False)
1151
1152 property_files.Verify(zip_fp, raw_metadata)
1153
1154
Tao Baofabe0832018-01-17 15:52:28 -08001155class PayloadSignerTest(unittest.TestCase):
1156
1157 SIGFILE = 'sigfile.bin'
1158 SIGNED_SIGFILE = 'signed-sigfile.bin'
1159
1160 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001161 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baofabe0832018-01-17 15:52:28 -08001162 self.assertTrue(os.path.exists(self.testdata_dir))
1163
1164 common.OPTIONS.payload_signer = None
1165 common.OPTIONS.payload_signer_args = []
1166 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1167 common.OPTIONS.key_passwords = {
1168 common.OPTIONS.package_key : None,
1169 }
1170
1171 def tearDown(self):
1172 common.Cleanup()
1173
1174 def _assertFilesEqual(self, file1, file2):
1175 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2:
1176 self.assertEqual(fp1.read(), fp2.read())
1177
1178 def test_init(self):
1179 payload_signer = PayloadSigner()
1180 self.assertEqual('openssl', payload_signer.signer)
1181
1182 def test_init_withPassword(self):
1183 common.OPTIONS.package_key = os.path.join(
1184 self.testdata_dir, 'testkey_with_passwd')
1185 common.OPTIONS.key_passwords = {
1186 common.OPTIONS.package_key : 'foo',
1187 }
1188 payload_signer = PayloadSigner()
1189 self.assertEqual('openssl', payload_signer.signer)
1190
1191 def test_init_withExternalSigner(self):
1192 common.OPTIONS.payload_signer = 'abc'
1193 common.OPTIONS.payload_signer_args = ['arg1', 'arg2']
1194 payload_signer = PayloadSigner()
1195 self.assertEqual('abc', payload_signer.signer)
1196 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args)
1197
1198 def test_Sign(self):
1199 payload_signer = PayloadSigner()
1200 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1201 signed_file = payload_signer.Sign(input_file)
1202
1203 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1204 self._assertFilesEqual(verify_file, signed_file)
1205
1206 def test_Sign_withExternalSigner_openssl(self):
1207 """Uses openssl as the external payload signer."""
1208 common.OPTIONS.payload_signer = 'openssl'
1209 common.OPTIONS.payload_signer_args = [
1210 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey',
1211 os.path.join(self.testdata_dir, 'testkey.pk8'),
1212 '-pkeyopt', 'digest:sha256']
1213 payload_signer = PayloadSigner()
1214 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1215 signed_file = payload_signer.Sign(input_file)
1216
1217 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1218 self._assertFilesEqual(verify_file, signed_file)
1219
1220 def test_Sign_withExternalSigner_script(self):
1221 """Uses testdata/payload_signer.sh as the external payload signer."""
1222 common.OPTIONS.payload_signer = os.path.join(
1223 self.testdata_dir, 'payload_signer.sh')
1224 common.OPTIONS.payload_signer_args = [
1225 os.path.join(self.testdata_dir, 'testkey.pk8')]
1226 payload_signer = PayloadSigner()
1227 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
1228 signed_file = payload_signer.Sign(input_file)
1229
1230 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
1231 self._assertFilesEqual(verify_file, signed_file)
Tao Baoc7b403a2018-01-30 18:19:04 -08001232
1233
1234class PayloadTest(unittest.TestCase):
1235
1236 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -08001237 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baoc7b403a2018-01-30 18:19:04 -08001238 self.assertTrue(os.path.exists(self.testdata_dir))
1239
1240 common.OPTIONS.wipe_user_data = False
1241 common.OPTIONS.payload_signer = None
1242 common.OPTIONS.payload_signer_args = None
1243 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
1244 common.OPTIONS.key_passwords = {
1245 common.OPTIONS.package_key : None,
1246 }
1247
1248 def tearDown(self):
1249 common.Cleanup()
1250
1251 @staticmethod
Tao Baof7140c02018-01-30 17:09:24 -08001252 def _create_payload_full(secondary=False):
1253 target_file = construct_target_files(secondary)
Tao Bao667ff572018-02-10 00:02:40 -08001254 payload = Payload(secondary)
Tao Baoc7b403a2018-01-30 18:19:04 -08001255 payload.Generate(target_file)
1256 return payload
1257
Tao Baof7140c02018-01-30 17:09:24 -08001258 @staticmethod
1259 def _create_payload_incremental():
1260 target_file = construct_target_files()
1261 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001262 payload = Payload()
1263 payload.Generate(target_file, source_file)
1264 return payload
1265
1266 def test_Generate_full(self):
1267 payload = self._create_payload_full()
1268 self.assertTrue(os.path.exists(payload.payload_file))
1269
1270 def test_Generate_incremental(self):
1271 payload = self._create_payload_incremental()
1272 self.assertTrue(os.path.exists(payload.payload_file))
1273
1274 def test_Generate_additionalArgs(self):
Tao Baof7140c02018-01-30 17:09:24 -08001275 target_file = construct_target_files()
1276 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001277 payload = Payload()
1278 # This should work the same as calling payload.Generate(target_file,
1279 # source_file).
1280 payload.Generate(
1281 target_file, additional_args=["--source_image", source_file])
1282 self.assertTrue(os.path.exists(payload.payload_file))
1283
1284 def test_Generate_invalidInput(self):
Tao Baof7140c02018-01-30 17:09:24 -08001285 target_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -08001286 common.ZipDelete(target_file, 'IMAGES/vendor.img')
1287 payload = Payload()
1288 self.assertRaises(AssertionError, payload.Generate, target_file)
1289
1290 def test_Sign_full(self):
1291 payload = self._create_payload_full()
1292 payload.Sign(PayloadSigner())
1293
1294 output_file = common.MakeTempFile(suffix='.zip')
1295 with zipfile.ZipFile(output_file, 'w') as output_zip:
1296 payload.WriteToZip(output_zip)
1297
1298 import check_ota_package_signature
1299 check_ota_package_signature.VerifyAbOtaPayload(
1300 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1301 output_file)
1302
1303 def test_Sign_incremental(self):
1304 payload = self._create_payload_incremental()
1305 payload.Sign(PayloadSigner())
1306
1307 output_file = common.MakeTempFile(suffix='.zip')
1308 with zipfile.ZipFile(output_file, 'w') as output_zip:
1309 payload.WriteToZip(output_zip)
1310
1311 import check_ota_package_signature
1312 check_ota_package_signature.VerifyAbOtaPayload(
1313 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
1314 output_file)
1315
1316 def test_Sign_withDataWipe(self):
1317 common.OPTIONS.wipe_user_data = True
1318 payload = self._create_payload_full()
1319 payload.Sign(PayloadSigner())
1320
1321 with open(payload.payload_properties) as properties_fp:
1322 self.assertIn("POWERWASH=1", properties_fp.read())
1323
Tao Bao667ff572018-02-10 00:02:40 -08001324 def test_Sign_secondary(self):
1325 payload = self._create_payload_full(secondary=True)
1326 payload.Sign(PayloadSigner())
1327
1328 with open(payload.payload_properties) as properties_fp:
1329 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read())
1330
Tao Baoc7b403a2018-01-30 18:19:04 -08001331 def test_Sign_badSigner(self):
1332 """Tests that signing failure can be captured."""
1333 payload = self._create_payload_full()
1334 payload_signer = PayloadSigner()
1335 payload_signer.signer_args.append('bad-option')
1336 self.assertRaises(AssertionError, payload.Sign, payload_signer)
1337
1338 def test_WriteToZip(self):
1339 payload = self._create_payload_full()
1340 payload.Sign(PayloadSigner())
1341
1342 output_file = common.MakeTempFile(suffix='.zip')
1343 with zipfile.ZipFile(output_file, 'w') as output_zip:
1344 payload.WriteToZip(output_zip)
1345
1346 with zipfile.ZipFile(output_file) as verify_zip:
1347 # First make sure we have the essential entries.
1348 namelist = verify_zip.namelist()
1349 self.assertIn(Payload.PAYLOAD_BIN, namelist)
1350 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
1351
1352 # Then assert these entries are stored.
1353 for entry_info in verify_zip.infolist():
1354 if entry_info.filename not in (Payload.PAYLOAD_BIN,
1355 Payload.PAYLOAD_PROPERTIES_TXT):
1356 continue
1357 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
1358
1359 def test_WriteToZip_unsignedPayload(self):
1360 """Unsigned payloads should not be allowed to be written to zip."""
1361 payload = self._create_payload_full()
1362
1363 output_file = common.MakeTempFile(suffix='.zip')
1364 with zipfile.ZipFile(output_file, 'w') as output_zip:
1365 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
1366
1367 # Also test with incremental payload.
1368 payload = self._create_payload_incremental()
1369
1370 output_file = common.MakeTempFile(suffix='.zip')
1371 with zipfile.ZipFile(output_file, 'w') as output_zip:
1372 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001373
1374 def test_WriteToZip_secondary(self):
1375 payload = self._create_payload_full(secondary=True)
1376 payload.Sign(PayloadSigner())
1377
1378 output_file = common.MakeTempFile(suffix='.zip')
1379 with zipfile.ZipFile(output_file, 'w') as output_zip:
Tao Bao667ff572018-02-10 00:02:40 -08001380 payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001381
1382 with zipfile.ZipFile(output_file) as verify_zip:
1383 # First make sure we have the essential entries.
1384 namelist = verify_zip.namelist()
1385 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist)
1386 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist)
1387
1388 # Then assert these entries are stored.
1389 for entry_info in verify_zip.infolist():
1390 if entry_info.filename not in (
1391 Payload.SECONDARY_PAYLOAD_BIN,
1392 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
1393 continue
1394 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)