moq-go

module
v0.0.0-...-1e79c0c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 28, 2026 License: Apache-2.0, MIT

README

moq-go

CI Go Reference Go Report Card License

A Go implementation of the Media over QUIC IETF drafts: a transport-agnostic session library, a single-instance reference relay, media packaging libraries, and demo publisher/subscriber CLIs.

This is library + reference-relay code, not a media player. Payloads are opaque to every layer — applications plug their own codec stack in at the LOC boundary.

Status: tracks moving IETF drafts. The wire format follows the draft versions above and the API is pre-1.0 — both change as the specs evolve.

Install

go get github.com/floatdrop/moq-go

Requires Go 1.26 or newer.

Quick start

Run the stack locally, each command in its own terminal:

go run ./cmd/relay              # ephemeral self-signed cert on :4433
go run ./cmd/msfdemo publish    # MSF catalog + LOC video frames
go run ./cmd/msfdemo subscribe  # discovers the video track from the catalog

For the simpler raw-MOQT case (no LOC/MSF), swap msfdemo for clock.

Using the library

The mental model:

  • A Session is one MOQT connection after the SETUP handshake. You get one from session.Client or session.Server over a transport Conn.
  • A publisher opens a PUBLISH request stream, then pushes objects on subgroup uni-streams.
  • A subscriber opens a SUBSCRIBE request stream, then reads objects via Session.AcceptDataStream.
  • A track is named by a (Namespace, Name) pair; a per-session Track Alias is the compact integer that data streams carry.

A minimal publisher looks like this:

sess, err := session.Client(ctx, quicconn.New(qconn),
	session.WithImplementation("my-app/0.1"))
if err != nil {
	return err
}
defer sess.Close(moqt.SessionNoError, "bye")

pub, err := sess.Publish(ctx, &message.Publish{
	Namespace: wire.Namespace("moq-example"),
	Name:      []byte("clock"),
})
if err != nil {
	return err
}
defer pub.Close()

// Publish assigned the Track Alias; the returned Publication carries it, so
// pub.OpenSubgroup fills it in for you. To manage aliases yourself, set
// Publish's TrackAlias (via sess.AllocOutboundTrackAlias) and use
// sess.OpenSubgroup directly.
sg, _ := pub.OpenSubgroup(message.SubgroupHeader{
	SubgroupIDMode: message.SubgroupIDImplicitZero,
	GroupID:        0,
})
// WriteObjectAt takes absolute Object IDs and computes the §11.4.2 delta
// encoding for you; WriteObject is the lower-level form that takes the delta.
_ = sg.WriteObjectAt(0, &message.SubgroupObject{Payload: []byte("hello")})
_ = sg.Close()

A subscriber reads objects off inbound data streams via Session.AcceptDataStream, type-switching the result to *IncomingSubgroupStream / *IncomingFetchStream. When you subscribe to several tracks on one session, session.Demux removes the hand-rolled accept loop: register a handler per track by its Track Alias (and per FETCH by Request ID), then call Demux.Run.

// sess here is the subscriber's session (from session.Client/Server).
sub, err := sess.Subscribe(ctx, &message.Subscribe{
	Namespace:  wire.Namespace("moq-example"),
	Name:       []byte("clock"),
	Parameters: message.Parameters{message.LargestObjectFilter()},
})
if err != nil {
	return err
}
defer sub.Close()

demux := session.NewDemux()
demux.HandleTrack(sub.TrackAlias(), func(s *session.IncomingSubgroupStream) {
	for {
		obj, err := s.ReadDecoded() // absolute IDs; deltas resolved for you
		if err != nil {
			return // io.EOF on clean FIN
		}
		_ = obj
	}
})
go demux.Run(ctx, sess) // HandleTrack is safe to call after Run starts
Examples

Worked, compile-checked examples for each part of the API live as Go example functions — browse them on pkg.go.dev or read the source, grouped here by the file they live in:

session

