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