• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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