Skip to main content

moq_net/model/
origin.rs

1use std::{
2	collections::{BTreeMap, HashMap, VecDeque},
3	fmt,
4	sync::atomic::{AtomicU64, Ordering},
5	task::{Poll, ready},
6};
7
8use rand::RngExt;
9use web_async::Lock;
10
11use super::BroadcastConsumer;
12use crate::{
13	AsPath, Broadcast, BroadcastProducer, Error, Path, PathOwned, PathPrefixes,
14	coding::{Decode, DecodeError, Encode, EncodeError},
15};
16
17/// A relay origin, identified by a 62-bit varint on the wire.
18///
19/// `id` must be non-zero for a real origin; `id == 0` is reserved as a
20/// placeholder for Lite03-style hops where the actual value isn't carried.
21/// Encoding a value outside the 62-bit range (>= 2^62) will fail at the
22/// varint layer; [`Origin::random`] picks a valid random nonzero id.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Origin {
26	/// Non-zero 62-bit identifier. Encoded as a QUIC varint on the wire.
27	pub id: u64,
28}
29
30impl Origin {
31	/// Placeholder for hop entries whose actual id is not on the wire (Lite03).
32	/// Never encoded for Lite04+: violates the non-zero invariant and would fail to round-trip.
33	pub(crate) const UNKNOWN: Self = Self { id: 0 };
34
35	/// Generate a fresh origin with a random non-zero id. Use this for any
36	/// origin that does not need a stable identity across restarts.
37	///
38	/// TEMPORARY: the wire format allows 62 bits, but older `@moq/lite` JS
39	/// clients decode `AnnounceInterest.exclude_hop` as a u53 (number) and
40	/// throw on anything > 2^53-1. To keep those clients alive against
41	/// fresh relays, we cap the random id at 53 bits. Restore to 62 bits
42	/// once the JS u62 fix has propagated to deployed bundles.
43	pub fn random() -> Self {
44		let mut rng = rand::rng();
45		let id = rng.random_range(1..(1u64 << 53));
46		Self { id }
47	}
48
49	/// Consume this [Origin] to create a producer that carries its id.
50	pub fn produce(self) -> OriginProducer {
51		OriginProducer::new(self)
52	}
53}
54
55impl From<u64> for Origin {
56	fn from(id: u64) -> Self {
57		Self { id }
58	}
59}
60
61impl fmt::Display for Origin {
62	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63		self.id.fmt(f)
64	}
65}
66
67impl<V: Copy> Encode<V> for Origin
68where
69	u64: Encode<V>,
70{
71	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
72		self.id.encode(w, version)
73	}
74}
75
76impl<V: Copy> Decode<V> for Origin
77where
78	u64: Decode<V>,
79{
80	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
81		let id = u64::decode(r, version)?;
82		if id >= 1u64 << 62 {
83			return Err(DecodeError::InvalidValue);
84		}
85		Ok(Self { id })
86	}
87}
88
89/// Maximum number of origins (hops) an [`OriginList`] can hold.
90///
91/// Caps pathological or loop-induced announcements at a reasonable cluster
92/// diameter; appending past this limit returns [`TooManyOrigins`] rather than
93/// silently truncating.
94pub(crate) const MAX_HOPS: usize = 32;
95
96/// Bounded list of [`Origin`] entries, typically the hop chain of a broadcast.
97///
98/// Guarantees `len() <= MAX_HOPS`. Construct via [`OriginList::new`] +
99/// [`OriginList::push`], or fall back to the fallible [`TryFrom<Vec<Origin>>`].
100#[derive(Debug, Clone, Default, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub struct OriginList(Vec<Origin>);
103
104/// Returned when an operation would grow an [`OriginList`] past its hop-count cap.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106#[non_exhaustive]
107pub struct TooManyOrigins;
108
109impl fmt::Display for TooManyOrigins {
110	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111		write!(f, "too many origins (max {MAX_HOPS})")
112	}
113}
114
115impl std::error::Error for TooManyOrigins {}
116
117impl From<TooManyOrigins> for DecodeError {
118	fn from(_: TooManyOrigins) -> Self {
119		DecodeError::BoundsExceeded
120	}
121}
122
123impl OriginList {
124	/// Create an empty list.
125	pub fn new() -> Self {
126		Self(Vec::new())
127	}
128
129	/// Append an [`Origin`]. Returns [`TooManyOrigins`] if the list is full.
130	pub fn push(&mut self, origin: Origin) -> Result<(), TooManyOrigins> {
131		if self.0.len() >= MAX_HOPS {
132			return Err(TooManyOrigins);
133		}
134		self.0.push(origin);
135		Ok(())
136	}
137
138	/// Replace the first entry equal to `target` with `replacement`, returning
139	/// true if a match was found. The length is unchanged.
140	pub fn replace_first(&mut self, target: Origin, replacement: Origin) -> bool {
141		for entry in &mut self.0 {
142			if *entry == target {
143				*entry = replacement;
144				return true;
145			}
146		}
147		false
148	}
149
150	/// Returns true if any entry matches `origin`.
151	pub fn contains(&self, origin: &Origin) -> bool {
152		self.0.contains(origin)
153	}
154
155	/// Number of entries currently in the list (always `<= MAX_HOPS`).
156	pub fn len(&self) -> usize {
157		self.0.len()
158	}
159
160	/// Whether the list contains no entries.
161	pub fn is_empty(&self) -> bool {
162		self.0.is_empty()
163	}
164
165	/// Iterate over the entries in hop order (oldest first).
166	pub fn iter(&self) -> std::slice::Iter<'_, Origin> {
167		self.0.iter()
168	}
169
170	/// Borrow the entries as a slice.
171	pub fn as_slice(&self) -> &[Origin] {
172		&self.0
173	}
174}
175
176impl TryFrom<Vec<Origin>> for OriginList {
177	type Error = TooManyOrigins;
178
179	fn try_from(v: Vec<Origin>) -> Result<Self, Self::Error> {
180		if v.len() > MAX_HOPS {
181			return Err(TooManyOrigins);
182		}
183		Ok(Self(v))
184	}
185}
186
187impl<'a> IntoIterator for &'a OriginList {
188	type Item = &'a Origin;
189	type IntoIter = std::slice::Iter<'a, Origin>;
190
191	fn into_iter(self) -> Self::IntoIter {
192		self.iter()
193	}
194}
195
196impl<V: Copy> Encode<V> for OriginList
197where
198	u64: Encode<V>,
199	Origin: Encode<V>,
200{
201	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
202		(self.0.len() as u64).encode(w, version)?;
203		for origin in &self.0 {
204			origin.encode(w, version)?;
205		}
206		Ok(())
207	}
208}
209
210impl<V: Copy> Decode<V> for OriginList
211where
212	u64: Decode<V>,
213	Origin: Decode<V>,
214{
215	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
216		let count = u64::decode(r, version)? as usize;
217		if count > MAX_HOPS {
218			return Err(DecodeError::BoundsExceeded);
219		}
220		let mut list = Vec::with_capacity(count);
221		for _ in 0..count {
222			list.push(Origin::decode(r, version)?);
223		}
224		Ok(Self(list))
225	}
226}
227
228static NEXT_CONSUMER_ID: AtomicU64 = AtomicU64::new(0);
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231struct ConsumerId(u64);
232
233impl ConsumerId {
234	fn new() -> Self {
235		Self(NEXT_CONSUMER_ID.fetch_add(1, Ordering::Relaxed))
236	}
237}
238
239// If there are multiple broadcasts with the same path, we keep the oldest active and queue the others.
240struct OriginBroadcast {
241	path: PathOwned,
242	active: BroadcastConsumer,
243	backup: VecDeque<BroadcastConsumer>,
244}
245
246/// Ordering key used to pick the active route among broadcasts at the same path.
247///
248/// Lower wins. Shorter hop chains sort first; equal-length chains are broken by a
249/// deterministic hash of the broadcast name and hop chain, so every node in the
250/// cluster, given the same candidate routes, converges on the same winner instead
251/// of relying on arrival order. Mixing the name in spreads equal-length routes
252/// across different upstreams rather than funneling every broadcast onto one.
253fn route_key(name: &Path, hops: &OriginList) -> (usize, u64) {
254	// FNV-1a, not the std hasher: its output is fixed across Rust versions and
255	// builds, which matters when nodes run mismatched binaries during a rolling
256	// deploy and still need to agree on the same route. SEED is a custom basis
257	// (any nonzero u64 works, the textbook one is just as arbitrary); FNV_PRIME is
258	// the standard FNV-64 prime and should stay put.
259	const SEED: u64 = 0x420C0DECB00B; // 420 C0DEC B00B
260	const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
261
262	let mut hash = SEED;
263	for &byte in name.as_str().as_bytes() {
264		hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
265	}
266	for hop in hops {
267		for &byte in &hop.id.to_le_bytes() {
268			hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
269		}
270	}
271
272	(hops.len(), hash)
273}
274
275/// One coalesced update queued for an `OriginConsumer`.
276///
277/// At most one entry exists per path, so a slow consumer's pending set is bounded
278/// by the number of distinct paths. `UnannounceAnnounce` preserves the
279/// signal that the broadcast at a path was replaced (the consumer must see
280/// `(path, None)` before `(path, Some(new))`), while a stale
281/// `Announce` cancels with a subsequent `unannounce` because the consumer
282/// has not yet observed it.
283enum PendingUpdate {
284	Announce(BroadcastConsumer),
285	Unannounce,
286	UnannounceAnnounce(BroadcastConsumer),
287}
288
289/// Pending updates keyed by path. `BTreeMap` keeps memory strictly bounded by
290/// the number of distinct paths with outstanding work (collapsed pairs are
291/// fully erased) and gives a deterministic lexicographic delivery order so
292/// tests can predict it.
293#[derive(Default)]
294struct OriginConsumerState {
295	pending: BTreeMap<PathOwned, PendingUpdate>,
296}
297
298impl OriginConsumerState {
299	fn apply_announce(&mut self, path: PathOwned, broadcast: BroadcastConsumer) {
300		let new = match self.pending.remove(&path) {
301			// First announce, or a stale announce being replaced.
302			None | Some(PendingUpdate::Announce(_)) => PendingUpdate::Announce(broadcast),
303			// Consumer needs to observe the unannounce before this announce.
304			Some(PendingUpdate::Unannounce | PendingUpdate::UnannounceAnnounce(_)) => {
305				PendingUpdate::UnannounceAnnounce(broadcast)
306			}
307		};
308		self.pending.insert(path, new);
309	}
310
311	fn apply_unannounce(&mut self, path: PathOwned) {
312		match self.pending.remove(&path) {
313			// Consumer has not seen the pending announce; drop both entirely.
314			Some(PendingUpdate::Announce(_)) => {}
315			None | Some(PendingUpdate::Unannounce) => {
316				self.pending.insert(path, PendingUpdate::Unannounce);
317			}
318			// The embedded announce cancels with this unannounce; the consumer
319			// still needs the leading unannounce.
320			Some(PendingUpdate::UnannounceAnnounce(_)) => {
321				self.pending.insert(path, PendingUpdate::Unannounce);
322			}
323		}
324	}
325
326	/// Take one update to deliver to the consumer, if any.
327	fn take(&mut self) -> Option<OriginAnnounce> {
328		let path = self.pending.keys().next()?.clone();
329		match self.pending.remove(&path).unwrap() {
330			PendingUpdate::Announce(broadcast) => Some((path, Some(broadcast))),
331			PendingUpdate::Unannounce => Some((path, None)),
332			PendingUpdate::UnannounceAnnounce(broadcast) => {
333				// Deliver the unannounce now; leave the trailing announce pending so
334				// the next take returns it for the same path.
335				self.pending.insert(path.clone(), PendingUpdate::Announce(broadcast));
336				Some((path, None))
337			}
338		}
339	}
340}
341
342#[derive(Clone)]
343struct OriginConsumerNotify {
344	root: PathOwned,
345	state: kio::Producer<OriginConsumerState>,
346}
347
348impl OriginConsumerNotify {
349	fn announce(&self, path: impl AsPath, broadcast: BroadcastConsumer) {
350		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
351		self.state
352			.write()
353			.ok()
354			.expect("consumer closed")
355			.apply_announce(path, broadcast);
356	}
357
358	fn reannounce(&self, path: impl AsPath, broadcast: BroadcastConsumer) {
359		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
360		let mut state = self.state.write().ok().expect("consumer closed");
361		state.apply_unannounce(path.clone());
362		state.apply_announce(path, broadcast);
363	}
364
365	fn unannounce(&self, path: impl AsPath) {
366		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
367		self.state.write().ok().expect("consumer closed").apply_unannounce(path);
368	}
369}
370
371struct NotifyNode {
372	parent: Option<Lock<NotifyNode>>,
373
374	// Consumers that are subscribed to this node.
375	// We store a consumer ID so we can remove it easily when it closes.
376	consumers: HashMap<ConsumerId, OriginConsumerNotify>,
377}
378
379impl NotifyNode {
380	fn new(parent: Option<Lock<NotifyNode>>) -> Self {
381		Self {
382			parent,
383			consumers: HashMap::new(),
384		}
385	}
386
387	fn announce(&mut self, path: impl AsPath, broadcast: &BroadcastConsumer) {
388		for consumer in self.consumers.values() {
389			consumer.announce(path.as_path(), broadcast.clone());
390		}
391
392		if let Some(parent) = &self.parent {
393			parent.lock().announce(path, broadcast);
394		}
395	}
396
397	fn reannounce(&mut self, path: impl AsPath, broadcast: &BroadcastConsumer) {
398		for consumer in self.consumers.values() {
399			consumer.reannounce(path.as_path(), broadcast.clone());
400		}
401
402		if let Some(parent) = &self.parent {
403			parent.lock().reannounce(path, broadcast);
404		}
405	}
406
407	fn unannounce(&mut self, path: impl AsPath) {
408		for consumer in self.consumers.values() {
409			consumer.unannounce(path.as_path());
410		}
411
412		if let Some(parent) = &self.parent {
413			parent.lock().unannounce(path);
414		}
415	}
416}
417
418struct OriginNode {
419	// The broadcast that is published to this node.
420	broadcast: Option<OriginBroadcast>,
421
422	// Nested nodes, one level down the tree.
423	nested: HashMap<String, Lock<OriginNode>>,
424
425	// Unfortunately, to notify consumers we need to traverse back up the tree.
426	notify: Lock<NotifyNode>,
427}
428
429impl OriginNode {
430	fn new(parent: Option<Lock<NotifyNode>>) -> Self {
431		Self {
432			broadcast: None,
433			nested: HashMap::new(),
434			notify: Lock::new(NotifyNode::new(parent)),
435		}
436	}
437
438	fn leaf(&mut self, path: &Path) -> Lock<OriginNode> {
439		let (dir, rest) = path.next_part().expect("leaf called with empty path");
440
441		let next = self.entry(dir);
442		if rest.is_empty() { next } else { next.lock().leaf(&rest) }
443	}
444
445	fn entry(&mut self, dir: &str) -> Lock<OriginNode> {
446		match self.nested.get(dir) {
447			Some(next) => next.clone(),
448			None => {
449				let next = Lock::new(OriginNode::new(Some(self.notify.clone())));
450				self.nested.insert(dir.to_string(), next.clone());
451				next
452			}
453		}
454	}
455
456	fn publish(&mut self, full: impl AsPath, broadcast: &BroadcastConsumer, relative: impl AsPath) {
457		let full = full.as_path();
458		let rest = relative.as_path();
459
460		// If the path has a directory component, then publish it to the nested node.
461		if let Some((dir, relative)) = rest.next_part() {
462			// Not using entry to avoid allocating a string most of the time.
463			self.entry(dir).lock().publish(&full, broadcast, &relative);
464		} else if let Some(existing) = &mut self.broadcast {
465			// This node is a leaf with an existing broadcast. Prefer the route with the
466			// lower ordering key (shorter hop chain, deterministic hash on ties), so every
467			// node converges on the same route regardless of the order announces arrive.
468			//
469			// Drop duplicates (same underlying broadcast delivered via multiple links) so the
470			// backup queue can't accumulate clones of the active entry and trigger redundant
471			// reannouncements when a peer churns.
472			if existing.active.is_clone(broadcast) || existing.backup.iter().any(|b| b.is_clone(broadcast)) {
473				return;
474			}
475
476			if route_key(&full, &broadcast.hops) < route_key(&full, &existing.active.hops) {
477				let old = existing.active.clone();
478				existing.active = broadcast.clone();
479				existing.backup.push_back(old);
480
481				self.notify.lock().reannounce(full, broadcast);
482			} else {
483				// Loses the ordering (longer path, or the tie-break): keep as a backup
484				// in case the active one drops.
485				existing.backup.push_back(broadcast.clone());
486			}
487		} else {
488			// This node is a leaf with no existing broadcast.
489			self.broadcast = Some(OriginBroadcast {
490				path: full.to_owned(),
491				active: broadcast.clone(),
492				backup: VecDeque::new(),
493			});
494			self.notify.lock().announce(full, broadcast);
495		}
496	}
497
498	fn consume(&mut self, id: ConsumerId, mut notify: OriginConsumerNotify) {
499		self.consume_initial(&mut notify);
500		self.notify.lock().consumers.insert(id, notify);
501	}
502
503	fn consume_initial(&mut self, notify: &mut OriginConsumerNotify) {
504		if let Some(broadcast) = &self.broadcast {
505			notify.announce(&broadcast.path, broadcast.active.clone());
506		}
507
508		// Recursively subscribe to all nested nodes.
509		for nested in self.nested.values() {
510			nested.lock().consume_initial(notify);
511		}
512	}
513
514	fn consume_broadcast(&self, rest: impl AsPath) -> Option<BroadcastConsumer> {
515		let rest = rest.as_path();
516
517		if let Some((dir, rest)) = rest.next_part() {
518			let node = self.nested.get(dir)?.lock();
519			node.consume_broadcast(&rest)
520		} else {
521			self.broadcast.as_ref().map(|b| b.active.clone())
522		}
523	}
524
525	fn unconsume(&mut self, id: ConsumerId) {
526		self.notify.lock().consumers.remove(&id).expect("consumer not found");
527		if self.is_empty() {
528			//tracing::warn!("TODO: empty node; memory leak");
529			// This happens when consuming a path that is not being broadcasted.
530		}
531	}
532
533	// Returns true if the broadcast should be unannounced.
534	fn remove(&mut self, full: impl AsPath, broadcast: BroadcastConsumer, relative: impl AsPath) {
535		let full = full.as_path();
536		let relative = relative.as_path();
537
538		if let Some((dir, relative)) = relative.next_part() {
539			let nested = self.entry(dir);
540			let mut locked = nested.lock();
541			locked.remove(&full, broadcast, &relative);
542
543			if locked.is_empty() {
544				drop(locked);
545				self.nested.remove(dir);
546			}
547		} else {
548			let entry = match &mut self.broadcast {
549				Some(existing) => existing,
550				None => return,
551			};
552
553			// See if we can remove the broadcast from the backup list.
554			let pos = entry.backup.iter().position(|b| b.is_clone(&broadcast));
555			if let Some(pos) = pos {
556				entry.backup.remove(pos);
557				// Nothing else to do
558				return;
559			}
560
561			// Okay so it must be the active broadcast or else we fucked up.
562			assert!(entry.active.is_clone(&broadcast));
563
564			// Promote the backup with the lowest ordering key, the same rule used when
565			// publishing, so the route a node heals to still matches its peers.
566			let best = entry
567				.backup
568				.iter()
569				.enumerate()
570				.min_by_key(|(_, b)| route_key(&full, &b.hops))
571				.map(|(i, _)| i);
572			if let Some(idx) = best {
573				let active = entry.backup.remove(idx).expect("index in range");
574				entry.active = active;
575				self.notify.lock().reannounce(full, &entry.active);
576			} else {
577				// No more backups, so remove the entry.
578				self.broadcast = None;
579				self.notify.lock().unannounce(full);
580			}
581		}
582	}
583
584	fn is_empty(&self) -> bool {
585		self.broadcast.is_none() && self.nested.is_empty() && self.notify.lock().consumers.is_empty()
586	}
587}
588
589#[derive(Clone)]
590struct OriginNodes {
591	nodes: Vec<(PathOwned, Lock<OriginNode>)>,
592}
593
594impl OriginNodes {
595	// Returns nested roots that match the prefixes.
596	// PathPrefixes guarantees no duplicates or overlapping prefixes.
597	pub fn select(&self, prefixes: &PathPrefixes) -> Option<Self> {
598		let mut roots = Vec::new();
599
600		for (root, state) in &self.nodes {
601			for prefix in prefixes {
602				if root.has_prefix(prefix) {
603					// Keep the existing node if we're allowed to access it.
604					roots.push((root.to_owned(), state.clone()));
605					continue;
606				}
607
608				if let Some(suffix) = prefix.strip_prefix(root) {
609					// If the requested prefix is larger than the allowed prefix, then we further scope it.
610					let nested = state.lock().leaf(&suffix);
611					roots.push((prefix.to_owned(), nested));
612				}
613			}
614		}
615
616		if roots.is_empty() {
617			None
618		} else {
619			Some(Self { nodes: roots })
620		}
621	}
622
623	pub fn root(&self, new_root: impl AsPath) -> Option<Self> {
624		let new_root = new_root.as_path();
625		let mut roots = Vec::new();
626
627		if new_root.is_empty() {
628			return Some(self.clone());
629		}
630
631		for (root, state) in &self.nodes {
632			if let Some(suffix) = root.strip_prefix(&new_root) {
633				// If the old root is longer than the new root, shorten the keys.
634				roots.push((suffix.to_owned(), state.clone()));
635			} else if let Some(suffix) = new_root.strip_prefix(root) {
636				// If the new root is longer than the old root, add a new root.
637				// NOTE: suffix can't be empty
638				let nested = state.lock().leaf(&suffix);
639				roots.push(("".into(), nested));
640			}
641		}
642
643		if roots.is_empty() {
644			None
645		} else {
646			Some(Self { nodes: roots })
647		}
648	}
649
650	// Returns the root that has this prefix.
651	pub fn get(&self, path: impl AsPath) -> Option<(Lock<OriginNode>, PathOwned)> {
652		let path = path.as_path();
653
654		for (root, state) in &self.nodes {
655			if let Some(suffix) = path.strip_prefix(root) {
656				return Some((state.clone(), suffix.to_owned()));
657			}
658		}
659
660		None
661	}
662}
663
664impl Default for OriginNodes {
665	fn default() -> Self {
666		Self {
667			nodes: vec![("".into(), Lock::new(OriginNode::new(None)))],
668		}
669	}
670}
671
672/// A broadcast path and its associated consumer, or None if closed.
673pub type OriginAnnounce = (PathOwned, Option<BroadcastConsumer>);
674
675/// Announces broadcasts to consumers over the network.
676#[derive(Clone)]
677pub struct OriginProducer {
678	// Identity for this origin. Appended to broadcast hops when
679	// re-announcing so downstream relays can detect loops and prefer the
680	// shortest path.
681	info: Origin,
682
683	// The roots of the tree that we are allowed to publish.
684	// A path of "" means we can publish anything.
685	nodes: OriginNodes,
686
687	// The prefix that is automatically stripped from all paths.
688	root: PathOwned,
689
690	// Fallback request queue, shared with every derived consumer. Separate from
691	// `nodes` because dynamic broadcasts are never announced: they only resolve a
692	// consumer's `request_broadcast` when no live announcement exists.
693	dynamic: kio::Producer<OriginDynamicState>,
694}
695
696impl std::ops::Deref for OriginProducer {
697	type Target = Origin;
698
699	fn deref(&self) -> &Self::Target {
700		&self.info
701	}
702}
703
704impl OriginProducer {
705	/// Build a producer for the given origin id with no scoped prefix and no
706	/// pre-existing broadcasts. Prefer [`Origin::produce`].
707	pub fn new(info: Origin) -> Self {
708		Self {
709			info,
710			nodes: OriginNodes::default(),
711			root: PathOwned::default(),
712			dynamic: kio::Producer::default(),
713		}
714	}
715
716	/// Create and publish a new broadcast, returning the producer.
717	///
718	/// This is a helper method when you only want to publish a broadcast to a single origin.
719	/// Returns [None] if the broadcast is not allowed to be published.
720	pub fn create_broadcast(&self, path: impl AsPath) -> Option<BroadcastProducer> {
721		let broadcast = Broadcast::new().produce();
722		self.publish_broadcast(path, broadcast.consume()).then_some(broadcast)
723	}
724
725	/// Publish a broadcast, announcing it to all consumers.
726	///
727	/// The broadcast will be unannounced when it is closed.
728	/// If there is already a broadcast with the same path, the new one replaces the active only
729	/// if it has a shorter hop path, or an equal-length path that wins a deterministic tie-break
730	/// (a hash of the broadcast name and hop chain); otherwise it is queued as a backup. The
731	/// tie-break is identical on every node, so a cluster converges on the same route.
732	/// When the active broadcast closes, the backup that wins the same ordering is promoted and
733	/// reannounced. Backups that close before being promoted are silently dropped.
734	///
735	/// Returns false if the broadcast is not allowed to be published, or if the full
736	/// path exceeds [`Path::MAX_PARTS`].
737	pub fn publish_broadcast(&self, path: impl AsPath, broadcast: BroadcastConsumer) -> bool {
738		let path = path.as_path();
739
740		// Loop detection: refuse broadcasts whose hop chain already contains our id.
741		if broadcast.hops.contains(&self.info) {
742			return false;
743		}
744
745		let (root, rest) = match self.nodes.get(&path) {
746			Some(root) => root,
747			None => return false,
748		};
749
750		let full = self.root.join(&path);
751
752		// A decoded announce prefix and suffix are each within the wire limit, but their
753		// join might not be. Enforcing here bounds the tree depth and guarantees the path
754		// can be re-encoded when forwarded.
755		if full.parts().count() > Path::MAX_PARTS {
756			return false;
757		}
758
759		root.lock().publish(&full, &broadcast, &rest);
760		let root = root.clone();
761
762		web_async::spawn(async move {
763			broadcast.closed().await;
764			root.lock().remove(&full, broadcast, &rest);
765		});
766
767		true
768	}
769
770	/// Returns a new OriginProducer restricted to publishing under one of `prefixes`.
771	///
772	/// Returns None if there are no legal prefixes (the requested prefixes are
773	/// disjoint from this producer's current scope).
774	// TODO accept PathPrefixes instead of &[Path]
775	pub fn scope(&self, prefixes: &[Path]) -> Option<OriginProducer> {
776		let prefixes = PathPrefixes::new(prefixes);
777		Some(OriginProducer {
778			info: self.info,
779			nodes: self.nodes.select(&prefixes)?,
780			root: self.root.clone(),
781			dynamic: self.dynamic.clone(),
782		})
783	}
784
785	/// Create a dynamic handler that picks up [`OriginConsumer::request_broadcast`]
786	/// calls for paths that are not announced.
787	///
788	/// This is the origin-level analogue of [`BroadcastProducer::dynamic`]: it serves
789	/// broadcasts on demand rather than tracks. Crucially the served broadcasts are
790	/// *not* announced, so [`OriginConsumer::announced`] never sees them; they exist
791	/// only as a fallback for a consumer that asks for an exact path with no live
792	/// announcement. Drop the handler (and every clone) to reject pending requests.
793	pub fn dynamic(&self) -> OriginDynamic {
794		OriginDynamic::new(self.info, self.root.clone(), self.dynamic.clone())
795	}
796
797	/// Subscribe to all announced broadcasts.
798	pub fn consume(&self) -> OriginConsumer {
799		OriginConsumer::new(self.info, self.root.clone(), self.nodes.clone(), self.dynamic.consume())
800	}
801
802	/// Get a broadcast by path if it has *already* been published.
803	///
804	/// Equivalent to `self.consume().get_broadcast(path)` but skips the
805	/// announcement-cursor allocation, which is currently relatively expensive.
806	#[deprecated(note = "use `consume().get_broadcast(path)` once `consume()` is cheap")]
807	pub fn get_broadcast(&self, path: impl AsPath) -> Option<BroadcastConsumer> {
808		let path = path.as_path();
809		let (root, rest) = self.nodes.get(&path)?;
810		let state = root.lock();
811		state.consume_broadcast(&rest)
812	}
813
814	/// Returns a new OriginProducer that automatically strips out the provided prefix.
815	///
816	/// Returns None if the provided root is not authorized; when [`Self::scope`]
817	/// was already used without a wildcard.
818	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
819		let prefix = prefix.as_path();
820
821		Some(Self {
822			info: self.info,
823			root: self.root.join(&prefix).to_owned(),
824			nodes: self.nodes.root(&prefix)?,
825			dynamic: self.dynamic.clone(),
826		})
827	}
828
829	/// Returns the root that is automatically stripped from all paths.
830	pub fn root(&self) -> &Path<'_> {
831		&self.root
832	}
833
834	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
835	// TODO return PathPrefixes
836	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
837		self.nodes.nodes.iter().map(|(root, _)| root)
838	}
839
840	/// Converts a relative path to an absolute path.
841	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
842		self.root.join(path)
843	}
844}
845
846/// Consumes announced broadcasts matching against an optional prefix.
847///
848/// NOTE: Clone is expensive, try to avoid it.
849pub struct OriginConsumer {
850	id: ConsumerId,
851	// Identity of the origin this consumer was derived from.
852	info: Origin,
853	nodes: OriginNodes,
854
855	// Pending updates queued for this consumer. Coalesced so a slow consumer
856	// can't accumulate redundant announce/unannounce pairs.
857	state: kio::Producer<OriginConsumerState>,
858
859	// A prefix that is automatically stripped from all paths.
860	root: PathOwned,
861
862	// Shared fallback request queue, fed to any `OriginDynamic` handler on the
863	// producer side. Used only by `request_broadcast`; announced lookups ignore it.
864	dynamic: kio::Consumer<OriginDynamicState>,
865}
866
867impl std::ops::Deref for OriginConsumer {
868	type Target = Origin;
869
870	fn deref(&self) -> &Self::Target {
871		&self.info
872	}
873}
874
875impl OriginConsumer {
876	fn new(info: Origin, root: PathOwned, nodes: OriginNodes, dynamic: kio::Consumer<OriginDynamicState>) -> Self {
877		let state = kio::Producer::<OriginConsumerState>::default();
878		let id = ConsumerId::new();
879
880		for (_, node) in &nodes.nodes {
881			let notify = OriginConsumerNotify {
882				root: root.clone(),
883				state: state.clone(),
884			};
885			node.lock().consume(id, notify);
886		}
887
888		Self {
889			id,
890			info,
891			nodes,
892			state,
893			root,
894			dynamic,
895		}
896	}
897
898	/// Returns the next (un)announced broadcast and the absolute path.
899	///
900	/// The broadcast will only be announced if it was previously unannounced.
901	/// The same path won't be announced/unannounced twice, instead it will toggle.
902	/// Returns None if the consumer is closed.
903	///
904	/// Note: The returned path is absolute and will always match this consumer's prefix.
905	pub async fn announced(&mut self) -> Option<OriginAnnounce> {
906		kio::wait(|waiter| self.poll_announced(waiter)).await
907	}
908
909	/// Poll for the next (un)announced broadcast, without blocking.
910	///
911	/// Returns `Poll::Ready(Some(_))` for an update, `Poll::Ready(None)` if the
912	/// consumer is closed, or `Poll::Pending` after registering `waiter` to be
913	/// notified when the next update arrives.
914	pub fn poll_announced(&mut self, waiter: &kio::Waiter) -> Poll<Option<OriginAnnounce>> {
915		let mut state = match ready!(self.state.poll(waiter, |state| {
916			if state.pending.is_empty() {
917				Poll::Pending
918			} else {
919				Poll::Ready(())
920			}
921		})) {
922			Ok(state) => state,
923			// Closed: discard the Ref so its MutexGuard doesn't escape this call.
924			Err(_) => return Poll::Ready(None),
925		};
926		Poll::Ready(Some(state.take().expect("predicate guaranteed an update")))
927	}
928
929	/// Returns the next (un)announced broadcast and the absolute path without blocking.
930	///
931	/// Returns None if there is no update available; NOT because the consumer is closed.
932	/// You have to use `is_closed` to check if the consumer is closed.
933	pub fn try_announced(&mut self) -> Option<OriginAnnounce> {
934		self.state.write().ok()?.take()
935	}
936
937	/// Create another consumer with its own announcement cursor over the same origin.
938	pub fn consume(&self) -> Self {
939		self.clone()
940	}
941
942	/// Get a broadcast by path if it has *already* been announced.
943	///
944	/// Returns `None` when the path is unknown to this consumer right now. Synchronous
945	/// lookup races announcement gossip — a freshly-connected consumer will see `None`
946	/// even when the broadcast is about to arrive. Prefer [`Self::announced_broadcast`]
947	/// (blocks until announced) unless you can guarantee the announcement has already
948	/// landed (e.g. you're responding to an `announced()` callback).
949	pub fn get_broadcast(&self, path: impl AsPath) -> Option<BroadcastConsumer> {
950		let path = path.as_path();
951		let (root, rest) = self.nodes.get(&path)?;
952		let state = root.lock();
953		state.consume_broadcast(&rest)
954	}
955
956	/// Block until a broadcast with the given path is announced and return it.
957	///
958	/// Returns `None` if the path is outside this consumer's allowed prefixes or if the consumer
959	/// is closed before the broadcast is announced. The returned broadcast may itself be closed
960	/// later — subscribers should watch [`BroadcastConsumer::closed`] to react to that.
961	///
962	/// Prefer this over [`Self::get_broadcast`] when you know the exact path you want but
963	/// cannot guarantee the announcement has already been received.
964	pub async fn announced_broadcast(&self, path: impl AsPath) -> Option<BroadcastConsumer> {
965		let path = path.as_path();
966
967		// Scope a fresh consumer down to this path so we only wake up for relevant announcements.
968		let mut consumer = self.scope(std::slice::from_ref(&path))?;
969
970		// `scope` keeps narrower permissions intact: if we ask for `foo` on a consumer limited
971		// to `foo/specific`, `scope` returns a consumer scoped to `foo/specific` — no
972		// announcement at the exact path `foo` can ever arrive. Bail rather than loop forever.
973		if !consumer.allowed().any(|allowed| path.has_prefix(allowed)) {
974			return None;
975		}
976
977		loop {
978			let (announced, broadcast) = consumer.announced().await?;
979			// `scope` narrows by prefix, but we only want an exact-path match.
980			if announced.as_path() == path {
981				if let Some(broadcast) = broadcast {
982					return Some(broadcast);
983				}
984			}
985		}
986	}
987
988	/// Returns a new OriginConsumer restricted to broadcasts under one of `prefixes`.
989	///
990	/// Returns None if there are no legal prefixes (the requested prefixes are
991	/// disjoint from this consumer's current scope, so it would always return None).
992	// TODO accept PathPrefixes instead of &[Path]
993	pub fn scope(&self, prefixes: &[Path]) -> Option<OriginConsumer> {
994		let prefixes = PathPrefixes::new(prefixes);
995		Some(OriginConsumer::new(
996			self.info,
997			self.root.clone(),
998			self.nodes.select(&prefixes)?,
999			self.dynamic.clone(),
1000		))
1001	}
1002
1003	/// Returns a new OriginConsumer that automatically strips out the provided prefix.
1004	///
1005	/// Returns None if the provided root is not authorized; when [`Self::scope`] was
1006	/// already used without a wildcard.
1007	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
1008		let prefix = prefix.as_path();
1009
1010		Some(Self::new(
1011			self.info,
1012			self.root.join(&prefix).to_owned(),
1013			self.nodes.root(&prefix)?,
1014			self.dynamic.clone(),
1015		))
1016	}
1017
1018	/// Get a broadcast by path, falling back to a dynamic request when it is not announced.
1019	///
1020	/// Returns a [`kio::Pending`] future (resolved synchronously for an announced broadcast,
1021	/// otherwise once a handler serves it). The lookup order is: an already-announced broadcast
1022	/// resolves immediately; otherwise, if an [`OriginDynamic`] handler is live (see
1023	/// [`OriginProducer::dynamic`]), a fallback request is registered and the future resolves
1024	/// when the handler [`accept`](BroadcastRequest::accept)s it (or errors if it
1025	/// [`reject`](BroadcastRequest::reject)s or every handler drops). Concurrent requests for
1026	/// the same unannounced path coalesce onto one handler request.
1027	///
1028	/// The returned future resolves to [`Error::Unroutable`] when the path is not announced and no
1029	/// dynamic handler exists, or [`Error::Dropped`] once the origin is gone. A request that is
1030	/// registered while a handler is live but then loses every handler before being served also
1031	/// resolves to [`Error::Unroutable`]. Unlike an announced broadcast, a dynamically served one
1032	/// is never visible to [`Self::announced`].
1033	pub fn request_broadcast(&self, path: impl AsPath) -> kio::Pending<BroadcastRequested> {
1034		let path = path.as_path();
1035
1036		// Prefer a live announcement when one is present; the dynamic queue is only a fallback.
1037		if let Some(broadcast) = self.get_broadcast(&path) {
1038			return kio::Pending::new(BroadcastRequested::ready(broadcast));
1039		}
1040
1041		// Key requests by absolute path so a scoped/rooted consumer and the handler
1042		// (which may have a different root) agree on the same entry.
1043		let absolute = self.root.join(&path).to_owned();
1044
1045		let Ok(mut state) = self.dynamic.write() else {
1046			return kio::Pending::new(BroadcastRequested::failed(Error::Dropped));
1047		};
1048
1049		// Coalesce onto a queued request for the same path; otherwise register a new one.
1050		let consumer = if let Some(producer) = state.requests.get(&absolute) {
1051			producer.consume()
1052		} else {
1053			if state.dynamic == 0 {
1054				return kio::Pending::new(BroadcastRequested::failed(Error::Unroutable));
1055			}
1056
1057			let producer = kio::Producer::<PendingBroadcast>::default();
1058			let consumer = producer.consume();
1059			state.requests.insert(absolute.clone(), producer);
1060			state.request_order.push_back(absolute);
1061			consumer
1062		};
1063
1064		kio::Pending::new(BroadcastRequested::pending(consumer))
1065	}
1066
1067	/// Returns the prefix that is automatically stripped from all paths.
1068	pub fn root(&self) -> &Path<'_> {
1069		&self.root
1070	}
1071
1072	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
1073	// TODO return PathPrefixes
1074	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
1075		self.nodes.nodes.iter().map(|(root, _)| root)
1076	}
1077
1078	/// Converts a relative path to an absolute path.
1079	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
1080		self.root.join(path)
1081	}
1082}
1083
1084impl Drop for OriginConsumer {
1085	fn drop(&mut self) {
1086		for (_, root) in &self.nodes.nodes {
1087			root.lock().unconsume(self.id);
1088		}
1089	}
1090}
1091
1092impl Clone for OriginConsumer {
1093	fn clone(&self) -> Self {
1094		OriginConsumer::new(self.info, self.root.clone(), self.nodes.clone(), self.dynamic.clone())
1095	}
1096}
1097
1098/// Shared fallback request queue for an origin.
1099///
1100/// Lives off to the side of the announce tree because dynamically served broadcasts
1101/// are never announced. Mirrors the `dynamic`/`requests`/`request_order` fields of the
1102/// broadcast and track models.
1103#[derive(Default)]
1104struct OriginDynamicState {
1105	// Result channels for queued requests, keyed by absolute path. Concurrent
1106	// `request_broadcast` calls for the same path coalesce onto the same channel while
1107	// it is queued. The producer is moved out (and the entry removed) when the handler
1108	// picks the request up via [`OriginDynamic::requested_broadcast`].
1109	requests: HashMap<PathOwned, kio::Producer<PendingBroadcast>>,
1110
1111	// Requested paths in FIFO order for the handler to drain.
1112	request_order: VecDeque<PathOwned>,
1113
1114	// The number of live `OriginDynamic` handlers. While zero, `request_broadcast`
1115	// fails fast with `Unroutable` rather than queueing a request nobody will serve.
1116	dynamic: usize,
1117}
1118
1119impl OriginDynamicState {
1120	/// Drop every queued request, closing its result channel so awaiting requesters
1121	/// resolve to an error. Called when the last handler goes away.
1122	fn reject_requests(&mut self) {
1123		self.requests.clear();
1124		self.request_order.clear();
1125	}
1126}
1127
1128/// One-shot result of a dynamic broadcast request.
1129///
1130/// Stays `None` until a handler [`accept`](BroadcastRequest::accept)s (yielding the served
1131/// broadcast) or [`reject`](BroadcastRequest::reject)s (yielding an error). The producer is
1132/// dropped right after writing, closing the channel; kio checks the value before the closed
1133/// flag, so an awaiting requester still observes the final result.
1134#[derive(Default)]
1135struct PendingBroadcast {
1136	resolved: Option<Result<BroadcastConsumer, Error>>,
1137}
1138
1139/// Picks up [`OriginConsumer::request_broadcast`] calls for paths that are not announced.
1140///
1141/// The origin-level analogue of [`crate::BroadcastDynamic`]: where that serves tracks on
1142/// demand within a broadcast, this serves whole broadcasts on demand within an origin. A
1143/// relay uses it as a fallback router, fetching a broadcast from upstream only when a
1144/// downstream consumer asks for an exact path that nobody announced.
1145///
1146/// Served broadcasts are deliberately *not* announced, so they never appear in
1147/// [`OriginConsumer::announced`]. Drop this handle (and every clone) to reject the
1148/// requests still waiting to be served.
1149pub struct OriginDynamic {
1150	info: Origin,
1151	root: PathOwned,
1152	state: kio::Producer<OriginDynamicState>,
1153}
1154
1155impl Clone for OriginDynamic {
1156	fn clone(&self) -> Self {
1157		// Mirror `new`: bump `dynamic` so each live handle is counted. Without this,
1158		// dropping a clone would decrement past `new`'s increment and prematurely flip
1159		// `dynamic` to zero, making future `request_broadcast` calls return `Unroutable`.
1160		if let Ok(mut state) = self.state.write() {
1161			state.dynamic += 1;
1162		}
1163
1164		Self {
1165			info: self.info,
1166			root: self.root.clone(),
1167			state: self.state.clone(),
1168		}
1169	}
1170}
1171
1172impl OriginDynamic {
1173	fn new(info: Origin, root: PathOwned, state: kio::Producer<OriginDynamicState>) -> Self {
1174		if let Ok(mut state) = state.write() {
1175			state.dynamic += 1;
1176		}
1177
1178		Self { info, root, state }
1179	}
1180
1181	/// The origin this handler belongs to.
1182	pub fn info(&self) -> &Origin {
1183		&self.info
1184	}
1185
1186	// Gate readiness on a queued request; mutate through the returned `Mut`.
1187	fn poll<F>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<kio::Mut<'_, OriginDynamicState>, Error>>
1188	where
1189		F: FnMut(&kio::Ref<'_, OriginDynamicState>) -> Poll<()>,
1190	{
1191		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
1192			Ok(state) => Ok(state),
1193			Err(_) => Err(Error::Dropped),
1194		})
1195	}
1196
1197	/// Poll for the next requested broadcast, without blocking.
1198	pub fn poll_requested_broadcast(&mut self, waiter: &kio::Waiter) -> Poll<Result<BroadcastRequest, Error>> {
1199		let mut state = ready!(self.poll(waiter, |state| {
1200			if state.request_order.is_empty() {
1201				Poll::Pending
1202			} else {
1203				Poll::Ready(())
1204			}
1205		}))?;
1206
1207		let path = state.request_order.pop_front().expect("predicate guaranteed a request");
1208		let producer = state.requests.remove(&path).expect("request_order out of sync");
1209		Poll::Ready(Ok(BroadcastRequest { path, producer }))
1210	}
1211
1212	/// Block until a consumer requests an unannounced broadcast, returning a
1213	/// [`BroadcastRequest`] to serve.
1214	pub async fn requested_broadcast(&mut self) -> Result<BroadcastRequest, Error> {
1215		kio::wait(|waiter| self.poll_requested_broadcast(waiter)).await
1216	}
1217
1218	/// Returns the prefix that is automatically stripped from requested paths.
1219	pub fn root(&self) -> &Path<'_> {
1220		&self.root
1221	}
1222}
1223
1224impl Drop for OriginDynamic {
1225	fn drop(&mut self) {
1226		if let Ok(mut state) = self.state.write() {
1227			// Saturating sub so `OriginProducer::dynamic` can stay infallible.
1228			state.dynamic = state.dynamic.saturating_sub(1);
1229			if state.dynamic == 0 {
1230				// No handlers left to fulfill queued requests; close them.
1231				state.reject_requests();
1232			}
1233		}
1234	}
1235}
1236
1237/// A pending request for a broadcast that was not announced.
1238///
1239/// Yielded by [`OriginDynamic::requested_broadcast`]. The requester is awaiting inside
1240/// [`OriginConsumer::request_broadcast`]; [`accept`](Self::accept) resolves it with a live
1241/// broadcast (which the handler keeps producing into) and [`reject`](Self::reject) resolves
1242/// it with an error. Dropping the request without either rejects it.
1243pub struct BroadcastRequest {
1244	// Absolute path that was requested.
1245	path: PathOwned,
1246
1247	// Result channel back to the awaiting requester(s). Writing `resolved` and dropping
1248	// this wakes them with the outcome.
1249	producer: kio::Producer<PendingBroadcast>,
1250}
1251
1252impl BroadcastRequest {
1253	/// The absolute path that was requested.
1254	pub fn path(&self) -> &Path<'_> {
1255		&self.path
1256	}
1257
1258	/// Accept the request, resolving every awaiting requester with `broadcast`.
1259	///
1260	/// The caller keeps producing into `broadcast` (e.g. a relay proxying tracks from
1261	/// upstream); the requesters receive a consumer for it. The broadcast is *not*
1262	/// announced.
1263	pub fn accept(self, broadcast: BroadcastConsumer) {
1264		if let Ok(mut state) = self.producer.write() {
1265			state.resolved = Some(Ok(broadcast));
1266		}
1267		// `self.producer` drops here, closing the channel; the value is still observable.
1268	}
1269
1270	/// Reject the request, resolving every awaiting requester with `err`.
1271	pub fn reject(self, err: Error) {
1272		if let Ok(mut state) = self.producer.write() {
1273			state.resolved = Some(Err(err));
1274		}
1275	}
1276}
1277
1278/// The pollable result of [`OriginConsumer::request_broadcast`].
1279///
1280/// Awaited via the [`kio::Pending`] wrapper; resolves to the [`BroadcastConsumer`]
1281/// immediately when the broadcast was already announced, or once an [`OriginDynamic`]
1282/// handler serves the request. Resolves to an error if the request is rejected or every
1283/// handler drops before serving it.
1284pub struct BroadcastRequested {
1285	inner: Requested,
1286}
1287
1288enum Requested {
1289	// Already announced: resolves immediately with a clone of this broadcast.
1290	Ready(BroadcastConsumer),
1291	// Unroutable at request time, or the origin was already dropped: resolves immediately
1292	// with this error. Baked in so `request_broadcast` itself stays infallible.
1293	Failed(Error),
1294	// Awaiting a handler: resolves when the request's result channel is written.
1295	Pending(kio::Consumer<PendingBroadcast>),
1296}
1297
1298impl BroadcastRequested {
1299	fn ready(broadcast: BroadcastConsumer) -> Self {
1300		Self {
1301			inner: Requested::Ready(broadcast),
1302		}
1303	}
1304
1305	fn failed(error: Error) -> Self {
1306		Self {
1307			inner: Requested::Failed(error),
1308		}
1309	}
1310
1311	fn pending(consumer: kio::Consumer<PendingBroadcast>) -> Self {
1312		Self {
1313			inner: Requested::Pending(consumer),
1314		}
1315	}
1316
1317	/// Poll for the requested broadcast without blocking.
1318	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<BroadcastConsumer, Error>> {
1319		match &self.inner {
1320			Requested::Ready(broadcast) => Poll::Ready(Ok(broadcast.clone())),
1321			Requested::Failed(error) => Poll::Ready(Err(error.clone())),
1322			Requested::Pending(consumer) => Poll::Ready(
1323				match ready!(consumer.poll(waiter, |state| match &state.resolved {
1324					Some(result) => Poll::Ready(result.clone()),
1325					None => Poll::Pending,
1326				})) {
1327					Ok(result) => result,
1328					// Every handler dropped without resolving: nobody could route it.
1329					Err(_closed) => Err(Error::Unroutable),
1330				},
1331			),
1332		}
1333	}
1334}
1335
1336impl kio::Future for BroadcastRequested {
1337	type Output = Result<BroadcastConsumer, Error>;
1338
1339	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
1340		self.poll_ok(waiter)
1341	}
1342}
1343
1344#[cfg(test)]
1345use futures::FutureExt;
1346
1347#[cfg(test)]
1348impl OriginConsumer {
1349	pub fn assert_next(&mut self, expected: impl AsPath, broadcast: &BroadcastConsumer) {
1350		let expected = expected.as_path();
1351		let (path, active) = self.announced().now_or_never().expect("next blocked").expect("no next");
1352		assert_eq!(path, expected, "wrong path");
1353		assert!(active.unwrap().is_clone(broadcast), "should be the same broadcast");
1354	}
1355
1356	pub fn assert_try_next(&mut self, expected: impl AsPath, broadcast: &BroadcastConsumer) {
1357		let expected = expected.as_path();
1358		let (path, active) = self.try_announced().expect("no next");
1359		assert_eq!(path, expected, "wrong path");
1360		assert!(active.unwrap().is_clone(broadcast), "should be the same broadcast");
1361	}
1362
1363	pub fn assert_next_none(&mut self, expected: impl AsPath) {
1364		let expected = expected.as_path();
1365		let (path, active) = self.announced().now_or_never().expect("next blocked").expect("no next");
1366		assert_eq!(path, expected, "wrong path");
1367		assert!(active.is_none(), "should be unannounced");
1368	}
1369
1370	pub fn assert_next_wait(&mut self) {
1371		if let Some(res) = self.announced().now_or_never() {
1372			panic!("next should block: got {:?}", res.map(|(path, _)| path));
1373		}
1374	}
1375
1376	/*
1377	pub fn assert_next_closed(&mut self) {
1378		assert!(
1379			self.announced().now_or_never().expect("next blocked").is_none(),
1380			"next should be closed"
1381		);
1382	}
1383	*/
1384}
1385
1386#[cfg(test)]
1387mod tests {
1388	use futures::FutureExt;
1389
1390	use crate::Broadcast;
1391
1392	use super::*;
1393
1394	#[test]
1395	fn origin_list_push_fails_at_limit() {
1396		let mut list = OriginList::new();
1397		for _ in 0..MAX_HOPS {
1398			list.push(Origin::random()).unwrap();
1399		}
1400		assert_eq!(list.len(), MAX_HOPS);
1401		assert_eq!(list.push(Origin::random()), Err(TooManyOrigins));
1402	}
1403
1404	#[test]
1405	fn origin_list_replace_first() {
1406		let mut list = OriginList::new();
1407		for _ in 0..3 {
1408			list.push(Origin::UNKNOWN).unwrap();
1409		}
1410
1411		// Rewrites only the first placeholder, keeping the length the same.
1412		assert!(list.replace_first(Origin::UNKNOWN, Origin::from(7)));
1413		assert_eq!(list.as_slice(), &[Origin::from(7), Origin::UNKNOWN, Origin::UNKNOWN]);
1414
1415		// No match leaves the list untouched.
1416		assert!(!list.replace_first(Origin::from(99), Origin::from(8)));
1417		assert_eq!(list.len(), 3);
1418	}
1419
1420	#[test]
1421	fn origin_list_try_from_vec_enforces_limit() {
1422		let under: Vec<Origin> = (0..MAX_HOPS).map(|_| Origin::random()).collect();
1423		assert!(OriginList::try_from(under).is_ok());
1424
1425		let over: Vec<Origin> = (0..MAX_HOPS + 1).map(|_| Origin::random()).collect();
1426		assert_eq!(OriginList::try_from(over), Err(TooManyOrigins));
1427	}
1428
1429	#[tokio::test]
1430	async fn test_announce() {
1431		tokio::time::pause();
1432
1433		let origin = Origin::random().produce();
1434		let broadcast1 = Broadcast::new().produce();
1435		let broadcast2 = Broadcast::new().produce();
1436
1437		let mut consumer1 = origin.consume();
1438		// Make a new consumer that should get it.
1439		consumer1.assert_next_wait();
1440
1441		// Publish the first broadcast.
1442		origin.publish_broadcast("test1", broadcast1.consume());
1443
1444		consumer1.assert_next("test1", &broadcast1.consume());
1445		consumer1.assert_next_wait();
1446
1447		// Make a new consumer that should get the existing broadcast.
1448		// But we don't consume it yet.
1449		let mut consumer2 = origin.consume();
1450
1451		// Publish the second broadcast.
1452		origin.publish_broadcast("test2", broadcast2.consume());
1453
1454		consumer1.assert_next("test2", &broadcast2.consume());
1455		consumer1.assert_next_wait();
1456
1457		consumer2.assert_next("test1", &broadcast1.consume());
1458		consumer2.assert_next("test2", &broadcast2.consume());
1459		consumer2.assert_next_wait();
1460
1461		// Close the first broadcast.
1462		drop(broadcast1);
1463
1464		// Wait for the async task to run.
1465		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1466
1467		// All consumers should get a None now.
1468		consumer1.assert_next_none("test1");
1469		consumer2.assert_next_none("test1");
1470		consumer1.assert_next_wait();
1471		consumer2.assert_next_wait();
1472
1473		// And a new consumer only gets the last broadcast.
1474		let mut consumer3 = origin.consume();
1475		consumer3.assert_next("test2", &broadcast2.consume());
1476		consumer3.assert_next_wait();
1477
1478		// Close the other producer and make sure it cleans up
1479		drop(broadcast2);
1480
1481		// Wait for the async task to run.
1482		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1483
1484		consumer1.assert_next_none("test2");
1485		consumer2.assert_next_none("test2");
1486		consumer3.assert_next_none("test2");
1487
1488		/* TODO close the origin consumer when the producer is dropped
1489		consumer1.assert_next_closed();
1490		consumer2.assert_next_closed();
1491		consumer3.assert_next_closed();
1492		*/
1493	}
1494
1495	#[tokio::test]
1496	async fn test_duplicate() {
1497		tokio::time::pause();
1498
1499		let origin = Origin::random().produce();
1500
1501		let broadcast1 = Broadcast::new().produce();
1502		let broadcast2 = Broadcast::new().produce();
1503		let broadcast3 = Broadcast::new().produce();
1504
1505		let consumer1 = broadcast1.consume();
1506		let consumer2 = broadcast2.consume();
1507		let consumer3 = broadcast3.consume();
1508
1509		let mut consumer = origin.consume();
1510
1511		origin.publish_broadcast("test", consumer1.clone());
1512		origin.publish_broadcast("test", consumer2.clone());
1513		origin.publish_broadcast("test", consumer3.clone());
1514		assert!(consumer.get_broadcast("test").is_some());
1515
1516		// Identical (empty) hop chains tie on the deterministic key, so the first publish
1517		// stays active and the rest queue as backups. No churn, no reannounce.
1518		consumer.assert_next("test", &consumer1);
1519		consumer.assert_next_wait();
1520
1521		// Drop a backup, nothing should change.
1522		drop(broadcast2);
1523
1524		// Wait for the async task to run.
1525		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1526
1527		assert!(consumer.get_broadcast("test").is_some());
1528		consumer.assert_next_wait();
1529
1530		// Drop the active, we should reannounce with the remaining backup.
1531		drop(broadcast1);
1532
1533		// Wait for the async task to run.
1534		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1535
1536		assert!(consumer.get_broadcast("test").is_some());
1537		consumer.assert_next_none("test");
1538		consumer.assert_next("test", &consumer3);
1539
1540		// Drop the final broadcast, we should unannounce.
1541		drop(broadcast3);
1542
1543		// Wait for the async task to run.
1544		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1545		assert!(consumer.get_broadcast("test").is_none());
1546
1547		consumer.assert_next_none("test");
1548		consumer.assert_next_wait();
1549	}
1550
1551	#[tokio::test]
1552	async fn test_duplicate_reverse() {
1553		tokio::time::pause();
1554
1555		let origin = Origin::random().produce();
1556		let broadcast1 = Broadcast::new().produce();
1557		let broadcast2 = Broadcast::new().produce();
1558
1559		origin.publish_broadcast("test", broadcast1.consume());
1560		origin.publish_broadcast("test", broadcast2.consume());
1561		assert!(origin.consume().get_broadcast("test").is_some());
1562
1563		// This is harder, dropping the new broadcast first.
1564		drop(broadcast2);
1565
1566		// Wait for the cleanup async task to run.
1567		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1568		assert!(origin.consume().get_broadcast("test").is_some());
1569
1570		drop(broadcast1);
1571
1572		// Wait for the cleanup async task to run.
1573		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1574		assert!(origin.consume().get_broadcast("test").is_none());
1575	}
1576
1577	#[tokio::test]
1578	async fn test_deterministic_tiebreak() {
1579		tokio::time::pause();
1580
1581		// Build a broadcast carrying a specific hop chain.
1582		fn route(ids: &[u64]) -> BroadcastProducer {
1583			let hops = OriginList::try_from(ids.iter().copied().map(Origin::from).collect::<Vec<_>>()).unwrap();
1584			Broadcast { hops }.produce()
1585		}
1586
1587		// Resolve the active route for "test" after publishing both routes in the given order.
1588		fn winner(first: &[u64], second: &[u64]) -> OriginList {
1589			let origin = Origin::random().produce();
1590			let a = route(first);
1591			let b = route(second);
1592			origin.publish_broadcast("test", a.consume());
1593			origin.publish_broadcast("test", b.consume());
1594			let hops = origin.consume().get_broadcast("test").unwrap().hops.clone();
1595			// Keep the producers alive until after we read the active route.
1596			drop((a, b));
1597			hops
1598		}
1599
1600		// Two routes with equal hop counts but distinct chains. The winner is decided by
1601		// the deterministic key, not arrival order, so both publish orders converge.
1602		let forward = winner(&[10, 20], &[30, 40]);
1603		let reverse = winner(&[30, 40], &[10, 20]);
1604		assert_eq!(forward, reverse, "tie-break must not depend on publish order");
1605
1606		// A strictly shorter chain always wins regardless of the hash.
1607		assert_eq!(winner(&[10, 20], &[30]).len(), 1);
1608		assert_eq!(winner(&[30], &[10, 20]).len(), 1);
1609	}
1610
1611	#[tokio::test]
1612	async fn test_double_publish() {
1613		tokio::time::pause();
1614
1615		let origin = Origin::random().produce();
1616		let broadcast = Broadcast::new().produce();
1617
1618		// Ensure it doesn't crash.
1619		origin.publish_broadcast("test", broadcast.consume());
1620		origin.publish_broadcast("test", broadcast.consume());
1621
1622		assert!(origin.consume().get_broadcast("test").is_some());
1623
1624		drop(broadcast);
1625
1626		// Wait for the async task to run.
1627		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1628		assert!(origin.consume().get_broadcast("test").is_none());
1629	}
1630	// A previous mpsc-based implementation could only deliver the first 127 broadcasts
1631	// instantly via `assert_next` (which uses `now_or_never`). The kio-backed
1632	// implementation polls synchronously and can deliver all of them without yielding.
1633	// Names are zero-padded so lexicographic delivery order matches the loop index.
1634	#[tokio::test]
1635	async fn test_many_announces() {
1636		let origin = Origin::random().produce();
1637		let broadcast = Broadcast::new().produce();
1638
1639		let mut consumer = origin.consume();
1640		for i in 0..256 {
1641			origin.publish_broadcast(format!("test{i:03}"), broadcast.consume());
1642		}
1643
1644		for i in 0..256 {
1645			consumer.assert_next(format!("test{i:03}"), &broadcast.consume());
1646		}
1647		consumer.assert_next_wait();
1648	}
1649
1650	#[tokio::test]
1651	async fn test_many_announces_try() {
1652		let origin = Origin::random().produce();
1653		let broadcast = Broadcast::new().produce();
1654
1655		let mut consumer = origin.consume();
1656		for i in 0..256 {
1657			origin.publish_broadcast(format!("test{i:03}"), broadcast.consume());
1658		}
1659
1660		for i in 0..256 {
1661			consumer.assert_try_next(format!("test{i:03}"), &broadcast.consume());
1662		}
1663	}
1664
1665	#[tokio::test]
1666	async fn test_with_root_basic() {
1667		let origin = Origin::random().produce();
1668		let broadcast = Broadcast::new().produce();
1669
1670		// Create a producer with root "/foo"
1671		let foo_producer = origin.with_root("foo").expect("should create root");
1672		assert_eq!(foo_producer.root().as_str(), "foo");
1673
1674		let mut consumer = origin.consume();
1675
1676		// When publishing to "bar/baz", it should actually publish to "foo/bar/baz"
1677		assert!(foo_producer.publish_broadcast("bar/baz", broadcast.consume()));
1678		// The original consumer should see the full path
1679		consumer.assert_next("foo/bar/baz", &broadcast.consume());
1680
1681		// A consumer created from the rooted producer should see the stripped path
1682		let mut foo_consumer = foo_producer.consume();
1683		foo_consumer.assert_next("bar/baz", &broadcast.consume());
1684	}
1685
1686	#[tokio::test]
1687	async fn test_with_root_nested() {
1688		let origin = Origin::random().produce();
1689		let broadcast = Broadcast::new().produce();
1690
1691		// Create nested roots
1692		let foo_producer = origin.with_root("foo").expect("should create foo root");
1693		let foo_bar_producer = foo_producer.with_root("bar").expect("should create bar root");
1694		assert_eq!(foo_bar_producer.root().as_str(), "foo/bar");
1695
1696		let mut consumer = origin.consume();
1697
1698		// Publishing to "baz" should actually publish to "foo/bar/baz"
1699		assert!(foo_bar_producer.publish_broadcast("baz", broadcast.consume()));
1700		// The original consumer sees the full path
1701		consumer.assert_next("foo/bar/baz", &broadcast.consume());
1702
1703		// Consumer from foo_bar_producer sees just "baz"
1704		let mut foo_bar_consumer = foo_bar_producer.consume();
1705		foo_bar_consumer.assert_next("baz", &broadcast.consume());
1706	}
1707
1708	#[tokio::test]
1709	async fn test_publish_scope_allows() {
1710		let origin = Origin::random().produce();
1711		let broadcast = Broadcast::new().produce();
1712
1713		// Create a producer that can only publish to "allowed" paths
1714		let limited_producer = origin
1715			.scope(&["allowed/path1".into(), "allowed/path2".into()])
1716			.expect("should create limited producer");
1717
1718		// Should be able to publish to allowed paths
1719		assert!(limited_producer.publish_broadcast("allowed/path1", broadcast.consume()));
1720		assert!(limited_producer.publish_broadcast("allowed/path1/nested", broadcast.consume()));
1721		assert!(limited_producer.publish_broadcast("allowed/path2", broadcast.consume()));
1722
1723		// Should not be able to publish to disallowed paths
1724		assert!(!limited_producer.publish_broadcast("notallowed", broadcast.consume()));
1725		assert!(!limited_producer.publish_broadcast("allowed", broadcast.consume())); // Parent of allowed path
1726		assert!(!limited_producer.publish_broadcast("other/path", broadcast.consume()));
1727	}
1728
1729	#[tokio::test]
1730	async fn test_publish_max_parts() {
1731		let origin = Origin::random().produce();
1732		let broadcast = Broadcast::new().produce();
1733
1734		let at_limit = (0..Path::MAX_PARTS)
1735			.map(|i| i.to_string())
1736			.collect::<Vec<_>>()
1737			.join("/");
1738		assert!(origin.publish_broadcast(at_limit.as_str(), broadcast.consume()));
1739
1740		let too_deep = format!("{at_limit}/extra");
1741		assert!(!origin.publish_broadcast(too_deep.as_str(), broadcast.consume()));
1742
1743		// The root counts toward the limit; a joined path past 32 parts is rejected.
1744		let rooted = origin.with_root("root").expect("wildcard allows any root");
1745		assert!(!rooted.publish_broadcast(at_limit.as_str(), broadcast.consume()));
1746	}
1747
1748	#[tokio::test]
1749	async fn test_publish_scope_empty() {
1750		let origin = Origin::random().produce();
1751
1752		// Creating a producer with no allowed paths should return None
1753		assert!(origin.scope(&[]).is_none());
1754	}
1755
1756	#[tokio::test]
1757	async fn test_consume_scope_filters() {
1758		let origin = Origin::random().produce();
1759		let broadcast1 = Broadcast::new().produce();
1760		let broadcast2 = Broadcast::new().produce();
1761		let broadcast3 = Broadcast::new().produce();
1762
1763		let mut consumer = origin.consume();
1764
1765		// Publish to different paths
1766		origin.publish_broadcast("allowed", broadcast1.consume());
1767		origin.publish_broadcast("allowed/nested", broadcast2.consume());
1768		origin.publish_broadcast("notallowed", broadcast3.consume());
1769
1770		// Create a consumer that only sees "allowed" paths
1771		let mut limited_consumer = origin
1772			.consume()
1773			.scope(&["allowed".into()])
1774			.expect("should create limited consumer");
1775
1776		// Should only receive broadcasts under "allowed"
1777		limited_consumer.assert_next("allowed", &broadcast1.consume());
1778		limited_consumer.assert_next("allowed/nested", &broadcast2.consume());
1779		limited_consumer.assert_next_wait(); // Should not see "notallowed"
1780
1781		// Unscoped consumer should see all
1782		consumer.assert_next("allowed", &broadcast1.consume());
1783		consumer.assert_next("allowed/nested", &broadcast2.consume());
1784		consumer.assert_next("notallowed", &broadcast3.consume());
1785	}
1786
1787	#[tokio::test]
1788	async fn test_consume_scope_multiple_prefixes() {
1789		let origin = Origin::random().produce();
1790		let broadcast1 = Broadcast::new().produce();
1791		let broadcast2 = Broadcast::new().produce();
1792		let broadcast3 = Broadcast::new().produce();
1793
1794		origin.publish_broadcast("foo/test", broadcast1.consume());
1795		origin.publish_broadcast("bar/test", broadcast2.consume());
1796		origin.publish_broadcast("baz/test", broadcast3.consume());
1797
1798		// Consumer that only sees "foo" and "bar" paths
1799		let mut limited_consumer = origin
1800			.consume()
1801			.scope(&["foo".into(), "bar".into()])
1802			.expect("should create limited consumer");
1803
1804		// Order depends on PathPrefixes canonical sort (lexicographic for same length)
1805		limited_consumer.assert_next("bar/test", &broadcast2.consume());
1806		limited_consumer.assert_next("foo/test", &broadcast1.consume());
1807		limited_consumer.assert_next_wait(); // Should not see "baz/test"
1808	}
1809
1810	#[tokio::test]
1811	async fn test_with_root_and_publish_scope() {
1812		let origin = Origin::random().produce();
1813		let broadcast = Broadcast::new().produce();
1814
1815		// User connects to /foo root
1816		let foo_producer = origin.with_root("foo").expect("should create foo root");
1817
1818		// Limit them to publish only to "bar" and "goop/pee" within /foo
1819		let limited_producer = foo_producer
1820			.scope(&["bar".into(), "goop/pee".into()])
1821			.expect("should create limited producer");
1822
1823		let mut consumer = origin.consume();
1824
1825		// Should be able to publish to foo/bar and foo/goop/pee (but user sees as bar and goop/pee)
1826		assert!(limited_producer.publish_broadcast("bar", broadcast.consume()));
1827		assert!(limited_producer.publish_broadcast("bar/nested", broadcast.consume()));
1828		assert!(limited_producer.publish_broadcast("goop/pee", broadcast.consume()));
1829		assert!(limited_producer.publish_broadcast("goop/pee/nested", broadcast.consume()));
1830
1831		// Should not be able to publish outside allowed paths
1832		assert!(!limited_producer.publish_broadcast("baz", broadcast.consume()));
1833		assert!(!limited_producer.publish_broadcast("goop", broadcast.consume())); // Parent of allowed
1834		assert!(!limited_producer.publish_broadcast("goop/other", broadcast.consume()));
1835
1836		// Original consumer sees full paths
1837		consumer.assert_next("foo/bar", &broadcast.consume());
1838		consumer.assert_next("foo/bar/nested", &broadcast.consume());
1839		consumer.assert_next("foo/goop/pee", &broadcast.consume());
1840		consumer.assert_next("foo/goop/pee/nested", &broadcast.consume());
1841	}
1842
1843	#[tokio::test]
1844	async fn test_with_root_and_consume_scope() {
1845		let origin = Origin::random().produce();
1846		let broadcast1 = Broadcast::new().produce();
1847		let broadcast2 = Broadcast::new().produce();
1848		let broadcast3 = Broadcast::new().produce();
1849
1850		// Publish broadcasts
1851		origin.publish_broadcast("foo/bar/test", broadcast1.consume());
1852		origin.publish_broadcast("foo/goop/pee/test", broadcast2.consume());
1853		origin.publish_broadcast("foo/other/test", broadcast3.consume());
1854
1855		// User connects to /foo root
1856		let foo_producer = origin.with_root("foo").expect("should create foo root");
1857
1858		// Create consumer limited to "bar" and "goop/pee" within /foo
1859		let mut limited_consumer = foo_producer
1860			.consume()
1861			.scope(&["bar".into(), "goop/pee".into()])
1862			.expect("should create limited consumer");
1863
1864		// Should only see allowed paths (without foo prefix)
1865		limited_consumer.assert_next("bar/test", &broadcast1.consume());
1866		limited_consumer.assert_next("goop/pee/test", &broadcast2.consume());
1867		limited_consumer.assert_next_wait(); // Should not see "other/test"
1868	}
1869
1870	#[tokio::test]
1871	async fn test_with_root_unauthorized() {
1872		let origin = Origin::random().produce();
1873
1874		// First limit the producer to specific paths
1875		let limited_producer = origin
1876			.scope(&["allowed".into()])
1877			.expect("should create limited producer");
1878
1879		// Trying to create a root outside allowed paths should fail
1880		assert!(limited_producer.with_root("notallowed").is_none());
1881
1882		// But creating a root within allowed paths should work
1883		let allowed_root = limited_producer
1884			.with_root("allowed")
1885			.expect("should create allowed root");
1886		assert_eq!(allowed_root.root().as_str(), "allowed");
1887	}
1888
1889	#[tokio::test]
1890	async fn test_wildcard_permission() {
1891		let origin = Origin::random().produce();
1892		let broadcast = Broadcast::new().produce();
1893
1894		// Producer with root access (empty string means wildcard)
1895		let root_producer = origin.clone();
1896
1897		// Should be able to publish anywhere
1898		assert!(root_producer.publish_broadcast("any/path", broadcast.consume()));
1899		assert!(root_producer.publish_broadcast("other/path", broadcast.consume()));
1900
1901		// Can create any root
1902		let foo_producer = root_producer.with_root("foo").expect("should create any root");
1903		assert_eq!(foo_producer.root().as_str(), "foo");
1904	}
1905
1906	#[tokio::test]
1907	async fn test_consume_broadcast_with_permissions() {
1908		let origin = Origin::random().produce();
1909		let broadcast1 = Broadcast::new().produce();
1910		let broadcast2 = Broadcast::new().produce();
1911
1912		origin.publish_broadcast("allowed/test", broadcast1.consume());
1913		origin.publish_broadcast("notallowed/test", broadcast2.consume());
1914
1915		// Create limited consumer
1916		let limited_consumer = origin
1917			.consume()
1918			.scope(&["allowed".into()])
1919			.expect("should create limited consumer");
1920
1921		// Should be able to get allowed broadcast
1922		let result = limited_consumer.get_broadcast("allowed/test");
1923		assert!(result.is_some());
1924		assert!(result.unwrap().is_clone(&broadcast1.consume()));
1925
1926		// Should not be able to get disallowed broadcast
1927		assert!(limited_consumer.get_broadcast("notallowed/test").is_none());
1928
1929		// Original consumer can get both
1930		let consumer = origin.consume();
1931		assert!(consumer.get_broadcast("allowed/test").is_some());
1932		assert!(consumer.get_broadcast("notallowed/test").is_some());
1933	}
1934
1935	#[tokio::test]
1936	async fn test_nested_paths_with_permissions() {
1937		let origin = Origin::random().produce();
1938		let broadcast = Broadcast::new().produce();
1939
1940		// Create producer limited to "a/b/c"
1941		let limited_producer = origin.scope(&["a/b/c".into()]).expect("should create limited producer");
1942
1943		// Should be able to publish to exact path and nested paths
1944		assert!(limited_producer.publish_broadcast("a/b/c", broadcast.consume()));
1945		assert!(limited_producer.publish_broadcast("a/b/c/d", broadcast.consume()));
1946		assert!(limited_producer.publish_broadcast("a/b/c/d/e", broadcast.consume()));
1947
1948		// Should not be able to publish to parent or sibling paths
1949		assert!(!limited_producer.publish_broadcast("a", broadcast.consume()));
1950		assert!(!limited_producer.publish_broadcast("a/b", broadcast.consume()));
1951		assert!(!limited_producer.publish_broadcast("a/b/other", broadcast.consume()));
1952	}
1953
1954	#[tokio::test]
1955	async fn test_multiple_consumers_with_different_permissions() {
1956		let origin = Origin::random().produce();
1957		let broadcast1 = Broadcast::new().produce();
1958		let broadcast2 = Broadcast::new().produce();
1959		let broadcast3 = Broadcast::new().produce();
1960
1961		// Publish to different paths
1962		origin.publish_broadcast("foo/test", broadcast1.consume());
1963		origin.publish_broadcast("bar/test", broadcast2.consume());
1964		origin.publish_broadcast("baz/test", broadcast3.consume());
1965
1966		// Create consumers with different permissions
1967		let mut foo_consumer = origin
1968			.consume()
1969			.scope(&["foo".into()])
1970			.expect("should create foo consumer");
1971
1972		let mut bar_consumer = origin
1973			.consume()
1974			.scope(&["bar".into()])
1975			.expect("should create bar consumer");
1976
1977		let mut foobar_consumer = origin
1978			.consume()
1979			.scope(&["foo".into(), "bar".into()])
1980			.expect("should create foobar consumer");
1981
1982		// Each consumer should only see their allowed paths
1983		foo_consumer.assert_next("foo/test", &broadcast1.consume());
1984		foo_consumer.assert_next_wait();
1985
1986		bar_consumer.assert_next("bar/test", &broadcast2.consume());
1987		bar_consumer.assert_next_wait();
1988
1989		foobar_consumer.assert_next("bar/test", &broadcast2.consume());
1990		foobar_consumer.assert_next("foo/test", &broadcast1.consume());
1991		foobar_consumer.assert_next_wait();
1992	}
1993
1994	#[tokio::test]
1995	async fn test_select_with_empty_prefix() {
1996		let origin = Origin::random().produce();
1997		let broadcast1 = Broadcast::new().produce();
1998		let broadcast2 = Broadcast::new().produce();
1999
2000		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
2001		let demo_producer = origin.with_root("demo").expect("should create demo root");
2002		let limited_producer = demo_producer
2003			.scope(&["worm-node".into(), "foobar".into()])
2004			.expect("should create limited producer");
2005
2006		// Publish some broadcasts
2007		assert!(limited_producer.publish_broadcast("worm-node/test", broadcast1.consume()));
2008		assert!(limited_producer.publish_broadcast("foobar/test", broadcast2.consume()));
2009
2010		// scope with empty prefix should keep the exact same "worm-node" and "foobar" nodes
2011		let mut consumer = limited_producer
2012			.consume()
2013			.scope(&["".into()])
2014			.expect("should create consumer with empty prefix");
2015
2016		// Should see both broadcasts (order depends on PathPrefixes sort)
2017		let a1 = consumer.try_announced().expect("expected first announcement");
2018		let a2 = consumer.try_announced().expect("expected second announcement");
2019		consumer.assert_next_wait();
2020
2021		let mut paths: Vec<_> = [&a1, &a2].iter().map(|(p, _)| p.to_string()).collect();
2022		paths.sort();
2023		assert_eq!(paths, ["foobar/test", "worm-node/test"]);
2024	}
2025
2026	#[tokio::test]
2027	async fn test_select_narrowing_scope() {
2028		let origin = Origin::random().produce();
2029		let broadcast1 = Broadcast::new().produce();
2030		let broadcast2 = Broadcast::new().produce();
2031		let broadcast3 = Broadcast::new().produce();
2032
2033		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
2034		let demo_producer = origin.with_root("demo").expect("should create demo root");
2035		let limited_producer = demo_producer
2036			.scope(&["worm-node".into(), "foobar".into()])
2037			.expect("should create limited producer");
2038
2039		// Publish broadcasts at different levels
2040		assert!(limited_producer.publish_broadcast("worm-node", broadcast1.consume()));
2041		assert!(limited_producer.publish_broadcast("worm-node/foo", broadcast2.consume()));
2042		assert!(limited_producer.publish_broadcast("foobar/bar", broadcast3.consume()));
2043
2044		// Test 1: scope("worm-node") should result in a single "" node with contents of "worm-node" ONLY
2045		let mut worm_consumer = limited_producer
2046			.consume()
2047			.scope(&["worm-node".into()])
2048			.expect("should create worm-node consumer");
2049
2050		// Should see worm-node content with paths stripped to ""
2051		worm_consumer.assert_next("worm-node", &broadcast1.consume());
2052		worm_consumer.assert_next("worm-node/foo", &broadcast2.consume());
2053		worm_consumer.assert_next_wait(); // Should NOT see foobar content
2054
2055		// Test 2: scope("worm-node/foo") should result in a "" node with contents of "worm-node/foo"
2056		let mut foo_consumer = limited_producer
2057			.consume()
2058			.scope(&["worm-node/foo".into()])
2059			.expect("should create worm-node/foo consumer");
2060
2061		foo_consumer.assert_next("worm-node/foo", &broadcast2.consume());
2062		foo_consumer.assert_next_wait(); // Should NOT see other content
2063	}
2064
2065	#[tokio::test]
2066	async fn test_select_multiple_roots_with_empty_prefix() {
2067		let origin = Origin::random().produce();
2068		let broadcast1 = Broadcast::new().produce();
2069		let broadcast2 = Broadcast::new().produce();
2070		let broadcast3 = Broadcast::new().produce();
2071
2072		// Producer with multiple allowed roots
2073		let limited_producer = origin
2074			.scope(&["app1".into(), "app2".into(), "shared".into()])
2075			.expect("should create limited producer");
2076
2077		// Publish to each root
2078		assert!(limited_producer.publish_broadcast("app1/data", broadcast1.consume()));
2079		assert!(limited_producer.publish_broadcast("app2/config", broadcast2.consume()));
2080		assert!(limited_producer.publish_broadcast("shared/resource", broadcast3.consume()));
2081
2082		// scope with empty prefix should maintain all roots
2083		let mut consumer = limited_producer
2084			.consume()
2085			.scope(&["".into()])
2086			.expect("should create consumer with empty prefix");
2087
2088		// Should see all broadcasts from all roots
2089		consumer.assert_next("app1/data", &broadcast1.consume());
2090		consumer.assert_next("app2/config", &broadcast2.consume());
2091		consumer.assert_next("shared/resource", &broadcast3.consume());
2092		consumer.assert_next_wait();
2093	}
2094
2095	#[tokio::test]
2096	async fn test_publish_scope_with_empty_prefix() {
2097		let origin = Origin::random().produce();
2098		let broadcast = Broadcast::new().produce();
2099
2100		// Producer with specific allowed paths
2101		let limited_producer = origin
2102			.scope(&["services/api".into(), "services/web".into()])
2103			.expect("should create limited producer");
2104
2105		// scope with empty prefix should keep the same restrictions
2106		let same_producer = limited_producer
2107			.scope(&["".into()])
2108			.expect("should create producer with empty prefix");
2109
2110		// Should still have the same publishing restrictions
2111		assert!(same_producer.publish_broadcast("services/api", broadcast.consume()));
2112		assert!(same_producer.publish_broadcast("services/web", broadcast.consume()));
2113		assert!(!same_producer.publish_broadcast("services/db", broadcast.consume()));
2114		assert!(!same_producer.publish_broadcast("other", broadcast.consume()));
2115	}
2116
2117	#[tokio::test]
2118	async fn test_select_narrowing_to_deeper_path() {
2119		let origin = Origin::random().produce();
2120		let broadcast1 = Broadcast::new().produce();
2121		let broadcast2 = Broadcast::new().produce();
2122		let broadcast3 = Broadcast::new().produce();
2123
2124		// Producer with broad permission
2125		let limited_producer = origin.scope(&["org".into()]).expect("should create limited producer");
2126
2127		// Publish at various depths
2128		assert!(limited_producer.publish_broadcast("org/team1/project1", broadcast1.consume()));
2129		assert!(limited_producer.publish_broadcast("org/team1/project2", broadcast2.consume()));
2130		assert!(limited_producer.publish_broadcast("org/team2/project1", broadcast3.consume()));
2131
2132		// Narrow down to team2 only
2133		let mut team2_consumer = limited_producer
2134			.consume()
2135			.scope(&["org/team2".into()])
2136			.expect("should create team2 consumer");
2137
2138		team2_consumer.assert_next("org/team2/project1", &broadcast3.consume());
2139		team2_consumer.assert_next_wait(); // Should NOT see team1 content
2140
2141		// Further narrow down to team1/project1
2142		let mut project1_consumer = limited_producer
2143			.consume()
2144			.scope(&["org/team1/project1".into()])
2145			.expect("should create project1 consumer");
2146
2147		// Should only see project1 content at root
2148		project1_consumer.assert_next("org/team1/project1", &broadcast1.consume());
2149		project1_consumer.assert_next_wait();
2150	}
2151
2152	#[tokio::test]
2153	async fn test_select_with_non_matching_prefix() {
2154		let origin = Origin::random().produce();
2155
2156		// Producer with specific allowed paths
2157		let limited_producer = origin
2158			.scope(&["allowed/path".into()])
2159			.expect("should create limited producer");
2160
2161		// Trying to scope with a completely different prefix should return None
2162		assert!(limited_producer.consume().scope(&["different/path".into()]).is_none());
2163
2164		// Similarly for scope
2165		assert!(limited_producer.scope(&["other/path".into()]).is_none());
2166	}
2167
2168	// Regression test for https://github.com/moq-dev/moq/issues/910
2169	// with_root panics when String has trailing slash (AsPath for String skips normalization)
2170	#[tokio::test]
2171	async fn test_with_root_trailing_slash_consumer() {
2172		let origin = Origin::random().produce();
2173
2174		// Use an owned String so the trailing slash is NOT normalized away.
2175		let prefix = "some_prefix/".to_string();
2176		let mut consumer = origin.consume().with_root(prefix).unwrap();
2177
2178		let b = origin.create_broadcast("some_prefix/test").unwrap();
2179		consumer.assert_next("test", &b.consume());
2180	}
2181
2182	// Same issue but for the producer side of with_root
2183	#[tokio::test]
2184	async fn test_with_root_trailing_slash_producer() {
2185		let origin = Origin::random().produce();
2186
2187		// Use an owned String so the trailing slash is NOT normalized away.
2188		let prefix = "some_prefix/".to_string();
2189		let rooted = origin.with_root(prefix).unwrap();
2190
2191		let b = rooted.create_broadcast("test").unwrap();
2192
2193		let mut consumer = rooted.consume();
2194		consumer.assert_next("test", &b.consume());
2195	}
2196
2197	// Verify unannounce also doesn't panic with trailing slash
2198	#[tokio::test]
2199	async fn test_with_root_trailing_slash_unannounce() {
2200		tokio::time::pause();
2201
2202		let origin = Origin::random().produce();
2203
2204		let prefix = "some_prefix/".to_string();
2205		let mut consumer = origin.consume().with_root(prefix).unwrap();
2206
2207		let b = origin.create_broadcast("some_prefix/test").unwrap();
2208		consumer.assert_next("test", &b.consume());
2209
2210		// Drop the broadcast producer to trigger unannounce
2211		drop(b);
2212		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2213
2214		// unannounce also calls strip_prefix(&self.root).unwrap()
2215		consumer.assert_next_none("test");
2216	}
2217
2218	#[tokio::test]
2219	async fn test_select_maintains_access_with_wider_prefix() {
2220		let origin = Origin::random().produce();
2221		let broadcast1 = Broadcast::new().produce();
2222		let broadcast2 = Broadcast::new().produce();
2223
2224		// Setup: user with root "demo" allowed to subscribe to specific paths
2225		let demo_producer = origin.with_root("demo").expect("should create demo root");
2226		let user_producer = demo_producer
2227			.scope(&["worm-node".into(), "foobar".into()])
2228			.expect("should create user producer");
2229
2230		// Publish some data
2231		assert!(user_producer.publish_broadcast("worm-node/data", broadcast1.consume()));
2232		assert!(user_producer.publish_broadcast("foobar", broadcast2.consume()));
2233
2234		// Key test: scope with "" should maintain access to allowed roots
2235		let mut consumer = user_producer
2236			.consume()
2237			.scope(&["".into()])
2238			.expect("scope with empty prefix should not fail when user has specific permissions");
2239
2240		// Should still receive broadcasts from allowed paths (order not guaranteed)
2241		let a1 = consumer.try_announced().expect("expected first announcement");
2242		let a2 = consumer.try_announced().expect("expected second announcement");
2243		consumer.assert_next_wait();
2244
2245		let mut paths: Vec<_> = [&a1, &a2].iter().map(|(p, _)| p.to_string()).collect();
2246		paths.sort();
2247		assert_eq!(paths, ["foobar", "worm-node/data"]);
2248
2249		// Also test that we can still narrow the scope
2250		let mut narrow_consumer = user_producer
2251			.consume()
2252			.scope(&["worm-node".into()])
2253			.expect("should be able to narrow scope to worm-node");
2254
2255		narrow_consumer.assert_next("worm-node/data", &broadcast1.consume());
2256		narrow_consumer.assert_next_wait(); // Should not see foobar
2257	}
2258
2259	#[tokio::test]
2260	async fn test_duplicate_prefixes_deduped() {
2261		let origin = Origin::random().produce();
2262		let broadcast = Broadcast::new().produce();
2263
2264		// scope with duplicate prefixes should work (deduped internally)
2265		let producer = origin
2266			.scope(&["demo".into(), "demo".into()])
2267			.expect("should create producer");
2268
2269		assert!(producer.publish_broadcast("demo/stream", broadcast.consume()));
2270
2271		let mut consumer = producer.consume();
2272		consumer.assert_next("demo/stream", &broadcast.consume());
2273		consumer.assert_next_wait();
2274	}
2275
2276	#[tokio::test]
2277	async fn test_overlapping_prefixes_deduped() {
2278		let origin = Origin::random().produce();
2279		let broadcast = Broadcast::new().produce();
2280
2281		// "demo" and "demo/foo" — "demo/foo" is redundant, only "demo" should remain
2282		let producer = origin
2283			.scope(&["demo".into(), "demo/foo".into()])
2284			.expect("should create producer");
2285
2286		// Can still publish under "demo/bar" since "demo" covers everything
2287		assert!(producer.publish_broadcast("demo/bar/stream", broadcast.consume()));
2288
2289		let mut consumer = producer.consume();
2290		consumer.assert_next("demo/bar/stream", &broadcast.consume());
2291		consumer.assert_next_wait();
2292	}
2293
2294	#[tokio::test]
2295	async fn test_overlapping_prefixes_no_duplicate_announcements() {
2296		let origin = Origin::random().produce();
2297		let broadcast = Broadcast::new().produce();
2298
2299		// Both "demo" and "demo/foo" are requested — should only have one node
2300		let producer = origin
2301			.scope(&["demo".into(), "demo/foo".into()])
2302			.expect("should create producer");
2303
2304		assert!(producer.publish_broadcast("demo/foo/stream", broadcast.consume()));
2305
2306		let mut consumer = producer.consume();
2307		// Should only get ONE announcement (not two from overlapping nodes)
2308		consumer.assert_next("demo/foo/stream", &broadcast.consume());
2309		consumer.assert_next_wait();
2310	}
2311
2312	#[tokio::test]
2313	async fn test_allowed_returns_deduped_prefixes() {
2314		let origin = Origin::random().produce();
2315
2316		let producer = origin
2317			.scope(&["demo".into(), "demo/foo".into(), "anon".into()])
2318			.expect("should create producer");
2319
2320		let allowed: Vec<_> = producer.allowed().collect();
2321		assert_eq!(allowed.len(), 2, "demo/foo should be subsumed by demo");
2322	}
2323
2324	#[tokio::test]
2325	async fn test_announced_broadcast_already_announced() {
2326		let origin = Origin::random().produce();
2327		let broadcast = Broadcast::new().produce();
2328
2329		origin.publish_broadcast("test", broadcast.consume());
2330
2331		let consumer = origin.consume();
2332		let result = consumer.announced_broadcast("test").await.expect("should find it");
2333		assert!(result.is_clone(&broadcast.consume()));
2334	}
2335
2336	#[tokio::test]
2337	async fn test_announced_broadcast_delayed() {
2338		tokio::time::pause();
2339
2340		let origin = Origin::random().produce();
2341		let broadcast = Broadcast::new().produce();
2342
2343		let consumer = origin.consume();
2344
2345		// Start waiting before it's announced.
2346		let wait = tokio::spawn({
2347			let consumer = consumer.clone();
2348			async move { consumer.announced_broadcast("test").await }
2349		});
2350
2351		// Give the spawned task a chance to subscribe.
2352		tokio::task::yield_now().await;
2353
2354		origin.publish_broadcast("test", broadcast.consume());
2355
2356		let result = wait.await.unwrap().expect("should find it");
2357		assert!(result.is_clone(&broadcast.consume()));
2358	}
2359
2360	#[tokio::test]
2361	async fn test_announced_broadcast_ignores_unrelated_paths() {
2362		tokio::time::pause();
2363
2364		let origin = Origin::random().produce();
2365		let other = Broadcast::new().produce();
2366		let target = Broadcast::new().produce();
2367
2368		let consumer = origin.consume();
2369
2370		let wait = tokio::spawn({
2371			let consumer = consumer.clone();
2372			async move { consumer.announced_broadcast("target").await }
2373		});
2374
2375		tokio::task::yield_now().await;
2376
2377		// Publish an unrelated broadcast first — announced_broadcast should skip it.
2378		origin.publish_broadcast("other", other.consume());
2379		tokio::task::yield_now().await;
2380		assert!(!wait.is_finished(), "must not resolve on unrelated path");
2381
2382		origin.publish_broadcast("target", target.consume());
2383		let result = wait.await.unwrap().expect("should find target");
2384		assert!(result.is_clone(&target.consume()));
2385	}
2386
2387	#[tokio::test]
2388	async fn test_announced_broadcast_skips_nested_paths() {
2389		tokio::time::pause();
2390
2391		let origin = Origin::random().produce();
2392		let nested = Broadcast::new().produce();
2393		let exact = Broadcast::new().produce();
2394
2395		let consumer = origin.consume();
2396
2397		let wait = tokio::spawn({
2398			let consumer = consumer.clone();
2399			async move { consumer.announced_broadcast("foo").await }
2400		});
2401
2402		tokio::task::yield_now().await;
2403
2404		// "foo/bar" is under the prefix scope, but it's not the exact path — skip it.
2405		origin.publish_broadcast("foo/bar", nested.consume());
2406		tokio::task::yield_now().await;
2407		assert!(!wait.is_finished(), "must not resolve on a nested path");
2408
2409		origin.publish_broadcast("foo", exact.consume());
2410		let result = wait.await.unwrap().expect("should find foo exactly");
2411		assert!(result.is_clone(&exact.consume()));
2412	}
2413
2414	#[tokio::test]
2415	async fn test_announced_broadcast_disallowed() {
2416		let origin = Origin::random().produce();
2417		let limited = origin
2418			.consume()
2419			.scope(&["allowed".into()])
2420			.expect("should create limited");
2421
2422		// Path is outside allowed prefixes — should return None immediately.
2423		assert!(limited.announced_broadcast("notallowed").await.is_none());
2424	}
2425
2426	#[tokio::test]
2427	async fn test_announced_broadcast_scope_too_narrow() {
2428		// Consumer's scope is narrower than the requested path: asking for `foo` on a consumer
2429		// limited to `foo/specific` can never resolve. Must return None, not loop forever.
2430		let origin = Origin::random().produce();
2431		let limited = origin
2432			.consume()
2433			.scope(&["foo/specific".into()])
2434			.expect("should create limited");
2435
2436		// now_or_never so we fail fast instead of hanging if the guard regresses.
2437		let result = limited
2438			.announced_broadcast("foo")
2439			.now_or_never()
2440			.expect("must not block");
2441		assert!(result.is_none());
2442	}
2443
2444	// Coalescing tests: a slow consumer that doesn't drain between updates
2445	// should observe a bounded number of deliveries.
2446
2447	#[tokio::test]
2448	async fn test_coalesce_announce_then_unannounce() {
2449		// announce + unannounce that the consumer hasn't observed yet collapses to nothing.
2450		tokio::time::pause();
2451
2452		let origin = Origin::random().produce();
2453		let mut consumer = origin.consume();
2454
2455		let broadcast = Broadcast::new().produce();
2456		origin.publish_broadcast("test", broadcast.consume());
2457		drop(broadcast);
2458
2459		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2460
2461		consumer.assert_next_wait();
2462	}
2463
2464	#[tokio::test]
2465	async fn test_coalesce_announce_unannounce_announce() {
2466		// announce, unannounce, announce that the consumer hasn't drained collapses
2467		// to a single Announce of the latest broadcast.
2468		tokio::time::pause();
2469
2470		let origin = Origin::random().produce();
2471		let mut consumer = origin.consume();
2472
2473		let broadcast1 = Broadcast::new().produce();
2474		let broadcast2 = Broadcast::new().produce();
2475
2476		origin.publish_broadcast("test", broadcast1.consume());
2477		drop(broadcast1);
2478		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2479		origin.publish_broadcast("test", broadcast2.consume());
2480
2481		consumer.assert_next("test", &broadcast2.consume());
2482		consumer.assert_next_wait();
2483	}
2484
2485	#[tokio::test]
2486	async fn test_coalesce_unannounce_announce_preserved() {
2487		// unannounce followed by announce of a different broadcast must be preserved
2488		// as two deliveries so the consumer learns the origin changed.
2489		tokio::time::pause();
2490
2491		let origin = Origin::random().produce();
2492		let broadcast1 = Broadcast::new().produce();
2493		origin.publish_broadcast("test", broadcast1.consume());
2494
2495		let mut consumer = origin.consume();
2496		consumer.assert_next("test", &broadcast1.consume());
2497
2498		// Drop, then publish a fresh broadcast at the same path.
2499		drop(broadcast1);
2500		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2501
2502		let broadcast2 = Broadcast::new().produce();
2503		origin.publish_broadcast("test", broadcast2.consume());
2504
2505		// The consumer must see the unannounce before the new announce.
2506		consumer.assert_next_none("test");
2507		consumer.assert_next("test", &broadcast2.consume());
2508		consumer.assert_next_wait();
2509	}
2510
2511	#[tokio::test]
2512	async fn test_coalesce_unannounce_announce_unannounce() {
2513		// unannounce + announce + unannounce collapses to a single unannounce: the
2514		// embedded announce was never observed.
2515		tokio::time::pause();
2516
2517		let origin = Origin::random().produce();
2518		let broadcast1 = Broadcast::new().produce();
2519		origin.publish_broadcast("test", broadcast1.consume());
2520
2521		let mut consumer = origin.consume();
2522		consumer.assert_next("test", &broadcast1.consume());
2523
2524		drop(broadcast1);
2525		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2526
2527		let broadcast2 = Broadcast::new().produce();
2528		origin.publish_broadcast("test", broadcast2.consume());
2529		drop(broadcast2);
2530		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2531
2532		consumer.assert_next_none("test");
2533		consumer.assert_next_wait();
2534	}
2535
2536	#[tokio::test]
2537	async fn test_coalesce_churn_bounded() {
2538		// A churn loop on a single path should keep the pending set bounded.
2539		// Backup promotion during cleanup can leave the consumer with zero or one
2540		// pending update for "test" depending on the order tasks run; we only
2541		// require that churn doesn't accumulate across iterations.
2542		tokio::time::pause();
2543
2544		let origin = Origin::random().produce();
2545		let mut consumer = origin.consume();
2546
2547		for _ in 0..1000 {
2548			let broadcast = Broadcast::new().produce();
2549			origin.publish_broadcast("test", broadcast.consume());
2550			drop(broadcast);
2551		}
2552		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2553
2554		let mut collected = Vec::new();
2555		while let Some(update) = consumer.try_announced() {
2556			collected.push(update);
2557		}
2558		assert!(
2559			collected.len() <= 1,
2560			"expected at most one pending update, got {}",
2561			collected.len()
2562		);
2563		assert!(
2564			collected.iter().all(|(path, _)| path == &Path::new("test")),
2565			"unexpected path in pending updates",
2566		);
2567	}
2568
2569	// With no OriginDynamic handler, an unannounced path resolves to Unroutable.
2570	#[tokio::test]
2571	async fn dynamic_request_unroutable_without_handler() {
2572		let origin = Origin::random().produce();
2573		let consumer = origin.consume();
2574		assert!(matches!(
2575			consumer.request_broadcast("missing").await,
2576			Err(Error::Unroutable)
2577		));
2578	}
2579
2580	// A dynamically served broadcast resolves the requester, but is never announced.
2581	#[tokio::test(start_paused = true)]
2582	async fn dynamic_request_served_not_announced() {
2583		let origin = Origin::random().produce();
2584		let mut dynamic = origin.dynamic();
2585		let consumer = origin.consume();
2586
2587		// A separate announce cursor must never observe the dynamic broadcast.
2588		let mut announced = origin.consume();
2589		announced.assert_next_wait();
2590
2591		// Request a path that nobody announced; the future stays pending until served.
2592		// Registration happens up front, so the handler sees the request immediately.
2593		let request_fut = consumer.request_broadcast("fallback");
2594
2595		// The handler serves it with a live broadcast it keeps producing into.
2596		let served = Broadcast::new().produce();
2597
2598		let request = dynamic.requested_broadcast().await.unwrap();
2599		assert_eq!(request.path(), &Path::new("fallback"));
2600		request.accept(served.consume());
2601
2602		let broadcast = request_fut.await.unwrap();
2603		assert!(broadcast.is_clone(&served.consume()));
2604
2605		// Still nothing announced.
2606		announced.assert_next_wait();
2607	}
2608
2609	// Concurrent requests for the same queued path coalesce onto one handler request.
2610	#[tokio::test(start_paused = true)]
2611	async fn dynamic_request_coalesces() {
2612		let origin = Origin::random().produce();
2613		let mut dynamic = origin.dynamic();
2614		let consumer = origin.consume();
2615
2616		// Both register before the handler drains either.
2617		let f1 = consumer.request_broadcast("dup");
2618		let f2 = consumer.request_broadcast("dup");
2619
2620		// Exactly one request reaches the handler.
2621		let request = dynamic.requested_broadcast().await.unwrap();
2622		assert_eq!(request.path(), &Path::new("dup"));
2623		assert!(
2624			dynamic.requested_broadcast().now_or_never().is_none(),
2625			"a coalesced request must not be served twice"
2626		);
2627
2628		// Accepting resolves both awaiting requesters with the same broadcast.
2629		let served = Broadcast::new().produce();
2630		request.accept(served.consume());
2631		assert!(f1.await.unwrap().is_clone(&served.consume()));
2632		assert!(f2.await.unwrap().is_clone(&served.consume()));
2633	}
2634
2635	// Rejecting a request resolves the requester with the error.
2636	#[tokio::test(start_paused = true)]
2637	async fn dynamic_request_rejected() {
2638		let origin = Origin::random().produce();
2639		let mut dynamic = origin.dynamic();
2640		let consumer = origin.consume();
2641
2642		let request_fut = consumer.request_broadcast("fallback");
2643
2644		let request = dynamic.requested_broadcast().await.unwrap();
2645		request.reject(Error::Cancel);
2646
2647		assert!(matches!(request_fut.await, Err(Error::Cancel)));
2648	}
2649
2650	// Dropping the last handler resolves queued requests with an error and reverts to
2651	// resolving Unroutable.
2652	#[tokio::test(start_paused = true)]
2653	async fn dynamic_request_handler_dropped() {
2654		let origin = Origin::random().produce();
2655		let dynamic = origin.dynamic();
2656		let consumer = origin.consume();
2657
2658		let request_fut = consumer.request_broadcast("fallback");
2659		drop(dynamic);
2660		assert!(matches!(request_fut.await, Err(Error::Unroutable)));
2661
2662		// With no handler left, a fresh request resolves Unroutable.
2663		assert!(matches!(
2664			consumer.request_broadcast("again").await,
2665			Err(Error::Unroutable)
2666		));
2667	}
2668
2669	// `accept` is decoupled from the dynamic count: once a handler has picked a request up,
2670	// it can still serve it even if every handler (including itself) drops first, flipping the
2671	// count to zero. The in-flight request must not be rejected as `Unroutable`.
2672	#[tokio::test(start_paused = true)]
2673	async fn dynamic_request_accept_after_handler_dropped() {
2674		let origin = Origin::random().produce();
2675		let mut dynamic = origin.dynamic();
2676		let consumer = origin.consume();
2677
2678		let request_fut = consumer.request_broadcast("fallback");
2679
2680		// The handler picks the request up, then every handler drops (count -> 0).
2681		let request = dynamic.requested_broadcast().await.unwrap();
2682		drop(dynamic);
2683
2684		// Accept still resolves the awaiting requester with the served broadcast.
2685		let served = Broadcast::new().produce();
2686		request.accept(served.consume());
2687		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
2688	}
2689
2690	// A live announcement wins over the dynamic fallback; no request is queued.
2691	#[tokio::test(start_paused = true)]
2692	async fn dynamic_request_prefers_announced() {
2693		let origin = Origin::random().produce();
2694		let mut dynamic = origin.dynamic();
2695		let consumer = origin.consume();
2696
2697		let broadcast = Broadcast::new().produce();
2698		assert!(origin.publish_broadcast("live", broadcast.consume()));
2699
2700		let got = consumer.request_broadcast("live").await.unwrap();
2701		assert!(
2702			got.is_clone(&broadcast.consume()),
2703			"should return the announced broadcast"
2704		);
2705		assert!(
2706			dynamic.requested_broadcast().now_or_never().is_none(),
2707			"an announced path must not queue a fallback request"
2708		);
2709	}
2710
2711	// Cloning a handler and dropping the clone must not flip the count to zero.
2712	#[tokio::test(start_paused = true)]
2713	async fn dynamic_clone_keeps_alive() {
2714		let origin = Origin::random().produce();
2715		let dynamic = origin.dynamic();
2716		let consumer = origin.consume();
2717
2718		drop(dynamic.clone());
2719
2720		// The original handle is still live, so the request registers (stays pending)
2721		// instead of resolving Unroutable.
2722		let request_fut = consumer.request_broadcast("fallback");
2723		assert!(
2724			request_fut.now_or_never().is_none(),
2725			"request should stay pending until served"
2726		);
2727	}
2728}