xref: /aosp_15_r20/development/tools/ota_analysis/src/services/payload.js (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/**
2*90c8c64dSAndroid Build Coastguard Worker * @fileoverview Class paypload is used to read in and
3*90c8c64dSAndroid Build Coastguard Worker * parse the payload.bin file from a OTA.zip file.
4*90c8c64dSAndroid Build Coastguard Worker * Class OpType creates a Map that can resolve the
5*90c8c64dSAndroid Build Coastguard Worker * operation type.
6*90c8c64dSAndroid Build Coastguard Worker * @package zip.js
7*90c8c64dSAndroid Build Coastguard Worker * @package protobufjs
8*90c8c64dSAndroid Build Coastguard Worker */
9*90c8c64dSAndroid Build Coastguard Worker
10*90c8c64dSAndroid Build Coastguard Workerimport * as zip from '@zip.js/zip.js/dist/zip-full.min.js'
11*90c8c64dSAndroid Build Coastguard Workerimport { chromeos_update_engine as update_metadata_pb } from './update_metadata_pb.js'
12*90c8c64dSAndroid Build Coastguard Workerimport { PayloadNonAB } from './payload_nonab.js'
13*90c8c64dSAndroid Build Coastguard Worker
14*90c8c64dSAndroid Build Coastguard Workerconst /** String */ _MAGIC = 'CrAU'
15*90c8c64dSAndroid Build Coastguard Workerconst /** Number */ _VERSION_SIZE = 8
16*90c8c64dSAndroid Build Coastguard Workerconst /** Number */ _MANIFEST_LEN_SIZE = 8
17*90c8c64dSAndroid Build Coastguard Workerconst /** Number */ _METADATA_SIGNATURE_LEN_SIZE = 4
18*90c8c64dSAndroid Build Coastguard Worker
19*90c8c64dSAndroid Build Coastguard Workerconst /** Number */ _PAYLOAD_HEADER_SIZE =
20*90c8c64dSAndroid Build Coastguard Worker  _MAGIC.length + _VERSION_SIZE + _MANIFEST_LEN_SIZE + _METADATA_SIGNATURE_LEN_SIZE
21*90c8c64dSAndroid Build Coastguard Worker
22*90c8c64dSAndroid Build Coastguard Workerconst /** Number */ _BRILLO_MAJOR_PAYLOAD_VERSION = 2
23*90c8c64dSAndroid Build Coastguard Workerexport const /** Array<Object> */ MetadataFormat = [
24*90c8c64dSAndroid Build Coastguard Worker  {
25*90c8c64dSAndroid Build Coastguard Worker    prefix: 'pre-build',
26*90c8c64dSAndroid Build Coastguard Worker    key: 'preBuild',
27*90c8c64dSAndroid Build Coastguard Worker    name: 'Pre-build'
28*90c8c64dSAndroid Build Coastguard Worker  },
29*90c8c64dSAndroid Build Coastguard Worker  {
30*90c8c64dSAndroid Build Coastguard Worker    prefix: 'pre-build-incremental',
31*90c8c64dSAndroid Build Coastguard Worker    key: 'preBuildVersion',
32*90c8c64dSAndroid Build Coastguard Worker    name: 'Pre-build version'
33*90c8c64dSAndroid Build Coastguard Worker  },
34*90c8c64dSAndroid Build Coastguard Worker  {
35*90c8c64dSAndroid Build Coastguard Worker    prefix: 'post-build',
36*90c8c64dSAndroid Build Coastguard Worker    key: 'postBuild',
37*90c8c64dSAndroid Build Coastguard Worker    name: 'Post-build'
38*90c8c64dSAndroid Build Coastguard Worker  },
39*90c8c64dSAndroid Build Coastguard Worker  {
40*90c8c64dSAndroid Build Coastguard Worker    prefix: 'post-build-incremental',
41*90c8c64dSAndroid Build Coastguard Worker    key: 'postBuildVersion',
42*90c8c64dSAndroid Build Coastguard Worker    name: 'Post-build version'
43*90c8c64dSAndroid Build Coastguard Worker  }
44*90c8c64dSAndroid Build Coastguard Worker]
45*90c8c64dSAndroid Build Coastguard Worker
46*90c8c64dSAndroid Build Coastguard Workerclass StopIteration extends Error {
47*90c8c64dSAndroid Build Coastguard Worker
48*90c8c64dSAndroid Build Coastguard Worker}
49*90c8c64dSAndroid Build Coastguard Worker
50*90c8c64dSAndroid Build Coastguard Workerclass OTAPayloadBlobWriter extends zip.Writer {
51*90c8c64dSAndroid Build Coastguard Worker  /**
52*90c8c64dSAndroid Build Coastguard Worker   * A zip.Writer that is tailored for OTA payload.bin read-in.
53*90c8c64dSAndroid Build Coastguard Worker   * Instead of reading in all the contents in payload.bin, this writer will
54*90c8c64dSAndroid Build Coastguard Worker   * throw an 'StopIteration' error when the header is read in.
55*90c8c64dSAndroid Build Coastguard Worker   * The header will be stored into the <payload>.
56*90c8c64dSAndroid Build Coastguard Worker   * @param {Payload} payload
57*90c8c64dSAndroid Build Coastguard Worker   * @param {String} contentType
58*90c8c64dSAndroid Build Coastguard Worker   */
59*90c8c64dSAndroid Build Coastguard Worker  constructor(payload, contentType = "") {
60*90c8c64dSAndroid Build Coastguard Worker    super()
61*90c8c64dSAndroid Build Coastguard Worker    this.offset = 0
62*90c8c64dSAndroid Build Coastguard Worker    this.contentType = contentType
63*90c8c64dSAndroid Build Coastguard Worker    this.blob = new Blob([], { type: contentType })
64*90c8c64dSAndroid Build Coastguard Worker    this.prefixLength = 0
65*90c8c64dSAndroid Build Coastguard Worker    this.payload = payload
66*90c8c64dSAndroid Build Coastguard Worker  }
67*90c8c64dSAndroid Build Coastguard Worker
68*90c8c64dSAndroid Build Coastguard Worker  async writeUint8Array(/**  @type {Uint8Array} */ array) {
69*90c8c64dSAndroid Build Coastguard Worker    super.writeUint8Array(array)
70*90c8c64dSAndroid Build Coastguard Worker    this.blob = new Blob([this.blob, array.buffer], { type: this.contentType })
71*90c8c64dSAndroid Build Coastguard Worker    this.offset = this.blob.size
72*90c8c64dSAndroid Build Coastguard Worker    // Once the prefixLength is non-zero, the address of manifest and signature
73*90c8c64dSAndroid Build Coastguard Worker    // become known and can be read in. Otherwise the header needs to be read
74*90c8c64dSAndroid Build Coastguard Worker    // in first to determine the prefixLength.
75*90c8c64dSAndroid Build Coastguard Worker    if (this.offset >= _PAYLOAD_HEADER_SIZE) {
76*90c8c64dSAndroid Build Coastguard Worker      await this.payload.readHeader(this.blob)
77*90c8c64dSAndroid Build Coastguard Worker      this.prefixLength =
78*90c8c64dSAndroid Build Coastguard Worker        _PAYLOAD_HEADER_SIZE
79*90c8c64dSAndroid Build Coastguard Worker        + this.payload.manifest_len
80*90c8c64dSAndroid Build Coastguard Worker        + this.payload.metadata_signature_len
81*90c8c64dSAndroid Build Coastguard Worker      console.log(`Computed metadata length: ${this.prefixLength}`);
82*90c8c64dSAndroid Build Coastguard Worker    }
83*90c8c64dSAndroid Build Coastguard Worker    if (this.prefixLength > 0) {
84*90c8c64dSAndroid Build Coastguard Worker      console.log(`${this.offset}/${this.prefixLength}`);
85*90c8c64dSAndroid Build Coastguard Worker      if (this.offset >= this.prefixLength) {
86*90c8c64dSAndroid Build Coastguard Worker        await this.payload.readManifest(this.blob)
87*90c8c64dSAndroid Build Coastguard Worker        await this.payload.readSignature(this.blob)
88*90c8c64dSAndroid Build Coastguard Worker      }
89*90c8c64dSAndroid Build Coastguard Worker    }
90*90c8c64dSAndroid Build Coastguard Worker    // The prefix has everything we need (header, manifest, signature). Once
91*90c8c64dSAndroid Build Coastguard Worker    // the offset is beyond the prefix, no need to move on.
92*90c8c64dSAndroid Build Coastguard Worker    if (this.offset >= this.prefixLength) {
93*90c8c64dSAndroid Build Coastguard Worker      throw new StopIteration()
94*90c8c64dSAndroid Build Coastguard Worker    }
95*90c8c64dSAndroid Build Coastguard Worker  }
96*90c8c64dSAndroid Build Coastguard Worker
97*90c8c64dSAndroid Build Coastguard Worker  getData() {
98*90c8c64dSAndroid Build Coastguard Worker    return this.blob
99*90c8c64dSAndroid Build Coastguard Worker  }
100*90c8c64dSAndroid Build Coastguard Worker}
101*90c8c64dSAndroid Build Coastguard Worker
102*90c8c64dSAndroid Build Coastguard Workerexport class Payload {
103*90c8c64dSAndroid Build Coastguard Worker  /**
104*90c8c64dSAndroid Build Coastguard Worker   * This class parses the metadata of a OTA package.
105*90c8c64dSAndroid Build Coastguard Worker   * @param {File} file A OTA.zip file read from user's machine.
106*90c8c64dSAndroid Build Coastguard Worker   */
107*90c8c64dSAndroid Build Coastguard Worker  constructor(file) {
108*90c8c64dSAndroid Build Coastguard Worker    this.packedFile = new zip.ZipReader(new zip.BlobReader(file))
109*90c8c64dSAndroid Build Coastguard Worker    this.cursor = 0
110*90c8c64dSAndroid Build Coastguard Worker  }
111*90c8c64dSAndroid Build Coastguard Worker
112*90c8c64dSAndroid Build Coastguard Worker  /**
113*90c8c64dSAndroid Build Coastguard Worker   * Unzip the OTA package, get payload.bin and metadata
114*90c8c64dSAndroid Build Coastguard Worker   */
115*90c8c64dSAndroid Build Coastguard Worker  async unzip() {
116*90c8c64dSAndroid Build Coastguard Worker    let /** Array<Entry> */ entries = await this.packedFile.getEntries()
117*90c8c64dSAndroid Build Coastguard Worker    this.payload = null
118*90c8c64dSAndroid Build Coastguard Worker    for (let entry of entries) {
119*90c8c64dSAndroid Build Coastguard Worker      if (entry.filename == 'payload.bin') {
120*90c8c64dSAndroid Build Coastguard Worker        let writer = new OTAPayloadBlobWriter(this, "")
121*90c8c64dSAndroid Build Coastguard Worker        try {
122*90c8c64dSAndroid Build Coastguard Worker          await entry.getData(writer)
123*90c8c64dSAndroid Build Coastguard Worker        } catch (e) {
124*90c8c64dSAndroid Build Coastguard Worker          if (e instanceof StopIteration) {
125*90c8c64dSAndroid Build Coastguard Worker            // Exception used as a hack to stop reading from zip. NO need to do anything
126*90c8c64dSAndroid Build Coastguard Worker            // Ideally zip.js would provide an API to partialll read a zip
127*90c8c64dSAndroid Build Coastguard Worker            // entry, but they don't. So this is what we get
128*90c8c64dSAndroid Build Coastguard Worker          } else {
129*90c8c64dSAndroid Build Coastguard Worker            throw e
130*90c8c64dSAndroid Build Coastguard Worker          }
131*90c8c64dSAndroid Build Coastguard Worker        }
132*90c8c64dSAndroid Build Coastguard Worker        this.payload = writer.getData()
133*90c8c64dSAndroid Build Coastguard Worker        break
134*90c8c64dSAndroid Build Coastguard Worker      }
135*90c8c64dSAndroid Build Coastguard Worker      if (entry.filename == 'META-INF/com/android/metadata') {
136*90c8c64dSAndroid Build Coastguard Worker        this.metadata = await entry.getData(new zip.TextWriter())
137*90c8c64dSAndroid Build Coastguard Worker      }
138*90c8c64dSAndroid Build Coastguard Worker    }
139*90c8c64dSAndroid Build Coastguard Worker    if (!this.payload) {
140*90c8c64dSAndroid Build Coastguard Worker      try {
141*90c8c64dSAndroid Build Coastguard Worker        // The temporary variable manifest has to be used here, to prevent the html page
142*90c8c64dSAndroid Build Coastguard Worker        // being rendered before everything is read in properly
143*90c8c64dSAndroid Build Coastguard Worker        let manifest = new PayloadNonAB(this.packedFile)
144*90c8c64dSAndroid Build Coastguard Worker        await manifest.init()
145*90c8c64dSAndroid Build Coastguard Worker        manifest.nonAB = true
146*90c8c64dSAndroid Build Coastguard Worker        this.manifest = manifest
147*90c8c64dSAndroid Build Coastguard Worker      } catch (error) {
148*90c8c64dSAndroid Build Coastguard Worker        alert('Please select a legit OTA package')
149*90c8c64dSAndroid Build Coastguard Worker        return
150*90c8c64dSAndroid Build Coastguard Worker      }
151*90c8c64dSAndroid Build Coastguard Worker    }
152*90c8c64dSAndroid Build Coastguard Worker  }
153*90c8c64dSAndroid Build Coastguard Worker
154*90c8c64dSAndroid Build Coastguard Worker  /**
155*90c8c64dSAndroid Build Coastguard Worker   * Read in an integer from binary bufferArray.
156*90c8c64dSAndroid Build Coastguard Worker   * @param {Int} size the size of a integer being read in
157*90c8c64dSAndroid Build Coastguard Worker   * @return {Int} an integer.
158*90c8c64dSAndroid Build Coastguard Worker   */
159*90c8c64dSAndroid Build Coastguard Worker  readInt(size) {
160*90c8c64dSAndroid Build Coastguard Worker    let /** DataView */ view = new DataView(
161*90c8c64dSAndroid Build Coastguard Worker      this.buffer.slice(this.cursor, this.cursor + size))
162*90c8c64dSAndroid Build Coastguard Worker    if (typeof view.getBigUint64 !== "function") {
163*90c8c64dSAndroid Build Coastguard Worker      view.getBigUint64 =
164*90c8c64dSAndroid Build Coastguard Worker        function (offset) {
165*90c8c64dSAndroid Build Coastguard Worker          const a = BigInt(view.getUint32(offset))
166*90c8c64dSAndroid Build Coastguard Worker          const b = BigInt(view.getUint32(offset + 4))
167*90c8c64dSAndroid Build Coastguard Worker          const bigNumber = a * 4294967296n + b
168*90c8c64dSAndroid Build Coastguard Worker          return bigNumber
169*90c8c64dSAndroid Build Coastguard Worker        }
170*90c8c64dSAndroid Build Coastguard Worker    }
171*90c8c64dSAndroid Build Coastguard Worker    this.cursor += size
172*90c8c64dSAndroid Build Coastguard Worker    switch (size) {
173*90c8c64dSAndroid Build Coastguard Worker    case 2:
174*90c8c64dSAndroid Build Coastguard Worker      return view.getUInt16(0)
175*90c8c64dSAndroid Build Coastguard Worker    case 4:
176*90c8c64dSAndroid Build Coastguard Worker      return view.getUint32(0)
177*90c8c64dSAndroid Build Coastguard Worker    case 8:
178*90c8c64dSAndroid Build Coastguard Worker      return Number(view.getBigUint64(0))
179*90c8c64dSAndroid Build Coastguard Worker    default:
180*90c8c64dSAndroid Build Coastguard Worker      throw 'Cannot read this integer with size ' + size
181*90c8c64dSAndroid Build Coastguard Worker    }
182*90c8c64dSAndroid Build Coastguard Worker  }
183*90c8c64dSAndroid Build Coastguard Worker
184*90c8c64dSAndroid Build Coastguard Worker  /**
185*90c8c64dSAndroid Build Coastguard Worker   * Read the header of payload.bin, including the magic, header_version,
186*90c8c64dSAndroid Build Coastguard Worker   * manifest_len, metadata_signature_len.
187*90c8c64dSAndroid Build Coastguard Worker   */
188*90c8c64dSAndroid Build Coastguard Worker  async readHeader(/** @type {Blob} */buffer) {
189*90c8c64dSAndroid Build Coastguard Worker    this.buffer = await buffer.slice(0, _PAYLOAD_HEADER_SIZE).arrayBuffer()
190*90c8c64dSAndroid Build Coastguard Worker    let /** TextDecoder */ decoder = new TextDecoder()
191*90c8c64dSAndroid Build Coastguard Worker    try {
192*90c8c64dSAndroid Build Coastguard Worker      this.magic = decoder.decode(
193*90c8c64dSAndroid Build Coastguard Worker        this.buffer.slice(this.cursor, _MAGIC.length))
194*90c8c64dSAndroid Build Coastguard Worker      if (this.magic != _MAGIC) {
195*90c8c64dSAndroid Build Coastguard Worker        throw new Error('MAGIC is not correct, please double check.')
196*90c8c64dSAndroid Build Coastguard Worker      }
197*90c8c64dSAndroid Build Coastguard Worker      this.cursor += _MAGIC.length
198*90c8c64dSAndroid Build Coastguard Worker      this.header_version = this.readInt(_VERSION_SIZE)
199*90c8c64dSAndroid Build Coastguard Worker      this.manifest_len = this.readInt(_MANIFEST_LEN_SIZE)
200*90c8c64dSAndroid Build Coastguard Worker      if (this.header_version == _BRILLO_MAJOR_PAYLOAD_VERSION) {
201*90c8c64dSAndroid Build Coastguard Worker        this.metadata_signature_len = this.readInt(_METADATA_SIGNATURE_LEN_SIZE)
202*90c8c64dSAndroid Build Coastguard Worker      }
203*90c8c64dSAndroid Build Coastguard Worker      else {
204*90c8c64dSAndroid Build Coastguard Worker        throw new Error(`Unexpected major version number: ${this.header_version}`)
205*90c8c64dSAndroid Build Coastguard Worker      }
206*90c8c64dSAndroid Build Coastguard Worker    } catch (err) {
207*90c8c64dSAndroid Build Coastguard Worker      console.log(err)
208*90c8c64dSAndroid Build Coastguard Worker      return
209*90c8c64dSAndroid Build Coastguard Worker    }
210*90c8c64dSAndroid Build Coastguard Worker  }
211*90c8c64dSAndroid Build Coastguard Worker
212*90c8c64dSAndroid Build Coastguard Worker  /**
213*90c8c64dSAndroid Build Coastguard Worker   * Read in the manifest in an OTA.zip file.
214*90c8c64dSAndroid Build Coastguard Worker   * The structure of the manifest can be found in:
215*90c8c64dSAndroid Build Coastguard Worker   * aosp/system/update_engine/update_metadata.proto
216*90c8c64dSAndroid Build Coastguard Worker   */
217*90c8c64dSAndroid Build Coastguard Worker  async readManifest(/** @type {Blob} */buffer) {
218*90c8c64dSAndroid Build Coastguard Worker    buffer = await buffer.slice(
219*90c8c64dSAndroid Build Coastguard Worker      this.cursor, this.cursor + this.manifest_len).arrayBuffer()
220*90c8c64dSAndroid Build Coastguard Worker    this.cursor += this.manifest_len
221*90c8c64dSAndroid Build Coastguard Worker    this.manifest = update_metadata_pb.DeltaArchiveManifest
222*90c8c64dSAndroid Build Coastguard Worker      .decode(new Uint8Array(buffer))
223*90c8c64dSAndroid Build Coastguard Worker    this.manifest.nonAB = false
224*90c8c64dSAndroid Build Coastguard Worker  }
225*90c8c64dSAndroid Build Coastguard Worker
226*90c8c64dSAndroid Build Coastguard Worker  async readSignature(/** @type {Blob} */buffer) {
227*90c8c64dSAndroid Build Coastguard Worker    buffer = await buffer.slice(
228*90c8c64dSAndroid Build Coastguard Worker      this.cursor, this.cursor + this.metadata_signature_len).arrayBuffer()
229*90c8c64dSAndroid Build Coastguard Worker    this.cursor += this.metadata_signature_len
230*90c8c64dSAndroid Build Coastguard Worker    this.metadata_signature = update_metadata_pb.Signatures
231*90c8c64dSAndroid Build Coastguard Worker      .decode(new Uint8Array(buffer))
232*90c8c64dSAndroid Build Coastguard Worker  }
233*90c8c64dSAndroid Build Coastguard Worker
234*90c8c64dSAndroid Build Coastguard Worker  parseMetadata() {
235*90c8c64dSAndroid Build Coastguard Worker    for (let formatter of MetadataFormat) {
236*90c8c64dSAndroid Build Coastguard Worker      let regex = new RegExp(formatter.prefix + '.+')
237*90c8c64dSAndroid Build Coastguard Worker      if (this.metadata.match(regex)) {
238*90c8c64dSAndroid Build Coastguard Worker        this[formatter.key] =
239*90c8c64dSAndroid Build Coastguard Worker          trimEntry(this.metadata.match(regex)[0], formatter.prefix)
240*90c8c64dSAndroid Build Coastguard Worker      } else this[formatter.key] = ''
241*90c8c64dSAndroid Build Coastguard Worker    }
242*90c8c64dSAndroid Build Coastguard Worker  }
243*90c8c64dSAndroid Build Coastguard Worker
244*90c8c64dSAndroid Build Coastguard Worker  async init() {
245*90c8c64dSAndroid Build Coastguard Worker    await this.unzip()
246*90c8c64dSAndroid Build Coastguard Worker    this.parseMetadata()
247*90c8c64dSAndroid Build Coastguard Worker  }
248*90c8c64dSAndroid Build Coastguard Worker
249*90c8c64dSAndroid Build Coastguard Worker}
250*90c8c64dSAndroid Build Coastguard Worker
251*90c8c64dSAndroid Build Coastguard Workerexport class DefaultMap extends Map {
252*90c8c64dSAndroid Build Coastguard Worker  /** Reload the original get method. Return the original key value if
253*90c8c64dSAndroid Build Coastguard Worker   * the key does not exist.
254*90c8c64dSAndroid Build Coastguard Worker   * @param {Any} key
255*90c8c64dSAndroid Build Coastguard Worker   */
256*90c8c64dSAndroid Build Coastguard Worker  getWithDefault(key) {
257*90c8c64dSAndroid Build Coastguard Worker    if (!this.has(key)) return key
258*90c8c64dSAndroid Build Coastguard Worker    return this.get(key)
259*90c8c64dSAndroid Build Coastguard Worker  }
260*90c8c64dSAndroid Build Coastguard Worker}
261*90c8c64dSAndroid Build Coastguard Worker
262*90c8c64dSAndroid Build Coastguard Workerexport class OpType {
263*90c8c64dSAndroid Build Coastguard Worker  /**
264*90c8c64dSAndroid Build Coastguard Worker   * OpType.mapType create a map that could resolve the operation
265*90c8c64dSAndroid Build Coastguard Worker   * types. The operation types are encoded as numbers in
266*90c8c64dSAndroid Build Coastguard Worker   * update_metadata.proto and must be decoded before any usage.
267*90c8c64dSAndroid Build Coastguard Worker   */
268*90c8c64dSAndroid Build Coastguard Worker  constructor() {
269*90c8c64dSAndroid Build Coastguard Worker    let /** Array<{String: Number}>*/ types = update_metadata_pb.InstallOperation.Type
270*90c8c64dSAndroid Build Coastguard Worker    this.mapType = new DefaultMap()
271*90c8c64dSAndroid Build Coastguard Worker    for (let key of Object.keys(types)) {
272*90c8c64dSAndroid Build Coastguard Worker      this.mapType.set(types[key], key)
273*90c8c64dSAndroid Build Coastguard Worker    }
274*90c8c64dSAndroid Build Coastguard Worker  }
275*90c8c64dSAndroid Build Coastguard Worker}
276*90c8c64dSAndroid Build Coastguard Worker
277*90c8c64dSAndroid Build Coastguard Workerexport class MergeOpType {
278*90c8c64dSAndroid Build Coastguard Worker  /**
279*90c8c64dSAndroid Build Coastguard Worker   * MergeOpType create a map that could resolve the COW merge operation
280*90c8c64dSAndroid Build Coastguard Worker   * types. This is very similar to OpType class except that one is for
281*90c8c64dSAndroid Build Coastguard Worker   * installation operations.
282*90c8c64dSAndroid Build Coastguard Worker   */
283*90c8c64dSAndroid Build Coastguard Worker  constructor() {
284*90c8c64dSAndroid Build Coastguard Worker    let /** Array<{String: Number}>*/ types =
285*90c8c64dSAndroid Build Coastguard Worker      update_metadata_pb.CowMergeOperation.Type
286*90c8c64dSAndroid Build Coastguard Worker    this.mapType = new DefaultMap()
287*90c8c64dSAndroid Build Coastguard Worker    for (let key of Object.keys(types)) {
288*90c8c64dSAndroid Build Coastguard Worker      this.mapType.set(types[key], key)
289*90c8c64dSAndroid Build Coastguard Worker    }
290*90c8c64dSAndroid Build Coastguard Worker  }
291*90c8c64dSAndroid Build Coastguard Worker}
292*90c8c64dSAndroid Build Coastguard Worker
293*90c8c64dSAndroid Build Coastguard Workerexport function octToHex(bufferArray, space = true, maxLine = 16) {
294*90c8c64dSAndroid Build Coastguard Worker  let hex_table = ''
295*90c8c64dSAndroid Build Coastguard Worker  for (let i = 0; i < bufferArray.length; i++) {
296*90c8c64dSAndroid Build Coastguard Worker    if (bufferArray[i].toString(16).length === 2) {
297*90c8c64dSAndroid Build Coastguard Worker      hex_table += bufferArray[i].toString(16) + (space ? ' ' : '')
298*90c8c64dSAndroid Build Coastguard Worker    } else {
299*90c8c64dSAndroid Build Coastguard Worker      hex_table += '0' + bufferArray[i].toString(16) + (space ? ' ' : '')
300*90c8c64dSAndroid Build Coastguard Worker    }
301*90c8c64dSAndroid Build Coastguard Worker    if ((i + 1) % maxLine == 0) {
302*90c8c64dSAndroid Build Coastguard Worker      hex_table += '\n'
303*90c8c64dSAndroid Build Coastguard Worker    }
304*90c8c64dSAndroid Build Coastguard Worker  }
305*90c8c64dSAndroid Build Coastguard Worker  return hex_table
306*90c8c64dSAndroid Build Coastguard Worker}
307*90c8c64dSAndroid Build Coastguard Worker
308*90c8c64dSAndroid Build Coastguard Worker/**
309*90c8c64dSAndroid Build Coastguard Worker * Trim the prefix in an entry. This is required because the lookbehind
310*90c8c64dSAndroid Build Coastguard Worker * regular expression is not supported in safari yet.
311*90c8c64dSAndroid Build Coastguard Worker * @param {String} entry
312*90c8c64dSAndroid Build Coastguard Worker * @param {String} prefix
313*90c8c64dSAndroid Build Coastguard Worker * @return String
314*90c8c64dSAndroid Build Coastguard Worker */
315*90c8c64dSAndroid Build Coastguard Workerfunction trimEntry(entry, prefix) {
316*90c8c64dSAndroid Build Coastguard Worker  return entry.slice(prefix.length + 1, entry.length)
317*90c8c64dSAndroid Build Coastguard Worker}