blob: d444d41f835bd22e0b5abc8b053bafb641293366 [file] [log] [blame]
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001# Copyright (C) 2020 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import copy
16import itertools
17import os
18import zipfile
19
Tianjiea2076132020-08-19 17:25:32 -070020import ota_metadata_pb2
Kelvin Zhangcff4d762020-07-29 16:37:51 -040021from common import (ZipDelete, ZipClose, OPTIONS, MakeTempFile,
22 ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
23 SignFile, PARTITIONS_WITH_CARE_MAP, PartitionBuildProps)
24
Kelvin Zhang2e417382020-08-20 11:33:11 -040025
26OPTIONS.no_signing = False
27OPTIONS.force_non_ab = False
28OPTIONS.wipe_user_data = False
29OPTIONS.downgrade = False
30OPTIONS.key_passwords = {}
31OPTIONS.package_key = None
32OPTIONS.incremental_source = None
33OPTIONS.retrofit_dynamic_partitions = False
34OPTIONS.output_metadata_path = None
35OPTIONS.boot_variable_file = None
36
Kelvin Zhangcff4d762020-07-29 16:37:51 -040037METADATA_NAME = 'META-INF/com/android/metadata'
Tianjiea2076132020-08-19 17:25:32 -070038METADATA_PROTO_NAME = 'META-INF/com/android/metadata.pb'
Kelvin Zhangcff4d762020-07-29 16:37:51 -040039UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
40
41
42def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
43 """Finalizes the metadata and signs an A/B OTA package.
44
45 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
46 that contains the offsets and sizes for the ZIP entries. An example
47 property-files string is as follows.
48
49 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
50
51 OTA server can pass down this string, in addition to the package URL, to the
52 system update client. System update client can then fetch individual ZIP
53 entries (ZIP_STORED) directly at the given offset of the URL.
54
55 Args:
56 metadata: The metadata dict for the package.
57 input_file: The input ZIP filename that doesn't contain the package METADATA
58 entry yet.
59 output_file: The final output ZIP filename.
60 needed_property_files: The list of PropertyFiles' to be generated.
61 """
62
63 def ComputeAllPropertyFiles(input_file, needed_property_files):
64 # Write the current metadata entry with placeholders.
65 with zipfile.ZipFile(input_file) as input_zip:
66 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070067 metadata.property_files[property_files.name] = property_files.Compute(
68 input_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040069 namelist = input_zip.namelist()
70
Tianjiea2076132020-08-19 17:25:32 -070071 if METADATA_NAME in namelist or METADATA_PROTO_NAME in namelist:
72 ZipDelete(input_file, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhangcff4d762020-07-29 16:37:51 -040073 output_zip = zipfile.ZipFile(input_file, 'a')
74 WriteMetadata(metadata, output_zip)
75 ZipClose(output_zip)
76
77 if OPTIONS.no_signing:
78 return input_file
79
80 prelim_signing = MakeTempFile(suffix='.zip')
81 SignOutput(input_file, prelim_signing)
82 return prelim_signing
83
84 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
85 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
86 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070087 metadata.property_files[property_files.name] = property_files.Finalize(
88 prelim_signing_zip,
89 len(metadata.property_files[property_files.name]))
Kelvin Zhangcff4d762020-07-29 16:37:51 -040090
91 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
92 # entries, as well as padding the entry headers. We do a preliminary signing
93 # (with an incomplete metadata entry) to allow that to happen. Then compute
94 # the ZIP entry offsets, write back the final metadata and do the final
95 # signing.
96 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
97 try:
98 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
99 except PropertyFiles.InsufficientSpaceException:
100 # Even with the preliminary signing, the entry orders may change
101 # dramatically, which leads to insufficiently reserved space during the
102 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
103 # preliminary signing works, based on the already ordered ZIP entries, to
104 # address the issue.
105 prelim_signing = ComputeAllPropertyFiles(
106 prelim_signing, needed_property_files)
107 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
108
109 # Replace the METADATA entry.
Tianjiea2076132020-08-19 17:25:32 -0700110 ZipDelete(prelim_signing, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400111 output_zip = zipfile.ZipFile(prelim_signing, 'a')
112 WriteMetadata(metadata, output_zip)
113 ZipClose(output_zip)
114
115 # Re-sign the package after updating the metadata entry.
116 if OPTIONS.no_signing:
117 output_file = prelim_signing
118 else:
119 SignOutput(prelim_signing, output_file)
120
121 # Reopen the final signed zip to double check the streaming metadata.
122 with zipfile.ZipFile(output_file) as output_zip:
123 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700124 property_files.Verify(
125 output_zip, metadata.property_files[property_files.name].strip())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400126
127 # If requested, dump the metadata to a separate file.
128 output_metadata_path = OPTIONS.output_metadata_path
129 if output_metadata_path:
130 WriteMetadata(metadata, output_metadata_path)
131
132
Tianjiea2076132020-08-19 17:25:32 -0700133def WriteMetadata(metadata_proto, output):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400134 """Writes the metadata to the zip archive or a file.
135
136 Args:
Tianjiea2076132020-08-19 17:25:32 -0700137 metadata_proto: The metadata protobuf for the package.
138 output: A ZipFile object or a string of the output file path. If a string
139 path is given, the metadata in the protobuf format will be written to
140 {output}.pb, e.g. ota_metadata.pb
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400141 """
142
Tianjiea2076132020-08-19 17:25:32 -0700143 metadata_dict = BuildLegacyOtaMetadata(metadata_proto)
144 legacy_metadata = "".join(["%s=%s\n" % kv for kv in
145 sorted(metadata_dict.items())])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400146 if isinstance(output, zipfile.ZipFile):
Tianjiea2076132020-08-19 17:25:32 -0700147 ZipWriteStr(output, METADATA_PROTO_NAME, metadata_proto.SerializeToString(),
148 compress_type=zipfile.ZIP_STORED)
149 ZipWriteStr(output, METADATA_NAME, legacy_metadata,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400150 compress_type=zipfile.ZIP_STORED)
151 return
152
Tianjiea2076132020-08-19 17:25:32 -0700153 with open('{}.pb'.format(output), 'w') as f:
154 f.write(metadata_proto.SerializeToString())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400155 with open(output, 'w') as f:
Tianjiea2076132020-08-19 17:25:32 -0700156 f.write(legacy_metadata)
157
158
159def UpdateDeviceState(device_state, build_info, boot_variable_values,
160 is_post_build):
161 """Update the fields of the DeviceState proto with build info."""
162
Tianjie2bb14862020-08-28 16:24:34 -0700163 def UpdatePartitionStates(partition_states):
164 """Update the per-partition state according to its build.prop"""
Kelvin Zhang39aea442020-08-17 11:04:25 -0400165 if not build_info.is_ab:
166 return
Tianjie2bb14862020-08-28 16:24:34 -0700167 build_info_set = ComputeRuntimeBuildInfos(build_info,
168 boot_variable_values)
Kelvin Zhang39aea442020-08-17 11:04:25 -0400169 assert "ab_partitions" in build_info.info_dict,\
170 "ab_partitions property required for ab update."
171 ab_partitions = set(build_info.info_dict.get("ab_partitions"))
172
173 # delta_generator will error out on unused timestamps,
174 # so only generate timestamps for dynamic partitions
175 # used in OTA update.
176 for partition in sorted(set(PARTITIONS_WITH_CARE_MAP) & ab_partitions):
Tianjie2bb14862020-08-28 16:24:34 -0700177 partition_prop = build_info.info_dict.get(
178 '{}.build.prop'.format(partition))
179 # Skip if the partition is missing, or it doesn't have a build.prop
180 if not partition_prop or not partition_prop.build_props:
181 continue
182
183 partition_state = partition_states.add()
184 partition_state.partition_name = partition
185 # Update the partition's runtime device names and fingerprints
186 partition_devices = set()
187 partition_fingerprints = set()
188 for runtime_build_info in build_info_set:
189 partition_devices.add(
190 runtime_build_info.GetPartitionBuildProp('ro.product.device',
191 partition))
192 partition_fingerprints.add(
193 runtime_build_info.GetPartitionFingerprint(partition))
194
195 partition_state.device.extend(sorted(partition_devices))
196 partition_state.build.extend(sorted(partition_fingerprints))
197
198 # TODO(xunchang) set the boot image's version with kmi. Note the boot
199 # image doesn't have a file map.
200 partition_state.version = build_info.GetPartitionBuildProp(
201 'ro.build.date.utc', partition)
202
203 # TODO(xunchang), we can save a call to ComputeRuntimeBuildInfos.
Tianjiea2076132020-08-19 17:25:32 -0700204 build_devices, build_fingerprints = \
205 CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values)
206 device_state.device.extend(sorted(build_devices))
207 device_state.build.extend(sorted(build_fingerprints))
208 device_state.build_incremental = build_info.GetBuildProp(
209 'ro.build.version.incremental')
210
Tianjie2bb14862020-08-28 16:24:34 -0700211 UpdatePartitionStates(device_state.partition_state)
Tianjiea2076132020-08-19 17:25:32 -0700212
213 if is_post_build:
214 device_state.sdk_level = build_info.GetBuildProp(
215 'ro.build.version.sdk')
216 device_state.security_patch_level = build_info.GetBuildProp(
217 'ro.build.version.security_patch')
218 # Use the actual post-timestamp, even for a downgrade case.
219 device_state.timestamp = int(build_info.GetBuildProp('ro.build.date.utc'))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400220
221
222def GetPackageMetadata(target_info, source_info=None):
Tianjiea2076132020-08-19 17:25:32 -0700223 """Generates and returns the metadata proto.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400224
Tianjiea2076132020-08-19 17:25:32 -0700225 It generates a ota_metadata protobuf that contains the info to be written
226 into an OTA package (META-INF/com/android/metadata.pb). It also handles the
227 detection of downgrade / data wipe based on the global options.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400228
229 Args:
230 target_info: The BuildInfo instance that holds the target build info.
231 source_info: The BuildInfo instance that holds the source build info, or
232 None if generating full OTA.
233
234 Returns:
Tianjiea2076132020-08-19 17:25:32 -0700235 A protobuf to be written into package metadata entry.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400236 """
237 assert isinstance(target_info, BuildInfo)
238 assert source_info is None or isinstance(source_info, BuildInfo)
239
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400240 boot_variable_values = {}
241 if OPTIONS.boot_variable_file:
242 d = LoadDictionaryFromFile(OPTIONS.boot_variable_file)
243 for key, values in d.items():
244 boot_variable_values[key] = [val.strip() for val in values.split(',')]
245
Tianjiea2076132020-08-19 17:25:32 -0700246 metadata_proto = ota_metadata_pb2.OtaMetadata()
247 # TODO(xunchang) some fields, e.g. post-device isn't necessary. We can
248 # consider skipping them if they aren't used by clients.
249 UpdateDeviceState(metadata_proto.postcondition, target_info,
250 boot_variable_values, True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400251
252 if target_info.is_ab and not OPTIONS.force_non_ab:
Tianjiea2076132020-08-19 17:25:32 -0700253 metadata_proto.type = ota_metadata_pb2.OtaMetadata.AB
254 metadata_proto.required_cache = 0
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400255 else:
Tianjiea2076132020-08-19 17:25:32 -0700256 metadata_proto.type = ota_metadata_pb2.OtaMetadata.BLOCK
257 # cache requirement will be updated by the non-A/B codes.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400258
259 if OPTIONS.wipe_user_data:
Tianjiea2076132020-08-19 17:25:32 -0700260 metadata_proto.wipe = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400261
262 if OPTIONS.retrofit_dynamic_partitions:
Tianjiea2076132020-08-19 17:25:32 -0700263 metadata_proto.retrofit_dynamic_partitions = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400264
265 is_incremental = source_info is not None
266 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700267 UpdateDeviceState(metadata_proto.precondition, source_info,
268 boot_variable_values, False)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400269 else:
Tianjiea2076132020-08-19 17:25:32 -0700270 metadata_proto.precondition.device.extend(
271 metadata_proto.postcondition.device)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400272
273 # Detect downgrades and set up downgrade flags accordingly.
274 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700275 HandleDowngradeMetadata(metadata_proto, target_info, source_info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400276
Tianjiea2076132020-08-19 17:25:32 -0700277 return metadata_proto
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400278
279
Tianjiea2076132020-08-19 17:25:32 -0700280def BuildLegacyOtaMetadata(metadata_proto):
281 """Converts the metadata proto to a legacy metadata dict.
282
283 This metadata dict is used to build the legacy metadata text file for
284 backward compatibility. We won't add new keys to the legacy metadata format.
285 If new information is needed, we should add it as a new field in OtaMetadata
286 proto definition.
287 """
288
289 separator = '|'
290
291 metadata_dict = {}
292 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB:
293 metadata_dict['ota-type'] = 'AB'
294 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK:
295 metadata_dict['ota-type'] = 'BLOCK'
296 if metadata_proto.wipe:
297 metadata_dict['ota-wipe'] = 'yes'
298 if metadata_proto.retrofit_dynamic_partitions:
299 metadata_dict['ota-retrofit-dynamic-partitions'] = 'yes'
300 if metadata_proto.downgrade:
301 metadata_dict['ota-downgrade'] = 'yes'
302
303 metadata_dict['ota-required-cache'] = str(metadata_proto.required_cache)
304
305 post_build = metadata_proto.postcondition
306 metadata_dict['post-build'] = separator.join(post_build.build)
307 metadata_dict['post-build-incremental'] = post_build.build_incremental
308 metadata_dict['post-sdk-level'] = post_build.sdk_level
309 metadata_dict['post-security-patch-level'] = post_build.security_patch_level
310 metadata_dict['post-timestamp'] = str(post_build.timestamp)
311
312 pre_build = metadata_proto.precondition
313 metadata_dict['pre-device'] = separator.join(pre_build.device)
314 # incremental updates
315 if len(pre_build.build) != 0:
316 metadata_dict['pre-build'] = separator.join(pre_build.build)
317 metadata_dict['pre-build-incremental'] = pre_build.build_incremental
318
319 metadata_dict.update(metadata_proto.property_files)
320
321 return metadata_dict
322
323
324def HandleDowngradeMetadata(metadata_proto, target_info, source_info):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400325 # Only incremental OTAs are allowed to reach here.
326 assert OPTIONS.incremental_source is not None
327
328 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
329 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
330 is_downgrade = int(post_timestamp) < int(pre_timestamp)
331
332 if OPTIONS.downgrade:
333 if not is_downgrade:
334 raise RuntimeError(
335 "--downgrade or --override_timestamp specified but no downgrade "
336 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tianjiea2076132020-08-19 17:25:32 -0700337 metadata_proto.downgrade = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400338 else:
339 if is_downgrade:
340 raise RuntimeError(
341 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
342 "Need to specify --override_timestamp OR --downgrade to allow "
343 "building the incremental." % (pre_timestamp, post_timestamp))
344
345
Tianjie2bb14862020-08-28 16:24:34 -0700346def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
347 """Returns a set of build info objects that may exist during runtime."""
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400348
Tianjie2bb14862020-08-28 16:24:34 -0700349 build_info_set = {default_build_info}
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400350 if not boot_variable_values:
Tianjie2bb14862020-08-28 16:24:34 -0700351 return build_info_set
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400352
353 # Calculate all possible combinations of the values for the boot variables.
354 keys = boot_variable_values.keys()
355 value_list = boot_variable_values.values()
356 combinations = [dict(zip(keys, values))
357 for values in itertools.product(*value_list)]
358 for placeholder_values in combinations:
359 # Reload the info_dict as some build properties may change their values
360 # based on the value of ro.boot* properties.
Tianjie2bb14862020-08-28 16:24:34 -0700361 info_dict = copy.deepcopy(default_build_info.info_dict)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400362 for partition in PARTITIONS_WITH_CARE_MAP:
363 partition_prop_key = "{}.build.prop".format(partition)
364 input_file = info_dict[partition_prop_key].input_file
365 if isinstance(input_file, zipfile.ZipFile):
366 with zipfile.ZipFile(input_file.filename) as input_zip:
367 info_dict[partition_prop_key] = \
368 PartitionBuildProps.FromInputFile(input_zip, partition,
369 placeholder_values)
370 else:
371 info_dict[partition_prop_key] = \
372 PartitionBuildProps.FromInputFile(input_file, partition,
373 placeholder_values)
374 info_dict["build.prop"] = info_dict["system.build.prop"]
Tianjie2bb14862020-08-28 16:24:34 -0700375 build_info_set.add(BuildInfo(info_dict, default_build_info.oem_dicts))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400376
Tianjie2bb14862020-08-28 16:24:34 -0700377 return build_info_set
378
379
380def CalculateRuntimeDevicesAndFingerprints(default_build_info,
381 boot_variable_values):
382 """Returns a tuple of sets for runtime devices and fingerprints"""
383
384 device_names = set()
385 fingerprints = set()
386 build_info_set = ComputeRuntimeBuildInfos(default_build_info,
387 boot_variable_values)
388 for runtime_build_info in build_info_set:
389 device_names.add(runtime_build_info.device)
390 fingerprints.add(runtime_build_info.fingerprint)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400391 return device_names, fingerprints
392
393
394class PropertyFiles(object):
395 """A class that computes the property-files string for an OTA package.
396
397 A property-files string is a comma-separated string that contains the
398 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
399 can be fetched directly with the package URL along with the offset/size info.
400 These strings can be used for streaming A/B OTAs, or allowing an updater to
401 download package metadata entry directly, without paying the cost of
402 downloading entire package.
403
404 Computing the final property-files string requires two passes. Because doing
405 the whole package signing (with signapk.jar) will possibly reorder the ZIP
406 entries, which may in turn invalidate earlier computed ZIP entry offset/size
407 values.
408
409 This class provides functions to be called for each pass. The general flow is
410 as follows.
411
412 property_files = PropertyFiles()
413 # The first pass, which writes placeholders before doing initial signing.
414 property_files.Compute()
415 SignOutput()
416
417 # The second pass, by replacing the placeholders with actual data.
418 property_files.Finalize()
419 SignOutput()
420
421 And the caller can additionally verify the final result.
422
423 property_files.Verify()
424 """
425
426 def __init__(self):
427 self.name = None
428 self.required = ()
429 self.optional = ()
430
431 def Compute(self, input_zip):
432 """Computes and returns a property-files string with placeholders.
433
434 We reserve extra space for the offset and size of the metadata entry itself,
435 although we don't know the final values until the package gets signed.
436
437 Args:
438 input_zip: The input ZIP file.
439
440 Returns:
441 A string with placeholders for the metadata offset/size info, e.g.
442 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
443 """
444 return self.GetPropertyFilesString(input_zip, reserve_space=True)
445
446 class InsufficientSpaceException(Exception):
447 pass
448
449 def Finalize(self, input_zip, reserved_length):
450 """Finalizes a property-files string with actual METADATA offset/size info.
451
452 The input ZIP file has been signed, with the ZIP entries in the desired
453 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
454 the ZIP entry offsets and construct the property-files string with actual
455 data. Note that during this process, we must pad the property-files string
456 to the reserved length, so that the METADATA entry size remains the same.
457 Otherwise the entries' offsets and sizes may change again.
458
459 Args:
460 input_zip: The input ZIP file.
461 reserved_length: The reserved length of the property-files string during
462 the call to Compute(). The final string must be no more than this
463 size.
464
465 Returns:
466 A property-files string including the metadata offset/size info, e.g.
467 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
468
469 Raises:
470 InsufficientSpaceException: If the reserved length is insufficient to hold
471 the final string.
472 """
473 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
474 if len(result) > reserved_length:
475 raise self.InsufficientSpaceException(
476 'Insufficient reserved space: reserved={}, actual={}'.format(
477 reserved_length, len(result)))
478
479 result += ' ' * (reserved_length - len(result))
480 return result
481
482 def Verify(self, input_zip, expected):
483 """Verifies the input ZIP file contains the expected property-files string.
484
485 Args:
486 input_zip: The input ZIP file.
487 expected: The property-files string that's computed from Finalize().
488
489 Raises:
490 AssertionError: On finding a mismatch.
491 """
492 actual = self.GetPropertyFilesString(input_zip)
493 assert actual == expected, \
494 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
495
496 def GetPropertyFilesString(self, zip_file, reserve_space=False):
497 """
498 Constructs the property-files string per request.
499
500 Args:
501 zip_file: The input ZIP file.
502 reserved_length: The reserved length of the property-files string.
503
504 Returns:
505 A property-files string including the metadata offset/size info, e.g.
506 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
507 """
508
509 def ComputeEntryOffsetSize(name):
510 """Computes the zip entry offset and size."""
511 info = zip_file.getinfo(name)
512 offset = info.header_offset
513 offset += zipfile.sizeFileHeader
514 offset += len(info.extra) + len(info.filename)
515 size = info.file_size
516 return '%s:%d:%d' % (os.path.basename(name), offset, size)
517
518 tokens = []
519 tokens.extend(self._GetPrecomputed(zip_file))
520 for entry in self.required:
521 tokens.append(ComputeEntryOffsetSize(entry))
522 for entry in self.optional:
523 if entry in zip_file.namelist():
524 tokens.append(ComputeEntryOffsetSize(entry))
525
526 # 'META-INF/com/android/metadata' is required. We don't know its actual
527 # offset and length (as well as the values for other entries). So we reserve
528 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
529 # the space for metadata entry. Because 'offset' allows a max of 10-digit
530 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
531 # reserved space serves the metadata entry only.
532 if reserve_space:
533 tokens.append('metadata:' + ' ' * 15)
Tianjiea2076132020-08-19 17:25:32 -0700534 tokens.append('metadata.pb:' + ' ' * 15)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400535 else:
536 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
Tianjiea2076132020-08-19 17:25:32 -0700537 tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400538
539 return ','.join(tokens)
540
541 def _GetPrecomputed(self, input_zip):
542 """Computes the additional tokens to be included into the property-files.
543
544 This applies to tokens without actual ZIP entries, such as
545 payload_metadata.bin. We want to expose the offset/size to updaters, so
546 that they can download the payload metadata directly with the info.
547
548 Args:
549 input_zip: The input zip file.
550
551 Returns:
552 A list of strings (tokens) to be added to the property-files string.
553 """
554 # pylint: disable=no-self-use
555 # pylint: disable=unused-argument
556 return []
557
558
559def SignOutput(temp_zip_name, output_zip_name):
560 pw = OPTIONS.key_passwords[OPTIONS.package_key]
561
562 SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
563 whole_file=True)