• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { 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): any[] {
316      const ret: any[]  = [];
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     * A helper function for generating list for obj api
328     * @param listAccessor function that accepts an index and return data at that index
329     * @param listLength listLength
330     * @param res result list
331     */
332    createObjList(listAccessor: (i: number) => unknown, listLength: number): any[] {
333      const ret: any[] = [];
334      for(let i = 0; i < listLength; ++i) {
335        const val = listAccessor(i);
336        if(val !== null) {
337          ret.push((val as IGeneratedObject).unpack());
338        }
339      }
340
341      return ret;
342    }
343
344  }