moq-net 0.3.2

The networking layer for Media over QUIC: real-time pub/sub with built-in caching, fan-out, and prioritization.
Documentation
use std::fmt;

/// A lite protocol version.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Version {
	Lite01,
	Lite02,
	Lite03,
	Lite04,
	/// lite-05. Adds the TRACK stream (immutable per-track properties incl.
	/// timescale), zigzag-delta timestamps in per-frame headers, and drops
	/// SUBSCRIBE_OK/FETCH_OK.
	Lite05,
	/// Lite-06. Adds announce ids: each `active` ANNOUNCE_BROADCAST
	/// implicitly assigns the next ordinal, and `ended`/`restart` reference that id
	/// instead of repeating the path. Also adds the route cost carried alongside the
	/// hop chain, ranking above hop count in route selection. Advertised over ALPN
	/// as `moq-lite-06`.
	Lite06,
	/// Lite-07. Adds the hidden opt-in to ANNOUNCE_REQUEST: without it, a route with
	/// a `.`-prefixed segment below the requested prefix is left out. Advertised over
	/// ALPN as `moq-lite-07` and preferred by the default version sets.
	Lite07,
}

impl Version {
	/// Whether the version has lite-05's dedicated TRACK stream and related stream
	/// layout changes.
	///
	/// This is the common feature boundary for TRACK_INFO, FETCH streams,
	/// SUBSCRIBE_START/END, and per-frame timestamp prefixes.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_track_stream(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 => false,
			_ => true,
		}
	}

	/// Whether the PROBE message carries the RTT field. Added in lite-04; lite-03
	/// carries the bitrate alone, so a report with only an RTT to give says nothing
	/// there and must not be sent.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_probe_rtt(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 => false,
			_ => true,
		}
	}

	/// Whether the session opens a unidirectional Setup Stream carrying a single SETUP
	/// message (capabilities + optional Path). Added in lite-05; the older bidirectional
	/// setup exchange (Lite01/02) and the no-setup drafts (Lite03/04) don't use it.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_setup_stream(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 => false,
			_ => true,
		}
	}

	/// Whether the session may deliver groups over unreliable QUIC datagrams (lite-05 ยง6.4).
	/// A datagram carries one single-frame group's `subscribe | sequence | timestamp | payload`
	/// and is routed over the existing subscription. Added in lite-05; older versions never
	/// send or accept datagram bodies.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_datagrams(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 => false,
			_ => true,
		}
	}

	/// Whether announce streams begin with ANNOUNCE_OK and omit the sender's origin
	/// from each announcement's hop chain. Added in lite-05.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_announce_ok(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 => false,
			_ => true,
		}
	}

	/// Whether the session supports the GOAWAY control stream (0x5) for graceful
	/// shutdown and migration. Added in lite-04.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_goaway(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 => false,
			_ => true,
		}
	}

	/// Whether announcements carry implicit announce ids: each `active`
	/// ANNOUNCE_BROADCAST assigns the next per-stream ordinal, and `ended`/`restart`
	/// reference that id instead of repeating the path. Added in lite-06.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_announce_id(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 | Self::Lite05 => false,
			_ => true,
		}
	}

	/// Whether ANNOUNCE_REQUEST carries the Exclude Hop field: the subscriber's own
	/// Hop ID, which the publisher uses to skip announces whose hop chain already
	/// passed through the subscriber. Present in lite-04 and lite-05 only.
	///
	/// The receiver's own reflected-announce check drops those announces anyway (and
	/// catches loops of any length, not just the two-hop case), so lite-06 drops the
	/// field and keeps the check. Lite-06 also declares the same identity session-wide
	/// in the SETUP `Hop` parameter, which filters announcements and subscriptions
	/// alike rather than one announce stream.
	///
	/// Unlike the gates above, this lists the versions that *have* the field: it was
	/// removed rather than added, so future versions default to not carrying it.
	pub fn has_exclude_hop(self) -> bool {
		matches!(self, Self::Lite04 | Self::Lite05)
	}

	/// Whether SUBSCRIBE, SUBSCRIBE_UPDATE, FETCH, and GROUP carry frame indices
	/// alongside their group sequences, so a subscription or fetch can start and end
	/// partway through a group. Added in lite-06.
	///
	/// Older versions only address whole groups, so a route change has to wait for the
	/// next group before it can resume.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_frame_bounds(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 | Self::Lite05 => false,
			_ => true,
		}
	}

	/// Whether this version's SUBSCRIBE, SUBSCRIBE_UPDATE, SUBSCRIBE_OK, and TRACK_INFO
	/// carry the retired `Ordered` byte.
	///
	/// The field is gone from the model: a publisher transmits newest-first within a
	/// track, always. Deployed drafts still have the byte in their layout, so it is
	/// written as 0 and ignored on read rather than shifting every field behind it.
	pub(crate) fn has_group_order(self) -> bool {
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 | Self::Lite05 => true,
			Self::Lite06 | Self::Lite07 => false,
		}
	}

	/// Whether SUBSCRIBE's `Group Start` is an absolute floor the publisher resolves a
	/// start from: the raw minimum group sequence (default 0), with `Subscriber Max Age`
	/// as the only gate on how far back delivery begins. Changed in lite-06.
	///
	/// Older versions encode `Group Start` as the sequence + 1, with 0 meaning the
	/// latest group, so an absent start there pins the cursor to the live edge instead
	/// of resolving it from the budget.
	#[allow(clippy::match_like_matches_macro)]
	pub(crate) fn resolves_start(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 | Self::Lite05 => false,
			_ => true,
		}
	}

	/// Whether ANNOUNCE_REQUEST carries the hidden opt-in. Added in lite-07; older
	/// requests decode as not opted in.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_hidden(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 | Self::Lite05 | Self::Lite06 => false,
			_ => true,
		}
	}

	/// Whether announcements carry the route cost: the marginal cost of pulling
	/// the broadcast via this route, accumulated per link. Added in lite-06.
	/// Older versions carry nothing, so a received route stays at zero and ranks
	/// on hop count alone, exactly as before.
	#[allow(clippy::match_like_matches_macro)]
	pub fn has_route_cost(self) -> bool {
		// Match form so future versions default forward (CLAUDE.md convention).
		match self {
			Self::Lite01 | Self::Lite02 | Self::Lite03 | Self::Lite04 | Self::Lite05 => false,
			_ => true,
		}
	}
}

