Skip to main content

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