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 }