wire

package
v0.0.0-...-e37c033 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: Apache-2.0, MIT Imports: 9 Imported by: 0

Documentation

Overview

Package wire implements MoQT wire-format primitives per draft-ietf-moq-transport-19: 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).

Encoding follows an append-style: builders accumulate bytes into a Writer. Decoding uses a stateful Reader bounded by an input buffer; running past the buffer yields ErrShortBuffer, which the message layer maps to a session-level PROTOCOL_VIOLATION (§3.5).

For streaming decoding (e.g. data uni-streams), use StreamReader which wraps an io.Reader and exposes the same Decoder interface as Reader.

Index

Constants

View Source
const MaxControlMessagePayload = 0xFFFF

MaxControlMessagePayload is the largest payload that fits in a control message's 16-bit Length field (§10).

View Source
const MaxFullTrackNameBytes = 4096

MaxFullTrackNameBytes is the upper bound on the sum of all namespace field lengths plus the track name length per §2.4.1.

View Source
const MaxKVPairValueBytes = 0xFFFF

MaxKVPairValueBytes is the per-pair byte-value cap from §1.4.3.

View Source
const MaxReasonPhraseBytes = 1024

MaxReasonPhraseBytes is the §1.4.4 cap on an encoded reason phrase. Reader rejects anything longer, and Writer.ReasonPhrase truncates to it.

View Source
const MaxTrackNamespaceFields = 32

MaxTrackNamespaceFields is the upper bound on tuple count per §2.4.1.

Variables

View Source
var ErrFieldTooLarge = errors.New("moqt/wire: field exceeds maximum size")

ErrFieldTooLarge is returned by StreamReader when a length-prefixed field claims more bytes than MaxStreamFieldSize. Callers should treat it as a malformed message (PROTOCOL_VIOLATION, §3.5).

View Source
var ErrShortBuffer = errors.New("moqt/wire: short buffer")

ErrShortBuffer is returned when a read would consume bytes past the end of the input buffer. Callers should treat this as a malformed message.

View Source
var MaxStreamFieldSize = 16 << 20 // 16 MiB

MaxStreamFieldSize bounds a single length-prefixed field (object payload, properties blob, name, …) that StreamReader will allocate for. Because a StreamReader reads from an unbounded io.Reader, FixedBytes refuses to pre-allocate more than this for a peer-supplied length, so a malicious peer cannot trigger an unbounded allocation by claiming a huge length before sending the bytes. (The in-memory Reader is already bounded by its buffer and is not subject to this limit.) The default is generous enough for large media objects such as 4K keyframes; deployments carrying larger objects can raise it.

Functions

func AppendVarint

func AppendVarint(dst []byte, v uint64) []byte

AppendVarint appends the minimal leading-ones encoding of v to dst and returns the extended slice.

func NewByteReader

func NewByteReader(r io.Reader) io.ByteReader

NewByteReader adapts an io.Reader to io.ByteReader by reading a single byte per call, with no buffering or look-ahead, so a varint read leaves the underlying reader positioned exactly after the varint.

func ParseVarint

func ParseVarint(b []byte) (uint64, int, error)

ParseVarint decodes a leading-ones varint from the front of b, returning the value and the number of bytes consumed. It returns ErrShortBuffer if b is shorter than the encoding the first byte announces.

func ReadFrame

func ReadFrame(r io.Reader) (uint64, []byte, error)

ReadFrame reads a single MoQT control-message frame (Type + Length + Payload) from r, returning the message type and the payload bytes. The returned payload is freshly allocated; the caller owns it.

ReadFrame returns io.EOF only when r reports EOF before the type byte has been read; once any byte has been consumed, a truncated frame surfaces as io.ErrUnexpectedEOF.

func ReadVarint

func ReadVarint(r io.ByteReader) (uint64, error)

ReadVarint decodes a leading-ones varint from r, reading exactly the bytes of one encoding (never any look-ahead), so it is safe to call repeatedly on the same underlying stream.

func VarintLen

func VarintLen(v uint64) int

VarintLen returns the number of bytes AppendVarint uses to encode v.

func WriteFrame

func WriteFrame(w io.Writer, msgType uint64, payload []byte) error

WriteFrame writes a MoQT control-message frame to w. It returns an error if the payload exceeds MaxControlMessagePayload.

Types

type Decoder

type Decoder interface {
	Varint() (uint64, error)
	UInt8() (uint8, error)
	FixedBytes(n int) ([]byte, error)
	VarintBytes() ([]byte, error)
}

Decoder is the read-side interface shared by Reader (in-memory) and StreamReader (streaming io.Reader). Parse methods in the message package accept Decoder so they work in both contexts.

type KVPair

type KVPair struct {
	Type    uint64
	IntVal  uint64
	ByteVal []byte
}

KVPair is a MoQT Key-Value-Pair (§1.4.3). When Type is even, IntVal carries the value (encoded as a single varint). When Type is odd, ByteVal carries the value (length-prefixed bytes).

KVPairs are used for SETUP Options (§10.3.1); they appear delta-encoded by Type within a list, with the running "previous type" starting at zero.

