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