1use std::fmt;
2use std::str::FromStr;
3
4use crate::{coding, ietf, lite};
5
6pub(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
17pub const ALPNS: &[&str] = &[
24 ALPN_LITE_06,
25 ALPN_LITE_05,
26 ALPN_LITE_04,
27 ALPN_LITE_03,
28 ALPN_LITE,
29 ALPN_22,
30 ALPN_21,
31 ALPN_20,
32 ALPN_19,
33 ALPN_18,
34 ALPN_17,
35 ALPN_16,
36 ALPN_15,
37 ALPN_14,
38];
39
40pub(crate) const ALPN_LITE: &str = "moql";
42pub(crate) const ALPN_LITE_03: &str = "moq-lite-03";
43pub(crate) const ALPN_LITE_04: &str = "moq-lite-04";
44pub(crate) const ALPN_LITE_05: &str = "moq-lite-05";
45pub(crate) const ALPN_LITE_06: &str = "moq-lite-06";
46pub(crate) const ALPN_LITE_07_WIP: &str = "moq-lite-07-wip";
47pub(crate) const ALPN_14: &str = "moq-00";
48pub(crate) const ALPN_15: &str = "moqt-15";
49pub(crate) const ALPN_16: &str = "moqt-16";
50pub(crate) const ALPN_17: &str = "moqt-17";
51pub(crate) const ALPN_18: &str = "moqt-18";
52pub(crate) const ALPN_19: &str = "moqt-19";
53pub(crate) const ALPN_20: &str = "moqt-20";
54pub(crate) const ALPN_21: &str = "moqt-21";
55pub(crate) const ALPN_22: &str = "moqt-22";
56
57const ALL: [Version; 16] = [
58 Version::Lite(lite::Version::Lite07),
59 Version::Lite(lite::Version::Lite06),
60 Version::Lite(lite::Version::Lite05),
61 Version::Lite(lite::Version::Lite04),
62 Version::Lite(lite::Version::Lite03),
63 Version::Lite(lite::Version::Lite02),
64 Version::Lite(lite::Version::Lite01),
65 Version::Ietf(ietf::Version::Draft22),
66 Version::Ietf(ietf::Version::Draft21),
67 Version::Ietf(ietf::Version::Draft20),
68 Version::Ietf(ietf::Version::Draft19),
69 Version::Ietf(ietf::Version::Draft18),
70 Version::Ietf(ietf::Version::Draft17),
71 Version::Ietf(ietf::Version::Draft16),
72 Version::Ietf(ietf::Version::Draft15),
73 Version::Ietf(ietf::Version::Draft14),
74];
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78#[non_exhaustive]
79pub enum Version {
80 Lite(lite::Version),
82 Ietf(ietf::Version),
84}
85
86impl Version {
87 pub fn names() -> impl Iterator<Item = &'static str> {
89 ALL.iter().map(Self::as_str)
90 }
91
92 pub fn as_str(&self) -> &'static str {
94 match self {
95 Self::Lite(lite::Version::Lite01) => "moq-lite-01",
96 Self::Lite(lite::Version::Lite02) => "moq-lite-02",
97 Self::Lite(lite::Version::Lite03) => "moq-lite-03",
98 Self::Lite(lite::Version::Lite04) => "moq-lite-04",
99 Self::Lite(lite::Version::Lite05) => "moq-lite-05",
100 Self::Lite(lite::Version::Lite06) => "moq-lite-06",
101 Self::Lite(lite::Version::Lite07) => "moq-lite-07-wip",
102 Self::Ietf(ietf::Version::Draft14) => "moq-transport-14",
103 Self::Ietf(ietf::Version::Draft15) => "moq-transport-15",
104 Self::Ietf(ietf::Version::Draft16) => "moq-transport-16",
105 Self::Ietf(ietf::Version::Draft17) => "moq-transport-17",
106 Self::Ietf(ietf::Version::Draft18) => "moq-transport-18",
107 Self::Ietf(ietf::Version::Draft19) => "moq-transport-19",
108 Self::Ietf(ietf::Version::Draft20) => "moq-transport-20",
109 Self::Ietf(ietf::Version::Draft21) => "moq-transport-21",
110 Self::Ietf(ietf::Version::Draft22) => "moq-transport-22",
111 }
112 }
113
114 pub fn from_code(code: u64) -> Option<Self> {
116 match code {
117 0xff0dad01 => Some(Self::Lite(lite::Version::Lite01)),
118 0xff0dad02 => Some(Self::Lite(lite::Version::Lite02)),
119 0xff0dad03 => Some(Self::Lite(lite::Version::Lite03)),
120 0xff0dad04 => Some(Self::Lite(lite::Version::Lite04)),
121 0xff0dad05 => Some(Self::Lite(lite::Version::Lite05)),
122 0xff0dad06 => Some(Self::Lite(lite::Version::Lite06)),
123 0xff0dad07 => Some(Self::Lite(lite::Version::Lite07)),
124 0xff00000e => Some(Self::Ietf(ietf::Version::Draft14)),
125 0xff00000f => Some(Self::Ietf(ietf::Version::Draft15)),
126 0xff000010 => Some(Self::Ietf(ietf::Version::Draft16)),
127 0xff000011 => Some(Self::Ietf(ietf::Version::Draft17)),
128 0xff000012 => Some(Self::Ietf(ietf::Version::Draft18)),
129 0xff000013 => Some(Self::Ietf(ietf::Version::Draft19)),
130 0xff000014 => Some(Self::Ietf(ietf::Version::Draft20)),
131 0xff000015 => Some(Self::Ietf(ietf::Version::Draft21)),
132 0xff000016 => Some(Self::Ietf(ietf::Version::Draft22)),
133 _ => None,
134 }
135 }
136
137 pub fn code(&self) -> u64 {
139 match self {
140 Self::Lite(lite::Version::Lite01) => 0xff0dad01,
141 Self::Lite(lite::Version::Lite02) => 0xff0dad02,
142 Self::Lite(lite::Version::Lite03) => 0xff0dad03,
143 Self::Lite(lite::Version::Lite04) => 0xff0dad04,
144 Self::Lite(lite::Version::Lite05) => 0xff0dad05,
145 Self::Lite(lite::Version::Lite06) => 0xff0dad06,
146 Self::Lite(lite::Version::Lite07) => 0xff0dad07,
147 Self::Ietf(ietf::Version::Draft14) => 0xff00000e,
148 Self::Ietf(ietf::Version::Draft15) => 0xff00000f,
149 Self::Ietf(ietf::Version::Draft16) => 0xff000010,
150 Self::Ietf(ietf::Version::Draft17) => 0xff000011,
151 Self::Ietf(ietf::Version::Draft18) => 0xff000012,
152 Self::Ietf(ietf::Version::Draft19) => 0xff000013,
153 Self::Ietf(ietf::Version::Draft20) => 0xff000014,
154 Self::Ietf(ietf::Version::Draft21) => 0xff000015,
155 Self::Ietf(ietf::Version::Draft22) => 0xff000016,
156 }
157 }
158
159 pub fn from_alpn(alpn: &str) -> Option<Self> {
164 match alpn {
165 ALPN_LITE => None, ALPN_LITE_03 => Some(Self::Lite(lite::Version::Lite03)),
167 ALPN_LITE_04 => Some(Self::Lite(lite::Version::Lite04)),
168 ALPN_LITE_05 => Some(Self::Lite(lite::Version::Lite05)),
169 ALPN_LITE_06 => Some(Self::Lite(lite::Version::Lite06)),
170 ALPN_LITE_07_WIP => Some(Self::Lite(lite::Version::Lite07)),
171 ALPN_14 => Some(Self::Ietf(ietf::Version::Draft14)),
172 ALPN_15 => Some(Self::Ietf(ietf::Version::Draft15)),
173 ALPN_16 => Some(Self::Ietf(ietf::Version::Draft16)),
174 ALPN_17 => Some(Self::Ietf(ietf::Version::Draft17)),
175 ALPN_18 => Some(Self::Ietf(ietf::Version::Draft18)),
176 ALPN_19 => Some(Self::Ietf(ietf::Version::Draft19)),
177 ALPN_20 => Some(Self::Ietf(ietf::Version::Draft20)),
178 ALPN_21 => Some(Self::Ietf(ietf::Version::Draft21)),
179 ALPN_22 => Some(Self::Ietf(ietf::Version::Draft22)),
180 _ => None,
181 }
182 }
183
184 pub fn alpn(&self) -> &'static str {
186 match self {
187 Self::Lite(lite::Version::Lite07) => ALPN_LITE_07_WIP,
188 Self::Lite(lite::Version::Lite06) => ALPN_LITE_06,
189 Self::Lite(lite::Version::Lite05) => ALPN_LITE_05,
190 Self::Lite(lite::Version::Lite04) => ALPN_LITE_04,
191 Self::Lite(lite::Version::Lite03) => ALPN_LITE_03,
192 Self::Lite(lite::Version::Lite01 | lite::Version::Lite02) => ALPN_LITE,
193 Self::Ietf(ietf::Version::Draft14) => ALPN_14,
194 Self::Ietf(ietf::Version::Draft15) => ALPN_15,
195 Self::Ietf(ietf::Version::Draft16) => ALPN_16,
196 Self::Ietf(ietf::Version::Draft17) => ALPN_17,
197 Self::Ietf(ietf::Version::Draft18) => ALPN_18,
198 Self::Ietf(ietf::Version::Draft19) => ALPN_19,
199 Self::Ietf(ietf::Version::Draft20) => ALPN_20,
200 Self::Ietf(ietf::Version::Draft21) => ALPN_21,
201 Self::Ietf(ietf::Version::Draft22) => ALPN_22,
202 }
203 }
204
205 pub fn uses_setup_negotiation(&self) -> bool {
208 matches!(
209 self,
210 Self::Lite(lite::Version::Lite01 | lite::Version::Lite02) | Self::Ietf(ietf::Version::Draft14)
211 )
212 }
213
214 pub fn is_lite(&self) -> bool {
216 match self {
217 Self::Lite(_) => true,
218 Self::Ietf(_) => false,
219 }
220 }
221
222 pub fn is_ietf(&self) -> bool {
224 match self {
225 Self::Ietf(_) => true,
226 Self::Lite(_) => false,
227 }
228 }
229}
230
231impl fmt::Display for Version {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 f.write_str(self.as_str())
234 }
235}
236
237impl FromStr for Version {
238 type Err = String;
239
240 fn from_str(s: &str) -> Result<Self, Self::Err> {
241 ALL.iter()
242 .find(|version| version.as_str() == s)
243 .copied()
244 .ok_or_else(|| format!("unknown version: {s}"))
245 }
246}
247
248impl serde::Serialize for Version {
249 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
250 serializer.serialize_str(&self.to_string())
251 }
252}
253
254impl<'de> serde::Deserialize<'de> for Version {
255 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
256 let s = String::deserialize(deserializer)?;
257 s.parse().map_err(serde::de::Error::custom)
258 }
259}
260
261impl TryFrom<coding::Version> for Version {
262 type Error = ();
263
264 fn try_from(value: coding::Version) -> Result<Self, Self::Error> {
265 Self::from_code(value.0).ok_or(())
266 }
267}
268
269impl coding::Decode<Version> for Version {
270 fn decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, coding::DecodeError> {
271 coding::Version::decode(r, version).and_then(|v| v.try_into().map_err(|_| coding::DecodeError::InvalidValue))
272 }
273}
274
275impl coding::Encode<Version> for Version {
276 fn encode<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), coding::EncodeError> {
277 coding::Version::from(*self).encode(w, v)
278 }
279}
280
281impl From<Version> for coding::Version {
282 fn from(value: Version) -> Self {
283 Self(value.code())
284 }
285}
286
287impl From<Vec<Version>> for coding::Versions {
288 fn from(value: Vec<Version>) -> Self {
289 let inner: Vec<coding::Version> = value.into_iter().map(|v| v.into()).collect();
290 coding::Versions::from(inner)
291 }
292}
293
294#[derive(Debug, Clone)]
296pub struct Versions(Vec<Version>);
297
298impl Versions {
299 pub fn all() -> Self {
305 Self(
306 ALL.iter()
307 .filter(|version| !matches!(version, Version::Lite(lite::Version::Lite07)))
308 .copied()
309 .collect(),
310 )
311 }
312
313 pub fn alpns(&self) -> Vec<&'static str> {
315 let mut alpns = Vec::new();
316 for v in &self.0 {
317 let alpn = v.alpn();
318 if !alpns.contains(&alpn) {
319 alpns.push(alpn);
320 }
321 }
322 alpns
323 }
324
325 pub fn filter(&self, other: &Versions) -> Option<Versions> {
327 let filtered: Vec<Version> = self.0.iter().filter(|v| other.0.contains(v)).copied().collect();
328 if filtered.is_empty() {
329 None
330 } else {
331 Some(Versions(filtered))
332 }
333 }
334
335 pub fn select(&self, version: Version) -> Option<Version> {
337 self.0.contains(&version).then_some(version)
338 }
339
340 pub fn contains(&self, version: &Version) -> bool {
342 self.0.contains(version)
343 }
344
345 pub fn iter(&self) -> impl Iterator<Item = &Version> {
347 self.0.iter()
348 }
349}
350
351impl Default for Versions {
352 fn default() -> Self {
353 Self::all()
354 }
355}
356
357impl From<Version> for Versions {
358 fn from(value: Version) -> Self {
359 Self(vec![value])
360 }
361}
362
363impl From<Vec<Version>> for Versions {
364 fn from(value: Vec<Version>) -> Self {
365 Self(value)
366 }
367}
368
369impl<const N: usize> From<[Version; N]> for Versions {
370 fn from(value: [Version; N]) -> Self {
371 Self(value.to_vec())
372 }
373}
374
375impl From<Versions> for coding::Versions {
376 fn from(value: Versions) -> Self {
377 let inner: Vec<coding::Version> = value.0.into_iter().map(|v| v.into()).collect();
378 coding::Versions::from(inner)
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn default_versions_prefer_lite_06() {
388 let newest = Version::Lite(lite::Version::Lite06);
389 assert_eq!(newest.alpn(), "moq-lite-06");
390 assert_eq!(Version::from_alpn("moq-lite-06-wip"), None);
391 assert!("moq-lite-06-wip".parse::<Version>().is_err());
392 assert_eq!(Versions::all().iter().next(), Some(&newest));
393 assert_eq!(Versions::all().alpns().first(), Some(&newest.alpn()));
394 assert_eq!(ALPNS.first(), Some(&newest.alpn()));
395 }
396
397 #[test]
398 fn lite_07_wip_is_opt_in_only() {
399 let wip = Version::Lite(lite::Version::Lite07);
400 assert_eq!(wip.alpn(), "moq-lite-07-wip");
401 assert_eq!(wip.to_string(), "moq-lite-07-wip");
402 assert_eq!("moq-lite-07-wip".parse::<Version>(), Ok(wip));
403 assert_eq!(Version::from_alpn("moq-lite-07-wip"), Some(wip));
404 assert!("moq-lite-07".parse::<Version>().is_err());
405 assert_eq!(Version::from_alpn("moq-lite-07"), None);
406
407 assert!(!Versions::all().contains(&wip));
408 assert!(!ALPNS.contains(&wip.alpn()));
409 assert_eq!(Versions::from(wip).alpns(), [wip.alpn()]);
410 }
411}