func (KVPair) IsBytes

func (p KVPair) IsBytes() bool

IsBytes reports whether this KVPair carries length-prefixed bytes (Type odd) rather than a varint (Type even).

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader consumes MoQT wire primitives from an in-memory buffer. It tracks the read offset; partial reads do not advance the offset.

func NewReader

func NewReader(buf []byte) *Reader

NewReader returns a Reader over buf. buf is not copied; the caller must not mutate it while the Reader is in use.

func (*Reader) Empty

func (r *Reader) Empty() bool

Empty reports whether the reader has consumed all bytes.

func (*Reader) FixedBytes

func (r *Reader) FixedBytes(n int) ([]byte, error)

FixedBytes reads exactly n bytes. The returned slice is a fresh copy that the caller owns; mutating it does not affect the Reader's buffer, and retaining it does not pin the buffer for GC. Zero-length reads return nil.

func (*Reader) KVPair

func (r *Reader) KVPair(prev uint64) (KVPair, uint64, error)

KVPair reads a single KVPair using prev as the running previous Type, and returns the pair plus the new previous Type.

func (*Reader) KVPairsRemaining

func (r *Reader) KVPairsRemaining() ([]KVPair, error)

KVPairsRemaining reads KVPairs until the reader is empty. This is used for SETUP, where Setup Options span the entire control-message payload (§10.3).

func (*Reader) ReasonPhrase

func (r *Reader) ReasonPhrase() (string, error)

ReasonPhrase reads a varint-length-prefixed UTF-8 string per §1.4.4. The maximum allowed length is 1024 bytes; exceeding this yields an error that the caller should map to PROTOCOL_VIOLATION.

func (*Reader) Remaining

func (r *Reader) Remaining() int

Remaining returns the number of bytes left to consume.

func (*Reader) RemainingBytes

func (r *Reader) RemainingBytes() []byte

RemainingBytes consumes and returns a copy of all unconsumed bytes. Used when a message has a trailing variable-length field bounded only by the outer frame length (e.g. Track Properties in SUBSCRIBE_OK / PUBLISH). Zero-length returns nil.

func (*Reader) Scanner

func (r *Reader) Scanner() *Scanner

Scanner returns a sticky-error cursor over r.

func (*Reader) TrackNamespace

func (r *Reader) TrackNamespace() (TrackNamespace, error)

TrackNamespace reads a TrackNamespace per §2.4.1. Each field must be at least one byte; the tuple count must not exceed MaxTrackNamespaceFields. The returned slices are owned by the caller (see Reader.FixedBytes).

func (*Reader) UInt8

func (r *Reader) UInt8() (uint8, error)

UInt8 reads a single byte.

func (*Reader) Varint

func (r *Reader) Varint() (uint64, error)

Varint reads a MoQT leading-ones varint (§1.4.1, 1–9 bytes).

func (*Reader) VarintBytes

func (r *Reader) VarintBytes() ([]byte, error)

VarintBytes reads a varint length followed by that many bytes. The returned slice is owned by the caller (see FixedBytes).

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner is a sticky-error decoding cursor over a Reader. Each accessor reads one field into the supplied pointer and records the first error it hits; once an error is recorded every later accessor is a no-op until Err is consulted. It removes the repetitive per-field error handling that otherwise dominates message Parse methods:

func (m *Subscribe) Parse(r *wire.Reader) error {
	s := r.Scanner()
	s.Varint(&m.RequestID)
	s.TrackNamespace(&m.Namespace)
	s.VarintBytes(&m.Name)
	if err := s.Err(); err != nil {
		return err
	}
	return m.Parameters.parse(r)
}

A Scanner delegates to its Reader and advances the same read offset, so the underlying Reader stays usable directly after the Scanner (e.g. for Parameters.parse or a RemainingBytes tail) once Err reports no error.

Scanner only wraps the in-memory Reader; the streaming StreamReader / Decoder path is unaffected.

func (*Scanner) Err

func (s *Scanner) Err() error

Err returns the first error any accessor recorded, or nil.

func (*Scanner) KVPairsRemaining

func (s *Scanner) KVPairsRemaining(dst *[]KVPair)

KVPairsRemaining reads delta-encoded KV pairs to end-of-buffer into dst.

func (*Scanner) ReasonPhrase

func (s *Scanner) ReasonPhrase(dst *string)

ReasonPhrase reads a §1.4.4 reason phrase into dst.

func (*Scanner) TrackNamespace

func (s *Scanner) TrackNamespace(dst *TrackNamespace)

TrackNamespace reads a §2.4.1 track namespace into dst.

func (*Scanner) UInt8

func (s *Scanner) UInt8(dst *uint8)

UInt8 reads a single byte into dst.

func (*Scanner) Varint

func (s *Scanner) Varint(dst *uint64)

Varint reads a leading-ones varint (§1.4.1) into dst.

func (*Scanner) VarintBytes

func (s *Scanner) VarintBytes(dst *[]byte)

VarintBytes reads a varint-length-prefixed byte slice into dst.

type StreamReader

