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 { 16 length as utf8Len, 17 write as utf8Write, 18} from '@protobufjs/utf8'; 19 20import {assertTrue} from '../base/logging'; 21 22// A token that can be appended to an `ArrayBufferBuilder`. 23export type ArrayBufferToken = string|number|Uint8Array; 24 25// Return the length, in bytes, of a token to be inserted. 26function tokenLength(token: ArrayBufferToken): number { 27 if (typeof token === 'string') { 28 return utf8Len(token); 29 } else if (token instanceof Uint8Array) { 30 return token.byteLength; 31 } else { 32 assertTrue(token >= 0 && token <= 0xffffffff); 33 // 32-bit integers take 4 bytes 34 return 4; 35 } 36} 37 38// Insert a token into the buffer, at position `byteOffset`. 39// 40// @param dataView A DataView into the buffer to write into. 41// @param typedArray A Uint8Array view into the buffer to write into. 42// @param byteOffset Position to write at, in the buffer. 43// @param token Token to insert into the buffer. 44function insertToken( 45 dataView: DataView, 46 typedArray: Uint8Array, 47 byteOffset: number, 48 token: ArrayBufferToken): void { 49 if (typeof token === 'string') { 50 // Encode the string in UTF-8 51 const written = utf8Write(token, typedArray, byteOffset); 52 assertTrue(written === utf8Len(token)); 53 } else if (token instanceof Uint8Array) { 54 // Copy the bytes from the other array 55 typedArray.set(token, byteOffset); 56 } else { 57 assertTrue(token >= 0 && token <= 0xffffffff); 58 // 32-bit little-endian value 59 dataView.setUint32(byteOffset, token, true); 60 } 61} 62 63// Like a string builder, but for an ArrayBuffer instead of a string. This 64// allows us to assemble messages to send/receive over the wire. Data can be 65// appended to the buffer using `append()`. The data we append can be of the 66// following types: 67// 68// - string: the ASCII string is appended. Throws an error if there are 69// non-ASCII characters. 70// - number: the number is appended as a 32-bit little-endian integer. 71// - Uint8Array: the bytes are appended as-is to the buffer. 72export class ArrayBufferBuilder { 73 private readonly tokens: ArrayBufferToken[] = []; 74 75 // Return an `ArrayBuffer` that is the concatenation of all the tokens. 76 toArrayBuffer(): ArrayBuffer { 77 // Calculate the size of the buffer we need. 78 let byteLength = 0; 79 for (const token of this.tokens) { 80 byteLength += tokenLength(token); 81 } 82 // Allocate the buffer. 83 const buffer = new ArrayBuffer(byteLength); 84 const dataView = new DataView(buffer); 85 const typedArray = new Uint8Array(buffer); 86 // Fill the buffer with the tokens. 87 let byteOffset = 0; 88 for (const token of this.tokens) { 89 insertToken(dataView, typedArray, byteOffset, token); 90 byteOffset += tokenLength(token); 91 } 92 assertTrue(byteOffset === byteLength); 93 // Return the values. 94 return buffer; 95 } 96 97 // Add one or more tokens to the value of this object. 98 append(token: ArrayBufferToken): void { 99 this.tokens.push(token); 100 } 101} 102