diff --git a/tools/otagui/.eslintrc.json b/tools/otagui/.eslintrc.json index 416c168f8..82ff479a3 100644 --- a/tools/otagui/.eslintrc.json +++ b/tools/otagui/.eslintrc.json @@ -1,17 +1,21 @@ { - "env": { - "browser": true, - "es6": true, - "node": true - }, - "parserOptions": { - "parser": "babel-eslint" - }, - "extends": [ - "plugin:vue/recommended" + "env": { + "browser": true, + "es6": true, + "node": true + }, + "parserOptions": { + "parser": "babel-eslint" + }, + "extends": [ + "plugin:vue/recommended" + ], + "rules": { + "indent": [ + "error", + 2 ], - "rules": { - "indent": ["error", 2], - "vue/no-multiple-template-root": 0 - } + "vue/no-multiple-template-root": 0, + "vue/attribute-hyphenation": 0 + } } \ No newline at end of file diff --git a/tools/otagui/package-lock.json b/tools/otagui/package-lock.json index 84c6b6045..754d3fc58 100644 --- a/tools/otagui/package-lock.json +++ b/tools/otagui/package-lock.json @@ -8,6 +8,7 @@ "name": "OTA_GUI", "version": "0.1.0", "dependencies": { + "@zip.js/zip.js": "^2.3.6", "core-js": "^3.6.5", "echarts": "^5.1.2", "eslint-config-airbnb-base": "^14.2.1", @@ -2944,6 +2945,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zip.js/zip.js": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.3.6.tgz", + "integrity": "sha512-VQE2MI7YChMmdeBN9CGuktE4Mws+gUGAjWGUwzoIsT/gNmI+7BK+qbArsC5RO/NXYKJ1pj2vzNSZCyUFhPdG1g==" + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2995,14 +3001,18 @@ } }, "node_modules/ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ajv-errors": { @@ -18115,6 +18125,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zip.js/zip.js": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.3.6.tgz", + "integrity": "sha512-VQE2MI7YChMmdeBN9CGuktE4Mws+gUGAjWGUwzoIsT/gNmI+7BK+qbArsC5RO/NXYKJ1pj2vzNSZCyUFhPdG1g==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -18149,9 +18164,9 @@ "dev": true }, "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", diff --git a/tools/otagui/package.json b/tools/otagui/package.json index 41ba6b131..158081765 100644 --- a/tools/otagui/package.json +++ b/tools/otagui/package.json @@ -8,6 +8,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@zip.js/zip.js": "^2.3.6", "core-js": "^3.6.5", "echarts": "^5.1.2", "eslint-config-airbnb-base": "^14.2.1", diff --git a/tools/otagui/src/App.vue b/tools/otagui/src/App.vue index 7b8362856..60d35ade3 100644 --- a/tools/otagui/src/App.vue +++ b/tools/otagui/src/App.vue @@ -6,7 +6,10 @@ | Jobs Status - | + | + + Analysis + | About diff --git a/tools/otagui/src/components/JobConfiguration.vue b/tools/otagui/src/components/JobConfiguration.vue index 17e660637..e1fc1539a 100644 --- a/tools/otagui/src/components/JobConfiguration.vue +++ b/tools/otagui/src/components/JobConfiguration.vue @@ -24,9 +24,6 @@ import FormDate from '../services/FormDate.js' export default { - components: { - FormDate, - }, props: { job: { type: Object, diff --git a/tools/otagui/src/components/OperationDetail.vue b/tools/otagui/src/components/OperationDetail.vue new file mode 100644 index 000000000..e2626e535 --- /dev/null +++ b/tools/otagui/src/components/OperationDetail.vue @@ -0,0 +1,66 @@ + + + \ No newline at end of file diff --git a/tools/otagui/src/components/PartitionDetail.vue b/tools/otagui/src/components/PartitionDetail.vue new file mode 100644 index 000000000..de27a5dc6 --- /dev/null +++ b/tools/otagui/src/components/PartitionDetail.vue @@ -0,0 +1,60 @@ + + + + + \ No newline at end of file diff --git a/tools/otagui/src/components/PayloadDetail.vue b/tools/otagui/src/components/PayloadDetail.vue new file mode 100644 index 000000000..245be24a1 --- /dev/null +++ b/tools/otagui/src/components/PayloadDetail.vue @@ -0,0 +1,81 @@ + + + + + \ No newline at end of file diff --git a/tools/otagui/src/router/index.js b/tools/otagui/src/router/index.js index 6f3c3a2fd..4673c2f70 100644 --- a/tools/otagui/src/router/index.js +++ b/tools/otagui/src/router/index.js @@ -3,6 +3,7 @@ import JobList from '@/views/JobList.vue' import JobDetails from '@/views/JobDetails.vue' import About from '@/views/About.vue' import SimpleForm from '@/views/SimpleForm.vue' +import PackageAnalysis from '@/views/PackageAnalysis.vue' const routes = [ { @@ -25,6 +26,11 @@ const routes = [ path: '/create', name: 'Create', component: SimpleForm + }, + { + path: '/analysis', + name: 'Analysis', + component: PackageAnalysis } ] diff --git a/tools/otagui/src/services/payload.js b/tools/otagui/src/services/payload.js new file mode 100644 index 000000000..e51d656e0 --- /dev/null +++ b/tools/otagui/src/services/payload.js @@ -0,0 +1,131 @@ +/** + * @fileoverview Clss paypload is used to read in and + * parse the payload.bin file from a OTA.zip file. + * Class OpType creates a Map that can resolve the + * operation type. + * @package zip.js + * @package protobufjs + */ + +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' + +const _MAGIC = 'CrAU' +const _VERSION_SIZE = 8 +const _MANIFEST_LEN_SIZE = 8 +const _METADATA_SIGNATURE_LEN_SIZE = 4 +const _BRILLO_MAJOR_PAYLOAD_VERSION = 2 + +export class Payload { + /** + * This class parses the metadata of a OTA package. + * @param {File} file A OTA.zip file read from user's machine. + */ + constructor(file) { + this.packedFile = new zip.ZipReader(new zip.BlobReader(file)) + this.cursor = 0 + } + + async unzipPayload() { + let entries = await this.packedFile.getEntries() + this.payload = null + for (let entry of entries) { + if (entry.filename == 'payload.bin') { + //TODO: only read in the manifest instead of the whole payload + this.payload = await entry.getData(new zip.BlobWriter()) + } + } + if (!this.payload) { + alert('Please select a legit OTA package') + return + } + this.buffer = await this.payload.arrayBuffer() + } + + /** + * Read in an integer from binary bufferArray. + * @param {Int} size the size of a integer being read in + * @return {Int} an integer. + */ + readInt(size) { + let view = new DataView( + this.buffer.slice(this.cursor, this.cursor + size)) + this.cursor += size + switch (size) { + case 2: + return view.getUInt16(0) + case 4: + return view.getUint32(0) + case 8: + return Number(view.getBigUint64(0)) + default: + throw 'Cannot read this integer with size ' + size + } + } + + readHeader() { + let decoder = new TextDecoder() + try { + this.magic = decoder.decode( + this.buffer.slice(this.cursor, _MAGIC.length)) + this.cursor += _MAGIC.length + if (this.magic != _MAGIC) { + alert('MAGIC is not correct, please double check.') + } + this.header_version = this.readInt(_VERSION_SIZE) + this.manifest_len = this.readInt(_MANIFEST_LEN_SIZE) + if (this.header_version == _BRILLO_MAJOR_PAYLOAD_VERSION) { + this.metadata_signature_len = this.readInt(_METADATA_SIGNATURE_LEN_SIZE) + } + } catch (err) { + console.log(err) + return + } + } + + /** + * Read in the manifest in an OTA.zip file. + * The structure of the manifest can be found in: + * aosp/system/update_engine/update_metadata.proto + */ + readManifest() { + let manifest_raw = new Uint8Array(this.buffer.slice( + this.cursor, this.cursor + this.manifest_len + )) + this.cursor += this.manifest_len + this.manifest = update_metadata_pb.DeltaArchiveManifest + .decode(manifest_raw) + } + + readSignature() { + let signature_raw = new Uint8Array(this.buffer.slice( + this.cursor, this.cursor + this.metadata_signature_len + )) + this.cursor += this.metadata_signature_len + this.metadata_signature = update_metadata_pb.Signatures + .decode(signature_raw) + } + + async init() { + await this.unzipPayload() + this.readHeader() + this.readManifest() + this.readSignature() + } + +} + +export class OpType { + /** + * OpType.mapType create a map that could resolve the operation + * types. The operation types are encoded as numbers in + * update_metadata.proto and must be decoded before any usage. + */ + constructor() { + let types = update_metadata_pb.InstallOperation.Type + this.mapType = new Map() + for (let key in types) { + this.mapType.set(types[key], key) + } + } +} \ No newline at end of file diff --git a/tools/otagui/src/views/JobDetails.vue b/tools/otagui/src/views/JobDetails.vue index 89abc0901..8baf9b561 100644 --- a/tools/otagui/src/views/JobDetails.vue +++ b/tools/otagui/src/views/JobDetails.vue @@ -38,10 +38,14 @@ import JobConfiguration from '../components/JobConfiguration.vue' export default { components: { - ApiService, JobConfiguration, }, - props: ['id'], + props: { + id: { + type: String, + required: true + } + }, setup() { const stderr = ref() const stdout = ref() diff --git a/tools/otagui/src/views/PackageAnalysis.vue b/tools/otagui/src/views/PackageAnalysis.vue new file mode 100644 index 000000000..538b6cf2f --- /dev/null +++ b/tools/otagui/src/views/PackageAnalysis.vue @@ -0,0 +1,44 @@ + + + diff --git a/tools/otagui/src/views/SimpleForm.vue b/tools/otagui/src/views/SimpleForm.vue index b6f6b045e..809f80f63 100644 --- a/tools/otagui/src/views/SimpleForm.vue +++ b/tools/otagui/src/views/SimpleForm.vue @@ -112,7 +112,6 @@ export default { UploadFile, FileSelect, PartialCheckbox, - FormDate, }, data() { return {