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