blob: bb77f3b20f32e2d92838d6872680eabfff947bc4 [file] [log] [blame]
James Kuszmaul8e62b022022-03-22 09:33:25 -07001import { FILE_IDENTIFIER_LENGTH, SIZEOF_INT } from "./constants.js";
2import { int32, isLittleEndian, float32, float64 } from "./utils.js";
3import { Offset, Table, IGeneratedObject } from "./types.js";
4import { Encoding } from "./encoding.js";
Austin Schuh272c6132020-11-14 16:37:52 -08005
6export class ByteBuffer {
7 private position_ = 0;
8
9 /**
10 * Create a new ByteBuffer with a given array of bytes (`Uint8Array`)
11 */
12 constructor(private bytes_: Uint8Array) { }
13
14 /**
15 * Create and allocate a new ByteBuffer with a given size.
16 */
17 static allocate(byte_size: number): ByteBuffer {
18 return new ByteBuffer(new Uint8Array(byte_size));
19 }
20
21 clear(): void {
22 this.position_ = 0;
23 }
24
25 /**
26 * Get the underlying `Uint8Array`.
27 */
28 bytes(): Uint8Array {
29 return this.bytes_;
30 }
31
32 /**
33 * Get the buffer's position.
34 */
35 position(): number {
36 return this.position_;
37 }
38
39 /**
40 * Set the buffer's position.
41 */
42 setPosition(position: number): void {
43 this.position_ = position;
44 }
45
46 /**
47 * Get the buffer's capacity.
48 */
49 capacity(): number {
50 return this.bytes_.length;
51 }
52
53 readInt8(offset: number): number {
54 return this.readUint8(offset) << 24 >> 24;
55 }
56
57 readUint8(offset: number): number {
58 return this.bytes_[offset];
59 }
60
61 readInt16(offset: number): number {
62 return this.readUint16(offset) << 16 >> 16;
63 }
64
65 readUint16(offset: number): number {
66 return this.bytes_[offset] | this.bytes_[offset + 1] << 8;
67 }
68
69 readInt32(offset: number): number {
70 return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24;
71 }
72
73 readUint32(offset: number): number {
74 return this.readInt32(offset) >>> 0;
75 }
76
James Kuszmaul8e62b022022-03-22 09:33:25 -070077 readInt64(offset: number): bigint {
78 return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
Austin Schuh272c6132020-11-14 16:37:52 -080079 }
80
James Kuszmaul8e62b022022-03-22 09:33:25 -070081 readUint64(offset: number): bigint {
82 return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
Austin Schuh272c6132020-11-14 16:37:52 -080083 }
84
85 readFloat32(offset: number): number {
86 int32[0] = this.readInt32(offset);
87 return float32[0];
88 }
89
90 readFloat64(offset: number): number {
91 int32[isLittleEndian ? 0 : 1] = this.readInt32(offset);
92 int32[isLittleEndian ? 1 : 0] = this.readInt32(offset + 4);
93 return float64[0];
94 }
95
96 writeInt8(offset: number, value: number): void {
97 this.bytes_[offset] = value;
98 }
99
100 writeUint8(offset: number, value: number): void {
101 this.bytes_[offset] = value;
102 }
103
104 writeInt16(offset: number, value: number): void {
105 this.bytes_[offset] = value;
106 this.bytes_[offset + 1] = value >> 8;
107 }
108
109 writeUint16(offset: number, value: number): void {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700110 this.bytes_[offset] = value;
111 this.bytes_[offset + 1] = value >> 8;
Austin Schuh272c6132020-11-14 16:37:52 -0800112 }
113
114 writeInt32(offset: number, value: number): void {
115 this.bytes_[offset] = value;
116 this.bytes_[offset + 1] = value >> 8;
117 this.bytes_[offset + 2] = value >> 16;
118 this.bytes_[offset + 3] = value >> 24;
119 }
120
121 writeUint32(offset: number, value: number): void {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700122 this.bytes_[offset] = value;
123 this.bytes_[offset + 1] = value >> 8;
124 this.bytes_[offset + 2] = value >> 16;
125 this.bytes_[offset + 3] = value >> 24;
Austin Schuh272c6132020-11-14 16:37:52 -0800126 }
127
James Kuszmaul8e62b022022-03-22 09:33:25 -0700128 writeInt64(offset: number, value: bigint): void {
129 this.writeInt32(offset, Number(BigInt.asIntN(32, value)));
130 this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32))));
Austin Schuh272c6132020-11-14 16:37:52 -0800131 }
132
James Kuszmaul8e62b022022-03-22 09:33:25 -0700133 writeUint64(offset: number, value: bigint): void {
134 this.writeUint32(offset, Number(BigInt.asUintN(32, value)));
135 this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32))));
Austin Schuh272c6132020-11-14 16:37:52 -0800136 }
137
138 writeFloat32(offset: number, value: number): void {
139 float32[0] = value;
140 this.writeInt32(offset, int32[0]);
141 }
142
143 writeFloat64(offset: number, value: number): void {
144 float64[0] = value;
145 this.writeInt32(offset, int32[isLittleEndian ? 0 : 1]);
146 this.writeInt32(offset + 4, int32[isLittleEndian ? 1 : 0]);
147 }
148
149 /**
150 * Return the file identifier. Behavior is undefined for FlatBuffers whose
151 * schema does not include a file_identifier (likely points at padding or the
152 * start of a the root vtable).
153 */
154 getBufferIdentifier(): string {
155 if (this.bytes_.length < this.position_ + SIZEOF_INT +
156 FILE_IDENTIFIER_LENGTH) {
157 throw new Error(
158 'FlatBuffers: ByteBuffer is too short to contain an identifier.');
159 }
160 let result = "";
161 for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) {
162 result += String.fromCharCode(
163 this.readInt8(this.position_ + SIZEOF_INT + i));
164 }
165 return result;
166 }
167
168 /**
169 * Look up a field in the vtable, return an offset into the object, or 0 if the
170 * field is not present.
171 */
172 __offset(bb_pos: number, vtable_offset: number): Offset {
173 const vtable = bb_pos - this.readInt32(bb_pos);
174 return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0;
175 }
176
177 /**
178 * Initialize any Table-derived type to point to the union at the given offset.
179 */
180 __union(t: Table, offset: number): Table {
181 t.bb_pos = offset + this.readInt32(offset);
182 t.bb = this;
183 return t;
184 }
185
186 /**
187 * Create a JavaScript string from UTF-8 data stored inside the FlatBuffer.
188 * This allocates a new string and converts to wide chars upon each access.
189 *
190 * To avoid the conversion to UTF-16, pass Encoding.UTF8_BYTES as
191 * the "optionalEncoding" argument. This is useful for avoiding conversion to
192 * and from UTF-16 when the data will just be packaged back up in another
193 * FlatBuffer later on.
194 *
195 * @param offset
196 * @param opt_encoding Defaults to UTF16_STRING
197 */
198 __string(offset: number, opt_encoding?: Encoding): string | Uint8Array {
199 offset += this.readInt32(offset);
200
201 const length = this.readInt32(offset);
202 let result = '';
203 let i = 0;
204
205 offset += SIZEOF_INT;
206
207 if (opt_encoding === Encoding.UTF8_BYTES) {
208 return this.bytes_.subarray(offset, offset + length);
209 }
210
211 while (i < length) {
212 let codePoint;
213
214 // Decode UTF-8
215 const a = this.readUint8(offset + i++);
216 if (a < 0xC0) {
217 codePoint = a;
218 } else {
219 const b = this.readUint8(offset + i++);
220 if (a < 0xE0) {
221 codePoint =
222 ((a & 0x1F) << 6) |
223 (b & 0x3F);
224 } else {
225 const c = this.readUint8(offset + i++);
226 if (a < 0xF0) {
227 codePoint =
228 ((a & 0x0F) << 12) |
229 ((b & 0x3F) << 6) |
230 (c & 0x3F);
231 } else {
232 const d = this.readUint8(offset + i++);
233 codePoint =
234 ((a & 0x07) << 18) |
235 ((b & 0x3F) << 12) |
236 ((c & 0x3F) << 6) |
237 (d & 0x3F);
238 }
239 }
240 }
241
242 // Encode UTF-16
243 if (codePoint < 0x10000) {
244 result += String.fromCharCode(codePoint);
245 } else {
246 codePoint -= 0x10000;
247 result += String.fromCharCode(
248 (codePoint >> 10) + 0xD800,
249 (codePoint & ((1 << 10) - 1)) + 0xDC00);
250 }
251 }
252
253 return result;
254 }
255
256 /**
257 * Handle unions that can contain string as its member, if a Table-derived type then initialize it,
258 * if a string then return a new one
259 *
260 * WARNING: strings are immutable in JS so we can't change the string that the user gave us, this
261 * makes the behaviour of __union_with_string different compared to __union
262 */
263 __union_with_string(o: Table | string, offset: number) : Table | string {
264 if(typeof o === 'string') {
265 return this.__string(offset) as string;
266 }
267 return this.__union(o, offset);
268 }
269
270 /**
271 * Retrieve the relative offset stored at "offset"
272 */
273 __indirect(offset: Offset): Offset {
274 return offset + this.readInt32(offset);
275 }
276
277 /**
278 * Get the start of data of a vector whose offset is stored at "offset" in this object.
279 */
280 __vector(offset: Offset): Offset {
281 return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length
282 }
283
284 /**
285 * Get the length of a vector whose offset is stored at "offset" in this object.
286 */
287 __vector_len(offset: Offset): Offset {
288 return this.readInt32(offset + this.readInt32(offset));
289 }
290
291 __has_identifier(ident: string): boolean {
292 if (ident.length != FILE_IDENTIFIER_LENGTH) {
293 throw new Error('FlatBuffers: file identifier must be length ' +
294 FILE_IDENTIFIER_LENGTH);
295 }
296 for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) {
297 if (ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i)) {
298 return false;
299 }
300 }
301 return true;
302 }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700303
Austin Schuh272c6132020-11-14 16:37:52 -0800304 /**
305 * A helper function for generating list for obj api
306 */
James Kuszmaul8e62b022022-03-22 09:33:25 -0700307 createScalarList(listAccessor: (i: number) => unknown, listLength: number): any[] {
308 const ret: any[] = [];
Austin Schuh272c6132020-11-14 16:37:52 -0800309 for(let i = 0; i < listLength; ++i) {
310 if(listAccessor(i) !== null) {
311 ret.push(listAccessor(i));
312 }
313 }
314
315 return ret;
316 }
317
318 /**
Austin Schuh272c6132020-11-14 16:37:52 -0800319 * A helper function for generating list for obj api
320 * @param listAccessor function that accepts an index and return data at that index
321 * @param listLength listLength
322 * @param res result list
323 */
James Kuszmaul8e62b022022-03-22 09:33:25 -0700324 createObjList(listAccessor: (i: number) => unknown, listLength: number): any[] {
325 const ret: any[] = [];
Austin Schuh272c6132020-11-14 16:37:52 -0800326 for(let i = 0; i < listLength; ++i) {
327 const val = listAccessor(i);
328 if(val !== null) {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700329 ret.push((val as IGeneratedObject).unpack());
Austin Schuh272c6132020-11-14 16:37:52 -0800330 }
331 }
332
333 return ret;
334 }
335
James Kuszmaul8e62b022022-03-22 09:33:25 -0700336 }