Skip to main content

moq_net/
version.rs

1use std::fmt;
2use std::str::FromStr;
3
4use crate::{coding, ietf, lite};
5
6/// The versions of MoQ that are negotiated via SETUP.
7///
8/// Ordered by preference, with the client's preference taking priority.
9/// This intentionally includes only SETUP-negotiated versions (Lite02, Lite01, Draft14);
10/// Lite03 and newer IETF drafts negotiate via dedicated ALPNs instead.
11pub(crate) const NEGOTIATED: [Version; 3] = [
12	Version::Lite(lite::Version::Lite02),
13	Version::Lite(lite::Version::Lite01),
14	Version::Ietf(ietf::Version::Draft14),
15];
16
17/// ALPN strings for supported versions, most-preferred first. `ALPNS[0]` is the
18/// newest moq-lite ALPN that both sides converge on.
19///
20/// `ALPN_LITE_06_WIP` is deliberately absent: lite-06's wire format is still
21/// work-in-progress, so it is never advertised or negotiated by default. It is only
22/// reachable when both peers explicitly opt in (e.g. `--version moq-lite-06-wip`),
23/// which is enough to exercise it in tests without shipping it to real peers.
24pub const ALPNS: &[&str] = &[
25	ALPN_LITE_05,
26	ALPN_LITE_04,
27	ALPN_LITE_03,
28	ALPN_LITE,
29	ALPN_19,
30	ALPN_18,
31	ALPN_17,
32	ALPN_16,
33	ALPN_15,
34	ALPN_14,
35];
36
37// ALPN constants
38pub(crate) const ALPN_LITE: &str = "moql";
39pub(crate) const ALPN_LITE_03: &str = "moq-lite-03";
40pub(crate) const ALPN_LITE_04: &str = "moq-lite-04";
41pub(crate) const ALPN_LITE_05: &str = "moq-lite-05";
42pub(crate) const ALPN_LITE_06_WIP: &str = "moq-lite-06-wip";
43pub(crate) const ALPN_14: &str = "moq-00";
44pub(crate) const ALPN_15: &str = "moqt-15";
45pub(crate) const ALPN_16: &str = "moqt-16";
46pub(crate) const ALPN_17: &str = "moqt-17";
47pub(crate) const ALPN_18: &str = "moqt-18";
48pub(crate) const ALPN_19: &str = "moqt-19";
49
50/// A MoQ protocol version.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52#[non_exhaustive]
53pub enum Version {
54	/// A `moq-lite` draft, the simplified protocol this project specifies.
55	Lite(lite::Version),
56	/// An IETF `moq-transport` draft.
57	Ietf(ietf::Version),
58}
59
60impl Version {
61	/// Parse from wire version code (used during SETUP negotiation).
62	pub fn from_code(code: u64) -> Option<Self> {
63		match code {
64			0xff0dad01 => Some(Self::Lite(lite::Version::Lite01)),
65			0xff0dad02 => Some(Self::Lite(lite::Version::Lite02)),
66			0xff0dad03 => Some(Self::Lite(lite::Version::Lite03)),
67			0xff0dad04 => Some(Self::Lite(lite::Version::Lite04)),
68			0xff0dad05 => Some(Self::Lite(lite::Version::Lite05)),
69			0xff0dad06 => Some(Self::Lite(lite::Version::Lite06Wip)),
70			0xff00000e => Some(Self::Ietf(ietf::Version::Draft14)),
71			0xff00000f => Some(Self::Ietf(ietf::Version::Draft15)),
72			0xff000010 => Some(Self::Ietf(ietf::Version::Draft16)),
73			0xff000011 => Some(Self::Ietf(ietf::Version::Draft17)),
74			0xff000012 => Some(Self::Ietf(ietf::Version::Draft18)),
75			0xff000013 => Some(Self::Ietf(ietf::Version::Draft19)),
76			_ => None,
77		}
78	}
79
80	/// Get the wire version code.
81	pub fn code(&self) -> u64 {
82		match self {
83			Self::Lite(lite::Version::Lite01) => 0xff0dad01,
84			Self::Lite(lite::Version::Lite02) => 0xff0dad02,
85			Self::Lite(lite::Version::Lite03) => 0xff0dad03,
86			Self::Lite(lite::Version::Lite04) => 0xff0dad04,
87			Self::Lite(lite::Version::Lite05) => 0xff0dad05,
88			Self::Lite(lite::Version::Lite06Wip) => 0xff0dad06,
89			Self::Ietf(ietf::Version::Draft14) => 0xff00000e,
90			Self::Ietf(ietf::Version::Draft15) => 0xff00000f,
91			Self::Ietf(ietf::Version::Draft16) => 0xff000010,
92			Self::Ietf(ietf::Version::Draft17) => 0xff000011,
93			Self::Ietf(ietf::Version::Draft18) => 0xff000012,
94			Self::Ietf(ietf::Version::Draft19) => 0xff000013,
95		}
96	}
97
98	/// Parse from ALPN string.
99	///
100	/// Returns `None` for `ALPN_LITE` since multiple versions share
101	/// that ALPN, requiring SETUP negotiation to determine the version.
102	pub fn from_alpn(alpn: &str) -> Option<Self> {
103		match alpn {
104			ALPN_LITE => None, // Multiple versions share this ALPN, need SETUP negotiation
105			ALPN_LITE_03 => Some(Self::Lite(lite::Version::Lite03)),
106			ALPN_LITE_04 => Some(Self::Lite(lite::Version::Lite04)),
107			ALPN_LITE_05 => Some(Self::Lite(lite::Version::Lite05)),
108			ALPN_LITE_06_WIP => Some(Self::Lite(lite::Version::Lite06Wip)),
109			ALPN_14 => Some(Self::Ietf(ietf::Version::Draft14)),
110			ALPN_15 => Some(Self::Ietf(ietf::Version::Draft15)),
111			ALPN_16 => Some(Self::Ietf(ietf::Version::Draft16)),
112			ALPN_17 => Some(Self::Ietf(ietf::Version::Draft17)),
113			ALPN_18 => Some(Self::Ietf(ietf::Version::Draft18)),
114			ALPN_19 => Some(Self::Ietf(ietf::Version::Draft19)),
115			_ => None,
116		}
117	}
118
119	/// Returns the ALPN string for this version.
120	pub fn alpn(&self) -> &'static str {
121		match self {
122			Self::Lite(lite::Version::Lite06Wip) => ALPN_LITE_06_WIP,
123			Self::Lite(lite::Version::Lite05) => ALPN_LITE_05,
124			Self::Lite(lite::Version::Lite04) => ALPN_LITE_04,
125			Self::Lite(lite::Version::Lite03) => ALPN_LITE_03,
126			Self::Lite(lite::Version::Lite01 | lite::Version::Lite02) => ALPN_LITE,
127			Self::Ietf(ietf::Version::Draft14) => ALPN_14,
128			Self::Ietf(ietf::Version::Draft15) => ALPN_15,
129			Self::Ietf(ietf::Version::Draft16) => ALPN_16,
130			Self::Ietf(ietf::Version::Draft17) => ALPN_17,
131			Self::Ietf(ietf::Version::Draft18) => ALPN_18,
132			Self::Ietf(ietf::Version::Draft19) => ALPN_19,
133		}
134	}
135
136	/// Whether this version uses SETUP version-code negotiation
137	/// (as opposed to ALPN-only).
138	pub fn uses_setup_negotiation(&self) -> bool {
139		matches!(
140			self,
141			Self::Lite(lite::Version::Lite01 | lite::Version::Lite02) | Self::Ietf(ietf::Version::Draft14)
142		)
143	}
144
145	/// Whether this is a lite protocol version.
146	pub fn is_lite(&self) -> bool {
147		match self {
148			Self::Lite(_) => true,
149			Self::Ietf(_) => false,
150		}
151	}
152
153	/// Whether this is an IETF protocol version.
154	pub fn is_ietf(&self) -> bool {
155		match self {
156			Self::Ietf(_) => true,
157			Self::Lite(_) => false,
158		}
159	}
160}
161
162impl fmt::Display for Version {
163	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164		match self {
165			Self::Lite(v) => v.fmt(f),
166			Self::Ietf(v) => v.fmt(f),
167		}
168	}
169}
170
171impl FromStr for Version {
172	type Err = String;
173
174	fn from_str(s: &str) -> Result<Self, Self::Err> {
175		match s {
176			"moq-lite-01" => Ok(Self::Lite(lite::Version::Lite01)),
177			"moq-lite-02" => Ok(Self::Lite(lite::Version::Lite02)),
178			"moq-lite-03" => Ok(Self::Lite(lite::Version::Lite03)),
179			"moq-lite-04" => Ok(Self::Lite(lite::Version::Lite04)),
180			"moq-lite-05" => Ok(Self::Lite(lite::Version::Lite05)),
181			"moq-lite-06-wip" => Ok(Self::Lite(lite::Version::Lite06Wip)),
182			"moq-transport-14" => Ok(Self::Ietf(ietf::Version::Draft14)),
183			"moq-transport-15" => Ok(Self::Ietf(ietf::Version::Draft15)),
184			"moq-transport-16" => Ok(Self::Ietf(ietf::Version::Draft16)),
185			"moq-transport-17" => Ok(Self::Ietf(ietf::Version::Draft17)),
186			"moq-transport-18" => Ok(Self::Ietf(ietf::Version::Draft18)),
187			"moq-transport-19" => Ok(Self::Ietf(ietf::Version::Draft19)),
188			_ => Err(format!("unknown version: {s}")),
189		}
190	}
191}
192
193impl serde::Serialize for Version {
194	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
195		serializer.serialize_str(&self.to_string())
196	}
197}
198
199impl<'de> serde::Deserialize<'de> for Version {
200	fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
201		let s = String::deserialize(deserializer)?;
202		s.parse().map_err(serde::de::Error::custom)
203	}
204}
205
206impl TryFrom<coding::Version> for Version {
207	type Error = ();
208
209	fn try_from(value: coding::Version) -> Result<Self, Self::Error> {
210		Self::from_code(value.0).ok_or(())
211	}
212}
213
214impl coding::Decode<Version> for Version {
215	fn decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, coding::DecodeError> {
216		coding::Version::decode(r, version).and_then(|v| v.try_into().map_err(|_| coding::DecodeError::InvalidValue))
217	}
218}
219
220impl coding::Encode<Version> for Version {
221	fn encode<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), coding::EncodeError> {
222		coding::Version::from(*self).encode(w, v)
223	}
224}
225
226impl From<Version> for coding::Version {
227	fn from(value: Version) -> Self {
228		Self(value.code())
229	}
230}
231
232impl From<Vec<Version>> for coding::Versions {
233	fn from(value: Vec<Version>) -> Self {
234		let inner: Vec<coding::Version> = value.into_iter().map(|v| v.into()).collect();
235		coding::Versions::from(inner)
236	}
237}
238
239/// A set of supported MoQ versions.
240#[derive(Debug, Clone)]
241pub struct Versions(Vec<Version>);
242
243impl Versions {
244	/// All versions exposed by default.
245	///
246	/// `Lite06Wip` is intentionally excluded: its wire format is still work-in-progress,
247	/// so it is not advertised until a caller opts in explicitly (e.g. a pinned
248	/// `version = ["moq-lite-06-wip"]`). It is otherwise a fully-defined version, so an
249	/// opt-in set that includes it negotiates normally.
250	pub fn all() -> Self {
251		Self(vec![
252			Version::Lite(lite::Version::Lite05),
253			Version::Lite(lite::Version::Lite04),
254			Version::Lite(lite::Version::Lite03),
255			Version::Lite(lite::Version::Lite02),
256			Version::Lite(lite::Version::Lite01),
257			Version::Ietf(ietf::Version::Draft19),
258			Version::Ietf(ietf::Version::Draft18),
259			Version::Ietf(ietf::Version::Draft17),
260			Version::Ietf(ietf::Version::Draft16),
261			Version::Ietf(ietf::Version::Draft15),
262			Version::Ietf(ietf::Version::Draft14),
263		])
264	}
265
266	/// Compute the unique ALPN strings needed for these versions.
267	pub fn alpns(&self) -> Vec<&'static str> {
268		let mut alpns = Vec::new();
269		for v in &self.0 {
270			let alpn = v.alpn();
271			if !alpns.contains(&alpn) {
272				alpns.push(alpn);
273			}
274		}
275		alpns
276	}
277
278	/// Return only versions present in both self and other, or `None` if the intersection is empty.
279	pub fn filter(&self, other: &Versions) -> Option<Versions> {
280		let filtered: Vec<Version> = self.0.iter().filter(|v| other.0.contains(v)).copied().collect();
281		if filtered.is_empty() {
282			None
283		} else {
284			Some(Versions(filtered))
285		}
286	}
287
288	/// Check if a specific version is in this set.
289	pub fn select(&self, version: Version) -> Option<Version> {
290		self.0.contains(&version).then_some(version)
291	}
292
293	/// Returns `true` if the set includes this version.
294	pub fn contains(&self, version: &Version) -> bool {
295		self.0.contains(version)
296	}
297
298	/// Iterate the set in preference order, most preferred first.
299	pub fn iter(&self) -> impl Iterator<Item = &Version> {
300		self.0.iter()
301	}
302}
303
304impl Default for Versions {
305	fn default() -> Self {
306		Self::all()
307	}
308}
309
310impl From<Version> for Versions {
311	fn from(value: Version) -> Self {
312		Self(vec![value])
313	}
314}
315
316impl From<Vec<Version>> for Versions {
317	fn from(value: Vec<Version>) -> Self {
318		Self(value)
319	}
320}
321
322impl<const N: usize> From<[Version; N]> for Versions {
323	fn from(value: [Version; N]) -> Self {
324		Self(value.to_vec())
325	}
326}
327
328impl From<Versions> for coding::Versions {
329	fn from(value: Versions) -> Self {
330		let inner: Vec<coding::Version> = value.0.into_iter().map(|v| v.into()).collect();
331		coding::Versions::from(inner)
332	}
333}