1*9e94795aSAndroid Build Coastguard Worker#!/usr/bin/env python 2*9e94795aSAndroid Build Coastguard Worker# Copyright (C) 2017 The Android Open Source Project 3*9e94795aSAndroid Build Coastguard Worker# 4*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*9e94795aSAndroid Build Coastguard Worker# 8*9e94795aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*9e94795aSAndroid Build Coastguard Worker# 10*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*9e94795aSAndroid Build Coastguard Worker# limitations under the License. 15*9e94795aSAndroid Build Coastguard Worker 16*9e94795aSAndroid Build Coastguard Workerimport argparse 17*9e94795aSAndroid Build Coastguard Workerimport logging 18*9e94795aSAndroid Build Coastguard Workerimport sys 19*9e94795aSAndroid Build Coastguard Workerimport traceback 20*9e94795aSAndroid Build Coastguard Workerimport zipfile 21*9e94795aSAndroid Build Coastguard Worker 22*9e94795aSAndroid Build Coastguard Workerfrom rangelib import RangeSet 23*9e94795aSAndroid Build Coastguard Worker 24*9e94795aSAndroid Build Coastguard Workerclass Stash(object): 25*9e94795aSAndroid Build Coastguard Worker """Build a map to track stashed blocks during update simulation.""" 26*9e94795aSAndroid Build Coastguard Worker 27*9e94795aSAndroid Build Coastguard Worker def __init__(self): 28*9e94795aSAndroid Build Coastguard Worker self.blocks_stashed = 0 29*9e94795aSAndroid Build Coastguard Worker self.overlap_blocks_stashed = 0 30*9e94795aSAndroid Build Coastguard Worker self.max_stash_needed = 0 31*9e94795aSAndroid Build Coastguard Worker self.current_stash_size = 0 32*9e94795aSAndroid Build Coastguard Worker self.stash_map = {} 33*9e94795aSAndroid Build Coastguard Worker 34*9e94795aSAndroid Build Coastguard Worker def StashBlocks(self, SHA1, blocks): 35*9e94795aSAndroid Build Coastguard Worker if SHA1 in self.stash_map: 36*9e94795aSAndroid Build Coastguard Worker logging.info("already stashed {}: {}".format(SHA1, blocks)) 37*9e94795aSAndroid Build Coastguard Worker return 38*9e94795aSAndroid Build Coastguard Worker self.blocks_stashed += blocks.size() 39*9e94795aSAndroid Build Coastguard Worker self.current_stash_size += blocks.size() 40*9e94795aSAndroid Build Coastguard Worker self.max_stash_needed = max(self.current_stash_size, self.max_stash_needed) 41*9e94795aSAndroid Build Coastguard Worker self.stash_map[SHA1] = blocks 42*9e94795aSAndroid Build Coastguard Worker 43*9e94795aSAndroid Build Coastguard Worker def FreeBlocks(self, SHA1): 44*9e94795aSAndroid Build Coastguard Worker assert self.stash_map.has_key(SHA1), "stash {} not found".format(SHA1) 45*9e94795aSAndroid Build Coastguard Worker self.current_stash_size -= self.stash_map[SHA1].size() 46*9e94795aSAndroid Build Coastguard Worker del self.stash_map[SHA1] 47*9e94795aSAndroid Build Coastguard Worker 48*9e94795aSAndroid Build Coastguard Worker def HandleOverlapBlocks(self, SHA1, blocks): 49*9e94795aSAndroid Build Coastguard Worker self.StashBlocks(SHA1, blocks) 50*9e94795aSAndroid Build Coastguard Worker self.overlap_blocks_stashed += blocks.size() 51*9e94795aSAndroid Build Coastguard Worker self.FreeBlocks(SHA1) 52*9e94795aSAndroid Build Coastguard Worker 53*9e94795aSAndroid Build Coastguard Worker 54*9e94795aSAndroid Build Coastguard Workerclass OtaPackageParser(object): 55*9e94795aSAndroid Build Coastguard Worker """Parse a block-based OTA package.""" 56*9e94795aSAndroid Build Coastguard Worker 57*9e94795aSAndroid Build Coastguard Worker def __init__(self, package): 58*9e94795aSAndroid Build Coastguard Worker self.package = package 59*9e94795aSAndroid Build Coastguard Worker self.new_data_size = 0 60*9e94795aSAndroid Build Coastguard Worker self.patch_data_size = 0 61*9e94795aSAndroid Build Coastguard Worker self.block_written = 0 62*9e94795aSAndroid Build Coastguard Worker self.block_stashed = 0 63*9e94795aSAndroid Build Coastguard Worker 64*9e94795aSAndroid Build Coastguard Worker @staticmethod 65*9e94795aSAndroid Build Coastguard Worker def GetSizeString(size): 66*9e94795aSAndroid Build Coastguard Worker assert size >= 0 67*9e94795aSAndroid Build Coastguard Worker base = 1024.0 68*9e94795aSAndroid Build Coastguard Worker if size <= base: 69*9e94795aSAndroid Build Coastguard Worker return "{} bytes".format(size) 70*9e94795aSAndroid Build Coastguard Worker for units in ['K', 'M', 'G']: 71*9e94795aSAndroid Build Coastguard Worker if size <= base * 1024 or units == 'G': 72*9e94795aSAndroid Build Coastguard Worker return "{:.1f}{}".format(size / base, units) 73*9e94795aSAndroid Build Coastguard Worker base *= 1024 74*9e94795aSAndroid Build Coastguard Worker 75*9e94795aSAndroid Build Coastguard Worker def ParseTransferList(self, name): 76*9e94795aSAndroid Build Coastguard Worker """Simulate the transfer commands and calculate the amout of I/O.""" 77*9e94795aSAndroid Build Coastguard Worker 78*9e94795aSAndroid Build Coastguard Worker logging.info("\nSimulating commands in '{}':".format(name)) 79*9e94795aSAndroid Build Coastguard Worker lines = self.package.read(name).strip().splitlines() 80*9e94795aSAndroid Build Coastguard Worker assert len(lines) >= 4, "{} is too short; Transfer list expects at least" \ 81*9e94795aSAndroid Build Coastguard Worker "4 lines, it has {}".format(name, len(lines)) 82*9e94795aSAndroid Build Coastguard Worker assert int(lines[0]) >= 3 83*9e94795aSAndroid Build Coastguard Worker logging.info("(version: {})".format(lines[0])) 84*9e94795aSAndroid Build Coastguard Worker 85*9e94795aSAndroid Build Coastguard Worker blocks_written = 0 86*9e94795aSAndroid Build Coastguard Worker my_stash = Stash() 87*9e94795aSAndroid Build Coastguard Worker for line in lines[4:]: 88*9e94795aSAndroid Build Coastguard Worker cmd_list = line.strip().split(" ") 89*9e94795aSAndroid Build Coastguard Worker cmd_name = cmd_list[0] 90*9e94795aSAndroid Build Coastguard Worker try: 91*9e94795aSAndroid Build Coastguard Worker if cmd_name == "new" or cmd_name == "zero": 92*9e94795aSAndroid Build Coastguard Worker assert len(cmd_list) == 2, "command format error: {}".format(line) 93*9e94795aSAndroid Build Coastguard Worker target_range = RangeSet.parse_raw(cmd_list[1]) 94*9e94795aSAndroid Build Coastguard Worker blocks_written += target_range.size() 95*9e94795aSAndroid Build Coastguard Worker elif cmd_name == "move": 96*9e94795aSAndroid Build Coastguard Worker # Example: move <onehash> <tgt_range> <src_blk_count> <src_range> 97*9e94795aSAndroid Build Coastguard Worker # [<loc_range> <stashed_blocks>] 98*9e94795aSAndroid Build Coastguard Worker assert len(cmd_list) >= 5, "command format error: {}".format(line) 99*9e94795aSAndroid Build Coastguard Worker target_range = RangeSet.parse_raw(cmd_list[2]) 100*9e94795aSAndroid Build Coastguard Worker blocks_written += target_range.size() 101*9e94795aSAndroid Build Coastguard Worker if cmd_list[4] == '-': 102*9e94795aSAndroid Build Coastguard Worker continue 103*9e94795aSAndroid Build Coastguard Worker SHA1 = cmd_list[1] 104*9e94795aSAndroid Build Coastguard Worker source_range = RangeSet.parse_raw(cmd_list[4]) 105*9e94795aSAndroid Build Coastguard Worker if target_range.overlaps(source_range): 106*9e94795aSAndroid Build Coastguard Worker my_stash.HandleOverlapBlocks(SHA1, source_range) 107*9e94795aSAndroid Build Coastguard Worker elif cmd_name == "bsdiff" or cmd_name == "imgdiff": 108*9e94795aSAndroid Build Coastguard Worker # Example: bsdiff <offset> <len> <src_hash> <tgt_hash> <tgt_range> 109*9e94795aSAndroid Build Coastguard Worker # <src_blk_count> <src_range> [<loc_range> <stashed_blocks>] 110*9e94795aSAndroid Build Coastguard Worker assert len(cmd_list) >= 8, "command format error: {}".format(line) 111*9e94795aSAndroid Build Coastguard Worker target_range = RangeSet.parse_raw(cmd_list[5]) 112*9e94795aSAndroid Build Coastguard Worker blocks_written += target_range.size() 113*9e94795aSAndroid Build Coastguard Worker if cmd_list[7] == '-': 114*9e94795aSAndroid Build Coastguard Worker continue 115*9e94795aSAndroid Build Coastguard Worker source_SHA1 = cmd_list[3] 116*9e94795aSAndroid Build Coastguard Worker source_range = RangeSet.parse_raw(cmd_list[7]) 117*9e94795aSAndroid Build Coastguard Worker if target_range.overlaps(source_range): 118*9e94795aSAndroid Build Coastguard Worker my_stash.HandleOverlapBlocks(source_SHA1, source_range) 119*9e94795aSAndroid Build Coastguard Worker elif cmd_name == "stash": 120*9e94795aSAndroid Build Coastguard Worker assert len(cmd_list) == 3, "command format error: {}".format(line) 121*9e94795aSAndroid Build Coastguard Worker SHA1 = cmd_list[1] 122*9e94795aSAndroid Build Coastguard Worker source_range = RangeSet.parse_raw(cmd_list[2]) 123*9e94795aSAndroid Build Coastguard Worker my_stash.StashBlocks(SHA1, source_range) 124*9e94795aSAndroid Build Coastguard Worker elif cmd_name == "free": 125*9e94795aSAndroid Build Coastguard Worker assert len(cmd_list) == 2, "command format error: {}".format(line) 126*9e94795aSAndroid Build Coastguard Worker SHA1 = cmd_list[1] 127*9e94795aSAndroid Build Coastguard Worker my_stash.FreeBlocks(SHA1) 128*9e94795aSAndroid Build Coastguard Worker except: 129*9e94795aSAndroid Build Coastguard Worker logging.error("failed to parse command in: " + line) 130*9e94795aSAndroid Build Coastguard Worker raise 131*9e94795aSAndroid Build Coastguard Worker 132*9e94795aSAndroid Build Coastguard Worker self.block_written += blocks_written 133*9e94795aSAndroid Build Coastguard Worker self.block_stashed += my_stash.blocks_stashed 134*9e94795aSAndroid Build Coastguard Worker 135*9e94795aSAndroid Build Coastguard Worker logging.info("blocks written: {} (expected: {})".format( 136*9e94795aSAndroid Build Coastguard Worker blocks_written, lines[1])) 137*9e94795aSAndroid Build Coastguard Worker logging.info("max blocks stashed simultaneously: {} (expected: {})". 138*9e94795aSAndroid Build Coastguard Worker format(my_stash.max_stash_needed, lines[3])) 139*9e94795aSAndroid Build Coastguard Worker logging.info("total blocks stashed: {}".format(my_stash.blocks_stashed)) 140*9e94795aSAndroid Build Coastguard Worker logging.info("blocks stashed implicitly: {}".format( 141*9e94795aSAndroid Build Coastguard Worker my_stash.overlap_blocks_stashed)) 142*9e94795aSAndroid Build Coastguard Worker 143*9e94795aSAndroid Build Coastguard Worker def PrintDataInfo(self, partition): 144*9e94795aSAndroid Build Coastguard Worker logging.info("\nReading data info for {} partition:".format(partition)) 145*9e94795aSAndroid Build Coastguard Worker new_data = self.package.getinfo(partition + ".new.dat") 146*9e94795aSAndroid Build Coastguard Worker patch_data = self.package.getinfo(partition + ".patch.dat") 147*9e94795aSAndroid Build Coastguard Worker logging.info("{:<40}{:<40}".format(new_data.filename, patch_data.filename)) 148*9e94795aSAndroid Build Coastguard Worker logging.info("{:<40}{:<40}".format( 149*9e94795aSAndroid Build Coastguard Worker "compress_type: " + str(new_data.compress_type), 150*9e94795aSAndroid Build Coastguard Worker "compress_type: " + str(patch_data.compress_type))) 151*9e94795aSAndroid Build Coastguard Worker logging.info("{:<40}{:<40}".format( 152*9e94795aSAndroid Build Coastguard Worker "compressed_size: " + OtaPackageParser.GetSizeString( 153*9e94795aSAndroid Build Coastguard Worker new_data.compress_size), 154*9e94795aSAndroid Build Coastguard Worker "compressed_size: " + OtaPackageParser.GetSizeString( 155*9e94795aSAndroid Build Coastguard Worker patch_data.compress_size))) 156*9e94795aSAndroid Build Coastguard Worker logging.info("{:<40}{:<40}".format( 157*9e94795aSAndroid Build Coastguard Worker "file_size: " + OtaPackageParser.GetSizeString(new_data.file_size), 158*9e94795aSAndroid Build Coastguard Worker "file_size: " + OtaPackageParser.GetSizeString(patch_data.file_size))) 159*9e94795aSAndroid Build Coastguard Worker 160*9e94795aSAndroid Build Coastguard Worker self.new_data_size += new_data.file_size 161*9e94795aSAndroid Build Coastguard Worker self.patch_data_size += patch_data.file_size 162*9e94795aSAndroid Build Coastguard Worker 163*9e94795aSAndroid Build Coastguard Worker def AnalyzePartition(self, partition): 164*9e94795aSAndroid Build Coastguard Worker assert partition in ("system", "vendor") 165*9e94795aSAndroid Build Coastguard Worker assert partition + ".new.dat" in self.package.namelist() 166*9e94795aSAndroid Build Coastguard Worker assert partition + ".patch.dat" in self.package.namelist() 167*9e94795aSAndroid Build Coastguard Worker assert partition + ".transfer.list" in self.package.namelist() 168*9e94795aSAndroid Build Coastguard Worker 169*9e94795aSAndroid Build Coastguard Worker self.PrintDataInfo(partition) 170*9e94795aSAndroid Build Coastguard Worker self.ParseTransferList(partition + ".transfer.list") 171*9e94795aSAndroid Build Coastguard Worker 172*9e94795aSAndroid Build Coastguard Worker def PrintMetadata(self): 173*9e94795aSAndroid Build Coastguard Worker metadata_path = "META-INF/com/android/metadata" 174*9e94795aSAndroid Build Coastguard Worker logging.info("\nMetadata info:") 175*9e94795aSAndroid Build Coastguard Worker metadata_info = {} 176*9e94795aSAndroid Build Coastguard Worker for line in self.package.read(metadata_path).strip().splitlines(): 177*9e94795aSAndroid Build Coastguard Worker index = line.find("=") 178*9e94795aSAndroid Build Coastguard Worker metadata_info[line[0 : index].strip()] = line[index + 1:].strip() 179*9e94795aSAndroid Build Coastguard Worker assert metadata_info.get("ota-type") == "BLOCK" 180*9e94795aSAndroid Build Coastguard Worker assert "pre-device" in metadata_info 181*9e94795aSAndroid Build Coastguard Worker logging.info("device: {}".format(metadata_info["pre-device"])) 182*9e94795aSAndroid Build Coastguard Worker if "pre-build" in metadata_info: 183*9e94795aSAndroid Build Coastguard Worker logging.info("pre-build: {}".format(metadata_info["pre-build"])) 184*9e94795aSAndroid Build Coastguard Worker assert "post-build" in metadata_info 185*9e94795aSAndroid Build Coastguard Worker logging.info("post-build: {}".format(metadata_info["post-build"])) 186*9e94795aSAndroid Build Coastguard Worker 187*9e94795aSAndroid Build Coastguard Worker def Analyze(self): 188*9e94795aSAndroid Build Coastguard Worker logging.info("Analyzing ota package: " + self.package.filename) 189*9e94795aSAndroid Build Coastguard Worker self.PrintMetadata() 190*9e94795aSAndroid Build Coastguard Worker assert "system.new.dat" in self.package.namelist() 191*9e94795aSAndroid Build Coastguard Worker self.AnalyzePartition("system") 192*9e94795aSAndroid Build Coastguard Worker if "vendor.new.dat" in self.package.namelist(): 193*9e94795aSAndroid Build Coastguard Worker self.AnalyzePartition("vendor") 194*9e94795aSAndroid Build Coastguard Worker 195*9e94795aSAndroid Build Coastguard Worker #TODO Add analysis of other partitions(e.g. bootloader, boot, radio) 196*9e94795aSAndroid Build Coastguard Worker 197*9e94795aSAndroid Build Coastguard Worker BLOCK_SIZE = 4096 198*9e94795aSAndroid Build Coastguard Worker logging.info("\nOTA package analyzed:") 199*9e94795aSAndroid Build Coastguard Worker logging.info("new data size (uncompressed): " + 200*9e94795aSAndroid Build Coastguard Worker OtaPackageParser.GetSizeString(self.new_data_size)) 201*9e94795aSAndroid Build Coastguard Worker logging.info("patch data size (uncompressed): " + 202*9e94795aSAndroid Build Coastguard Worker OtaPackageParser.GetSizeString(self.patch_data_size)) 203*9e94795aSAndroid Build Coastguard Worker logging.info("total data written: " + 204*9e94795aSAndroid Build Coastguard Worker OtaPackageParser.GetSizeString(self.block_written * BLOCK_SIZE)) 205*9e94795aSAndroid Build Coastguard Worker logging.info("total data stashed: " + 206*9e94795aSAndroid Build Coastguard Worker OtaPackageParser.GetSizeString(self.block_stashed * BLOCK_SIZE)) 207*9e94795aSAndroid Build Coastguard Worker 208*9e94795aSAndroid Build Coastguard Worker 209*9e94795aSAndroid Build Coastguard Workerdef main(argv): 210*9e94795aSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description='Analyze an OTA package.') 211*9e94795aSAndroid Build Coastguard Worker parser.add_argument("ota_package", help='Path of the OTA package.') 212*9e94795aSAndroid Build Coastguard Worker args = parser.parse_args(argv) 213*9e94795aSAndroid Build Coastguard Worker 214*9e94795aSAndroid Build Coastguard Worker logging_format = '%(message)s' 215*9e94795aSAndroid Build Coastguard Worker logging.basicConfig(level=logging.INFO, format=logging_format) 216*9e94795aSAndroid Build Coastguard Worker 217*9e94795aSAndroid Build Coastguard Worker try: 218*9e94795aSAndroid Build Coastguard Worker with zipfile.ZipFile(args.ota_package, 'r', allowZip64=True) as package: 219*9e94795aSAndroid Build Coastguard Worker package_parser = OtaPackageParser(package) 220*9e94795aSAndroid Build Coastguard Worker package_parser.Analyze() 221*9e94795aSAndroid Build Coastguard Worker except: 222*9e94795aSAndroid Build Coastguard Worker logging.error("Failed to read " + args.ota_package) 223*9e94795aSAndroid Build Coastguard Worker traceback.print_exc() 224*9e94795aSAndroid Build Coastguard Worker sys.exit(1) 225*9e94795aSAndroid Build Coastguard Worker 226*9e94795aSAndroid Build Coastguard Worker 227*9e94795aSAndroid Build Coastguard Workerif __name__ == '__main__': 228*9e94795aSAndroid Build Coastguard Worker main(sys.argv[1:]) 229