Skip to main content

moq_net/
server.rs

1//! Accepting a MoQ session, including the paused handshake that inspects the
2//! peer's SETUP before granting origins.
3
4use web_transport_trait::{MaybeSend, MaybeSync};
5
6use crate::origin;
7use crate::time::{Clock, Instant};
8use crate::{
9	ALPN_14, ALPN_15, ALPN_16, ALPN_17, ALPN_18, ALPN_19, ALPN_20, ALPN_21, ALPN_22, ALPN_LITE, ALPN_LITE_03,
10	ALPN_LITE_04, ALPN_LITE_05, ALPN_LITE_06, ALPN_LITE_07_WIP, Consume, Error, NEGOTIATED, Role, Session,
11	SessionError, Version, Versions,
12	coding::{Decode, Encode, Stream},
13	ietf, lite, setup, stats,
14};
15
16/// A MoQ server session builder.
17#[derive(Default, Clone)]
18pub struct Server {
19	publish: Option<origin::Consumer>,
20	subscribe: Option<origin::Producer>,
21	stats: stats::Session,
22	versions: Versions,
23}
24
25impl Server {
26	/// A server that neither publishes nor subscribes until configured.
27	pub fn new() -> Self {
28		Default::default()
29	}
30
31	/// Publish to the connected client: the session reads from the given origin
32	/// (pass an [`origin::Producer`] or [`origin::Consumer`] by reference) and forwards
33	/// its announcements. Omit to publish nothing. Pre-scoped via
34	/// [`origin::Producer::scope`] for token-gated relays.
35	pub fn with_publisher(mut self, publish: impl Consume<origin::Consumer>) -> Self {
36		self.publish = Some(publish.consume());
37		self
38	}
39
40	/// Subscribe to the connected client: the session writes the broadcasts the
41	/// client announces into this [`origin::Producer`]. Omit to subscribe to nothing.
42	pub fn with_subscriber(mut self, subscribe: origin::Producer) -> Self {
43		self.subscribe = Some(subscribe);
44		self
45	}
46
47	/// Attach a per-connection [`stats::Session`] context. The session's publish
48	/// (egress) and subscribe (ingress) origin handles are tagged with it, so all
49	/// traffic counters are attributed through the model for this session's lifetime.
50	/// Pass [`stats::Session::default`] (a no-op context) to opt out.
51	pub fn with_stats(mut self, stats: stats::Session) -> Self {
52		self.stats = stats;
53		self
54	}
55
56	/// Set both publish and subscribe from one shared [`origin::Producer`].
57	pub fn with_origin(self, origin: origin::Producer) -> Self {
58		self.with_publisher(&origin).with_subscriber(origin)
59	}
60
61	/// Restrict which protocol versions to accept, in preference order.
62	/// Defaults to every version this crate supports.
63	pub fn with_versions(mut self, versions: Versions) -> Self {
64		self.versions = versions;
65		self
66	}
67
68	/// The configured origin pair, each tagged with the stats context so the
69	/// model attributes reads (egress) and writes (ingress) for this session.
70	/// One shared context across both halves keeps presence and viewer counts
71	/// from double-attributing.
72	fn stat_tagged_origins(&self) -> (Option<origin::Consumer>, Option<origin::Producer>) {
73		let publish = self.publish.clone().map(|origin| origin.with_stats(self.stats.clone()));
74		let subscribe = self
75			.subscribe
76			.clone()
77			.map(|origin| origin.with_stats(self.stats.clone()));
78		(publish, subscribe)
79	}
80
81	/// Start a lite session on an accepted transport: wire the origins, answer
82	/// with our SETUP, and return the session and its driver.
83	fn start_lite<S>(
84		&self,
85		runtime: Clock,
86		session: S,
87		version: lite::Version,
88		client_setup: Option<lite::Setup>,
89		peer_hop: Option<crate::Hop>,
90	) -> Result<(Session, crate::Driver<S>), Error>
91	where
92		S: crate::transport::poll::Session,
93	{
94		let (publish, subscribe) = self.stat_tagged_origins();
95
96		// We report what the transport actually measures; a server never
97		// advertises a request Path or Role, and only the dialing side prices a
98		// link. Versions without a Setup Stream have nothing to advertise.
99		let our_setup = if version.has_setup_stream() {
100			lite::Setup {
101				probe: lite::ProbeLevel::detect(&session),
102				path: None,
103				role: None,
104				cost: None,
105				// Filled by `lite::start` from the attached origin handles.
106				hop: None,
107			}
108		} else {
109			lite::Setup::default()
110		};
111
112		let start = lite::start(lite::Config {
113			runtime: runtime.clone(),
114			stats: self.stats.clone(),
115			session: session.clone(),
116			setup_stream: None,
117			publish,
118			subscribe,
119			peer_hop,
120			version,
121			our_setup,
122			peer_setup: client_setup,
123		})?;
124
125		Ok(Session::new(
126			runtime,
127			session,
128			version.into(),
129			start.recv_bandwidth,
130			crate::driver::Protocol::Lite(Box::new(start.driver)),
131			start.goaway,
132			Some(start.estimate),
133		))
134	}
135
136	/// Perform the MoQ handshake for moq-lite only, over any transport.
137	///
138	/// Same trade as [`Client::connect_lite`](crate::Client::connect_lite): no
139	/// thread-affinity bound on the transport, so a pinned `!Send` transport
140	/// works, and only a moq-lite ALPN is accepted (anything else is refused
141	/// with [`Error::Version`]). Completes the handshake immediately; a caller
142	/// gating on the advertised path uses
143	/// [`accept_request_lite`](Self::accept_request_lite) instead.
144	pub async fn accept_lite<S>(&self, now: Instant, session: S) -> Result<(Session, crate::Driver<S>), Error>
145	where
146		S: crate::transport::poll::Session,
147	{
148		self.accept_request_lite(now, session).await?.ok().await
149	}
150
151	/// Begin the moq-lite handshake, pausing like
152	/// [`accept_request`](Self::accept_request) but for moq-lite ALPNs only,
153	/// which is what drops the thread-affinity bounds: a pinned `!Send`
154	/// transport can gate on the advertised path too. Anything but a moq-lite
155	/// ALPN is refused with [`Error::Version`].
156	pub async fn accept_request_lite<S>(&self, now: Instant, session: S) -> Result<Handshake<S>, Error>
157	where
158		S: crate::transport::poll::Session,
159	{
160		let mut refused = session.clone();
161		self.handshake_lite(now, session)
162			.await
163			.inspect_err(|err| close(&mut refused, err))
164	}
165
166	async fn handshake_lite<S>(&self, now: Instant, mut session: S) -> Result<Handshake<S>, Error>
167	where
168		S: crate::transport::poll::Session,
169	{
170		let runtime = Clock::new(now);
171		let (path, role, origin, handshake) = match session.protocol() {
172			Some(alpn @ (ALPN_LITE_05 | ALPN_LITE_06 | ALPN_LITE_07_WIP)) => {
173				let version = match alpn {
174					ALPN_LITE_07_WIP => lite::Version::Lite07,
175					ALPN_LITE_06 => lite::Version::Lite06,
176					_ => lite::Version::Lite05,
177				};
178				self.versions.select(Version::Lite(version)).ok_or(Error::Version)?;
179				// Gate on the client's SETUP: read it before serving so the
180				// caller can scope by the advertised path. Seeded back into
181				// `start` on `ok()` so PROBE gating resolves without
182				// re-reading the (consumed) Setup Stream.
183				let (client_setup, client_setup_size) = lite::accept_setup(&mut session, version).await?;
184				(
185					client_setup.path.clone(),
186					client_setup.role,
187					client_setup.hop,
188					PausedHandshake::LiteSetup {
189						session,
190						version,
191						client_setup,
192						client_setup_size,
193					},
194				)
195			}
196			Some(ALPN_LITE_04) => {
197				self.versions
198					.select(Version::Lite(lite::Version::Lite04))
199					.ok_or(Error::Version)?;
200				(
201					None,
202					None,
203					None,
204					PausedHandshake::LiteBare {
205						session,
206						version: lite::Version::Lite04,
207					},
208				)
209			}
210			Some(ALPN_LITE_03) => {
211				self.versions
212					.select(Version::Lite(lite::Version::Lite03))
213					.ok_or(Error::Version)?;
214				(
215					None,
216					None,
217					None,
218					PausedHandshake::LiteBare {
219						session,
220						version: lite::Version::Lite03,
221					},
222				)
223			}
224			_ => return Err(Error::Version),
225		};
226
227		Ok(Handshake {
228			path,
229			role,
230			origin,
231			// moq-lite carries no SETUP token.
232			token: None,
233			assigned_hop: crate::Hop::random(),
234			inner: Some(RequestInner {
235				server: self.clone(),
236				runtime,
237				handshake,
238			}),
239		})
240	}
241
242	/// Perform the MoQ handshake as a server, returning the [`Session`] and its [`Driver`](crate::Driver).
243	///
244	/// Poll the returned driver with nondecreasing time, starting at `now`.
245	///
246	/// Convenience wrapper over [`accept_request`](Self::accept_request) that
247	/// completes the handshake immediately. Use `accept_request` when you need to
248	/// inspect the client's advertised path before deciding what to serve.
249	pub async fn accept<S>(&self, now: Instant, session: S) -> Result<(Session, crate::Driver<S>), Error>
250	where
251		S: crate::transport::poll::Boxable,
252		S::SendStream: MaybeSync,
253		S::RecvStream: MaybeSync,
254	{
255		self.accept_request(now, session).await?.ok().await
256	}
257
258	/// Begin the MoQ handshake, pausing once the client's request path is known so
259	/// the caller can authorize/scope before serving.
260	///
261	/// Reads the client's SETUP (the in-band path lives there on URL-less transports),
262	/// then returns a [`Handshake`]: inspect [`path`](Handshake::path), set the origins to
263	/// serve, and call [`ok`](Handshake::ok) or [`close`](Handshake::close). Session start
264	/// is deferred to `ok()`, so origins set on the handshake always take effect.
265	///
266	/// The path is surfaced for moq-lite-05 and newer, and every moq-transport
267	/// draft we speak; it's empty on versions with no in-band request path (lite 01-04).
268	///
269	/// A SETUP that fails to parse or negotiate closes the session with the matching code,
270	/// so the peer learns why instead of seeing a bare disconnect.
271	pub async fn accept_request<S>(&self, now: Instant, session: S) -> Result<Handshake<S>, Error>
272	where
273		S: crate::transport::poll::Boxable,
274		S::SendStream: MaybeSync,
275		S::RecvStream: MaybeSync,
276	{
277		let mut refused = session.clone();
278		self.handshake(now, session)
279			.await
280			.inspect_err(|err| close(&mut refused, err))
281	}
282
283	async fn handshake<S>(&self, now: Instant, mut session: S) -> Result<Handshake<S>, Error>
284	where
285		S: crate::transport::poll::Boxable,
286		S::SendStream: MaybeSync,
287		S::RecvStream: MaybeSync,
288	{
289		let runtime = Clock::new(now);
290		let (encoding, supported) = match session.protocol() {
291			Some(alpn @ (ALPN_22 | ALPN_21 | ALPN_20 | ALPN_19 | ALPN_18 | ALPN_17)) => {
292				let draft = match alpn {
293					ALPN_22 => ietf::Version::Draft22,
294					ALPN_21 => ietf::Version::Draft21,
295					ALPN_20 => ietf::Version::Draft20,
296					ALPN_19 => ietf::Version::Draft19,
297					ALPN_18 => ietf::Version::Draft18,
298					_ => ietf::Version::Draft17,
299				};
300
301				self.versions.select(Version::Ietf(draft)).ok_or(Error::Version)?;
302				return self.accept_ietf_modern(runtime, session, draft).await;
303			}
304			Some(ALPN_16) => {
305				let v = self
306					.versions
307					.select(Version::Ietf(ietf::Version::Draft16))
308					.ok_or(Error::Version)?;
309				(v, v.into())
310			}
311			Some(ALPN_15) => {
312				let v = self
313					.versions
314					.select(Version::Ietf(ietf::Version::Draft15))
315					.ok_or(Error::Version)?;
316				(v, v.into())
317			}
318			Some(ALPN_14) => {
319				let v = self
320					.versions
321					.select(Version::Ietf(ietf::Version::Draft14))
322					.ok_or(Error::Version)?;
323				(v, v.into())
324			}
325			// Every lite ALPN goes through the same entry point, which is also
326			// what a `!Send` transport calls directly.
327			Some(ALPN_LITE_07_WIP | ALPN_LITE_06 | ALPN_LITE_05 | ALPN_LITE_04 | ALPN_LITE_03) => {
328				return self.handshake_lite(now, session).await;
329			}
330			Some(ALPN_LITE) | None => {
331				let supported = self.versions.filter(&NEGOTIATED.into()).ok_or(Error::Version)?;
332				(Version::Ietf(ietf::Version::Draft14), supported)
333			}
334			Some(p) => return Err(Error::UnknownAlpn(p.to_string())),
335		};
336
337		// Legacy bidi SETUP exchange (IETF 14-16, lite 01/02). Read the client's
338		// SETUP to choose the version; `ok()` sends the server SETUP and starts.
339		// Stats attach in `ok()`: a caller may still replace them on the handshake.
340		let mut stream = Stream::accept(&mut session, encoding).await?;
341		let (mut client, client_setup_size) = stream.reader.decode_sized::<setup::Client>().await?;
342
343		let version = client
344			.versions
345			.iter()
346			.flat_map(|v| Version::try_from(*v).ok())
347			.find(|v| supported.contains(v))
348			.ok_or(Error::Version)?;
349
350		// Pull the request path and max request ID out now (IETF only) so `ok()`
351		// doesn't re-decode the consumed parameters. moq-transport carries the path
352		// in its SETUP just like lite-05.
353		let (path, token, request_id_max, peer_declared) = match version {
354			Version::Ietf(v) => {
355				let params = ietf::Parameters::decode(&mut client.parameters, v)?;
356				let path = match params.get_bytes(ietf::ParameterBytes::Path) {
357					Some(bytes) => Some(
358						std::str::from_utf8(bytes)
359							.map_err(|_| Error::Decode(crate::DecodeError::InvalidValue))?
360							.to_owned(),
361					),
362					None => None,
363				};
364				let token = ietf::token::from_setup(&params, v)?;
365				let request_id_max = params
366					.get_varint(ietf::ParameterVarInt::MaxRequestId)
367					.map(ietf::RequestId);
368				let peer_declared = ietf::peer::Peer {
369					solicit: ietf::solicit::from_setup(&params, v)?,
370					hidden: ietf::hidden::from_setup(&params, v),
371					..Default::default()
372				};
373				(path, token, request_id_max, peer_declared)
374			}
375			Version::Lite(_) => (None, None, None, ietf::peer::Peer::default()),
376		};
377
378		Ok(Handshake {
379			path,
380			role: None,
381			origin: None,
382			token,
383			assigned_hop: crate::Hop::random(),
384			inner: Some(RequestInner {
385				server: self.clone(),
386				runtime,
387				handshake: PausedHandshake::Boxed(Box::new(PausedLegacy {
388					session,
389					stream,
390					version,
391					request_id_max,
392					peer_declared,
393					client_setup_size,
394				})),
395			}),
396		})
397	}
398
399	/// Read a draft-17/18 client's SETUP (with its request path) off its uni stream,
400	/// then pause. `ok()` starts the session and hands the stream back for GOAWAY.
401	async fn accept_ietf_modern<S>(
402		&self,
403		runtime: Clock,
404		mut session: S,
405		version: ietf::Version,
406	) -> Result<Handshake<S>, Error>
407	where
408		S: crate::transport::poll::Boxable,
409		S::SendStream: MaybeSync,
410		S::RecvStream: MaybeSync,
411	{
412		let peer_setup = ietf::accept_setup(&mut session, version).await?;
413		Ok(Handshake {
414			path: peer_setup.path.clone(),
415			role: None,
416			// A moq-transport peer only has an identity if it negotiated the MoQ
417			// Cluster extension and declared a non-zero Hop ID.
418			origin: peer_setup.declared.cluster.hop.filter(|h| *h != crate::Hop::UNKNOWN),
419			token: peer_setup.token.clone(),
420			assigned_hop: crate::Hop::random(),
421			inner: Some(RequestInner {
422				server: self.clone(),
423				runtime,
424				handshake: PausedHandshake::Boxed(Box::new(PausedIetfModern {
425					session,
426					version,
427					peer_setup,
428				})),
429			}),
430		})
431	}
432}
433
434/// A paused server-side handshake.
435///
436/// Returned by [`Server::accept_request`] once the peer's advertised
437/// [`path`](Self::path) is known but before the session is granted anything. Set
438/// the origins to serve, then call [`ok`](Self::ok) to complete the handshake, or
439/// [`close`](Self::close) to reject it. Modeled on the WebTransport `Request` in
440/// moq-tokio.
441pub struct Handshake<S: crate::transport::poll::Session> {
442	path: Option<String>,
443	role: Option<Role>,
444	origin: Option<crate::Hop>,
445	token: Option<setup::Token>,
446	/// The identity this session's routes are stamped with when the peer declares none
447	/// on the wire. Fresh per request unless the caller overrides it
448	/// ([`Handshake::with_peer_hop`]).
449	assigned_hop: crate::Hop,
450	// Taken by `ok`/`close`; `Drop` rejects the handshake if neither ran.
451	inner: Option<RequestInner<S>>,
452}
453
454/// The parts of a [`Handshake`] consumed by [`Handshake::ok`] / [`Handshake::close`].
455struct RequestInner<S: crate::transport::poll::Session> {
456	server: Server,
457	/// Supplies the clock and timers for the accepted session.
458	runtime: Clock,
459	handshake: PausedHandshake<S>,
460}
461
462/// The handshake state captured at the pause point. Every variant defers its
463/// session start to [`Handshake::ok`] so origins set on the handshake still apply.
464enum PausedHandshake<S: crate::transport::poll::Session> {
465	/// moq-lite 03/04: no Setup Stream.
466	LiteBare { session: S, version: lite::Version },
467	/// moq-lite 05+: the client's Setup Stream has been read. `ok()` starts the
468	/// session, seeding the SETUP back so PROBE gating resolves.
469	LiteSetup {
470		session: S,
471		version: lite::Version,
472		client_setup: lite::Setup,
473		/// The client SETUP's encoded size, counted under the final stats.
474		client_setup_size: usize,
475	},
476	/// An IETF (or legacy bidi-SETUP) handshake, boxed where its
477	/// thread-affinity bounds held. The boxing is what keeps [`Handshake`] and
478	/// its lite path free of those bounds: the ietf machinery erases its
479	/// futures, which forces a per-target `Send` choice a pinned `!Send`
480	/// transport cannot satisfy, so the choice is made here, at construction,
481	/// where the caller proved the bounds.
482	Boxed(Box<dyn Paused<S>>),
483}
484
485type Accept<S> = crate::util::MaybeSendBox<'static, Result<(Session, crate::Driver<S>), Error>>;
486
487/// A paused non-lite handshake. See [`PausedHandshake::Boxed`] for why this is a
488/// trait object.
489///
490/// `MaybeSync` is not decoration: a caller holding a [`Handshake`] across an
491/// await behind `&self` (moq-relay authenticates that way) needs
492/// `&Handshake: Send`, which is `Handshake: Sync`, which is this.
493trait Paused<S: crate::transport::poll::Session>: MaybeSend + MaybeSync {
494	/// Complete the handshake with the final server config.
495	fn ok(self: Box<Self>, server: Server, runtime: Clock, peer_hop: Option<crate::Hop>) -> Accept<S>;
496
497	/// Reject the handshake, closing the transport with `err`'s wire code.
498	fn close(self: Box<Self>, err: Error);
499}
500
501/// Modern IETF (17/18): the client's SETUP (with its request path) has been
502/// read off its uni stream; `ok` starts the session, handing that stream back
503/// for GOAWAY monitoring.
504struct PausedIetfModern<S: crate::transport::poll::Session> {
505	session: S,
506	version: ietf::Version,
507	peer_setup: ietf::PeerSetup<S>,
508}
509
510impl<S> Paused<S> for PausedIetfModern<S>
511where
512	S: crate::transport::poll::Boxable,
513	S::SendStream: MaybeSync,
514	S::RecvStream: MaybeSync,
515{
516	fn ok(self: Box<Self>, server: Server, runtime: Clock, peer_hop: Option<crate::Hop>) -> Accept<S> {
517		use crate::util::MaybeBoxedExt as _;
518		async move {
519			let Self {
520				session,
521				version,
522				peer_setup,
523			} = *self;
524			server.stats.control(stats::Control::Session, peer_setup.size, true);
525			let (publish, subscribe) = server.stat_tagged_origins();
526
527			// The client's SETUP was read at the pause; hand the stream back
528			// for GOAWAY. A server never advertises a path, hence `None`.
529			let (protocol, goaway) = ietf::start(ietf::Config {
530				runtime: runtime.clone(),
531				stats: server.stats.clone(),
532				session: session.clone(),
533				setup: None,
534				request_id_max: None,
535				client: false,
536				publish,
537				subscribe,
538				peer_hop,
539				// Only the dialing side prices a link.
540				cost: None,
541				version,
542				path: None,
543				authority: None,
544				peer_setup_stream: Some(peer_setup.stream),
545				peer_declared: Some(peer_setup.declared),
546				early_unis: peer_setup.early,
547			})?;
548			tracing::debug!(?version, "connected");
549			Ok(Session::new(
550				runtime,
551				session,
552				version.into(),
553				None,
554				crate::driver::Protocol::Ietf(protocol),
555				goaway,
556				None,
557			))
558		}
559		.maybe_boxed()
560	}
561
562	fn close(mut self: Box<Self>, err: Error) {
563		close(&mut self.session, &err);
564	}
565}
566
567/// Legacy IETF (draft 14-16) and lite 01/02: the client SETUP has been read
568/// off the bidi stream (including its request path) but the server SETUP
569/// hasn't been sent; `ok` finishes it.
570struct PausedLegacy<S: crate::transport::poll::Session> {
571	session: S,
572	stream: Stream<S, Version>,
573	version: Version,
574	request_id_max: Option<ietf::RequestId>,
575	/// What the client's SETUP declared, for the options `ok` acts on.
576	peer_declared: ietf::peer::Peer,
577	/// The client SETUP's encoded size, counted under the final stats.
578	client_setup_size: usize,
579}
580
581impl<S> Paused<S> for PausedLegacy<S>
582where
583	S: crate::transport::poll::Boxable,
584	S::SendStream: MaybeSync,
585	S::RecvStream: MaybeSync,
586{
587	fn ok(self: Box<Self>, server: Server, runtime: Clock, peer_hop: Option<crate::Hop>) -> Accept<S> {
588		use crate::util::MaybeBoxedExt as _;
589		async move {
590			let Self {
591				session,
592				stream,
593				version,
594				request_id_max,
595				peer_declared,
596				client_setup_size,
597			} = *self;
598			let mut stream = stream.with_stats(&server.stats);
599			server.stats.control(stats::Control::Session, client_setup_size, true);
600			let (publish, subscribe) = server.stat_tagged_origins();
601
602			// Encode parameters using the version-appropriate type.
603			let parameters = match version {
604				Version::Ietf(v) => {
605					let mut parameters = ietf::Parameters::default();
606					parameters.set_varint(ietf::ParameterVarInt::MaxRequestId, u32::MAX as u64);
607					parameters.set_bytes(ietf::ParameterBytes::Implementation, b"moq-lite-rs".to_vec());
608					ietf::solicit::into_setup(&mut parameters, v);
609					ietf::hidden::into_setup(&mut parameters, v);
610					parameters.encode_bytes(v)?
611				}
612				Version::Lite(v) => lite::Parameters::default().encode_bytes(v)?,
613			};
614
615			let server_setup = setup::Server {
616				version: version.into(),
617				parameters,
618			};
619			stream.writer.encode(&server_setup).await?;
620
621			let (recv_bw, protocol, goaway, estimate) = match version {
622				Version::Lite(v) => {
623					let stream = stream.with_version(v);
624					// Pre-lite-05: no Setup Stream, so nothing to advertise or seed.
625					let start = lite::start(lite::Config {
626						runtime: runtime.clone(),
627						stats: server.stats.clone(),
628						session: session.clone(),
629						setup_stream: Some(stream),
630						publish,
631						subscribe,
632						peer_hop,
633						version: v,
634						our_setup: lite::Setup::default(),
635						peer_setup: None,
636					})?;
637					(
638						start.recv_bandwidth,
639						crate::driver::Protocol::Lite(Box::new(start.driver)),
640						start.goaway,
641						Some(start.estimate),
642					)
643				}
644				Version::Ietf(v) => {
645					let stream = stream.with_version(v);
646					// Draft 14-16: path came in the bidi SETUP, no uni SETUP to hand back.
647					let (protocol, goaway) = ietf::start(ietf::Config {
648						runtime: runtime.clone(),
649						stats: server.stats.clone(),
650						session: session.clone(),
651						setup: Some(stream),
652						request_id_max,
653						client: false,
654						publish,
655						subscribe,
656						peer_hop,
657						cost: None,
658						version: v,
659						path: None,
660						authority: None,
661						peer_setup_stream: None,
662						peer_declared: Some(peer_declared),
663						early_unis: Vec::new(),
664					})?;
665					(None, crate::driver::Protocol::Ietf(protocol), goaway, None)
666				}
667			};
668
669			Ok(Session::new(
670				runtime, session, version, recv_bw, protocol, goaway, estimate,
671			))
672		}
673		.maybe_boxed()
674	}
675
676	fn close(mut self: Box<Self>, err: Error) {
677		close(&mut self.session, &err);
678	}
679}
680
681impl<S> Handshake<S>
682where
683	S: crate::transport::poll::Session,
684{
685	/// The request path the client advertised in its SETUP.
686	///
687	/// Empty when the client advertised none: either it sent an empty path, or the
688	/// version carries none in-band (lite 01-04). Those mean the same thing, so the
689	/// wire distinction isn't surfaced. Populated for moq-lite-05 and newer,
690	/// and every moq-transport draft we speak. See the note on [`Server::accept_request`].
691	pub fn path(&self) -> &str {
692		self.path.as_deref().unwrap_or("")
693	}
694
695	/// The single [`Role`] the client advertised in its SETUP, or `None` for a
696	/// bidirectional session.
697	///
698	/// Only moq-lite-05 and newer carry a role, so `None` covers three cases
699	/// that the wire doesn't distinguish: an older version, a client that omitted the parameter, and a
700	/// client that explicitly advertised both directions. All three mean the same thing
701	/// (the client may publish and subscribe), so authorize on what the token grants.
702	/// See the note on [`Server::accept_request`].
703	pub fn role(&self) -> Option<Role> {
704		self.role
705	}
706
707	/// The Hop ID declared by the peer, when the negotiated protocol carries one.
708	///
709	/// A moq-lite-05+ endpoint declares this when it attaches a publish or subscribe
710	/// origin; a `moqt-17`+ endpoint declares it via the MoQ Cluster extension. Older
711	/// versions and endpoints without one return `None`.
712	///
713	/// Self-declared, so treat it as a correlation hint rather than an
714	/// authenticated identity: authorize on the token or client certificate.
715	pub fn peer_hop(&self) -> Option<crate::Hop> {
716		self.origin
717	}
718
719	/// The credential the client presented in its SETUP's `AUTHORIZATION TOKEN` option.
720	///
721	/// Only moq-transport carries one, so moq-lite sessions return `None`. The transport
722	/// has not verified it: authorize on it the way you would a URL token.
723	pub fn token(&self) -> Option<&setup::Token> {
724		self.token.as_ref()
725	}
726
727	/// Publish to the connected client. Overrides any value from the [`Server`]
728	/// builder; typically set after inspecting [`path`](Self::path).
729	pub fn with_publisher(mut self, publish: impl Consume<origin::Consumer>) -> Self {
730		self.inner_mut().server.publish = Some(publish.consume());
731		self
732	}
733
734	/// Subscribe to the connected client. Overrides any value from the [`Server`] builder.
735	pub fn with_subscriber(mut self, subscribe: origin::Producer) -> Self {
736		self.inner_mut().server.subscribe = Some(subscribe);
737		self
738	}
739
740	/// Assign the identity this peer's routes are attributed to, overriding the fresh
741	/// per-session default.
742	///
743	/// Only for a peer whose identity the server has actually established, such as one
744	/// authenticated by mTLS or a token ([`crate::Client::with_peer_hop`] is the
745	/// dialing-side equivalent). An identity the peer declares on the wire still wins.
746	///
747	/// Two sessions given the same origin are treated as one endpoint: routes learned
748	/// from either are kept off both, and content arriving on either is interchangeable
749	/// with the other's. That is the point when they really are one peer reconnecting or
750	/// running redundant links, and a bug otherwise. Derive it from the authenticated
751	/// identity, never from something coarser like the remote address.
752	pub fn with_peer_hop(mut self, hop: crate::Hop) -> Self {
753		self.assigned_hop = hop;
754		self
755	}
756
757	/// Set the per-connection [`stats::Session`] context. Overrides any value from the
758	/// [`Server`] builder.
759	pub fn with_stats(mut self, stats: stats::Session) -> Self {
760		self.inner_mut().server.stats = stats;
761		self
762	}
763
764	fn inner_mut(&mut self) -> &mut RequestInner<S> {
765		self.inner.as_mut().expect("request already responded")
766	}
767
768	/// Accept the session, returning the [`Session`] and its [`Driver`](crate::Driver).
769	///
770	/// Poll or spawn the returned driver to run the session.
771	pub async fn ok(mut self) -> Result<(Session, crate::Driver<S>), Error> {
772		let peer_hop = Some(self.assigned_hop);
773		let RequestInner {
774			server,
775			runtime,
776			handshake,
777		} = self.inner.take().expect("request already responded");
778
779		match handshake {
780			PausedHandshake::LiteBare { session, version } => {
781				server.start_lite(runtime, session, version, None, peer_hop)
782			}
783			PausedHandshake::LiteSetup {
784				session,
785				version,
786				client_setup,
787				client_setup_size,
788			} => {
789				server.stats.control(stats::Control::Session, client_setup_size, true);
790				server.start_lite(runtime, session, version, Some(client_setup), peer_hop)
791			}
792			PausedHandshake::Boxed(paused) => paused.ok(server, runtime, peer_hop).await,
793		}
794	}
795
796	/// Reject the session, closing the transport with `err`'s wire code.
797	pub fn close(mut self, err: Error) {
798		let inner = self.inner.take().expect("request already responded");
799		inner.close(err);
800	}
801}
802
803impl<S: crate::transport::poll::Session> RequestInner<S> {
804	fn close(self, err: Error) {
805		let mut session = match self.handshake {
806			PausedHandshake::LiteBare { session, .. } => session,
807			PausedHandshake::LiteSetup { session, .. } => session,
808			PausedHandshake::Boxed(paused) => return paused.close(err),
809		};
810		close(&mut session, &err);
811	}
812}
813
814/// Close `session` with `err`'s wire code.
815fn close<S: crate::transport::poll::Session>(session: &mut S, err: &Error) {
816	session.close(SessionError::from(err).to_code(), &err.to_string());
817}
818
819impl<S: crate::transport::poll::Session> Drop for Handshake<S> {
820	// A dropped request would otherwise leave the client hanging until its idle
821	// timeout: it already sent SETUP and is waiting on a response. Reject loudly.
822	fn drop(&mut self) {
823		if let Some(inner) = self.inner.take() {
824			tracing::warn!("Handshake dropped without ok() or close(); rejecting the session");
825			inner.close(Error::Cancel);
826		}
827	}
828}
829
830#[cfg(test)]
831mod tests {
832	use super::*;
833	use crate::Hop;
834	use crate::model::ProduceTest;
835	use std::{
836		collections::VecDeque,
837		sync::{Arc, Mutex},
838	};
839
840	use crate::ALPN_LITE_05;
841	use bytes::Bytes;
842
843	fn occurrences(log: &crate::lite::test_transport::Log, needle: &[u8]) -> usize {
844		let writes = log.writes.lock().unwrap();
845		writes.windows(needle.len()).filter(|window| *window == needle).count()
846	}
847
848	#[derive(Debug, Clone, Default)]
849	struct FakeError;
850	impl std::fmt::Display for FakeError {
851		fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
852			write!(f, "fake transport error")
853		}
854	}
855	impl std::error::Error for FakeError {}
856	impl web_transport_trait::Error for FakeError {
857		fn session_error(&self) -> Option<(u32, String)> {
858			Some((0, "closed".to_string()))
859		}
860	}
861
862	/// A session that replays a queue of streams (each a `Vec<u8>`) in order from
863	/// `accept_uni` and `accept_bi`, and records the code it was closed with; everything
864	/// else is inert.
865	#[derive(Clone)]
866	struct FakeSession {
867		protocol: Option<&'static str>,
868		uni: Arc<Mutex<VecDeque<Vec<u8>>>>,
869		bi: Arc<Mutex<VecDeque<Vec<u8>>>>,
870		closed: Arc<Mutex<Option<u32>>>,
871	}
872
873	impl FakeSession {
874		fn new(protocol: &'static str, uni: impl IntoIterator<Item = Vec<u8>>) -> Self {
875			Self {
876				protocol: Some(protocol),
877				uni: Arc::new(Mutex::new(uni.into_iter().collect())),
878				bi: Default::default(),
879				closed: Default::default(),
880			}
881		}
882
883		fn with_bi(self, bi: Vec<u8>) -> Self {
884			self.bi.lock().unwrap().push_back(bi);
885			self
886		}
887
888		fn closed(&self) -> Option<u32> {
889			*self.closed.lock().unwrap()
890		}
891	}
892
893	impl web_transport_trait::poll::Session for FakeSession {
894		type SendStream = FakeSend;
895		type RecvStream = FakeRecv;
896		type Error = FakeError;
897
898		fn poll_accept_uni(
899			&mut self,
900			_cx: &mut std::task::Context<'_>,
901		) -> std::task::Poll<Result<Self::RecvStream, Self::Error>> {
902			match self.uni.lock().unwrap().pop_front() {
903				Some(data) => std::task::Poll::Ready(Ok(FakeRecv { data: data.into() })),
904				None => std::task::Poll::Pending,
905			}
906		}
907		fn poll_accept_bi(
908			&mut self,
909			_cx: &mut std::task::Context<'_>,
910		) -> std::task::Poll<Result<(Self::SendStream, Self::RecvStream), Self::Error>> {
911			match self.bi.lock().unwrap().pop_front() {
912				Some(data) => std::task::Poll::Ready(Ok((FakeSend, FakeRecv { data: data.into() }))),
913				None => std::task::Poll::Pending,
914			}
915		}
916		fn poll_open_bi(
917			&mut self,
918			_cx: &mut std::task::Context<'_>,
919		) -> std::task::Poll<Result<(Self::SendStream, Self::RecvStream), Self::Error>> {
920			std::task::Poll::Pending
921		}
922		fn poll_open_uni(
923			&mut self,
924			_cx: &mut std::task::Context<'_>,
925		) -> std::task::Poll<Result<Self::SendStream, Self::Error>> {
926			std::task::Poll::Pending
927		}
928		fn poll_send_datagram(
929			&mut self,
930			_cx: &mut std::task::Context<'_>,
931			_payload: &[u8],
932		) -> std::task::Poll<Result<(), Self::Error>> {
933			std::task::Poll::Ready(Ok(()))
934		}
935		fn poll_recv_datagram(
936			&mut self,
937			_cx: &mut std::task::Context<'_>,
938		) -> std::task::Poll<Result<Bytes, Self::Error>> {
939			std::task::Poll::Pending
940		}
941		fn max_datagram_size(&self) -> usize {
942			1200
943		}
944		fn protocol(&self) -> Option<&str> {
945			self.protocol
946		}
947		fn close(&mut self, code: u32, _reason: &str) {
948			self.closed.lock().unwrap().get_or_insert(code);
949		}
950		fn poll_closed(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Error> {
951			std::task::Poll::Pending
952		}
953		fn stats(&self) -> impl web_transport_trait::Stats {
954			web_transport_trait::StatsUnavailable
955		}
956	}
957
958	#[derive(Clone, Default)]
959	struct FakeSend;
960	impl web_transport_trait::poll::SendStream for FakeSend {
961		type Error = FakeError;
962		fn poll_write(
963			&mut self,
964			_cx: &mut std::task::Context<'_>,
965			buf: &[u8],
966		) -> std::task::Poll<Result<usize, Self::Error>> {
967			std::task::Poll::Ready(Ok(buf.len()))
968		}
969		fn set_priority(&mut self, _order: u8) {}
970		fn finish(&mut self) -> Result<(), Self::Error> {
971			Ok(())
972		}
973		fn reset(&mut self, _code: u32) {}
974		fn poll_closed(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
975			std::task::Poll::Ready(Ok(()))
976		}
977	}
978
979	struct FakeRecv {
980		data: VecDeque<u8>,
981	}
982	impl web_transport_trait::poll::RecvStream for FakeRecv {
983		type Error = FakeError;
984		fn poll_read(
985			&mut self,
986			_cx: &mut std::task::Context<'_>,
987			dst: &mut [u8],
988		) -> std::task::Poll<Result<Option<usize>, Self::Error>> {
989			if self.data.is_empty() {
990				return std::task::Poll::Ready(Ok(None));
991			}
992			let size = dst.len().min(self.data.len());
993			for slot in dst.iter_mut().take(size) {
994				*slot = self.data.pop_front().unwrap();
995			}
996			std::task::Poll::Ready(Ok(Some(size)))
997		}
998		fn stop(&mut self, _code: u32) {}
999		fn poll_closed(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
1000			std::task::Poll::Ready(Ok(()))
1001		}
1002	}
1003
1004	/// Encode a lite-05 Setup Stream: the `DataType::Setup` tag then the SETUP message.
1005	fn lite05_setup(path: Option<&str>, role: Option<Role>, hop: Option<Hop>) -> Vec<u8> {
1006		let v = lite::Version::Lite05;
1007		let mut buf = Vec::new();
1008		lite::DataType::Setup.encode(&mut buf, v).unwrap();
1009		lite::Setup {
1010			probe: lite::ProbeLevel::None,
1011			path: path.map(str::to_string),
1012			role,
1013			cost: None,
1014			hop,
1015		}
1016		.encode(&mut buf, v)
1017		.unwrap();
1018		buf
1019	}
1020
1021	/// Encode a draft-17+ Setup Stream: the unified SETUP message, whose parameters
1022	/// carry the request path the same way lite-05's does.
1023	fn ietf_setup(version: ietf::Version, path: Option<&str>) -> Vec<u8> {
1024		let mut params = ietf::Parameters::default();
1025		if let Some(path) = path {
1026			params.set_bytes(ietf::ParameterBytes::Path, path.as_bytes().to_vec());
1027		}
1028		ietf_setup_with(version, params)
1029	}
1030
1031	fn ietf_setup_with(version: ietf::Version, params: ietf::Parameters) -> Vec<u8> {
1032		let parameters = params.encode_bytes(version).unwrap();
1033
1034		let mut buf = Vec::new();
1035		setup::Setup { parameters }
1036			.encode(&mut buf, crate::Version::Ietf(version))
1037			.unwrap();
1038		buf
1039	}
1040
1041	/// Encode a draft 14-16 CLIENT_SETUP, sent on the control bidi stream.
1042	fn legacy_setup(version: ietf::Version, params: ietf::Parameters) -> Vec<u8> {
1043		let mut buf = Vec::new();
1044		setup::Client {
1045			versions: crate::coding::Versions::from([crate::Version::Ietf(version).into()]),
1046			parameters: params.encode_bytes(version).unwrap(),
1047		}
1048		.encode(&mut buf, crate::Version::Ietf(version))
1049		.unwrap();
1050		buf
1051	}
1052
1053	fn setup_token() -> setup::Token {
1054		setup::Token {
1055			kind: setup::Token::OUT_OF_BAND,
1056			value: vec![0x00, 0xff, b'j', b'w', b't'],
1057		}
1058	}
1059
1060	fn token_params(version: ietf::Version) -> ietf::Parameters {
1061		let mut params = ietf::Parameters::default();
1062		ietf::token::into_setup(&mut params, &setup_token(), version).unwrap();
1063		params
1064	}
1065
1066	#[tokio::test(start_paused = true)]
1067	async fn accept_request_exposes_the_setup_token() {
1068		let modern = FakeSession::new(
1069			ALPN_19,
1070			[ietf_setup_with(
1071				ietf::Version::Draft19,
1072				token_params(ietf::Version::Draft19),
1073			)],
1074		);
1075		let legacy = FakeSession::new(ALPN_16, []).with_bi(legacy_setup(
1076			ietf::Version::Draft16,
1077			token_params(ietf::Version::Draft16),
1078		));
1079		for (name, session) in [("draft-19", modern), ("draft-16", legacy)] {
1080			let request = Server::new()
1081				.accept_request(tokio::time::Instant::now().into_std(), session)
1082				.await
1083				.unwrap();
1084			assert_eq!(request.token(), Some(&setup_token()), "{name}");
1085		}
1086	}
1087
1088	#[tokio::test(start_paused = true)]
1089	async fn accept_request_without_a_token_reports_none() {
1090		let ietf = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, None)]);
1091		let lite = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, None)]);
1092		for (name, session) in [("draft-19", ietf), ("lite-05", lite)] {
1093			let request = Server::new()
1094				.accept_request(tokio::time::Instant::now().into_std(), session)
1095				.await
1096				.unwrap();
1097			assert_eq!(request.token(), None, "{name}");
1098		}
1099	}
1100
1101	/// A SETUP the server refuses closes the session with the code naming why, on both
1102	/// the draft-17+ uni stream and the draft 14-16 bidi stream.
1103	#[tokio::test(start_paused = true)]
1104	async fn a_refused_setup_token_closes_with_its_code() {
1105		let delete = [0x0, 0x7]; // DELETE alias 7
1106		let truncated = [0x3]; // USE_VALUE with no Token Type
1107		for (raw, code) in [
1108			(&delete[..], SessionError::ProtocolViolation),
1109			(&truncated[..], SessionError::KeyValueFormatting),
1110		] {
1111			let mut params = ietf::Parameters::default();
1112			params.set_bytes(ietf::ParameterBytes::AuthorizationToken, raw.to_vec());
1113
1114			let modern = FakeSession::new(ALPN_19, [ietf_setup_with(ietf::Version::Draft19, params.clone())]);
1115			let legacy = FakeSession::new(ALPN_16, []).with_bi(legacy_setup(ietf::Version::Draft16, params));
1116			for (name, session) in [("draft-19", modern), ("draft-16", legacy)] {
1117				let result = Server::new()
1118					.accept_request(tokio::time::Instant::now().into_std(), session.clone())
1119					.await;
1120				assert!(result.is_err(), "{name}");
1121				assert_eq!(session.closed(), Some(code.to_code()), "{name} {code}");
1122			}
1123		}
1124	}
1125
1126	#[tokio::test(start_paused = true)]
1127	async fn accept_request_reads_ietf_path() {
1128		// Every draft-17+ version gates on the SETUP stream before starting, so the
1129		// path is known at authorization time just like lite-05.
1130		for (alpn, version) in [
1131			(ALPN_17, ietf::Version::Draft17),
1132			(ALPN_18, ietf::Version::Draft18),
1133			(ALPN_19, ietf::Version::Draft19),
1134		] {
1135			let session = FakeSession::new(alpn, [ietf_setup(version, Some("/team/room"))]);
1136			let request = Server::new()
1137				.accept_request(tokio::time::Instant::now().into_std(), session)
1138				.await
1139				.unwrap();
1140			assert_eq!(request.path(), "/team/room", "{alpn}");
1141		}
1142	}
1143
1144	#[tokio::test(start_paused = true)]
1145	async fn accept_request_ietf_without_path_is_empty() {
1146		let session = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, None)]);
1147		let request = Server::new()
1148			.accept_request(tokio::time::Instant::now().into_std(), session)
1149			.await
1150			.unwrap();
1151		assert_eq!(request.path(), "");
1152	}
1153
1154	#[tokio::test(start_paused = true)]
1155	async fn accept_request_ietf_empty_path_is_accepted() {
1156		let session = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, Some(""))]);
1157		let request = Server::new()
1158			.accept_request(tokio::time::Instant::now().into_std(), session)
1159			.await
1160			.unwrap();
1161		assert_eq!(request.path(), "");
1162	}
1163
1164	/// Encode a lite-05 GROUP uni stream header (just the `DataType::Group` tag).
1165	fn lite05_group() -> Vec<u8> {
1166		let mut buf = Vec::new();
1167		lite::DataType::Group.encode(&mut buf, lite::Version::Lite05).unwrap();
1168		buf
1169	}
1170
1171	#[tokio::test(start_paused = true)]
1172	async fn accept_request_reads_lite05_path() {
1173		let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(Some("/team/room"), None, None)]);
1174		let request = Server::new()
1175			.accept_request(tokio::time::Instant::now().into_std(), session)
1176			.await
1177			.unwrap();
1178		assert_eq!(request.path(), "/team/room");
1179		assert_eq!(request.role(), None, "a client that omits the role is bidirectional");
1180	}
1181
1182	#[tokio::test(start_paused = true)]
1183	async fn accept_request_lite05_without_path_is_empty() {
1184		let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, None)]);
1185		let request = Server::new()
1186			.accept_request(tokio::time::Instant::now().into_std(), session)
1187			.await
1188			.unwrap();
1189		assert_eq!(request.path(), "");
1190	}
1191
1192	#[tokio::test(start_paused = true)]
1193	async fn accept_request_lite05_empty_path_is_accepted() {
1194		// An empty path is valid on the wire and means the same as omitting it, so a
1195		// client that wants the root doesn't have to special-case the parameter.
1196		let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(Some(""), None, None)]);
1197		let request = Server::new()
1198			.accept_request(tokio::time::Instant::now().into_std(), session)
1199			.await
1200			.unwrap();
1201		assert_eq!(request.path(), "");
1202	}
1203
1204	#[tokio::test(start_paused = true)]
1205	async fn repeated_lite_setup_parameter_closes_the_session() {
1206		// SETUP type, five-byte message, then two empty parameters with id 0.
1207		let session = FakeSession::new(ALPN_LITE_05, [vec![1, 5, 2, 0, 0, 0, 0]]);
1208		let result = Server::new()
1209			.accept_request(tokio::time::Instant::now().into_std(), session.clone())
1210			.await;
1211		assert!(result.is_err());
1212		assert_eq!(session.closed(), Some(SessionError::ProtocolViolation.to_code()));
1213	}
1214
1215	#[tokio::test(start_paused = true)]
1216	async fn accepted_lite_setup_refuses_a_second_setup_stream() {
1217		let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, None)]);
1218		let transport = session.clone();
1219		let request = Server::new()
1220			.accept_request(tokio::time::Instant::now().into_std(), session)
1221			.await
1222			.unwrap();
1223		let (_session, mut driver) = request.ok().await.unwrap();
1224		transport.uni.lock().unwrap().push_back(vec![1]);
1225		let _ = driver.poll(tokio::time::Instant::now().into_std(), &kio::Waiter::noop());
1226		assert_eq!(transport.closed(), Some(SessionError::ProtocolViolation.to_code()));
1227	}
1228
1229	#[tokio::test(start_paused = true)]
1230	async fn accept_request_reads_lite05_role() {
1231		let session = FakeSession::new(
1232			ALPN_LITE_05,
1233			[lite05_setup(Some("/team/room"), Some(Role::Publisher), None)],
1234		);
1235		let request = Server::new()
1236			.accept_request(tokio::time::Instant::now().into_std(), session)
1237			.await
1238			.unwrap();
1239		assert_eq!(request.role(), Some(Role::Publisher));
1240	}
1241
1242	#[tokio::test(start_paused = true)]
1243	async fn accept_request_skips_uni_stream_before_setup() {
1244		// A GROUP racing ahead of the SETUP is STOP_SENDING-ed and skipped; the gate
1245		// keeps reading until it finds the SETUP.
1246		let session = FakeSession::new(
1247			ALPN_LITE_05,
1248			[lite05_group(), lite05_setup(Some("/team/room"), None, None)],
1249		);
1250		let request = Server::new()
1251			.accept_request(tokio::time::Instant::now().into_std(), session)
1252			.await
1253			.unwrap();
1254		assert_eq!(request.path(), "/team/room");
1255	}
1256
1257	#[tokio::test(start_paused = true)]
1258	async fn accept_request_reads_lite05_peer_hop() {
1259		let hop = Hop::new(42).unwrap();
1260		let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, Some(hop))]);
1261		let request = Server::new()
1262			.accept_request(tokio::time::Instant::now().into_std(), session)
1263			.await
1264			.unwrap();
1265		assert_eq!(request.peer_hop(), Some(hop));
1266	}
1267
1268	#[tokio::test(start_paused = true)]
1269	async fn anonymous_peer_hop_filters_routes_from_server_session() {
1270		let other = Hop::new(778).unwrap();
1271		let origin = crate::origin::Config::new(Hop::new(1).unwrap()).produce();
1272
1273		let gate = kio::Producer::new(true);
1274		let transport = crate::lite::test_transport::SinkSession::gated_bi(gate.consume());
1275		let log = transport.log.clone();
1276		let version = ietf::Version::Draft18;
1277		let request = Handshake {
1278			path: None,
1279			role: None,
1280			origin: None,
1281			token: None,
1282			assigned_hop: Hop::random(),
1283			inner: Some(RequestInner {
1284				server: Server::new().with_publisher(&origin),
1285				runtime: Clock::new(tokio::time::Instant::now().into_std()),
1286				handshake: PausedHandshake::Boxed(Box::new(PausedIetfModern {
1287					session: transport,
1288					version,
1289					peer_setup: ietf::PeerSetup {
1290						stream: crate::coding::Reader::new(
1291							crate::lite::test_transport::PendingRecv,
1292							Version::Ietf(version),
1293						),
1294						size: 0,
1295						path: None,
1296						token: None,
1297						declared: ietf::peer::Peer::default(),
1298						early: Vec::new(),
1299					},
1300				})),
1301			}),
1302		};
1303		let assigned = request.assigned_hop;
1304
1305		let mut echoed_hops = crate::Hops::new();
1306		echoed_hops.push(crate::Hop::UNKNOWN).unwrap();
1307		let _echoed = origin
1308			.announce(
1309				"echoed-route",
1310				crate::origin::Route::default()
1311					.with_hops(echoed_hops)
1312					.with_via(assigned),
1313			)
1314			.unwrap();
1315
1316		let mut local_hops = crate::Hops::new();
1317		local_hops.push(other).unwrap();
1318		let _local = origin
1319			.announce("local-route", crate::origin::Route::default().with_hops(local_hops))
1320			.unwrap();
1321
1322		let (session, driver) = request.ok().await.unwrap();
1323		tokio::spawn(crate::time::run(driver));
1324
1325		for _ in 0..100 {
1326			if occurrences(&log, b"local-route") > 0 {
1327				break;
1328			}
1329			tokio::time::sleep(std::time::Duration::from_millis(1)).await;
1330		}
1331
1332		assert_eq!(occurrences(&log, b"echoed-route"), 0);
1333		assert_eq!(occurrences(&log, b"local-route"), 1);
1334		drop(session);
1335	}
1336}