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