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