impl fmt::Display for Version {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::Lite01 => write!(f, "moq-lite-01"),
			Self::Lite02 => write!(f, "moq-lite-02"),
			Self::Lite03 => write!(f, "moq-lite-03"),
			Self::Lite04 => write!(f, "moq-lite-04"),
			Self::Lite05 => write!(f, "moq-lite-05"),
			Self::Lite06 => write!(f, "moq-lite-06"),
			Self::Lite07 => write!(f, "moq-lite-07"),
		}
	}
}

impl From<Version> for crate::Version {
	fn from(v: Version) -> Self {
		match v {
			Version::Lite01 => crate::Version::Lite(Version::Lite01),
			Version::Lite02 => crate::Version::Lite(Version::Lite02),
			Version::Lite03 => crate::Version::Lite(Version::Lite03),
			Version::Lite04 => crate::Version::Lite(Version::Lite04),
			Version::Lite05 => crate::Version::Lite(Version::Lite05),
			Version::Lite06 => crate::Version::Lite(Version::Lite06),
			Version::Lite07 => crate::Version::Lite(Version::Lite07),
		}
	}
}

impl TryFrom<crate::Version> for Version {
	type Error = ();

	fn try_from(v: crate::Version) -> Result<Self, Self::Error> {
		match v {
			crate::Version::Lite(v) => Ok(v),
			crate::Version::Ietf(_) => Err(()),
		}
	}
}