1import { 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"; 5 6export class ByteBuffer { 7 private position_ = 0; 8 private text_decoder_ = new TextDecoder(); 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): bigint { 79 return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); 80 } 81 82 readUint64(offset: number): bigint { 83 return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); 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: bigint): void { 130 this.writeInt32(offset, Number(BigInt.asIntN(32, value))); 131 this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32)))); 132 } 133 134 writeUint64(offset: number, value: bigint): void { 135 this.writeUint32(offset, Number(BigInt.asUintN(32, value))); 136 this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32)))); 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 string, pass Encoding.UTF8_BYTES as the 192 * "optionalEncoding" argument. This is useful for avoiding conversion when 193 * the data will just be packaged back up in another 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 const length = this.readInt32(offset); 201 offset += SIZEOF_INT; 202 const utf8bytes = this.bytes_.subarray(offset, offset + length); 203 if (opt_encoding === Encoding.UTF8_BYTES) 204 return utf8bytes; 205 else 206 return this.text_decoder_.decode(utf8bytes); 207 } 208 209 /** 210 * Handle unions that can contain string as its member, if a Table-derived type then initialize it, 211 * if a string then return a new one 212 * 213 * WARNING: strings are immutable in JS so we can't change the string that the user gave us, this 214 * makes the behaviour of __union_with_string different compared to __union 215 */ 216 __union_with_string(o: Table | string, offset: number) : Table | string { 217 if(typeof o === 'string') { 218 return this.__string(offset) as string; 219 } 220 return this.__union(o, offset); 221 } 222 223 /** 224 * Retrieve the relative offset stored at "offset" 225 */ 226 __indirect(offset: Offset): Offset { 227 return offset + this.readInt32(offset); 228 } 229 230 /** 231 * Get the start of data of a vector whose offset is stored at "offset" in this object. 232 */ 233 __vector(offset: Offset): Offset { 234 return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length 235 } 236 237 /** 238 * Get the length of a vector whose offset is stored at "offset" in this object. 239 */ 240 __vector_len(offset: Offset): Offset { 241 return this.readInt32(offset + this.readInt32(offset)); 242 } 243 244 __has_identifier(ident: string): boolean { 245 if (ident.length != FILE_IDENTIFIER_LENGTH) { 246 throw new Error('FlatBuffers: file identifier must be length ' + 247 FILE_IDENTIFIER_LENGTH); 248 } 249 for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { 250 if (ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i)) { 251 return false; 252 } 253 } 254 return true; 255 } 256 257 /** 258 * A helper function for generating list for obj api 259 */ 260 createScalarList(listAccessor: (i: number) => unknown, listLength: number): any[] { 261 const ret: any[] = []; 262 for(let i = 0; i < listLength; ++i) { 263 if(listAccessor(i) !== null) { 264 ret.push(listAccessor(i)); 265 } 266 } 267 268 return ret; 269 } 270 271 /** 272 * A helper function for generating list for obj api 273 * @param listAccessor function that accepts an index and return data at that index 274 * @param listLength listLength 275 * @param res result list 276 */ 277 createObjList(listAccessor: (i: number) => unknown, listLength: number): any[] { 278 const ret: any[] = []; 279 for(let i = 0; i < listLength; ++i) { 280 const val = listAccessor(i); 281 if(val !== null) { 282 ret.push((val as IGeneratedObject).unpack()); 283 } 284 } 285 286 return ret; 287 } 288 289 } 290