blob: 06349a240badf44048f0eb4b8c40c3b5b0af88ce [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
Kelvin Zhangb9fdf2d2022-08-12 14:07:31 -070019import shutil
Kelvin Zhang25ab9982021-06-22 09:51:34 -040020import struct
Kelvin Zhangcff4d762020-07-29 16:37:51 -040021import zipfile
22
Tianjiea2076132020-08-19 17:25:32 -070023import ota_metadata_pb2
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +000024import common
Kelvin Zhangcff4d762020-07-29 16:37:51 -040025from common import (ZipDelete, ZipClose, OPTIONS, MakeTempFile,
26 ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
TJ Rhoades6f488e92022-05-01 22:16:22 -070027 SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
28 GetRamdiskFormat)
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +000029from payload_signer import PayloadSigner
30
Kelvin Zhangcff4d762020-07-29 16:37:51 -040031
Yifan Hong125d0b62020-09-24 17:07:03 -070032logger = logging.getLogger(__name__)
Kelvin Zhang2e417382020-08-20 11:33:11 -040033
34OPTIONS.no_signing = False
35OPTIONS.force_non_ab = False
36OPTIONS.wipe_user_data = False
37OPTIONS.downgrade = False
38OPTIONS.key_passwords = {}
39OPTIONS.package_key = None
40OPTIONS.incremental_source = None
41OPTIONS.retrofit_dynamic_partitions = False
42OPTIONS.output_metadata_path = None
43OPTIONS.boot_variable_file = None
44
Kelvin Zhangcff4d762020-07-29 16:37:51 -040045METADATA_NAME = 'META-INF/com/android/metadata'
Tianjiea2076132020-08-19 17:25:32 -070046METADATA_PROTO_NAME = 'META-INF/com/android/metadata.pb'
Kelvin Zhangcff4d762020-07-29 16:37:51 -040047UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Kelvin Zhang05ff7052021-02-10 09:13:26 -050048SECURITY_PATCH_LEVEL_PROP_NAME = "ro.build.version.security_patch"
49
Kelvin Zhangcff4d762020-07-29 16:37:51 -040050
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000051def FinalizeMetadata(metadata, input_file, output_file, needed_property_files=None, package_key=None, pw=None):
Kelvin Zhangcff4d762020-07-29 16:37:51 -040052 """Finalizes the metadata and signs an A/B OTA package.
53
54 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
55 that contains the offsets and sizes for the ZIP entries. An example
56 property-files string is as follows.
57
58 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
59
60 OTA server can pass down this string, in addition to the package URL, to the
61 system update client. System update client can then fetch individual ZIP
62 entries (ZIP_STORED) directly at the given offset of the URL.
63
64 Args:
65 metadata: The metadata dict for the package.
66 input_file: The input ZIP filename that doesn't contain the package METADATA
67 entry yet.
68 output_file: The final output ZIP filename.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000069 needed_property_files: The list of PropertyFiles' to be generated. Default is [AbOtaPropertyFiles(), StreamingPropertyFiles()]
70 package_key: The key used to sign this OTA package
71 pw: Password for the package_key
Kelvin Zhangcff4d762020-07-29 16:37:51 -040072 """
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000073 no_signing = package_key is None
74
75 if needed_property_files is None:
76 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
77 # all the info of the latter. However, system updaters and OTA servers need to
78 # take time to switch to the new flag. We keep both of the flags for
79 # P-timeframe, and will remove StreamingPropertyFiles in later release.
80 needed_property_files = (
81 AbOtaPropertyFiles(),
82 StreamingPropertyFiles(),
83 )
Kelvin Zhangcff4d762020-07-29 16:37:51 -040084
85 def ComputeAllPropertyFiles(input_file, needed_property_files):
86 # Write the current metadata entry with placeholders.
Kelvin Zhang928c2342020-09-22 16:15:57 -040087 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040088 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070089 metadata.property_files[property_files.name] = property_files.Compute(
90 input_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040091 namelist = input_zip.namelist()
92
Tianjiea2076132020-08-19 17:25:32 -070093 if METADATA_NAME in namelist or METADATA_PROTO_NAME in namelist:
94 ZipDelete(input_file, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang928c2342020-09-22 16:15:57 -040095 output_zip = zipfile.ZipFile(input_file, 'a', allowZip64=True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040096 WriteMetadata(metadata, output_zip)
97 ZipClose(output_zip)
98
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000099 if no_signing:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400100 return input_file
101
102 prelim_signing = MakeTempFile(suffix='.zip')
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000103 SignOutput(input_file, prelim_signing, package_key, pw)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400104 return prelim_signing
105
106 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400107 with zipfile.ZipFile(prelim_signing, allowZip64=True) as prelim_signing_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400108 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700109 metadata.property_files[property_files.name] = property_files.Finalize(
110 prelim_signing_zip,
111 len(metadata.property_files[property_files.name]))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400112
113 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
114 # entries, as well as padding the entry headers. We do a preliminary signing
115 # (with an incomplete metadata entry) to allow that to happen. Then compute
116 # the ZIP entry offsets, write back the final metadata and do the final
117 # signing.
118 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
119 try:
120 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
121 except PropertyFiles.InsufficientSpaceException:
122 # Even with the preliminary signing, the entry orders may change
123 # dramatically, which leads to insufficiently reserved space during the
124 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
125 # preliminary signing works, based on the already ordered ZIP entries, to
126 # address the issue.
127 prelim_signing = ComputeAllPropertyFiles(
128 prelim_signing, needed_property_files)
129 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
130
131 # Replace the METADATA entry.
Tianjiea2076132020-08-19 17:25:32 -0700132 ZipDelete(prelim_signing, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang928c2342020-09-22 16:15:57 -0400133 output_zip = zipfile.ZipFile(prelim_signing, 'a', allowZip64=True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400134 WriteMetadata(metadata, output_zip)
135 ZipClose(output_zip)
136
137 # Re-sign the package after updating the metadata entry.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000138 if no_signing:
Kelvin Zhangb9fdf2d2022-08-12 14:07:31 -0700139 shutil.copy(prelim_signing, output_file)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400140 else:
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000141 SignOutput(prelim_signing, output_file, package_key, pw)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400142
143 # Reopen the final signed zip to double check the streaming metadata.
Kelvin Zhang928c2342020-09-22 16:15:57 -0400144 with zipfile.ZipFile(output_file, allowZip64=True) as output_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400145 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700146 property_files.Verify(
147 output_zip, metadata.property_files[property_files.name].strip())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400148
149 # If requested, dump the metadata to a separate file.
150 output_metadata_path = OPTIONS.output_metadata_path
151 if output_metadata_path:
152 WriteMetadata(metadata, output_metadata_path)
153
154
Tianjiea2076132020-08-19 17:25:32 -0700155def WriteMetadata(metadata_proto, output):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400156 """Writes the metadata to the zip archive or a file.
157
158 Args:
Tianjiea2076132020-08-19 17:25:32 -0700159 metadata_proto: The metadata protobuf for the package.
160 output: A ZipFile object or a string of the output file path. If a string
161 path is given, the metadata in the protobuf format will be written to
162 {output}.pb, e.g. ota_metadata.pb
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400163 """
164
Tianjiea2076132020-08-19 17:25:32 -0700165 metadata_dict = BuildLegacyOtaMetadata(metadata_proto)
166 legacy_metadata = "".join(["%s=%s\n" % kv for kv in
167 sorted(metadata_dict.items())])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400168 if isinstance(output, zipfile.ZipFile):
Tianjiea2076132020-08-19 17:25:32 -0700169 ZipWriteStr(output, METADATA_PROTO_NAME, metadata_proto.SerializeToString(),
170 compress_type=zipfile.ZIP_STORED)
171 ZipWriteStr(output, METADATA_NAME, legacy_metadata,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400172 compress_type=zipfile.ZIP_STORED)
173 return
174
Cole Faustb820bcd2021-10-28 13:59:48 -0700175 with open('{}.pb'.format(output), 'wb') as f:
Tianjiea2076132020-08-19 17:25:32 -0700176 f.write(metadata_proto.SerializeToString())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400177 with open(output, 'w') as f:
Tianjiea2076132020-08-19 17:25:32 -0700178 f.write(legacy_metadata)
179
180
181def UpdateDeviceState(device_state, build_info, boot_variable_values,
182 is_post_build):
183 """Update the fields of the DeviceState proto with build info."""
184
Tianjie2bb14862020-08-28 16:24:34 -0700185 def UpdatePartitionStates(partition_states):
186 """Update the per-partition state according to its build.prop"""
Kelvin Zhang39aea442020-08-17 11:04:25 -0400187 if not build_info.is_ab:
188 return
Tianjie2bb14862020-08-28 16:24:34 -0700189 build_info_set = ComputeRuntimeBuildInfos(build_info,
190 boot_variable_values)
Kelvin Zhang39aea442020-08-17 11:04:25 -0400191 assert "ab_partitions" in build_info.info_dict,\
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500192 "ab_partitions property required for ab update."
Kelvin Zhang39aea442020-08-17 11:04:25 -0400193 ab_partitions = set(build_info.info_dict.get("ab_partitions"))
194
195 # delta_generator will error out on unused timestamps,
196 # so only generate timestamps for dynamic partitions
197 # used in OTA update.
Yifan Hong5057b952021-01-07 14:09:57 -0800198 for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
Tianjie2bb14862020-08-28 16:24:34 -0700199 partition_prop = build_info.info_dict.get(
200 '{}.build.prop'.format(partition))
201 # Skip if the partition is missing, or it doesn't have a build.prop
202 if not partition_prop or not partition_prop.build_props:
203 continue
204
205 partition_state = partition_states.add()
206 partition_state.partition_name = partition
207 # Update the partition's runtime device names and fingerprints
208 partition_devices = set()
209 partition_fingerprints = set()
210 for runtime_build_info in build_info_set:
211 partition_devices.add(
212 runtime_build_info.GetPartitionBuildProp('ro.product.device',
213 partition))
214 partition_fingerprints.add(
215 runtime_build_info.GetPartitionFingerprint(partition))
216
217 partition_state.device.extend(sorted(partition_devices))
218 partition_state.build.extend(sorted(partition_fingerprints))
219
220 # TODO(xunchang) set the boot image's version with kmi. Note the boot
221 # image doesn't have a file map.
222 partition_state.version = build_info.GetPartitionBuildProp(
223 'ro.build.date.utc', partition)
224
225 # TODO(xunchang), we can save a call to ComputeRuntimeBuildInfos.
Tianjiea2076132020-08-19 17:25:32 -0700226 build_devices, build_fingerprints = \
227 CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values)
228 device_state.device.extend(sorted(build_devices))
229 device_state.build.extend(sorted(build_fingerprints))
230 device_state.build_incremental = build_info.GetBuildProp(
231 'ro.build.version.incremental')
232
Tianjie2bb14862020-08-28 16:24:34 -0700233 UpdatePartitionStates(device_state.partition_state)
Tianjiea2076132020-08-19 17:25:32 -0700234
235 if is_post_build:
236 device_state.sdk_level = build_info.GetBuildProp(
237 'ro.build.version.sdk')
238 device_state.security_patch_level = build_info.GetBuildProp(
239 'ro.build.version.security_patch')
240 # Use the actual post-timestamp, even for a downgrade case.
241 device_state.timestamp = int(build_info.GetBuildProp('ro.build.date.utc'))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400242
243
244def GetPackageMetadata(target_info, source_info=None):
Tianjiea2076132020-08-19 17:25:32 -0700245 """Generates and returns the metadata proto.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400246
Tianjiea2076132020-08-19 17:25:32 -0700247 It generates a ota_metadata protobuf that contains the info to be written
248 into an OTA package (META-INF/com/android/metadata.pb). It also handles the
249 detection of downgrade / data wipe based on the global options.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400250
251 Args:
252 target_info: The BuildInfo instance that holds the target build info.
253 source_info: The BuildInfo instance that holds the source build info, or
254 None if generating full OTA.
255
256 Returns:
Tianjiea2076132020-08-19 17:25:32 -0700257 A protobuf to be written into package metadata entry.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400258 """
259 assert isinstance(target_info, BuildInfo)
260 assert source_info is None or isinstance(source_info, BuildInfo)
261
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400262 boot_variable_values = {}
263 if OPTIONS.boot_variable_file:
264 d = LoadDictionaryFromFile(OPTIONS.boot_variable_file)
265 for key, values in d.items():
266 boot_variable_values[key] = [val.strip() for val in values.split(',')]
267
Tianjiea2076132020-08-19 17:25:32 -0700268 metadata_proto = ota_metadata_pb2.OtaMetadata()
269 # TODO(xunchang) some fields, e.g. post-device isn't necessary. We can
270 # consider skipping them if they aren't used by clients.
271 UpdateDeviceState(metadata_proto.postcondition, target_info,
272 boot_variable_values, True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400273
274 if target_info.is_ab and not OPTIONS.force_non_ab:
Tianjiea2076132020-08-19 17:25:32 -0700275 metadata_proto.type = ota_metadata_pb2.OtaMetadata.AB
276 metadata_proto.required_cache = 0
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400277 else:
Tianjiea2076132020-08-19 17:25:32 -0700278 metadata_proto.type = ota_metadata_pb2.OtaMetadata.BLOCK
279 # cache requirement will be updated by the non-A/B codes.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400280
281 if OPTIONS.wipe_user_data:
Tianjiea2076132020-08-19 17:25:32 -0700282 metadata_proto.wipe = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400283
284 if OPTIONS.retrofit_dynamic_partitions:
Tianjiea2076132020-08-19 17:25:32 -0700285 metadata_proto.retrofit_dynamic_partitions = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400286
287 is_incremental = source_info is not None
288 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700289 UpdateDeviceState(metadata_proto.precondition, source_info,
290 boot_variable_values, False)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400291 else:
Tianjiea2076132020-08-19 17:25:32 -0700292 metadata_proto.precondition.device.extend(
293 metadata_proto.postcondition.device)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400294
295 # Detect downgrades and set up downgrade flags accordingly.
296 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700297 HandleDowngradeMetadata(metadata_proto, target_info, source_info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400298
Tianjiea2076132020-08-19 17:25:32 -0700299 return metadata_proto
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400300
301
Tianjiea2076132020-08-19 17:25:32 -0700302def BuildLegacyOtaMetadata(metadata_proto):
303 """Converts the metadata proto to a legacy metadata dict.
304
305 This metadata dict is used to build the legacy metadata text file for
306 backward compatibility. We won't add new keys to the legacy metadata format.
307 If new information is needed, we should add it as a new field in OtaMetadata
308 proto definition.
309 """
310
311 separator = '|'
312
313 metadata_dict = {}
314 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB:
315 metadata_dict['ota-type'] = 'AB'
316 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK:
317 metadata_dict['ota-type'] = 'BLOCK'
318 if metadata_proto.wipe:
319 metadata_dict['ota-wipe'] = 'yes'
320 if metadata_proto.retrofit_dynamic_partitions:
321 metadata_dict['ota-retrofit-dynamic-partitions'] = 'yes'
322 if metadata_proto.downgrade:
323 metadata_dict['ota-downgrade'] = 'yes'
324
325 metadata_dict['ota-required-cache'] = str(metadata_proto.required_cache)
326
327 post_build = metadata_proto.postcondition
328 metadata_dict['post-build'] = separator.join(post_build.build)
329 metadata_dict['post-build-incremental'] = post_build.build_incremental
330 metadata_dict['post-sdk-level'] = post_build.sdk_level
331 metadata_dict['post-security-patch-level'] = post_build.security_patch_level
332 metadata_dict['post-timestamp'] = str(post_build.timestamp)
333
334 pre_build = metadata_proto.precondition
335 metadata_dict['pre-device'] = separator.join(pre_build.device)
336 # incremental updates
337 if len(pre_build.build) != 0:
338 metadata_dict['pre-build'] = separator.join(pre_build.build)
339 metadata_dict['pre-build-incremental'] = pre_build.build_incremental
340
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500341 if metadata_proto.spl_downgrade:
342 metadata_dict['spl-downgrade'] = 'yes'
Tianjiea2076132020-08-19 17:25:32 -0700343 metadata_dict.update(metadata_proto.property_files)
344
345 return metadata_dict
346
347
348def HandleDowngradeMetadata(metadata_proto, target_info, source_info):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400349 # Only incremental OTAs are allowed to reach here.
350 assert OPTIONS.incremental_source is not None
351
352 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
353 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
354 is_downgrade = int(post_timestamp) < int(pre_timestamp)
355
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500356 if OPTIONS.spl_downgrade:
357 metadata_proto.spl_downgrade = True
358
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400359 if OPTIONS.downgrade:
360 if not is_downgrade:
361 raise RuntimeError(
362 "--downgrade or --override_timestamp specified but no downgrade "
363 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tianjiea2076132020-08-19 17:25:32 -0700364 metadata_proto.downgrade = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400365 else:
366 if is_downgrade:
367 raise RuntimeError(
368 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
369 "Need to specify --override_timestamp OR --downgrade to allow "
370 "building the incremental." % (pre_timestamp, post_timestamp))
371
372
Tianjie2bb14862020-08-28 16:24:34 -0700373def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
374 """Returns a set of build info objects that may exist during runtime."""
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400375
Tianjie2bb14862020-08-28 16:24:34 -0700376 build_info_set = {default_build_info}
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400377 if not boot_variable_values:
Tianjie2bb14862020-08-28 16:24:34 -0700378 return build_info_set
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400379
380 # Calculate all possible combinations of the values for the boot variables.
381 keys = boot_variable_values.keys()
382 value_list = boot_variable_values.values()
383 combinations = [dict(zip(keys, values))
384 for values in itertools.product(*value_list)]
385 for placeholder_values in combinations:
386 # Reload the info_dict as some build properties may change their values
387 # based on the value of ro.boot* properties.
Tianjie2bb14862020-08-28 16:24:34 -0700388 info_dict = copy.deepcopy(default_build_info.info_dict)
Yifan Hong5057b952021-01-07 14:09:57 -0800389 for partition in PARTITIONS_WITH_BUILD_PROP:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400390 partition_prop_key = "{}.build.prop".format(partition)
391 input_file = info_dict[partition_prop_key].input_file
TJ Rhoades6f488e92022-05-01 22:16:22 -0700392 ramdisk = GetRamdiskFormat(info_dict)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400393 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400394 with zipfile.ZipFile(input_file.filename, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400395 info_dict[partition_prop_key] = \
396 PartitionBuildProps.FromInputFile(input_zip, partition,
TJ Rhoades6f488e92022-05-01 22:16:22 -0700397 placeholder_values,
398 ramdisk)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400399 else:
400 info_dict[partition_prop_key] = \
401 PartitionBuildProps.FromInputFile(input_file, partition,
TJ Rhoades6f488e92022-05-01 22:16:22 -0700402 placeholder_values,
403 ramdisk)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400404 info_dict["build.prop"] = info_dict["system.build.prop"]
Tianjie2bb14862020-08-28 16:24:34 -0700405 build_info_set.add(BuildInfo(info_dict, default_build_info.oem_dicts))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400406
Tianjie2bb14862020-08-28 16:24:34 -0700407 return build_info_set
408
409
410def CalculateRuntimeDevicesAndFingerprints(default_build_info,
411 boot_variable_values):
412 """Returns a tuple of sets for runtime devices and fingerprints"""
413
414 device_names = set()
415 fingerprints = set()
416 build_info_set = ComputeRuntimeBuildInfos(default_build_info,
417 boot_variable_values)
418 for runtime_build_info in build_info_set:
419 device_names.add(runtime_build_info.device)
420 fingerprints.add(runtime_build_info.fingerprint)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400421 return device_names, fingerprints
422
423
Kelvin Zhang25ab9982021-06-22 09:51:34 -0400424def GetZipEntryOffset(zfp, entry_info):
425 """Get offset to a beginning of a particular zip entry
426 Args:
427 fp: zipfile.ZipFile
428 entry_info: zipfile.ZipInfo
429
430 Returns:
431 (offset, size) tuple
432 """
433 # Don't use len(entry_info.extra). Because that returns size of extra
434 # fields in central directory. We need to look at local file directory,
435 # as these two might have different sizes.
436
437 # We cannot work with zipfile.ZipFile instances, we need a |fp| for the underlying file.
438 zfp = zfp.fp
439 zfp.seek(entry_info.header_offset)
440 data = zfp.read(zipfile.sizeFileHeader)
441 fheader = struct.unpack(zipfile.structFileHeader, data)
442 # Last two fields of local file header are filename length and
443 # extra length
444 filename_len = fheader[-2]
445 extra_len = fheader[-1]
446 offset = entry_info.header_offset
447 offset += zipfile.sizeFileHeader
448 offset += filename_len + extra_len
449 size = entry_info.file_size
450 return (offset, size)
451
452
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400453class PropertyFiles(object):
454 """A class that computes the property-files string for an OTA package.
455
456 A property-files string is a comma-separated string that contains the
457 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
458 can be fetched directly with the package URL along with the offset/size info.
459 These strings can be used for streaming A/B OTAs, or allowing an updater to
460 download package metadata entry directly, without paying the cost of
461 downloading entire package.
462
463 Computing the final property-files string requires two passes. Because doing
464 the whole package signing (with signapk.jar) will possibly reorder the ZIP
465 entries, which may in turn invalidate earlier computed ZIP entry offset/size
466 values.
467
468 This class provides functions to be called for each pass. The general flow is
469 as follows.
470
471 property_files = PropertyFiles()
472 # The first pass, which writes placeholders before doing initial signing.
473 property_files.Compute()
474 SignOutput()
475
476 # The second pass, by replacing the placeholders with actual data.
477 property_files.Finalize()
478 SignOutput()
479
480 And the caller can additionally verify the final result.
481
482 property_files.Verify()
483 """
484
485 def __init__(self):
486 self.name = None
487 self.required = ()
488 self.optional = ()
489
490 def Compute(self, input_zip):
491 """Computes and returns a property-files string with placeholders.
492
493 We reserve extra space for the offset and size of the metadata entry itself,
494 although we don't know the final values until the package gets signed.
495
496 Args:
497 input_zip: The input ZIP file.
498
499 Returns:
500 A string with placeholders for the metadata offset/size info, e.g.
501 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
502 """
503 return self.GetPropertyFilesString(input_zip, reserve_space=True)
504
505 class InsufficientSpaceException(Exception):
506 pass
507
508 def Finalize(self, input_zip, reserved_length):
509 """Finalizes a property-files string with actual METADATA offset/size info.
510
511 The input ZIP file has been signed, with the ZIP entries in the desired
512 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
513 the ZIP entry offsets and construct the property-files string with actual
514 data. Note that during this process, we must pad the property-files string
515 to the reserved length, so that the METADATA entry size remains the same.
516 Otherwise the entries' offsets and sizes may change again.
517
518 Args:
519 input_zip: The input ZIP file.
520 reserved_length: The reserved length of the property-files string during
521 the call to Compute(). The final string must be no more than this
522 size.
523
524 Returns:
525 A property-files string including the metadata offset/size info, e.g.
526 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
527
528 Raises:
529 InsufficientSpaceException: If the reserved length is insufficient to hold
530 the final string.
531 """
532 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
533 if len(result) > reserved_length:
534 raise self.InsufficientSpaceException(
535 'Insufficient reserved space: reserved={}, actual={}'.format(
536 reserved_length, len(result)))
537
538 result += ' ' * (reserved_length - len(result))
539 return result
540
541 def Verify(self, input_zip, expected):
542 """Verifies the input ZIP file contains the expected property-files string.
543
544 Args:
545 input_zip: The input ZIP file.
546 expected: The property-files string that's computed from Finalize().
547
548 Raises:
549 AssertionError: On finding a mismatch.
550 """
551 actual = self.GetPropertyFilesString(input_zip)
552 assert actual == expected, \
553 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
554
555 def GetPropertyFilesString(self, zip_file, reserve_space=False):
556 """
557 Constructs the property-files string per request.
558
559 Args:
560 zip_file: The input ZIP file.
561 reserved_length: The reserved length of the property-files string.
562
563 Returns:
564 A property-files string including the metadata offset/size info, e.g.
565 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
566 """
567
568 def ComputeEntryOffsetSize(name):
569 """Computes the zip entry offset and size."""
570 info = zip_file.getinfo(name)
Kelvin Zhang25ab9982021-06-22 09:51:34 -0400571 (offset, size) = GetZipEntryOffset(zip_file, info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400572 return '%s:%d:%d' % (os.path.basename(name), offset, size)
573
574 tokens = []
575 tokens.extend(self._GetPrecomputed(zip_file))
576 for entry in self.required:
577 tokens.append(ComputeEntryOffsetSize(entry))
578 for entry in self.optional:
579 if entry in zip_file.namelist():
580 tokens.append(ComputeEntryOffsetSize(entry))
581
582 # 'META-INF/com/android/metadata' is required. We don't know its actual
583 # offset and length (as well as the values for other entries). So we reserve
584 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
585 # the space for metadata entry. Because 'offset' allows a max of 10-digit
586 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
587 # reserved space serves the metadata entry only.
588 if reserve_space:
589 tokens.append('metadata:' + ' ' * 15)
Tianjiea2076132020-08-19 17:25:32 -0700590 tokens.append('metadata.pb:' + ' ' * 15)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400591 else:
592 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
Luca Stefanib6075c52021-11-03 17:10:54 +0100593 if METADATA_PROTO_NAME in zip_file.namelist():
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000594 tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400595
596 return ','.join(tokens)
597
598 def _GetPrecomputed(self, input_zip):
599 """Computes the additional tokens to be included into the property-files.
600
601 This applies to tokens without actual ZIP entries, such as
602 payload_metadata.bin. We want to expose the offset/size to updaters, so
603 that they can download the payload metadata directly with the info.
604
605 Args:
606 input_zip: The input zip file.
607
608 Returns:
609 A list of strings (tokens) to be added to the property-files string.
610 """
611 # pylint: disable=no-self-use
612 # pylint: disable=unused-argument
613 return []
614
615
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000616def SignOutput(temp_zip_name, output_zip_name, package_key=None, pw=None):
617 if package_key is None:
618 package_key = OPTIONS.package_key
619 if pw is None and OPTIONS.key_passwords:
620 pw = OPTIONS.key_passwords[package_key]
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400621
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000622 SignFile(temp_zip_name, output_zip_name, package_key, pw,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400623 whole_file=True)
Tianjiea5fca032021-06-01 22:06:28 -0700624
625
626def ConstructOtaApexInfo(target_zip, source_file=None):
627 """If applicable, add the source version to the apex info."""
628
629 def _ReadApexInfo(input_zip):
630 if "META/apex_info.pb" not in input_zip.namelist():
631 logger.warning("target_file doesn't contain apex_info.pb %s", input_zip)
632 return None
633
634 with input_zip.open("META/apex_info.pb", "r") as zfp:
635 return zfp.read()
636
637 target_apex_string = _ReadApexInfo(target_zip)
638 # Return early if the target apex info doesn't exist or is empty.
639 if not target_apex_string:
640 return target_apex_string
641
642 # If the source apex info isn't available, just return the target info
643 if not source_file:
644 return target_apex_string
645
646 with zipfile.ZipFile(source_file, "r", allowZip64=True) as source_zip:
647 source_apex_string = _ReadApexInfo(source_zip)
648 if not source_apex_string:
649 return target_apex_string
650
651 source_apex_proto = ota_metadata_pb2.ApexMetadata()
652 source_apex_proto.ParseFromString(source_apex_string)
653 source_apex_versions = {apex.package_name: apex.version for apex in
654 source_apex_proto.apex_info}
655
656 # If the apex package is available in the source build, initialize the source
657 # apex version.
658 target_apex_proto = ota_metadata_pb2.ApexMetadata()
659 target_apex_proto.ParseFromString(target_apex_string)
660 for target_apex in target_apex_proto.apex_info:
661 name = target_apex.package_name
662 if name in source_apex_versions:
663 target_apex.source_version = source_apex_versions[name]
664
665 return target_apex_proto.SerializeToString()
Kelvin Zhang410bb382022-01-06 09:15:54 -0800666
667
Kelvin Zhangf2728d62022-01-10 11:42:36 -0800668def IsLz4diffCompatible(source_file: str, target_file: str):
669 """Check whether lz4diff versions in two builds are compatible
670
671 Args:
672 source_file: Path to source build's target_file.zip
673 target_file: Path to target build's target_file.zip
674
675 Returns:
676 bool true if and only if lz4diff versions are compatible
677 """
678 if source_file is None or target_file is None:
679 return False
680 # Right now we enable lz4diff as long as source build has liblz4.so.
681 # In the future we might introduce version system to lz4diff as well.
682 if zipfile.is_zipfile(source_file):
683 with zipfile.ZipFile(source_file, "r") as zfp:
684 return "META/liblz4.so" in zfp.namelist()
685 else:
686 assert os.path.isdir(source_file)
687 return os.path.exists(os.path.join(source_file, "META", "liblz4.so"))
688
689
Kelvin Zhang410bb382022-01-06 09:15:54 -0800690def IsZucchiniCompatible(source_file: str, target_file: str):
691 """Check whether zucchini versions in two builds are compatible
692
693 Args:
694 source_file: Path to source build's target_file.zip
695 target_file: Path to target build's target_file.zip
696
697 Returns:
698 bool true if and only if zucchini versions are compatible
699 """
700 if source_file is None or target_file is None:
701 return False
702 assert os.path.exists(source_file)
703 assert os.path.exists(target_file)
704
705 assert zipfile.is_zipfile(source_file) or os.path.isdir(source_file)
706 assert zipfile.is_zipfile(target_file) or os.path.isdir(target_file)
707 _ZUCCHINI_CONFIG_ENTRY_NAME = "META/zucchini_config.txt"
708
709 def ReadEntry(path, entry):
710 # Read an entry inside a .zip file or extracted dir of .zip file
711 if zipfile.is_zipfile(path):
712 with zipfile.ZipFile(path, "r", allowZip64=True) as zfp:
713 if entry in zfp.namelist():
714 return zfp.read(entry).decode()
715 else:
Zhou Xuezand0d49f52022-09-14 16:26:55 +0800716 entry_path = os.path.join(path, entry)
Kelvin Zhang410bb382022-01-06 09:15:54 -0800717 if os.path.exists(entry_path):
718 with open(entry_path, "r") as fp:
719 return fp.read()
HÃ¥kan Kvist3db1ef62022-05-03 10:19:41 +0200720 return False
721 sourceEntry = ReadEntry(source_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
722 targetEntry = ReadEntry(target_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
723 return sourceEntry and targetEntry and sourceEntry == targetEntry
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000724
725
Kelvin Zhangfa928692022-08-16 17:01:53 +0000726class PayloadGenerator(object):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000727 """Manages the creation and the signing of an A/B OTA Payload."""
728
729 PAYLOAD_BIN = 'payload.bin'
730 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
731 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
732 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
733
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000734 def __init__(self, secondary=False, wipe_user_data=False):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000735 """Initializes a Payload instance.
736
737 Args:
738 secondary: Whether it's generating a secondary payload (default: False).
739 """
740 self.payload_file = None
741 self.payload_properties = None
742 self.secondary = secondary
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000743 self.wipe_user_data = wipe_user_data
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000744
745 def _Run(self, cmd): # pylint: disable=no-self-use
746 # Don't pipe (buffer) the output if verbose is set. Let
747 # brillo_update_payload write to stdout/stderr directly, so its progress can
748 # be monitored.
749 if OPTIONS.verbose:
750 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
751 else:
752 common.RunAndCheckOutput(cmd)
753
754 def Generate(self, target_file, source_file=None, additional_args=None):
755 """Generates a payload from the given target-files zip(s).
756
757 Args:
758 target_file: The filename of the target build target-files zip.
759 source_file: The filename of the source build target-files zip; or None if
760 generating a full OTA.
761 additional_args: A list of additional args that should be passed to
762 brillo_update_payload script; or None.
763 """
764 if additional_args is None:
765 additional_args = []
766
767 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
768 cmd = ["brillo_update_payload", "generate",
769 "--payload", payload_file,
770 "--target_image", target_file]
771 if source_file is not None:
772 cmd.extend(["--source_image", source_file])
773 if OPTIONS.disable_fec_computation:
774 cmd.extend(["--disable_fec_computation", "true"])
775 if OPTIONS.disable_verity_computation:
776 cmd.extend(["--disable_verity_computation", "true"])
777 cmd.extend(additional_args)
778 self._Run(cmd)
779
780 self.payload_file = payload_file
781 self.payload_properties = None
782
783 def Sign(self, payload_signer):
784 """Generates and signs the hashes of the payload and metadata.
785
786 Args:
787 payload_signer: A PayloadSigner() instance that serves the signing work.
788
789 Raises:
790 AssertionError: On any failure when calling brillo_update_payload script.
791 """
792 assert isinstance(payload_signer, PayloadSigner)
793
794 # 1. Generate hashes of the payload and metadata files.
795 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
796 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
797 cmd = ["brillo_update_payload", "hash",
798 "--unsigned_payload", self.payload_file,
799 "--signature_size", str(payload_signer.maximum_signature_size),
800 "--metadata_hash_file", metadata_sig_file,
801 "--payload_hash_file", payload_sig_file]
802 self._Run(cmd)
803
804 # 2. Sign the hashes.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000805 signed_payload_sig_file = payload_signer.SignHashFile(payload_sig_file)
806 signed_metadata_sig_file = payload_signer.SignHashFile(metadata_sig_file)
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000807
808 # 3. Insert the signatures back into the payload file.
809 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
810 suffix=".bin")
811 cmd = ["brillo_update_payload", "sign",
812 "--unsigned_payload", self.payload_file,
813 "--payload", signed_payload_file,
814 "--signature_size", str(payload_signer.maximum_signature_size),
815 "--metadata_signature_file", signed_metadata_sig_file,
816 "--payload_signature_file", signed_payload_sig_file]
817 self._Run(cmd)
818
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000819 self.payload_file = signed_payload_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000820
821 def WriteToZip(self, output_zip):
822 """Writes the payload to the given zip.
823
824 Args:
825 output_zip: The output ZipFile instance.
826 """
827 assert self.payload_file is not None
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000828 # 4. Dump the signed payload properties.
829 properties_file = common.MakeTempFile(prefix="payload-properties-",
830 suffix=".txt")
831 cmd = ["brillo_update_payload", "properties",
832 "--payload", self.payload_file,
833 "--properties_file", properties_file]
834 self._Run(cmd)
835
836 if self.secondary:
837 with open(properties_file, "a") as f:
838 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
839
840 if self.wipe_user_data:
841 with open(properties_file, "a") as f:
842 f.write("POWERWASH=1\n")
843
844 self.payload_properties = properties_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000845
846 if self.secondary:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000847 payload_arcname = PayloadGenerator.SECONDARY_PAYLOAD_BIN
848 payload_properties_arcname = PayloadGenerator.SECONDARY_PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000849 else:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000850 payload_arcname = PayloadGenerator.PAYLOAD_BIN
851 payload_properties_arcname = PayloadGenerator.PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000852
853 # Add the signed payload file and properties into the zip. In order to
854 # support streaming, we pack them as ZIP_STORED. So these entries can be
855 # read directly with the offset and length pairs.
856 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
857 compress_type=zipfile.ZIP_STORED)
858 common.ZipWrite(output_zip, self.payload_properties,
859 arcname=payload_properties_arcname,
860 compress_type=zipfile.ZIP_STORED)
861
862
863class StreamingPropertyFiles(PropertyFiles):
864 """A subclass for computing the property-files for streaming A/B OTAs."""
865
866 def __init__(self):
867 super(StreamingPropertyFiles, self).__init__()
868 self.name = 'ota-streaming-property-files'
869 self.required = (
870 # payload.bin and payload_properties.txt must exist.
871 'payload.bin',
872 'payload_properties.txt',
873 )
874 self.optional = (
875 # apex_info.pb isn't directly used in the update flow
876 'apex_info.pb',
877 # care_map is available only if dm-verity is enabled.
878 'care_map.pb',
879 'care_map.txt',
880 # compatibility.zip is available only if target supports Treble.
881 'compatibility.zip',
882 )
883
884
885class AbOtaPropertyFiles(StreamingPropertyFiles):
886 """The property-files for A/B OTA that includes payload_metadata.bin info.
887
888 Since P, we expose one more token (aka property-file), in addition to the ones
889 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
890 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
891 doesn't exist as a separate ZIP entry, but can be used to verify if the
892 payload can be applied on the given device.
893
894 For backward compatibility, we keep both of the 'ota-streaming-property-files'
895 and the newly added 'ota-property-files' in P. The new token will only be
896 available in 'ota-property-files'.
897 """
898
899 def __init__(self):
900 super(AbOtaPropertyFiles, self).__init__()
901 self.name = 'ota-property-files'
902
903 def _GetPrecomputed(self, input_zip):
904 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
905 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
906
907 @staticmethod
908 def _GetPayloadMetadataOffsetAndSize(input_zip):
909 """Computes the offset and size of the payload metadata for a given package.
910
911 (From system/update_engine/update_metadata.proto)
912 A delta update file contains all the deltas needed to update a system from
913 one specific version to another specific version. The update format is
914 represented by this struct pseudocode:
915
916 struct delta_update_file {
917 char magic[4] = "CrAU";
918 uint64 file_format_version;
919 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
920
921 // Only present if format_version > 1:
922 uint32 metadata_signature_size;
923
924 // The Bzip2 compressed DeltaArchiveManifest
925 char manifest[metadata_signature_size];
926
927 // The signature of the metadata (from the beginning of the payload up to
928 // this location, not including the signature itself). This is a
929 // serialized Signatures message.
930 char medatada_signature_message[metadata_signature_size];
931
932 // Data blobs for files, no specific format. The specific offset
933 // and length of each data blob is recorded in the DeltaArchiveManifest.
934 struct {
935 char data[];
936 } blobs[];
937
938 // These two are not signed:
939 uint64 payload_signatures_message_size;
940 char payload_signatures_message[];
941 };
942
943 'payload-metadata.bin' contains all the bytes from the beginning of the
944 payload, till the end of 'medatada_signature_message'.
945 """
946 payload_info = input_zip.getinfo('payload.bin')
947 (payload_offset, payload_size) = GetZipEntryOffset(input_zip, payload_info)
948
949 # Read the underlying raw zipfile at specified offset
950 payload_fp = input_zip.fp
951 payload_fp.seek(payload_offset)
952 header_bin = payload_fp.read(24)
953
954 # network byte order (big-endian)
955 header = struct.unpack("!IQQL", header_bin)
956
957 # 'CrAU'
958 magic = header[0]
959 assert magic == 0x43724155, "Invalid magic: {:x}, computed offset {}" \
960 .format(magic, payload_offset)
961
962 manifest_size = header[2]
963 metadata_signature_size = header[3]
964 metadata_total = 24 + manifest_size + metadata_signature_size
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000965 assert metadata_total <= payload_size
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000966
967 return (payload_offset, metadata_total)