blob: 6bbcc923bb46241d93f73f093cfe2e68d1d04185 [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
Yifan Hong125d0b62020-09-24 17:07:03 -070017import logging
Kelvin Zhangcff4d762020-07-29 16:37:51 -040018import os
19import zipfile
20
Tianjiea2076132020-08-19 17:25:32 -070021import ota_metadata_pb2
Kelvin Zhangcff4d762020-07-29 16:37:51 -040022from common import (ZipDelete, ZipClose, OPTIONS, MakeTempFile,
23 ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
Yifan Hong5057b952021-01-07 14:09:57 -080024 SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040025
Yifan Hong125d0b62020-09-24 17:07:03 -070026logger = logging.getLogger(__name__)
Kelvin Zhang2e417382020-08-20 11:33:11 -040027
28OPTIONS.no_signing = False
29OPTIONS.force_non_ab = False
30OPTIONS.wipe_user_data = False
31OPTIONS.downgrade = False
32OPTIONS.key_passwords = {}
33OPTIONS.package_key = None
34OPTIONS.incremental_source = None
35OPTIONS.retrofit_dynamic_partitions = False
36OPTIONS.output_metadata_path = None
37OPTIONS.boot_variable_file = None
38
Kelvin Zhangcff4d762020-07-29 16:37:51 -040039METADATA_NAME = 'META-INF/com/android/metadata'
Tianjiea2076132020-08-19 17:25:32 -070040METADATA_PROTO_NAME = 'META-INF/com/android/metadata.pb'
Kelvin Zhangcff4d762020-07-29 16:37:51 -040041UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
42
Kelvin Zhangcff4d762020-07-29 16:37:51 -040043def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
44 """Finalizes the metadata and signs an A/B OTA package.
45
46 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
47 that contains the offsets and sizes for the ZIP entries. An example
48 property-files string is as follows.
49
50 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
51
52 OTA server can pass down this string, in addition to the package URL, to the
53 system update client. System update client can then fetch individual ZIP
54 entries (ZIP_STORED) directly at the given offset of the URL.
55
56 Args:
57 metadata: The metadata dict for the package.
58 input_file: The input ZIP filename that doesn't contain the package METADATA
59 entry yet.
60 output_file: The final output ZIP filename.
61 needed_property_files: The list of PropertyFiles' to be generated.
62 """
63
64 def ComputeAllPropertyFiles(input_file, needed_property_files):
65 # Write the current metadata entry with placeholders.
Kelvin Zhang928c2342020-09-22 16:15:57 -040066 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040067 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070068 metadata.property_files[property_files.name] = property_files.Compute(
69 input_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040070 namelist = input_zip.namelist()
71
Tianjiea2076132020-08-19 17:25:32 -070072 if METADATA_NAME in namelist or METADATA_PROTO_NAME in namelist:
73 ZipDelete(input_file, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang928c2342020-09-22 16:15:57 -040074 output_zip = zipfile.ZipFile(input_file, 'a', allowZip64=True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040075 WriteMetadata(metadata, output_zip)
76 ZipClose(output_zip)
77
78 if OPTIONS.no_signing:
79 return input_file
80
81 prelim_signing = MakeTempFile(suffix='.zip')
82 SignOutput(input_file, prelim_signing)
83 return prelim_signing
84
85 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -040086 with zipfile.ZipFile(prelim_signing, allowZip64=True) as prelim_signing_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040087 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070088 metadata.property_files[property_files.name] = property_files.Finalize(
89 prelim_signing_zip,
90 len(metadata.property_files[property_files.name]))
Kelvin Zhangcff4d762020-07-29 16:37:51 -040091
92 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
93 # entries, as well as padding the entry headers. We do a preliminary signing
94 # (with an incomplete metadata entry) to allow that to happen. Then compute
95 # the ZIP entry offsets, write back the final metadata and do the final
96 # signing.
97 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
98 try:
99 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
100 except PropertyFiles.InsufficientSpaceException:
101 # Even with the preliminary signing, the entry orders may change
102 # dramatically, which leads to insufficiently reserved space during the
103 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
104 # preliminary signing works, based on the already ordered ZIP entries, to
105 # address the issue.
106 prelim_signing = ComputeAllPropertyFiles(
107 prelim_signing, needed_property_files)
108 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
109
110 # Replace the METADATA entry.
Tianjiea2076132020-08-19 17:25:32 -0700111 ZipDelete(prelim_signing, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang928c2342020-09-22 16:15:57 -0400112 output_zip = zipfile.ZipFile(prelim_signing, 'a', allowZip64=True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400113 WriteMetadata(metadata, output_zip)
114 ZipClose(output_zip)
115
116 # Re-sign the package after updating the metadata entry.
117 if OPTIONS.no_signing:
118 output_file = prelim_signing
119 else:
120 SignOutput(prelim_signing, output_file)
121
122 # Reopen the final signed zip to double check the streaming metadata.
Kelvin Zhang928c2342020-09-22 16:15:57 -0400123 with zipfile.ZipFile(output_file, allowZip64=True) as output_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400124 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700125 property_files.Verify(
126 output_zip, metadata.property_files[property_files.name].strip())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400127
128 # If requested, dump the metadata to a separate file.
129 output_metadata_path = OPTIONS.output_metadata_path
130 if output_metadata_path:
131 WriteMetadata(metadata, output_metadata_path)
132
133
Tianjiea2076132020-08-19 17:25:32 -0700134def WriteMetadata(metadata_proto, output):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400135 """Writes the metadata to the zip archive or a file.
136
137 Args:
Tianjiea2076132020-08-19 17:25:32 -0700138 metadata_proto: The metadata protobuf for the package.
139 output: A ZipFile object or a string of the output file path. If a string
140 path is given, the metadata in the protobuf format will be written to
141 {output}.pb, e.g. ota_metadata.pb
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400142 """
143
Tianjiea2076132020-08-19 17:25:32 -0700144 metadata_dict = BuildLegacyOtaMetadata(metadata_proto)
145 legacy_metadata = "".join(["%s=%s\n" % kv for kv in
146 sorted(metadata_dict.items())])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400147 if isinstance(output, zipfile.ZipFile):
Tianjiea2076132020-08-19 17:25:32 -0700148 ZipWriteStr(output, METADATA_PROTO_NAME, metadata_proto.SerializeToString(),
149 compress_type=zipfile.ZIP_STORED)
150 ZipWriteStr(output, METADATA_NAME, legacy_metadata,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400151 compress_type=zipfile.ZIP_STORED)
152 return
153
Tianjiea2076132020-08-19 17:25:32 -0700154 with open('{}.pb'.format(output), 'w') as f:
155 f.write(metadata_proto.SerializeToString())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400156 with open(output, 'w') as f:
Tianjiea2076132020-08-19 17:25:32 -0700157 f.write(legacy_metadata)
158
159
160def UpdateDeviceState(device_state, build_info, boot_variable_values,
161 is_post_build):
162 """Update the fields of the DeviceState proto with build info."""
163
Tianjie2bb14862020-08-28 16:24:34 -0700164 def UpdatePartitionStates(partition_states):
165 """Update the per-partition state according to its build.prop"""
Kelvin Zhang39aea442020-08-17 11:04:25 -0400166 if not build_info.is_ab:
167 return
Tianjie2bb14862020-08-28 16:24:34 -0700168 build_info_set = ComputeRuntimeBuildInfos(build_info,
169 boot_variable_values)
Kelvin Zhang39aea442020-08-17 11:04:25 -0400170 assert "ab_partitions" in build_info.info_dict,\
171 "ab_partitions property required for ab update."
172 ab_partitions = set(build_info.info_dict.get("ab_partitions"))
173
174 # delta_generator will error out on unused timestamps,
175 # so only generate timestamps for dynamic partitions
176 # used in OTA update.
Yifan Hong5057b952021-01-07 14:09:57 -0800177 for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
Tianjie2bb14862020-08-28 16:24:34 -0700178 partition_prop = build_info.info_dict.get(
179 '{}.build.prop'.format(partition))
180 # Skip if the partition is missing, or it doesn't have a build.prop
181 if not partition_prop or not partition_prop.build_props:
182 continue
183
184 partition_state = partition_states.add()
185 partition_state.partition_name = partition
186 # Update the partition's runtime device names and fingerprints
187 partition_devices = set()
188 partition_fingerprints = set()
189 for runtime_build_info in build_info_set:
190 partition_devices.add(
191 runtime_build_info.GetPartitionBuildProp('ro.product.device',
192 partition))
193 partition_fingerprints.add(
194 runtime_build_info.GetPartitionFingerprint(partition))
195
196 partition_state.device.extend(sorted(partition_devices))
197 partition_state.build.extend(sorted(partition_fingerprints))
198
199 # TODO(xunchang) set the boot image's version with kmi. Note the boot
200 # image doesn't have a file map.
201 partition_state.version = build_info.GetPartitionBuildProp(
202 'ro.build.date.utc', partition)
203
204 # TODO(xunchang), we can save a call to ComputeRuntimeBuildInfos.
Tianjiea2076132020-08-19 17:25:32 -0700205 build_devices, build_fingerprints = \
206 CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values)
207 device_state.device.extend(sorted(build_devices))
208 device_state.build.extend(sorted(build_fingerprints))
209 device_state.build_incremental = build_info.GetBuildProp(
210 'ro.build.version.incremental')
211
Tianjie2bb14862020-08-28 16:24:34 -0700212 UpdatePartitionStates(device_state.partition_state)
Tianjiea2076132020-08-19 17:25:32 -0700213
214 if is_post_build:
215 device_state.sdk_level = build_info.GetBuildProp(
216 'ro.build.version.sdk')
217 device_state.security_patch_level = build_info.GetBuildProp(
218 'ro.build.version.security_patch')
219 # Use the actual post-timestamp, even for a downgrade case.
220 device_state.timestamp = int(build_info.GetBuildProp('ro.build.date.utc'))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400221
222
223def GetPackageMetadata(target_info, source_info=None):
Tianjiea2076132020-08-19 17:25:32 -0700224 """Generates and returns the metadata proto.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400225
Tianjiea2076132020-08-19 17:25:32 -0700226 It generates a ota_metadata protobuf that contains the info to be written
227 into an OTA package (META-INF/com/android/metadata.pb). It also handles the
228 detection of downgrade / data wipe based on the global options.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400229
230 Args:
231 target_info: The BuildInfo instance that holds the target build info.
232 source_info: The BuildInfo instance that holds the source build info, or
233 None if generating full OTA.
234
235 Returns:
Tianjiea2076132020-08-19 17:25:32 -0700236 A protobuf to be written into package metadata entry.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400237 """
238 assert isinstance(target_info, BuildInfo)
239 assert source_info is None or isinstance(source_info, BuildInfo)
240
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400241 boot_variable_values = {}
242 if OPTIONS.boot_variable_file:
243 d = LoadDictionaryFromFile(OPTIONS.boot_variable_file)
244 for key, values in d.items():
245 boot_variable_values[key] = [val.strip() for val in values.split(',')]
246
Tianjiea2076132020-08-19 17:25:32 -0700247 metadata_proto = ota_metadata_pb2.OtaMetadata()
248 # TODO(xunchang) some fields, e.g. post-device isn't necessary. We can
249 # consider skipping them if they aren't used by clients.
250 UpdateDeviceState(metadata_proto.postcondition, target_info,
251 boot_variable_values, True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400252
253 if target_info.is_ab and not OPTIONS.force_non_ab:
Tianjiea2076132020-08-19 17:25:32 -0700254 metadata_proto.type = ota_metadata_pb2.OtaMetadata.AB
255 metadata_proto.required_cache = 0
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400256 else:
Tianjiea2076132020-08-19 17:25:32 -0700257 metadata_proto.type = ota_metadata_pb2.OtaMetadata.BLOCK
258 # cache requirement will be updated by the non-A/B codes.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400259
260 if OPTIONS.wipe_user_data:
Tianjiea2076132020-08-19 17:25:32 -0700261 metadata_proto.wipe = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400262
263 if OPTIONS.retrofit_dynamic_partitions:
Tianjiea2076132020-08-19 17:25:32 -0700264 metadata_proto.retrofit_dynamic_partitions = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400265
266 is_incremental = source_info is not None
267 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700268 UpdateDeviceState(metadata_proto.precondition, source_info,
269 boot_variable_values, False)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400270 else:
Tianjiea2076132020-08-19 17:25:32 -0700271 metadata_proto.precondition.device.extend(
272 metadata_proto.postcondition.device)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400273
274 # Detect downgrades and set up downgrade flags accordingly.
275 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700276 HandleDowngradeMetadata(metadata_proto, target_info, source_info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400277
Tianjiea2076132020-08-19 17:25:32 -0700278 return metadata_proto
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400279
280
Tianjiea2076132020-08-19 17:25:32 -0700281def BuildLegacyOtaMetadata(metadata_proto):
282 """Converts the metadata proto to a legacy metadata dict.
283
284 This metadata dict is used to build the legacy metadata text file for
285 backward compatibility. We won't add new keys to the legacy metadata format.
286 If new information is needed, we should add it as a new field in OtaMetadata
287 proto definition.
288 """
289
290 separator = '|'
291
292 metadata_dict = {}
293 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB:
294 metadata_dict['ota-type'] = 'AB'
295 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK:
296 metadata_dict['ota-type'] = 'BLOCK'
297 if metadata_proto.wipe:
298 metadata_dict['ota-wipe'] = 'yes'
299 if metadata_proto.retrofit_dynamic_partitions:
300 metadata_dict['ota-retrofit-dynamic-partitions'] = 'yes'
301 if metadata_proto.downgrade:
302 metadata_dict['ota-downgrade'] = 'yes'
303
304 metadata_dict['ota-required-cache'] = str(metadata_proto.required_cache)
305
306 post_build = metadata_proto.postcondition
307 metadata_dict['post-build'] = separator.join(post_build.build)
308 metadata_dict['post-build-incremental'] = post_build.build_incremental
309 metadata_dict['post-sdk-level'] = post_build.sdk_level
310 metadata_dict['post-security-patch-level'] = post_build.security_patch_level
311 metadata_dict['post-timestamp'] = str(post_build.timestamp)
312
313 pre_build = metadata_proto.precondition
314 metadata_dict['pre-device'] = separator.join(pre_build.device)
315 # incremental updates
316 if len(pre_build.build) != 0:
317 metadata_dict['pre-build'] = separator.join(pre_build.build)
318 metadata_dict['pre-build-incremental'] = pre_build.build_incremental
319
320 metadata_dict.update(metadata_proto.property_files)
321
322 return metadata_dict
323
324
325def HandleDowngradeMetadata(metadata_proto, target_info, source_info):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400326 # Only incremental OTAs are allowed to reach here.
327 assert OPTIONS.incremental_source is not None
328
329 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
330 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
331 is_downgrade = int(post_timestamp) < int(pre_timestamp)
332
333 if OPTIONS.downgrade:
334 if not is_downgrade:
335 raise RuntimeError(
336 "--downgrade or --override_timestamp specified but no downgrade "
337 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tianjiea2076132020-08-19 17:25:32 -0700338 metadata_proto.downgrade = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400339 else:
340 if is_downgrade:
341 raise RuntimeError(
342 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
343 "Need to specify --override_timestamp OR --downgrade to allow "
344 "building the incremental." % (pre_timestamp, post_timestamp))
345
346
Tianjie2bb14862020-08-28 16:24:34 -0700347def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
348 """Returns a set of build info objects that may exist during runtime."""
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400349
Tianjie2bb14862020-08-28 16:24:34 -0700350 build_info_set = {default_build_info}
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400351 if not boot_variable_values:
Tianjie2bb14862020-08-28 16:24:34 -0700352 return build_info_set
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400353
354 # Calculate all possible combinations of the values for the boot variables.
355 keys = boot_variable_values.keys()
356 value_list = boot_variable_values.values()
357 combinations = [dict(zip(keys, values))
358 for values in itertools.product(*value_list)]
359 for placeholder_values in combinations:
360 # Reload the info_dict as some build properties may change their values
361 # based on the value of ro.boot* properties.
Tianjie2bb14862020-08-28 16:24:34 -0700362 info_dict = copy.deepcopy(default_build_info.info_dict)
Yifan Hong5057b952021-01-07 14:09:57 -0800363 for partition in PARTITIONS_WITH_BUILD_PROP:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400364 partition_prop_key = "{}.build.prop".format(partition)
365 input_file = info_dict[partition_prop_key].input_file
366 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400367 with zipfile.ZipFile(input_file.filename, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400368 info_dict[partition_prop_key] = \
369 PartitionBuildProps.FromInputFile(input_zip, partition,
370 placeholder_values)
371 else:
372 info_dict[partition_prop_key] = \
373 PartitionBuildProps.FromInputFile(input_file, partition,
374 placeholder_values)
375 info_dict["build.prop"] = info_dict["system.build.prop"]
Tianjie2bb14862020-08-28 16:24:34 -0700376 build_info_set.add(BuildInfo(info_dict, default_build_info.oem_dicts))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400377
Tianjie2bb14862020-08-28 16:24:34 -0700378 return build_info_set
379
380
381def CalculateRuntimeDevicesAndFingerprints(default_build_info,
382 boot_variable_values):
383 """Returns a tuple of sets for runtime devices and fingerprints"""
384
385 device_names = set()
386 fingerprints = set()
387 build_info_set = ComputeRuntimeBuildInfos(default_build_info,
388 boot_variable_values)
389 for runtime_build_info in build_info_set:
390 device_names.add(runtime_build_info.device)
391 fingerprints.add(runtime_build_info.fingerprint)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400392 return device_names, fingerprints
393
394
395class PropertyFiles(object):
396 """A class that computes the property-files string for an OTA package.
397
398 A property-files string is a comma-separated string that contains the
399 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
400 can be fetched directly with the package URL along with the offset/size info.
401 These strings can be used for streaming A/B OTAs, or allowing an updater to
402 download package metadata entry directly, without paying the cost of
403 downloading entire package.
404
405 Computing the final property-files string requires two passes. Because doing
406 the whole package signing (with signapk.jar) will possibly reorder the ZIP
407 entries, which may in turn invalidate earlier computed ZIP entry offset/size
408 values.
409
410 This class provides functions to be called for each pass. The general flow is
411 as follows.
412
413 property_files = PropertyFiles()
414 # The first pass, which writes placeholders before doing initial signing.
415 property_files.Compute()
416 SignOutput()
417
418 # The second pass, by replacing the placeholders with actual data.
419 property_files.Finalize()
420 SignOutput()
421
422 And the caller can additionally verify the final result.
423
424 property_files.Verify()
425 """
426
427 def __init__(self):
428 self.name = None
429 self.required = ()
430 self.optional = ()
431
432 def Compute(self, input_zip):
433 """Computes and returns a property-files string with placeholders.
434
435 We reserve extra space for the offset and size of the metadata entry itself,
436 although we don't know the final values until the package gets signed.
437
438 Args:
439 input_zip: The input ZIP file.
440
441 Returns:
442 A string with placeholders for the metadata offset/size info, e.g.
443 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
444 """
445 return self.GetPropertyFilesString(input_zip, reserve_space=True)
446
447 class InsufficientSpaceException(Exception):
448 pass
449
450 def Finalize(self, input_zip, reserved_length):
451 """Finalizes a property-files string with actual METADATA offset/size info.
452
453 The input ZIP file has been signed, with the ZIP entries in the desired
454 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
455 the ZIP entry offsets and construct the property-files string with actual
456 data. Note that during this process, we must pad the property-files string
457 to the reserved length, so that the METADATA entry size remains the same.
458 Otherwise the entries' offsets and sizes may change again.
459
460 Args:
461 input_zip: The input ZIP file.
462 reserved_length: The reserved length of the property-files string during
463 the call to Compute(). The final string must be no more than this
464 size.
465
466 Returns:
467 A property-files string including the metadata offset/size info, e.g.
468 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
469
470 Raises:
471 InsufficientSpaceException: If the reserved length is insufficient to hold
472 the final string.
473 """
474 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
475 if len(result) > reserved_length:
476 raise self.InsufficientSpaceException(
477 'Insufficient reserved space: reserved={}, actual={}'.format(
478 reserved_length, len(result)))
479
480 result += ' ' * (reserved_length - len(result))
481 return result
482
483 def Verify(self, input_zip, expected):
484 """Verifies the input ZIP file contains the expected property-files string.
485
486 Args:
487 input_zip: The input ZIP file.
488 expected: The property-files string that's computed from Finalize().
489
490 Raises:
491 AssertionError: On finding a mismatch.
492 """
493 actual = self.GetPropertyFilesString(input_zip)
494 assert actual == expected, \
495 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
496
497 def GetPropertyFilesString(self, zip_file, reserve_space=False):
498 """
499 Constructs the property-files string per request.
500
501 Args:
502 zip_file: The input ZIP file.
503 reserved_length: The reserved length of the property-files string.
504
505 Returns:
506 A property-files string including the metadata offset/size info, e.g.
507 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
508 """
509
510 def ComputeEntryOffsetSize(name):
511 """Computes the zip entry offset and size."""
512 info = zip_file.getinfo(name)
513 offset = info.header_offset
514 offset += zipfile.sizeFileHeader
515 offset += len(info.extra) + len(info.filename)
516 size = info.file_size
517 return '%s:%d:%d' % (os.path.basename(name), offset, size)
518
519 tokens = []
520 tokens.extend(self._GetPrecomputed(zip_file))
521 for entry in self.required:
522 tokens.append(ComputeEntryOffsetSize(entry))
523 for entry in self.optional:
524 if entry in zip_file.namelist():
525 tokens.append(ComputeEntryOffsetSize(entry))
526
527 # 'META-INF/com/android/metadata' is required. We don't know its actual
528 # offset and length (as well as the values for other entries). So we reserve
529 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
530 # the space for metadata entry. Because 'offset' allows a max of 10-digit
531 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
532 # reserved space serves the metadata entry only.
533 if reserve_space:
534 tokens.append('metadata:' + ' ' * 15)
Tianjiea2076132020-08-19 17:25:32 -0700535 tokens.append('metadata.pb:' + ' ' * 15)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400536 else:
537 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
Tianjiea2076132020-08-19 17:25:32 -0700538 tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400539
540 return ','.join(tokens)
541
542 def _GetPrecomputed(self, input_zip):
543 """Computes the additional tokens to be included into the property-files.
544
545 This applies to tokens without actual ZIP entries, such as
546 payload_metadata.bin. We want to expose the offset/size to updaters, so
547 that they can download the payload metadata directly with the info.
548
549 Args:
550 input_zip: The input zip file.
551
552 Returns:
553 A list of strings (tokens) to be added to the property-files string.
554 """
555 # pylint: disable=no-self-use
556 # pylint: disable=unused-argument
557 return []
558
559
560def SignOutput(temp_zip_name, output_zip_name):
561 pw = OPTIONS.key_passwords[OPTIONS.package_key]
562
563 SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
564 whole_file=True)