UNPKG

@hazae41/kcp

Version:

Zero-copy KCP protocol for the web

91 lines (88 loc) 3.37 kB
import { Readable, Empty } from '@hazae41/binary'; import { Cursor } from '@hazae41/cursor'; import { Console } from '../console/index.mjs'; import { KcpSegment } from './segment.mjs'; class ExpectedKcpSegmentError extends Error { #class = ExpectedKcpSegmentError; name = this.#class.name; constructor() { super(`Expected a KCP segment`); } } class UnknownKcpCommandError extends Error { #class = UnknownKcpCommandError; name = this.#class.name; constructor() { super(`Unknown KCP command`); } } class SecretKcpReader { parent; #buffer = new Map(); constructor(parent) { this.parent = parent; } async onWrite(chunk) { const cursor = new Cursor(chunk.bytes); while (cursor.remaining) await this.#onSegment(Readable.readOrRollbackAndThrow(KcpSegment, cursor)); return; } async #onSegment(segment) { if (segment.conversation !== this.parent.conversation) return; if (segment.command === KcpSegment.commands.push) return await this.#onPushSegment(segment); if (segment.command === KcpSegment.commands.ack) return await this.#onAckSegment(segment); if (segment.command === KcpSegment.commands.wask) return await this.#onWaskSegment(segment); throw new UnknownKcpCommandError(); } async #onPushSegment(segment) { const conversation = this.parent.conversation; const command = KcpSegment.commands.ack; const timestamp = segment.timestamp; const serial = segment.serial; const unackSerial = this.parent.recvCounter; const fragment = new Empty(); const ack = KcpSegment.empty({ conversation, command, timestamp, serial, unackSerial, fragment }); this.parent.output.enqueue(ack); if (segment.serial < this.parent.recvCounter) { Console.debug(`Received previous KCP segment`); return; } if (segment.serial > this.parent.recvCounter) { Console.debug(`Received next KCP segment`); this.#buffer.set(segment.serial, segment); return; } this.parent.input.enqueue(segment.fragment); this.parent.recvCounter++; let next; while (next = this.#buffer.get(this.parent.recvCounter)) { Console.debug(`Unblocked next KCP segment`); this.parent.input.enqueue(next.fragment); this.#buffer.delete(this.parent.recvCounter); this.parent.recvCounter++; } } async #onAckSegment(segment) { const future = this.parent.resolveOnAckBySerial.get(segment.serial); if (future == null) return; this.parent.resolveOnAckBySerial.delete(segment.serial); future.resolve(); } async #onWaskSegment(segment) { const conversation = this.parent.conversation; const command = KcpSegment.commands.wins; const serial = 0; const unackSerial = this.parent.recvCounter; const fragment = new Empty(); const wins = KcpSegment.empty({ conversation, command, serial, unackSerial, fragment }); this.parent.output.enqueue(wins); } } export { ExpectedKcpSegmentError, SecretKcpReader, UnknownKcpCommandError }; //# sourceMappingURL=reader.mjs.map