| // Copyright (C) 2022 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import {length as utf8Len, write as utf8Write} from '@protobufjs/utf8'; |
| |
| import {assertTrue} from '../base/logging'; |
| import {isString} from '../base/object_utils'; |
| |
| // A token that can be appended to an `ArrayBufferBuilder`. |
| export type ArrayBufferToken = string | number | Uint8Array; |
| |
| // Return the length, in bytes, of a token to be inserted. |
| function tokenLength(token: ArrayBufferToken): number { |
| if (isString(token)) { |
| return utf8Len(token); |
| } else if (token instanceof Uint8Array) { |
| return token.byteLength; |
| } else { |
| assertTrue(token >= 0 && token <= 0xffffffff); |
| // 32-bit integers take 4 bytes |
| return 4; |
| } |
| } |
| |
| // Insert a token into the buffer, at position `byteOffset`. |
| // |
| // @param dataView A DataView into the buffer to write into. |
| // @param typedArray A Uint8Array view into the buffer to write into. |
| // @param byteOffset Position to write at, in the buffer. |
| // @param token Token to insert into the buffer. |
| function insertToken( |
| dataView: DataView, |
| typedArray: Uint8Array, |
| byteOffset: number, |
| token: ArrayBufferToken, |
| ): void { |
| if (isString(token)) { |
| // Encode the string in UTF-8 |
| const written = utf8Write(token, typedArray, byteOffset); |
| assertTrue(written === utf8Len(token)); |
| } else if (token instanceof Uint8Array) { |
| // Copy the bytes from the other array |
| typedArray.set(token, byteOffset); |
| } else { |
| assertTrue(token >= 0 && token <= 0xffffffff); |
| // 32-bit little-endian value |
| dataView.setUint32(byteOffset, token, true); |
| } |
| } |
| |
| // Like a string builder, but for an ArrayBuffer instead of a string. This |
| // allows us to assemble messages to send/receive over the wire. Data can be |
| // appended to the buffer using `append()`. The data we append can be of the |
| // following types: |
| // |
| // - string: the ASCII string is appended. Throws an error if there are |
| // non-ASCII characters. |
| // - number: the number is appended as a 32-bit little-endian integer. |
| // - Uint8Array: the bytes are appended as-is to the buffer. |
| export class ArrayBufferBuilder { |
| private readonly tokens: ArrayBufferToken[] = []; |
| |
| // Return an `ArrayBuffer` that is the concatenation of all the tokens. |
| toArrayBuffer(): ArrayBuffer { |
| // Calculate the size of the buffer we need. |
| let byteLength = 0; |
| for (const token of this.tokens) { |
| byteLength += tokenLength(token); |
| } |
| // Allocate the buffer. |
| const buffer = new ArrayBuffer(byteLength); |
| const dataView = new DataView(buffer); |
| const typedArray = new Uint8Array(buffer); |
| // Fill the buffer with the tokens. |
| let byteOffset = 0; |
| for (const token of this.tokens) { |
| insertToken(dataView, typedArray, byteOffset, token); |
| byteOffset += tokenLength(token); |
| } |
| assertTrue(byteOffset === byteLength); |
| // Return the values. |
| return buffer; |
| } |
| |
| // Add one or more tokens to the value of this object. |
| append(token: ArrayBufferToken): void { |
| this.tokens.push(token); |
| } |
| } |