1use crate::origin;
2use crate::{
3 ALPN_14, ALPN_15, ALPN_16, ALPN_17, ALPN_18, ALPN_19, ALPN_20, ALPN_21, ALPN_LITE, ALPN_LITE_03, ALPN_LITE_04,
4 ALPN_LITE_05, ALPN_LITE_06_WIP, Consume, Driver, Error, NEGOTIATED, Role, Session, Version, Versions,
5 coding::{Decode, Encode, Stream},
6 ietf, lite, setup, stats,
7};
8
9#[derive(Default, Clone)]
11pub struct Server {
12 publish: Option<origin::Consumer>,
13 subscribe: Option<origin::Producer>,
14 stats: stats::Session,
15 versions: Versions,
16}
17
18impl Server {
19 pub fn new() -> Self {
21 Default::default()
22 }
23
24 pub fn with_publisher(mut self, publish: impl Consume<origin::Consumer>) -> Self {
29 self.publish = Some(publish.consume());
30 self
31 }
32
33 pub fn with_subscriber(mut self, subscribe: origin::Producer) -> Self {
36 self.subscribe = Some(subscribe);
37 self
38 }
39
40 pub fn with_stats(mut self, stats: stats::Session) -> Self {
45 self.stats = stats;
46 self
47 }
48
49 pub fn with_origin(self, origin: origin::Producer) -> Self {
51 self.with_publisher(&origin).with_subscriber(origin)
52 }
53
54 pub fn with_versions(mut self, versions: Versions) -> Self {
57 self.versions = versions;
58 self
59 }
60
61 pub async fn accept<S: web_transport_trait::Session>(&self, session: S) -> Result<(Session, Driver), Error> {
68 self.accept_request(session).await?.ok().await
69 }
70
71 pub async fn accept_request<S: web_transport_trait::Session>(&self, session: S) -> Result<Request<S>, Error> {
82 let deferred = |handshake| Request {
85 path: None,
86 role: None,
87 origin: None,
88 assigned_origin: crate::Origin::random(),
89 inner: Some(RequestInner {
90 server: self.clone(),
91 handshake,
92 }),
93 };
94
95 let (encoding, supported) = match session.protocol() {
96 Some(alpn @ (ALPN_21 | ALPN_20 | ALPN_19 | ALPN_18 | ALPN_17)) => {
97 let draft = match alpn {
98 ALPN_21 => ietf::Version::Draft21,
99 ALPN_20 => ietf::Version::Draft20,
100 ALPN_19 => ietf::Version::Draft19,
101 ALPN_18 => ietf::Version::Draft18,
102 _ => ietf::Version::Draft17,
103 };
104
105 self.versions.select(Version::Ietf(draft)).ok_or(Error::Version)?;
106 return self.accept_ietf_modern(session, draft).await;
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 @ (ALPN_LITE_05 | ALPN_LITE_06_WIP)) => {
130 let version = match alpn {
131 ALPN_LITE_06_WIP => lite::Version::Lite06Wip,
132 _ => lite::Version::Lite05,
133 };
134 self.versions.select(Version::Lite(version)).ok_or(Error::Version)?;
135
136 let client_setup = lite::accept_setup(&session, version).await?;
140 return Ok(Request {
141 path: client_setup.path.clone(),
142 role: client_setup.role,
143 origin: client_setup.origin,
144 assigned_origin: crate::Origin::random(),
145 inner: Some(RequestInner {
146 server: self.clone(),
147 handshake: Handshake::LiteSetup {
148 session,
149 version,
150 client_setup,
151 },
152 }),
153 });
154 }
155 Some(ALPN_LITE_04) => {
156 self.versions
157 .select(Version::Lite(lite::Version::Lite04))
158 .ok_or(Error::Version)?;
159 return Ok(deferred(Handshake::LiteBare {
160 session,
161 version: lite::Version::Lite04,
162 }));
163 }
164 Some(ALPN_LITE_03) => {
165 self.versions
166 .select(Version::Lite(lite::Version::Lite03))
167 .ok_or(Error::Version)?;
168 return Ok(deferred(Handshake::LiteBare {
169 session,
170 version: lite::Version::Lite03,
171 }));
172 }
173 Some(ALPN_LITE) | None => {
174 let supported = self.versions.filter(&NEGOTIATED.into()).ok_or(Error::Version)?;
175 (Version::Ietf(ietf::Version::Draft14), supported)
176 }
177 Some(p) => return Err(Error::UnknownAlpn(p.to_string())),
178 };
179
180 let mut stream = Stream::accept(&session, encoding).await?;
183 let mut client: setup::Client = stream.reader.decode().await?;
184
185 let version = client
186 .versions
187 .iter()
188 .flat_map(|v| Version::try_from(*v).ok())
189 .find(|v| supported.contains(v))
190 .ok_or(Error::Version)?;
191
192 let (path, request_id_max, peer_declared) = match version {
196 Version::Ietf(v) => {
197 let params = ietf::Parameters::decode(&mut client.parameters, v)?;
198 let path = match params.get_bytes(ietf::ParameterBytes::Path) {
199 Some(bytes) => Some(
200 std::str::from_utf8(bytes)
201 .map_err(|_| Error::Decode(crate::DecodeError::InvalidValue))?
202 .to_owned(),
203 ),
204 None => None,
205 };
206 let request_id_max = params
207 .get_varint(ietf::ParameterVarInt::MaxRequestId)
208 .map(ietf::RequestId);
209 let peer_declared = ietf::peer::Peer {
210 solicit: ietf::solicit::from_setup(¶ms, v)?,
211 ..Default::default()
212 };
213 (path, request_id_max, peer_declared)
214 }
215 Version::Lite(_) => (None, None, ietf::peer::Peer::default()),
216 };
217
218 Ok(Request {
219 path,
220 role: None,
221 origin: None,
222 assigned_origin: crate::Origin::random(),
223 inner: Some(RequestInner {
224 server: self.clone(),
225 handshake: Handshake::Legacy {
226 session,
227 stream,
228 version,
229 request_id_max,
230 peer_declared,
231 },
232 }),
233 })
234 }
235
236 async fn accept_ietf_modern<S: web_transport_trait::Session>(
239 &self,
240 session: S,
241 version: ietf::Version,
242 ) -> Result<Request<S>, Error> {
243 let peer_setup = ietf::accept_setup(&session, version).await?;
244 Ok(Request {
245 path: peer_setup.path.clone(),
246 role: None,
247 origin: peer_setup
250 .declared
251 .cluster
252 .origin
253 .filter(|o| *o != crate::Origin::UNKNOWN),
254 assigned_origin: crate::Origin::random(),
255 inner: Some(RequestInner {
256 server: self.clone(),
257 handshake: Handshake::IetfModern {
258 session,
259 version,
260 peer_setup,
261 },
262 }),
263 })
264 }
265}
266
267pub struct Request<S: web_transport_trait::Session> {
275 path: Option<String>,
276 role: Option<Role>,
277 origin: Option<crate::Origin>,
278 assigned_origin: crate::Origin,
282 inner: Option<RequestInner<S>>,
284}
285
286struct RequestInner<S: web_transport_trait::Session> {
288 server: Server,
289 handshake: Handshake<S>,
290}
291
292enum Handshake<S: web_transport_trait::Session> {
295 IetfModern {
299 session: S,
300 version: ietf::Version,
301 peer_setup: ietf::PeerSetup<S>,
302 },
303 LiteBare { session: S, version: lite::Version },
305 Legacy {
309 session: S,
310 stream: Stream<S, Version>,
311 version: Version,
312 request_id_max: Option<ietf::RequestId>,
313 peer_declared: ietf::peer::Peer,
315 },
316 LiteSetup {
319 session: S,
320 version: lite::Version,
321 client_setup: lite::Setup,
322 },
323}
324
325impl<S: web_transport_trait::Session> Request<S> {
326 pub fn path(&self) -> &str {
333 self.path.as_deref().unwrap_or("")
334 }
335
336 pub fn role(&self) -> Option<Role> {
345 self.role
346 }
347
348 pub fn peer_origin(&self) -> Option<crate::Origin> {
357 self.origin
358 }
359
360 pub fn with_publisher(mut self, publish: impl Consume<origin::Consumer>) -> Self {
363 self.inner_mut().server.publish = Some(publish.consume());
364 self
365 }
366
367 pub fn with_subscriber(mut self, subscribe: origin::Producer) -> Self {
369 self.inner_mut().server.subscribe = Some(subscribe);
370 self
371 }
372
373 pub fn with_peer_origin(mut self, origin: crate::Origin) -> Self {
386 self.assigned_origin = origin;
387 self
388 }
389
390 pub fn with_stats(mut self, stats: stats::Session) -> Self {
393 self.inner_mut().server.stats = stats;
394 self
395 }
396
397 fn inner_mut(&mut self) -> &mut RequestInner<S> {
398 self.inner.as_mut().expect("request already responded")
399 }
400
401 pub async fn ok(mut self) -> Result<(Session, Driver), Error> {
404 let peer_origin = Some(self.assigned_origin);
405 let RequestInner { server, handshake } = self.inner.take().expect("request already responded");
406
407 let publish = server.publish.map(|origin| origin.with_stats(server.stats.clone()));
411 let subscribe = server.subscribe.map(|origin| origin.with_stats(server.stats.clone()));
412
413 let (session, mut stream, version, request_id_max, peer_declared) = match handshake {
414 Handshake::IetfModern {
415 session,
416 version,
417 peer_setup,
418 } => {
419 let protocol = ietf::start(ietf::Config {
422 session: session.clone(),
423 setup: None,
424 request_id_max: None,
425 client: false,
426 publish,
427 subscribe,
428 peer_origin,
429 cost: None,
431 version,
432 path: None,
433 peer_setup_stream: Some(peer_setup.stream),
434 peer_declared: Some(peer_setup.declared),
435 })?;
436 tracing::debug!(?version, "connected");
437 return Ok(Session::new(session, version.into(), None, protocol));
438 }
439 Handshake::LiteBare { session, version } => {
440 let start = lite::start(lite::Config {
441 session: session.clone(),
442 setup_stream: None,
443 publish,
444 subscribe,
445 peer_origin,
446 version,
447 our_setup: lite::Setup::default(),
448 peer_setup: None,
449 })?;
450 return Ok(Session::new(
451 session,
452 version.into(),
453 start.recv_bandwidth,
454 start.driver,
455 ));
456 }
457 Handshake::LiteSetup {
458 session,
459 version,
460 client_setup,
461 } => {
462 let our_setup = lite::Setup {
465 probe: lite::ProbeLevel::detect(&session),
466 path: None,
467 role: None,
468 cost: None,
470 origin: None,
472 };
473 let start = lite::start(lite::Config {
474 session: session.clone(),
475 setup_stream: None,
476 publish,
477 subscribe,
478 peer_origin,
479 version,
480 our_setup,
481 peer_setup: Some(client_setup),
482 })?;
483 return Ok(Session::new(
484 session,
485 version.into(),
486 start.recv_bandwidth,
487 start.driver,
488 ));
489 }
490 Handshake::Legacy {
491 session,
492 stream,
493 version,
494 request_id_max,
495 peer_declared,
496 } => (session, stream, version, request_id_max, peer_declared),
497 };
498
499 let parameters = match version {
501 Version::Ietf(v) => {
502 let mut parameters = ietf::Parameters::default();
503 parameters.set_varint(ietf::ParameterVarInt::MaxRequestId, u32::MAX as u64);
504 parameters.set_bytes(ietf::ParameterBytes::Implementation, b"moq-lite-rs".to_vec());
505 ietf::solicit::into_setup(&mut parameters, v);
506 parameters.encode_bytes(v)?
507 }
508 Version::Lite(v) => lite::Parameters::default().encode_bytes(v)?,
509 };
510
511 let server_setup = setup::Server {
512 version: version.into(),
513 parameters,
514 };
515 stream.writer.encode(&server_setup).await?;
516
517 let (recv_bw, protocol) = match version {
518 Version::Lite(v) => {
519 let stream = stream.with_version(v);
520 let start = lite::start(lite::Config {
522 session: session.clone(),
523 setup_stream: Some(stream),
524 publish,
525 subscribe,
526 peer_origin,
527 version: v,
528 our_setup: lite::Setup::default(),
529 peer_setup: None,
530 })?;
531 (start.recv_bandwidth, start.driver)
532 }
533 Version::Ietf(v) => {
534 let stream = stream.with_version(v);
535 let protocol = ietf::start(ietf::Config {
537 session: session.clone(),
538 setup: Some(stream),
539 request_id_max,
540 client: false,
541 publish,
542 subscribe,
543 peer_origin,
544 cost: None,
545 version: v,
546 path: None,
547 peer_setup_stream: None,
548 peer_declared: Some(peer_declared),
549 })?;
550 (None, protocol)
551 }
552 };
553
554 Ok(Session::new(session, version, recv_bw, protocol))
555 }
556
557 pub fn close(mut self, err: Error) {
559 let inner = self.inner.take().expect("request already responded");
560 inner.close(err);
561 }
562}
563
564impl<S: web_transport_trait::Session> RequestInner<S> {
565 fn close(self, err: Error) {
566 let session = match self.handshake {
567 Handshake::IetfModern { session, .. } => session,
568 Handshake::LiteBare { session, .. } => session,
569 Handshake::Legacy { session, .. } => session,
570 Handshake::LiteSetup { session, .. } => session,
571 };
572 session.close(err.to_code(), &err.to_string());
573 }
574}
575
576impl<S: web_transport_trait::Session> Drop for Request<S> {
577 fn drop(&mut self) {
580 if let Some(inner) = self.inner.take() {
581 tracing::warn!("Request dropped without ok() or close(); rejecting the session");
582 inner.close(Error::Cancel);
583 }
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use crate::Origin;
591 use std::{
592 collections::VecDeque,
593 sync::{Arc, Mutex},
594 };
595
596 use crate::ALPN_LITE_05;
597 use bytes::Bytes;
598
599 fn occurrences(log: &crate::lite::test_transport::Log, needle: &[u8]) -> usize {
600 let writes = log.writes.lock().unwrap();
601 writes.windows(needle.len()).filter(|window| *window == needle).count()
602 }
603
604 #[derive(Debug, Clone, Default)]
605 struct FakeError;
606 impl std::fmt::Display for FakeError {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 write!(f, "fake transport error")
609 }
610 }
611 impl std::error::Error for FakeError {}
612 impl web_transport_trait::Error for FakeError {
613 fn session_error(&self) -> Option<(u32, String)> {
614 Some((0, "closed".to_string()))
615 }
616 }
617
618 #[derive(Clone)]
621 struct FakeSession {
622 protocol: Option<&'static str>,
623 uni: Arc<Mutex<VecDeque<Vec<u8>>>>,
624 }
625
626 impl FakeSession {
627 fn new(protocol: &'static str, uni: impl IntoIterator<Item = Vec<u8>>) -> Self {
628 Self {
629 protocol: Some(protocol),
630 uni: Arc::new(Mutex::new(uni.into_iter().collect())),
631 }
632 }
633 }
634
635 impl web_transport_trait::Session for FakeSession {
636 type SendStream = FakeSend;
637 type RecvStream = FakeRecv;
638 type Error = FakeError;
639
640 async fn accept_uni(&self) -> Result<Self::RecvStream, Self::Error> {
641 let data = self.uni.lock().unwrap().pop_front();
643 match data {
644 Some(data) => Ok(FakeRecv { data: data.into() }),
645 None => std::future::pending().await,
646 }
647 }
648 async fn accept_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
649 std::future::pending().await
650 }
651 async fn open_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
652 std::future::pending().await
653 }
654 async fn open_uni(&self) -> Result<Self::SendStream, Self::Error> {
655 std::future::pending().await
656 }
657 fn send_datagram(&self, _payload: Bytes) -> Result<(), Self::Error> {
658 Ok(())
659 }
660 async fn recv_datagram(&self) -> Result<Bytes, Self::Error> {
661 std::future::pending().await
662 }
663 fn max_datagram_size(&self) -> usize {
664 1200
665 }
666 fn protocol(&self) -> Option<&str> {
667 self.protocol
668 }
669 fn close(&self, _code: u32, _reason: &str) {}
670 async fn closed(&self) -> Self::Error {
671 std::future::pending().await
672 }
673 }
674
675 #[derive(Clone, Default)]
676 struct FakeSend;
677 impl web_transport_trait::SendStream for FakeSend {
678 type Error = FakeError;
679 async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
680 Ok(buf.len())
681 }
682 fn set_priority(&mut self, _order: u8) {}
683 fn finish(&mut self) -> Result<(), Self::Error> {
684 Ok(())
685 }
686 fn reset(&mut self, _code: u32) {}
687 async fn closed(&mut self) -> Result<(), Self::Error> {
688 Ok(())
689 }
690 }
691
692 struct FakeRecv {
693 data: VecDeque<u8>,
694 }
695 impl web_transport_trait::RecvStream for FakeRecv {
696 type Error = FakeError;
697 async fn read(&mut self, dst: &mut [u8]) -> Result<Option<usize>, Self::Error> {
698 if self.data.is_empty() {
699 return Ok(None);
700 }
701 let size = dst.len().min(self.data.len());
702 for slot in dst.iter_mut().take(size) {
703 *slot = self.data.pop_front().unwrap();
704 }
705 Ok(Some(size))
706 }
707 fn stop(&mut self, _code: u32) {}
708 async fn closed(&mut self) -> Result<(), Self::Error> {
709 Ok(())
710 }
711 }
712
713 fn lite05_setup(path: Option<&str>, role: Option<Role>, origin: Option<Origin>) -> Vec<u8> {
715 let v = lite::Version::Lite05;
716 let mut buf = Vec::new();
717 lite::DataType::Setup.encode(&mut buf, v).unwrap();
718 lite::Setup {
719 probe: lite::ProbeLevel::None,
720 path: path.map(str::to_string),
721 role,
722 cost: None,
723 origin,
724 }
725 .encode(&mut buf, v)
726 .unwrap();
727 buf
728 }
729
730 fn ietf_setup(version: ietf::Version, path: Option<&str>) -> Vec<u8> {
733 let mut params = ietf::Parameters::default();
734 if let Some(path) = path {
735 params.set_bytes(ietf::ParameterBytes::Path, path.as_bytes().to_vec());
736 }
737 let parameters = params.encode_bytes(version).unwrap();
738
739 let mut buf = Vec::new();
740 setup::Setup { parameters }
741 .encode(&mut buf, crate::Version::Ietf(version))
742 .unwrap();
743 buf
744 }
745
746 #[tokio::test(start_paused = true)]
747 async fn accept_request_reads_ietf_path() {
748 for (alpn, version) in [
751 (ALPN_17, ietf::Version::Draft17),
752 (ALPN_18, ietf::Version::Draft18),
753 (ALPN_19, ietf::Version::Draft19),
754 ] {
755 let session = FakeSession::new(alpn, [ietf_setup(version, Some("/team/room"))]);
756 let request = Server::new().accept_request(session).await.unwrap();
757 assert_eq!(request.path(), "/team/room", "{alpn}");
758 }
759 }
760
761 #[tokio::test(start_paused = true)]
762 async fn accept_request_ietf_without_path_is_empty() {
763 let session = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, None)]);
764 let request = Server::new().accept_request(session).await.unwrap();
765 assert_eq!(request.path(), "");
766 }
767
768 #[tokio::test(start_paused = true)]
769 async fn accept_request_ietf_empty_path_is_accepted() {
770 let session = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, Some(""))]);
771 let request = Server::new().accept_request(session).await.unwrap();
772 assert_eq!(request.path(), "");
773 }
774
775 fn lite05_group() -> Vec<u8> {
777 let mut buf = Vec::new();
778 lite::DataType::Group.encode(&mut buf, lite::Version::Lite05).unwrap();
779 buf
780 }
781
782 #[tokio::test(start_paused = true)]
783 async fn accept_request_reads_lite05_path() {
784 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(Some("/team/room"), None, None)]);
785 let request = Server::new().accept_request(session).await.unwrap();
786 assert_eq!(request.path(), "/team/room");
787 assert_eq!(request.role(), None, "a client that omits the role is bidirectional");
788 }
789
790 #[tokio::test(start_paused = true)]
791 async fn accept_request_lite05_without_path_is_empty() {
792 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, None)]);
793 let request = Server::new().accept_request(session).await.unwrap();
794 assert_eq!(request.path(), "");
795 }
796
797 #[tokio::test(start_paused = true)]
798 async fn accept_request_lite05_empty_path_is_accepted() {
799 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(Some(""), None, None)]);
802 let request = Server::new().accept_request(session).await.unwrap();
803 assert_eq!(request.path(), "");
804 }
805
806 #[tokio::test(start_paused = true)]
807 async fn accept_request_reads_lite05_role() {
808 let session = FakeSession::new(
809 ALPN_LITE_05,
810 [lite05_setup(Some("/team/room"), Some(Role::Publisher), None)],
811 );
812 let request = Server::new().accept_request(session).await.unwrap();
813 assert_eq!(request.role(), Some(Role::Publisher));
814 }
815
816 #[tokio::test(start_paused = true)]
817 async fn accept_request_skips_uni_stream_before_setup() {
818 let session = FakeSession::new(
821 ALPN_LITE_05,
822 [lite05_group(), lite05_setup(Some("/team/room"), None, None)],
823 );
824 let request = Server::new().accept_request(session).await.unwrap();
825 assert_eq!(request.path(), "/team/room");
826 }
827
828 #[tokio::test(start_paused = true)]
829 async fn accept_request_reads_lite05_peer_origin() {
830 let origin = Origin::new(42).unwrap();
831 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, Some(origin))]);
832 let request = Server::new().accept_request(session).await.unwrap();
833 assert_eq!(request.peer_origin(), Some(origin));
834 }
835
836 #[tokio::test(start_paused = true)]
837 async fn anonymous_peer_origin_filters_routes_from_server_session() {
838 let other = Origin::new(778).unwrap();
839 let origin = crate::origin::Info::new(Origin::new(1).unwrap()).produce();
840
841 let gate = kio::Producer::new(true);
842 let transport = crate::lite::test_transport::SinkSession::gated_bi(gate.consume());
843 let log = transport.log.clone();
844 let version = ietf::Version::Draft18;
845 let request = Request {
846 path: None,
847 role: None,
848 origin: None,
849 assigned_origin: Origin::random(),
850 inner: Some(RequestInner {
851 server: Server::new().with_publisher(&origin),
852 handshake: Handshake::IetfModern {
853 session: transport,
854 version,
855 peer_setup: ietf::PeerSetup {
856 stream: crate::coding::Reader::new(
857 crate::lite::test_transport::PendingRecv,
858 Version::Ietf(version),
859 ),
860 path: None,
861 declared: ietf::peer::Peer::default(),
862 },
863 },
864 }),
865 };
866 let assigned = request.assigned_origin;
867
868 let mut echoed_hops = crate::OriginList::new();
869 echoed_hops.push(assigned).unwrap();
870 let _echoed = origin
871 .create_broadcast(
872 "echoed-route",
873 crate::broadcast::Route::new()
874 .with_hops(echoed_hops)
875 .with_announce(true),
876 )
877 .unwrap();
878
879 let mut local_hops = crate::OriginList::new();
880 local_hops.push(other).unwrap();
881 let _local = origin
882 .create_broadcast(
883 "local-route",
884 crate::broadcast::Route::new().with_hops(local_hops).with_announce(true),
885 )
886 .unwrap();
887
888 let (session, driver) = request.ok().await.unwrap();
889 let _driver = tokio::spawn(driver);
890
891 for _ in 0..100 {
892 if occurrences(&log, b"local-route") > 0 {
893 break;
894 }
895 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
896 }
897
898 assert_eq!(occurrences(&log, b"echoed-route"), 0);
899 assert_eq!(occurrences(&log, b"local-route"), 1);
900 drop(session);
901 }
902}