type StreamReader struct {
	// contains filtered or unexported fields
}

StreamReader wraps an io.Reader and exposes the same Decoder interface as Reader. It is intended for parsing self-delimiting wire objects directly from a QUIC uni-stream without buffering the entire object first.

func NewStreamReader

func NewStreamReader(r io.Reader) *StreamReader

NewStreamReader returns a StreamReader over r. r should already be buffered (e.g. a *bufio.Reader) for efficiency; StreamReader does not add its own buffering layer.

func (*StreamReader) FixedBytes

func (s *StreamReader) FixedBytes(n int) ([]byte, error)

FixedBytes reads exactly n bytes.

func (*StreamReader) UInt8

func (s *StreamReader) UInt8() (uint8, error)

UInt8 reads a single byte.

func (*StreamReader) Varint

func (s *StreamReader) Varint() (uint64, error)

Varint reads a MoQT leading-ones varint (§1.4.1) from the underlying stream.

func (*StreamReader) VarintBytes

func (s *StreamReader) VarintBytes() ([]byte, error)

VarintBytes reads a varint length then that many bytes.

type TrackNamespace

type TrackNamespace [][]byte

TrackNamespace is an ordered set of 0..32 binary fields (§2.4.1).

func Namespace

func Namespace(parts ...string) TrackNamespace

Namespace builds a TrackNamespace from string fields — the ergonomic form of the TrackNamespace{[]byte("a"), []byte("b")} literal. Each argument becomes one §2.4.1 field, in order. Namespace fields MAY contain arbitrary bytes; for non-UTF-8 fields use the [][]byte literal directly.

func (TrackNamespace) ByteLen

func (ns TrackNamespace) ByteLen() int

ByteLen reports the sum of field lengths (used to enforce the 4096-byte Full Track Name limit alongside the Track Name's length).

func (TrackNamespace) HasPrefix

func (ns TrackNamespace) HasPrefix(prefix TrackNamespace) bool

HasPrefix reports whether prefix is a (non-strict) prefix of ns in the field-by-field sense of §2.4.1. A zero-length prefix matches every ns, matching the §6.1 "Either message with zero Track Namespace fields indicates the sender is interested in all namespaces" rule used by SUBSCRIBE_NAMESPACE / SUBSCRIBE_TRACKS matching.

Fields are compared as opaque binary; namespace components MAY contain any bytes per §2.4.1.

func (TrackNamespace) String

func (ns TrackNamespace) String() string

String renders the namespace as "/comp1/comp2/..." with each component shown verbatim. Intended for log and error messages; callers that need a strict serialization should use Writer.TrackNamespace.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer accumulates encoded MoQT bytes. The zero value is ready to use.

func NewWriter

func NewWriter(buf []byte) *Writer

NewWriter returns a Writer that appends to buf (which may be nil). Use Bytes to retrieve the accumulated output.

func (*Writer) Bytes

func (w *Writer) Bytes() []byte

Bytes returns the accumulated output. The returned slice aliases the Writer's internal buffer.

func (*Writer) FixedBytes

func (w *Writer) FixedBytes(p []byte)

FixedBytes appends raw bytes without any length prefix.

func (*Writer) KVPair

func (w *Writer) KVPair(p KVPair, prev uint64) uint64

KVPair appends a single KVPair using prev as the running previous Type, and returns the new previous Type. The first pair in a list passes prev=0.

func (*Writer) KVPairs

func (w *Writer) KVPairs(pairs []KVPair)

KVPairs appends a list of KVPairs with delta encoding starting from prev=0. Pairs are sorted by Type before encoding so callers do not need to order them.

func (*Writer) ReasonPhrase

func (w *Writer) ReasonPhrase(s string)

ReasonPhrase appends a reason phrase per §1.4.4, truncating to MaxReasonPhraseBytes.

Truncating rather than encoding as-is because a Writer method has no way to report an error, so the alternative is emitting a frame that every conforming peer must treat as a PROTOCOL_VIOLATION — losing the tail of a diagnostic string is strictly better than losing the session that was trying to report it. The reason phrase is not always ours to bound: REQUEST_ERROR and PUBLISH_ERROR carry one built from a token verifier's error text, and a third-party [TokenVerifier] can return a string of any length.

The cut lands on a rune boundary, since §1.4.4 specifies UTF-8 and slicing mid-rune would produce a phrase the peer decodes as replacement characters.

func (*Writer) Reset

func (w *Writer) Reset()

Reset clears the writer's buffer, allowing it to be reused.

func (*Writer) TrackNamespace

func (w *Writer) TrackNamespace(ns TrackNamespace)

TrackNamespace appends a TrackNamespace per §2.4.1.

func (*Writer) UInt8

func (w *Writer) UInt8(v uint8)

UInt8 appends a single byte.

func (*Writer) Varint

func (w *Writer) Varint(v uint64)

Varint appends a MoQT leading-ones varint (§1.4.1).

func (*Writer) VarintBytes

func (w *Writer) VarintBytes(p []byte)

VarintBytes appends a varint length followed by the bytes themselves.

Jump to

Keyboard shortcuts

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