Last active
June 26, 2024 05:21
-
-
Save ZenLiuCN/41a3d8e7b05cd6d9b544ea493332387d to your computer and use it in GitHub Desktop.
a brower only protobuf's buffer writer and reader, see protobuf.js for the orignal project (github.com/protobufjs/protobuf.js)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const charCodeAt = String.prototype.charCodeAt | |
const fromCharCode = String.fromCharCode; | |
export const MaxInt = 4294967296 | |
export const utf8 = { | |
length(string) { | |
let len = 0, | |
c = 0; | |
for (let i = 0; i < string.length; ++i) { | |
c = charCodeAt.call(string, i) | |
if (c < 128) | |
len += 1; | |
else if (c < 2048) | |
len += 2; | |
else if ((c & 0xFC00) === 0xD800 && (charCodeAt.call(string, i + 1) & 0xFC00) === 0xDC00) { | |
++i; | |
len += 4; | |
} else | |
len += 3; | |
} | |
return len; | |
} | |
, read(buffer, start, end) { | |
if (end - start < 1) { | |
return ""; | |
} | |
let str = ""; | |
for (let i = start; i < end;) { | |
let t = buffer[i++]; | |
if (t <= 0x7F) { | |
str += fromCharCode(t); | |
} else if (t >= 0xC0 && t < 0xE0) { | |
str += fromCharCode((t & 0x1F) << 6 | buffer[i++] & 0x3F); | |
} else if (t >= 0xE0 && t < 0xF0) { | |
str += fromCharCode((t & 0xF) << 12 | (buffer[i++] & 0x3F) << 6 | buffer[i++] & 0x3F); | |
} else if (t >= 0xF0) { | |
let t2 = ((t & 7) << 18 | (buffer[i++] & 0x3F) << 12 | (buffer[i++] & 0x3F) << 6 | buffer[i++] & 0x3F) - 0x10000; | |
str += fromCharCode(0xD800 + (t2 >> 10)); | |
str += fromCharCode(0xDC00 + (t2 & 0x3FF)); | |
} | |
} | |
return str; | |
} | |
, write(string, buffer, offset) { | |
let start = offset, | |
c1, // character 1 | |
c2; // character 2 | |
for (let i = 0; i < string.length; ++i) { | |
c1 = charCodeAt.call(string, i) | |
if (c1 < 128) { | |
buffer[offset++] = c1; | |
} else if (c1 < 2048) { | |
buffer[offset++] = c1 >> 6 | 192; | |
buffer[offset++] = c1 & 63 | 128; | |
} else if ((c1 & 0xFC00) === 0xD800 && ((c2 = charCodeAt.call(string, i + 1)) & 0xFC00) === 0xDC00) { | |
c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); | |
++i; | |
buffer[offset++] = c1 >> 18 | 240; | |
buffer[offset++] = c1 >> 12 & 63 | 128; | |
buffer[offset++] = c1 >> 6 & 63 | 128; | |
buffer[offset++] = c1 & 63 | 128; | |
} else { | |
buffer[offset++] = c1 >> 12 | 224; | |
buffer[offset++] = c1 >> 6 & 63 | 128; | |
buffer[offset++] = c1 & 63 | 128; | |
} | |
} | |
return offset - start; | |
} | |
} | |
export const isString = (value) => typeof value === "string" || value instanceof String | |
export interface Long { | |
low: number | |
high: number | |
unsigned: boolean | |
} | |
export class LongBits { | |
static zeroHash = "\0\0\0\0\0\0\0\0"; | |
static zero = new LongBits(0, 0) | |
lo: number | |
hi: number | |
constructor(lo, hi) { | |
/** | |
* Low bits. | |
* @type {number} | |
*/ | |
this.lo = lo >>> 0; | |
/** | |
* High bits. | |
* @type {number} | |
*/ | |
this.hi = hi >>> 0; | |
} | |
/** | |
* Constructs new long bits from the specified number. | |
* @param {number} value Value | |
* @returns {LongBits} Instance | |
*/ | |
static fromNumber(value) { | |
if (value === 0) | |
return LongBits.zero; | |
let sign = value < 0; | |
if (sign) | |
value = -value; | |
let lo = value >>> 0, | |
hi = (value - lo) / MaxInt >>> 0; | |
if (sign) { | |
hi = ~hi >>> 0; | |
lo = ~lo >>> 0; | |
if (++lo > 4294967295) { | |
lo = 0; | |
if (++hi > 4294967295) | |
hi = 0; | |
} | |
} | |
return new LongBits(lo, hi); | |
} | |
/** | |
* Constructs new long bits from a number, long or string. | |
* @param {Long|number|string} value Value | |
* @returns {LongBits} Instance | |
*/ | |
static from(value) { | |
if (typeof value === "number") | |
return LongBits.fromNumber(value); | |
if (isString(value)) { | |
return LongBits.fromNumber(parseInt(value, 10)); | |
} | |
return value.low || value.high ? new LongBits(value.low >>> 0, value.high >>> 0) : LongBits.zero; | |
} | |
/** | |
* Converts this long bits to a possibly unsafe JavaScript number. | |
* @param {boolean} [unsigned=false] Whether unsigned or not | |
* @returns {number} Possibly unsafe number | |
*/ | |
toNumber(unsigned: boolean): number { | |
if (!unsigned && this.hi >>> 31) { | |
let lo = ~this.lo + 1 >>> 0, | |
hi = ~this.hi >>> 0; | |
if (!lo) | |
hi = hi + 1 >>> 0; | |
return -(lo + hi * MaxInt); | |
} | |
return this.lo + this.hi * MaxInt; | |
} | |
/** | |
* Converts this long bits to a long. | |
* @param {boolean} [unsigned=false] Whether unsigned or not | |
* @returns {Long} Long | |
*/ | |
toLong(unsigned: boolean): Long { | |
return {low: this.lo | 0, high: this.hi | 0, unsigned: Boolean(unsigned)} | |
} | |
/** | |
* Constructs new long bits from the specified 8 characters long hash. | |
* @param {string} hash Hash | |
* @returns {LongBits} Bits | |
*/ | |
fromHash(hash) { | |
if (hash === LongBits.zeroHash) | |
return LongBits.zero; | |
return new LongBits( | |
(charCodeAt.call(hash, 0) | |
| charCodeAt.call(hash, 1) << 8 | |
| charCodeAt.call(hash, 2) << 16 | |
| charCodeAt.call(hash, 3) << 24) >>> 0 | |
, | |
(charCodeAt.call(hash, 4) | |
| charCodeAt.call(hash, 5) << 8 | |
| charCodeAt.call(hash, 6) << 16 | |
| charCodeAt.call(hash, 7) << 24) >>> 0 | |
); | |
} | |
/** | |
* Converts this long bits to a 8 characters long hash. | |
* @returns {string} Hash | |
*/ | |
toHash() { | |
return String.fromCharCode( | |
this.lo & 255, | |
this.lo >>> 8 & 255, | |
this.lo >>> 16 & 255, | |
this.lo >>> 24, | |
this.hi & 255, | |
this.hi >>> 8 & 255, | |
this.hi >>> 16 & 255, | |
this.hi >>> 24 | |
); | |
} | |
/** | |
* Zig-zag encodes this long bits. | |
* @returns {LongBits} `this` | |
*/ | |
zzEncode() { | |
let mask = this.hi >> 31; | |
this.hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0; | |
this.lo = (this.lo << 1 ^ mask) >>> 0; | |
return this; | |
} | |
/** | |
* Zig-zag decodes this long bits. | |
* @returns {LongBits} `this` | |
*/ | |
zzDecode() { | |
let mask = -(this.lo & 1); | |
this.lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0; | |
this.hi = (this.hi >>> 1 ^ mask) >>> 0; | |
return this; | |
} | |
/** | |
* Calculates the length of this longbits when encoded as a varint. | |
* @returns {number} Length | |
*/ | |
length() { | |
let part0 = this.lo, | |
part1 = (this.lo >>> 28 | this.hi << 4) >>> 0, | |
part2 = this.hi >>> 24; | |
return part2 === 0 | |
? part1 === 0 | |
? part0 < 16384 | |
? part0 < 128 ? 1 : 2 | |
: part0 < 2097152 ? 3 : 4 | |
: part1 < 16384 | |
? part1 < 128 ? 5 : 6 | |
: part1 < 2097152 ? 7 : 8 | |
: part2 < 128 ? 9 : 10; | |
} | |
} | |
LongBits.zero.toNumber = function () { | |
return 0; | |
} | |
LongBits.zero.zzDecode = function () { | |
return this; | |
} | |
LongBits.zero.zzEncode = LongBits.zero.zzDecode | |
LongBits.zero.length = function () { | |
return 1; | |
} | |
export const base64: { | |
length(string: string): number | |
encode(buffer: Uint8Array , start: number, end: number): string | |
decode(string: string, buffer: Uint8Array, offset: number): number | |
} = (() => { | |
const invalidEncoding = "invalid encoding" | |
// Base64 encoding table | |
const b64 = new Array(64); | |
// Base64 decoding table | |
const s64 = new Array(123); | |
// 65..90, 97..122, 48..57, 43, 47 | |
for (let i = 0; i < 64;) | |
s64[b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; | |
return { | |
length(string: string): number { | |
let p = string.length; | |
if (!p) | |
return 0; | |
let n = 0; | |
while (--p % 4 > 1 && string.charAt(p) === "=") | |
++n; | |
return Math.ceil(string.length * 3) / 4 - n; | |
} | |
, encode(buffer: ArrayBuffer | Array<number>, start: number, end: number): string { | |
let parts = null, | |
chunk = []; | |
let i = 0, // output index | |
j = 0, // goto index | |
t; // temporary | |
while (start < end) { | |
let b = buffer[start++]; | |
switch (j) { | |
case 0: | |
chunk[i++] = b64[b >> 2]; | |
t = (b & 3) << 4; | |
j = 1; | |
break; | |
case 1: | |
chunk[i++] = b64[t | b >> 4]; | |
t = (b & 15) << 2; | |
j = 2; | |
break; | |
case 2: | |
chunk[i++] = b64[t | b >> 6]; | |
chunk[i++] = b64[b & 63]; | |
j = 0; | |
break; | |
} | |
if (i > 8191) { | |
(parts || (parts = [])).push(fromCharCode.apply(String, chunk)); | |
i = 0; | |
} | |
} | |
if (j) { | |
chunk[i++] = b64[t]; | |
chunk[i++] = 61; | |
if (j === 1) | |
chunk[i++] = 61; | |
} | |
if (parts) { | |
if (i) | |
parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); | |
return parts.join(""); | |
} | |
return fromCharCode.apply(String, chunk.slice(0, i)); | |
} | |
, decode(string: string, buffer: Array<number> | ArrayBuffer, offset: number) { | |
let start = offset; | |
let j = 0, // goto index | |
t; // temporary | |
for (let i = 0; i < string.length;) { | |
let c = string.charCodeAt(i++); | |
if (c === 61 && j > 1) | |
break; | |
if ((c = s64[c]) === undefined) | |
throw Error(invalidEncoding); | |
switch (j) { | |
case 0: | |
t = c; | |
j = 1; | |
break; | |
case 1: | |
buffer[offset++] = t << 2 | (c & 48) >> 4; | |
t = c; | |
j = 2; | |
break; | |
case 2: | |
buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2; | |
t = c; | |
j = 3; | |
break; | |
case 3: | |
buffer[offset++] = (t & 3) << 6 | c; | |
j = 0; | |
break; | |
} | |
} | |
if (j === 1) | |
throw Error(invalidEncoding); | |
return offset - start; | |
} | |
} | |
})() | |
export const float: { | |
writeFloatLE(val: number, buf: Uint8Array, pos: number) | |
writeFloatBE(val: number, buf: Uint8Array, pos: number) | |
readFloatLE(buf: Uint8Array, pos: number): number | |
readFloatBE(buf: Uint8Array, pos: number): number | |
readDoubleLE(buf: Uint8Array, pos: number): number | |
readDoubleBE(buf: Uint8Array, pos: number): number | |
writeDoubleLE(val: number, buf: Uint8Array, pos: number) | |
writeDoubleBE(val: number, buf: Uint8Array, pos: number) | |
} = (() => { | |
const f32 = new Float32Array([-0]), | |
f8b = new Uint8Array(f32.buffer), | |
le = f8b[3] === 128; | |
const writeFloat_f32_cpy = (val, buf, pos) => { | |
f32[0] = val; | |
buf[pos] = f8b[0]; | |
buf[pos + 1] = f8b[1]; | |
buf[pos + 2] = f8b[2]; | |
buf[pos + 3] = f8b[3]; | |
} | |
const writeFloat_f32_rev = (val, buf, pos) => { | |
f32[0] = val; | |
buf[pos] = f8b[3]; | |
buf[pos + 1] = f8b[2]; | |
buf[pos + 2] = f8b[1]; | |
buf[pos + 3] = f8b[0]; | |
} | |
const readFloat_f32_cpy = (buf, pos) => { | |
f8b[0] = buf[pos]; | |
f8b[1] = buf[pos + 1]; | |
f8b[2] = buf[pos + 2]; | |
f8b[3] = buf[pos + 3]; | |
return f32[0]; | |
} | |
const readFloat_f32_rev = (buf, pos) => { | |
f8b[3] = buf[pos]; | |
f8b[2] = buf[pos + 1]; | |
f8b[1] = buf[pos + 2]; | |
f8b[0] = buf[pos + 3]; | |
return f32[0]; | |
} | |
const f64 = new Float64Array([-0]), | |
d8b = new Uint8Array(f64.buffer), | |
dle = d8b[7] === 128; | |
const writeDouble_f64_cpy = (val, buf, pos) => { | |
f64[0] = val | |
buf[pos] = d8b[0] | |
buf[pos + 1] = d8b[1] | |
buf[pos + 2] = d8b[2] | |
buf[pos + 3] = d8b[3] | |
buf[pos + 4] = d8b[4] | |
buf[pos + 5] = d8b[5] | |
buf[pos + 6] = d8b[6] | |
buf[pos + 7] = d8b[7] | |
} | |
const writeDouble_f64_rev = (val, buf, pos) => { | |
f64[0] = val | |
buf[pos] = d8b[7] | |
buf[pos + 1] = d8b[6] | |
buf[pos + 2] = d8b[5] | |
buf[pos + 3] = d8b[4] | |
buf[pos + 4] = d8b[3] | |
buf[pos + 5] = d8b[2] | |
buf[pos + 6] = d8b[1] | |
buf[pos + 7] = d8b[0] | |
} | |
const readDouble_f64_cpy = (buf, pos) => { | |
d8b[0] = buf[pos]; | |
d8b[1] = buf[pos + 1]; | |
d8b[2] = buf[pos + 2]; | |
d8b[3] = buf[pos + 3]; | |
d8b[4] = buf[pos + 4]; | |
d8b[5] = buf[pos + 5]; | |
d8b[6] = buf[pos + 6]; | |
d8b[7] = buf[pos + 7]; | |
return f64[0]; | |
} | |
const readDouble_f64_rev = (buf, pos) => { | |
d8b[7] = buf[pos] | |
d8b[6] = buf[pos + 1] | |
d8b[5] = buf[pos + 2] | |
d8b[4] = buf[pos + 3] | |
d8b[3] = buf[pos + 4] | |
d8b[2] = buf[pos + 5] | |
d8b[1] = buf[pos + 6] | |
d8b[0] = buf[pos + 7] | |
return f64[0] | |
} | |
return { | |
writeFloatLE: le ? writeFloat_f32_cpy : writeFloat_f32_rev, | |
writeFloatBE: le ? writeFloat_f32_rev : writeFloat_f32_cpy, | |
readFloatLE: le ? readFloat_f32_cpy : readFloat_f32_rev, | |
readFloatBE: le ? readFloat_f32_rev : readFloat_f32_cpy, | |
readDoubleLE: dle ? readDouble_f64_cpy : readDouble_f64_rev, | |
readDoubleBE: dle ? readDouble_f64_rev : readDouble_f64_cpy, | |
writeDoubleLE: dle ? writeDouble_f64_cpy : writeDouble_f64_rev, | |
writeDoubleBE: dle ? writeDouble_f64_rev : writeDouble_f64_cpy, | |
} | |
})() | |
export const pool = (alloc: (size: number) => Uint8Array, slice: Function, size?: number) => { | |
let SIZE = size || 8192; | |
let MAX = SIZE >>> 1; | |
let slab = null; | |
let offset = SIZE; | |
return (size: number) => { | |
if (size < 1 || size > MAX) | |
return alloc(size) | |
if (offset + size > SIZE) { | |
slab = alloc(SIZE) | |
offset = 0 | |
} | |
let buf = slice.call(slab, offset, offset += size); | |
if (offset & 7) // align to 32 bit | |
offset = (offset | 7) + 1; | |
return buf | |
}; | |
} | |
export const Uint8ArrayPool: (size: number) => Uint8Array = pool((size: number) => new Uint8Array(size), Uint8Array.prototype.subarray) | |
interface Operate { | |
next: Operate | undefined | |
fn: Operation | |
len: number | |
val: any | |
} | |
type Operation = (val: any, buf: Uint8Array, pos: number) => void | |
class Op implements Operate { | |
next: Op | undefined | |
constructor(public fn: Operation, public len: number, public val: any) { | |
this.next = undefined; | |
} | |
} | |
class State { | |
/** | |
* Current head. | |
*/ | |
head: Operate | |
/** | |
* Current tail. | |
*/ | |
tail: Operate | |
/** | |
* Current buffer length. | |
* @type {number} | |
*/ | |
len: number | |
/** | |
* Next state. | |
* @type {State|null} | |
*/ | |
next: State | |
constructor(writer: Writer) { | |
this.head = writer.head | |
this.tail = writer.tail | |
this.len = writer.len | |
this.next = writer.states | |
} | |
} | |
class VarintOp implements Operate { | |
next: Operate | undefined | |
constructor(public len: number, public val: any) { | |
this.next = undefined; | |
} | |
fn = writeVarint32 | |
} | |
const noop: Operation = (val, buf, pos) => { | |
} | |
const writeVarint64: Operation = (val, buf, pos) => { | |
while (val.hi) { | |
buf[pos++] = val.lo & 127 | 128; | |
val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; | |
val.hi >>>= 7; | |
} | |
while (val.lo > 127) { | |
buf[pos++] = val.lo & 127 | 128; | |
val.lo = val.lo >>> 7; | |
} | |
buf[pos++] = val.lo; | |
} | |
const writeByte: Operation = (val, buf, pos) => { | |
buf[pos] = val & 255; | |
} | |
const writeVarint32: Operation = (val, buf, pos) => { | |
while (val > 127) { | |
buf[pos++] = val & 127 | 128; | |
val >>>= 7; | |
} | |
buf[pos] = val; | |
} | |
const writeFixed32: Operation = (val, buf, pos) => { | |
buf[pos] = val & 255; | |
buf[pos + 1] = val >>> 8 & 255; | |
buf[pos + 2] = val >>> 16 & 255; | |
buf[pos + 3] = val >>> 24; | |
} | |
const writeBytes: Operation = (val, buf, pos) => { | |
buf.set(val, pos); | |
} | |
export class Writer { | |
public static create(): Writer { | |
return new Writer() | |
} | |
public static alloc = Uint8ArrayPool | |
/** | |
* Current length. | |
* @type {number} | |
*/ | |
public len: number | |
/** | |
* Operations head. | |
* @type {Object} | |
*/ | |
public head: Op | |
/** | |
* Operations tail | |
* @type {Object} | |
*/ | |
public tail: Op | |
/** | |
* Linked forked states. | |
* @type {Object|null} | |
*/ | |
public states: State | null | |
constructor() { | |
this.len = 0; | |
this.head = new Op(noop, 0, 0); | |
this.tail = this.head; | |
this.states = null; | |
} | |
private _push(fn, len, val) { | |
this.tail = this.tail.next = new Op(fn, len, val); | |
this.len += len; | |
return this; | |
} | |
uint32(value: number): Writer { | |
// here, the call to this.push has been inlined and a varint specific Op subclass is used. | |
// uint32 is by far the most frequently used operation and benefits significantly from this. | |
this.len += (this.tail = this.tail.next = new VarintOp( | |
(value = value >>> 0) < 128 ? 1 | |
: value < 16384 ? 2 | |
: value < 2097152 ? 3 | |
: value < 268435456 ? 4 | |
: 5, | |
value)).len; | |
return this; | |
}; | |
int32(value: number): Writer { | |
return value < 0 | |
? this._push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec | |
: this.uint32(value); | |
} | |
sint32(value: number): Writer { | |
return this.uint32((value << 1 ^ value >> 31) >>> 0); | |
} | |
uint64(value: Long | number | string): Writer { | |
let bits = LongBits.from(value); | |
return this._push(writeVarint64, bits.length(), bits); | |
} | |
int64 = this.uint64 | |
sint64(value: Long | number | string): Writer { | |
let bits = LongBits.from(value).zzEncode(); | |
return this._push(writeVarint64, bits.length(), bits); | |
}; | |
bool(value: boolean): Writer { | |
return this._push(writeByte, 1, value ? 1 : 0); | |
}; | |
fixed32(value: number): Writer { | |
return this._push(writeFixed32, 4, value >>> 0); | |
}; | |
sfixed32 = this.fixed32 | |
fixed64(value: Long | number | string): Writer { | |
let bits = LongBits.from(value); | |
return this._push(writeFixed32, 4, bits.lo)._push(writeFixed32, 4, bits.hi); | |
}; | |
sfixed64 = this.fixed64 | |
float(value: number) { | |
return this._push(float.writeFloatLE, 4, value); | |
}; | |
double(value: number) { | |
return this._push(float.writeDoubleLE, 8, value); | |
}; | |
bytes(value: Uint8Array | string) { | |
let len = value.length >>> 0; | |
if (!len) return this._push(writeByte, 1, 0); | |
if (isString(value)) { | |
let buf = Writer.alloc(len = base64.length(value as string)); | |
base64.decode(value as string, buf, 0); | |
value = buf; | |
} | |
return this.uint32(len)._push(writeBytes, len, value); | |
}; | |
string(value: string) { | |
let len = utf8.length(value); | |
return len | |
? this.uint32(len)._push(utf8.write, len, value) | |
: this._push(writeByte, 1, 0); | |
}; | |
fork(): Writer { | |
this.states = new State(this); | |
this.head = this.tail = new Op(noop, 0, 0); | |
this.len = 0; | |
return this; | |
}; | |
reset(): Writer { | |
if (this.states) { | |
this.head = this.states.head; | |
this.tail = this.states.tail; | |
this.len = this.states.len; | |
this.states = this.states.next; | |
} else { | |
this.head = this.tail = new Op(noop, 0, 0); | |
this.len = 0; | |
} | |
return this; | |
}; | |
ldelim(): Writer { | |
let head = this.head, | |
tail = this.tail, | |
len = this.len; | |
this.reset().uint32(len); | |
if (len) { | |
this.tail.next = head.next; // skip noop | |
this.tail = tail; | |
this.len += len; | |
} | |
return this; | |
}; | |
finish(): Uint8Array { | |
let head = this.head.next, // skip noop | |
buf = Writer.alloc(this.len), | |
pos = 0; | |
while (head) { | |
head.fn(head.val, buf, pos); | |
pos += head.len; | |
head = head.next; | |
} | |
// this.head = this.tail = null; | |
return buf; | |
}; | |
} | |
function indexOutOfRange(reader, writeLength?) { | |
return RangeError("index out of range: " + reader.pos + " + " + (writeLength || 1) + " > " + reader.len); | |
} | |
function readLongVarint() { | |
// tends to deopt with local vars for octet etc. | |
let 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) { | |
/* istanbul ignore if */ | |
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; | |
} | |
} | |
/* istanbul ignore next */ | |
throw Error("invalid varint encoding"); | |
} | |
export class Reader { | |
public static create(buffer: Uint8Array ): Reader { | |
return new Reader(buffer) | |
} | |
public pos: number = 0 | |
public len: number = 0 | |
constructor(public buf: Uint8Array ) { | |
this.len = buf.length; | |
} | |
private _slice = Uint8Array.prototype.subarray | |
uint32 = (function () { | |
let value = 4294967295; // optimizer type-hint, tends to deopt otherwise (?!) | |
return function () { | |
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; | |
}; | |
})() | |
int32(): number { | |
return this.uint32() | 0 | |
} | |
sint32(): number { | |
const value = this.uint32(); | |
return value >>> 1 ^ -(value & 1) | 0; | |
} | |
bool(): boolean { | |
return this.uint32() !== 0; | |
} | |
static readFixed32_end(buf, end) { // note that this uses `end`, not `pos` | |
return (buf[end - 4] | |
| buf[end - 3] << 8 | |
| buf[end - 2] << 16 | |
| buf[end - 1] << 24) >>> 0; | |
} | |
fixed32(): number { | |
if (this.pos + 4 > this.len) | |
throw indexOutOfRange(this, 4); | |
return Reader.readFixed32_end(this.buf, this.pos += 4); | |
} | |
sfixed32(): number { | |
if (this.pos + 4 > this.len) | |
throw indexOutOfRange(this, 4); | |
return Reader.readFixed32_end(this.buf, this.pos += 4) | 0; | |
} | |
float() { | |
if (this.pos + 4 > this.len) | |
throw indexOutOfRange(this, 4); | |
const value = float.readFloatLE(this.buf, this.pos); | |
this.pos += 4; | |
return value; | |
} | |
double() { | |
if (this.pos + 8 > this.len) throw indexOutOfRange(this, 4); | |
const value = float.readDoubleLE(this.buf, this.pos); | |
this.pos += 8; | |
return value; | |
}; | |
bytes() { | |
let length = this.uint32(), | |
start = this.pos, | |
end = this.pos + length; | |
if (end > this.len) | |
throw indexOutOfRange(this, length); | |
this.pos += length; | |
if (Array.isArray(this.buf)) // plain array | |
return this.buf.slice(start, end); | |
if (start === end) { // fix for IE 10/Win8 and others' subarray returning array of size 1 | |
return new this.buf.constructor(0) | |
} | |
return this._slice.call(this.buf, start, end); | |
} | |
string() { | |
let bytes = this.bytes(); | |
return utf8.read(bytes, 0, bytes.length); | |
} | |
/** | |
* Skips the specified number of bytes if specified, otherwise skips a varint. | |
* @param {number?} [length] Length if known, otherwise a varint is assumed | |
* @returns {Reader} `this` | |
*/ | |
skip(length?:number):Reader { | |
if (typeof length === "number") { | |
if (this.pos + length > this.len) | |
throw indexOutOfRange(this, length); | |
this.pos += length; | |
} else { | |
do { | |
if (this.pos >= this.len) | |
throw indexOutOfRange(this); | |
} while (this.buf[this.pos++] & 128); | |
} | |
return this; | |
} | |
int64(long:boolean):number|Long { | |
return readLongVarint.call(this)[long?ln:num](false); | |
} | |
uint64(long:boolean):number|Long { | |
return readLongVarint.call(this)[long?ln:num](true); | |
} | |
sint64(long:boolean):number |Long{ | |
return readLongVarint.call(this).zzDecode()[long?ln:num](false); | |
} | |
fixed64(long:boolean):number|Long { | |
return readFixed64.call(this)[long?ln:num](true); | |
} | |
sfixed64(long:boolean):number|Long { | |
return readFixed64.call(this)[long?ln:num](false); | |
} | |
} | |
const ln='toLong' | |
const num='toNumber' | |
function readFixed64(/*{Reader}this*/) { | |
if (this.pos + 8 > this.len) throw indexOutOfRange(this, 8); | |
return new LongBits(Reader.readFixed32_end(this.buf, this.pos += 4), Reader.readFixed32_end(this.buf, this.pos += 4)); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment