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