Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 13 additions & 137 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,123 +161,9 @@ The two demo commands — [`cmd/clock`](cmd/clock) and
[`cmd/msfdemo`](cmd/msfdemo) — are complete, runnable versions of these patterns
end to end; each has its own README with sequence diagrams.

## Repo layout

```
pkg/moqt/ MOQT protocol implementation
├── wire/ Wire-format primitives (varint, KV pairs, namespaces, framing)
├── message/ Typed control- and request-stream messages
├── track/ Full track name + canonical map keys
├── session/ SETUP handshake, control multiplexing, GOAWAY, alias mgmt
│ ├── quicconn/ Native-QUIC Conn adapter (quic-go)
│ ├── wtconn/ WebTransport Conn adapter (webtransport-go)
│ └── sessiontest/ In-process pipe-backed Conn for tests
├── loc/ Low-Overhead Container per draft-ietf-moq-loc-02
├── msf/ MOQT Streaming Format per draft-ietf-moq-msf-01
└── errors.go Session / request / publish-done / stream-reset codes

pkg/relay/ Single-instance MOQT relay
├── cache/ Per-track object cache
└── discovery/ Cross-instance discovery interface + in-memory impl

cmd/
├── relay/ MOQT relay binary
├── interop-client/ moq-interop-runner test client (drives the session library)
├── clock/ Wall-clock publish/subscribe demo (raw MOQT)
└── msfdemo/ MSF catalog + LOC video frame demo

apps/
└── tlmst/ Wails3 desktop app (separate Go module, isolated deps)
```

## What's implemented

