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