Topic Example function(s)
Open a session ExampleClient
Publish a track ExampleSession_Publish
Subscribe to a track ExampleSession_Subscribe
Route many tracks' data streams ExampleDemux
Route inbound requests (server side) ExampleRequestMux
Joining / standalone FETCH ExampleSession_Fetch, ExampleSession_Fetch_standalone, ExampleIncomingFetchStream
Update a live request ExampleSession_UpdateRequest
End a publication Example_endingAPublication
Stream exhaustion (PUBLISH_BLOCKED) ExampleSession_OpenPublish, ExampleSession_ReadPublishBlocked
Announce / discover namespaces ExampleSession_PublishNamespace, ExampleSession_SubscribeNamespace
Accept requests (server side) ExampleSession_AcceptRequest
Graceful shutdown (GOAWAY) ExampleSession_SendGoaway, ExampleSession_OnGoaway

relay

Topic Example function(s)
Run / authorize the relay ExampleNew, ExampleNew_authorizer

loc

Topic Example function(s)
LOC media packaging ExampleObject_Encode

msf

Topic Example function(s)
MSF catalogs (build / parse / delta) ExampleBeginBroadcast, Example_subscribeCatalog, ExampleApply

The two demo commands — cmd/clock and cmd/msfdemo — are complete, runnable versions of these patterns end to end; each has its own README with sequence diagrams.

A per-feature breakdown of draft-18 completeness, the full list of what's implemented per package, and known limitations live in STATUS.md.

Building and testing

go build ./...
go test ./...                          # full suite — hermetic, no fixtures or network
go test -race ./pkg/moqt/session/...   # race detector for goroutine/stream code
golangci-lint run                      # lint + format check (.golangci.yml)

go test ./... from the root does not include apps/tlmst (a separate module with CGO/WebKit deps). For the benchmark suite and the benchstat regression-comparison workflow, see benchmarks/README.md.

Interoperability tests

This implementation is registered (as moq-go) in the moq-interop-runner, which exercises it in both directions against independent draft-18 implementations. See cmd/relay/README.md for the local make interop targets.

CI runs on every push and pull request (.github/workflows/ci.yml): go build ./..., go test ./..., go test -race ./..., golangci-lint run, a govulncheck 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

Licensed under either:

Directories

