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