| import { FILE_IDENTIFIER_LENGTH, SIZEOF_INT } from "./constants.js"; |
| import { int32, isLittleEndian, float32, float64 } from "./utils.js"; |
| import { Offset, Table, IGeneratedObject } from "./types.js"; |
| import { Encoding } from "./encoding.js"; |
| |
| export class ByteBuffer { |
| private position_ = 0; |
| private text_decoder_ = new TextDecoder(); |
| |
| /** |
| * Create a new ByteBuffer with a given array of bytes (`Uint8Array`) |
| */ |
| constructor(private bytes_: Uint8Array) { } |
| |
| /** |
| * Create and allocate a new ByteBuffer with a given size. |
| */ |
| static allocate(byte_size: number): ByteBuffer { |
| return new ByteBuffer(new Uint8Array(byte_size)); |
| } |
| |
| clear(): void { |
| this.position_ = 0; |
| } |
| |
| /** |
| * Get the underlying `Uint8Array`. |
| */ |
| bytes(): Uint8Array { |
| return this.bytes_; |
| } |
| |
| /** |
| * Get the buffer's position. |
| */ |
| position(): number { |
| return this.position_; |
| } |
| |
| /** |
| * Set the buffer's position. |
| */ |
| setPosition(position: number): void { |
| this.position_ = position; |
| } |
| |
| /** |
| * Get the buffer's capacity. |
| */ |
| capacity(): number { |
| return this.bytes_.length; |
| } |
| |
| readInt8(offset: number): number { |
| return this.readUint8(offset) << 24 >> 24; |
| } |
| |
| readUint8(offset: number): number { |
| return this.bytes_[offset]; |
| } |
| |
| readInt16(offset: number): number { |
| return this.readUint16(offset) << 16 >> 16; |
| } |
| |
| readUint16(offset: number): number { |
| return this.bytes_[offset] | this.bytes_[offset + 1] << 8; |
| } |
| |
| readInt32(offset: number): number { |
| return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24; |
| } |
| |
| readUint32(offset: number): number { |
| return this.readInt32(offset) >>> 0; |
| } |
| |
| readInt64(offset: number): bigint { |
| return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); |
| } |
| |
| readUint64(offset: number): bigint { |
| return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); |
| } |
| |
| readFloat32(offset: number): number { |
| int32[0] = this.readInt32(offset); |
| return float32[0]; |
| } |
| |
| readFloat64(offset: number): number { |
| int32[isLittleEndian ? 0 : 1] = this.readInt32(offset); |
| int32[isLittleEndian ? 1 : 0] = this.readInt32(offset + 4); |
| return float64[0]; |
| } |
| |
| writeInt8(offset: number, value: number): void { |
| this.bytes_[offset] = value; |
| } |
| |
| writeUint8(offset: number, value: number): void { |
| this.bytes_[offset] = value; |
| } |
| |
| writeInt16(offset: number, value: number): void { |
| this.bytes_[offset] = value; |
| this.bytes_[offset + 1] = value >> 8; |
| } |
| |
| writeUint16(offset: number, value: number): void { |
| this.bytes_[offset] = value; |
| this.bytes_[offset + 1] = value >> 8; |
| } |
| |
| writeInt32(offset: number, value: number): void { |
| this.bytes_[offset] = value; |
| this.bytes_[offset + 1] = value >> 8; |
| this.bytes_[offset + 2] = value >> 16; |
| this.bytes_[offset + 3] = value >> 24; |
| } |
| |
| writeUint32(offset: number, value: number): void { |
| this.bytes_[offset] = value; |
| this.bytes_[offset + 1] = value >> 8; |
| this.bytes_[offset + 2] = value >> 16; |
| this.bytes_[offset + 3] = value >> 24; |
| } |
| |
| writeInt64(offset: number, value: bigint): void { |
| this.writeInt32(offset, Number(BigInt.asIntN(32, value))); |
| this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32)))); |
| } |
| |
| writeUint64(offset: number, value: bigint): void { |
| this.writeUint32(offset, Number(BigInt.asUintN(32, value))); |
| this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32)))); |
| } |
| |
| writeFloat32(offset: number, value: number): void { |
| float32[0] = value; |
| this.writeInt32(offset, int32[0]); |
| } |
| |
| writeFloat64(offset: number, value: number): void { |
| float64[0] = value; |
| this.writeInt32(offset, int32[isLittleEndian ? 0 : 1]); |
| this.writeInt32(offset + 4, int32[isLittleEndian ? 1 : 0]); |
| } |
| |
| /** |
| * Return the file identifier. Behavior is undefined for FlatBuffers whose |
| * schema does not include a file_identifier (likely points at padding or the |
| * start of a the root vtable). |
| */ |
| getBufferIdentifier(): string { |
| if (this.bytes_.length < this.position_ + SIZEOF_INT + |
| FILE_IDENTIFIER_LENGTH) { |
| throw new Error( |
| 'FlatBuffers: ByteBuffer is too short to contain an identifier.'); |
| } |
| let result = ""; |
| for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { |
| result += String.fromCharCode( |
| this.readInt8(this.position_ + SIZEOF_INT + i)); |
| } |
| return result; |
| } |
| |
| /** |
| * Look up a field in the vtable, return an offset into the object, or 0 if the |
| * field is not present. |
| */ |
| __offset(bb_pos: number, vtable_offset: number): Offset { |
| const vtable = bb_pos - this.readInt32(bb_pos); |
| return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0; |
| } |
| |
| /** |
| * Initialize any Table-derived type to point to the union at the given offset. |
| */ |
| __union(t: Table, offset: number): Table { |
| t.bb_pos = offset + this.readInt32(offset); |
| t.bb = this; |
| return t; |
| } |
| |
| /** |
| * Create a JavaScript string from UTF-8 data stored inside the FlatBuffer. |
| * This allocates a new string and converts to wide chars upon each access. |
| * |
| * To avoid the conversion to string, pass Encoding.UTF8_BYTES as the |
| * "optionalEncoding" argument. This is useful for avoiding conversion when |
| * the data will just be packaged back up in another FlatBuffer later on. |
| * |
| * @param offset |
| * @param opt_encoding Defaults to UTF16_STRING |
| */ |
| __string(offset: number, opt_encoding?: Encoding): string | Uint8Array { |
| offset += this.readInt32(offset); |
| const length = this.readInt32(offset); |
| offset += SIZEOF_INT; |
| const utf8bytes = this.bytes_.subarray(offset, offset + length); |
| if (opt_encoding === Encoding.UTF8_BYTES) |
| return utf8bytes; |
| else |
| return this.text_decoder_.decode(utf8bytes); |
| } |
| |
| /** |
| * Handle unions that can contain string as its member, if a Table-derived type then initialize it, |
| * if a string then return a new one |
| * |
| * WARNING: strings are immutable in JS so we can't change the string that the user gave us, this |
| * makes the behaviour of __union_with_string different compared to __union |
| */ |
| __union_with_string(o: Table | string, offset: number) : Table | string { |
| if(typeof o === 'string') { |
| return this.__string(offset) as string; |
| } |
| return this.__union(o, offset); |
| } |
| |
| /** |
| * Retrieve the relative offset stored at "offset" |
| */ |
| __indirect(offset: Offset): Offset { |
| return offset + this.readInt32(offset); |
| } |
| |
| /** |
| * Get the start of data of a vector whose offset is stored at "offset" in this object. |
| */ |
| __vector(offset: Offset): Offset { |
| return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length |
| } |
| |
| /** |
| * Get the length of a vector whose offset is stored at "offset" in this object. |
| */ |
| __vector_len(offset: Offset): Offset { |
| return this.readInt32(offset + this.readInt32(offset)); |
| } |
| |
| __has_identifier(ident: string): boolean { |
| if (ident.length != FILE_IDENTIFIER_LENGTH) { |
| throw new Error('FlatBuffers: file identifier must be length ' + |
| FILE_IDENTIFIER_LENGTH); |
| } |
| for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { |
| if (ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * A helper function for generating list for obj api |
| */ |
| createScalarList(listAccessor: (i: number) => unknown, listLength: number): any[] { |
| const ret: any[] = []; |
| for(let i = 0; i < listLength; ++i) { |
| if(listAccessor(i) !== null) { |
| ret.push(listAccessor(i)); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * A helper function for generating list for obj api |
| * @param listAccessor function that accepts an index and return data at that index |
| * @param listLength listLength |
| * @param res result list |
| */ |
| createObjList(listAccessor: (i: number) => unknown, listLength: number): any[] { |
| const ret: any[] = []; |
| for(let i = 0; i < listLength; ++i) { |
| const val = listAccessor(i); |
| if(val !== null) { |
| ret.push((val as IGeneratedObject).unpack()); |
| } |
| } |
| |
| return ret; |
| } |
| |
| } |