1use crate::origin;
2use crate::{
3 ALPN_14, ALPN_15, ALPN_16, ALPN_17, ALPN_18, ALPN_19, ALPN_LITE, ALPN_LITE_03, ALPN_LITE_04, ALPN_LITE_05,
4 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_19) => {
97 self.versions
98 .select(Version::Ietf(ietf::Version::Draft19))
99 .ok_or(Error::Version)?;
100 return self.accept_ietf_modern(session, ietf::Version::Draft19).await;
101 }
102 Some(ALPN_18) => {
103 self.versions
104 .select(Version::Ietf(ietf::Version::Draft18))
105 .ok_or(Error::Version)?;
106 return self.accept_ietf_modern(session, ietf::Version::Draft18).await;
107 }
108 Some(ALPN_17) => {
109 self.versions
110 .select(Version::Ietf(ietf::Version::Draft17))
111 .ok_or(Error::Version)?;
112 return self.accept_ietf_modern(session, ietf::Version::Draft17).await;
113 }
114 Some(ALPN_16) => {
115 let v = self
116 .versions
117 .select(Version::Ietf(ietf::Version::Draft16))
118 .ok_or(Error::Version)?;
119 (v, v.into())
120 }
121 Some(ALPN_15) => {
122 let v = self
123 .versions
124 .select(Version::Ietf(ietf::Version::Draft15))
125 .ok_or(Error::Version)?;
126 (v, v.into())
127 }
128 Some(ALPN_14) => {
129 let v = self
130 .versions
131 .select(Version::Ietf(ietf::Version::Draft14))
132 .ok_or(Error::Version)?;
133 (v, v.into())
134 }
135 Some(alpn @ (ALPN_LITE_05 | ALPN_LITE_06_WIP)) => {
136 let version = match alpn {
137 ALPN_LITE_06_WIP => lite::Version::Lite06Wip,
138 _ => lite::Version::Lite05,
139 };
140 self.versions.select(Version::Lite(version)).ok_or(Error::Version)?;
141
142 let client_setup = lite::accept_setup(&session, version).await?;
146 return Ok(Request {
147 path: client_setup.path.clone(),
148 role: client_setup.role,
149 origin: client_setup.origin,
150 assigned_origin: crate::Origin::random(),
151 inner: Some(RequestInner {
152 server: self.clone(),
153 handshake: Handshake::LiteSetup {
154 session,
155 version,
156 client_setup,
157 },
158 }),
159 });
160 }
161 Some(ALPN_LITE_04) => {
162 self.versions
163 .select(Version::Lite(lite::Version::Lite04))
164 .ok_or(Error::Version)?;
165 return Ok(deferred(Handshake::LiteBare {
166 session,
167 version: lite::Version::Lite04,
168 }));
169 }
170 Some(ALPN_LITE_03) => {
171 self.versions
172 .select(Version::Lite(lite::Version::Lite03))
173 .ok_or(Error::Version)?;
174 return Ok(deferred(Handshake::LiteBare {
175 session,
176 version: lite::Version::Lite03,
177 }));
178 }
179 Some(ALPN_LITE) | None => {
180 let supported = self.versions.filter(&NEGOTIATED.into()).ok_or(Error::Version)?;
181 (Version::Ietf(ietf::Version::Draft14), supported)
182 }
183 Some(p) => return Err(Error::UnknownAlpn(p.to_string())),
184 };
185
186 let mut stream = Stream::accept(&session, encoding).await?;
189 let mut client: setup::Client = stream.reader.decode().await?;
190
191 let version = client
192 .versions
193 .iter()
194 .flat_map(|v| Version::try_from(*v).ok())
195 .find(|v| supported.contains(v))
196 .ok_or(Error::Version)?;
197
198 let (path, request_id_max, peer_declared) = match version {
202 Version::Ietf(v) => {
203 let params = ietf::Parameters::decode(&mut client.parameters, v)?;
204 let path = match params.get_bytes(ietf::ParameterBytes::Path) {
205 Some(bytes) => Some(
206 std::str::from_utf8(bytes)
207 .map_err(|_| Error::Decode(crate::DecodeError::InvalidValue))?
208 .to_owned(),
209 ),
210 None => None,
211 };
212 let request_id_max = params
213 .get_varint(ietf::ParameterVarInt::MaxRequestId)
214 .map(ietf::RequestId);
215 let peer_declared = ietf::peer::Peer {
216 solicit: ietf::solicit::from_setup(¶ms, v)?,
217 ..Default::default()
218 };
219 (path, request_id_max, peer_declared)
220 }
221 Version::Lite(_) => (None, None, ietf::peer::Peer::default()),
222 };
223
224 Ok(Request {
225 path,
226 role: None,
227 origin: None,
228 assigned_origin: crate::Origin::random(),
229 inner: Some(RequestInner {
230 server: self.clone(),
231 handshake: Handshake::Legacy {
232 session,
233 stream,
234 version,
235 request_id_max,
236 peer_declared,
237 },
238 }),
239 })
240 }
241
242 async fn accept_ietf_modern<S: web_transport_trait::Session>(
245 &self,
246 session: S,
247 version: ietf::Version,
248 ) -> Result<Request<S>, Error> {
249 let peer_setup = ietf::accept_setup(&session, version).await?;
250 Ok(Request {
251 path: peer_setup.path.clone(),
252 role: None,
253 origin: peer_setup
256 .declared
257 .cluster
258 .origin
259 .filter(|o| *o != crate::Origin::UNKNOWN),
260 assigned_origin: crate::Origin::random(),
261 inner: Some(RequestInner {
262 server: self.clone(),
263 handshake: Handshake::IetfModern {
264 session,
265 version,
266 peer_setup,
267 },
268 }),
269 })
270 }
271}
272
273pub struct Request<S: web_transport_trait::Session> {
281 path: Option<String>,
282 role: Option<Role>,
283 origin: Option<crate::Origin>,
284 assigned_origin: crate::Origin,
288 inner: Option<RequestInner<S>>,
290}
291
292struct RequestInner<S: web_transport_trait::Session> {
294 server: Server,
295 handshake: Handshake<S>,
296}
297
298enum Handshake<S: web_transport_trait::Session> {
301 IetfModern {
305 session: S,
306 version: ietf::Version,
307 peer_setup: ietf::PeerSetup<S>,
308 },
309 LiteBare { session: S, version: lite::Version },
311 Legacy {
315 session: S,
316 stream: Stream<S, Version>,
317 version: Version,
318 request_id_max: Option<ietf::RequestId>,
319 peer_declared: ietf::peer::Peer,
321 },
322 LiteSetup {
325 session: S,
326 version: lite::Version,
327 client_setup: lite::Setup,
328 },
329}
330
331impl<S: web_transport_trait::Session> Request<S> {
332 pub fn path(&self) -> &str {
339 self.path.as_deref().unwrap_or("")
340 }
341
342 pub fn role(&self) -> Option<Role> {
351 self.role
352 }
353
354 pub fn peer_origin(&self) -> Option<crate::Origin> {
363 self.origin
364 }
365
366 pub fn with_publisher(mut self, publish: impl Consume<origin::Consumer>) -> Self {
369 self.inner_mut().server.publish = Some(publish.consume());
370 self
371 }
372
373 pub fn with_subscriber(mut self, subscribe: origin::Producer) -> Self {
375 self.inner_mut().server.subscribe = Some(subscribe);
376 self
377 }
378
379 pub fn with_peer_origin(mut self, origin: crate::Origin) -> Self {
392 self.assigned_origin = origin;
393 self
394 }
395
396 pub fn with_stats(mut self, stats: stats::Session) -> Self {
399 self.inner_mut().server.stats = stats;
400 self
401 }
402
403 fn inner_mut(&mut self) -> &mut RequestInner<S> {
404 self.inner.as_mut().expect("request already responded")
405 }
406
407 pub async fn ok(mut self) -> Result<(Session, Driver), Error> {
410 let peer_origin = Some(self.assigned_origin);
411 let RequestInner { server, handshake } = self.inner.take().expect("request already responded");
412
413 let publish = server.publish.map(|origin| origin.with_stats(server.stats.clone()));
417 let subscribe = server.subscribe.map(|origin| origin.with_stats(server.stats.clone()));
418
419 let (session, mut stream, version, request_id_max, peer_declared) = match handshake {
420 Handshake::IetfModern {
421 session,
422 version,
423 peer_setup,
424 } => {
425 let protocol = ietf::start(ietf::Config {
428 session: session.clone(),
429 setup: None,
430 request_id_max: None,
431 client: false,
432 publish,
433 subscribe,
434 peer_origin,
435 cost: None,
437 version,
438 path: None,
439 peer_setup_stream: Some(peer_setup.stream),
440 peer_declared: Some(peer_setup.declared),
441 })?;
442 tracing::debug!(?version, "connected");
443 return Ok(Session::new(session, version.into(), None, protocol));
444 }
445 Handshake::LiteBare { session, version } => {
446 let start = lite::start(lite::Config {
447 session: session.clone(),
448 setup_stream: None,
449 publish,
450 subscribe,
451 peer_origin,
452 version,
453 our_setup: lite::Setup::default(),
454 peer_setup: None,
455 })?;
456 return Ok(Session::new(
457 session,
458 version.into(),
459 start.recv_bandwidth,
460 start.driver,
461 ));
462 }
463 Handshake::LiteSetup {
464 session,
465 version,
466 client_setup,
467 } => {
468 let our_setup = lite::Setup {
471 probe: lite::ProbeLevel::detect(&session),
472 path: None,
473 role: None,
474 cost: None,
476 origin: None,
478 };
479 let start = lite::start(lite::Config {
480 session: session.clone(),
481 setup_stream: None,
482 publish,
483 subscribe,
484 peer_origin,
485 version,
486 our_setup,
487 peer_setup: Some(client_setup),
488 })?;
489 return Ok(Session::new(
490 session,
491 version.into(),
492 start.recv_bandwidth,
493 start.driver,
494 ));
495 }
496 Handshake::Legacy {
497 session,
498 stream,
499 version,
500 request_id_max,
501 peer_declared,
502 } => (session, stream, version, request_id_max, peer_declared),
503 };
504
505 let parameters = match version {
507 Version::Ietf(v) => {
508 let mut parameters = ietf::Parameters::default();
509 parameters.set_varint(ietf::ParameterVarInt::MaxRequestId, u32::MAX as u64);
510 parameters.set_bytes(ietf::ParameterBytes::Implementation, b"moq-lite-rs".to_vec());
511 ietf::solicit::into_setup(&mut parameters, v);
512 parameters.encode_bytes(v)?
513 }
514 Version::Lite(v) => lite::Parameters::default().encode_bytes(v)?,
515 };
516
517 let server_setup = setup::Server {
518 version: version.into(),
519 parameters,
520 };
521 stream.writer.encode(&server_setup).await?;
522
523 let (recv_bw, protocol) = match version {
524 Version::Lite(v) => {
525 let stream = stream.with_version(v);
526 let start = lite::start(lite::Config {
528 session: session.clone(),
529 setup_stream: Some(stream),
530 publish,
531 subscribe,
532 peer_origin,
533 version: v,
534 our_setup: lite::Setup::default(),
535 peer_setup: None,
536 })?;
537 (start.recv_bandwidth, start.driver)
538 }
539 Version::Ietf(v) => {
540 let stream = stream.with_version(v);
541 let protocol = ietf::start(ietf::Config {
543 session: session.clone(),
544 setup: Some(stream),
545 request_id_max,
546 client: false,
547 publish,
548 subscribe,
549 peer_origin,
550 cost: None,
551 version: v,
552 path: None,
553 peer_setup_stream: None,
554 peer_declared: Some(peer_declared),
555 })?;
556 (None, protocol)
557 }
558 };
559
560 Ok(Session::new(session, version, recv_bw, protocol))
561 }
562
563 pub fn close(mut self, err: Error) {
565 let inner = self.inner.take().expect("request already responded");
566 inner.close(err);
567 }
568}
569
570impl<S: web_transport_trait::Session> RequestInner<S> {
571 fn close(self, err: Error) {
572 let session = match self.handshake {
573 Handshake::IetfModern { session, .. } => session,
574 Handshake::LiteBare { session, .. } => session,
575 Handshake::Legacy { session, .. } => session,
576 Handshake::LiteSetup { session, .. } => session,
577 };
578 session.close(err.to_code(), &err.to_string());
579 }
580}
581
582impl<S: web_transport_trait::Session> Drop for Request<S> {
583 fn drop(&mut self) {
586 if let Some(inner) = self.inner.take() {
587 tracing::warn!("Request dropped without ok() or close(); rejecting the session");
588 inner.close(Error::Cancel);
589 }
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use crate::Origin;
597 use std::{
598 collections::VecDeque,
599 sync::{Arc, Mutex},
600 };
601
602 use crate::ALPN_LITE_05;
603 use bytes::Bytes;
604
605 fn occurrences(log: &crate::lite::test_transport::Log, needle: &[u8]) -> usize {
606 let writes = log.writes.lock().unwrap();
607 writes.windows(needle.len()).filter(|window| *window == needle).count()
608 }
609
610 #[derive(Debug, Clone, Default)]
611 struct FakeError;
612 impl std::fmt::Display for FakeError {
613 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614 write!(f, "fake transport error")
615 }
616 }
617 impl std::error::Error for FakeError {}
618 impl web_transport_trait::Error for FakeError {
619 fn session_error(&self) -> Option<(u32, String)> {
620 Some((0, "closed".to_string()))
621 }
622 }
623
624 #[derive(Clone)]
627 struct FakeSession {
628 protocol: Option<&'static str>,
629 uni: Arc<Mutex<VecDeque<Vec<u8>>>>,
630 }
631
632 impl FakeSession {
633 fn new(protocol: &'static str, uni: impl IntoIterator<Item = Vec<u8>>) -> Self {
634 Self {
635 protocol: Some(protocol),
636 uni: Arc::new(Mutex::new(uni.into_iter().collect())),
637 }
638 }
639 }
640
641 impl web_transport_trait::Session for FakeSession {
642 type SendStream = FakeSend;
643 type RecvStream = FakeRecv;
644 type Error = FakeError;
645
646 async fn accept_uni(&self) -> Result<Self::RecvStream, Self::Error> {
647 let data = self.uni.lock().unwrap().pop_front();
649 match data {
650 Some(data) => Ok(FakeRecv { data: data.into() }),
651 None => std::future::pending().await,
652 }
653 }
654 async fn accept_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
655 std::future::pending().await
656 }
657 async fn open_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
658 std::future::pending().await
659 }
660 async fn open_uni(&self) -> Result<Self::SendStream, Self::Error> {
661 std::future::pending().await
662 }
663 fn send_datagram(&self, _payload: Bytes) -> Result<(), Self::Error> {
664 Ok(())
665 }
666 async fn recv_datagram(&self) -> Result<Bytes, Self::Error> {
667 std::future::pending().await
668 }
669 fn max_datagram_size(&self) -> usize {
670 1200
671 }
672 fn protocol(&self) -> Option<&str> {
673 self.protocol
674 }
675 fn close(&self, _code: u32, _reason: &str) {}
676 async fn closed(&self) -> Self::Error {
677 std::future::pending().await
678 }
679 }
680
681 #[derive(Clone, Default)]
682 struct FakeSend;
683 impl web_transport_trait::SendStream for FakeSend {
684 type Error = FakeError;
685 async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
686 Ok(buf.len())
687 }
688 fn set_priority(&mut self, _order: u8) {}
689 fn finish(&mut self) -> Result<(), Self::Error> {
690 Ok(())
691 }
692 fn reset(&mut self, _code: u32) {}
693 async fn closed(&mut self) -> Result<(), Self::Error> {
694 Ok(())
695 }
696 }
697
698 struct FakeRecv {
699 data: VecDeque<u8>,
700 }
701 impl web_transport_trait::RecvStream for FakeRecv {
702 type Error = FakeError;
703 async fn read(&mut self, dst: &mut [u8]) -> Result<Option<usize>, Self::Error> {
704 if self.data.is_empty() {
705 return Ok(None);
706 }
707 let size = dst.len().min(self.data.len());
708 for slot in dst.iter_mut().take(size) {
709 *slot = self.data.pop_front().unwrap();
710 }
711 Ok(Some(size))
712 }
713 fn stop(&mut self, _code: u32) {}
714 async fn closed(&mut self) -> Result<(), Self::Error> {
715 Ok(())
716 }
717 }
718
719 fn lite05_setup(path: Option<&str>, role: Option<Role>, origin: Option<Origin>) -> Vec<u8> {
721 let v = lite::Version::Lite05;
722 let mut buf = Vec::new();
723 lite::DataType::Setup.encode(&mut buf, v).unwrap();
724 lite::Setup {
725 probe: lite::ProbeLevel::None,
726 path: path.map(str::to_string),
727 role,
728 cost: None,
729 origin,
730 }
731 .encode(&mut buf, v)
732 .unwrap();
733 buf
734 }
735
736 fn ietf_setup(version: ietf::Version, path: Option<&str>) -> Vec<u8> {
739 let mut params = ietf::Parameters::default();
740 if let Some(path) = path {
741 params.set_bytes(ietf::ParameterBytes::Path, path.as_bytes().to_vec());
742 }
743 let parameters = params.encode_bytes(version).unwrap();
744
745 let mut buf = Vec::new();
746 setup::Setup { parameters }
747 .encode(&mut buf, crate::Version::Ietf(version))
748 .unwrap();
749 buf
750 }
751
752 #[tokio::test(start_paused = true)]
753 async fn accept_request_reads_ietf_path() {
754 for (alpn, version) in [
757 (ALPN_17, ietf::Version::Draft17),
758 (ALPN_18, ietf::Version::Draft18),
759 (ALPN_19, ietf::Version::Draft19),
760 ] {
761 let session = FakeSession::new(alpn, [ietf_setup(version, Some("/team/room"))]);
762 let request = Server::new().accept_request(session).await.unwrap();
763 assert_eq!(request.path(), "/team/room", "{alpn}");
764 }
765 }
766
767 #[tokio::test(start_paused = true)]
768 async fn accept_request_ietf_without_path_is_empty() {
769 let session = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, None)]);
770 let request = Server::new().accept_request(session).await.unwrap();
771 assert_eq!(request.path(), "");
772 }
773
774 #[tokio::test(start_paused = true)]
775 async fn accept_request_ietf_empty_path_is_accepted() {
776 let session = FakeSession::new(ALPN_19, [ietf_setup(ietf::Version::Draft19, Some(""))]);
777 let request = Server::new().accept_request(session).await.unwrap();
778 assert_eq!(request.path(), "");
779 }
780
781 fn lite05_group() -> Vec<u8> {
783 let mut buf = Vec::new();
784 lite::DataType::Group.encode(&mut buf, lite::Version::Lite05).unwrap();
785 buf
786 }
787
788 #[tokio::test(start_paused = true)]
789 async fn accept_request_reads_lite05_path() {
790 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(Some("/team/room"), None, None)]);
791 let request = Server::new().accept_request(session).await.unwrap();
792 assert_eq!(request.path(), "/team/room");
793 assert_eq!(request.role(), None, "a client that omits the role is bidirectional");
794 }
795
796 #[tokio::test(start_paused = true)]
797 async fn accept_request_lite05_without_path_is_empty() {
798 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, None)]);
799 let request = Server::new().accept_request(session).await.unwrap();
800 assert_eq!(request.path(), "");
801 }
802
803 #[tokio::test(start_paused = true)]
804 async fn accept_request_lite05_empty_path_is_accepted() {
805 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(Some(""), None, None)]);
808 let request = Server::new().accept_request(session).await.unwrap();
809 assert_eq!(request.path(), "");
810 }
811
812 #[tokio::test(start_paused = true)]
813 async fn accept_request_reads_lite05_role() {
814 let session = FakeSession::new(
815 ALPN_LITE_05,
816 [lite05_setup(Some("/team/room"), Some(Role::Publisher), None)],
817 );
818 let request = Server::new().accept_request(session).await.unwrap();
819 assert_eq!(request.role(), Some(Role::Publisher));
820 }
821
822 #[tokio::test(start_paused = true)]
823 async fn accept_request_skips_uni_stream_before_setup() {
824 let session = FakeSession::new(
827 ALPN_LITE_05,
828 [lite05_group(), lite05_setup(Some("/team/room"), None, None)],
829 );
830 let request = Server::new().accept_request(session).await.unwrap();
831 assert_eq!(request.path(), "/team/room");
832 }
833
834 #[tokio::test(start_paused = true)]
835 async fn accept_request_reads_lite05_peer_origin() {
836 let origin = Origin::new(42).unwrap();
837 let session = FakeSession::new(ALPN_LITE_05, [lite05_setup(None, None, Some(origin))]);
838 let request = Server::new().accept_request(session).await.unwrap();
839 assert_eq!(request.peer_origin(), Some(origin));
840 }
841
842 #[tokio::test(start_paused = true)]
843 async fn anonymous_peer_origin_filters_routes_from_server_session() {
844 let other = Origin::new(778).unwrap();
845 let origin = crate::origin::Info::new(Origin::new(1).unwrap()).produce();
846
847 let gate = kio::Producer::new(true);
848 let transport = crate::lite::test_transport::SinkSession::gated_bi(gate.consume());
849 let log = transport.log.clone();
850 let version = ietf::Version::Draft18;
851 let request = Request {
852 path: None,
853 role: None,
854 origin: None,
855 assigned_origin: Origin::random(),
856 inner: Some(RequestInner {
857 server: Server::new().with_publisher(&origin),
858 handshake: Handshake::IetfModern {
859 session: transport,
860 version,
861 peer_setup: ietf::PeerSetup {
862 stream: crate::coding::Reader::new(
863 crate::lite::test_transport::PendingRecv,
864 Version::Ietf(version),
865 ),
866 path: None,
867 declared: ietf::peer::Peer::default(),
868 },
869 },
870 }),
871 };
872 let assigned = request.assigned_origin;
873
874 let mut echoed_hops = crate::OriginList::new();
875 echoed_hops.push(assigned).unwrap();
876 let _echoed = origin
877 .create_broadcast(
878 "echoed-route",
879 crate::broadcast::Route::new()
880 .with_hops(echoed_hops)
881 .with_announce(true),
882 )
883 .unwrap();
884
885 let mut local_hops = crate::OriginList::new();
886 local_hops.push(other).unwrap();
887 let _local = origin
888 .create_broadcast(
889 "local-route",
890 crate::broadcast::Route::new().with_hops(local_hops).with_announce(true),
891 )
892 .unwrap();
893
894 let (session, driver) = request.ok().await.unwrap();
895 let _driver = tokio::spawn(driver);
896
897 for _ in 0..100 {
898 if occurrences(&log, b"local-route") > 0 {
899 break;
900 }
901 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
902 }
903
904 assert_eq!(occurrences(&log, b"echoed-route"), 0);
905 assert_eq!(occurrences(&log, b"local-route"), 1);
906 drop(session);
907 }
908}