359 lines
7.6 KiB
TypeScript
359 lines
7.6 KiB
TypeScript
const TE = new TextEncoder();
|
|
const TD = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true });
|
|
|
|
export class Writer {
|
|
buf: Uint8Array;
|
|
view: DataView;
|
|
pos: number;
|
|
|
|
constructor(initial = 1024) {
|
|
this.buf = new Uint8Array(initial);
|
|
this.view = new DataView(this.buf.buffer);
|
|
this.pos = 0;
|
|
}
|
|
|
|
reset(): void {
|
|
this.pos = 0;
|
|
}
|
|
|
|
bytes(): Uint8Array {
|
|
return this.buf.subarray(0, this.pos);
|
|
}
|
|
|
|
bytesCopy(): Uint8Array {
|
|
return this.buf.slice(0, this.pos);
|
|
}
|
|
|
|
ensure(n: number): void {
|
|
if (this.pos + n > this.buf.byteLength) this.grow(n);
|
|
}
|
|
|
|
private grow(n: number): void {
|
|
let next = this.buf.byteLength * 2;
|
|
const need = this.pos + n;
|
|
while (next < need) next *= 2;
|
|
const nb = new Uint8Array(next);
|
|
nb.set(this.buf);
|
|
this.buf = nb;
|
|
this.view = new DataView(nb.buffer);
|
|
}
|
|
|
|
u8(v: number): void {
|
|
if (this.pos + 1 > this.buf.byteLength) this.grow(1);
|
|
this.buf[this.pos++] = v;
|
|
}
|
|
|
|
u16(v: number): void {
|
|
if (this.pos + 2 > this.buf.byteLength) this.grow(2);
|
|
this.view.setUint16(this.pos, v, true);
|
|
this.pos += 2;
|
|
}
|
|
|
|
i16(v: number): void {
|
|
if (this.pos + 2 > this.buf.byteLength) this.grow(2);
|
|
this.view.setInt16(this.pos, v, true);
|
|
this.pos += 2;
|
|
}
|
|
|
|
u32(v: number): void {
|
|
if (this.pos + 4 > this.buf.byteLength) this.grow(4);
|
|
this.view.setUint32(this.pos, v, true);
|
|
this.pos += 4;
|
|
}
|
|
|
|
i32(v: number): void {
|
|
if (this.pos + 4 > this.buf.byteLength) this.grow(4);
|
|
this.view.setInt32(this.pos, v, true);
|
|
this.pos += 4;
|
|
}
|
|
|
|
f32(v: number): void {
|
|
if (this.pos + 4 > this.buf.byteLength) this.grow(4);
|
|
this.view.setFloat32(this.pos, v, true);
|
|
this.pos += 4;
|
|
}
|
|
|
|
f64(v: number): void {
|
|
if (this.pos + 8 > this.buf.byteLength) this.grow(8);
|
|
this.view.setFloat64(this.pos, v, true);
|
|
this.pos += 8;
|
|
}
|
|
|
|
bool(v: boolean): void {
|
|
if (this.pos + 1 > this.buf.byteLength) this.grow(1);
|
|
this.buf[this.pos++] = v ? 1 : 0;
|
|
}
|
|
|
|
varu32(v: number): void {
|
|
if (this.pos + 5 > this.buf.byteLength) this.grow(5);
|
|
while (v >= 0x80) {
|
|
this.buf[this.pos++] = (v & 0x7f) | 0x80;
|
|
v >>>= 7;
|
|
}
|
|
this.buf[this.pos++] = v;
|
|
}
|
|
|
|
vari32(v: number): void {
|
|
const z = ((v << 1) ^ (v >> 31)) >>> 0;
|
|
this.varu32(z);
|
|
}
|
|
|
|
varu53(v: number): void {
|
|
if (this.pos + 10 > this.buf.byteLength) this.grow(10);
|
|
while (v >= 0x80) {
|
|
this.buf[this.pos++] = (v & 0x7f) | 0x80;
|
|
v = Math.floor(v / 128);
|
|
}
|
|
this.buf[this.pos++] = v;
|
|
}
|
|
|
|
vari53(v: number): void {
|
|
const z = v >= 0 ? BigInt(v) * 2n : -BigInt(v) * 2n - 1n;
|
|
this.varbu(z);
|
|
}
|
|
|
|
varbu(v: bigint): void {
|
|
if (this.pos + 10 > this.buf.byteLength) this.grow(10);
|
|
while (v >= 0x80n) {
|
|
this.buf[this.pos++] = Number(v & 0x7fn) | 0x80;
|
|
v >>= 7n;
|
|
}
|
|
this.buf[this.pos++] = Number(v);
|
|
}
|
|
|
|
varbi(v: bigint): void {
|
|
const z = v >= 0n ? v << 1n : (-v << 1n) - 1n;
|
|
this.varbu(z);
|
|
}
|
|
|
|
raw(src: Uint8Array): void {
|
|
if (this.pos + src.length > this.buf.byteLength) this.grow(src.length);
|
|
this.buf.set(src, this.pos);
|
|
this.pos += src.length;
|
|
}
|
|
|
|
bytesPrefixed(src: Uint8Array): void {
|
|
this.varu53(src.length);
|
|
this.raw(src);
|
|
}
|
|
|
|
str(s: string): void {
|
|
const len = s.length;
|
|
if (len === 0) {
|
|
this.u8(0);
|
|
return;
|
|
}
|
|
|
|
if (len < 64) {
|
|
if (this.pos + len + 5 > this.buf.byteLength) this.grow(len + 5);
|
|
let allAscii = true;
|
|
for (let i = 0; i < len; i++) {
|
|
if (s.charCodeAt(i) > 127) {
|
|
allAscii = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allAscii) {
|
|
let p = this.pos;
|
|
let lv = len;
|
|
while (lv >= 0x80) {
|
|
this.buf[p++] = (lv & 0x7f) | 0x80;
|
|
lv >>>= 7;
|
|
}
|
|
this.buf[p++] = lv;
|
|
for (let i = 0; i < len; i++) this.buf[p++] = s.charCodeAt(i);
|
|
this.pos = p;
|
|
return;
|
|
}
|
|
}
|
|
|
|
const maxBytes = len * 3;
|
|
if (this.pos + maxBytes + 5 > this.buf.byteLength) this.grow(maxBytes + 5);
|
|
const dst = this.buf.subarray(this.pos + 5);
|
|
const { written } = TE.encodeInto(s, dst);
|
|
const w = written ?? 0;
|
|
|
|
let lenBytes = 1;
|
|
let lv = w;
|
|
while (lv >= 0x80) {
|
|
lenBytes++;
|
|
lv >>>= 7;
|
|
}
|
|
|
|
if (lenBytes < 5 && w > 0) {
|
|
this.buf.copyWithin(this.pos + lenBytes, this.pos + 5, this.pos + 5 + w);
|
|
}
|
|
|
|
let p = this.pos;
|
|
lv = w;
|
|
while (lv >= 0x80) {
|
|
this.buf[p++] = (lv & 0x7f) | 0x80;
|
|
lv >>>= 7;
|
|
}
|
|
this.buf[p++] = lv;
|
|
this.pos = p + w;
|
|
}
|
|
}
|
|
|
|
export class Reader {
|
|
buf: Uint8Array;
|
|
view: DataView;
|
|
pos: number;
|
|
end: number;
|
|
|
|
constructor(buf: Uint8Array) {
|
|
this.buf = buf;
|
|
this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
this.pos = 0;
|
|
this.end = buf.byteLength;
|
|
}
|
|
|
|
reset(buf: Uint8Array): void {
|
|
this.buf = buf;
|
|
this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
this.pos = 0;
|
|
this.end = buf.byteLength;
|
|
}
|
|
|
|
remaining(): number {
|
|
return this.end - this.pos;
|
|
}
|
|
|
|
u8(): number {
|
|
return this.buf[this.pos++]!;
|
|
}
|
|
|
|
u16(): number {
|
|
const v = this.view.getUint16(this.pos, true);
|
|
this.pos += 2;
|
|
return v;
|
|
}
|
|
|
|
i16(): number {
|
|
const v = this.view.getInt16(this.pos, true);
|
|
this.pos += 2;
|
|
return v;
|
|
}
|
|
|
|
u32(): number {
|
|
const v = this.view.getUint32(this.pos, true);
|
|
this.pos += 4;
|
|
return v;
|
|
}
|
|
|
|
i32(): number {
|
|
const v = this.view.getInt32(this.pos, true);
|
|
this.pos += 4;
|
|
return v;
|
|
}
|
|
|
|
f32(): number {
|
|
const v = this.view.getFloat32(this.pos, true);
|
|
this.pos += 4;
|
|
return v;
|
|
}
|
|
|
|
f64(): number {
|
|
const v = this.view.getFloat64(this.pos, true);
|
|
this.pos += 8;
|
|
return v;
|
|
}
|
|
|
|
bool(): boolean {
|
|
return this.buf[this.pos++] !== 0;
|
|
}
|
|
|
|
varu32(): number {
|
|
let v = 0;
|
|
let shift = 0;
|
|
let byte = 0;
|
|
do {
|
|
byte = this.buf[this.pos++]!;
|
|
v |= (byte & 0x7f) << shift;
|
|
shift += 7;
|
|
} while (byte & 0x80);
|
|
return v >>> 0;
|
|
}
|
|
|
|
vari32(): number {
|
|
const z = this.varu32();
|
|
return (z >>> 1) ^ -(z & 1);
|
|
}
|
|
|
|
varu53(): number {
|
|
let v = 0;
|
|
let mult = 1;
|
|
let byte = 0;
|
|
do {
|
|
byte = this.buf[this.pos++]!;
|
|
v += (byte & 0x7f) * mult;
|
|
mult *= 128;
|
|
} while (byte & 0x80);
|
|
return v;
|
|
}
|
|
|
|
vari53(): number {
|
|
const z = this.varbu();
|
|
const v = (z & 1n) === 0n ? z >> 1n : -((z >> 1n) + 1n);
|
|
return Number(v);
|
|
}
|
|
|
|
varbu(): bigint {
|
|
let v = 0n;
|
|
let shift = 0n;
|
|
let byte = 0;
|
|
do {
|
|
byte = this.buf[this.pos++]!;
|
|
v |= BigInt(byte & 0x7f) << shift;
|
|
shift += 7n;
|
|
} while (byte & 0x80);
|
|
return v;
|
|
}
|
|
|
|
varbi(): bigint {
|
|
const z = this.varbu();
|
|
return (z & 1n) === 0n ? z >> 1n : -((z >> 1n) + 1n);
|
|
}
|
|
|
|
bytes(n: number): Uint8Array {
|
|
const slice = this.buf.subarray(this.pos, this.pos + n);
|
|
this.pos += n;
|
|
return slice;
|
|
}
|
|
|
|
bytesPrefixed(): Uint8Array {
|
|
const n = this.varu53();
|
|
return this.bytes(n);
|
|
}
|
|
|
|
str(): string {
|
|
const len = this.varu53();
|
|
if (len === 0) return '';
|
|
const start = this.pos;
|
|
const end = start + len;
|
|
const buf = this.buf;
|
|
|
|
if (len < 32) {
|
|
let allAscii = true;
|
|
for (let i = start; i < end; i++) {
|
|
if (buf[i]! > 127) {
|
|
allAscii = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allAscii) {
|
|
let s = '';
|
|
for (let i = start; i < end; i++) {
|
|
s += String.fromCharCode(buf[i]!);
|
|
}
|
|
this.pos = end;
|
|
return s;
|
|
}
|
|
}
|
|
|
|
const slice = buf.subarray(start, end);
|
|
this.pos = end;
|
|
return TD.decode(slice);
|
|
}
|
|
}
|