Merge "Add support for non-A/B OTA package in OTA_analysis."
This commit is contained in:
@@ -34,7 +34,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<strong> Incremental </strong>
|
<strong> Incremental </strong>
|
||||||
<!-- Check if the first partition is incremental or not -->
|
<!-- Check if the first partition is incremental or not -->
|
||||||
<span v-if="payload.manifest.partitions[0].oldPartitionInfo">
|
<span v-if="payload.preBuild">
|
||||||
✅
|
✅
|
||||||
</span>
|
</span>
|
||||||
<span v-else> ❌ </span>
|
<span v-else> ❌ </span>
|
||||||
@@ -44,6 +44,13 @@
|
|||||||
<span v-if="payload.manifest.partialUpdate"> ✅ </span>
|
<span v-if="payload.manifest.partialUpdate"> ✅ </span>
|
||||||
<span v-else> ❌ </span>
|
<span v-else> ❌ </span>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong> A/B update </strong>
|
||||||
|
<span v-if="!payload.manifest.nonAB">
|
||||||
|
✅
|
||||||
|
</span>
|
||||||
|
<span v-else> ❌ </span>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong> VAB </strong>
|
<strong> VAB </strong>
|
||||||
<span v-if="payload.manifest.dynamicPartitionMetadata.snapshotEnabled">
|
<span v-if="payload.manifest.dynamicPartitionMetadata.snapshotEnabled">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul>
|
<ul>
|
||||||
<h5> {{ mapType.get(operation.type) }} </h5>
|
<h5> {{ mapType.getWithDefault(operation.type) }} </h5>
|
||||||
<li v-if="operation.hasOwnProperty('dataOffset')">
|
<li v-if="operation.hasOwnProperty('dataOffset')">
|
||||||
<strong> Data offset: </strong> {{ operation.dataOffset }}
|
<strong> Data offset: </strong> {{ operation.dataOffset }}
|
||||||
</li>
|
</li>
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { numBlocks, displayBlocks } from '../services/payload_composition.js'
|
import { numBlocks, displayBlocks } from '../services/payload_composition.js'
|
||||||
|
import { DefaultMap } from '../services/payload.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@@ -33,7 +34,7 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
mapType: {
|
mapType: {
|
||||||
type: Map,
|
type: DefaultMap,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
<p v-if="partition.estimateCowSize">
|
<p v-if="partition.estimateCowSize">
|
||||||
<strong> Estimate COW Size: </strong> {{ partition.estimateCowSize }} Bytes
|
<strong> Estimate COW Size: </strong> {{ partition.estimateCowSize }} Bytes
|
||||||
</p>
|
</p>
|
||||||
<p v-else>
|
|
||||||
<strong> Estimate COW Size: </strong> 0 Bytes
|
|
||||||
</p>
|
|
||||||
<div
|
<div
|
||||||
class="toggle"
|
class="toggle"
|
||||||
@click="toggle('showInfo')"
|
@click="toggle('showInfo')"
|
||||||
@@ -30,7 +27,7 @@
|
|||||||
</strong>
|
</strong>
|
||||||
{{ partition.newPartitionInfo.size }} Bytes
|
{{ partition.newPartitionInfo.size }} Bytes
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li v-if="partition.newPartitionInfo.hash">
|
||||||
<strong>
|
<strong>
|
||||||
New Partition Hash:
|
New Partition Hash:
|
||||||
</strong>
|
</strong>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
md="6"
|
md="6"
|
||||||
>
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
|
:disabled="manifest.nonAB"
|
||||||
block
|
block
|
||||||
@click="updateChart('COWmerge')"
|
@click="updateChart('COWmerge')"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -28,11 +28,11 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-divider />
|
<v-divider />
|
||||||
<h3>Metadata Signature</h3>
|
|
||||||
<div
|
<div
|
||||||
v-if="payload.metadata_signature"
|
v-if="payload.metadata_signature && !payload.manifest.nonAB"
|
||||||
class="signature"
|
class="signature"
|
||||||
>
|
>
|
||||||
|
<h3>Metadata Signature</h3>
|
||||||
<span style="white-space: pre-wrap">
|
<span style="white-space: pre-wrap">
|
||||||
{{ octToHex(payload.metadata_signature.signatures[0].data) }}
|
{{ octToHex(payload.metadata_signature.signatures[0].data) }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import * as zip from '@zip.js/zip.js/dist/zip-full.min.js'
|
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 { chromeos_update_engine as update_metadata_pb } from './update_metadata_pb.js'
|
||||||
|
import { PayloadNonAB } from './payload_nonab.js'
|
||||||
|
|
||||||
const /** String */ _MAGIC = 'CrAU'
|
const /** String */ _MAGIC = 'CrAU'
|
||||||
const /** Number */ _VERSION_SIZE = 8
|
const /** Number */ _VERSION_SIZE = 8
|
||||||
@@ -134,8 +135,17 @@ export class Payload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.payload) {
|
if (!this.payload) {
|
||||||
alert('Please select a legit OTA package')
|
try {
|
||||||
return
|
// 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.cursor += this.manifest_len
|
||||||
this.manifest = update_metadata_pb.DeltaArchiveManifest
|
this.manifest = update_metadata_pb.DeltaArchiveManifest
|
||||||
.decode(new Uint8Array(buffer))
|
.decode(new Uint8Array(buffer))
|
||||||
|
this.manifest.nonAB = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async readSignature(/** @type {Blob} */buffer) {
|
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 {
|
export class OpType {
|
||||||
/**
|
/**
|
||||||
* OpType.mapType create a map that could resolve the operation
|
* OpType.mapType create a map that could resolve the operation
|
||||||
@@ -243,8 +265,8 @@ export class OpType {
|
|||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
let /** Array<{String: Number}>*/ types = update_metadata_pb.InstallOperation.Type
|
let /** Array<{String: Number}>*/ types = update_metadata_pb.InstallOperation.Type
|
||||||
this.mapType = new Map()
|
this.mapType = new DefaultMap()
|
||||||
for (let key in types) {
|
for (let key of Object.keys(types)) {
|
||||||
this.mapType.set(types[key], key)
|
this.mapType.set(types[key], key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,8 +281,8 @@ export class MergeOpType {
|
|||||||
constructor() {
|
constructor() {
|
||||||
let /** Array<{String: Number}>*/ types =
|
let /** Array<{String: Number}>*/ types =
|
||||||
update_metadata_pb.CowMergeOperation.Type
|
update_metadata_pb.CowMergeOperation.Type
|
||||||
this.mapType = new Map()
|
this.mapType = new DefaultMap()
|
||||||
for (let key in types) {
|
for (let key of Object.keys(types)) {
|
||||||
this.mapType.set(types[key], key)
|
this.mapType.set(types[key], key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function operatedBlockStatistics(partitions) {
|
|||||||
let /** OpType */ opType = new OpType()
|
let /** OpType */ opType = new OpType()
|
||||||
for (let partition of partitions) {
|
for (let partition of partitions) {
|
||||||
for (let operation of partition.operations) {
|
for (let operation of partition.operations) {
|
||||||
let operationType = opType.mapType.get(operation.type)
|
let operationType = opType.mapType.getWithDefault(operation.type)
|
||||||
addNumberToMap(
|
addNumberToMap(
|
||||||
operatedBlocks,
|
operatedBlocks,
|
||||||
operationType,
|
operationType,
|
||||||
@@ -50,7 +50,7 @@ export function mergeOperationStatistics(partitions, blockSize) {
|
|||||||
let /** Number */ totalBlocks = 0
|
let /** Number */ totalBlocks = 0
|
||||||
for (let partition of partitions) {
|
for (let partition of partitions) {
|
||||||
for (let operation of partition.mergeOperations) {
|
for (let operation of partition.mergeOperations) {
|
||||||
let operationType = opType.mapType.get(operation.type)
|
let operationType = opType.mapType.getWithDefault(operation.type)
|
||||||
addNumberToMap(
|
addNumberToMap(
|
||||||
mergeOperations,
|
mergeOperations,
|
||||||
operationType,
|
operationType,
|
||||||
@@ -79,7 +79,7 @@ export function operatedPayloadStatistics(partitions) {
|
|||||||
let /** OpType */ opType = new OpType()
|
let /** OpType */ opType = new OpType()
|
||||||
for (let partition of partitions) {
|
for (let partition of partitions) {
|
||||||
for (let operation of partition.operations) {
|
for (let operation of partition.operations) {
|
||||||
let operationType = opType.mapType.get(operation.type)
|
let operationType = opType.mapType.getWithDefault(operation.type)
|
||||||
addNumberToMap(
|
addNumberToMap(
|
||||||
operatedBlocks,
|
operatedBlocks,
|
||||||
operationType,
|
operationType,
|
||||||
|
|||||||
145
tools/ota_analysis/src/services/payload_nonab.js
Normal file
145
tools/ota_analysis/src/services/payload_nonab.js
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user