112 lines · 4.1 KB
const encoder = new TextEncoder();
const decoder = new TextDecoder();

export const FLUSH_PKT = encoder.encode("0000");
const LENGTH_BYTES = 4;
const MAX_PKT_DATA = 65516;

export function pktLine(data: string | Uint8Array): Uint8Array<ArrayBuffer> {
  const payload = typeof data === "string" ? encoder.encode(data) : data;
  if (payload.byteLength > MAX_PKT_DATA) {
    throw new Error("pkt-line payload too large");
  }
  const header = encoder.encode((payload.byteLength + LENGTH_BYTES).toString(16).padStart(LENGTH_BYTES, "0"));
  return concatBytes([header, payload]);
}

export function concatBytes(chunks: Uint8Array[]): Uint8Array<ArrayBuffer> {
  const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
  const out = new Uint8Array(total);
  let offset = 0;
  for (const chunk of chunks) {
    out.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return out;
}

export type Packet = { kind: "data"; data: Uint8Array } | { kind: "flush" } | { kind: "delim" } | { kind: "end" };

export function parsePacket(buffer: Uint8Array, offset: number): { packet: Packet; next: number } | null {
  if (buffer.byteLength - offset < LENGTH_BYTES) return null;
  const length = Number.parseInt(decoder.decode(buffer.subarray(offset, offset + LENGTH_BYTES)), 16);
  if (Number.isNaN(length)) throw new Error("Invalid pkt-line length");
  if (length === 0) return { packet: { kind: "flush" }, next: offset + LENGTH_BYTES };
  if (length === 1) return { packet: { kind: "delim" }, next: offset + LENGTH_BYTES };
  if (length === 2) return { packet: { kind: "end" }, next: offset + LENGTH_BYTES };
  if (buffer.byteLength - offset < length) return null;
  return {
    packet: { kind: "data", data: buffer.subarray(offset + LENGTH_BYTES, offset + length) },
    next: offset + length,