1// Copyright (C) 2022 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {length as utf8Len, write as utf8Write} from '@protobufjs/utf8'; 16 17import {assertTrue} from '../base/logging'; 18import {isString} from '../base/object_utils'; 19 20// A token that can be appended to an `ArrayBufferBuilder`. 21export type ArrayBufferToken = string | number | Uint8Array; 22 23// Return the length, in bytes, of a token to be inserted. 24function tokenLength(token: ArrayBufferToken): number { 25 if (isString(token)) { 26 return utf8Len(token); 27 } else if (token instanceof Uint8Array) { 28 return token.byteLength; 29 } else { 30 assertTrue(token >= 0 && token <= 0xffffffff); 31 // 32-bit integers take 4 bytes 32 return 4; 33 } 34} 35 36// Insert a token into the buffer, at position `byteOffset`. 37// 38// @param dataView A DataView into the buffer to write into. 39// @param typedArray A Uint8Array view into the buffer to write into. 40// @param byteOffset Position to write at, in the buffer. 41// @param token Token to insert into the buffer. 42function insertToken( 43 dataView: DataView, 44 typedArray: Uint8Array, 45 byteOffset: number, 46 token: ArrayBufferToken, 47): void { 48 if (isString(token)) { 49 // Encode the string in UTF-8 50 const written = utf8Write(token, typedArray, byteOffset); 51 assertTrue(written === utf8Len(token)); 52 } else if (token instanceof Uint8Array) { 53 // Copy the bytes from the other array 54 typedArray.set(token, byteOffset); 55 } else { 56 assertTrue(token >= 0 && token <= 0xffffffff); 57 // 32-bit little-endian value 58 dataView.setUint32(byteOffset, token, true); 59 } 60} 61 62// Like a string builder, but for an ArrayBuffer instead of a string. This 63// allows us to assemble messages to send/receive over the wire. Data can be 64// appended to the buffer using `append()`. The data we append can be of the 65// following types: 66// 67// - string: the ASCII string is appended. Throws an error if there are 68// non-ASCII characters. 69// - number: the number is appended as a 32-bit little-endian integer. 70// - Uint8Array: the bytes are appended as-is to the buffer. 71export class ArrayBufferBuilder { 72 private readonly tokens: ArrayBufferToken[] = []; 73 74 // Return an `ArrayBuffer` that is the concatenation of all the tokens. 75 toArrayBuffer(): ArrayBuffer { 76 // Calculate the size of the buffer we need. 77 let byteLength = 0; 78 for (const token of this.tokens) { 79 byteLength += tokenLength(token); 80 } 81 // Allocate the buffer. 82 const buffer = new ArrayBuffer(byteLength); 83 const dataView = new DataView(buffer); 84 const typedArray = new Uint8Array(buffer); 85 // Fill the buffer with the tokens. 86 let byteOffset = 0; 87 for (const token of this.tokens) { 88 insertToken(dataView, typedArray, byteOffset, token); 89 byteOffset += tokenLength(token); 90 } 91 assertTrue(byteOffset === byteLength); 92 // Return the values. 93 return buffer; 94 } 95 96 // Add one or more tokens to the value of this object. 97 append(token: ArrayBufferToken): void { 98 this.tokens.push(token); 99 } 100} 101