@hazae41/kcp
Version:
Zero-copy KCP protocol for the web
91 lines (88 loc) • 3.37 kB
JavaScript
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