1/* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17export class ResizableBuffer { 18 private buffer: Uint8Array; 19 private capacityUsed = 0; 20 21 constructor() { 22 this.buffer = new Uint8Array(128); 23 } 24 25 append(data: ArrayLike<number>) { 26 const capacityNeeded = this.capacityUsed + data.length; 27 if (this.buffer.length < capacityNeeded) { 28 this.increaseCapacity(capacityNeeded); 29 } 30 this.buffer.set(data, this.capacityUsed); 31 this.capacityUsed = capacityNeeded; 32 } 33 34 get(): Uint8Array { 35 return this.buffer.subarray(0, this.capacityUsed); 36 } 37 38 private increaseCapacity(newCapacity: number) { 39 let capacity = this.buffer.length; 40 const mb32 = 32 * 1024 * 1024; 41 do { 42 capacity = capacity < mb32 ? capacity * 2 : capacity + mb32; 43 } while (capacity < newCapacity); 44 const newBuf = new Uint8Array(capacity); 45 newBuf.set(this.buffer); 46 this.buffer = newBuf; 47 } 48} 49 50export type BufferToken = string | number | Uint8Array; 51 52export class ArrayBufferBuilder { 53 private readonly tokens: BufferToken[] = []; 54 55 build(): ArrayBuffer { 56 let byteLength = 0; 57 this.tokens.forEach((token) => { 58 byteLength += this.getTokenLength(token); 59 }); 60 const buffer = new ArrayBuffer(byteLength); 61 const dataView = new DataView(buffer); 62 const typedArray = new Uint8Array(buffer); 63 let byteOffset = 0; 64 for (const token of this.tokens) { 65 this.insertToken(dataView, typedArray, byteOffset, token); 66 byteOffset += this.getTokenLength(token); 67 } 68 return buffer; 69 } 70 71 append(tokens: BufferToken[]): this { 72 this.tokens.push(...tokens); 73 return this; 74 } 75 76 private getTokenLength(token: BufferToken): number { 77 if (typeof token === 'string') { 78 return token.length; 79 } else if (token instanceof Uint8Array) { 80 return token.byteLength; 81 } else { 82 return 4; 83 } 84 } 85 86 private insertToken( 87 dataView: DataView, 88 typedArray: Uint8Array, 89 byteOffset: number, 90 token: BufferToken, 91 ) { 92 if (typeof token === 'string') { 93 this.setAscii(typedArray, byteOffset, token); 94 } else if (token instanceof Uint8Array) { 95 typedArray.set(token, byteOffset); 96 } else { 97 dataView.setUint32(byteOffset, token, true); 98 } 99 } 100 101 private setAscii(buffer: Uint8Array, byteOffset: number, token: string) { 102 const byteArray = stringToByteArray(token); 103 buffer.set(byteArray, byteOffset); 104 } 105} 106 107export function stringToByteArray(str: string): Uint8Array { 108 const data = new Uint8Array(str.length); 109 for (let i = 0; i < str.length; ++i) { 110 data[i] = str.charCodeAt(i); 111 } 112 return data; 113} 114 115export function byteArrayToString(data: Uint8Array): string { 116 return new TextDecoder('utf-8').decode(data); 117} 118