Merge "Add support for non-A/B OTA package in OTA_analysis."

This commit is contained in:
Treehugger Robot
2021-08-09 18:56:55 +00:00
committed by Gerrit Code Review
8 changed files with 191 additions and 18 deletions

View File

@@ -34,7 +34,7 @@
<li>
<strong> Incremental </strong>
<!-- Check if the first partition is incremental or not -->
<span v-if="payload.manifest.partitions[0].oldPartitionInfo">
<span v-if="payload.preBuild">
&#9989;
</span>
<span v-else> &#10060; </span>
@@ -44,6 +44,13 @@
<span v-if="payload.manifest.partialUpdate"> &#9989; </span>
<span v-else> &#10060; </span>
</li>
<li>
<strong> A/B update </strong>
<span v-if="!payload.manifest.nonAB">
&#9989;
</span>
<span v-else> &#10060; </span>
</li>
<li>
<strong> VAB </strong>
<span v-if="payload.manifest.dynamicPartitionMetadata.snapshotEnabled">

View File

@@ -1,6 +1,6 @@
<template>
<ul>
<h5> {{ mapType.get(operation.type) }} </h5>
<h5> {{ mapType.getWithDefault(operation.type) }} </h5>
<li v-if="operation.hasOwnProperty('dataOffset')">
<strong> Data offset: </strong> {{ operation.dataOffset }}
</li>
@@ -25,6 +25,7 @@
<script>
import { numBlocks, displayBlocks } from '../services/payload_composition.js'
import { DefaultMap } from '../services/payload.js'
export default {
props: {
@@ -33,7 +34,7 @@ export default {
required: true,
},
mapType: {
type: Map,
type: DefaultMap,
required: true,
},
},

View File

@@ -3,9 +3,6 @@
<p v-if="partition.estimateCowSize">
<strong> Estimate COW Size: </strong> {{ partition.estimateCowSize }} Bytes
</p>
<p v-else>
<strong> Estimate COW Size: </strong> 0 Bytes
</p>
<div
class="toggle"
@click="toggle('showInfo')"
@@ -30,7 +27,7 @@
</strong>
{{ partition.newPartitionInfo.size }} Bytes
</li>
<li>
<li v-if="partition.newPartitionInfo.hash">
<strong>
New Partition Hash:
</strong>

View File

@@ -37,6 +37,7 @@
md="6"
>
<v-btn
:disabled="manifest.nonAB"
block
@click="updateChart('COWmerge')"
>

View File

@@ -28,11 +28,11 @@
</v-col>
</v-row>
<v-divider />
<h3>Metadata Signature</h3>
<div
v-if="payload.metadata_signature"
v-if="payload.metadata_signature && !payload.manifest.nonAB"
class="signature"
>
<h3>Metadata Signature</h3>
<span style="white-space: pre-wrap">
{{ octToHex(payload.metadata_signature.signatures[0].data) }}
</span>

View File

@@ -9,6 +9,7 @@
import * as zip from '@zip.js/zip.js/dist/zip-full.min.js'
import { chromeos_update_engine as update_metadata_pb } from './update_metadata_pb.js'
import { PayloadNonAB } from './payload_nonab.js'
const /** String */ _MAGIC = 'CrAU'
const /** Number */ _VERSION_SIZE = 8
@@ -134,8 +135,17 @@ export class Payload {
}
}
if (!this.payload) {
alert('Please select a legit OTA package')
return
try {
// The temporary variable manifest has to be used here, to prevent the html page
// being rendered before everything is read in properly
let manifest = new PayloadNonAB(this.packedFile)
await manifest.init()
manifest.nonAB = true
this.manifest = manifest
} catch (error) {
alert('Please select a legit OTA package')
return
}
}
}
@@ -208,6 +218,7 @@ export class Payload {
this.cursor += this.manifest_len
this.manifest = update_metadata_pb.DeltaArchiveManifest
.decode(new Uint8Array(buffer))
this.manifest.nonAB = false
}
async readSignature(/** @type {Blob} */buffer) {
@@ -235,6 +246,17 @@ export class Payload {
}
export class DefaultMap extends Map {
/** Reload the original get method. Return the original key value if
* the key does not exist.
* @param {Any} key
*/
getWithDefault(key) {
if (!this.has(key)) return key
return this.get(key)
}
}
export class OpType {
/**
* OpType.mapType create a map that could resolve the operation
@@ -243,8 +265,8 @@ export class OpType {
*/
constructor() {
let /** Array<{String: Number}>*/ types = update_metadata_pb.InstallOperation.Type
this.mapType = new Map()
for (let key in types) {
this.mapType = new DefaultMap()
for (let key of Object.keys(types)) {
this.mapType.set(types[key], key)
}
}
@@ -259,8 +281,8 @@ export class MergeOpType {
constructor() {
let /** Array<{String: Number}>*/ types =
update_metadata_pb.CowMergeOperation.Type
this.mapType = new Map()
for (let key in types) {
this.mapType = new DefaultMap()
for (let key of Object.keys(types)) {
this.mapType.set(types[key], key)
}
}

View File

@@ -34,7 +34,7 @@ export function operatedBlockStatistics(partitions) {
let /** OpType */ opType = new OpType()
for (let partition of partitions) {
for (let operation of partition.operations) {
let operationType = opType.mapType.get(operation.type)
let operationType = opType.mapType.getWithDefault(operation.type)
addNumberToMap(
operatedBlocks,
operationType,
@@ -50,7 +50,7 @@ export function mergeOperationStatistics(partitions, blockSize) {
let /** Number */ totalBlocks = 0
for (let partition of partitions) {
for (let operation of partition.mergeOperations) {
let operationType = opType.mapType.get(operation.type)
let operationType = opType.mapType.getWithDefault(operation.type)
addNumberToMap(
mergeOperations,
operationType,
@@ -79,7 +79,7 @@ export function operatedPayloadStatistics(partitions) {
let /** OpType */ opType = new OpType()
for (let partition of partitions) {
for (let operation of partition.operations) {
let operationType = opType.mapType.get(operation.type)
let operationType = opType.mapType.getWithDefault(operation.type)
addNumberToMap(
operatedBlocks,
operationType,

View File

@@ -0,0 +1,145 @@
import * as zip from '@zip.js/zip.js/dist/zip-full.min.js'
import { chromeos_update_engine } from './update_metadata_pb.js'
/**
* Parse the non-A/B OTA package and return it as a DeltaArchiveManifest
* @param {ZipReader} packedFile
*/
export class PayloadNonAB extends chromeos_update_engine.DeltaArchiveManifest{
constructor(packedFile) {
super()
this.packedFile = packedFile
}
async init() {
this.Blocksize = 4096
this.partialUpdate = false
this.dynamicPartitionMetadata =
new chromeos_update_engine.DynamicPartitionMetadata(
{ snapshotEnabled: false, vabcEnabled: false }
)
this.partitions = []
const /** RegExp*/ regexName = /[\w_]+(?=\.transfer.list)/g
const /** Array<Entry> */ entries = await this.packedFile.getEntries()
for (const entry of entries) {
if (entry.filename.match(regexName)) {
let newPartition = new chromeos_update_engine.PartitionUpdate(
{partitionName: entry.filename.match(regexName)[0]}
)
newPartition.rawText = await entry.getData(new zip.TextWriter())
await this.parseTransferList(newPartition)
this.partitions.push(newPartition)
}
}
}
async parseTransferList(partition) {
let /** Array<String> */ lines = partition.rawText.split('\n')
// First four line in header: version, total blocks,
// number of stashed entries, maximum used memory for stash
if (lines.length < 4) {
throw "At least 4 lines in header should be provided."
}
partition.version = parseInt(lines[0])
partition.totalBlocks = parseInt(lines[1])
partition.entryStashed = parseInt(lines[2])
partition.maxStashed = parseInt(lines[3])
partition.newPartitionInfo = new chromeos_update_engine.PartitionInfo()
partition.newPartitionInfo.hash = ''
partition.newPartitionInfo.size = 'unknown'
/**
* The main body have 8 different ops:
* zero [rangeset] : fill zeros
* new [rangeset] : fill with new data from <partitionName.new.data>
* erase [rangeset] : mark given blocks as empty
* move <...>
* bsdiff <patchstart> <patchlen> <...>
* imgdiff <patchstart> <patchlen> <...> :
* Read the source blocks and apply (not for move op) to the target blocks
* stash <stash_id> <src_range> : load the given source range to memory
* free <stash_id> : free the given <stash_id>
* format:
* [rangeset]: <# of pairs>, <pair A start>, <pair A end>, ...
* <stash_id>: a hex number with length of 40
*/
partition.operations = new Array()
let newDataSize = await this.sizeNewData(partition.partitionName)
for (const line of lines.slice(4)) {
let op = new chromeos_update_engine.InstallOperation()
let elements = line.split(' ')
op.type = elements[0]
switch (op.type) {
case 'zero':
op.dstExtents = elements.slice(1).reduce(parseRange, [])
break
case 'new':
// unlike an A/B OTA, the payload only exists in the payload.bin,
// in an non-A/B OTA, the payload exists in both .new.data and .patch.data.
// The new ops do not have any information about data length.
// what we do here is: read in the size of .new.data, assume the first new
// op have the data length of the size of .new.data.
op.dataLength = newDataSize
newDataSize = 0
op.dstExtents = elements.slice(1).reduce(parseRange, [])
break
case 'erase':
op.dstExtents = elements.slice(1).reduce(parseRange, [])
break
case 'move':
break
case 'bsdiff':
op.dataOffset = parseInt(elements[1])
op.dataLength = parseInt(elements[2])
break
case 'imgdiff':
op.dataOffset = parseInt(elements[1])
op.dataLength = parseInt(elements[2])
break
case 'stash':
break
case 'free':
break
}
partition.operations.push(op)
}
}
/**
* Return the size of <partitionName>.new.data.*
* @param {String} partitionName
* @return {Number}
*/
async sizeNewData(partitionName) {
const /** Array<Entry> */ entries = await this.packedFile.getEntries()
const /** RegExp */ regexName = new RegExp(partitionName + '.new.dat.*')
for (const entry of entries) {
if (entry.filename.match(regexName)) {
return entry.uncompressedSize
}
}
}
}
/**
* Parse the range string and return it as an array of extents
* @param {extents} Array<extents>
* @param {String} rangeset
* @return Array<extents>
*/
function parseRange(extents, rangeset) {
const regexRange = new RegExp('[\d,]+')
if (rangeset.match(regexRange)) {
let elements = rangeset.split(',')
for (let i=1; i<elements.length; i=i+2) {
let extent = new Object(
{
startBlock: parseInt(elements[i]),
numBlocks: parseInt(elements[i+1]) - parseInt(elements[i])
}
)
extents.push(extent)
}
}
return extents
}