1use crate::{broadcast, cache, stats, track};
2use kio::Pollable;
3use std::{
4 cmp::Reverse,
5 collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
6 fmt,
7 sync::Arc,
8 sync::atomic::{AtomicU64, Ordering},
9 task::{Poll, ready},
10 time::Duration,
11};
12
13use rand::RngExt;
14
15use super::{
16 Requests, WeakCache, WeakEntry,
17 front::{Action, Candidate, Event, Front, Pin, Refusal},
18};
19use crate::{
20 AsPath, Error, InvalidPattern, Path, PathOwned, Pattern, Patterns,
21 coding::{BoundsExceeded, Decode, DecodeError, Encode, EncodeError},
22 runtime::{Instant, Timers},
23 time::Clock,
24 util::{Keepalive, TaskSet, Tasks, TasksWeak},
25};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub struct Hop {
38 id: u64,
40}
41
42impl Hop {
43 pub const UNKNOWN: Self = Self { id: 0 };
50
51 pub fn new(id: u64) -> Result<Self, InvalidHop> {
57 if id == 0 || id >= 1u64 << 62 {
58 return Err(InvalidHop::Range);
59 }
60 Ok(Self { id })
61 }
62
63 pub fn random() -> Self {
70 let mut rng = rand::rng();
71 let id = rng.random_range(1..(1u64 << 53));
72 Self { id }
73 }
74
75 pub fn id(self) -> u64 {
77 self.id
78 }
79
80 pub(crate) fn from_wire(id: u64) -> Result<Self, DecodeError> {
82 if id >= 1u64 << 62 {
83 return Err(DecodeError::InvalidValue);
84 }
85 Ok(Self { id })
86 }
87}
88
89#[derive(Clone, Debug)]
95#[non_exhaustive]
96pub struct Config {
97 pub hop: Hop,
100
101 pub pool: cache::Pool,
108
109 pub cache_duration: Duration,
117
118 pub default_max_age: Duration,
128}
129
130impl Default for Config {
131 fn default() -> Self {
133 let pool = cache::Pool::new(cache::Config::default().with_expiry(cache::DEFAULT_EXPIRY));
134 Self {
135 hop: Hop::random(),
136 pool,
137 cache_duration: Duration::MAX,
138 default_max_age: track::DEFAULT_MAX_AGE,
139 }
140 }
141}
142
143impl Config {
144 pub fn new(hop: Hop) -> Self {
146 Self { hop, ..Self::default() }
147 }
148}
149
150impl From<Hop> for Config {
151 fn from(hop: Hop) -> Self {
153 Self::new(hop)
154 }
155}
156
157impl TryFrom<u64> for Hop {
158 type Error = InvalidHop;
159
160 fn try_from(id: u64) -> Result<Self, Self::Error> {
161 Self::new(id)
162 }
163}
164
165impl fmt::Display for Hop {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 self.id.fmt(f)
168 }
169}
170
171impl<V: Copy> Encode<V> for Hop
172where
173 u64: Encode<V>,
174{
175 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
176 self.id.encode(w, version)
177 }
178}
179
180impl<V: Copy> Decode<V> for Hop
181where
182 u64: Decode<V>,
183{
184 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
185 Self::from_wire(u64::decode(r, version)?)
186 }
187}
188
189pub(crate) const MAX_HOPS: usize = 32;
195
196#[derive(Debug, Clone, Default, PartialEq, Eq)]
204pub struct Hops(Vec<Hop>);
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208#[non_exhaustive]
209pub enum InvalidHop {
210 Range,
213
214 TooMany,
217
218 Duplicate,
222}
223
224impl fmt::Display for InvalidHop {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match self {
227 Self::Range => write!(f, "local hop id must be non-zero and below 2^62"),
228 Self::TooMany => write!(f, "too many hops (max {MAX_HOPS})"),
229 Self::Duplicate => write!(f, "hop already in the chain"),
230 }
231 }
232}
233
234impl std::error::Error for InvalidHop {}
235
236impl From<InvalidHop> for DecodeError {
237 fn from(err: InvalidHop) -> Self {
238 match err {
239 InvalidHop::TooMany => DecodeError::BoundsExceeded,
240 InvalidHop::Range | InvalidHop::Duplicate => DecodeError::InvalidValue,
241 }
242 }
243}
244
245impl Hops {
246 pub fn new() -> Self {
248 Self(Vec::new())
249 }
250
251 pub fn push(&mut self, hop: Hop) -> Result<(), InvalidHop> {
257 if self.0.len() >= MAX_HOPS {
258 return Err(InvalidHop::TooMany);
259 }
260 if hop != Hop::UNKNOWN && self.0.contains(&hop) {
261 return Err(InvalidHop::Duplicate);
262 }
263 self.0.push(hop);
264 Ok(())
265 }
266
267 pub fn contains(&self, hop: &Hop) -> bool {
269 self.0.contains(hop)
270 }
271
272 pub fn len(&self) -> usize {
274 self.0.len()
275 }
276
277 pub fn is_empty(&self) -> bool {
279 self.0.is_empty()
280 }
281
282 pub fn iter(&self) -> std::slice::Iter<'_, Hop> {
284 self.0.iter()
285 }
286
287 pub fn as_slice(&self) -> &[Hop] {
289 &self.0
290 }
291}
292
293impl TryFrom<Vec<Hop>> for Hops {
294 type Error = InvalidHop;
295
296 fn try_from(v: Vec<Hop>) -> Result<Self, Self::Error> {
297 if v.len() > MAX_HOPS {
298 return Err(InvalidHop::TooMany);
299 }
300 for (i, hop) in v.iter().enumerate() {
302 if *hop != Hop::UNKNOWN && v[i + 1..].contains(hop) {
303 return Err(InvalidHop::Duplicate);
304 }
305 }
306 Ok(Self(v))
307 }
308}
309
310impl<'a> IntoIterator for &'a Hops {
311 type Item = &'a Hop;
312 type IntoIter = std::slice::Iter<'a, Hop>;
313
314 fn into_iter(self) -> Self::IntoIter {
315 self.iter()
316 }
317}
318
319impl<V: Copy> Encode<V> for Hops
320where
321 u64: Encode<V>,
322 Hop: Encode<V>,
323{
324 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
325 (self.0.len() as u64).encode(w, version)?;
326 for origin in &self.0 {
327 origin.encode(w, version)?;
328 }
329 Ok(())
330 }
331}
332
333impl<V: Copy> Decode<V> for Hops
334where
335 u64: Decode<V>,
336 Hop: Decode<V>,
337{
338 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
339 let count = u64::decode(r, version)? as usize;
340 if count > MAX_HOPS {
341 return Err(DecodeError::BoundsExceeded);
342 }
343 let mut list = Self(Vec::with_capacity(count));
346 for _ in 0..count {
347 list.push(Hop::decode(r, version)?)?;
348 }
349 Ok(list)
350 }
351}
352
353const MAX_COST: u64 = (1 << 62) - 1;
360
361#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
370pub struct Cost {
371 pub warm: u64,
380
381 pub cold: u64,
388}
389
390impl Cost {
391 pub const fn new(cost: u64) -> Self {
394 Self { warm: cost, cold: cost }
395 }
396
397 pub const MAX: Self = Self::new(MAX_COST);
404
405 pub const DRAIN: Self = Self::MAX;
408
409 pub(crate) const UNKNOWN: Self = Self {
413 warm: 0,
414 cold: MAX_COST,
415 };
416
417 pub(crate) fn charged(self, link_cost: u64) -> Self {
420 Self {
421 warm: self.warm.saturating_add(link_cost).min(MAX_COST),
422 cold: self.cold.saturating_add(link_cost).min(MAX_COST),
423 }
424 }
425
426 pub(crate) fn clamped(self) -> Self {
429 Self {
430 warm: self.warm.min(MAX_COST),
431 cold: self.cold.min(MAX_COST),
432 }
433 }
434}
435
436impl From<u64> for Cost {
437 fn from(cost: u64) -> Self {
438 Self::new(cost)
439 }
440}
441
442#[derive(Clone, Debug, PartialEq, Eq)]
453#[non_exhaustive]
454pub struct Route {
455 pub hops: Hops,
460
461 pub cost: Cost,
466
467 pub(crate) via: Hop,
473
474 pub(crate) source: Source,
476}
477
478impl Default for Route {
479 fn default() -> Self {
480 Self {
481 hops: Hops::new(),
482 cost: Cost::default(),
483 via: Hop::UNKNOWN,
484 source: Source::Local,
485 }
486 }
487}
488
489#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
495pub enum Source {
496 #[default]
498 Local,
499 Peer(Hop),
502}
503
504impl Route {
505 pub fn with_hops(mut self, hops: Hops) -> Self {
507 self.hops = hops;
508 self
509 }
510
511 pub fn with_cost(mut self, cost: impl Into<Cost>) -> Self {
516 self.cost = cost.into();
517 self
518 }
519
520 pub(crate) fn with_via(mut self, via: Hop) -> Self {
525 self.via = via;
526 self
527 }
528
529 pub fn is_anonymous(&self) -> bool {
537 self.hops.iter().any(|hop| *hop == Hop::UNKNOWN)
538 }
539
540 pub fn source(&self) -> Source {
545 self.source
546 }
547}
548
549static NEXT_CONSUMER_ID: AtomicU64 = AtomicU64::new(0);
550
551#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
552struct ConsumerId(u64);
553
554impl ConsumerId {
555 fn new() -> Self {
556 Self(NEXT_CONSUMER_ID.fetch_add(1, Ordering::Relaxed))
557 }
558}
559
560fn fnv_key(name: &str, origins: impl IntoIterator<Item = Hop>) -> u64 {
569 const SEED: u64 = 0x420C0DECB00B; const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
571
572 let mut hash = SEED;
573 for &byte in name.as_bytes() {
574 hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
575 }
576 for origin in origins {
577 for &byte in &origin.id().to_le_bytes() {
578 hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
579 }
580 }
581
582 hash
583}
584
585fn route_order(prefix: &Path, entry: &RouteEntry) -> (bool, Cost, bool, usize, u64, Reverse<u64>) {
593 (
594 entry.is_anonymous(),
595 entry.cost,
596 !entry.local,
597 entry.hops.len(),
598 fnv_key(prefix.as_str(), entry.hops.iter().copied()),
599 Reverse(entry.id),
600 )
601}
602
603type RouteMeta = (Hops, Cost, Source);
605
606type AnnounceMeta = (RouteMeta, Option<Vec<Pattern>>);
613
614enum PendingUpdate {
615 Announce(AnnounceMeta),
616 Unannounce(AnnounceMeta),
617 UnannounceAnnounce { old: AnnounceMeta, new: AnnounceMeta },
618}
619
620#[derive(Default)]
625struct OriginConsumerState {
626 pending: BTreeMap<PathOwned, PendingUpdate>,
627 delivered: BTreeSet<PathOwned>,
632 ended: bool,
635}
636
637impl OriginConsumerState {
638 fn apply_announce(&mut self, prefix: PathOwned, meta: RouteMeta, captures: Option<Vec<Pattern>>) {
639 let meta = (meta, captures);
640 let new = match self.pending.remove(&prefix) {
641 None | Some(PendingUpdate::Announce(_)) => PendingUpdate::Announce(meta),
643 Some(PendingUpdate::Unannounce(old) | PendingUpdate::UnannounceAnnounce { old, .. }) => {
645 PendingUpdate::UnannounceAnnounce { old, new: meta }
646 }
647 };
648 self.pending.insert(prefix, new);
649 }
650
651 fn apply_unannounce(&mut self, prefix: PathOwned, last: RouteMeta, captures: Option<Vec<Pattern>>) {
652 let last = (last, captures);
653 match self.pending.remove(&prefix) {
654 Some(PendingUpdate::Announce(_)) if !self.delivered.contains(&prefix) => {}
657 None | Some(PendingUpdate::Announce(_) | PendingUpdate::Unannounce(_)) => {
660 self.pending.insert(prefix, PendingUpdate::Unannounce(last));
661 }
662 Some(PendingUpdate::UnannounceAnnounce { old, .. }) => {
665 self.pending.insert(prefix, PendingUpdate::Unannounce(old));
666 }
667 }
668 }
669
670 fn take(&mut self) -> Option<AnnounceUpdate> {
672 let prefix = self.pending.keys().next()?.clone();
673 let ((meta, captures), kind) = match self.pending.remove(&prefix).unwrap() {
674 PendingUpdate::Announce(meta) => {
675 let kind = match self.delivered.insert(prefix.clone()) {
677 true => AnnounceKind::Announced,
678 false => AnnounceKind::Updated,
679 };
680 (meta, kind)
681 }
682 PendingUpdate::Unannounce(meta) => {
683 self.delivered.remove(&prefix);
684 (meta, AnnounceKind::Retracted)
685 }
686 PendingUpdate::UnannounceAnnounce { old, new } => {
687 self.delivered.remove(&prefix);
690 self.pending.insert(prefix.clone(), PendingUpdate::Announce(new));
691 (old, AnnounceKind::Retracted)
692 }
693 };
694 Some(AnnounceUpdate {
695 prefix,
696 captures,
697 route: Route {
698 hops: meta.0,
699 cost: meta.1,
700 via: Hop::UNKNOWN,
701 source: meta.2,
702 },
703 kind,
704 })
705 }
706}
707
708struct RouteEntry {
710 id: u64,
711 prefix: PathOwned,
712 scope: Patterns,
715 hops: Hops,
716 cost: Cost,
717 via: Hop,
721 local: bool,
724 peer: bool,
727 server: Option<kio::Shared<ServeState>>,
731 source: Option<broadcast::Consumer>,
735 advertised: bool,
739 claim: Pattern,
745}
746
747impl RouteEntry {
748 fn is_anonymous(&self) -> bool {
749 self.hops.iter().any(|hop| *hop == Hop::UNKNOWN)
750 }
751
752 fn entered(&self) -> Source {
754 match self.peer {
755 true => Source::Peer(self.via),
756 false => Source::Local,
757 }
758 }
759
760 fn serves(&self, path: &Path) -> bool {
764 self.server.is_some() || (self.source.is_some() && self.prefix == *path)
765 }
766
767 fn qualifies(&self, pin: Pin) -> bool {
769 match pin {
770 Pin::Any => true,
771 Pin::Local => self.local,
772 Pin::Publisher(first) => self.hops.iter().next() == Some(&first),
773 Pin::Route(id) => self.id == id,
774 }
775 }
776
777 fn visible_to(&self, exclude: Option<Hop>) -> bool {
782 match exclude {
783 Some(peer) if peer != Hop::UNKNOWN => self.via != peer && !self.hops.contains(&peer),
784 _ => true,
785 }
786 }
787
788 fn overlaps(&self, allowed: &Patterns) -> bool {
790 self.scope.iter().any(|scope| {
791 scope
792 .intersect(&self.claim)
793 .is_ok_and(|scoped| scoped.iter().any(|restriction| allowed.overlaps(restriction)))
794 })
795 }
796}
797
798fn prefix_claim(prefix: &Path) -> Result<Pattern, InvalidPattern> {
800 if prefix.parts().count() == Path::MAX_PARTS {
801 Pattern::literal(prefix.as_str())
802 } else {
803 Pattern::subtree(prefix.as_str())
804 }
805}
806
807#[derive(Default)]
812struct ServeState {
813 requests: Requests<PathOwned, kio::Producer<PendingBroadcast>>,
816
817 served: WeakCache<PathOwned, broadcast::WeakConsumer>,
823
824 closed: bool,
827}
828
829type FrontKey = (PathOwned, Horizon);
834
835#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
837struct Horizon {
838 exclude: Option<Hop>,
842 local: bool,
844}
845
846impl Horizon {
847 fn admits(&self, entry: &RouteEntry) -> bool {
849 !(self.local && entry.peer) && entry.visible_to(self.exclude)
850 }
851}
852
853#[derive(Clone)]
856struct RemoteFront {
857 request: kio::Producer<PendingBroadcast>,
861 broadcast: broadcast::WeakConsumer,
864 pin: kio::Lock<Pin>,
867}
868
869type CursorRoute = (u64, RouteMeta, bool, Option<Vec<Pattern>>);
871
872impl WeakEntry for RemoteFront {
873 fn is_closed(&self) -> bool {
874 self.broadcast.is_closed()
875 }
876
877 fn same_channel(&self, other: &Self) -> bool {
878 self.broadcast.same_channel(&other.broadcast)
879 }
880}
881
882struct TableCursor {
885 root: PathOwned,
887 allowed: Patterns,
889 heads: Vec<PathOwned>,
893 horizon: Horizon,
895 hidden: Hidden,
897 state: kio::Producer<OriginConsumerState>,
899 current: HashMap<PathOwned, CursorRoute>,
904}
905
906impl TableCursor {
907 fn presented(&self, prefix: &Path, claim: &Pattern) -> Option<PathOwned> {
913 if !self.allowed.overlaps(claim) {
914 return None;
915 }
916
917 if let Some(relative) = prefix.strip_prefix(&self.root) {
918 return Some(relative.to_owned());
919 }
920 self.root.has_prefix(prefix).then(PathOwned::default)
921 }
922
923 fn captures(&self, prefix: &Path) -> Option<Vec<Pattern>> {
926 let literal = Pattern::literal(prefix.as_str()).ok()?;
927 self.allowed
928 .iter()
929 .filter_map(|allowed| {
930 allowed
931 .captures(&literal)
932 .map(|captures| (allowed.specificity(), captures))
933 })
934 .max_by_key(|(specificity, _)| *specificity)
935 .map(|(_, captures)| captures)
936 }
937
938 fn visible(&self, entry: &RouteEntry) -> bool {
941 entry.advertised && self.horizon.admits(entry) && entry.overlaps(&self.allowed) && self.discovers(&entry.prefix)
942 }
943
944 fn discovers(&self, prefix: &Path) -> bool {
946 (self.hidden.include || !hides(&self.heads, prefix))
947 && self.hidden.beyond.as_ref().is_none_or(|outer| hides(outer, prefix))
948 }
949}
950
951#[derive(Clone)]
953struct OriginScope {
954 allowed: Patterns,
956}
957
958impl OriginScope {
959 fn empty() -> Self {
961 Self {
962 allowed: Patterns::new(),
963 }
964 }
965
966 fn narrow(&self, patterns: &Patterns) -> Option<Self> {
968 let allowed = self.allowed.intersect(patterns).ok()?;
969 if allowed.is_empty() {
970 None
971 } else {
972 Some(Self { allowed })
973 }
974 }
975
976 fn permits(&self, path: &Path) -> bool {
978 self.allowed.matches(path.as_str())
979 }
980
981 fn relative(&self, root: &Path) -> Patterns {
983 self.allowed.rebase(root.as_str())
984 }
985}
986
987impl Default for OriginScope {
988 fn default() -> Self {
989 Self {
990 allowed: Patterns::from(Pattern::all()),
991 }
992 }
993}
994
995pub(crate) fn interest_prefixes(allowed: &Patterns) -> Vec<PathOwned> {
998 let mut heads: Vec<PathOwned> = allowed
999 .iter()
1000 .map(|pattern| Path::new(pattern.head()).to_owned())
1001 .collect();
1002 heads.sort();
1003 heads.dedup();
1004 let covered = heads.clone();
1005 heads.retain(|head| !covered.iter().any(|other| other != head && head.has_prefix(other)));
1006 heads
1007}
1008
1009#[derive(Clone, Debug, Default, PartialEq, Eq)]
1016pub(crate) struct Hidden {
1017 include: bool,
1019 beyond: Option<Vec<PathOwned>>,
1022}
1023
1024fn hides(heads: &[PathOwned], prefix: &Path) -> bool {
1027 heads
1028 .iter()
1029 .any(|head| prefix.strip_prefix(head).is_some_and(|below| below.is_hidden()))
1030}
1031
1032#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1034pub enum AnnounceKind {
1035 Announced,
1037 Updated,
1039 Retracted,
1041}
1042
1043impl AnnounceKind {
1044 pub fn is_active(self) -> bool {
1046 !matches!(self, Self::Retracted)
1047 }
1048}
1049
1050#[derive(Clone, Debug)]
1058pub struct AnnounceUpdate {
1059 pub prefix: PathOwned,
1061 pub captures: Option<Vec<Pattern>>,
1064 pub route: Route,
1067 pub kind: AnnounceKind,
1069}
1070
1071#[derive(Clone)]
1073pub struct Producer {
1074 hop: Hop,
1077
1078 scope: OriginScope,
1080
1081 root: PathOwned,
1083
1084 shared: kio::Shared<OriginState>,
1087
1088 pool: cache::Pool,
1091
1092 cache_duration: Duration,
1095
1096 default_max_age: Duration,
1099
1100 stats: stats::Session,
1104
1105 peer: bool,
1108
1109 tasks: Tasks,
1113
1114 timers: Clock,
1116}
1117
1118impl Producer {
1119 pub fn new(config: Config) -> (Self, Driver) {
1126 let (tasks, set) = TaskSet::new();
1127 let scope = OriginScope::default();
1128 let shared = kio::Shared::<OriginState>::default();
1129 let timers = Clock::default();
1130 let pool = config.pool.clone();
1131 let producer = Self {
1132 hop: config.hop,
1133 scope: scope.clone(),
1134 root: PathOwned::default(),
1135 shared: shared.clone(),
1136 pool: config.pool,
1137 cache_duration: config.cache_duration,
1138 default_max_age: config.default_max_age,
1139 stats: stats::Session::default(),
1140 peer: false,
1141 tasks,
1142 timers: timers.clone(),
1143 };
1144 let driver = Driver {
1145 state: DriverState {
1146 set,
1147 shared,
1148 done: false,
1149 },
1150 timers,
1151 pool,
1152 };
1153 (producer, driver)
1154 }
1155
1156 pub fn with_stats(mut self, session: stats::Session) -> Self {
1160 self.stats = session;
1161 self
1162 }
1163
1164 pub fn peer(mut self) -> Self {
1172 self.peer = true;
1173 self
1174 }
1175
1176 pub fn config(&self) -> Config {
1178 Config {
1179 hop: self.hop,
1180 pool: self.pool.clone(),
1181 cache_duration: self.cache_duration,
1182 default_max_age: self.default_max_age,
1183 }
1184 }
1185
1186 pub fn hop(&self) -> Hop {
1188 self.hop
1189 }
1190
1191 pub(crate) fn default_max_age(&self) -> Duration {
1194 self.default_max_age
1195 }
1196
1197 pub(crate) fn empty(hop: Hop) -> Self {
1202 let (tasks, _) = TaskSet::new();
1205 Self {
1206 hop,
1207 scope: OriginScope::empty(),
1208 root: PathOwned::default(),
1209 shared: kio::Shared::default(),
1210 pool: cache::Pool::default(),
1211 cache_duration: Duration::MAX,
1212 default_max_age: track::DEFAULT_MAX_AGE,
1213 stats: stats::Session::default(),
1214 peer: false,
1215 tasks,
1216 timers: Clock::default(),
1217 }
1218 }
1219
1220 pub fn create_broadcast(&self, path: impl AsPath) -> Result<broadcast::Producer, Error> {
1253 let path = path.as_path();
1254
1255 let full = self.root.join(&path).to_owned();
1256 if !self.scope.permits(&full) {
1257 return Err(Error::Unauthorized);
1258 }
1259 if full.parts().count() > Path::MAX_PARTS {
1263 return Err(BoundsExceeded.into());
1264 }
1265 let claim = prefix_claim(&full)?;
1268
1269 let ingress = self.stats.ingress(&full);
1271
1272 let announcing = Announcing {
1277 hop: self.hop,
1278 shared: self.shared.clone(),
1279 requested: full.clone(),
1280 prefixes: vec![(full.clone(), claim)],
1281 scope: self.scope.allowed.clone(),
1282 local: true,
1283 peer: self.peer,
1284 stats: self.stats.clone(),
1285 };
1286 let info = broadcast::Info {
1287 pool: self.pool.clone(),
1288 cache_duration: self.cache_duration,
1289 path: full,
1290 };
1291 let source = info.produce().with_stats(ingress.clone());
1292 let entry = announcing.announce(
1293 Route::default(),
1294 Serving {
1295 server: None,
1296 source: Some(source.consume()),
1297 advertised: false,
1298 },
1299 )?;
1300 Ok(source.with_announcer(Announcer {
1301 entry,
1302 ingress,
1303 _keepalive: self.tasks.keepalive(),
1304 }))
1305 }
1306
1307 pub fn publish(&self, path: impl AsPath, route: Route) -> Result<broadcast::Producer, Error> {
1309 let broadcast = self.create_broadcast(path)?;
1310 broadcast.announce(route)?;
1311 Ok(broadcast)
1312 }
1313
1314 pub(crate) fn create_source(&self, path: impl AsPath) -> broadcast::Producer {
1320 let path = path.as_path();
1321 let full = self.root.join(&path).to_owned();
1322 let ingress = self.stats.ingress(&full);
1323 broadcast::Info {
1324 pool: self.pool.clone(),
1325 cache_duration: self.cache_duration,
1326 path: full,
1327 }
1328 .produce()
1329 .with_stats(ingress)
1330 }
1331
1332 #[cfg(test)]
1341 pub(crate) fn announce(&self, prefix: impl AsPath, route: Route) -> Result<AnnounceProducer, Error> {
1342 Announcing::new(self, prefix)?.announce(
1343 route,
1344 Serving {
1345 server: None,
1346 source: None,
1347 advertised: true,
1348 },
1349 )
1350 }
1351
1352 pub fn dynamic(&self, prefix: impl AsPath, route: Route) -> Result<Dynamic, Error> {
1374 let announcing = Announcing::new(self, prefix)?;
1375 let serve = kio::Shared::<ServeState>::default();
1376 serve.lock().requests.add_handler();
1377 let announcement = announcing.announce(
1378 route,
1379 Serving {
1380 server: Some(serve.clone()),
1381 source: None,
1382 advertised: true,
1383 },
1384 )?;
1385 Ok(Dynamic {
1386 announcement,
1387 state: serve,
1388 })
1389 }
1390
1391 pub fn scope(&self, root: impl AsPath, patterns: &Patterns) -> Result<Producer, Error> {
1398 let root = self.root.join(root).to_owned();
1399 let rooted = patterns.rooted(root.as_str()).map_err(|_| BoundsExceeded)?;
1400 let scope = self.scope.narrow(&rooted).ok_or(Error::Unauthorized)?;
1401 Ok(Producer {
1402 hop: self.hop,
1403 scope,
1404 root,
1405 shared: self.shared.clone(),
1406 pool: self.pool.clone(),
1407 cache_duration: self.cache_duration,
1408 default_max_age: self.default_max_age,
1409 stats: self.stats.clone(),
1410 peer: self.peer,
1411 tasks: self.tasks.clone(),
1412 timers: self.timers.clone(),
1413 })
1414 }
1415
1416 pub fn consume(&self) -> Consumer {
1421 Consumer::from_producer(self, stats::Session::default())
1424 }
1425
1426 pub fn root(&self) -> &Path<'_> {
1428 &self.root
1429 }
1430
1431 pub fn allowed(&self) -> Patterns {
1433 self.scope.relative(&self.root)
1434 }
1435
1436 pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
1438 self.root.join(path)
1439 }
1440}
1441
1442struct Announcing {
1446 hop: Hop,
1447 shared: kio::Shared<OriginState>,
1448 requested: PathOwned,
1450 prefixes: Vec<(PathOwned, Pattern)>,
1454 scope: Patterns,
1456 local: bool,
1457 peer: bool,
1459 stats: stats::Session,
1460}
1461
1462impl Announcing {
1463 fn new(producer: &Producer, prefix: impl AsPath) -> Result<Self, Error> {
1465 let requested = producer.root.join(prefix.as_path()).to_owned();
1466 if requested.parts().count() > Path::MAX_PARTS {
1467 return Err(BoundsExceeded.into());
1468 }
1469 let claim = prefix_claim(&requested)?;
1470 if !producer.scope.allowed.overlaps(&claim) {
1471 return Err(Error::Unauthorized);
1472 }
1473 Ok(Self {
1474 hop: producer.hop,
1475 shared: producer.shared.clone(),
1476 requested: requested.clone(),
1477 prefixes: vec![(requested, claim)],
1478 scope: producer.scope.allowed.clone(),
1479 local: false,
1480 peer: producer.peer,
1481 stats: producer.stats.clone(),
1482 })
1483 }
1484
1485 fn announce(&self, route: Route, serving: Serving) -> Result<AnnounceProducer, Error> {
1486 debug_assert!(
1487 !route.hops.contains(&self.hop),
1488 "announce called with a looping hop chain",
1489 );
1490
1491 let via = route.via;
1492
1493 let mut shared = self.shared.lock();
1494 if shared.closed {
1495 return Err(Error::Closed);
1496 }
1497
1498 let mut entries = Vec::with_capacity(self.prefixes.len());
1499 for (prefix, claim) in &self.prefixes {
1500 let id = shared.next_route;
1501 shared.next_route += 1;
1502 shared.routes.insert(RouteEntry {
1503 id,
1504 prefix: prefix.clone(),
1505 scope: self.scope.clone(),
1506 hops: route.hops.clone(),
1507 cost: route.cost,
1508 via,
1509 local: self.local,
1510 peer: self.peer,
1511 server: serving.server.clone(),
1512 source: serving.source.clone(),
1513 advertised: serving.advertised,
1514 claim: claim.clone(),
1515 });
1516 shared.sync_route(prefix, claim);
1517 entries.push((prefix.clone(), id));
1518 }
1519 drop(shared);
1520
1521 let guard = serving
1523 .advertised
1524 .then(|| self.stats.ingress(&self.requested).announce());
1525
1526 Ok(AnnounceProducer {
1527 shared: self.shared.clone(),
1528 entries,
1529 guard,
1530 })
1531 }
1532}
1533
1534struct Serving {
1536 server: Option<kio::Shared<ServeState>>,
1537 source: Option<broadcast::Consumer>,
1538 advertised: bool,
1539}
1540
1541pub(crate) struct Announcer {
1548 entry: AnnounceProducer,
1549 ingress: stats::Scope,
1551 _keepalive: Keepalive,
1555}
1556
1557impl Announcer {
1558 pub(crate) fn announce(&mut self, route: Route) -> Result<(), Error> {
1560 self.entry.update(route)?;
1561 if self.entry.guard.is_none() {
1562 self.entry.guard = Some(self.ingress.announce());
1563 }
1564 Ok(())
1565 }
1566
1567 pub(crate) fn withdraw(&mut self) {
1569 self.entry.withdraw();
1570 self.entry.guard = None;
1571 }
1572}
1573
1574#[must_use = "dropping an announcement retracts the route"]
1581pub(crate) struct AnnounceProducer {
1582 shared: kio::Shared<OriginState>,
1583 entries: Vec<(PathOwned, u64)>,
1586 guard: Option<stats::Announce>,
1588}
1589
1590impl AnnounceProducer {
1591 pub fn update(&self, route: Route) -> Result<(), Error> {
1599 let mut shared = self.shared.lock();
1600 if shared.closed {
1601 return Err(Error::Closed);
1602 }
1603 for (prefix, id) in &self.entries {
1604 let Some(entry) = shared.routes.entry_mut(prefix, *id) else {
1606 return Err(Error::Closed);
1607 };
1608 entry.hops = route.hops.clone();
1609 entry.cost = route.cost;
1610 entry.via = route.via;
1611 entry.advertised = true;
1612 let claim = entry.claim.clone();
1613 shared.sync_route(prefix, &claim);
1614 }
1615 Ok(())
1616 }
1617
1618 fn withdraw(&self) {
1623 let mut shared = self.shared.lock();
1624 for (prefix, id) in &self.entries {
1625 let Some(entry) = shared.routes.entry_mut(prefix, *id) else {
1626 continue;
1627 };
1628 if !entry.advertised {
1629 continue;
1630 }
1631 entry.advertised = false;
1632 let claim = entry.claim.clone();
1633 shared.sync_route(prefix, &claim);
1634 }
1635 }
1636
1637 fn retract(&self) {
1640 let mut shared = self.shared.lock();
1641 for (prefix, id) in &self.entries {
1642 let Some(entry) = shared.routes.remove(prefix, *id) else {
1643 continue;
1644 };
1645 if let Some(server) = &entry.server {
1648 let mut server = server.lock();
1649 server.closed = true;
1650 for producer in server.requests.drain_all() {
1651 if let Ok(mut request) = producer.write() {
1652 request.resolved.get_or_insert(Err(Error::Unroutable));
1653 }
1654 }
1655 }
1656 shared.sync_route(&entry.prefix, &entry.claim);
1657 }
1658 }
1659}
1660
1661impl Drop for AnnounceProducer {
1662 fn drop(&mut self) {
1663 self.retract();
1664 }
1665}
1666
1667#[must_use = "poll the driver or the origin makes no progress"]
1679pub struct Driver {
1680 state: DriverState,
1681 timers: Clock,
1683 pool: cache::Pool,
1686}
1687
1688struct DriverState {
1690 set: TaskSet,
1692 shared: kio::Shared<OriginState>,
1695 done: bool,
1697}
1698
1699impl Driver {
1700 pub fn poll(&mut self, now: Instant, waiter: &kio::Waiter) -> Result<Option<Instant>, Error> {
1706 self.timers.advance(now);
1707 let result = self.state.poll(waiter);
1708 let gc = self.pool.gc(now);
1709 if result.is_ready() {
1710 return Err(Error::Closed);
1711 }
1712 Ok(self.timers.timeout().into_iter().chain(gc).min())
1713 }
1714}
1715
1716impl crate::time::Driver for Driver {
1717 fn poll(&mut self, now: Instant, waiter: &kio::Waiter) -> Result<Option<Instant>, Error> {
1718 self.poll(now, waiter)
1719 }
1720}
1721
1722impl DriverState {
1723 fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
1724 if !self.done {
1728 ready!(self.set.poll(waiter));
1729 self.done = true;
1730 }
1731 Poll::Ready(())
1732 }
1733
1734 fn teardown(&mut self) {
1738 drop(std::mem::replace(&mut self.set, TaskSet::owned()));
1741
1742 let (servers, cursors, fronts) = {
1747 let mut shared = self.shared.lock();
1748 shared.closed = true;
1749 shared.routes.poke_all();
1751 let servers: Vec<_> = shared
1752 .routes
1753 .entries()
1754 .filter_map(|entry| entry.server.clone())
1755 .collect();
1756 let cursors: Vec<_> = shared.cursors.values().map(|cursor| cursor.state.clone()).collect();
1757 let fronts: Vec<_> = shared.fronts.values().map(|front| front.request.clone()).collect();
1758 (servers, cursors, fronts)
1759 };
1760
1761 for producer in fronts {
1764 if let Ok(mut request) = producer.write() {
1765 request.resolved.get_or_insert(Err(Error::Dropped));
1766 }
1767 }
1768 for server in servers {
1772 let mut server = server.lock();
1773 server.closed = true;
1774 for producer in server.requests.drain_all() {
1775 if let Ok(mut request) = producer.write() {
1776 request.resolved.get_or_insert(Err(Error::Dropped));
1777 }
1778 }
1779 }
1780
1781 for state in cursors {
1784 if let Ok(mut state) = state.write() {
1785 state.ended = true;
1786 }
1787 }
1788 }
1789}
1790
1791impl Drop for DriverState {
1792 fn drop(&mut self) {
1793 self.teardown();
1794 }
1795}
1796
1797const TRACK_IDLE_LINGER: Duration = Duration::from_secs(30);
1810
1811struct WarmCopy {
1816 track: track::Producer,
1817 _dynamic: track::Dynamic,
1818}
1819
1820impl Drop for WarmCopy {
1821 fn drop(&mut self) {
1822 let _ = self.track.finish();
1823 }
1824}
1825
1826fn warm_copy(source: &track::Consumer) -> Option<WarmCopy> {
1828 let info = source.cached_info()?;
1829 let mut track = track::Producer::new(Arc::new(source.broadcast().clone()), source.name(), info);
1830 for (group, visible) in source.cached_groups() {
1831 if group.is_finished() {
1835 let _ = track.adopt_group(group, visible);
1836 }
1837 }
1838 let dynamic = track.dynamic();
1839 Some(WarmCopy {
1840 track,
1841 _dynamic: dynamic,
1842 })
1843}
1844
1845struct FrontTask {
1847 shared: kio::Shared<OriginState>,
1849 broadcast: broadcast::Producer,
1851 path: PathOwned,
1853 horizon: Horizon,
1855 watch: Watch,
1857 request: kio::Producer<PendingBroadcast>,
1859 pin: kio::Lock<Pin>,
1861 timers: Clock,
1862}
1863
1864struct TrackIo {
1867 resume: super::resume::Producer,
1868 staged: Option<(u64, track::Consumer)>,
1870 query: Option<(u64, track::Consumer, track::Querying)>,
1872 copy: Option<(u64, track::Consumer)>,
1874 edge: Option<track::Position>,
1879 warm: Option<WarmCopy>,
1882 used: bool,
1884}
1885
1886async fn run_front(task: FrontTask) {
1890 let FrontTask {
1891 shared,
1892 broadcast,
1893 path,
1894 horizon,
1895 watch,
1896 request,
1897 pin,
1898 timers,
1899 } = task;
1900
1901 enum Step {
1903 Assigned(Arc<str>, super::resume::Producer),
1904 Resolved(u64, Result<broadcast::Consumer, Error>),
1905 SourceClosed(u64),
1906 Info(Arc<str>, u64, Result<track::Info, Error>),
1907 Ended(Arc<str>, u64, Result<(), Error>),
1908 Demand(Arc<str>),
1909 Deadline,
1910 Table,
1911 }
1912
1913 let mut front = Front::new(TRACK_IDLE_LINGER);
1914 let mut sources: HashMap<u64, broadcast::Consumer> = HashMap::new();
1915 let mut next_source = 0u64;
1916 let mut upstream: Option<(u64, kio::Consumer<PendingBroadcast>)> = None;
1918 let mut tracks: HashMap<Arc<str>, TrackIo> = HashMap::new();
1919 let mut deadline = crate::runtime::Deadline::new(&timers);
1920 let mut seen = 0;
1922 let mut events: VecDeque<Event> = VecDeque::new();
1923
1924 let select = |front: &mut Front, sources: &HashMap<u64, broadcast::Consumer>, seen: &mut u64| -> Event {
1927 let table = shared.read();
1928 if table.closed {
1929 return Event::Closed;
1930 }
1931 *seen = watch.seen();
1933 front.retain_routes(|route| table.routes.covers(&path.as_path(), route));
1934 let best = table
1935 .best_route(&path.as_path(), horizon, front.pin(), front.refused_routes())
1936 .map(|entry| Candidate {
1937 route: entry.id,
1938 first: entry.hops.iter().next().copied(),
1939 local: entry.local,
1940 });
1941 let serving_closing = front
1942 .serving()
1943 .and_then(|id| sources.get(&id))
1944 .is_some_and(|source| source.is_closing());
1945 Event::Selected { best, serving_closing }
1946 };
1947
1948 events.push_back(select(&mut front, &sources, &mut seen));
1949
1950 loop {
1951 while let Some(event) = events.pop_front() {
1952 for action in front.step(event) {
1953 match action {
1954 Action::Reselect => events.push_back(select(&mut front, &sources, &mut seen)),
1955 Action::Request { route } => {
1956 let found = {
1958 let table = shared.read();
1959 table
1960 .routes
1961 .covering(&path.as_path())
1962 .find(|entry| entry.id == route)
1963 .map(|entry| {
1964 (
1965 Candidate {
1966 route,
1967 first: entry.hops.iter().next().copied(),
1968 local: entry.local,
1969 },
1970 entry.source.clone(),
1971 entry.server.clone(),
1972 )
1973 })
1974 };
1975 let Some((candidate, source, server)) = found else {
1976 events.push_back(Event::Resolved {
1977 route,
1978 result: Err(Refusal {
1979 err: Error::Unroutable,
1980 standing: false,
1981 }),
1982 });
1983 continue;
1984 };
1985 front.identify(candidate);
1986 *pin.lock() = front.pin();
1987 if let Some(source) = source {
1988 let id = next_source;
1989 next_source += 1;
1990 sources.insert(id, source);
1991 events.push_back(Event::Resolved { route, result: Ok(id) });
1992 continue;
1993 }
1994 let Some(server) = server else {
1995 events.push_back(Event::Resolved {
1996 route,
1997 result: Err(Refusal {
1998 err: Error::Unroutable,
1999 standing: true,
2000 }),
2001 });
2002 continue;
2003 };
2004 let mut serve = server.lock();
2005 if serve.closed {
2006 drop(serve);
2009 events.push_back(Event::Resolved {
2010 route,
2011 result: Err(Refusal {
2012 err: Error::Unroutable,
2013 standing: true,
2014 }),
2015 });
2016 continue;
2017 }
2018 if let Some(weak) = serve.served.get(&path) {
2021 drop(serve);
2022 let id = next_source;
2023 next_source += 1;
2024 sources.insert(id, weak.consume());
2025 events.push_back(Event::Resolved { route, result: Ok(id) });
2026 continue;
2027 }
2028 let pending = match serve.requests.join(&path) {
2029 Some(producer) => producer.consume(),
2030 None => {
2031 let producer = kio::Producer::<PendingBroadcast>::default();
2032 let consumer = producer.consume();
2033 match serve.requests.insert(path.clone(), producer) {
2034 Ok(()) => consumer,
2035 Err(_) => {
2038 drop(serve);
2039 events.push_back(Event::Resolved {
2040 route,
2041 result: Err(Refusal {
2042 err: Error::Unroutable,
2043 standing: true,
2044 }),
2045 });
2046 continue;
2047 }
2048 }
2049 }
2050 };
2051 upstream = Some((route, pending));
2052 }
2053 Action::Detach { source } => {
2054 sources.remove(&source);
2055 for io in tracks.values_mut() {
2058 if io.copy.as_ref().is_some_and(|(s, _)| *s == source) {
2059 io.copy = None;
2060 }
2061 if io.query.as_ref().is_some_and(|(s, ..)| *s == source) {
2062 io.query = None;
2063 }
2064 if io.staged.as_ref().is_some_and(|(s, _)| *s == source) {
2065 io.staged = None;
2066 }
2067 }
2068 }
2069 Action::Resolve => {
2070 if let Ok(mut pending) = request.write() {
2071 pending.resolved.get_or_insert(Ok(broadcast.consume()));
2072 }
2073 }
2074 Action::Query { track: name, source } => {
2075 let Some(io) = tracks.get_mut(&name) else { continue };
2076 let closing = sources.get(&source).is_some_and(|s| s.is_closing());
2077 match sources.get(&source).map(|s| s.track(&name)) {
2078 Some(Ok(copy)) => {
2079 let query = copy.query().into_inner();
2082 io.query = Some((source, copy, query));
2083 }
2084 Some(Err(err)) => events.push_back(Event::TrackInfo {
2085 track: name,
2086 source,
2087 closing,
2088 result: Err(err),
2089 }),
2090 None => {}
2091 }
2092 }
2093 Action::Splice { track: name, source } => {
2094 let Some(io) = tracks.get_mut(&name) else { continue };
2095 let Some((staged, copy)) = io.staged.take() else {
2096 continue;
2097 };
2098 if staged != source {
2099 continue;
2100 }
2101 if let Err(err) = io.resume.takeover(©) {
2102 let _ = io.resume.abort(err);
2106 tracks.remove(&name);
2107 continue;
2108 }
2109 io.warm = None;
2110 io.edge = io.resume.resume_position();
2113 io.copy = Some((source, copy));
2114 }
2115 Action::Park { track: name } => {
2116 let Some(io) = tracks.get_mut(&name) else { continue };
2117 let Some((_, copy)) = io.copy.take() else { continue };
2118 let warm = warm_copy(©);
2122 drop(copy);
2123 if io.resume.release().is_err() {
2124 tracks.remove(&name);
2125 continue;
2126 }
2127 if let Some(warm) = warm {
2128 if let Err(err) = io.resume.takeover(&warm.track) {
2129 let _ = io.resume.abort(err);
2130 tracks.remove(&name);
2131 continue;
2132 }
2133 io.warm = Some(warm);
2134 }
2135 }
2136 Action::Release { track: name } => {
2137 let Some(io) = tracks.get_mut(&name) else { continue };
2138 io.warm = None;
2139 if io.resume.release().is_err() {
2140 tracks.remove(&name);
2141 }
2142 }
2143 Action::Finish { track: name } => {
2144 if let Some(mut io) = tracks.remove(&name) {
2145 let _ = io.resume.finish();
2146 }
2147 }
2148 Action::Abort { track: name, err } => {
2149 if let Some(mut io) = tracks.remove(&name) {
2150 tracing::debug!(name = %name, %err, "aborting track");
2151 let _ = io.resume.abort(err);
2152 }
2153 }
2154 Action::Arm { at } => deadline.set(at),
2155 Action::End { err } => {
2156 if let Ok(mut pending) = request.write() {
2157 pending.resolved.get_or_insert(Err(err.clone()));
2158 }
2159 broadcast.finish();
2166 broadcast.release_spliced(err.clone());
2167 for (_, mut io) in tracks.drain() {
2168 let waiting = io.staged.take().map(|(_, copy)| copy);
2172 let waiting = waiting.or_else(|| io.query.take().map(|(_, copy, _)| copy));
2173 if let Some(copy) = waiting
2174 && io.resume.is_used()
2175 {
2176 if io.resume.takeover(©).is_err() {
2177 continue;
2178 }
2179 io.warm = None;
2180 }
2181 if !io.resume.is_used() || !io.resume.is_spliced() || io.warm.is_some() {
2183 let _ = io.resume.abort(err.clone());
2184 }
2185 }
2186 return;
2187 }
2188 }
2189 }
2190 }
2191
2192 let step = kio::wait(|waiter| {
2193 if let Poll::Ready((name, resume)) = broadcast.poll_spliced_assigned(waiter) {
2194 return Poll::Ready(Step::Assigned(name, resume));
2195 }
2196 if let Some((route, pending)) = &upstream
2197 && let Poll::Ready(result) = pending.poll(waiter, |p| match &p.resolved {
2198 Some(result) => Poll::Ready(result.clone()),
2199 None => Poll::Pending,
2200 }) {
2201 return Poll::Ready(Step::Resolved(
2202 *route,
2203 match result {
2204 Ok(resolved) => resolved,
2205 Err(_closed) => Err(Error::Unroutable),
2208 },
2209 ));
2210 }
2211 if let Some(id) = front.serving()
2212 && let Some(source) = sources.get(&id)
2213 && source.poll_closed(waiter).is_ready()
2214 {
2215 return Poll::Ready(Step::SourceClosed(id));
2216 }
2217 for (name, io) in &tracks {
2218 if let Some((source, _, query)) = &io.query
2219 && let Poll::Ready(result) = query.poll(waiter)
2220 {
2221 return Poll::Ready(Step::Info(name.clone(), *source, result));
2222 }
2223 if let Some((source, copy)) = &io.copy
2224 && let Poll::Ready(result) = copy.poll_complete(waiter)
2225 {
2226 return Poll::Ready(Step::Ended(name.clone(), *source, result));
2227 }
2228 let edge = match io.used {
2230 true => io.resume.poll_unused(waiter),
2231 false => io.resume.poll_used(waiter),
2232 };
2233 if edge.is_ready() {
2234 return Poll::Ready(Step::Demand(name.clone()));
2235 }
2236 }
2237 if deadline.poll(waiter).is_ready() {
2238 return Poll::Ready(Step::Deadline);
2239 }
2240 watch.poll_changed(waiter, seen).map(|()| Step::Table)
2241 })
2242 .await;
2243
2244 let event = match step {
2245 Step::Assigned(name, resume) => {
2246 tracks.insert(
2247 name.clone(),
2248 TrackIo {
2249 resume,
2250 staged: None,
2251 query: None,
2252 copy: None,
2253 edge: None,
2254 warm: None,
2255 used: false,
2256 },
2257 );
2258 Event::TrackAssigned { track: name }
2259 }
2260 Step::Resolved(route, result) => {
2261 upstream = None;
2262 match result {
2263 Ok(source) => {
2264 let id = next_source;
2265 next_source += 1;
2266 sources.insert(id, source);
2267 Event::Resolved { route, result: Ok(id) }
2268 }
2269 Err(err) => {
2270 let standing =
2274 !matches!(err, Error::Unroutable) || shared.read().routes.covers(&path.as_path(), route);
2275 Event::Resolved {
2276 route,
2277 result: Err(Refusal { err, standing }),
2278 }
2279 }
2280 }
2281 }
2282 Step::SourceClosed(source) => Event::SourceClosed { source },
2283 Step::Info(name, source, result) => {
2284 let closing = sources.get(&source).is_some_and(|s| s.is_closing());
2285 let Some(io) = tracks.get_mut(&name) else { continue };
2286 let Some((_, copy, _)) = io.query.take() else { continue };
2287 let result = match result {
2290 Ok(info) => match copy.poll_complete(&kio::Waiter::noop()) {
2291 Poll::Ready(Err(err)) => Err(err),
2292 _ => Ok(info),
2293 },
2294 Err(err) => Err(err),
2295 };
2296 if result.is_ok() && io.used {
2299 io.staged = Some((source, copy));
2300 }
2301 Event::TrackInfo {
2302 track: name,
2303 source,
2304 closing,
2305 result,
2306 }
2307 }
2308 Step::Ended(name, source, result) => {
2309 let closing = sources.get(&source).is_some_and(|s| s.is_closing());
2310 let Some(io) = tracks.get_mut(&name) else { continue };
2311 io.copy = None;
2312 let delivered = io.resume.resume_position() != io.edge;
2313 Event::TrackEnded {
2314 track: name,
2315 source,
2316 closing,
2317 result,
2318 delivered,
2319 }
2320 }
2321 Step::Demand(name) => {
2322 let Some(io) = tracks.get_mut(&name) else { continue };
2323 io.used = io.resume.is_used();
2324 if !io.used {
2325 io.query = None;
2328 io.staged = None;
2329 }
2330 match io.used {
2331 true => Event::Used { track: name },
2332 false => Event::Unused {
2333 track: name,
2334 now: timers.now(),
2335 },
2336 }
2337 }
2338 Step::Deadline => {
2339 deadline.set(None);
2342 Event::Deadline { now: timers.now() }
2343 }
2344 Step::Table => select(&mut front, &sources, &mut seen),
2345 };
2346 events.push_back(event);
2347 }
2348}
2349
2350#[derive(Default)]
2355struct RouteTable {
2356 root: RouteNode,
2357}
2358
2359#[derive(Default)]
2362struct RouteNode {
2363 entries: Vec<RouteEntry>,
2365 cursors: Vec<ConsumerId>,
2367 cursors_below: usize,
2370 watches: Vec<(u64, kio::Producer<Watched>)>,
2373 watches_below: usize,
2376 children: HashMap<String, RouteNode>,
2377}
2378
2379#[derive(Default)]
2382struct Watched {
2383 generation: u64,
2384}
2385
2386struct Watch {
2392 shared: kio::Shared<OriginState>,
2393 path: PathOwned,
2394 id: u64,
2395 signal: kio::Consumer<Watched>,
2396}
2397
2398impl Watch {
2399 fn seen(&self) -> u64 {
2403 self.signal.read().generation
2404 }
2405
2406 fn poll_changed(&self, waiter: &kio::Waiter, seen: u64) -> Poll<()> {
2408 self.signal
2409 .poll(waiter, |watched| match watched.generation != seen {
2410 true => Poll::Ready(()),
2411 false => Poll::Pending,
2412 })
2413 .map(|_| ())
2414 }
2415}
2416
2417impl Drop for Watch {
2418 fn drop(&mut self) {
2419 self.shared.lock().routes.remove_watch(&self.path, self.id);
2420 }
2421}
2422
2423#[derive(Clone, Copy)]
2425struct Below {
2426 cursors: usize,
2427 watches: usize,
2428}
2429
2430impl Below {
2431 const NONE: Self = Self { cursors: 0, watches: 0 };
2432 const CURSOR: Self = Self { cursors: 1, watches: 0 };
2433 const WATCH: Self = Self { cursors: 0, watches: 1 };
2434}
2435
2436impl RouteNode {
2437 fn is_empty(&self) -> bool {
2439 self.entries.is_empty() && self.cursors.is_empty() && self.watches.is_empty() && self.children.is_empty()
2440 }
2441
2442 fn find<'a>(&self, mut parts: impl Iterator<Item = &'a str>) -> Option<&Self> {
2444 match parts.next() {
2445 None => Some(self),
2446 Some(part) => self.children.get(part)?.find(parts),
2447 }
2448 }
2449
2450 fn reach<'a>(&mut self, mut parts: impl Iterator<Item = &'a str>, below: Below) -> &mut Self {
2453 self.cursors_below += below.cursors;
2454 self.watches_below += below.watches;
2455 match parts.next() {
2456 None => self,
2457 Some(part) => self.children.entry(part.to_string()).or_default().reach(parts, below),
2458 }
2459 }
2460
2461 fn edit<'a, R>(
2465 &mut self,
2466 mut parts: impl Iterator<Item = &'a str>,
2467 below: Below,
2468 f: impl FnOnce(&mut Self) -> R,
2469 ) -> Option<R> {
2470 let result = match parts.next() {
2471 None => f(self),
2472 Some(part) => {
2473 let child = self.children.get_mut(part)?;
2474 let result = child.edit(parts, below, f)?;
2475 if child.is_empty() {
2476 self.children.remove(part);
2477 }
2478 result
2479 }
2480 };
2481 self.cursors_below -= below.cursors;
2482 self.watches_below -= below.watches;
2483 Some(result)
2484 }
2485
2486 fn poke(&self) {
2488 for (_, watch) in &self.watches {
2489 if let Ok(mut watched) = watch.write() {
2490 watched.generation += 1;
2491 }
2492 }
2493 }
2494
2495 fn poke_below(&self) {
2498 if self.watches_below == 0 {
2499 return;
2500 }
2501 self.poke();
2502 for child in self.children.values() {
2503 child.poke_below();
2504 }
2505 }
2506
2507 fn walk<'a>(&'a self, visit: &mut impl FnMut(&'a Self)) {
2509 visit(self);
2510 for child in self.children.values() {
2511 child.walk(visit);
2512 }
2513 }
2514
2515 fn collect_cursors(&self, out: &mut Vec<ConsumerId>) {
2517 if self.cursors_below == 0 {
2518 return;
2519 }
2520 out.extend(&self.cursors);
2521 for child in self.children.values() {
2522 child.collect_cursors(out);
2523 }
2524 }
2525}
2526
2527impl RouteTable {
2528 fn split(&self, path: &Path) -> (Vec<&RouteNode>, Option<&RouteNode>) {
2531 let mut above = Vec::new();
2532 let mut node = &self.root;
2533 for part in path.parts() {
2534 above.push(node);
2535 match node.children.get(part) {
2536 Some(child) => node = child,
2537 None => return (above, None),
2538 }
2539 }
2540 (above, Some(node))
2541 }
2542
2543 fn covering(&self, path: &Path) -> impl Iterator<Item = &RouteEntry> {
2545 let (above, at) = self.split(path);
2546 above.into_iter().chain(at).flat_map(|node| node.entries.iter())
2547 }
2548
2549 fn covers(&self, path: &Path, id: u64) -> bool {
2551 self.covering(path).any(|entry| entry.id == id)
2552 }
2553
2554 fn at(&self, prefix: &Path) -> impl Iterator<Item = &RouteEntry> {
2556 self.root
2557 .find(prefix.parts())
2558 .into_iter()
2559 .flat_map(|node| node.entries.iter())
2560 }
2561
2562 fn entries(&self) -> impl Iterator<Item = &RouteEntry> {
2564 let mut nodes = Vec::new();
2565 self.root.walk(&mut |node| nodes.push(node));
2566 nodes.into_iter().flat_map(|node| node.entries.iter())
2567 }
2568
2569 fn insert(&mut self, entry: RouteEntry) {
2571 let node = self.root.reach(entry.prefix.parts(), Below::NONE);
2572 node.entries.push(entry);
2573 }
2574
2575 fn entry_mut(&mut self, prefix: &Path, id: u64) -> Option<&mut RouteEntry> {
2577 let mut node = &mut self.root;
2578 for part in prefix.parts() {
2579 node = node.children.get_mut(part)?;
2580 }
2581 node.entries.iter_mut().find(|entry| entry.id == id)
2582 }
2583
2584 fn remove(&mut self, prefix: &Path, id: u64) -> Option<RouteEntry> {
2586 self.root
2587 .edit(prefix.parts(), Below::NONE, |node| {
2588 let index = node.entries.iter().position(|entry| entry.id == id)?;
2589 Some(node.entries.swap_remove(index))
2590 })
2591 .flatten()
2592 }
2593
2594 fn add_cursor(&mut self, head: &Path, id: ConsumerId) {
2596 self.root.reach(head.parts(), Below::CURSOR).cursors.push(id);
2597 }
2598
2599 fn remove_cursor(&mut self, head: &Path, id: ConsumerId) {
2602 self.root.edit(head.parts(), Below::CURSOR, |node| {
2603 node.cursors.retain(|cursor| *cursor != id)
2604 });
2605 }
2606
2607 fn add_watch(&mut self, path: &Path, id: u64) -> kio::Consumer<Watched> {
2609 let producer = kio::Producer::<Watched>::default();
2610 let consumer = producer.consume();
2611 self.root.reach(path.parts(), Below::WATCH).watches.push((id, producer));
2612 consumer
2613 }
2614
2615 fn remove_watch(&mut self, path: &Path, id: u64) {
2618 self.root.edit(path.parts(), Below::WATCH, |node| {
2619 node.watches.retain(|(watch, _)| *watch != id)
2620 });
2621 }
2622
2623 fn poke_below(&self, prefix: &Path) {
2625 if let (_, Some(node)) = self.split(prefix) {
2626 node.poke_below();
2627 }
2628 }
2629
2630 fn poke_all(&self) {
2632 self.root.walk(&mut |node| node.poke());
2633 }
2634
2635 fn cursors_touching(&self, prefix: &Path) -> Vec<ConsumerId> {
2639 let (above, at) = self.split(prefix);
2640 let mut cursors: Vec<ConsumerId> = above.iter().flat_map(|node| node.cursors.iter().copied()).collect();
2641 if let Some(node) = at {
2642 node.collect_cursors(&mut cursors);
2643 }
2644 cursors.sort_unstable();
2646 cursors.dedup();
2647 cursors
2648 }
2649}
2650
2651#[derive(Default)]
2658struct OriginState {
2659 routes: RouteTable,
2662 next_route: u64,
2663 next_watch: u64,
2664
2665 cursors: HashMap<ConsumerId, TableCursor>,
2669
2670 fronts: WeakCache<FrontKey, RemoteFront>,
2678
2679 closed: bool,
2682}
2683
2684impl OriginState {
2685 fn sync_route(&mut self, prefix: &Path, claim: &Pattern) {
2690 let routes = &self.routes;
2692 for id in routes.cursors_touching(prefix) {
2693 let Some(cursor) = self.cursors.get_mut(&id) else {
2694 continue;
2695 };
2696 if let Some(presented) = cursor.presented(prefix, claim) {
2697 Self::sync_cursor(routes, cursor, &presented);
2698 }
2699 }
2700 routes.poke_below(prefix);
2702 }
2703
2704 fn watch(&mut self, shared: &kio::Shared<OriginState>, path: &Path) -> Watch {
2706 let id = self.next_watch;
2707 self.next_watch += 1;
2708 let signal = self.routes.add_watch(path, id);
2709 Watch {
2710 shared: shared.clone(),
2711 path: path.to_owned(),
2712 id,
2713 signal,
2714 }
2715 }
2716
2717 fn sync_cursor(routes: &RouteTable, cursor: &mut TableCursor, presented: &PathOwned) {
2720 let candidates: Vec<&RouteEntry> = match presented.is_empty() {
2726 true => routes
2727 .covering(&cursor.root)
2728 .filter(|entry| cursor.visible(entry))
2729 .collect(),
2730 false => {
2731 let absolute = cursor.root.join(presented);
2732 routes.at(&absolute).filter(|entry| cursor.visible(entry)).collect()
2733 }
2734 };
2735 let most = candidates.iter().map(|entry| entry.prefix.len()).max();
2736 let best = most.and_then(|most| {
2737 candidates
2738 .into_iter()
2739 .filter(|entry| entry.prefix.len() == most)
2740 .min_by_key(|entry| route_order(&entry.prefix, entry))
2741 });
2742
2743 match best {
2744 Some(entry) => {
2745 let meta = (entry.hops.clone(), entry.cost, entry.entered());
2746 let served = entry.server.is_some();
2747 let captures = cursor.captures(&entry.prefix);
2748 let previous = cursor
2749 .current
2750 .insert(presented.clone(), (entry.id, meta.clone(), served, captures.clone()));
2751 match previous {
2752 Some((_, prev, prev_served, prev_captures))
2759 if prev == meta && prev_served == served && prev_captures == captures => {}
2760 Some((_, prev, _, prev_captures)) if prev_captures != captures => {
2763 if let Ok(mut state) = cursor.state.write() {
2764 state.apply_unannounce(presented.clone(), prev, prev_captures);
2765 state.apply_announce(presented.clone(), meta, captures);
2766 }
2767 }
2768 _ => {
2769 if let Ok(mut state) = cursor.state.write() {
2770 state.apply_announce(presented.clone(), meta, captures);
2771 }
2772 }
2773 }
2774 }
2775 None => {
2776 if let Some((_, last, _, captures)) = cursor.current.remove(presented)
2777 && let Ok(mut state) = cursor.state.write()
2778 {
2779 state.apply_unannounce(presented.clone(), last, captures);
2780 }
2781 }
2782 }
2783 }
2784
2785 fn register_cursor(&mut self, id: ConsumerId, mut cursor: TableCursor) {
2787 let mut presented: BTreeSet<PathOwned> = BTreeSet::new();
2790 for head in &cursor.heads {
2791 let (above, at) = self.routes.split(head);
2792 let mut nodes = above;
2793 if let Some(node) = at {
2794 node.walk(&mut |node| nodes.push(node));
2795 }
2796 for entry in nodes.into_iter().flat_map(|node| node.entries.iter()) {
2797 if let Some(p) = cursor.presented(&entry.prefix, &entry.claim) {
2798 presented.insert(p);
2799 }
2800 }
2801 }
2802 for p in &presented {
2803 Self::sync_cursor(&self.routes, &mut cursor, p);
2804 }
2805 for head in &cursor.heads {
2806 self.routes.add_cursor(head, id);
2807 }
2808 self.cursors.insert(id, cursor);
2809 }
2810
2811 fn best_route(&self, path: &Path, horizon: Horizon, pin: Pin, refused: &HashSet<u64>) -> Option<&RouteEntry> {
2826 let (above, at) = self.routes.split(path);
2830 let mut best = None;
2831 for node in above.into_iter().chain(at) {
2832 let mut candidates = node
2833 .entries
2834 .iter()
2835 .filter(|entry| entry.advertised)
2836 .filter(|entry| entry.scope.matches(path.as_str()))
2837 .filter(|entry| horizon.admits(entry))
2838 .filter(|entry| entry.qualifies(pin))
2839 .filter(|entry| !refused.contains(&entry.id))
2840 .peekable();
2841 if candidates.peek().is_some() {
2842 best = candidates
2843 .filter(|entry| entry.serves(path))
2844 .min_by_key(|entry| route_order(&entry.prefix, entry));
2845 }
2846 }
2847 best
2848 }
2849}
2850
2851#[derive(Default)]
2858struct PendingBroadcast {
2859 resolved: Option<Result<broadcast::Consumer, Error>>,
2860}
2861
2862#[must_use = "dropping an origin::Dynamic retracts the route"]
2874pub struct Dynamic {
2875 announcement: AnnounceProducer,
2877 state: kio::Shared<ServeState>,
2878}
2879
2880impl Dynamic {
2881 pub fn update(&self, route: Route) -> Result<(), Error> {
2889 self.announcement.update(route)
2890 }
2891
2892 pub fn poll_requested_broadcast(&self, waiter: &kio::Waiter) -> Poll<Result<Request, Error>> {
2897 let mut state = ready!(self.state.poll(waiter, |state| {
2898 if state.closed || state.requests.has_queued() {
2899 Poll::Ready(())
2900 } else {
2901 Poll::Pending
2902 }
2903 }));
2904
2905 if state.closed {
2907 return Poll::Ready(Err(Error::Closed));
2908 }
2909
2910 let path = state.requests.pop().expect("predicate guaranteed a request");
2911 let producer = state.requests.get(&path).expect("popped key must be pending").clone();
2917 Poll::Ready(Ok(Request {
2918 path,
2919 producer,
2920 home: self.state.clone(),
2921 }))
2922 }
2923
2924 pub async fn requested_broadcast(&self) -> Result<Request, Error> {
2930 kio::wait(|waiter| self.poll_requested_broadcast(waiter)).await
2931 }
2932}
2933
2934impl ServeState {
2935 fn resolve(
2946 shared: &kio::Shared<Self>,
2947 path: &PathOwned,
2948 producer: &kio::Producer<PendingBroadcast>,
2949 result: Result<broadcast::Consumer, Error>,
2950 ) {
2951 let mut state = shared.lock();
2952 if state.closed {
2953 return;
2954 }
2955 let resolved = match result {
2956 Ok(broadcast) => {
2957 let existing = state.served.insert(path.clone(), broadcast.weak());
2961 Ok(existing.map(|weak| weak.consume()).unwrap_or(broadcast))
2962 }
2963 Err(err) => Err(err),
2964 };
2965 state.requests.remove_if(path, |p| p.same_channel(producer));
2966 if let Ok(mut pending) = producer.write() {
2967 pending.resolved.get_or_insert(resolved);
2968 drop(state);
2969 }
2970 }
2971
2972 fn forget(shared: &kio::Shared<Self>, path: &PathOwned, producer: &kio::Producer<PendingBroadcast>) {
2974 shared.lock().requests.remove_if(path, |p| p.same_channel(producer));
2975 }
2976}
2977
2978pub struct Request {
2985 path: PathOwned,
2987
2988 producer: kio::Producer<PendingBroadcast>,
2991
2992 home: kio::Shared<ServeState>,
2995}
2996
2997impl Request {
2998 pub fn path(&self) -> &Path<'_> {
3000 &self.path
3001 }
3002
3003 pub fn accept(self, broadcast: impl Consume<broadcast::Consumer>) {
3009 let broadcast = broadcast.consume();
3010 ServeState::resolve(&self.home, &self.path, &self.producer, Ok(broadcast));
3011 }
3013
3014 pub fn reject(self, err: Error) {
3016 ServeState::resolve(&self.home, &self.path, &self.producer, Err(err));
3017 }
3018}
3019
3020impl Drop for Request {
3021 fn drop(&mut self) {
3022 ServeState::forget(&self.home, &self.path, &self.producer);
3030 }
3031}
3032
3033pub struct Requesting {
3040 inner: RequestState,
3041 path: PathOwned,
3045 stats: stats::Scope,
3048}
3049
3050enum RequestState {
3051 Failed(Error),
3054 Pending(kio::Consumer<PendingBroadcast>),
3056}
3057
3058impl Requesting {
3059 fn failed(error: Error) -> Self {
3060 Self::new(RequestState::Failed(error))
3061 }
3062
3063 fn queued(consumer: kio::Consumer<PendingBroadcast>) -> Self {
3064 Self::new(RequestState::Pending(consumer))
3065 }
3066
3067 pub fn is_queued(&self) -> bool {
3076 matches!(self.inner, RequestState::Pending(_))
3077 }
3078
3079 fn new(inner: RequestState) -> Self {
3080 Self {
3081 inner,
3082 path: PathOwned::default(),
3083 stats: stats::Scope::default(),
3084 }
3085 }
3086
3087 fn with_path(mut self, path: PathOwned) -> Self {
3088 self.path = path;
3089 self
3090 }
3091
3092 fn with_stats(mut self, scope: stats::Scope) -> Self {
3094 self.stats = scope;
3095 self
3096 }
3097
3098 fn hand_out(&self, broadcast: broadcast::Consumer) -> broadcast::Consumer {
3100 broadcast.with_path(self.path.clone()).with_stats(self.stats.clone())
3101 }
3102
3103 pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<broadcast::Consumer, Error>> {
3105 match &self.inner {
3106 RequestState::Failed(error) => Poll::Ready(Err(error.clone())),
3107 RequestState::Pending(consumer) => Poll::Ready(
3108 match ready!(consumer.poll(waiter, |state| match &state.resolved {
3109 Some(result) => Poll::Ready(result.clone()),
3110 None => Poll::Pending,
3111 })) {
3112 Ok(result) => result.map(|broadcast| self.hand_out(broadcast)),
3113 Err(_closed) => Err(Error::Unroutable),
3115 },
3116 ),
3117 }
3118 }
3119}
3120
3121impl kio::Pollable for Requesting {
3122 type Output = Result<broadcast::Consumer, Error>;
3123
3124 fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
3125 self.poll_ok(waiter)
3126 }
3127}
3128
3129pub trait Consume<T> {
3137 fn consume(&self) -> T;
3139}
3140
3141impl<T, U: Consume<T>> Consume<T> for &U {
3142 fn consume(&self) -> T {
3143 (**self).consume()
3144 }
3145}
3146
3147impl Consume<Consumer> for Producer {
3148 fn consume(&self) -> Consumer {
3149 Consumer::from_producer(self, stats::Session::default())
3153 }
3154}
3155
3156impl Consume<Consumer> for Consumer {
3157 fn consume(&self) -> Consumer {
3158 self.clone()
3159 }
3160}
3161
3162impl Consume<broadcast::Consumer> for broadcast::Producer {
3163 fn consume(&self) -> broadcast::Consumer {
3164 self.consume()
3166 }
3167}
3168
3169impl Consume<broadcast::Consumer> for broadcast::Consumer {
3170 fn consume(&self) -> broadcast::Consumer {
3171 self.clone()
3172 }
3173}
3174
3175impl Consume<track::Consumer> for track::Producer {
3176 fn consume(&self) -> track::Consumer {
3177 self.consume()
3178 }
3179}
3180
3181impl Consume<track::Consumer> for track::Consumer {
3182 fn consume(&self) -> track::Consumer {
3183 self.clone()
3184 }
3185}
3186
3187#[derive(Clone)]
3193pub struct Consumer {
3194 hop: Hop,
3196 scope: OriginScope,
3197
3198 root: PathOwned,
3200
3201 shared: kio::Shared<OriginState>,
3204
3205 stats: stats::Session,
3209
3210 horizon: Horizon,
3215
3216 hidden: Hidden,
3218
3219 pool: cache::Pool,
3222 cache_duration: Duration,
3223
3224 tasks: TasksWeak,
3228
3229 timers: Clock,
3232}
3233
3234impl Consumer {
3235 fn from_producer(producer: &Producer, stats: stats::Session) -> Self {
3236 Self {
3237 hop: producer.hop,
3238 scope: producer.scope.clone(),
3239 root: producer.root.clone(),
3240 shared: producer.shared.clone(),
3241 stats,
3242 horizon: Horizon::default(),
3243 hidden: Hidden::default(),
3244 pool: producer.pool.clone(),
3245 cache_duration: producer.cache_duration,
3246 tasks: producer.tasks.downgrade(),
3247 timers: producer.timers.clone(),
3248 }
3249 }
3250
3251 pub fn hop(&self) -> Hop {
3253 self.hop
3254 }
3255
3256 pub(crate) fn excluding(mut self, peer: Hop) -> Self {
3264 self.horizon.exclude = Some(peer);
3265 self
3266 }
3267
3268 pub fn local(mut self) -> Self {
3275 self.horizon.local = true;
3276 self
3277 }
3278
3279 pub fn with_hidden(mut self, hidden: bool) -> Self {
3284 self.hidden.include = hidden;
3285 self
3286 }
3287
3288 pub(crate) fn beyond(mut self, outer: &Consumer) -> Self {
3291 self.hidden.beyond = Some(interest_prefixes(&outer.scope.allowed));
3292 self
3293 }
3294
3295 pub(crate) fn includes_hidden(&self) -> bool {
3297 self.hidden.include
3298 }
3299
3300 pub fn with_stats(mut self, session: stats::Session) -> Self {
3304 self.stats = session;
3305 self
3306 }
3307
3308 fn untagged(&self) -> Self {
3312 Self {
3313 stats: stats::Session::default(),
3314 ..self.clone()
3315 }
3316 }
3317
3318 pub(crate) fn empty(&self) -> Self {
3323 Self {
3324 scope: OriginScope::empty(),
3325 ..self.clone()
3326 }
3327 }
3328
3329 pub fn announced(&self) -> AnnounceConsumer {
3338 AnnounceConsumer::new(
3339 self.root.clone(),
3340 self.scope.allowed.clone(),
3341 self.stats.clone(),
3342 self.horizon,
3343 self.hidden.clone(),
3344 &self.shared,
3345 )
3346 }
3347
3348 pub fn consume(&self) -> Self {
3350 self.clone()
3351 }
3352
3353 #[cfg(test)]
3356 pub(crate) fn get_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
3357 let full = self.root.join(path).to_owned();
3358 if !self.scope.permits(&full) {
3359 return None;
3360 }
3361 let table = self.shared.lock();
3362 table
3363 .routes
3364 .at(&full)
3365 .filter(|entry| entry.local)
3366 .min_by_key(|entry| route_order(&entry.prefix, entry))
3367 .and_then(|entry| entry.source.clone())
3368 }
3369
3370 pub async fn routed(&self, path: impl AsPath) -> Option<Route> {
3380 let path = path.as_path();
3381
3382 let consumer = match Pattern::subtree(path.as_str()) {
3387 Ok(subtree) => self.scope("", &Patterns::from(subtree)).ok()?,
3388 Err(InvalidPattern::TooManySegments) => self.clone(),
3389 Err(_) => return None,
3390 };
3391
3392 if !consumer.allowed().matches(path.as_str()) {
3396 return None;
3397 }
3398
3399 let mut announced = consumer.untagged().with_hidden(true).announced();
3403 loop {
3404 let update = announced.next().await?;
3405 if update.kind.is_active() && path.has_prefix(&update.prefix) {
3406 return Some(update.route);
3407 }
3408 }
3409 }
3410
3411 pub async fn routed_broadcast(&self, path: impl AsPath) -> Result<broadcast::Consumer, Error> {
3425 let path = path.as_path();
3426
3427 if !self.allowed().matches(path.as_str()) {
3430 return Err(Error::Unauthorized);
3431 }
3432 loop {
3433 let (watch, seen) = {
3442 let mut table = self.shared.lock();
3443 if table.closed {
3444 return Err(Error::Closed);
3445 }
3446 let watch = table.watch(&self.shared, &self.root.join(&path));
3447 let seen = watch.seen();
3448 (watch, seen)
3449 };
3450 match self.request_broadcast(&path).await {
3451 Ok(broadcast) => return Ok(broadcast),
3452 Err(Error::Unroutable) => {
3453 kio::wait(|waiter| watch.poll_changed(waiter, seen)).await;
3454 }
3455 Err(Error::Dropped) if self.shared.lock().closed => return Err(Error::Closed),
3458 Err(err) => return Err(err),
3459 }
3460 }
3461 }
3462
3463 pub fn scope(&self, root: impl AsPath, patterns: &Patterns) -> Result<Consumer, Error> {
3470 let root = self.root.join(root).to_owned();
3471 let rooted = patterns.rooted(root.as_str()).map_err(|_| BoundsExceeded)?;
3472 let scope = self.scope.narrow(&rooted).ok_or(Error::Unauthorized)?;
3473 Ok(Consumer {
3474 scope,
3475 root,
3476 ..self.clone()
3477 })
3478 }
3479
3480 pub fn request_broadcast(&self, path: impl AsPath) -> kio::Pending<Requesting> {
3503 let path = path.as_path();
3504
3505 let absolute = self.root.join(&path).to_owned();
3509 let scope = self.stats.egress(&absolute);
3510 let requested = path.to_owned();
3514
3515 if !self.scope.permits(&absolute) {
3517 return kio::Pending::new(Requesting::failed(Error::Unauthorized));
3518 }
3519
3520 let mut state = self.shared.lock();
3521
3522 if state.closed {
3524 return kio::Pending::new(Requesting::failed(Error::Closed));
3525 }
3526
3527 if state
3531 .best_route(&absolute.as_path(), self.horizon, Pin::Any, &HashSet::new())
3532 .is_none()
3533 {
3534 return kio::Pending::new(Requesting::failed(Error::Unroutable));
3535 }
3536
3537 let key = (absolute.clone(), self.horizon);
3545 if let Some(front) = state.fronts.get(&key) {
3546 let pin = *front.pin.lock();
3547 let current = state
3548 .best_route(&absolute.as_path(), self.horizon, Pin::Any, &HashSet::new())
3549 .is_some_and(|entry| entry.qualifies(pin));
3550 if current {
3551 let pending = Requesting::queued(front.request.consume())
3552 .with_path(requested)
3553 .with_stats(scope);
3554 return kio::Pending::new(pending);
3555 }
3556 state.fronts.remove(&key);
3557 }
3558
3559 let broadcast = broadcast::Producer::new_spliced(broadcast::Info {
3564 pool: self.pool.clone(),
3565 cache_duration: self.cache_duration,
3566 path: absolute.clone(),
3567 });
3568 let request = kio::Producer::<PendingBroadcast>::default();
3569 let consumer = request.consume();
3570 let watch = state.watch(&self.shared, &absolute);
3571 let pin = kio::Lock::new(Pin::Any);
3572 state.fronts.insert(
3573 key,
3574 RemoteFront {
3575 request: request.clone(),
3576 broadcast: broadcast.consume().weak(),
3577 pin: pin.clone(),
3578 },
3579 );
3580 drop(state);
3583 self.tasks.push(run_front(FrontTask {
3584 shared: self.shared.clone(),
3585 broadcast,
3586 path: absolute,
3587 horizon: self.horizon,
3588 watch,
3589 request,
3590 pin,
3591 timers: self.timers.clone(),
3592 }));
3593 kio::Pending::new(Requesting::queued(consumer).with_path(requested).with_stats(scope))
3594 }
3595
3596 pub fn root(&self) -> &Path<'_> {
3598 &self.root
3599 }
3600
3601 pub fn allowed(&self) -> Patterns {
3603 self.scope.relative(&self.root)
3604 }
3605
3606 pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
3608 self.root.join(path)
3609 }
3610}
3611
3612pub struct AnnounceConsumer {
3617 id: ConsumerId,
3618 shared: kio::Shared<OriginState>,
3619 root: PathOwned,
3620
3621 state: kio::Producer<OriginConsumerState>,
3624
3625 stats: stats::Session,
3628
3629 guards: HashMap<PathOwned, stats::Announce>,
3633
3634 park: kio::Park,
3637}
3638
3639impl AnnounceConsumer {
3640 fn new(
3641 root: PathOwned,
3642 allowed: Patterns,
3643 stats: stats::Session,
3644 horizon: Horizon,
3645 hidden: Hidden,
3646 shared: &kio::Shared<OriginState>,
3647 ) -> Self {
3648 let state = kio::Producer::<OriginConsumerState>::default();
3649 let id = ConsumerId::new();
3650
3651 {
3652 let mut table = shared.lock();
3653 if table.closed {
3654 if let Ok(mut state) = state.write() {
3656 state.ended = true;
3657 }
3658 } else {
3659 table.register_cursor(
3660 id,
3661 TableCursor {
3662 root: root.clone(),
3663 heads: interest_prefixes(&allowed),
3664 allowed,
3665 horizon,
3666 hidden,
3667 state: state.clone(),
3668 current: HashMap::new(),
3669 },
3670 );
3671 }
3672 }
3673
3674 Self {
3675 id,
3676 shared: shared.clone(),
3677 root,
3678 state,
3679 stats,
3680 guards: HashMap::new(),
3681 park: kio::Park::default(),
3682 }
3683 }
3684
3685 fn hand_out(&mut self, update: AnnounceUpdate) -> AnnounceUpdate {
3687 let absolute = self.root.join(&update.prefix).to_owned();
3688 if update.kind.is_active() {
3689 let scope = self.stats.egress(&absolute);
3690 self.guards
3691 .entry(update.prefix.clone())
3692 .or_insert_with(|| scope.announce());
3693 } else {
3694 self.guards.remove(&update.prefix);
3695 }
3696 update
3697 }
3698
3699 pub async fn next(&mut self) -> Option<AnnounceUpdate> {
3707 kio::wait(|waiter| self.poll_next(waiter)).await
3708 }
3709
3710 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<AnnounceUpdate>> {
3716 let update = {
3717 let mut state = match ready!(self.state.poll(waiter, |state| {
3718 if state.pending.is_empty() && !state.ended {
3719 Poll::Pending
3720 } else {
3721 Poll::Ready(())
3722 }
3723 })) {
3724 Ok(state) => state,
3725 Err(_) => return Poll::Ready(None),
3727 };
3728 match state.take() {
3729 Some(update) => update,
3730 None => {
3731 state.close();
3734 return Poll::Ready(None);
3735 }
3736 }
3737 };
3738 Poll::Ready(Some(self.hand_out(update)))
3739 }
3740
3741 pub fn try_next(&mut self) -> Option<AnnounceUpdate> {
3746 let update = self.state.write().ok()?.take()?;
3747 Some(self.hand_out(update))
3748 }
3749
3750 pub fn is_closed(&self) -> bool {
3752 let state = self.state.read();
3753 state.is_closed() || state.ended
3754 }
3755
3756 pub fn root(&self) -> &Path<'_> {
3758 &self.root
3759 }
3760
3761 pub fn absolute(&self, prefix: impl AsPath) -> Path<'_> {
3763 self.root.join(prefix)
3764 }
3765}
3766
3767impl futures::Stream for AnnounceConsumer {
3768 type Item = AnnounceUpdate;
3769
3770 fn poll_next(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Option<Self::Item>> {
3771 let this = self.get_mut();
3772 let waiter = this.park.hold(cx).clone();
3773 this.poll_next(&waiter)
3774 }
3775}
3776
3777impl Drop for AnnounceConsumer {
3778 fn drop(&mut self) {
3779 let mut shared = self.shared.lock();
3780 if let Some(cursor) = shared.cursors.remove(&self.id) {
3781 for head in &cursor.heads {
3782 shared.routes.remove_cursor(head, self.id);
3783 }
3784 }
3785 }
3786}
3787
3788#[cfg(test)]
3789use futures::FutureExt;
3790
3791#[cfg(test)]
3792#[allow(missing_docs)] impl AnnounceConsumer {
3794 pub fn assert_next_active(&mut self, expected: impl AsPath) -> Route {
3796 let expected = expected.as_path();
3797 let update = self.next().now_or_never().expect("next blocked").expect("no next");
3798 assert_eq!(update.prefix, expected, "wrong prefix");
3799 assert!(update.kind.is_active(), "should be an active route");
3800 update.route
3801 }
3802
3803 pub fn assert_try_next_active(&mut self, expected: impl AsPath) -> Route {
3805 let expected = expected.as_path();
3806 let update = self.try_next().expect("no next");
3807 assert_eq!(update.prefix, expected, "wrong prefix");
3808 assert!(update.kind.is_active(), "should be an active route");
3809 update.route
3810 }
3811
3812 pub fn assert_next_ended(&mut self, expected: impl AsPath) {
3814 let expected = expected.as_path();
3815 let update = self.next().now_or_never().expect("next blocked").expect("no next");
3816 assert_eq!(update.prefix, expected, "wrong prefix");
3817 assert_eq!(update.kind, AnnounceKind::Retracted, "should be a retraction");
3818 }
3819
3820 pub fn assert_next_wait(&mut self) {
3821 if let Some(res) = self.next().now_or_never() {
3822 panic!("next should block: got {:?}", res.map(|u| u.prefix));
3823 }
3824 }
3825}
3826
3827#[cfg(test)]
3831pub(crate) trait ProduceTest {
3832 fn produce(self) -> Producer;
3833}
3834
3835#[cfg(test)]
3836impl ProduceTest for Config {
3837 fn produce(self) -> Producer {
3838 let (producer, driver) = Producer::new(self);
3839 if tokio::runtime::Handle::try_current().is_ok() {
3840 tokio::spawn(crate::time::run(driver));
3841 } else {
3842 std::mem::forget(driver);
3845 }
3846 producer
3847 }
3848}
3849
3850#[cfg(test)]
3851impl ProduceTest for Hop {
3852 fn produce(self) -> Producer {
3853 Config::new(self).produce()
3854 }
3855}
3856
3857#[cfg(test)]
3858mod tests {
3859 use super::*;
3860 use futures::FutureExt;
3861
3862 fn origin(id: u64) -> Hop {
3863 Hop::new(id).unwrap()
3864 }
3865
3866 fn hops(ids: &[u64]) -> Hops {
3867 let mut list = Hops::new();
3868 for &id in ids {
3869 list.push(if id == 0 { Hop::UNKNOWN } else { origin(id) }).unwrap();
3870 }
3871 list
3872 }
3873
3874 fn scopes(prefixes: &[&str]) -> Patterns {
3876 prefixes
3877 .iter()
3878 .map(|prefix| Pattern::subtree(prefix).unwrap())
3879 .collect()
3880 }
3881
3882 #[test]
3883 fn default_config_mints_a_real_hop() {
3884 let config = Config::default();
3885 assert_ne!(config.hop, Hop::UNKNOWN);
3886 let (producer, _driver) = Producer::new(config.clone());
3887 assert_eq!(producer.hop(), config.hop);
3888 assert_eq!(producer.consume().hop(), config.hop);
3889 }
3890
3891 #[test]
3892 fn random_hops_fit_legacy_lite_clients() {
3893 for _ in 0..32 {
3894 assert!(Hop::random().id() < 1u64 << 53);
3895 }
3896 }
3897
3898 async fn settle(mut check: impl FnMut() -> bool) {
3901 for _ in 0..100 {
3902 if check() {
3903 return;
3904 }
3905 tokio::task::yield_now().await;
3906 }
3907 panic!("condition never settled");
3908 }
3909
3910 async fn queued(server: &Dynamic) -> Request {
3912 let mut request = None;
3913 settle(|| match server.poll_requested_broadcast(&kio::Waiter::noop()) {
3914 Poll::Ready(Ok(popped)) => {
3915 request = Some(popped);
3916 true
3917 }
3918 _ => false,
3919 })
3920 .await;
3921 request.unwrap()
3922 }
3923
3924 async fn next_group(subscription: &mut crate::track::Subscriber) -> Result<Option<crate::group::Consumer>, Error> {
3926 let mut next = None;
3927 settle(|| match subscription.poll_recv_group(&kio::Waiter::noop()) {
3928 Poll::Ready(result) => {
3929 next = Some(result);
3930 true
3931 }
3932 Poll::Pending => false,
3933 })
3934 .await;
3935 next.unwrap()
3936 }
3937
3938 #[tokio::test]
3939 async fn announce_and_retract() {
3940 let producer = origin(1).produce();
3941 let consumer = producer.consume();
3942 let mut announced = consumer.announced();
3943 announced.assert_next_wait();
3944
3945 let announcement = producer.announce("room/alice", Route::default()).unwrap();
3946 let route = announced.assert_next_active("room/alice");
3947 assert!(route.hops.is_empty());
3948 assert_eq!(route.cost, Cost::default());
3949 announced.assert_next_wait();
3950
3951 drop(announcement);
3952 announced.assert_next_ended("room/alice");
3953 announced.assert_next_wait();
3954 }
3955
3956 #[tokio::test]
3959 async fn hidden_routes_need_an_opt_in() {
3960 let producer = origin(1).produce();
3961 let consumer = producer.consume();
3962 let _visible = producer.announce("room/alice", Route::default()).unwrap();
3963 let _stats = producer.announce(".stats/node", Route::default()).unwrap();
3964 let _nested = producer.announce("room/.internal", Route::default()).unwrap();
3965 let _suffix = producer.announce("room/catalog.pro", Route::default()).unwrap();
3967
3968 let mut announced = consumer.announced();
3969 announced.assert_next_active("room/alice");
3970 announced.assert_next_active("room/catalog.pro");
3971 announced.assert_next_wait();
3972
3973 let mut announced = consumer.clone().with_hidden(true).announced();
3974 announced.assert_next_active(".stats/node");
3975 announced.assert_next_active("room/.internal");
3976 announced.assert_next_active("room/alice");
3977 announced.assert_next_active("room/catalog.pro");
3978 announced.assert_next_wait();
3979
3980 let mut announced = consumer
3982 .scope(".stats", &Patterns::from(Pattern::all()))
3983 .unwrap()
3984 .announced();
3985 announced.assert_next_active("node");
3986 announced.assert_next_wait();
3987 let mut announced = consumer.scope("", &scopes(&["room/.internal"])).unwrap().announced();
3988 announced.assert_next_active("room/.internal");
3989 announced.assert_next_wait();
3990
3991 let mut announced = consumer.clone().with_hidden(true).beyond(&consumer).announced();
3994 announced.assert_next_active(".stats/node");
3995 announced.assert_next_active("room/.internal");
3996 announced.assert_next_wait();
3997 let room = consumer.scope("", &scopes(&["room"])).unwrap().beyond(&consumer);
3998 let mut announced = room.announced();
3999 announced.assert_next_wait();
4000 let stats = consumer.scope("", &scopes(&[".stats"])).unwrap().beyond(&consumer);
4001 let mut announced = stats.announced();
4002 announced.assert_next_active(".stats/node");
4003 announced.assert_next_wait();
4004 }
4005
4006 #[tokio::test]
4008 async fn hidden_broadcast_resolves_by_path() {
4009 let producer = origin(1).produce();
4010 let consumer = producer.consume();
4011 let broadcast = producer.create_broadcast(".stats/node").unwrap();
4012 broadcast.announce(Route::default()).unwrap();
4013
4014 consumer.announced().assert_next_wait();
4015 let resolved = consumer.request_broadcast(".stats/node").await.expect("resolves");
4016 assert_eq!(resolved.info().path.as_str(), ".stats/node");
4017 }
4018
4019 #[tokio::test]
4021 async fn hidden_route_announced_later_stays_hidden() {
4022 let producer = origin(1).produce();
4023 let consumer = producer.consume();
4024 let mut announced = consumer.announced();
4025 let mut opted = consumer.clone().with_hidden(true).announced();
4026
4027 let hidden = producer.announce(".stats/node", Route::default()).unwrap();
4028 announced.assert_next_wait();
4029 opted.assert_next_active(".stats/node");
4030
4031 drop(hidden);
4032 announced.assert_next_wait();
4033 opted.assert_next_ended(".stats/node");
4034 }
4035
4036 #[tokio::test]
4037 async fn broadcast_announces_its_own_path() {
4038 let producer = origin(1).produce();
4039 let consumer = producer.consume();
4040 let mut announced = consumer.announced();
4041 let mut peer = consumer.clone().excluding(Hop::UNKNOWN).announced();
4042
4043 let broadcast = producer.create_broadcast("room/alice").unwrap();
4045 announced.assert_next_wait();
4046 peer.assert_next_wait();
4047
4048 broadcast.announce(Route::default().with_cost(3)).unwrap();
4049 assert_eq!(announced.assert_next_active("room/alice").cost, Cost::new(3));
4050 assert_eq!(peer.assert_next_active("room/alice").cost, Cost::new(3));
4051
4052 broadcast.announce(Route::default().with_cost(1)).unwrap();
4054 assert_eq!(announced.assert_next_active("room/alice").cost, Cost::new(1));
4055 assert_eq!(peer.assert_next_active("room/alice").cost, Cost::new(1));
4056
4057 broadcast.unannounce();
4059 announced.assert_next_ended("room/alice");
4060 peer.assert_next_ended("room/alice");
4061 broadcast.unannounce();
4062 announced.assert_next_wait();
4063 let err = consumer.request_broadcast("room/alice").await.err().unwrap();
4064 assert!(matches!(err, Error::Unroutable));
4065
4066 broadcast.announce(Route::default()).unwrap();
4068 announced.assert_next_active("room/alice");
4069 peer.assert_next_active("room/alice");
4070 broadcast.finish();
4071 announced.assert_next_ended("room/alice");
4072 peer.assert_next_ended("room/alice");
4073 assert!(matches!(broadcast.announce(Route::default()), Err(Error::Closed)));
4074 announced.assert_next_wait();
4075 }
4076
4077 #[tokio::test]
4078 async fn broadcast_announcement_retracts_with_the_last_producer() {
4079 let producer = origin(1).produce();
4080 let consumer = producer.consume();
4081 let mut announced = consumer.announced();
4082
4083 let broadcast = producer.create_broadcast("room/alice").unwrap();
4084 let clone = broadcast.clone();
4085 broadcast.announce(Route::default()).unwrap();
4086 announced.assert_next_active("room/alice");
4087
4088 drop(broadcast);
4090 announced.assert_next_wait();
4091 drop(clone);
4092 announced.assert_next_ended("room/alice");
4093 }
4094
4095 #[tokio::test]
4096 async fn publish_creates_and_announces_together() {
4097 let producer = origin(1).produce();
4098 let mut announced = producer.consume().announced();
4099 let _broadcast = producer.publish("room/alice", Route::default()).unwrap();
4100 announced.assert_next_active("room/alice");
4101 }
4102
4103 #[tokio::test]
4104 async fn standalone_broadcast_cannot_announce() {
4105 let broadcast = broadcast::Info::new().produce();
4106 assert!(matches!(broadcast.announce(Route::default()), Err(Error::Closed)));
4107 broadcast.unannounce();
4109 }
4110
4111 #[tokio::test]
4112 async fn announce_replays_to_late_cursor() {
4113 let producer = origin(1).produce();
4114 let _a = producer.announce("room/alice", Route::default()).unwrap();
4115 let _b = producer.announce("room/bob", Route::default()).unwrap();
4116
4117 let mut announced = producer.consume().announced();
4118 announced.assert_next_active("room/alice");
4120 announced.assert_next_active("room/bob");
4121 announced.assert_next_wait();
4122 }
4123
4124 #[tokio::test]
4125 async fn announce_keeps_its_prefix_under_a_producer_scope() {
4126 let producer = origin(1).produce();
4127 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
4128
4129 let _a = scoped.announce("", Route::default()).unwrap();
4131 let mut announced = producer.consume().announced();
4132 announced.assert_next_active("");
4133
4134 assert!(matches!(
4136 scoped.announce("other", Route::default()),
4137 Err(Error::Unauthorized)
4138 ));
4139 }
4140
4141 #[tokio::test]
4142 async fn cursor_keeps_an_overlapping_prefix_above_its_scope() {
4143 let producer = origin(1).produce();
4144 let _a = producer.announce("", Route::default()).unwrap();
4145
4146 let consumer = producer.consume().scope("", &scopes(&["room"])).unwrap();
4147 let mut announced = consumer.announced();
4148 announced.assert_next_active("");
4149 }
4150
4151 #[tokio::test]
4152 async fn cursor_root_strips_prefix() {
4153 let producer = origin(1).produce();
4154 let _a = producer.announce("room/alice", Route::default()).unwrap();
4155
4156 let consumer = producer
4157 .consume()
4158 .scope("room", &Patterns::from(Pattern::all()))
4159 .unwrap();
4160 let mut announced = consumer.announced();
4161 announced.assert_next_active("alice");
4162 }
4163
4164 #[tokio::test]
4165 async fn best_route_wins_and_fails_over() {
4166 let producer = origin(1).produce();
4167 let mut announced = producer.consume().announced();
4168
4169 let expensive = producer
4170 .announce("room", Route::default().with_hops(hops(&[10])).with_cost(5))
4171 .unwrap();
4172 let route = announced.assert_next_active("room");
4173 assert_eq!(route.cost, Cost::new(5));
4174
4175 let cheap = producer
4177 .announce("room", Route::default().with_hops(hops(&[20])).with_cost(1))
4178 .unwrap();
4179 let route = announced.assert_next_active("room");
4180 assert_eq!(route.cost, Cost::new(1));
4181
4182 drop(cheap);
4184 let route = announced.assert_next_active("room");
4185 assert_eq!(route.cost, Cost::new(5));
4186
4187 drop(expensive);
4189 announced.assert_next_ended("room");
4190 }
4191
4192 #[tokio::test]
4193 async fn identical_reannounce_is_invisible() {
4194 let producer = origin(1).produce();
4195 let mut announced = producer.consume().announced();
4196
4197 let old = producer
4198 .announce("room", Route::default().with_hops(hops(&[10])))
4199 .unwrap();
4200 let first = announced.assert_next_active("room");
4201 assert_eq!(first.hops.as_slice(), hops(&[10]).as_slice());
4202
4203 let _new = producer
4207 .announce("room", Route::default().with_hops(hops(&[10])))
4208 .unwrap();
4209 announced.assert_next_wait();
4210
4211 drop(old);
4213 announced.assert_next_wait();
4214 }
4215
4216 #[tokio::test]
4217 async fn exclude_hides_routes_through_the_peer() {
4218 let producer = origin(1).produce();
4219 let _a = producer
4220 .announce("room", Route::default().with_hops(hops(&[7])))
4221 .unwrap();
4222
4223 let mut hidden = producer.consume().excluding(origin(7)).announced();
4224 hidden.assert_next_wait();
4225
4226 let mut visible = producer.consume().excluding(origin(8)).announced();
4227 visible.assert_next_active("room");
4228 }
4229
4230 #[tokio::test]
4231 async fn exclude_matches_via_when_the_chain_is_anonymous() {
4232 let producer = origin(1).produce();
4233 let assigned = origin(777);
4234 let _echoed = producer
4235 .announce("echoed", Route::default().with_hops(hops(&[0])).with_via(assigned))
4236 .unwrap();
4237 let _local = producer
4238 .announce("local", Route::default().with_hops(hops(&[10])))
4239 .unwrap();
4240
4241 let mut hidden = producer.consume().excluding(assigned).announced();
4242 hidden.assert_next_active("local");
4243 hidden.assert_next_wait();
4244 }
4245
4246 #[tokio::test]
4247 async fn anonymous_route_loses_to_identified_at_any_cost() {
4248 let producer = origin(1).produce();
4249 let mut announced = producer.consume().announced();
4250
4251 let _anonymous = producer
4252 .announce("room", Route::default().with_hops(hops(&[0])).with_cost(1))
4253 .unwrap();
4254 let route = announced.assert_next_active("room");
4255 assert!(route.is_anonymous());
4256 assert_eq!(route.cost, Cost::new(1));
4257
4258 let _identified = producer
4259 .announce("room", Route::default().with_hops(hops(&[10])).with_cost(5))
4260 .unwrap();
4261 let route = announced.assert_next_active("room");
4262 assert!(!route.is_anonymous());
4263 assert_eq!(route.cost, Cost::new(5));
4264 }
4265
4266 #[tokio::test]
4267 async fn anonymous_routes_order_by_cost() {
4268 let producer = origin(1).produce();
4269 let mut announced = producer.consume().announced();
4270
4271 let expensive = producer
4272 .announce("room", Route::default().with_hops(hops(&[0])).with_cost(5))
4273 .unwrap();
4274 let route = announced.assert_next_active("room");
4275 assert_eq!(route.cost, Cost::new(5));
4276
4277 let _cheap = producer
4278 .announce("room", Route::default().with_hops(hops(&[0, 7])).with_cost(1))
4279 .unwrap();
4280 let route = announced.assert_next_active("room");
4281 assert!(route.is_anonymous());
4282 assert_eq!(route.cost, Cost::new(1));
4283
4284 drop(expensive);
4285 announced.assert_next_wait();
4286 }
4287
4288 #[tokio::test]
4289 async fn anonymous_chain_from_identified_peer_still_ranks_last() {
4290 let producer = origin(1).produce();
4291 let mut announced = producer.consume().announced();
4292
4293 let _anonymous = producer
4294 .announce(
4295 "room",
4296 Route::default()
4297 .with_hops(hops(&[0, 7]))
4298 .with_cost(1)
4299 .with_via(origin(7)),
4300 )
4301 .unwrap();
4302 announced.assert_next_active("room");
4303
4304 let _identified = producer
4305 .announce("room", Route::default().with_hops(hops(&[10, 20])).with_cost(5))
4306 .unwrap();
4307 let route = announced.assert_next_active("room");
4308 assert!(!route.is_anonymous());
4309 assert_eq!(route.cost, Cost::new(5));
4310 }
4311
4312 #[tokio::test]
4313 async fn request_prefers_identified_over_cheaper_anonymous() {
4314 let producer = origin(1).produce();
4315 let consumer = producer.consume();
4316
4317 let anonymous = producer
4318 .dynamic("room", Route::default().with_hops(hops(&[0])).with_cost(1))
4319 .unwrap();
4320 let identified = producer
4321 .dynamic("room", Route::default().with_hops(hops(&[10])).with_cost(5))
4322 .unwrap();
4323
4324 let _pending = consumer.request_broadcast("room/alice");
4325 let request = queued(&identified).await;
4326 assert_eq!(request.path().as_str(), "room/alice");
4327 assert!(
4328 anonymous.poll_requested_broadcast(&kio::Waiter::noop()).is_pending(),
4329 "the cheaper anonymous route must not serve"
4330 );
4331 }
4332
4333 #[tokio::test]
4334 async fn update_reprices_in_place() {
4335 let producer = origin(1).produce();
4336 let mut announced = producer.consume().announced();
4337
4338 let announcement = producer.announce("room", Route::default()).unwrap();
4339 announced.assert_next_active("room");
4340
4341 announcement.update(Route::default().with_cost(9)).unwrap();
4342 let route = announced.assert_next_active("room");
4343 assert_eq!(route.cost, Cost::new(9));
4344 }
4345
4346 #[tokio::test]
4347 async fn retract_after_undelivered_reprice_still_delivered() {
4348 let producer = origin(1).produce();
4349 let mut announced = producer.consume().announced();
4350
4351 let announcement = producer.announce("room", Route::default()).unwrap();
4352 announced.assert_next_active("room");
4353
4354 announcement.update(Route::default().with_cost(9)).unwrap();
4358 drop(announcement);
4359 announced.assert_next_ended("room");
4360 announced.assert_next_wait();
4361 }
4362
4363 #[tokio::test]
4364 async fn scoped_cursor_advertises_most_specific_covering_route() {
4365 let producer = origin(1).produce();
4366 let _broad = producer.announce("room", Route::default().with_cost(1)).unwrap();
4369 let _narrow = producer.announce("room/alice", Route::default().with_cost(9)).unwrap();
4370
4371 let consumer = producer
4372 .consume()
4373 .scope("room/alice", &Patterns::from(Pattern::all()))
4374 .unwrap();
4375 let mut announced = consumer.announced();
4376 let route = announced.assert_next_active("");
4377 assert_eq!(route.cost, Cost::new(9));
4378 announced.assert_next_wait();
4379 }
4380
4381 #[tokio::test]
4382 async fn capture_change_retracts_before_reannouncing_a_presented_prefix() {
4383 let producer = origin(1).produce();
4384 let _broad = producer.announce("room", Route::default()).unwrap();
4385 let exact = producer.announce("room/alice", Route::default()).unwrap();
4386 let consumer = producer
4387 .consume()
4388 .scope("", &Patterns::from("room/*".parse::<Pattern>().unwrap()))
4389 .unwrap()
4390 .scope("room/alice", &Patterns::from(Pattern::all()))
4391 .unwrap();
4392 let mut announced = consumer.announced();
4393
4394 let first = announced.next().now_or_never().expect("next").expect("announce");
4395 assert_eq!(first.prefix.as_str(), "");
4396 assert_eq!(first.kind, AnnounceKind::Announced);
4397 assert_eq!(first.captures, Some(Vec::new()));
4398
4399 drop(exact);
4400 let retracted = announced.next().now_or_never().expect("next").expect("retract");
4401 assert_eq!(retracted.prefix.as_str(), "");
4402 assert_eq!(retracted.kind, AnnounceKind::Retracted);
4403 assert_eq!(retracted.captures, Some(Vec::new()));
4404 let replacement = announced.next().now_or_never().expect("next").expect("announce");
4405 assert_eq!(replacement.prefix.as_str(), "");
4406 assert_eq!(replacement.kind, AnnounceKind::Announced);
4407 assert_eq!(replacement.captures, None);
4408 }
4409
4410 #[tokio::test]
4411 async fn routed_broadcast_resolves_once_announced() {
4412 let producer = origin(1).produce();
4413 let consumer = producer.consume();
4414
4415 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
4417 assert!((&mut resolving).now_or_never().is_none());
4418
4419 let broadcast = producer.create_broadcast("room/alice").unwrap();
4421 for _ in 0..20 {
4422 tokio::task::yield_now().await;
4423 }
4424 assert!((&mut resolving).now_or_never().is_none());
4425
4426 broadcast.announce(Route::default()).unwrap();
4427 let resolved = resolving.await.expect("resolves once announced");
4428 assert_eq!(resolved.info().path.as_str(), "room/alice");
4429 drop(broadcast);
4430 }
4431
4432 #[tokio::test]
4435 async fn cheaper_remote_route_beats_a_local_broadcast() {
4436 let producer = origin(1).produce();
4437 let consumer = producer.consume();
4438 let mut announced = consumer.announced();
4439
4440 let _local = producer.publish("room/alice", Route::default().with_cost(5)).unwrap();
4441 assert_eq!(announced.assert_next_active("room/alice").cost, Cost::new(5));
4442
4443 let server = producer
4444 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(1))
4445 .unwrap();
4446 let route = announced.assert_next_active("room/alice");
4447 assert_eq!(route.cost, Cost::new(1));
4448 assert_eq!(route.hops, hops(&[10]));
4449
4450 let pending = consumer.request_broadcast("room/alice");
4452 let request = queued(&server).await;
4453 let upstream = broadcast::Info::new().produce();
4454 request.accept(&upstream);
4455 pending.await.expect("resolves through the cheaper route");
4456 }
4457
4458 #[tokio::test]
4462 async fn cheaper_route_after_a_front_wins_new_requests() {
4463 let producer = origin(1).produce();
4464 let consumer = producer.consume();
4465
4466 let _local = producer.publish("room/alice", Route::default().with_cost(5)).unwrap();
4467 let first = consumer
4468 .request_broadcast("room/alice")
4469 .await
4470 .expect("resolves locally");
4471
4472 let server = producer
4473 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(1))
4474 .unwrap();
4475
4476 let pending = consumer.request_broadcast("room/alice");
4477 let request = queued(&server).await;
4478 let upstream = broadcast::Info::new().produce();
4479 request.accept(&upstream);
4480 let second = pending.await.expect("resolves through the cheaper route");
4481
4482 assert!(!first.is_closed(), "the old front must keep serving its readers");
4483 assert!(!first.is_clone(&second), "the newcomer must not join the old front");
4484 }
4485
4486 #[tokio::test]
4490 async fn local_broadcast_wins_a_tie_with_a_hopless_route() {
4491 let producer = origin(1).produce();
4492 let consumer = producer.consume();
4493
4494 let _local = producer.publish("room/alice", Route::default()).unwrap();
4495 let server = producer.dynamic("room/alice", Route::default()).unwrap();
4496
4497 let resolved = tokio::time::timeout(Duration::from_secs(1), consumer.request_broadcast("room/alice"))
4499 .await
4500 .expect("the newer hopless route won the tie")
4501 .expect("resolves");
4502 assert_eq!(resolved.info().path.as_str(), "room/alice");
4503 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
4504 }
4505
4506 #[tokio::test]
4509 async fn announce_stats_follow_the_advertisement() {
4510 let registry = stats::Registry::new(stats::Config::new());
4511 let producer = origin(1)
4512 .produce()
4513 .with_stats(registry.tier(stats::Tier::default()).session("root"));
4514 let announces = || {
4515 registry
4516 .snapshot()
4517 .traffic()
4518 .into_iter()
4519 .find(|(_, role, _)| *role == stats::Role::Subscriber)
4520 .map(|(_, _, traffic)| (traffic.announces_started, traffic.announces_ended))
4521 .unwrap_or_default()
4522 };
4523
4524 let broadcast = producer.create_broadcast("room/alice").unwrap();
4525 assert_eq!(announces(), (0, 0), "a hidden broadcast is not announced");
4526 broadcast.announce(Route::default()).unwrap();
4527 broadcast
4528 .announce(Route {
4529 cost: Cost::new(3),
4530 ..Route::default()
4531 })
4532 .unwrap();
4533 assert_eq!(announces(), (1, 0), "a re-price is not another announce");
4534 broadcast.unannounce();
4535 assert_eq!(announces(), (1, 1));
4536 broadcast.announce(Route::default()).unwrap();
4537 drop(broadcast);
4538 assert_eq!(announces(), (2, 2));
4539 }
4540
4541 #[tokio::test]
4543 async fn local_broadcast_wins_a_cost_tie() {
4544 let producer = origin(1).produce();
4545 let consumer = producer.consume();
4546 let mut announced = consumer.announced();
4547
4548 let server = producer
4549 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(2))
4550 .unwrap();
4551 announced.assert_next_active("room/alice");
4552 let _local = producer.publish("room/alice", Route::default().with_cost(2)).unwrap();
4553 assert!(announced.assert_next_active("room/alice").hops.is_empty());
4554
4555 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
4556 assert_eq!(resolved.info().path.as_str(), "room/alice");
4557 for _ in 0..20 {
4558 tokio::task::yield_now().await;
4559 }
4560 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
4561 }
4562
4563 #[tokio::test]
4566 async fn unannounce_ends_the_front() {
4567 let producer = origin(1).produce();
4568 let consumer = producer.consume();
4569
4570 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
4571 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
4572
4573 broadcast.unannounce();
4574 let err = consumer.request_broadcast("room/alice").await.err().unwrap();
4575 assert!(matches!(err, Error::Unroutable), "joined a retracted front: {err}");
4576 settle(|| resolved.is_closed()).await;
4577 assert!(!broadcast.consume().is_closed(), "the broadcast itself lives on");
4578
4579 broadcast.announce(Route::default()).unwrap();
4581 let again = consumer.request_broadcast("room/alice").await.expect("resolves again");
4582 assert!(!again.is_clone(&resolved));
4583 }
4584
4585 #[tokio::test]
4589 async fn unannounce_keeps_a_track_awaiting_its_info() {
4590 let producer = origin(1).produce();
4591 let consumer = producer.consume();
4592
4593 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
4594 let mut dynamic = broadcast.dynamic();
4595 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
4596 let track = resolved.track("video").unwrap();
4597 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
4598 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
4599 .await
4600 .expect("the front asked the source")
4601 .expect("request");
4602
4603 broadcast.unannounce();
4604 settle(|| resolved.is_closed()).await;
4605
4606 let source = request.accept(None);
4607 let mut group = source.append_group().unwrap();
4608 group.write_frame(crate::Timestamp::ZERO, b"late".as_ref()).unwrap();
4609 group.finish().unwrap();
4610 source.finish().unwrap();
4611
4612 let mut subscription = subscribing.await.unwrap().expect("subscribe survives the retraction");
4613 let mut group = subscription.recv_group().await.unwrap().expect("the source's group");
4614 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"late");
4615 assert!(matches!(subscription.recv_group().await, Ok(None)), "ends cleanly");
4616 }
4617
4618 #[tokio::test]
4621 async fn unannounce_keeps_a_returning_reader_awaiting_its_info() {
4622 let producer = origin(1).produce();
4623 let consumer = producer.consume();
4624
4625 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
4626 let mut dynamic = broadcast.dynamic();
4627 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
4628 let track = resolved.track("video").unwrap();
4629 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
4630 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
4631 .await
4632 .expect("the front asked the source")
4633 .expect("request");
4634 let source = request.accept(None);
4635 let mut group = source.append_group().unwrap();
4636 group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap();
4637 group.finish().unwrap();
4638 let mut subscription = subscribing.await.unwrap().expect("subscribe");
4639 subscription.recv_group().await.unwrap().expect("the cached group");
4640 drop(subscription);
4641
4642 tokio::time::timeout(Duration::from_secs(1), source.unused())
4645 .await
4646 .expect("parked")
4647 .expect("source open");
4648 drop(source);
4649 let track = resolved.track("video").unwrap();
4650 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
4651 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
4652 .await
4653 .expect("the front asked the source again")
4654 .expect("request");
4655
4656 broadcast.unannounce();
4657 settle(|| resolved.is_closed()).await;
4658
4659 let source = request.accept(None);
4661 let mut group = source.create_group(1u64.into()).unwrap();
4662 group.write_frame(crate::Timestamp::ZERO, b"late".as_ref()).unwrap();
4663 group.finish().unwrap();
4664 source.finish().unwrap();
4665
4666 let mut subscription = subscribing.await.unwrap().expect("subscribe survives the retraction");
4667 let mut payloads = Vec::new();
4668 while let Some(mut group) = subscription.recv_group().await.expect("ends cleanly") {
4669 payloads.push(group.read_frame().await.unwrap().unwrap().payload);
4670 }
4671 assert_eq!(payloads.last().map(|p| &p[..]), Some(&b"late"[..]));
4672 }
4673
4674 #[tokio::test]
4677 async fn reannounce_before_the_front_acts_keeps_it() {
4678 let producer = origin(1).produce();
4679 let consumer = producer.consume();
4680
4681 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
4682 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
4683
4684 broadcast.unannounce();
4685 broadcast.announce(Route::default()).unwrap();
4686 for _ in 0..20 {
4687 tokio::task::yield_now().await;
4688 }
4689 assert!(!resolved.is_closed(), "the front ended across a reannouncement");
4690 let again = consumer.request_broadcast("room/alice").await.expect("resolves");
4691 assert!(again.is_clone(&resolved));
4692 }
4693
4694 #[tokio::test]
4695 async fn local_broadcast_resolves_once_announced() {
4696 let producer = origin(1).produce();
4697 let consumer = producer.consume();
4698
4699 let broadcast = producer.create_broadcast("room/alice").unwrap();
4701 let err = consumer
4702 .request_broadcast("room/alice")
4703 .now_or_never()
4704 .expect("unroutable is synchronous")
4705 .err()
4706 .unwrap();
4707 assert!(matches!(err, Error::Unroutable));
4708
4709 broadcast.announce(Route::default()).unwrap();
4710 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
4711 assert_eq!(resolved.info().path.as_str(), "room/alice");
4712 drop(broadcast);
4713
4714 let err = consumer
4716 .request_broadcast("room/bob")
4717 .now_or_never()
4718 .expect("unroutable is synchronous")
4719 .err()
4720 .unwrap();
4721 assert!(matches!(err, Error::Unroutable));
4722 }
4723
4724 #[test]
4725 fn create_broadcast_accepts_a_max_depth_path() {
4726 let producer = origin(1).produce();
4727 let path = vec!["a"; Path::MAX_PARTS].join("/");
4728 let _broadcast = producer.create_broadcast(path.as_str()).expect("max depth is allowed");
4729 let deeper = vec!["a"; Path::MAX_PARTS + 1].join("/");
4730 assert!(matches!(
4731 producer.create_broadcast(deeper.as_str()),
4732 Err(Error::BoundsExceeded(_))
4733 ));
4734 }
4735
4736 #[tokio::test]
4737 async fn duplicate_routes_aggregate_until_the_last_leaves() {
4738 let producer = origin(1).produce();
4739 let first = producer.dynamic("live", Route::default().with_cost(3)).unwrap();
4740 let second = producer.dynamic("live", Route::default().with_cost(1)).unwrap();
4741
4742 let mut announced = producer.consume().announced();
4743 let update = announced.next().now_or_never().expect("next").expect("no next");
4744 assert_eq!(update.prefix.as_str(), "live");
4745 assert_eq!(update.kind, AnnounceKind::Announced);
4746 assert_eq!(update.route.cost, Cost::new(1));
4747 announced.assert_next_wait();
4748
4749 drop(second);
4750 let update = announced.next().now_or_never().expect("next").expect("no next");
4751 assert_eq!(update.prefix.as_str(), "live");
4752 assert_eq!(update.kind, AnnounceKind::Updated);
4753 assert_eq!(update.route.cost, Cost::new(3));
4754
4755 drop(first);
4756 announced.assert_next_ended("live");
4757 announced.assert_next_wait();
4758 }
4759
4760 #[test]
4761 fn dynamic_may_cover_a_scope_but_disjoint_prefixes_are_refused() {
4762 let producer = origin(1).produce();
4763 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
4764 let _broad = scoped
4765 .dynamic("", Route::default())
4766 .expect("an overlapping prefix is accepted");
4767
4768 let _ok = scoped
4769 .dynamic("room/alice", Route::default())
4770 .expect("a contained prefix is accepted");
4771 assert!(matches!(
4772 scoped.dynamic("other", Route::default()),
4773 Err(Error::Unauthorized)
4774 ));
4775 }
4776
4777 #[tokio::test]
4778 async fn dynamic_route_keeps_its_producer_scope() {
4779 let producer = origin(1).produce();
4780 let scope = Patterns::from("*/chat".parse::<Pattern>().unwrap());
4781 let scoped = producer.scope("", &scope).unwrap();
4782 let dynamic = scoped.dynamic("", Route::default()).unwrap();
4783
4784 let mut matching = producer
4785 .consume()
4786 .scope("", &scopes(&["room/chat"]))
4787 .unwrap()
4788 .announced();
4789 matching.assert_next_active("");
4790 let mut outside = producer
4791 .consume()
4792 .scope("", &scopes(&["room/video"]))
4793 .unwrap()
4794 .announced();
4795 outside.assert_next_wait();
4796
4797 let refused = producer
4798 .consume()
4799 .request_broadcast("room/video")
4800 .now_or_never()
4801 .expect("an out-of-scope request must be refused synchronously");
4802 assert!(matches!(refused, Err(Error::Unroutable)));
4803 assert!(dynamic.requested_broadcast().now_or_never().is_none());
4804
4805 let _pending = producer.consume().request_broadcast("room/chat");
4806 let request = queued(&dynamic).await;
4807 assert_eq!(request.path().as_str(), "room/chat");
4808 }
4809
4810 #[tokio::test]
4811 async fn dynamic_accepts_a_max_depth_prefix() {
4812 let producer = origin(1).produce();
4813 let path = (0..Path::MAX_PARTS)
4814 .map(|i| format!("s{i}"))
4815 .collect::<Vec<_>>()
4816 .join("/");
4817 let mut announced = producer.consume().announced();
4818
4819 let dynamic = producer.dynamic(&path, Route::default()).expect("max depth is allowed");
4820 announced.assert_next_active(&path);
4821
4822 let _pending = producer.consume().request_broadcast(&path);
4823 let request = queued(&dynamic).await;
4824 assert_eq!(request.path().as_str(), path);
4825 }
4826
4827 #[tokio::test]
4828 async fn dynamic_exclusion_skips_routes_through_the_subscriber() {
4829 let producer = origin(1).produce();
4830 let _server = producer
4831 .dynamic("live", Route::default().with_hops(hops(&[7])))
4832 .unwrap();
4833
4834 let mut excluded = producer.consume().excluding(origin(7)).announced();
4835 excluded.assert_next_wait();
4836
4837 let mut clean = producer.consume().excluding(origin(8)).announced();
4838 clean.assert_next_active("live");
4839 }
4840
4841 #[tokio::test]
4843 async fn announce_consumer_is_a_stream() {
4844 use futures::StreamExt;
4845 let producer = origin(1).produce();
4846 let server = producer.dynamic("live", Route::default()).unwrap();
4847 let mut announced = producer.consume().announced();
4848 let update = StreamExt::next(&mut announced)
4849 .now_or_never()
4850 .expect("next")
4851 .expect("no next");
4852 assert_eq!(update.prefix.as_str(), "live");
4853 assert_eq!(update.kind, AnnounceKind::Announced);
4854 assert!(StreamExt::next(&mut announced).now_or_never().is_none());
4855 drop(server);
4856 let update = StreamExt::next(&mut announced)
4857 .now_or_never()
4858 .expect("next")
4859 .expect("no next");
4860 assert_eq!(update.kind, AnnounceKind::Retracted);
4861 }
4862
4863 #[tokio::test]
4864 async fn dynamic_retracts() {
4865 let producer = origin(1).produce();
4866 let server = producer.dynamic("live", Route::default()).unwrap();
4867 let mut announced = producer.consume().announced();
4868 announced.assert_next_active("live");
4869
4870 drop(server);
4871 announced.assert_next_ended("live");
4872 }
4873
4874 #[test]
4875 fn charged_wildcard_cost_accumulates_across_hops() {
4876 let first = Cost::new(4).charged(1);
4877 let second = first.charged(2);
4878 assert_eq!(second, Cost { warm: 7, cold: 7 });
4879 }
4880
4881 #[tokio::test]
4882 async fn local_broadcast_is_invisible_until_announced() {
4883 let producer = origin(1).produce();
4884 let mut local = producer.consume().announced();
4885 let mut peer = producer.consume().excluding(Hop::UNKNOWN).announced();
4886 let broadcast = producer.create_broadcast("room/alice").unwrap();
4887 local.assert_next_wait();
4888 peer.assert_next_wait();
4889
4890 broadcast.announce(Route::default()).unwrap();
4891 local.assert_next_active("room/alice");
4892 peer.assert_next_active("room/alice");
4893
4894 drop(broadcast);
4895 local.assert_next_ended("room/alice");
4896 peer.assert_next_ended("room/alice");
4897 }
4898
4899 #[tokio::test]
4900 async fn served_route_materializes_on_demand() {
4901 let producer = origin(1).produce();
4902 let consumer = producer.consume();
4903
4904 let server = producer.dynamic("room", Route::default()).unwrap();
4905
4906 let pending = consumer.request_broadcast("room/alice");
4907 let request = queued(&server).await;
4908 assert_eq!(request.path().as_str(), "room/alice");
4909
4910 let source = broadcast::Info::new().produce();
4911 request.accept(&source);
4912
4913 let resolved = pending.await.expect("resolves");
4914 assert_eq!(resolved.info().path.as_str(), "room/alice");
4916
4917 let again = consumer.request_broadcast("room/alice").await.expect("resolves");
4919 assert!(again.is_clone(&resolved));
4920 }
4921
4922 #[tokio::test]
4923 async fn served_requests_coalesce() {
4924 let producer = origin(1).produce();
4925 let consumer = producer.consume();
4926 let server = producer.dynamic("room", Route::default()).unwrap();
4927
4928 let first = consumer.request_broadcast("room/alice");
4929 let second = consumer.request_broadcast("room/alice");
4930
4931 let request = queued(&server).await;
4932 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
4934
4935 let source = broadcast::Info::new().produce();
4936 request.accept(&source);
4937
4938 let first = first.await.expect("resolves");
4939 let second = second.await.expect("resolves");
4940 assert!(first.is_clone(&second));
4941 }
4942
4943 #[tokio::test]
4944 async fn retract_rejects_pending_requests() {
4945 let producer = origin(1).produce();
4946 let consumer = producer.consume();
4947 let server = producer.dynamic("room", Route::default()).unwrap();
4948
4949 let pending = consumer.request_broadcast("room/alice");
4950 drop(server);
4951
4952 let err = pending.await.err().unwrap();
4953 assert!(matches!(err, Error::Unroutable));
4954
4955 let err = consumer
4957 .request_broadcast("room/alice")
4958 .now_or_never()
4959 .expect("unroutable")
4960 .err()
4961 .unwrap();
4962 assert!(matches!(err, Error::Unroutable));
4963 }
4964
4965 #[tokio::test]
4966 async fn routed_broadcast_survives_serving_route_retraction() {
4967 let producer = origin(1).produce();
4968 let consumer = producer.consume();
4969
4970 let standby_server = producer.dynamic("room", Route::default()).unwrap();
4973 let second_server = producer.dynamic("room", Route::default()).unwrap();
4974 let incumbent_server = producer.dynamic("room", Route::default()).unwrap();
4975
4976 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
4977 assert!((&mut resolving).now_or_never().is_none());
4978
4979 drop(incumbent_server);
4985 assert!((&mut resolving).now_or_never().is_none());
4986 drop(second_server);
4987 assert!((&mut resolving).now_or_never().is_none());
4988
4989 let request = queued(&standby_server).await;
4990 let source = broadcast::Info::new().produce();
4991 request.accept(&source);
4992
4993 let resolved = resolving.await.expect("resolves via the standby");
4994 assert_eq!(resolved.info().path.as_str(), "room/alice");
4995 }
4996
4997 #[tokio::test]
4998 async fn split_horizon_skips_routes_through_the_requester() {
4999 let producer = origin(1).produce();
5000 let _server = producer
5001 .dynamic("room", Route::default().with_hops(hops(&[7])))
5002 .unwrap();
5003
5004 let excluded = producer.consume().excluding(origin(7));
5006 let err = excluded
5007 .request_broadcast("room/alice")
5008 .now_or_never()
5009 .expect("unroutable")
5010 .err()
5011 .unwrap();
5012 assert!(matches!(err, Error::Unroutable));
5013
5014 let clean = producer.consume().excluding(origin(8));
5016 let pending = clean.request_broadcast("room/alice");
5017 assert!(pending.now_or_never().is_none());
5018 }
5019
5020 #[tokio::test]
5021 async fn routes_report_where_they_entered() {
5022 let producer = origin(1).produce();
5023 let peer = producer.clone().peer();
5024 let mut announced = producer.consume().announced();
5025
5026 let _ingest = producer
5027 .dynamic("client", Route::default().with_hops(hops(&[5])).with_via(origin(5)))
5028 .unwrap();
5029 let _gateway = producer.publish("gateway", Route::default()).unwrap();
5030 let _forwarded = peer
5031 .dynamic(
5032 "forwarded",
5033 Route::default().with_hops(hops(&[5, 7])).with_via(origin(7)),
5034 )
5035 .unwrap();
5036
5037 assert_eq!(announced.assert_next_active("client").source(), Source::Local);
5038 assert_eq!(
5039 announced.assert_next_active("forwarded").source(),
5040 Source::Peer(origin(7))
5041 );
5042 assert_eq!(announced.assert_next_active("gateway").source(), Source::Local);
5043
5044 let scoped = peer.scope("room", &Patterns::from(Pattern::all())).unwrap();
5046 let _nested = scoped.dynamic("x", Route::default().with_via(origin(8))).unwrap();
5047 assert_eq!(announced.assert_next_active("room/x").source(), Source::Peer(origin(8)));
5048 }
5049
5050 #[tokio::test]
5053 async fn source_change_is_an_update() {
5054 let producer = origin(1).produce();
5055 let peer = producer.clone().peer();
5056 let mut announced = producer.consume().announced();
5057
5058 let route = Route::default().with_hops(hops(&[7])).with_via(origin(7));
5059 let _forwarded = peer.dynamic("room", route.clone()).unwrap();
5060 assert_eq!(announced.assert_next_active("room").source(), Source::Peer(origin(7)));
5061
5062 let local = producer.dynamic("room", route).unwrap();
5064 let update = announced.next().now_or_never().expect("next blocked").expect("no next");
5065 assert_eq!(update.kind, AnnounceKind::Updated);
5066 assert_eq!(update.route.source(), Source::Local);
5067
5068 drop(local);
5069 assert_eq!(announced.assert_next_active("room").source(), Source::Peer(origin(7)));
5070 }
5071
5072 #[tokio::test]
5073 async fn local_view_hides_peer_routes() {
5074 let producer = origin(1).produce();
5075 let peer = producer.clone().peer();
5076 let mut local = producer.consume().local().announced();
5077
5078 let _forwarded = peer
5079 .dynamic("remote", Route::default().with_hops(hops(&[7])).with_via(origin(7)))
5080 .unwrap();
5081 local.assert_next_wait();
5082
5083 let _shadow = peer
5087 .dynamic("both", Route::default().with_hops(hops(&[7])).with_via(origin(7)))
5088 .unwrap();
5089 let ingest = producer
5090 .dynamic(
5091 "both",
5092 Route::default().with_hops(hops(&[5])).with_via(origin(5)).with_cost(9),
5093 )
5094 .unwrap();
5095 assert_eq!(local.assert_next_active("both").source(), Source::Local);
5096 drop(ingest);
5097 local.assert_next_ended("both");
5098
5099 let err = producer
5102 .consume()
5103 .local()
5104 .request_broadcast("remote/alice")
5105 .now_or_never()
5106 .expect("unroutable")
5107 .err()
5108 .unwrap();
5109 assert!(matches!(err, Error::Unroutable));
5110 assert!(
5111 producer
5112 .consume()
5113 .request_broadcast("remote/alice")
5114 .now_or_never()
5115 .is_none()
5116 );
5117 }
5118
5119 #[tokio::test]
5123 async fn handler_rejection_is_final() {
5124 let producer = origin(1).produce();
5125 let consumer = producer.consume();
5126 let server = producer.dynamic("room", Route::default()).unwrap();
5127
5128 let pending = consumer.request_broadcast("room/alice");
5129 let request = queued(&server).await;
5130 request.reject(Error::Unroutable);
5131 let err = tokio::time::timeout(Duration::from_secs(5), pending)
5132 .await
5133 .expect("the front must give up, not spin")
5134 .err()
5135 .unwrap();
5136 assert!(matches!(err, Error::Unroutable));
5137
5138 let pending = consumer.request_broadcast("room/bob");
5140 let request = queued(&server).await;
5141 assert_eq!(request.path().as_str(), "room/bob");
5142 let served = broadcast::Info::new().produce();
5143 request.accept(&served);
5144 pending.await.expect("resolves");
5145 }
5146
5147 #[tokio::test]
5150 async fn routed_broadcast_waits_out_a_rejection() {
5151 let producer = origin(1).produce();
5152 let consumer = producer.consume();
5153 let server = producer.dynamic("room", Route::default()).unwrap();
5154
5155 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
5156 assert!((&mut resolving).now_or_never().is_none());
5157 let request = queued(&server).await;
5158 request.reject(Error::Unroutable);
5159
5160 for _ in 0..20 {
5162 tokio::task::yield_now().await;
5163 }
5164 assert!((&mut resolving).now_or_never().is_none());
5165 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
5166
5167 server.update(Route::default().with_cost(2)).unwrap();
5169 assert!((&mut resolving).now_or_never().is_none());
5170 let request = queued(&server).await;
5171 let served = broadcast::Info::new().produce();
5172 request.accept(&served);
5173 resolving.await.expect("resolves");
5174 }
5175
5176 #[tokio::test]
5179 async fn routed_broadcast_reports_teardown_as_closed() {
5180 let (producer, driver) = Producer::new(Config::new(origin(1)));
5181 let consumer = producer.consume();
5182 let _server = producer.dynamic("room", Route::default()).unwrap();
5183
5184 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
5186 assert!((&mut resolving).now_or_never().is_none());
5187
5188 drop(driver);
5189
5190 let err = tokio::time::timeout(Duration::from_secs(5), resolving)
5191 .await
5192 .expect("teardown resolves the wait")
5193 .err()
5194 .unwrap();
5195 assert!(matches!(err, Error::Closed), "unexpected end: {err}");
5196 }
5197
5198 #[tokio::test]
5201 async fn routed_broadcast_wakes_for_a_local_broadcast() {
5202 let producer = origin(1).produce();
5203 let consumer = producer.consume();
5204 let server = producer.dynamic("room", Route::default()).unwrap();
5205
5206 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
5207 assert!((&mut resolving).now_or_never().is_none());
5208 queued(&server).await.reject(Error::Unroutable);
5209 for _ in 0..20 {
5210 tokio::task::yield_now().await;
5211 }
5212 assert!((&mut resolving).now_or_never().is_none());
5213
5214 let _local = producer.publish("room/alice", Route::default()).unwrap();
5216 let resolved = resolving.await.expect("resolves locally");
5217 assert_eq!(resolved.info().path.as_str(), "room/alice");
5218 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
5219 }
5220
5221 #[tokio::test]
5224 async fn late_track_on_a_served_front_replays() {
5225 let producer = origin(1).produce();
5226 let consumer = producer.consume();
5227 let server = producer.dynamic("room", Route::default()).unwrap();
5228
5229 let source = broadcast::Info::new().produce();
5230 for name in ["a", "b"] {
5231 let track = source.create_track(name, None).unwrap();
5232 let mut group = track.append_group().unwrap();
5233 group.write_frame(crate::Timestamp::ZERO, name.as_bytes()).unwrap();
5234 group.finish().unwrap();
5235 std::mem::forget(track);
5237 }
5238
5239 let pending = consumer.request_broadcast("room/alice");
5240 queued(&server).await.accept(&source);
5241 let resolved = pending.await.expect("resolves");
5242
5243 let budget = track::Subscription::default().with_max_age(Duration::from_secs(3600));
5244 for name in ["a", "b"] {
5245 let mut subscription = resolved
5246 .track(name)
5247 .unwrap()
5248 .subscribe(budget.clone())
5249 .await
5250 .expect("subscribe");
5251 let mut group = tokio::time::timeout(Duration::from_secs(5), subscription.recv_group())
5252 .await
5253 .expect("the late track must replay, not park")
5254 .expect("recv group")
5255 .expect("track ended early");
5256 let frame = group.read_frame().await.expect("read frame").expect("frame");
5257 assert_eq!(&frame.payload[..], name.as_bytes());
5258 }
5259 }
5260
5261 #[tokio::test]
5262 async fn most_specific_prefix_shadows() {
5263 let producer = origin(1).produce();
5264 let consumer = producer.consume();
5265
5266 let broad_server = producer.dynamic("", Route::default()).unwrap();
5267 let _narrow = producer.announce(".dash", Route::default()).unwrap();
5270
5271 let err = consumer
5272 .request_broadcast(".dash/pid")
5273 .now_or_never()
5274 .expect("unroutable")
5275 .err()
5276 .unwrap();
5277 assert!(matches!(err, Error::Unroutable));
5278
5279 let _pending = consumer.request_broadcast("room/alice");
5281 let request = queued(&broad_server).await;
5282 assert_eq!(request.path().as_str(), "room/alice");
5283 }
5284
5285 #[tokio::test]
5286 async fn root_dynamic_serves_any_path() {
5287 let producer = origin(1).produce();
5288 let consumer = producer.consume();
5289 let mut announced = consumer.announced();
5290 let dynamic = producer.dynamic("", Route::default()).unwrap();
5291 announced.assert_next_active("");
5293
5294 let pending = consumer.request_broadcast("anything/at/all");
5295 let request = queued(&dynamic).await;
5296 assert_eq!(request.path().as_str(), "anything/at/all");
5297
5298 let source = broadcast::Info::new().produce();
5299 request.accept(&source);
5300 let resolved = pending.await.expect("resolves");
5301 assert_eq!(resolved.info().path.as_str(), "anything/at/all");
5302
5303 drop(dynamic);
5305 announced.assert_next_ended("");
5306 let err = consumer
5307 .request_broadcast("something/else")
5308 .now_or_never()
5309 .expect("unroutable")
5310 .err()
5311 .unwrap();
5312 assert!(matches!(err, Error::Unroutable));
5313 }
5314
5315 #[tokio::test]
5321 async fn out_of_scope_request_never_reaches_the_dynamic_handler() {
5322 let producer = origin(1).produce();
5323 let dynamic = producer.dynamic("", Route::default()).unwrap();
5324 let scoped = producer.consume().scope("", &scopes(&["tenant-a"])).unwrap();
5325
5326 for path in ["tenant-b/live", "tenant-a-other/live"] {
5329 let refused = scoped
5330 .request_broadcast(path)
5331 .now_or_never()
5332 .expect("an out-of-scope request must be refused synchronously, not queued");
5333 assert!(matches!(refused, Err(Error::Unauthorized)));
5334 assert!(
5335 dynamic.requested_broadcast().now_or_never().is_none(),
5336 "the dynamic handler was asked to create a broadcast the requester may not read"
5337 );
5338 }
5339 }
5340
5341 #[tokio::test]
5342 async fn routed_waits_for_coverage() {
5343 let producer = origin(1).produce();
5344 let consumer = producer.consume();
5345
5346 let mut fut = consumer.routed("room/alice").boxed();
5347 assert!((&mut fut).now_or_never().is_none());
5348
5349 let _a = producer.announce("room", Route::default().with_cost(3)).unwrap();
5351 let route = fut.now_or_never().expect("covered").expect("routed");
5352 assert_eq!(route.cost, Cost::new(3));
5353
5354 consumer
5356 .routed("room/alice/cam")
5357 .now_or_never()
5358 .expect("covered")
5359 .expect("routed");
5360 }
5361
5362 #[tokio::test]
5363 async fn routed_ignores_deeper_routes() {
5364 let producer = origin(1).produce();
5365 let consumer = producer.consume();
5366
5367 let _deep = producer.announce("room/alice/cam", Route::default()).unwrap();
5369 let mut fut = consumer.routed("room/alice").boxed();
5370 assert!((&mut fut).now_or_never().is_none());
5371
5372 let _exact = producer.announce("room/alice", Route::default()).unwrap();
5373 fut.now_or_never().expect("covered").expect("routed");
5374 }
5375
5376 #[tokio::test]
5377 async fn routed_accepts_a_max_depth_path() {
5378 let producer = origin(1).produce();
5379 let consumer = producer.consume();
5380 let path = (0..Path::MAX_PARTS)
5381 .map(|i| format!("s{i}"))
5382 .collect::<Vec<_>>()
5383 .join("/");
5384 assert_eq!(Path::new(&path).parts().count(), Path::MAX_PARTS);
5385
5386 assert!(consumer.allowed().matches(&path));
5387
5388 let mut fut = consumer.routed(&path).boxed();
5389 assert!((&mut fut).now_or_never().is_none());
5390
5391 let _a = producer.announce("", Route::default()).unwrap();
5393 fut.now_or_never().expect("covered").expect("routed");
5394 }
5395
5396 #[tokio::test]
5397 async fn teardown_ends_everything() {
5398 let (producer, driver) = Producer::new(Config::new(origin(1)));
5399 let consumer = producer.consume();
5400 let _announcement = producer.announce("room", Route::default()).unwrap();
5401 let mut announced = consumer.announced();
5402 announced.assert_next_active("room");
5403
5404 let _server = producer.dynamic("served", Route::default()).unwrap();
5405 let pending = consumer.request_broadcast("served/path");
5406
5407 drop(driver);
5408
5409 announced.assert_next_active("served");
5411 assert!(announced.next().now_or_never().expect("ended").is_none());
5412
5413 assert!(pending.now_or_never().expect("rejected").is_err());
5415 assert!(matches!(producer.announce("x", Route::default()), Err(Error::Closed)));
5416 assert!(matches!(producer.create_broadcast("x"), Err(Error::Closed)));
5417 let err = consumer
5418 .request_broadcast("y")
5419 .now_or_never()
5420 .expect("closed")
5421 .err()
5422 .unwrap();
5423 assert!(matches!(err, Error::Closed));
5424
5425 let mut late = consumer.announced();
5427 assert!(late.next().now_or_never().expect("ended").is_none());
5428 }
5429
5430 struct ResumeRig {
5433 producer: Producer,
5434 resolved: broadcast::Consumer,
5435 subscription: track::Subscriber,
5436 incumbent_track: track::Producer,
5439 }
5440
5441 impl ResumeRig {
5442 async fn new(first: &[u64]) -> (Self, Dynamic, broadcast::Producer) {
5445 let producer = origin(1).produce();
5446 let consumer = producer.consume();
5447
5448 let server = producer
5449 .dynamic("room", Route::default().with_hops(hops(first)))
5450 .unwrap();
5451
5452 let pending = consumer.request_broadcast("room/alice");
5453 let request = queued(&server).await;
5454 let source = broadcast::Info::new().produce();
5455 let track = source.create_track("video", None).unwrap();
5456 let mut group = track.append_group().unwrap();
5457 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
5458 group.finish().unwrap();
5459 request.accept(&source);
5460
5461 let resolved = pending.await.expect("resolves");
5462 let mut subscription = resolved
5463 .track("video")
5464 .unwrap()
5465 .subscribe(None)
5466 .await
5467 .expect("subscribe");
5468 let mut group = subscription
5469 .recv_group()
5470 .await
5471 .expect("recv group")
5472 .expect("track ended early");
5473 let frame = group.read_frame().await.expect("read frame").expect("frame");
5474 assert_eq!(&frame.payload[..], b"before");
5475
5476 (
5477 Self {
5478 producer,
5479 resolved,
5480 subscription,
5481 incumbent_track: track,
5482 },
5483 server,
5484 source,
5485 )
5486 }
5487
5488 fn standby(&self, first: &[u64]) -> Dynamic {
5491 self.producer
5492 .dynamic("room", Route::default().with_hops(hops(first)))
5493 .unwrap()
5494 }
5495 }
5496
5497 async fn assert_resumes(rig: &mut ResumeRig, server: &Dynamic) {
5502 let request = queued(server).await;
5503 let replacement = broadcast::Info::new().produce();
5504 let track = replacement.create_track("video", None).unwrap();
5505 let mut group = track.append_group().unwrap();
5508 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
5509 group.finish().unwrap();
5510 request.accept(&replacement);
5511
5512 let mut group = track.append_group().unwrap();
5513 group.write_frame(crate::Timestamp::ZERO, b"resumed".as_ref()).unwrap();
5514 group.finish().unwrap();
5515
5516 let mut group = rig
5517 .subscription
5518 .recv_group()
5519 .await
5520 .expect("subscription survives the failover")
5521 .expect("track ended early");
5522 let frame = group.read_frame().await.expect("read frame").expect("frame");
5523 assert_eq!(&frame.payload[..], b"resumed");
5524 }
5525
5526 #[tokio::test]
5529 async fn driver_resolves_with_live_consumers() {
5530 let (producer, driver) = Producer::new(Config::new(origin(1)));
5531 let consumer = producer.consume();
5532 let run = crate::time::run(driver);
5533 drop(producer);
5534 tokio::time::timeout(Duration::from_secs(5), run)
5535 .await
5536 .expect("driver must finish once the producers are gone");
5537 drop(consumer);
5538 }
5539
5540 #[tokio::test]
5541 async fn remote_source_resumes_through_same_first_hop() {
5542 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
5543 let standby_server = rig.standby(&[10, 20]);
5544
5545 drop(incumbent);
5547 drop(source);
5548
5549 assert_resumes(&mut rig, &standby_server).await;
5551 }
5552
5553 #[tokio::test]
5557 async fn incompatible_successor_is_refused() {
5558 for replacement in [
5559 track::Info::default().with_timescale(crate::Timescale::MICRO),
5560 track::Info::default().with_priority(7),
5561 track::Info::default().with_max_age(Duration::from_secs(7)),
5562 ] {
5563 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
5564 let standby_server = rig.standby(&[10, 20]);
5565 drop(incumbent);
5566 drop(source);
5567
5568 let request = queued(&standby_server).await;
5571 let successor = broadcast::Info::new().produce();
5572 let track = successor.create_track("video", replacement).unwrap();
5573 let mut group = track.append_group().unwrap();
5574 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
5575 group.finish().unwrap();
5576 request.accept(&successor);
5577
5578 assert!(
5579 matches!(rig.subscription.recv_group().await, Err(Error::Unsupported)),
5580 "the subscription must abort rather than resume onto incompatible metadata"
5581 );
5582
5583 let reopened = rig.resolved.track("video").unwrap();
5585 assert!(matches!(reopened.query().await, Err(Error::Unsupported)));
5586 assert!(matches!(reopened.subscribe(None).await, Err(Error::Unsupported)));
5587 }
5588 }
5589
5590 #[tokio::test]
5591 async fn different_first_hop_ends_the_subscription() {
5592 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
5593 let rival_server = rig.standby(&[11]);
5595
5596 drop(incumbent);
5599 drop(source);
5600 rig.incumbent_track.abort(Error::Dropped).unwrap();
5601
5602 let err = rig.subscription.recv_group().await.err().expect("subscription ends");
5604 assert!(matches!(err, Error::Dropped), "unexpected end: {err}");
5605
5606 let consumer = rig.producer.consume();
5608 let pending = consumer.request_broadcast("room/alice");
5609 let request = queued(&rival_server).await;
5610 let replacement = broadcast::Info::new().produce();
5611 request.accept(&replacement);
5612 pending.await.expect("re-request resolves through the rival");
5613 }
5614
5615 #[tokio::test]
5616 async fn anonymous_routes_never_resume() {
5617 let (mut rig, incumbent, source) = ResumeRig::new(&[]).await;
5620 let _twin_server = rig.standby(&[]);
5621
5622 drop(incumbent);
5623 drop(source);
5624 rig.incumbent_track.abort(Error::Dropped).unwrap();
5625
5626 let err = rig.subscription.recv_group().await.err().expect("subscription ends");
5627 assert!(matches!(err, Error::Dropped), "unexpected end: {err}");
5628 }
5629
5630 #[tokio::test]
5637 async fn anonymous_handoff_serves_the_newcomer_immediately() {
5638 let producer = origin(1).produce();
5639
5640 let server_a = producer
5642 .dynamic("room", Route::default().with_hops(hops(&[10])))
5643 .unwrap();
5644
5645 let consumer = producer.consume().excluding(origin(30));
5648 let pending = consumer.request_broadcast("room/alice");
5649 let request = queued(&server_a).await;
5650 let source_a = broadcast::Info::new().produce();
5651 let track_a = source_a.create_track("video", None).unwrap();
5652 let mut group = track_a.append_group().unwrap();
5653 group.write_frame(crate::Timestamp::ZERO, b"from-a".as_ref()).unwrap();
5654 group.finish().unwrap();
5655 request.accept(&source_a);
5656
5657 let resolved_a = pending.await.expect("resolves");
5658 let mut sub_a = resolved_a
5659 .track("video")
5660 .unwrap()
5661 .subscribe(None)
5662 .await
5663 .expect("subscribe");
5664 let mut group = sub_a
5665 .recv_group()
5666 .await
5667 .expect("recv group")
5668 .expect("track ended early");
5669 assert_eq!(
5670 &group.read_frame().await.expect("read frame").expect("frame").payload[..],
5671 b"from-a"
5672 );
5673
5674 drop(track_a);
5677 drop(source_a);
5678 drop(server_a);
5679
5680 let err = sub_a.recv_group().await.err().expect("front closed");
5682 assert!(matches!(err, Error::Dropped), "unexpected end: {err}");
5683
5684 settle(|| consumer.get_broadcast("room/alice").is_none()).await;
5688 settle(|| {
5689 matches!(
5690 consumer.request_broadcast("room/alice").now_or_never(),
5691 Some(Err(Error::Unroutable))
5692 )
5693 })
5694 .await;
5695
5696 let server_b = producer
5698 .dynamic("room", Route::default().with_hops(hops(&[20])))
5699 .unwrap();
5700 let pending = consumer.request_broadcast("room/alice");
5701 let request = queued(&server_b).await;
5702 let source_b = broadcast::Info::new().produce();
5703 let track_b = source_b.create_track("video", None).unwrap();
5704 let mut group = track_b.append_group().unwrap();
5705 group.write_frame(crate::Timestamp::ZERO, b"from-b".as_ref()).unwrap();
5706 group.finish().unwrap();
5707 request.accept(&source_b);
5708
5709 let resolved_b = pending.await.expect("B's front is served immediately");
5710 assert!(
5711 !resolved_b.is_clone(&resolved_a),
5712 "B must not splice into A's closed front"
5713 );
5714
5715 let mut sub_b = resolved_b
5716 .track("video")
5717 .unwrap()
5718 .subscribe(None)
5719 .await
5720 .expect("subscribe");
5721 let mut group = sub_b
5722 .recv_group()
5723 .await
5724 .expect("recv group")
5725 .expect("track ended early");
5726 assert_eq!(
5727 &group.read_frame().await.expect("read frame").expect("frame").payload[..],
5728 b"from-b"
5729 );
5730 }
5731
5732 #[tokio::test]
5733 async fn reprice_is_invisible_to_the_subscription() {
5734 let (rig, incumbent, source) = ResumeRig::new(&[10]).await;
5735
5736 incumbent
5739 .update(Route::default().with_hops(hops(&[10])).with_cost(9))
5740 .unwrap();
5741
5742 let track = source.create_track("audio", None).unwrap();
5743 let mut group = track.append_group().unwrap();
5744 group.write_frame(crate::Timestamp::ZERO, b"steady".as_ref()).unwrap();
5745 group.finish().unwrap();
5746
5747 let mut audio = rig
5748 .resolved
5749 .track("audio")
5750 .unwrap()
5751 .subscribe(None)
5752 .await
5753 .expect("subscribe survives the reprice");
5754 let mut group = audio
5755 .recv_group()
5756 .await
5757 .expect("recv group")
5758 .expect("track ended early");
5759 let frame = group.read_frame().await.expect("read frame").expect("frame");
5760 assert_eq!(&frame.payload[..], b"steady");
5761 }
5762
5763 #[tokio::test]
5764 async fn drain_reprice_migrates_before_the_session_dies() {
5765 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
5766 let standby_server = rig.standby(&[10, 20]);
5767
5768 incumbent
5772 .update(Route::default().with_hops(hops(&[10])).with_cost(Cost::DRAIN))
5773 .unwrap();
5774
5775 assert_resumes(&mut rig, &standby_server).await;
5776
5777 drop(incumbent);
5779 drop(source);
5780 }
5781
5782 #[tokio::test]
5783 async fn local_sources_splice_newest_first() {
5784 let producer = origin(1).produce();
5785 let consumer = producer.consume();
5786
5787 let first = producer.publish("room/alice", Route::default()).unwrap();
5788 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
5789
5790 let second = producer.publish("room/alice", Route::default()).unwrap();
5792 let again = consumer.request_broadcast("room/alice").await.expect("resolves");
5793 assert!(again.is_clone(&resolved));
5794
5795 first.finish();
5797 settle(|| consumer.get_broadcast("room/alice").is_some()).await;
5798 second.finish();
5799 settle(|| consumer.get_broadcast("room/alice").is_none()).await;
5800
5801 let _third = producer.publish("room/alice", Route::default()).unwrap();
5803 assert!(consumer.get_broadcast("room/alice").is_some());
5804 }
5805
5806 #[tokio::test]
5814 async fn a_finished_broadcast_concludes_in_flight_subscriptions() {
5815 let producer = origin(1).produce();
5816 let consumer = producer.consume();
5817
5818 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
5819 let track = broadcast.create_track("video", None).unwrap();
5820
5821 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
5822 let mut subscription = resolved
5823 .track("video")
5824 .unwrap()
5825 .subscribe(None)
5826 .await
5827 .expect("subscribe");
5828 let mut group = track.append_group().unwrap();
5830 group.write_frame(crate::Timestamp::ZERO, b"tail".as_ref()).unwrap();
5831 group.finish().unwrap();
5832 track.finish().unwrap();
5833 drop(track);
5834 broadcast.finish();
5835
5836 let mut group = next_group(&mut subscription)
5837 .await
5838 .expect("a cleanly finished track was served as an error")
5839 .expect("the track ended before its last group");
5840 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"tail");
5841 drop(group);
5842
5843 let end = next_group(&mut subscription)
5844 .await
5845 .expect("a cleanly finished track ended as an error");
5846 assert!(end.is_none(), "a group followed the final one");
5847 }
5848
5849 #[tokio::test]
5856 async fn a_retracted_route_concludes_in_flight_subscriptions() {
5857 let producer = origin(1).produce();
5858 let consumer = producer.consume();
5859 let server = producer
5860 .dynamic("room", Route::default().with_hops(hops(&[10])))
5861 .unwrap();
5862
5863 let pending = consumer.request_broadcast("room/alice");
5864 let request = queued(&server).await;
5865 let source = broadcast::Info::new().produce();
5866 let track = source.create_track("video", None).unwrap();
5867 request.accept(&source);
5868
5869 let resolved = pending.await.expect("resolves");
5870 let mut subscription = resolved
5871 .track("video")
5872 .unwrap()
5873 .subscribe(None)
5874 .await
5875 .expect("subscribe");
5876
5877 source.finish();
5880 drop(server);
5881 settle(|| resolved.is_closed()).await;
5882 let mut group = track.append_group().unwrap();
5883 group.write_frame(crate::Timestamp::ZERO, b"tail".as_ref()).unwrap();
5884 group.finish().unwrap();
5885 track.finish().unwrap();
5886 drop(track);
5887
5888 let mut group = next_group(&mut subscription)
5889 .await
5890 .expect("a retracted route's track was served as an error")
5891 .expect("the track ended before its last group");
5892 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"tail");
5893 drop(group);
5894
5895 let end = next_group(&mut subscription)
5896 .await
5897 .expect("a cleanly finished track ended as an error");
5898 assert!(end.is_none(), "a group followed the final one");
5899 }
5900
5901 #[tokio::test]
5906 async fn origin_front_drops_the_source_when_unused() {
5907 let producer = origin(1).produce();
5908 let consumer = producer.consume();
5909
5910 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
5911 let track = broadcast.create_track("video", None).unwrap();
5912 let mut group = track.append_group().unwrap();
5913 group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap();
5914 group.finish().unwrap();
5915
5916 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
5917 let mut subscription = resolved
5918 .track("video")
5919 .unwrap()
5920 .subscribe(None)
5921 .await
5922 .expect("subscribe");
5923 let mut group = subscription.recv_group().await.unwrap().unwrap();
5924 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
5925 drop(group);
5926 drop(subscription);
5927
5928 tokio::time::timeout(Duration::from_secs(1), track.unused())
5929 .await
5930 .expect("source unused should resolve far below TRACK_IDLE_LINGER")
5931 .expect("source closed");
5932
5933 let mut again = resolved
5936 .track("video")
5937 .unwrap()
5938 .subscribe(track::Subscription::default().with_max_age(Duration::from_secs(3600)))
5939 .await
5940 .expect("resubscribe");
5941 let mut group = tokio::time::timeout(Duration::from_secs(1), again.recv_group())
5942 .await
5943 .expect("cached group is still on the front")
5944 .expect("recv group")
5945 .expect("track ended early");
5946 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
5947
5948 tokio::time::timeout(Duration::from_secs(1), track.used())
5949 .await
5950 .expect("returning reader re-splices the source")
5951 .expect("source closed");
5952
5953 let mut group = track.append_group().unwrap();
5954 group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap();
5955 group.finish().unwrap();
5956 let mut group = tokio::time::timeout(Duration::from_secs(1), again.recv_group())
5957 .await
5958 .expect("groups past the cached edge come from the re-splice")
5959 .expect("recv group")
5960 .expect("track ended early");
5961 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"live");
5962 }
5963
5964 #[tokio::test]
5969 async fn chained_front_drops_the_source_when_unused() {
5970 let leaf = origin(1).produce();
5971 let leaf_consumer = leaf.consume();
5972
5973 let broadcast = leaf.publish("room/alice", Route::default()).unwrap();
5974 let track = broadcast.create_track("video", None).unwrap();
5975 let mut group = track.append_group().unwrap();
5976 group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap();
5977 group.finish().unwrap();
5978
5979 let leaf_front = leaf_consumer.request_broadcast("room/alice").await.expect("resolves");
5982
5983 let mid = origin(2).produce();
5984 let mid_server = mid.dynamic("room", Route::default().with_hops(hops(&[10]))).unwrap();
5985 let mid_pending = mid.consume().request_broadcast("room/alice");
5986 queued(&mid_server).await.accept(&leaf_front);
5987 let mid_resolved = mid_pending.await.expect("mid resolves");
5988
5989 let edge = origin(3).produce();
5990 let edge_server = edge.dynamic("room", Route::default().with_hops(hops(&[20]))).unwrap();
5991 let edge_pending = edge.consume().request_broadcast("room/alice");
5992 queued(&edge_server).await.accept(&mid_resolved);
5993 let edge_resolved = edge_pending.await.expect("edge resolves");
5994
5995 let mut subscription = edge_resolved
5996 .track("video")
5997 .unwrap()
5998 .subscribe(None)
5999 .await
6000 .expect("subscribe");
6001 let mut group = subscription.recv_group().await.unwrap().unwrap();
6002 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
6003 drop(group);
6004 drop(subscription);
6005
6006 tokio::time::timeout(Duration::from_secs(5), track.unused())
6007 .await
6008 .expect("chained unused should resolve far below TRACK_IDLE_LINGER")
6009 .expect("source closed");
6010
6011 let cached = edge_resolved.track("video").unwrap().cached_groups();
6012 assert_eq!(
6013 cached.iter().map(|(group, _)| group.sequence).collect::<Vec<_>>(),
6014 vec![0],
6015 "every front keeps the delivered groups after releasing its source"
6016 );
6017
6018 let mut subscription = edge_resolved
6019 .track("video")
6020 .unwrap()
6021 .subscribe(None)
6022 .await
6023 .expect("resubscribe");
6024 tokio::time::timeout(Duration::from_secs(5), track.used())
6025 .await
6026 .expect("resubscribe should reach the leaf")
6027 .expect("source open");
6028 let mut group = track.append_group().unwrap();
6029 group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap();
6030 group.finish().unwrap();
6031 let mut group = subscription.recv_group().await.unwrap().unwrap();
6032 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
6033 drop(group);
6034 let mut group = subscription.recv_group().await.unwrap().unwrap();
6035 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"live");
6036 drop(group);
6037 drop(subscription);
6038
6039 tokio::time::timeout(Duration::from_secs(5), track.unused())
6040 .await
6041 .expect("second chained unused should resolve far below TRACK_IDLE_LINGER")
6042 .expect("source closed");
6043
6044 let cached = edge_resolved.track("video").unwrap().cached_groups();
6045 assert_eq!(
6046 cached.iter().map(|(group, _)| group.sequence).collect::<Vec<_>>(),
6047 vec![0, 1],
6048 "repeated demand keeps every complete group while releasing its source"
6049 );
6050
6051 let fetch = edge_resolved.track("video").unwrap().fetch_group(2, None);
6052 let mut fetch = std::pin::pin!(fetch);
6053 assert!(futures::poll!(fetch.as_mut()).is_pending(), "fetch should re-splice");
6054 tokio::time::timeout(Duration::from_secs(5), track.used())
6055 .await
6056 .expect("fetch should reach the leaf")
6057 .expect("source open");
6058 let mut group = track.append_group().unwrap();
6059 group.write_frame(crate::Timestamp::ZERO, b"fetched".as_ref()).unwrap();
6060 group.finish().unwrap();
6061 let mut group = tokio::time::timeout(Duration::from_secs(5), fetch)
6062 .await
6063 .expect("re-spliced source should answer the fetch")
6064 .expect("fetch succeeds");
6065 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"fetched");
6066 }
6067
6068 #[tokio::test]
6072 async fn incompatible_local_source_keeps_the_incumbent() {
6073 let producer = origin(1).produce();
6074 let consumer = producer.consume();
6075
6076 let first = producer.publish("room/alice", Route::default()).unwrap();
6077 let track = first.create_track("video", None).unwrap();
6078 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
6079 let mut subscription = resolved
6080 .track("video")
6081 .unwrap()
6082 .subscribe(None)
6083 .await
6084 .expect("subscribe");
6085 let mut group = track.append_group().unwrap();
6086 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
6087 group.finish().unwrap();
6088 let mut group = subscription.recv_group().await.unwrap().unwrap();
6089 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"before");
6090
6091 let second = producer.publish("room/alice", Route::default()).unwrap();
6093 let _incompatible = second
6094 .create_track("video", track::Info::default().with_timescale(crate::Timescale::MICRO))
6095 .unwrap();
6096 for _ in 0..10 {
6097 tokio::task::yield_now().await;
6098 }
6099
6100 let mut group = track.append_group().unwrap();
6102 group.write_frame(crate::Timestamp::ZERO, b"still".as_ref()).unwrap();
6103 group.finish().unwrap();
6104 let mut group = subscription.recv_group().await.unwrap().unwrap();
6105 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"still");
6106
6107 drop(track);
6109 first.finish();
6110 assert!(matches!(subscription.recv_group().await, Err(Error::Unsupported)));
6111 }
6112
6113 #[tokio::test]
6114 async fn multiple_scopes_present_one_broad_prefix() {
6115 let producer = origin(1).produce();
6116 let _a = producer.announce("", Route::default()).unwrap();
6117
6118 let consumer = producer.consume().scope("", &scopes(&["alpha", "beta"])).unwrap();
6119 let mut announced = consumer.announced();
6120 announced.assert_next_active("");
6121 announced.assert_next_wait();
6122 }
6123
6124 #[test]
6125 fn scope_accepts_every_pattern_union() {
6126 let producer = origin(1).produce();
6127
6128 let root = producer.scope("", &Patterns::from(Pattern::all())).unwrap();
6130 assert_eq!(root.allowed(), Patterns::from(Pattern::all()));
6131
6132 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
6134 assert_eq!(scoped.allowed(), scopes(&["room"]));
6135
6136 let multi = producer.scope("", &scopes(&["room", "room/chat", "anon"])).unwrap();
6138 assert_eq!(multi.allowed(), scopes(&["room", "anon"]));
6139
6140 let consumer = producer.consume().scope("", &scopes(&["room"])).unwrap();
6142 assert_eq!(consumer.allowed(), scopes(&["room"]));
6143
6144 for text in ["room", "", "*room", "room/*", "*", "**/room", "room/**/chat", "*.hang"] {
6145 let union = Patterns::from(text.parse::<Pattern>().unwrap());
6146 assert_eq!(producer.scope("", &union).expect(text).allowed(), union, "{text}");
6147 assert_eq!(
6148 producer.consume().scope("", &union).expect(text).allowed(),
6149 union,
6150 "{text}"
6151 );
6152 }
6153
6154 let mixed: Patterns = ["room/**".parse().unwrap(), "other".parse().unwrap()]
6155 .into_iter()
6156 .collect();
6157 assert_eq!(producer.scope("", &mixed).unwrap().allowed(), mixed);
6158 }
6159
6160 #[test]
6161 fn route_table_prunes_to_empty() {
6162 let producer = origin(1).produce();
6163 let consumer = producer.consume();
6164
6165 let cursor = consumer
6168 .scope("", &scopes(&["room/a", "other/deep/head"]))
6169 .unwrap()
6170 .announced();
6171 let route = producer.announce("room/a/b/c", Route::default()).unwrap();
6172 {
6173 let table = producer.shared.lock();
6174 assert!(table.routes.root.find(Path::new("room/a/b/c").parts()).is_some());
6175 assert!(table.routes.root.find(Path::new("other/deep/head").parts()).is_some());
6176 assert_eq!(table.routes.root.cursors_below, 2);
6177 }
6178
6179 drop(route);
6180 drop(cursor);
6181 let table = producer.shared.lock();
6182 assert!(table.routes.root.is_empty());
6183 assert_eq!(table.routes.root.cursors_below, 0);
6184 }
6185
6186 #[test]
6190 fn a_published_broadcast_keeps_the_driver_running() {
6191 let (producer, mut driver) = Producer::new(Config::new(origin(1)));
6192 let waiter = kio::Waiter::noop();
6193 let broadcast = producer.create_broadcast("room/a").unwrap();
6194 drop(producer);
6195 assert!(
6196 driver.poll(Instant::now(), &waiter).is_ok(),
6197 "the broadcast is lifecycle work"
6198 );
6199 drop(broadcast);
6200 assert!(matches!(driver.poll(Instant::now(), &waiter), Err(Error::Closed)));
6201 }
6202
6203 #[test]
6204 fn watch_wakes_only_for_covering_changes() {
6205 let producer = origin(1).produce();
6206 let waiter = kio::Waiter::noop();
6207 let watch = producer.shared.lock().watch(&producer.shared, &Path::new("room/a"));
6208 let seen = watch.seen();
6209
6210 let _other = producer.announce("other", Route::default()).unwrap();
6212 let _below = producer.announce("room/a/b", Route::default()).unwrap();
6213 assert!(watch.poll_changed(&waiter, seen).is_pending());
6214
6215 let above = producer.announce("room", Route::default()).unwrap();
6217 assert!(watch.poll_changed(&waiter, seen).is_ready());
6218 let seen = watch.seen();
6219 drop(above);
6220 assert!(watch.poll_changed(&waiter, seen).is_ready());
6221 let seen = watch.seen();
6222
6223 let _beside = producer.create_broadcast("room/b").unwrap();
6225 assert!(watch.poll_changed(&waiter, seen).is_pending());
6226 let _here = producer.create_broadcast("room/a").unwrap();
6227 assert!(watch.poll_changed(&waiter, seen).is_ready());
6228
6229 drop(watch);
6231 let table = producer.shared.lock();
6232 let node = table
6233 .routes
6234 .root
6235 .find(Path::new("room/a").parts())
6236 .expect("route below keeps the node");
6237 assert!(node.watches.is_empty());
6238 assert_eq!(table.routes.root.watches_below, 0);
6239 }
6240
6241 #[test]
6242 fn a_discarded_front_task_unregisters_its_watch() {
6243 let (producer, _driver) = Producer::new(Config {
6244 hop: origin(1),
6245 ..Default::default()
6246 });
6247 let consumer = producer.consume();
6248 let _served = producer.dynamic("room", Route::default()).unwrap();
6249 drop(producer);
6254 let _pending = consumer.request_broadcast("room/a");
6255 }
6256
6257 #[test]
6258 fn create_broadcast_refuses_a_path_no_pattern_can_spell() {
6259 let producer = origin(1).produce();
6260
6261 assert!(matches!(
6265 producer.create_broadcast("room/*"),
6266 Err(Error::InvalidPath(_))
6267 ));
6268 assert!(matches!(
6269 producer.announce("room/**", Route::default()),
6270 Err(Error::InvalidPath(_))
6271 ));
6272 }
6273
6274 #[test]
6275 fn scope_empty_union_grants_nothing() {
6276 let producer = origin(1).produce();
6277
6278 assert!(matches!(producer.scope("", &Patterns::new()), Err(Error::Unauthorized)));
6280 assert!(matches!(
6281 producer.consume().scope("", &Patterns::new()),
6282 Err(Error::Unauthorized)
6283 ));
6284 }
6285
6286 #[test]
6287 fn scope_nests_and_rebases_roots() {
6288 let producer = origin(1).produce();
6289
6290 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
6292 let nested = scoped.scope("", &scopes(&["room/chat"])).unwrap();
6293 assert_eq!(nested.allowed(), scopes(&["room/chat"]));
6294
6295 assert!(matches!(
6297 scoped.scope("", &scopes(&["other"])),
6298 Err(Error::Unauthorized)
6299 ));
6300
6301 let rooted = nested.scope("room/chat", &Patterns::from(Pattern::all())).unwrap();
6303 assert_eq!(rooted.allowed(), scopes(&[""]));
6304
6305 let broadcast = nested.create_broadcast("room/chat/live").unwrap();
6307 assert!(producer.consume().get_broadcast("room/chat/live").is_some());
6308 broadcast.finish();
6309 }
6310
6311 #[test]
6312 fn scope_intersects_and_rebases_arbitrary_grants() {
6313 let producer = origin(1).produce();
6314 let rooms = producer
6315 .scope("", &Patterns::from("room/*".parse::<Pattern>().unwrap()))
6316 .unwrap();
6317 let chats = rooms
6318 .scope("", &Patterns::from("*/chat".parse::<Pattern>().unwrap()))
6319 .unwrap();
6320 assert_eq!(chats.allowed(), Patterns::from("room/chat".parse::<Pattern>().unwrap()));
6321
6322 let exact = producer
6323 .scope("", &Patterns::from("room/alice".parse::<Pattern>().unwrap()))
6324 .unwrap();
6325 let rooted = exact.scope("room", &Patterns::from(Pattern::all())).unwrap();
6326 assert_eq!(rooted.allowed(), Patterns::from("alice".parse::<Pattern>().unwrap()));
6327 assert!(matches!(
6328 exact.scope("room/bob", &Patterns::from(Pattern::all())),
6329 Err(Error::Unauthorized)
6330 ));
6331
6332 let broadcast = exact.create_broadcast("room/alice").unwrap();
6333 assert!(matches!(
6334 exact.create_broadcast("room/alice/cam"),
6335 Err(Error::Unauthorized)
6336 ));
6337 assert!(producer.consume().get_broadcast("room/alice").is_some());
6338 drop(broadcast);
6339 }
6340
6341 #[tokio::test]
6342 async fn wildcard_scope_filters_announcements_and_reports_captures() {
6343 let producer = origin(1).produce();
6344 let consumer = producer
6345 .consume()
6346 .scope("", &Patterns::from("room/*/chat".parse::<Pattern>().unwrap()))
6347 .unwrap();
6348 let mut announced = consumer.announced();
6349
6350 let alice = producer.create_broadcast("room/alice/chat").unwrap();
6351 alice.announce(Route::default()).unwrap();
6352 let update = announced.try_next().expect("alice's chat");
6353 assert_eq!(update.prefix.as_str(), "room/alice/chat");
6354 assert_eq!(update.captures, Some(vec!["alice".parse::<Pattern>().unwrap()]));
6355
6356 let audio = producer.create_broadcast("room/alice/audio").unwrap();
6357 audio.announce(Route::default()).unwrap();
6358 announced.assert_next_wait();
6359
6360 let broad = producer.announce("room", Route::default()).unwrap();
6361 let update = announced.try_next().expect("overlapping broad route");
6362 assert_eq!(update.prefix.as_str(), "room");
6363 assert_eq!(update.captures, None, "an overlap does not pin the wildcard");
6364
6365 drop(broad);
6366 drop(audio);
6367 drop(alice);
6368 }
6369
6370 #[tokio::test]
6371 async fn local_broadcast_wins_announcement_ties() {
6372 let producer = origin(1).produce();
6373 let remote = producer.announce("room/alice", Route::default().with_cost(9)).unwrap();
6374 let local = producer.create_broadcast("room/alice").unwrap();
6375 local.announce(Route::default()).unwrap();
6376
6377 let mut announced = producer.consume().announced();
6378 let update = announced.try_next().expect("one winning route");
6379 assert_eq!(update.prefix.as_str(), "room/alice");
6380 assert_eq!(update.route.cost, Cost::default());
6381 announced.assert_next_wait();
6382
6383 drop(local);
6384 drop(remote);
6385 }
6386
6387 #[test]
6392 fn cost_charge_saturates() {
6393 assert_eq!(Cost { warm: 4, cold: 6 }.charged(5), Cost { warm: 9, cold: 11 });
6394 assert_eq!(Cost::new(u64::MAX).charged(10), Cost::new(MAX_COST));
6395
6396 assert_eq!(Cost::UNKNOWN.charged(3).cold, MAX_COST);
6399 }
6400
6401 fn expiring_origin(expiry: Duration) -> Producer {
6403 let pool = cache::Pool::new(cache::Config::default().with_expiry(expiry));
6404 Config {
6405 pool,
6406 ..Config::default()
6407 }
6408 .produce()
6409 }
6410
6411 #[tokio::test(start_paused = true)]
6415 async fn stalled_publisher_open_group_is_reclaimed() {
6416 let expiry = Duration::from_secs(1);
6417 let origin = expiring_origin(expiry);
6418 let broadcast = origin.create_broadcast("test").unwrap();
6419 let track = broadcast.create_track("video", None).unwrap();
6420
6421 let mut stalled = track.append_group().unwrap();
6422 stalled.write_frame(crate::Timestamp::ZERO, b"x".as_slice()).unwrap();
6423 let _successor = track.append_group().unwrap();
6427
6428 let mut reading = stalled.consume();
6429 assert!(reading.read_frame().await.unwrap().is_some());
6430
6431 crate::model::clock::advance(expiry * 2);
6433
6434 let reclaimed = tokio::time::timeout(Duration::from_secs(60), reading.read_frame()).await;
6437 assert!(
6438 matches!(reclaimed, Ok(Err(Error::Old))),
6439 "the sweep must reclaim an idle open group and surface the gap, got {reclaimed:?}"
6440 );
6441 }
6442
6443 #[tokio::test(start_paused = true)]
6446 async fn sweep_respects_a_disabled_expiry() {
6447 let origin = Config {
6448 pool: cache::Pool::unbounded(),
6449 ..Config::default()
6450 }
6451 .produce();
6452 let broadcast = origin.create_broadcast("test").unwrap();
6453 let track = broadcast.create_track("video", None).unwrap();
6454
6455 let mut stalled = track.append_group().unwrap();
6456 stalled.write_frame(crate::Timestamp::ZERO, b"x".as_slice()).unwrap();
6457 let _successor = track.append_group().unwrap();
6458
6459 let mut reading = stalled.consume();
6460 assert!(reading.read_frame().await.unwrap().is_some());
6461
6462 crate::model::clock::advance(Duration::from_secs(3600));
6463 tokio::time::advance(Duration::from_secs(3600)).await;
6464
6465 assert!(
6466 reading.read_frame().now_or_never().is_none(),
6467 "a pool without an expiry window never reclaims"
6468 );
6469 }
6470
6471 #[test]
6474 fn drain_cost_is_encodable() {
6475 use crate::coding::Encode;
6476
6477 let mut buf = Vec::new();
6478 Cost::DRAIN
6479 .encode(&mut buf, crate::lite::Version::Lite06)
6480 .expect("a draining route is still forwarded, so its cost must encode");
6481 }
6482}