v0.x

Retransmission as range inversion

QUIC never retransmits packets — it retransmits data. Here is how QUICO turns "what is missing?" into a single range operation.

TCP retransmits segments. QUIC does something subtler: a lost packet is never sent again. Its packet number is burned, and whatever data it carried is queued for a new packet. That one rule changes how you think about loss recovery.

Think in byte ranges, not packets

For every stream QUICO keeps two sets of byte ranges: what the peer has acknowledged, and what is in flight. Everything else between zero and the end of the stream is, by definition, data that still has to be sent.

javascript
// The entire retransmit logic:
const missing = flat_ranges.invert(
  acked_ranges ∪ in_flight_ranges,
  0, total_size
);

When loss detection declares packet pn lost, we simply delete in_flight[pn]. The byte ranges it covered drop out of the in-flight set, the inversion picks them up as missing, and the next packet the sender builds carries them. No retransmission queue, no per-packet copies of payloads.

Why a flat representation

Ranges are stored as a sorted flat array [start0, end0, start1, end1, …]. Union and inversion are a single linear merge, and the common case — one contiguous acknowledged prefix plus a short tail — stays tiny. Allocation per ACK is close to zero, which matters in JavaScript where garbage is the hidden cost of every abstraction.

What it buys us

  • Spurious loss is harmless: if the "lost" packet is acknowledged later, its ranges simply become acked.
  • Re-packing is free: missing ranges from several lost packets coalesce into one full-size packet.
  • The code is small enough to read in one sitting — which is the whole point of a pure-JS stack.

Loss detection itself (PTO timers, RFC 9002) is still on the roadmap; this post describes the data model it plugs into.

colocohen

Author of QUICO, LemonTLS and a family of pure-JavaScript network servers.