1import * as zip from '@zip.js/zip.js/dist/zip-full.min.js' 2import { chromeos_update_engine } from './update_metadata_pb.js' 3 4/** 5 * Parse the non-A/B OTA package and return it as a DeltaArchiveManifest 6 * @param {ZipReader} packedFile 7 */ 8export class PayloadNonAB extends chromeos_update_engine.DeltaArchiveManifest{ 9 constructor(packedFile) { 10 super() 11 this.packedFile = packedFile 12 } 13 14 async init() { 15 this.Blocksize = 4096 16 this.partialUpdate = false 17 this.dynamicPartitionMetadata = 18 new chromeos_update_engine.DynamicPartitionMetadata( 19 { snapshotEnabled: false, vabcEnabled: false } 20 ) 21 this.partitions = [] 22 23 const /** RegExp*/ regexName = /[\w_]+(?=\.transfer.list)/g 24 const /** Array<Entry> */ entries = await this.packedFile.getEntries() 25 for (const entry of entries) { 26 if (entry.filename.match(regexName)) { 27 let newPartition = new chromeos_update_engine.PartitionUpdate( 28 {partitionName: entry.filename.match(regexName)[0]} 29 ) 30 newPartition.rawText = await entry.getData(new zip.TextWriter()) 31 await this.parseTransferList(newPartition) 32 this.partitions.push(newPartition) 33 } 34 } 35 } 36 37 async parseTransferList(partition) { 38 let /** Array<String> */ lines = partition.rawText.split('\n') 39 // First four line in header: version, total blocks, 40 // number of stashed entries, maximum used memory for stash 41 if (lines.length < 4) { 42 throw "At least 4 lines in header should be provided." 43 } 44 partition.version = parseInt(lines[0]) 45 partition.totalBlocks = parseInt(lines[1]) 46 partition.entryStashed = parseInt(lines[2]) 47 partition.maxStashed = parseInt(lines[3]) 48 partition.newPartitionInfo = new chromeos_update_engine.PartitionInfo() 49 partition.newPartitionInfo.hash = '' 50 partition.newPartitionInfo.size = 'unknown' 51 /** 52 * The main body have 8 different ops: 53 * zero [rangeset] : fill zeros 54 * new [rangeset] : fill with new data from <partitionName.new.data> 55 * erase [rangeset] : mark given blocks as empty 56 * move <src_hash> <...> 57 * bsdiff <patchstart> <patchlen> <src_hash> <tgt_hash> <...> 58 * imgdiff <patchstart> <patchlen> <src_hash> <tgt_hash> <...> : 59 * Read the source blocks and apply (not for move op) to the target blocks 60 * stash <stash_id> <src_range> : load the given source range to memory 61 * free <stash_id> : free the given <stash_id> 62 * format: 63 * [rangeset]: <# of pairs>, <pair A start>, <pair A end>, ... 64 * <stash_id>: a hex number with length of 40 65 * <...>: We expect to parse the remainder of the parameter tokens as one of: 66 * <tgt_range> <src_block_count> <src_range> (loads data from source image only) 67 * <tgt_range> <src_block_count> - <[stash_id:stash_range] ...> (loads data from stashes only) 68 * <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> 69 * (loads data from both source image and stashes) 70 */ 71 partition.operations = new Array() 72 let newDataSize = await this.sizeNewData(partition.partitionName) 73 for (const line of lines.slice(4)) { 74 let op = new chromeos_update_engine.InstallOperation() 75 let elements = line.split(' ') 76 op.type = elements[0] 77 switch (op.type) { 78 case 'zero': 79 op.dstExtents = elements.slice(1).reduce(parseRange, []) 80 break 81 case 'new': 82 // unlike an A/B OTA, the payload only exists in the payload.bin, 83 // in an non-A/B OTA, the payload exists in both .new.data and .patch.data. 84 // The new ops do not have any information about data length. 85 // what we do here is: read in the size of .new.data, assume the first new 86 // op have the data length of the size of .new.data. 87 op.dataLength = newDataSize 88 newDataSize = 0 89 op.dstExtents = elements.slice(1).reduce(parseRange, []) 90 break 91 case 'erase': 92 op.dstExtents = elements.slice(1).reduce(parseRange, []) 93 break 94 case 'move': 95 op.dstExtents = parseRange([], elements[2]) 96 break 97 case 'bsdiff': 98 op.dataOffset = parseInt(elements[1]) 99 op.dataLength = parseInt(elements[2]) 100 op.dstExtents = parseRange([], elements[5]) 101 break 102 case 'imgdiff': 103 op.dataOffset = parseInt(elements[1]) 104 op.dataLength = parseInt(elements[2]) 105 op.dstExtents = parseRange([], elements[5]) 106 break 107 case 'stash': 108 break 109 case 'free': 110 break 111 } 112 partition.operations.push(op) 113 } 114 } 115 116 /** 117 * Return the size of <partitionName>.new.data.* 118 * @param {String} partitionName 119 * @return {Number} 120 */ 121 async sizeNewData(partitionName) { 122 const /** Array<Entry> */ entries = await this.packedFile.getEntries() 123 const /** RegExp */ regexName = new RegExp(partitionName + '.new.dat.*') 124 for (const entry of entries) { 125 if (entry.filename.match(regexName)) { 126 return entry.uncompressedSize 127 } 128 } 129 } 130} 131 132/** 133 * Parse the range string and return it as an array of extents 134 * @param {extents} Array<extents> 135 * @param {String} rangeset 136 * @return Array<extents> 137 */ 138function parseRange(extents, rangeset) { 139 const regexRange = new RegExp('[\d,]+') 140 if (rangeset.match(regexRange)) { 141 let elements = rangeset.split(',') 142 for (let i=1; i<elements.length; i=i+2) { 143 let extent = new Object( 144 { 145 startBlock: parseInt(elements[i]), 146 numBlocks: parseInt(elements[i+1]) - parseInt(elements[i]) 147 } 148 ) 149 extents.push(extent) 150 } 151 } 152 return extents 153}