1*890232f2SAndroid Build Coastguard Workerimport { fromByteWidth } from './bit-width-util.js' 2*890232f2SAndroid Build Coastguard Workerimport { ValueType } from './value-type.js' 3*890232f2SAndroid Build Coastguard Workerimport { isNumber, isIndirectNumber, isAVector, fixedTypedVectorElementSize, isFixedTypedVector, isTypedVector, typedVectorElementType, packedType, fixedTypedVectorElementType } from './value-type-util.js' 4*890232f2SAndroid Build Coastguard Workerimport { indirect, keyForIndex, keyIndex, readFloat, readInt, readUInt } from './reference-util.js' 5*890232f2SAndroid Build Coastguard Workerimport { fromUTF8Array } from './flexbuffers-util.js'; 6*890232f2SAndroid Build Coastguard Workerimport { BitWidth } from './bit-width.js'; 7*890232f2SAndroid Build Coastguard Worker 8*890232f2SAndroid Build Coastguard Workerexport function toReference(buffer: ArrayBuffer): Reference { 9*890232f2SAndroid Build Coastguard Worker const len = buffer.byteLength; 10*890232f2SAndroid Build Coastguard Worker 11*890232f2SAndroid Build Coastguard Worker if (len < 3) { 12*890232f2SAndroid Build Coastguard Worker throw "Buffer needs to be bigger than 3"; 13*890232f2SAndroid Build Coastguard Worker } 14*890232f2SAndroid Build Coastguard Worker 15*890232f2SAndroid Build Coastguard Worker const dataView = new DataView(buffer); 16*890232f2SAndroid Build Coastguard Worker const byteWidth = dataView.getUint8(len - 1); 17*890232f2SAndroid Build Coastguard Worker const packedType = dataView.getUint8(len - 2); 18*890232f2SAndroid Build Coastguard Worker const parentWidth = fromByteWidth(byteWidth); 19*890232f2SAndroid Build Coastguard Worker const offset = len - byteWidth - 2; 20*890232f2SAndroid Build Coastguard Worker 21*890232f2SAndroid Build Coastguard Worker return new Reference(dataView, offset, parentWidth, packedType, "/") 22*890232f2SAndroid Build Coastguard Worker} 23*890232f2SAndroid Build Coastguard Worker 24*890232f2SAndroid Build Coastguard Workerfunction valueForIndexWithKey(index: number, key: string, dataView: DataView, offset: number, parentWidth: number, byteWidth: number, length: number, path: string): Reference { 25*890232f2SAndroid Build Coastguard Worker const _indirect = indirect(dataView, offset, parentWidth); 26*890232f2SAndroid Build Coastguard Worker const elementOffset = _indirect + index * byteWidth; 27*890232f2SAndroid Build Coastguard Worker const packedType = dataView.getUint8(_indirect + length * byteWidth + index); 28*890232f2SAndroid Build Coastguard Worker return new Reference(dataView, elementOffset, fromByteWidth(byteWidth), packedType, `${path}/${key}`) 29*890232f2SAndroid Build Coastguard Worker} 30*890232f2SAndroid Build Coastguard Worker 31*890232f2SAndroid Build Coastguard Workerexport class Reference { 32*890232f2SAndroid Build Coastguard Worker private readonly byteWidth: number 33*890232f2SAndroid Build Coastguard Worker private readonly valueType: ValueType 34*890232f2SAndroid Build Coastguard Worker private _length = -1 35*890232f2SAndroid Build Coastguard Worker constructor(private dataView: DataView, private offset: number, private parentWidth: number, private packedType: ValueType, private path: string) { 36*890232f2SAndroid Build Coastguard Worker this.byteWidth = 1 << (packedType & 3) 37*890232f2SAndroid Build Coastguard Worker this.valueType = packedType >> 2 38*890232f2SAndroid Build Coastguard Worker } 39*890232f2SAndroid Build Coastguard Worker 40*890232f2SAndroid Build Coastguard Worker isNull(): boolean { return this.valueType === ValueType.NULL; } 41*890232f2SAndroid Build Coastguard Worker isNumber(): boolean { return isNumber(this.valueType) || isIndirectNumber(this.valueType); } 42*890232f2SAndroid Build Coastguard Worker isFloat(): boolean { return ValueType.FLOAT === this.valueType || ValueType.INDIRECT_FLOAT === this.valueType; } 43*890232f2SAndroid Build Coastguard Worker isInt(): boolean { return this.isNumber() && !this.isFloat(); } 44*890232f2SAndroid Build Coastguard Worker isString(): boolean { return ValueType.STRING === this.valueType || ValueType.KEY === this.valueType; } 45*890232f2SAndroid Build Coastguard Worker isBool(): boolean { return ValueType.BOOL === this.valueType; } 46*890232f2SAndroid Build Coastguard Worker isBlob(): boolean { return ValueType.BLOB === this.valueType; } 47*890232f2SAndroid Build Coastguard Worker isVector(): boolean { return isAVector(this.valueType); } 48*890232f2SAndroid Build Coastguard Worker isMap(): boolean { return ValueType.MAP === this.valueType; } 49*890232f2SAndroid Build Coastguard Worker 50*890232f2SAndroid Build Coastguard Worker boolValue(): boolean | null { 51*890232f2SAndroid Build Coastguard Worker if (this.isBool()) { 52*890232f2SAndroid Build Coastguard Worker return readInt(this.dataView, this.offset, this.parentWidth) > 0; 53*890232f2SAndroid Build Coastguard Worker } 54*890232f2SAndroid Build Coastguard Worker return null; 55*890232f2SAndroid Build Coastguard Worker } 56*890232f2SAndroid Build Coastguard Worker 57*890232f2SAndroid Build Coastguard Worker intValue(): number | bigint | null { 58*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.INT) { 59*890232f2SAndroid Build Coastguard Worker return readInt(this.dataView, this.offset, this.parentWidth); 60*890232f2SAndroid Build Coastguard Worker } 61*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.UINT) { 62*890232f2SAndroid Build Coastguard Worker return readUInt(this.dataView, this.offset, this.parentWidth); 63*890232f2SAndroid Build Coastguard Worker } 64*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.INDIRECT_INT) { 65*890232f2SAndroid Build Coastguard Worker return readInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth)); 66*890232f2SAndroid Build Coastguard Worker } 67*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.INDIRECT_UINT) { 68*890232f2SAndroid Build Coastguard Worker return readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth)); 69*890232f2SAndroid Build Coastguard Worker } 70*890232f2SAndroid Build Coastguard Worker return null; 71*890232f2SAndroid Build Coastguard Worker } 72*890232f2SAndroid Build Coastguard Worker 73*890232f2SAndroid Build Coastguard Worker floatValue(): number | null { 74*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.FLOAT) { 75*890232f2SAndroid Build Coastguard Worker return readFloat(this.dataView, this.offset, this.parentWidth); 76*890232f2SAndroid Build Coastguard Worker } 77*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.INDIRECT_FLOAT) { 78*890232f2SAndroid Build Coastguard Worker return readFloat(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth)); 79*890232f2SAndroid Build Coastguard Worker } 80*890232f2SAndroid Build Coastguard Worker return null; 81*890232f2SAndroid Build Coastguard Worker } 82*890232f2SAndroid Build Coastguard Worker 83*890232f2SAndroid Build Coastguard Worker numericValue(): number | bigint | null { return this.floatValue() || this.intValue()} 84*890232f2SAndroid Build Coastguard Worker 85*890232f2SAndroid Build Coastguard Worker stringValue(): string | null { 86*890232f2SAndroid Build Coastguard Worker if (this.valueType === ValueType.STRING || this.valueType === ValueType.KEY) { 87*890232f2SAndroid Build Coastguard Worker const begin = indirect(this.dataView, this.offset, this.parentWidth); 88*890232f2SAndroid Build Coastguard Worker return fromUTF8Array(new Uint8Array(this.dataView.buffer, begin, this.length())); 89*890232f2SAndroid Build Coastguard Worker } 90*890232f2SAndroid Build Coastguard Worker return null; 91*890232f2SAndroid Build Coastguard Worker } 92*890232f2SAndroid Build Coastguard Worker 93*890232f2SAndroid Build Coastguard Worker blobValue(): Uint8Array | null { 94*890232f2SAndroid Build Coastguard Worker if (this.isBlob()) { 95*890232f2SAndroid Build Coastguard Worker const begin = indirect(this.dataView, this.offset, this.parentWidth); 96*890232f2SAndroid Build Coastguard Worker return new Uint8Array(this.dataView.buffer, begin, this.length()); 97*890232f2SAndroid Build Coastguard Worker } 98*890232f2SAndroid Build Coastguard Worker return null; 99*890232f2SAndroid Build Coastguard Worker } 100*890232f2SAndroid Build Coastguard Worker 101*890232f2SAndroid Build Coastguard Worker get(key: number): Reference { 102*890232f2SAndroid Build Coastguard Worker const length = this.length(); 103*890232f2SAndroid Build Coastguard Worker if (Number.isInteger(key) && isAVector(this.valueType)) { 104*890232f2SAndroid Build Coastguard Worker if (key >= length || key < 0) { 105*890232f2SAndroid Build Coastguard Worker throw `Key: [${key}] is not applicable on ${this.path} of ${this.valueType} length: ${length}`; 106*890232f2SAndroid Build Coastguard Worker } 107*890232f2SAndroid Build Coastguard Worker const _indirect = indirect(this.dataView, this.offset, this.parentWidth); 108*890232f2SAndroid Build Coastguard Worker const elementOffset = _indirect + key * this.byteWidth; 109*890232f2SAndroid Build Coastguard Worker let _packedType = this.dataView.getUint8(_indirect + length * this.byteWidth + key); 110*890232f2SAndroid Build Coastguard Worker if (isTypedVector(this.valueType)) { 111*890232f2SAndroid Build Coastguard Worker const _valueType = typedVectorElementType(this.valueType); 112*890232f2SAndroid Build Coastguard Worker _packedType = packedType(_valueType, BitWidth.WIDTH8); 113*890232f2SAndroid Build Coastguard Worker } else if (isFixedTypedVector(this.valueType)) { 114*890232f2SAndroid Build Coastguard Worker const _valueType = fixedTypedVectorElementType(this.valueType); 115*890232f2SAndroid Build Coastguard Worker _packedType = packedType(_valueType, BitWidth.WIDTH8); 116*890232f2SAndroid Build Coastguard Worker } 117*890232f2SAndroid Build Coastguard Worker return new Reference(this.dataView, elementOffset, fromByteWidth(this.byteWidth), _packedType, `${this.path}[${key}]`); 118*890232f2SAndroid Build Coastguard Worker } 119*890232f2SAndroid Build Coastguard Worker if (typeof key === 'string') { 120*890232f2SAndroid Build Coastguard Worker const index = keyIndex(key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length); 121*890232f2SAndroid Build Coastguard Worker if (index !== null) { 122*890232f2SAndroid Build Coastguard Worker return valueForIndexWithKey(index, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path) 123*890232f2SAndroid Build Coastguard Worker } 124*890232f2SAndroid Build Coastguard Worker } 125*890232f2SAndroid Build Coastguard Worker throw `Key [${key}] is not applicable on ${this.path} of ${this.valueType}`; 126*890232f2SAndroid Build Coastguard Worker } 127*890232f2SAndroid Build Coastguard Worker 128*890232f2SAndroid Build Coastguard Worker length(): number { 129*890232f2SAndroid Build Coastguard Worker let size; 130*890232f2SAndroid Build Coastguard Worker if (this._length > -1) { 131*890232f2SAndroid Build Coastguard Worker return this._length; 132*890232f2SAndroid Build Coastguard Worker } 133*890232f2SAndroid Build Coastguard Worker if (isFixedTypedVector(this.valueType)) { 134*890232f2SAndroid Build Coastguard Worker this._length = fixedTypedVectorElementSize(this.valueType); 135*890232f2SAndroid Build Coastguard Worker } else if (this.valueType === ValueType.BLOB 136*890232f2SAndroid Build Coastguard Worker || this.valueType === ValueType.MAP 137*890232f2SAndroid Build Coastguard Worker || isAVector(this.valueType)) { 138*890232f2SAndroid Build Coastguard Worker this._length = readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth) - this.byteWidth, fromByteWidth(this.byteWidth)) as number 139*890232f2SAndroid Build Coastguard Worker } else if (this.valueType === ValueType.NULL) { 140*890232f2SAndroid Build Coastguard Worker this._length = 0; 141*890232f2SAndroid Build Coastguard Worker } else if (this.valueType === ValueType.STRING) { 142*890232f2SAndroid Build Coastguard Worker const _indirect = indirect(this.dataView, this.offset, this.parentWidth); 143*890232f2SAndroid Build Coastguard Worker let sizeByteWidth = this.byteWidth; 144*890232f2SAndroid Build Coastguard Worker size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth)); 145*890232f2SAndroid Build Coastguard Worker while (this.dataView.getInt8(_indirect + (size as number)) !== 0) { 146*890232f2SAndroid Build Coastguard Worker sizeByteWidth <<= 1; 147*890232f2SAndroid Build Coastguard Worker size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth)); 148*890232f2SAndroid Build Coastguard Worker } 149*890232f2SAndroid Build Coastguard Worker this._length = size as number; 150*890232f2SAndroid Build Coastguard Worker } else if (this.valueType === ValueType.KEY) { 151*890232f2SAndroid Build Coastguard Worker const _indirect = indirect(this.dataView, this.offset, this.parentWidth); 152*890232f2SAndroid Build Coastguard Worker size = 1; 153*890232f2SAndroid Build Coastguard Worker while (this.dataView.getInt8(_indirect + size) !== 0) { 154*890232f2SAndroid Build Coastguard Worker size++; 155*890232f2SAndroid Build Coastguard Worker } 156*890232f2SAndroid Build Coastguard Worker this._length = size; 157*890232f2SAndroid Build Coastguard Worker } else { 158*890232f2SAndroid Build Coastguard Worker this._length = 1; 159*890232f2SAndroid Build Coastguard Worker } 160*890232f2SAndroid Build Coastguard Worker return Number(this._length); 161*890232f2SAndroid Build Coastguard Worker } 162*890232f2SAndroid Build Coastguard Worker 163*890232f2SAndroid Build Coastguard Worker toObject(): unknown { 164*890232f2SAndroid Build Coastguard Worker const length = this.length(); 165*890232f2SAndroid Build Coastguard Worker if (this.isVector()) { 166*890232f2SAndroid Build Coastguard Worker const result = []; 167*890232f2SAndroid Build Coastguard Worker for (let i = 0; i < length; i++) { 168*890232f2SAndroid Build Coastguard Worker result.push(this.get(i).toObject()); 169*890232f2SAndroid Build Coastguard Worker } 170*890232f2SAndroid Build Coastguard Worker return result; 171*890232f2SAndroid Build Coastguard Worker } 172*890232f2SAndroid Build Coastguard Worker if (this.isMap()) { 173*890232f2SAndroid Build Coastguard Worker const result: Record<string, unknown> = {}; 174*890232f2SAndroid Build Coastguard Worker for (let i = 0; i < length; i++) { 175*890232f2SAndroid Build Coastguard Worker const key = keyForIndex(i, this.dataView, this.offset, this.parentWidth, this.byteWidth); 176*890232f2SAndroid Build Coastguard Worker result[key] = valueForIndexWithKey(i, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path).toObject(); 177*890232f2SAndroid Build Coastguard Worker } 178*890232f2SAndroid Build Coastguard Worker return result; 179*890232f2SAndroid Build Coastguard Worker } 180*890232f2SAndroid Build Coastguard Worker if (this.isNull()) { 181*890232f2SAndroid Build Coastguard Worker return null; 182*890232f2SAndroid Build Coastguard Worker } 183*890232f2SAndroid Build Coastguard Worker if (this.isBool()) { 184*890232f2SAndroid Build Coastguard Worker return this.boolValue(); 185*890232f2SAndroid Build Coastguard Worker } 186*890232f2SAndroid Build Coastguard Worker if (this.isNumber()) { 187*890232f2SAndroid Build Coastguard Worker return this.numericValue(); 188*890232f2SAndroid Build Coastguard Worker } 189*890232f2SAndroid Build Coastguard Worker return this.blobValue() || this.stringValue(); 190*890232f2SAndroid Build Coastguard Worker } 191*890232f2SAndroid Build Coastguard Worker} 192