- **`wire`** — byte-level codec: MoQT leading-ones varints (§1.4.1, distinct
from QUIC's RFC 9000 varints), length-prefixed bytes, KV pairs
with delta-encoded types (§1.4.3), track namespaces (§2.4.1), reason phrases,
and both an in-memory `Reader` and a streaming `Decoder` over the same
control-frame interface.
- **`message`** — typed control, request-stream, and data-stream messages with
parameter negotiation: SETUP, GOAWAY, SUBSCRIBE, PUBLISH (+ DONE/BLOCKED),
FETCH (standalone, relative/absolute joining), TRACK_STATUS, REQUEST_UPDATE,
the namespace messages, the §11 object framing (subgroup, fetch, datagram)
with absence markers, subscription filters, GREASE handling, and a `Validate`
hook the decoder runs on parse to reject structurally-malformed messages
(FETCH End < Start, REQUEST_ERROR redirect consistency, object status/flags,
…).
- **`session`** — the SETUP handshake with version negotiation, control
multiplexing and request-ID allocation, Track-Alias management with §3.5
collision detection, the request openers (`Publish`/`Subscribe`/`Fetch`/…)
and the `AcceptRequest` responder, typed inbound data streams that resolve
§11.4.2/§11.4.4 deltas to absolute IDs, GOAWAY, the §10.20 token cache, and
pluggable transport via the `Conn` interface (`quicconn` + `wtconn` adapters).
- **`loc`** — `Object.Encode`/`Decode` producing the bytes that drop into a
`SubgroupObject`: typed Timestamp/Timescale/VideoConfig/VideoFrameMarking/
AudioLevel properties with `Extras` passthrough for unknown IDs, an RFC 6464
audio-level codec, and AVC/HEVC NAL framing detection.
- **`msf`** — `Catalog`/`Track` JSON (independent and delta catalogs, with
`Apply` replaying delta operations in document order), group-ID sequencing,
the Media and Event Timeline record formats, the `BeginBroadcast` /
`EndBroadcast*` workflow helpers, and `Catalog.Validate` enforcing the §5.1/
§5.2 invariants.
- **`relay`** — accepts publisher and subscriber sessions, routes objects
through a track registry with per-subscription live fanout under a §8
latency-window slow-reader policy, merges multiple upstream publishers per
track (§9.5) into one outbound subgroup stream per subscriber with §2.1
{Group, Object} deduplication and survivor-continues failover, serves FETCHes
from a per-track object cache and stitches the evicted part of a range from an
upstream FETCH, issues on-demand upstream SUBSCRIBEs to *every* matching
publisher (a local publisher and/or, via a `DiscoveryStore` `FindNamespace`
lookup + a pluggable `Dialer`, each advertising relay instance), reflects
namespaces other relays advertise to local subscribers by consuming
`WatchNamespaces`, forwards namespace interest, gates each request through an
`Authorizer` hook, emits telemetry through a `Metrics` hook, and drains
sessions with GOAWAY.
- **CLIs** — `cmd/relay` (with cert flags + self-signed fallback), `cmd/clock`
(raw subgroup demo), and `cmd/msfdemo` (the LOC + MSF stack end to end).

## Limitations

This is a **single-instance** reference relay, though cross-relay routing works
when wired up: set `Config.Discovery` + `Config.Dialer` and the relay follows a
`FindNamespace` lookup to dial and subscribe upstream on another instance (and
reflects remote namespaces to local subscribers via `WatchNamespaces`). What
remains out of scope: multi-hop **loop detection** (the only guard is skipping
the relay's own `RelayAddr`), an upstream **connection-health / redial policy**
beyond dial-on-demand, production `DiscoveryStore` backends (only the in-process
`MemoryStore` ships), GOAWAY **cascading**, and a `Dialer` for `cmd/relay`
itself (the binary stays single-instance; cross-relay is library-level). Known
gaps in the current code, roughly ordered by how
load-bearing they are:

- **Late publisher pickup** (§9.5) — multiple publishers per track *are*
merged and deduplicated, and an on-demand SUBSCRIBE subscribes to every
publisher matching at that moment. A publisher (or remote relay) that begins
advertising *after* a track's upstream set is established is not retroactively
pulled in until that set drains and a fresh SUBSCRIBE re-establishes it;
publishers that PUBLISH proactively are always merged regardless.
- **Subscriber-priority scheduling** (§10.2.7) — fully plumbed but not yet
enforced on the wire. `SUBSCRIBER_PRIORITY` is parsed and stored, the §7.2
four-rule composite key is computed (`EffectiveStreamPriority`) and pushed
through the `session.PrioritizedSendStream` interface on every stream
open/reopen (propagation is end-to-end test-covered). The missing piece is a
transport adapter that honors the knob: quic-go and webtransport-go expose no
per-stream priority API today, so the bundled adapters silently absorb it and
quic-go round-robins instead. Lights up with a one-line adapter change once
[quic-go#437](https://github.com/quic-go/quic-go/issues/437) lands. A
REQUEST_UPDATE that changes the priority mid-stream applies to *subsequently
opened* subgroup streams, not in-flight ones.
- **LOC encryption / SecureObjects** (LOC §3) and **Private Properties** —
intentionally out of scope pending a chosen SecureObjects revision. Property
IDs are draft-tentative (`PropAudioLevel = 0x0A` deviates from the draft's
*unassigned* suggested `6`, which collides with the registered
`PropTimestamp` (`0x06`); pending IANA assignment).
- **MSF** — no timeline GZIP compression, content protection (§4.3), token
authorization, or logs/analytics (each is a TODO or unspecified in the
draft). There is no built-in ABR helper: the library surfaces every catalog
field a selector needs (AltGroup, Width/Height, Bitrate, RenderGroup,
Depends, TemporalID, SpatialID), but variant-selection policy is the
application's job.
A per-feature breakdown of draft-18 completeness, the full list of what's
implemented per package, and known limitations live in
[`STATUS.md`](STATUS.md).

## Building and testing

Expand All @@ -295,30 +181,20 @@ regression-comparison workflow, see

### Interoperability tests

Interop is tested in both directions against independent implementations:

- **Relay direction** — `make interop` runs a third-party MoQT test client
(from the [moq-interop-runner](https://github.com/englishm/moq-interop-runner))
against our relay over both transports; `make interop-matrix` runs several
clients and prints a pass/skip/fail matrix.
- **Client direction** — `make interop-client` runs our own test client
([`cmd/interop-client`](cmd/interop-client)) against a relay (loopback by
default; override `CLIENT_RELAY_IMAGE`/`CLIENT_RELAY_URL` for a third-party
relay).

See [`cmd/relay/README.md`](cmd/relay/README.md) for the targets and options;
current results are tracked in [`STATUS.md`](STATUS.md).
This implementation is registered (as `moq-go`) in the
[moq-interop-runner](https://github.com/englishm/moq-interop-runner), which
exercises it in both directions against independent draft-18 implementations.
See [`cmd/relay/README.md`](cmd/relay/README.md) for the local `make interop`
targets.

CI runs on every push and pull request
([`.github/workflows/ci.yml`](.github/workflows/ci.yml)): `go build ./...`,
`go test ./...`, `go test -race ./...`, `golangci-lint run`, a `govulncheck`
scan, and the interop suite (`make interop` and `make interop-client`). The
interop run is not redundant with `go test`: the unit tests round-trip through
our own codec, so a wire-encoding regression (e.g. emitting QUIC varints instead
of the §1.4.1 leading-ones encoding) passes every unit test yet breaks interop —
only a run against an independent implementation catches it. The advisory
`make interop-matrix` is not gated, as it has known cross-implementation
divergences (see [`STATUS.md`](STATUS.md)).
scan, and the interop suite. The interop run is not redundant with `go test`:
the unit tests round-trip through our own codec, so a wire-encoding regression
(e.g. emitting QUIC varints instead of the §1.4.1 leading-ones encoding) passes
every unit test yet breaks interop — only a run against an independent
implementation catches it.

## License

Expand Down
Loading