Path Synopsis
cmd
clock command
interop-client command
Command interop-client is a MoQT test client that drives the moq-interop-runner test cases against a relay, exercising this repository's session library from the client side (the inverse of cmd/relay, which is tested *by* third-party clients).
Command interop-client is a MoQT test client that drives the moq-interop-runner test cases against a relay, exercising this repository's session library from the client side (the inverse of cmd/relay, which is tested *by* third-party clients).
msfdemo command
Command msfdemo exercises the LOC + MSF stack end-to-end against a running relay.
Command msfdemo exercises the LOC + MSF stack end-to-end against a running relay.
relay command
pkg
moqt
Package moqt is the umbrella for MoQT protocol primitives, error codes, and session machinery used by the relay.
Package moqt is the umbrella for MoQT protocol primitives, error codes, and session machinery used by the relay.
moqt/loc
Package loc implements the Low Overhead Media Container described by draft-ietf-moq-loc-02.
Package loc implements the Low Overhead Media Container described by draft-ietf-moq-loc-02.
moqt/message
Package message implements MoQT control- and request-stream message types per draft-ietf-moq-transport-18.
Package message implements MoQT control- and request-stream message types per draft-ietf-moq-transport-18.
moqt/msf
Package msf implements the MOQT Streaming Format described by draft-ietf-moq-msf-01: catalog encode/decode, delta updates, group ID numbering (§6.1), and the Media Timeline (§7) and Event Timeline (§8) tracks.
Package msf implements the MOQT Streaming Format described by draft-ietf-moq-msf-01: catalog encode/decode, delta updates, group ID numbering (§6.1), and the Media Timeline (§7) and Event Timeline (§8) tracks.
moqt/session
Package session implements the MoQT session layer: SETUP handshake, control stream multiplexing, request-ID allocation, and graceful termination via GOAWAY (§3.3, §3.5, §10.3, §10.4 of draft-ietf-moq-transport-18).
Package session implements the MoQT session layer: SETUP handshake, control stream multiplexing, request-ID allocation, and graceful termination via GOAWAY (§3.3, §3.5, §10.3, §10.4 of draft-ietf-moq-transport-18).
moqt/session/internal/conntest
Package conntest holds transport-test helpers shared between the quicconn and wtconn adapter test packages.
Package conntest holds transport-test helpers shared between the quicconn and wtconn adapter test packages.
moqt/session/quicconn
Package quicconn adapts github.com/quic-go/quic-go's *quic.Conn to the transport-neutral session.Conn interface.
Package quicconn adapts github.com/quic-go/quic-go's *quic.Conn to the transport-neutral session.Conn interface.
moqt/session/sessiontest
Package sessiontest provides in-process helpers for testing MoQT session code without a real QUIC transport.
Package sessiontest provides in-process helpers for testing MoQT session code without a real QUIC transport.
moqt/session/wtconn
Package wtconn adapts github.com/quic-go/webtransport-go's *webtransport.Session to the transport-neutral session.Conn interface.
Package wtconn adapts github.com/quic-go/webtransport-go's *webtransport.Session to the transport-neutral session.Conn interface.
moqt/track
Package track provides domain types for MoQT track identification per §2.4.1 of draft-ietf-moq-transport: Full Track Name and a comparable Key derived from it for use as a Go map key.
Package track provides domain types for MoQT track identification per §2.4.1 of draft-ietf-moq-transport: Full Track Name and a comparable Key derived from it for use as a Go map key.
moqt/uri
Package uri parses and validates "moqt" URIs and their fragment identifiers as defined by draft-ietf-moq-transport-18 §3.1.1 and §3.1.2.
Package uri parses and validates "moqt" URIs and their fragment identifiers as defined by draft-ietf-moq-transport-18 §3.1.1 and §3.1.2.
moqt/wire
Package wire implements MoQT wire-format primitives per draft-ietf-moq-transport-18: variable-length integers (§1.4.1, RFC 9000 §16), reason phrases (§1.4.4), track namespaces (§2.4.1), key-value pairs used in SETUP options (§1.4.3, §10.3.1), and control-message framing (§10).
Package wire implements MoQT wire-format primitives per draft-ietf-moq-transport-18: variable-length integers (§1.4.1, RFC 9000 §16), reason phrases (§1.4.4), track namespaces (§2.4.1), key-value pairs used in SETUP options (§1.4.3, §10.3.1), and control-message framing (§10).
relay
Package relay implements an MOQT relay (§9 of draft-ietf-moq-transport-18): an entity that is both a Publisher and a Subscriber, terminates Transport Sessions, caches Objects, aggregates subscriptions, and forwards data between upstream publishers and downstream subscribers.
Package relay implements an MOQT relay (§9 of draft-ietf-moq-transport-18): an entity that is both a Publisher and a Subscriber, terminates Transport Sessions, caches Objects, aggregates subscriptions, and forwards data between upstream publishers and downstream subscribers.
relay/cache
Package cache holds the relay's per-track Object Cache (§9.4 fetch support).
Package cache holds the relay's per-track Object Cache (§9.4 fetch support).
relay/discovery
Package discovery is the relay's cross-instance track + namespace advertisement abstraction.
Package discovery is the relay's cross-instance track + namespace advertisement abstraction.
relay/internal/registry
Package registry holds the relay's process-wide shared state: the track registry (object routing + per-track cache), the namespace registry (PUBLISH_NAMESPACE / SUBSCRIBE_NAMESPACE bookkeeping), the fetch router (rendezvous for upstream FETCH response streams), and the subscription state machine (UpstreamSub / DownstreamSub).
Package registry holds the relay's process-wide shared state: the track registry (object routing + per-track cache), the namespace registry (PUBLISH_NAMESPACE / SUBSCRIBE_NAMESPACE bookkeeping), the fetch router (rendezvous for upstream FETCH response streams), and the subscription state machine (UpstreamSub / DownstreamSub).
relay/internal/relaytest
Package relaytest holds helpers shared across the relay tests (the relay_test and registry_test packages).
Package relaytest holds helpers shared across the relay tests (the relay_test and registry_test packages).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL