import { decodeUint8Array, encodingLength } from 'uint8-varint' import { withArrayBuffer } from 'uint8arrays/with-array-buffer' import { readFloatLE, readDoubleLE } from './float.ts' import { LongBits } from './longbits.ts' import * as utf8 from './utf8.ts' import type { Reader } from '../index.ts' import type { Uint8ArrayList } from 'uint8arraylist' /* istanbul ignore next */ function indexOutOfRange (reader: Reader, writeLength?: number): RangeError { return RangeError(`index out of range: ${reader.pos} + ${writeLength ?? 1} > ${reader.len}`) } function readFixed32End (buf: Uint8Array, end: number): number { // note that this uses `end`, not `pos` return (buf[end - 4] | buf[end - 3] << 8 | buf[end - 2] << 16 | buf[end - 1] << 24) >>> 0 } /** * Constructs a new reader instance using the specified buffer. */ export class Uint8ArrayReader implements Reader { public buf: Uint8Array public pos: number public len: number public _slice = Uint8Array.prototype.subarray constructor (buffer: Uint8Array) { /** * Read buffer */ this.buf = withArrayBuffer(buffer) /** * Read buffer position */ this.pos = 0 /** * Read buffer length */ this.len = buffer.length } /** * Reads a varint as an unsigned 32 bit value */ uint32 (): number { let value = 4294967295 value = (this.buf[this.pos] & 127) >>> 0; if (this.buf[this.pos++] < 128) { return value } value = (value | (this.buf[this.pos] & 127) << 7) >>> 0; if (this.buf[this.pos++] < 128) { return value } value = (value | (this.buf[this.pos] & 127) << 14) >>> 0; if (this.buf[this.pos++] < 128) { return value } value = (value | (this.buf[this.pos] & 127) << 21) >>> 0; if (this.buf[this.pos++] < 128) { return value } value = (value | (this.buf[this.pos] & 15) << 28) >>> 0; if (this.buf[this.pos++] < 128) { return value } if ((this.pos += 5) > this.len) { this.pos = this.len throw indexOutOfRange(this, 10) } return value } /** * Reads a varint as a signed 32 bit value */ int32 (): number { return this.uint32() | 0 } /** * Reads a zig-zag encoded varint as a signed 32 bit value */ sint32 (): number { const value = this.uint32() return value >>> 1 ^ -(value & 1) | 0 } /** * Reads a varint as a boolean */ bool (): boolean { return this.uint32() !== 0 } /** * Reads fixed 32 bits as an unsigned 32 bit integer */ fixed32 (): number { if (this.pos + 4 > this.len) { throw indexOutOfRange(this, 4) } const res = readFixed32End(this.buf, this.pos += 4) return res } /** * Reads fixed 32 bits as a signed 32 bit integer */ sfixed32 (): number { if (this.pos + 4 > this.len) { throw indexOutOfRange(this, 4) } const res = readFixed32End(this.buf, this.pos += 4) | 0 return res } /** * Reads a float (32 bit) as a number */ float (): number { if (this.pos + 4 > this.len) { throw indexOutOfRange(this, 4) } const value = readFloatLE(this.buf, this.pos) this.pos += 4 return value } /** * Reads a double (64 bit float) as a number */ double (): number { /* istanbul ignore if */ if (this.pos + 8 > this.len) { throw indexOutOfRange(this, 4) } const value = readDoubleLE(this.buf, this.pos) this.pos += 8 return value } /** * Reads a sequence of bytes preceded by its length as a varint */ bytes (): Uint8Array { const length = this.uint32() const start = this.pos const end = this.pos + length /* istanbul ignore if */ if (end > this.len) { throw indexOutOfRange(this, length) } this.pos += length return start === end // fix for IE 10/Win8 and others' subarray returning array of size 1 ? new Uint8Array(0) : this.buf.subarray(start, end) } /** * Reads a string preceded by its byte length as a varint */ string (): string { const bytes = this.bytes() return utf8.read(bytes, 0, bytes.length) } /** * Skips the specified number of bytes if specified, otherwise skips a varint */ skip (length?: number): this { if (typeof length === 'number') { /* istanbul ignore if */ if (this.pos + length > this.len) { throw indexOutOfRange(this, length) } this.pos += length } else { do { /* istanbul ignore if */ if (this.pos >= this.len) { throw indexOutOfRange(this) } } while ((this.buf[this.pos++] & 128) !== 0) } return this } /** * Skips the next element of the specified wire type */ skipType (wireType: number): this { switch (wireType) { case 0: this.skip() break case 1: this.skip(8) break case 2: this.skip(this.uint32()) break case 3: while ((wireType = this.uint32() & 7) !== 4) { this.skipType(wireType) } break case 5: this.skip(4) break /* istanbul ignore next */ default: throw Error(`invalid wire type ${wireType} at offset ${this.pos}`) } return this } private readLongVarint (): LongBits { // tends to deopt with local vars for octet etc. const bits = new LongBits(0, 0) let i = 0 if (this.len - this.pos > 4) { // fast route (lo) for (; i < 4; ++i) { // 1st..4th bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0 if (this.buf[this.pos++] < 128) { return bits } } // 5th bits.lo = (bits.lo | (this.buf[this.pos] & 127) << 28) >>> 0 bits.hi = (bits.hi | (this.buf[this.pos] & 127) >> 4) >>> 0 if (this.buf[this.pos++] < 128) { return bits } i = 0 } else { for (; i < 3; ++i) { /* istanbul ignore if */ if (this.pos >= this.len) { throw indexOutOfRange(this) } // 1st..3th bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0 if (this.buf[this.pos++] < 128) { return bits } } // 4th bits.lo = (bits.lo | (this.buf[this.pos++] & 127) << i * 7) >>> 0 return bits } if (this.len - this.pos > 4) { // fast route (hi) for (; i < 5; ++i) { // 6th..10th bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0 if (this.buf[this.pos++] < 128) { return bits } } } else { for (; i < 5; ++i) { if (this.pos >= this.len) { throw indexOutOfRange(this) } // 6th..10th bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0 if (this.buf[this.pos++] < 128) { return bits } } } throw Error('invalid varint encoding') } private readFixed64 (): LongBits { if (this.pos + 8 > this.len) { throw indexOutOfRange(this, 8) } const lo = readFixed32End(this.buf, this.pos += 4) const hi = readFixed32End(this.buf, this.pos += 4) return new LongBits(lo, hi) } /** * Reads a varint as a signed 64 bit value */ int64 (): bigint { return this.readLongVarint().toBigInt() } /** * Reads a varint as a signed 64 bit value returned as a possibly unsafe * JavaScript number */ int64Number (): number { return this.readLongVarint().toNumber() } /** * Reads a varint as a signed 64 bit value returned as a string */ int64String (): string { return this.readLongVarint().toString() } /** * Reads a varint as an unsigned 64 bit value */ uint64 (): bigint { return this.readLongVarint().toBigInt(true) } /** * Reads a varint as an unsigned 64 bit value returned as a possibly unsafe * JavaScript number */ uint64Number (): number { const value = decodeUint8Array(this.buf, this.pos) this.pos += encodingLength(value) return value } /** * Reads a varint as an unsigned 64 bit value returned as a string */ uint64String (): string { return this.readLongVarint().toString(true) } /** * Reads a zig-zag encoded varint as a signed 64 bit value */ sint64 (): bigint { return this.readLongVarint().zzDecode().toBigInt() } /** * Reads a zig-zag encoded varint as a signed 64 bit value returned as a * possibly unsafe JavaScript number */ sint64Number (): number { return this.readLongVarint().zzDecode().toNumber() } /** * Reads a zig-zag encoded varint as a signed 64 bit value returned as a * string */ sint64String (): string { return this.readLongVarint().zzDecode().toString() } /** * Reads fixed 64 bits */ fixed64 (): bigint { return this.readFixed64().toBigInt() } /** * Reads fixed 64 bits returned as a possibly unsafe JavaScript number */ fixed64Number (): number { return this.readFixed64().toNumber() } /** * Reads fixed 64 bits returned as a string */ fixed64String (): string { return this.readFixed64().toString() } /** * Reads zig-zag encoded fixed 64 bits */ sfixed64 (): bigint { return this.readFixed64().toBigInt() } /** * Reads zig-zag encoded fixed 64 bits returned as a possibly unsafe * JavaScript number */ sfixed64Number (): number { return this.readFixed64().toNumber() } /** * Reads zig-zag encoded fixed 64 bits returned as a string */ sfixed64String (): string { return this.readFixed64().toString() } } export function createReader (buf: Uint8Array | Uint8ArrayList): Reader { return new Uint8ArrayReader(buf instanceof Uint8Array ? buf : buf.subarray()) }