Skip to main content

moq_net/
server.rs

1use crate::{
2	ALPN_14, ALPN_15, ALPN_16, ALPN_17, ALPN_18, ALPN_19, ALPN_LITE, ALPN_LITE_03, ALPN_LITE_04, ALPN_LITE_05_WIP,
3	Error, NEGOTIATED, OriginConsumer, OriginProducer, Session, StatsHandle, Version, Versions,
4	coding::{Decode, Encode, Stream},
5	ietf, lite, setup,
6};
7
8/// A MoQ server session builder.
9#[derive(Default, Clone)]
10pub struct Server {
11	publish: Option<OriginConsumer>,
12	consume: Option<OriginProducer>,
13	stats: StatsHandle,
14	versions: Versions,
15}
16
17impl Server {
18	pub fn new() -> Self {
19		Default::default()
20	}
21
22	pub fn with_publish(mut self, publish: impl Into<Option<OriginConsumer>>) -> Self {
23		self.publish = publish.into();
24		self
25	}
26
27	pub fn with_consume(mut self, consume: impl Into<Option<OriginProducer>>) -> Self {
28		self.consume = consume.into();
29		self
30	}
31
32	/// Attach a tier-scoped [`StatsHandle`]. Per-broadcast and per-subscription
33	/// counters will be bumped through this handle for the lifetime of the session.
34	/// Pass [`StatsHandle::default`] (a no-op handle) to opt out.
35	pub fn with_stats(mut self, stats: StatsHandle) -> Self {
36		self.stats = stats;
37		self
38	}
39
40	/// Set both publish and consume from an `OriginProducer`.
41	///
42	/// This is equivalent to calling `with_publish(origin.consume())` and `with_consume(origin)`.
43	pub fn with_origin(self, origin: OriginProducer) -> Self {
44		let consumer = origin.consume();
45		self.with_publish(consumer).with_consume(origin)
46	}
47
48	pub fn with_versions(mut self, versions: Versions) -> Self {
49		self.versions = versions;
50		self
51	}
52
53	/// Perform the MoQ handshake as a server, returning the established [`Session`].
54	///
55	/// Convenience wrapper over [`accept_request`](Self::accept_request) that completes
56	/// the handshake immediately. Use `accept_request` when you need to inspect the
57	/// client's advertised path before deciding what to serve.
58	pub async fn accept<S: web_transport_trait::Session>(&self, session: S) -> Result<Session, Error> {
59		self.accept_request(session).await?.ok().await
60	}
61
62	/// Begin the MoQ handshake, pausing once the client's request path is known so the
63	/// caller can authorize or scope before serving.
64	///
65	/// Reads the client's SETUP (the in-band path lives there on URL-less transports),
66	/// then returns a [`Request`]: inspect [`path`](Request::path), set the origins to
67	/// serve, and call [`ok`](Request::ok) or [`close`](Request::close). Starting the
68	/// session is deferred to `ok()`, so origins set on the `Request` take effect.
69	///
70	/// The path is surfaced for moq-lite-05; it is `None` on versions with no in-band
71	/// request path (lite 01-04, and the IETF drafts in this build).
72	pub async fn accept_request<S: web_transport_trait::Session>(&self, session: S) -> Result<Request<S>, Error> {
73		// Regimes without an in-band path defer to `ok()` without surfacing one.
74		let deferred = |handshake| Request {
75			server: self.clone(),
76			path: None,
77			handshake,
78		};
79
80		let (encoding, supported) = match session.protocol() {
81			Some(ALPN_19) => {
82				self.versions
83					.select(Version::Ietf(ietf::Version::Draft19))
84					.ok_or(Error::Version)?;
85				return Ok(deferred(Handshake::IetfModern {
86					session,
87					version: ietf::Version::Draft19,
88				}));
89			}
90			Some(ALPN_18) => {
91				self.versions
92					.select(Version::Ietf(ietf::Version::Draft18))
93					.ok_or(Error::Version)?;
94				return Ok(deferred(Handshake::IetfModern {
95					session,
96					version: ietf::Version::Draft18,
97				}));
98			}
99			Some(ALPN_17) => {
100				self.versions
101					.select(Version::Ietf(ietf::Version::Draft17))
102					.ok_or(Error::Version)?;
103				return Ok(deferred(Handshake::IetfModern {
104					session,
105					version: ietf::Version::Draft17,
106				}));
107			}
108			Some(ALPN_16) => {
109				let v = self
110					.versions
111					.select(Version::Ietf(ietf::Version::Draft16))
112					.ok_or(Error::Version)?;
113				(v, v.into())
114			}
115			Some(ALPN_15) => {
116				let v = self
117					.versions
118					.select(Version::Ietf(ietf::Version::Draft15))
119					.ok_or(Error::Version)?;
120				(v, v.into())
121			}
122			Some(ALPN_14) => {
123				let v = self
124					.versions
125					.select(Version::Ietf(ietf::Version::Draft14))
126					.ok_or(Error::Version)?;
127				(v, v.into())
128			}
129			Some(ALPN_LITE_05_WIP) => {
130				self.versions
131					.select(Version::Lite(lite::Version::Lite05Wip))
132					.ok_or(Error::Version)?;
133
134				// Gate on the client's SETUP: read it before serving so the caller can
135				// scope by the advertised path.
136				let client_setup = lite::accept_setup(&session, lite::Version::Lite05Wip).await?;
137				return Ok(Request {
138					server: self.clone(),
139					path: client_setup.path,
140					handshake: Handshake::Lite05 { session },
141				});
142			}
143			Some(ALPN_LITE_04) => {
144				self.versions
145					.select(Version::Lite(lite::Version::Lite04))
146					.ok_or(Error::Version)?;
147				return Ok(deferred(Handshake::LiteBare {
148					session,
149					version: lite::Version::Lite04,
150				}));
151			}
152			Some(ALPN_LITE_03) => {
153				self.versions
154					.select(Version::Lite(lite::Version::Lite03))
155					.ok_or(Error::Version)?;
156				return Ok(deferred(Handshake::LiteBare {
157					session,
158					version: lite::Version::Lite03,
159				}));
160			}
161			Some(ALPN_LITE) | None => {
162				let supported = self.versions.filter(&NEGOTIATED.into()).ok_or(Error::Version)?;
163				(Version::Ietf(ietf::Version::Draft14), supported)
164			}
165			Some(p) => return Err(Error::UnknownAlpn(p.to_string())),
166		};
167
168		// Legacy bidi SETUP exchange (IETF 14-16, lite 01/02). Read the client's SETUP
169		// to choose the version; `ok()` sends the server SETUP and starts the session.
170		let mut stream = Stream::accept(&session, encoding).await?;
171		let mut client: setup::Client = stream.reader.decode().await?;
172
173		let version = client
174			.versions
175			.iter()
176			.flat_map(|v| Version::try_from(*v).ok())
177			.find(|v| supported.contains(v))
178			.ok_or(Error::Version)?;
179
180		// Pull the max request ID out now (IETF only) so `ok()` doesn't re-decode the
181		// consumed parameters.
182		let request_id_max = match version {
183			Version::Ietf(v) => {
184				let params = ietf::Parameters::decode(&mut client.parameters, v)?;
185				params
186					.get_varint(ietf::ParameterVarInt::MaxRequestId)
187					.map(ietf::RequestId)
188			}
189			Version::Lite(_) => None,
190		};
191
192		Ok(deferred(Handshake::Legacy {
193			session,
194			stream,
195			version,
196			request_id_max,
197		}))
198	}
199}
200
201/// A paused server-side handshake.
202///
203/// Returned by [`Server::accept_request`] once the client's advertised
204/// [`path`](Self::path) is known but before the session is granted anything. Set the
205/// origins to serve, then call [`ok`](Self::ok) to complete the handshake, or
206/// [`close`](Self::close) to reject it. Modeled on the WebTransport `Request`.
207pub struct Request<S: web_transport_trait::Session> {
208	server: Server,
209	path: Option<String>,
210	handshake: Handshake<S>,
211}
212
213/// The handshake state captured at the pause point. Every variant defers its session
214/// start to [`Request::ok`] so origins set on the `Request` still apply.
215enum Handshake<S: web_transport_trait::Session> {
216	/// Modern IETF (17/18): SETUP is exchanged in the background by the session.
217	IetfModern { session: S, version: ietf::Version },
218	/// moq-lite 03/04: no Setup stream.
219	LiteBare { session: S, version: lite::Version },
220	/// moq-lite 05+: the client's Setup stream has already been read.
221	Lite05 { session: S },
222	/// Legacy IETF (14-16) and lite 01/02: the client SETUP has been read off the bidi
223	/// stream but the server SETUP hasn't been sent. `ok()` finishes it.
224	Legacy {
225		session: S,
226		stream: Stream<S, Version>,
227		version: Version,
228		request_id_max: Option<ietf::RequestId>,
229	},
230}
231
232impl<S: web_transport_trait::Session> Request<S> {
233	/// The request path the client advertised in its SETUP, if any.
234	///
235	/// Populated for moq-lite-05; `None` on versions without an in-band request path.
236	/// See the note on [`Server::accept_request`].
237	pub fn path(&self) -> Option<&str> {
238		self.path.as_deref()
239	}
240
241	/// Publish to the connected client. Overrides any value from the [`Server`]
242	/// builder; typically set after inspecting [`path`](Self::path).
243	pub fn with_publish(mut self, publish: impl Into<Option<OriginConsumer>>) -> Self {
244		self.server.publish = publish.into();
245		self
246	}
247
248	/// Subscribe to the connected client. Overrides any value from the [`Server`] builder.
249	pub fn with_consume(mut self, consume: impl Into<Option<OriginProducer>>) -> Self {
250		self.server.consume = consume.into();
251		self
252	}
253
254	/// Set the tier-scoped stats handle. Overrides any value from the [`Server`] builder.
255	pub fn with_stats(mut self, stats: StatsHandle) -> Self {
256		self.server.stats = stats;
257		self
258	}
259
260	/// Accept the session, completing the handshake.
261	pub async fn ok(self) -> Result<Session, Error> {
262		let server = self.server;
263
264		// Warn here, not in `accept_request`: callers attach origins on the Request
265		// (after inspecting the path), so checking earlier gives false positives.
266		if server.publish.is_none() && server.consume.is_none() {
267			tracing::warn!("not publishing or consuming anything");
268		}
269
270		let (session, mut stream, version, request_id_max) = match self.handshake {
271			Handshake::IetfModern { session, version } => {
272				ietf::start(
273					session.clone(),
274					None,
275					None,
276					false,
277					server.publish,
278					server.consume,
279					server.stats,
280					version,
281				)?;
282				tracing::debug!(?version, "connected");
283				return Ok(Session::new(session, version.into(), None));
284			}
285			Handshake::LiteBare { session, version } => {
286				let recv_bw = lite::start(
287					session.clone(),
288					None,
289					server.publish,
290					server.consume,
291					server.stats,
292					version,
293					lite::Setup::default(),
294				)?;
295				return Ok(Session::new(session, version.into(), recv_bw));
296			}
297			Handshake::Lite05 { session } => {
298				// A server never advertises a request path.
299				let recv_bw = lite::start(
300					session.clone(),
301					None,
302					server.publish,
303					server.consume,
304					server.stats,
305					lite::Version::Lite05Wip,
306					lite::Setup::default(),
307				)?;
308				return Ok(Session::new(session, lite::Version::Lite05Wip.into(), recv_bw));
309			}
310			Handshake::Legacy {
311				session,
312				stream,
313				version,
314				request_id_max,
315			} => (session, stream, version, request_id_max),
316		};
317
318		// Encode parameters using the version-appropriate type.
319		let parameters = match version {
320			Version::Ietf(v) => {
321				let mut parameters = ietf::Parameters::default();
322				parameters.set_varint(ietf::ParameterVarInt::MaxRequestId, u32::MAX as u64);
323				parameters.set_bytes(ietf::ParameterBytes::Implementation, b"moq-lite-rs".to_vec());
324				parameters.encode_bytes(v)?
325			}
326			Version::Lite(v) => lite::Parameters::default().encode_bytes(v)?,
327		};
328
329		let server_setup = setup::Server {
330			version: version.into(),
331			parameters,
332		};
333		stream.writer.encode(&server_setup).await?;
334
335		let recv_bw = match version {
336			Version::Lite(v) => {
337				let stream = stream.with_version(v);
338				// Pre-lite-05: no Setup stream, so nothing to advertise.
339				lite::start(
340					session.clone(),
341					Some(stream),
342					server.publish,
343					server.consume,
344					server.stats,
345					v,
346					lite::Setup::default(),
347				)?
348			}
349			Version::Ietf(v) => {
350				let stream = stream.with_version(v);
351				ietf::start(
352					session.clone(),
353					Some(stream),
354					request_id_max,
355					false,
356					server.publish,
357					server.consume,
358					server.stats,
359					v,
360				)?;
361				None
362			}
363		};
364
365		Ok(Session::new(session, version, recv_bw))
366	}
367
368	/// Reject the session, closing the transport with `err`'s wire code.
369	pub fn close(self, err: Error) {
370		let session = match self.handshake {
371			Handshake::IetfModern { session, .. } => session,
372			Handshake::LiteBare { session, .. } => session,
373			Handshake::Lite05 { session } => session,
374			Handshake::Legacy { session, .. } => session,
375		};
376		session.close(err.to_code(), &err.to_string());
377	}
378}
379
380#[cfg(test)]
381mod tests {
382	use super::*;
383	use std::{
384		collections::VecDeque,
385		sync::{Arc, Mutex},
386	};
387
388	use bytes::Bytes;
389
390	#[derive(Debug, Clone, Default)]
391	struct FakeError;
392	impl std::fmt::Display for FakeError {
393		fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394			write!(f, "fake transport error")
395		}
396	}
397	impl std::error::Error for FakeError {}
398	impl web_transport_trait::Error for FakeError {
399		fn session_error(&self) -> Option<(u32, String)> {
400			Some((0, "closed".to_string()))
401		}
402	}
403
404	/// A session that replays a queue of unidirectional streams (each a `Vec<u8>`) in
405	/// order from `accept_uni`; everything else is inert.
406	#[derive(Clone)]
407	struct FakeSession {
408		protocol: Option<&'static str>,
409		uni: Arc<Mutex<VecDeque<Vec<u8>>>>,
410	}
411
412	impl FakeSession {
413		fn new(protocol: &'static str, uni: impl IntoIterator<Item = Vec<u8>>) -> Self {
414			Self {
415				protocol: Some(protocol),
416				uni: Arc::new(Mutex::new(uni.into_iter().collect())),
417			}
418		}
419	}
420
421	impl web_transport_trait::Session for FakeSession {
422		type SendStream = FakeSend;
423		type RecvStream = FakeRecv;
424		type Error = FakeError;
425
426		async fn accept_uni(&self) -> Result<Self::RecvStream, Self::Error> {
427			// Drop the guard before any await so the future stays Send.
428			let data = self.uni.lock().unwrap().pop_front();
429			match data {
430				Some(data) => Ok(FakeRecv { data: data.into() }),
431				None => std::future::pending().await,
432			}
433		}
434		async fn accept_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
435			std::future::pending().await
436		}
437		async fn open_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
438			std::future::pending().await
439		}
440		async fn open_uni(&self) -> Result<Self::SendStream, Self::Error> {
441			std::future::pending().await
442		}
443		fn send_datagram(&self, _payload: Bytes) -> Result<(), Self::Error> {
444			Ok(())
445		}
446		async fn recv_datagram(&self) -> Result<Bytes, Self::Error> {
447			std::future::pending().await
448		}
449		fn max_datagram_size(&self) -> usize {
450			1200
451		}
452		fn protocol(&self) -> Option<&str> {
453			self.protocol
454		}
455		fn close(&self, _code: u32, _reason: &str) {}
456		async fn closed(&self) -> Self::Error {
457			std::future::pending().await
458		}
459	}
460
461	#[derive(Clone, Default)]
462	struct FakeSend;
463	impl web_transport_trait::SendStream for FakeSend {
464		type Error = FakeError;
465		async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
466			Ok(buf.len())
467		}
468		fn set_priority(&mut self, _order: u8) {}
469		fn finish(&mut self) -> Result<(), Self::Error> {
470			Ok(())
471		}
472		fn reset(&mut self, _code: u32) {}
473		async fn closed(&mut self) -> Result<(), Self::Error> {
474			Ok(())
475		}
476	}
477
478	struct FakeRecv {
479		data: VecDeque<u8>,
480	}
481	impl web_transport_trait::RecvStream for FakeRecv {
482		type Error = FakeError;
483		async fn read(&mut self, dst: &mut [u8]) -> Result<Option<usize>, Self::Error> {
484			if self.data.is_empty() {
485				return Ok(None);
486			}
487			let size = dst.len().min(self.data.len());
488			for slot in dst.iter_mut().take(size) {
489				*slot = self.data.pop_front().unwrap();
490			}
491			Ok(Some(size))
492		}
493		fn stop(&mut self, _code: u32) {}
494		async fn closed(&mut self) -> Result<(), Self::Error> {
495			Ok(())
496		}
497	}
498
499	/// Encode a lite-05 Setup stream: the `DataType::Setup` tag then the SETUP message.
500	fn lite05_setup(path: Option<&str>) -> Vec<u8> {
501		let v = lite::Version::Lite05Wip;
502		let mut buf = Vec::new();
503		lite::DataType::Setup.encode(&mut buf, v).unwrap();
504		lite::Setup {
505			path: path.map(str::to_string),
506		}
507		.encode(&mut buf, v)
508		.unwrap();
509		buf
510	}
511
512	/// Encode a lite-05 Group uni stream header (just the `DataType::Group` tag).
513	fn lite05_group() -> Vec<u8> {
514		let mut buf = Vec::new();
515		lite::DataType::Group
516			.encode(&mut buf, lite::Version::Lite05Wip)
517			.unwrap();
518		buf
519	}
520
521	#[tokio::test(start_paused = true)]
522	async fn accept_request_reads_lite05_path() {
523		let session = FakeSession::new(ALPN_LITE_05_WIP, [lite05_setup(Some("/team/room"))]);
524		let request = Server::new()
525			.with_versions(Version::Lite(lite::Version::Lite05Wip).into())
526			.accept_request(session)
527			.await
528			.unwrap();
529		assert_eq!(request.path(), Some("/team/room"));
530	}
531
532	#[tokio::test(start_paused = true)]
533	async fn accept_request_lite05_without_path_is_none() {
534		let session = FakeSession::new(ALPN_LITE_05_WIP, [lite05_setup(None)]);
535		let request = Server::new()
536			.with_versions(Version::Lite(lite::Version::Lite05Wip).into())
537			.accept_request(session)
538			.await
539			.unwrap();
540		assert_eq!(request.path(), None);
541	}
542
543	#[tokio::test(start_paused = true)]
544	async fn accept_request_skips_uni_stream_before_setup() {
545		// A Group racing ahead of the SETUP is reset and skipped; the gate keeps
546		// reading until it finds the SETUP.
547		let session = FakeSession::new(ALPN_LITE_05_WIP, [lite05_group(), lite05_setup(Some("/team/room"))]);
548		let request = Server::new()
549			.with_versions(Version::Lite(lite::Version::Lite05Wip).into())
550			.accept_request(session)
551			.await
552			.unwrap();
553		assert_eq!(request.path(), Some("/team/room"));
554	}
555}