import { inspect } from "util"; //import { logger } from "../utils/logger"; const logger = { debug(...x: string[]) {}, }; type uint32 = number; export interface Writer { write(chunk: Buffer): void; } export class BufferedWriter implements Writer { private chunks: Buffer[] = []; write(chunk: Buffer) { this.chunks.push(chunk); } collect() { return Buffer.concat(this.chunks); } } export class BinaryWriter { constructor(readonly target: Writer) {} uint8(x: number) { if (!Number.isInteger(x) || x < 0 || x > 255) { throw new Error(`internal: Invalid uint8 ${x}`); } const buffer = Buffer.alloc(1); buffer.writeUInt8(x); this.target.write(buffer); logger.debug(`Wrote uint8 ${x}`); } uint16(x: number) { if (!Number.isInteger(x) || x < 0 || x >= 2 ** 16) { throw new Error(`internal: Invalid uint16 ${x}`); } const buffer = Buffer.alloc(2); buffer.writeUInt16LE(x); this.target.write(buffer); logger.debug(`Wrote uint16 ${x}`); } uint32(x: number) { if (!Number.isInteger(x) || x < 0 || x >= 2 ** 32) { throw new Error(`internal: Invalid uint32 ${x}`); } const buffer = Buffer.alloc(4); buffer.writeUInt32LE(x); this.target.write(buffer); logger.debug(`Wrote uint32 ${x}`); } boolean(x: boolean) { logger.debug(`Writing boolean ${x}`); this.uint8(x ? 1 : 0); } maybe(x: A | null, f: (_: A) => void) { logger.debug(`Writing maybe ${inspect(x)}`); if (x == null) { this.boolean(false); } else { this.boolean(true); f(x); } } array(xs: A[], f: (_: A) => void) { logger.debug(`Writing array of length ${xs.length} - ${inspect(xs)}`); this.uint32(xs.length); for (const x of xs) { f(x); } } map(map: Map, f: (k: K, v: V) => void) { logger.debug(`Writing map of length ${map.size} - ${inspect(map)}`); this.uint32(map.size); for (const [k, v] of map.entries()) { f(k, v); } } uint64(x: number) { if (!Number.isInteger(x)) { throw new Error(`internal: Invalid integer ${x}`); } const buffer = Buffer.alloc(8); buffer.writeBigInt64LE(BigInt(x)); this.target.write(buffer); logger.debug(`Wrote int64 ${x}`); } bigint(x: bigint) { logger.debug(`Writing bigint ${x}`); let bytes = (x < 0 ? -x : x).toString(16); if (bytes.length % 2 != 0) bytes = "0" + bytes; const buffer = new Uint8Array(bytes.length / 2); for (let i = 0; i < buffer.length; ++i) { buffer[i] = parseInt(bytes.substr(i * 2, 2), 16); } logger.debug(`Bytes are ${inspect(buffer)}`); this.boolean(x < 0); this.bytes(Buffer.from(buffer)); } double(x: number) { const buffer = Buffer.alloc(8); buffer.writeDoubleLE(x); this.target.write(buffer); logger.debug(`Wrote double ${x}`); } text(x: string) { const buffer = Buffer.from(x, "utf-8"); this.target.write(buffer); logger.debug(`Wrote text ${x}`); } string(x: string) { logger.debug(`Writing string ${x}`); const buffer = Buffer.from(x, "utf-8"); this.bytes(buffer); } bytes(x: Buffer) { this.uint32(x.length); this.target.write(x); logger.debug(`Wrote bytes ${x.toString("hex")}`); } } export class BinaryReader { public offset: uint32 = 0; constructor(readonly source: Buffer) {} uint8(): number { const result = this.source.readUInt8(this.offset); logger.debug(`Read uint8 ${result} at ${this.offset.toString(16)}`); this.offset += 1; return result; } uint16(): number { const result = this.source.readUInt16LE(this.offset); logger.debug(`Read uint16 ${result} at ${this.offset.toString(16)}`); this.offset += 2; return result; } uint32(): number { const result = this.source.readUInt32LE(this.offset); logger.debug(`Read uint32 ${result} at ${this.offset.toString(16)}`); this.offset += 4; return result; } uint64(): number { const result = this.source.readBigUInt64LE(this.offset); logger.debug(`Read uint64 ${result} at ${this.offset.toString(16)}`); this.offset += 8; return Number(result); } bigint(): bigint { logger.debug(`Reading bigint at ${this.offset.toString(16)}`); const negative = this.boolean(); const bytes = this.bytes(); const result = BigInt(`0x${bytes.toString("hex")}`); logger.debug( `Read bigint ${result} at ${this.offset.toString( 16 )}. Bytes are ${inspect(bytes)}` ); return negative ? -result : result; } boolean(): boolean { const result = this.uint8(); if (result === 0) { logger.debug(`Read boolean true at ${this.offset.toString(16)}`); return false; } else if (result === 1) { logger.debug(`Read boolean false at ${this.offset.toString(16)}`); return true; } else { throw new Error(`Invalid boolean ${result} at position ${this.offset}`); } } maybe(f: () => A): A | null { logger.debug(`Reading maybe at ${this.offset.toString(16)}`); if (this.boolean()) { return f(); } else { return null; } } array(f: (index: number) => A): A[] { logger.debug(`Reading array at ${this.offset.toString(16)}`); const length = this.uint32(); const result = []; for (let i = 0; i < length; ++i) { const item = f(i); logger.debug( `Read array item ${inspect(item)} at ${this.offset.toString(16)}` ); result.push(item); } logger.debug( `Read array ${inspect(result)} at ${this.offset.toString(16)}` ); return result; } map(f: (index: number) => [K, V]): Map { logger.debug(`Reading map at ${this.offset.toString(16)}`); const length = this.uint32(); const result = new Map(); for (let i = 0; i < length; ++i) { const [k, v] = f(i); logger.debug( `Read map pair ${inspect(k)}=${inspect(v)} at ${this.offset.toString( 16 )}` ); result.set(k, v); } logger.debug(`Read map ${inspect(result)} at ${this.offset.toString(16)}`); return result; } double(): number { const result = this.source.readDoubleLE(this.offset); logger.debug(`Read double ${result} at ${this.offset.toString(16)}`); this.offset += 8; return result; } bytes_with_size(size: number): Buffer { const result = this.source.slice(this.offset, this.offset + size); logger.debug( `Read bytes ${result.toString("hex")} at ${this.offset.toString(16)}` ); this.offset += size; return result; } bytes(): Buffer { const length = this.uint32(); return this.bytes_with_size(length); } text(x: string) { logger.debug(`Reading text at ${this.offset.toString(16)}`); const byteLength = Buffer.from(x, "utf-8").length; const result = this.bytes_with_size(byteLength).toString("utf-8"); logger.debug(`Read text ${result} at ${this.offset.toString(16)}`); if (result !== x) { throw new Error( `Expected ${x}, got ${result} at position ${this.offset}` ); } return result; } string(): string { logger.debug(`Reading string at ${this.offset.toString(16)}`); const bytes = this.bytes(); const result = bytes.toString("utf-8"); logger.debug(`Read string ${result} at ${this.offset.toString(16)}`); return result; } }