Skip to main content

moq_net/model/
origin.rs

1use crate::{broadcast, cache, stats, track};
2use kio::Pollable;
3use std::{
4	collections::{BTreeMap, HashMap},
5	fmt,
6	sync::Arc,
7	sync::atomic::{AtomicU64, Ordering},
8	task::{Poll, ready},
9	time::Duration,
10};
11
12use rand::RngExt;
13use web_async::Lock;
14
15use super::{Requests, WeakCache};
16use crate::{
17	AsPath, Error, Path, PathOwned, PathPrefixes,
18	coding::{BoundsExceeded, Decode, DecodeError, Encode, EncodeError},
19};
20
21/// A relay origin, identified by a 62-bit varint on the wire.
22///
23/// Local origins are built with [`Origin::new`] or [`Origin::random`], both of
24/// which guarantee a non-zero id so loop detection can work. Remote peers may
25/// still send `0`; it is legal on the wire but cannot be used for loop detection.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct Origin {
28	/// 62-bit identifier. Encoded as a QUIC varint on the wire.
29	id: u64,
30}
31
32/// Returned when a local origin id is zero or outside the 62-bit wire range.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[non_exhaustive]
35pub struct InvalidOrigin;
36
37impl fmt::Display for InvalidOrigin {
38	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39		write!(f, "local origin id must be non-zero and below 2^62")
40	}
41}
42
43impl std::error::Error for InvalidOrigin {}
44
45impl Origin {
46	/// Placeholder for hop entries whose actual id is not on the wire (Lite03).
47	/// Also used for remote peers that choose the legal but loop-blind id 0.
48	pub(crate) const UNKNOWN: Self = Self { id: 0 };
49
50	/// Build an origin from a stable id.
51	///
52	/// The id must be non-zero and fit in the 62-bit QUIC varint range. Wire
53	/// decode accepts remote id 0, but local origins should not use it because
54	/// downstream peers cannot exclude it for loop detection.
55	pub fn new(id: u64) -> Result<Self, InvalidOrigin> {
56		if id == 0 || id >= 1u64 << 62 {
57			return Err(InvalidOrigin);
58		}
59		Ok(Self { id })
60	}
61
62	/// Generate a fresh origin with a random non-zero id. Use this for any
63	/// origin that does not need a stable identity across restarts.
64	///
65	/// TEMPORARY: the wire format allows 62 bits, but older `@moq/lite` JS
66	/// clients decode `AnnounceInterest.exclude_hop` as a u53 (number) and
67	/// throw on anything > 2^53-1. To keep those clients alive against
68	/// fresh relays, we cap the random id at 53 bits. Restore to 62 bits
69	/// once the JS u62 fix has propagated to deployed bundles.
70	pub fn random() -> Self {
71		let mut rng = rand::rng();
72		let id = rng.random_range(1..(1u64 << 53));
73		Self { id }
74	}
75
76	/// Return the origin's wire id.
77	pub fn id(self) -> u64 {
78		self.id
79	}
80
81	/// Consume this [Origin] to create a producer that carries its id, with an
82	/// unbounded cache pool. Use [`Info::produce`] to configure the pool.
83	pub fn produce(self) -> Producer {
84		Info::new(self).produce()
85	}
86}
87
88/// An origin's identity plus the cache pool its broadcasts inherit.
89///
90/// Doubles as the construction config for an [origin `Producer`](Producer) and as the
91/// parent handle every broadcast carries ([`broadcast::Info::origin`]): the origin owns
92/// the [`cache::Pool`] every group in the tree registers with, so a relay configures one
93/// bounded pool here and every broadcast, track, and group beneath it reaches that single
94/// budget by walking up the ownership chain. Defaults to an unbounded pool
95/// ([`Origin::produce`] is the shorthand for that). Cheap to clone (a `Copy` id plus an
96/// `Arc`-handle bump), so it's stored by value rather than behind another `Arc`.
97#[derive(Clone, Debug)]
98#[non_exhaustive]
99pub struct Info {
100	/// The origin's wire identity, appended to broadcast hop chains for loop
101	/// detection and shortest-path routing.
102	pub id: Origin,
103
104	/// The cache pool broadcasts under this origin register their groups with. It
105	/// flows down the ownership chain (origin -> broadcast -> track -> group), so a
106	/// group reaches it via `track.broadcast.origin.pool`. Unbounded by default; a
107	/// relay sets a bounded one (via [`Self::with_pool`]) so cached groups across the
108	/// whole process share one memory budget.
109	pub pool: cache::Pool,
110
111	/// How long a broadcast under this origin outlives the *ungraceful* loss of its
112	/// last source before closing. Within the window the path stays announced and a
113	/// source re-attaching at it (a session reconnecting, a publisher re-announcing)
114	/// splices in seamlessly, so consumers never observe the gap. A source that ends
115	/// deliberately ([`broadcast::Producer::finish`], a clean unannounce from a peer)
116	/// closes the broadcast immediately regardless. Zero (the default) closes
117	/// immediately either way; a duration too large to represent as a deadline
118	/// (e.g. [`Duration::MAX`]) lingers indefinitely.
119	pub linger: Duration,
120}
121
122impl Default for Info {
123	/// An unknown origin (id `0`, no loop detection) with an unbounded pool. This is
124	/// what a standalone broadcast (no relay origin) inherits.
125	fn default() -> Self {
126		Self {
127			id: Origin::UNKNOWN,
128			pool: cache::Pool::default(),
129			linger: Duration::ZERO,
130		}
131	}
132}
133
134impl Info {
135	/// Config for the given origin id with an unbounded cache pool.
136	pub fn new(id: Origin) -> Self {
137		Self { id, ..Self::default() }
138	}
139
140	/// Set the cache pool this origin's broadcasts inherit, returning `self` for chaining.
141	pub fn with_pool(mut self, pool: cache::Pool) -> Self {
142		self.pool = pool;
143		self
144	}
145
146	/// Set how long a broadcast survives ungracefully losing its last source (see
147	/// [`Self::linger`]), returning `self` for chaining.
148	pub fn with_linger(mut self, linger: Duration) -> Self {
149		self.linger = linger;
150		self
151	}
152
153	/// Consume this config to create an origin [`Producer`].
154	pub fn produce(self) -> Producer {
155		Producer::new(self)
156	}
157}
158
159impl TryFrom<u64> for Origin {
160	type Error = InvalidOrigin;
161
162	fn try_from(id: u64) -> Result<Self, Self::Error> {
163		Self::new(id)
164	}
165}
166
167impl fmt::Display for Origin {
168	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169		self.id.fmt(f)
170	}
171}
172
173impl<V: Copy> Encode<V> for Origin
174where
175	u64: Encode<V>,
176{
177	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
178		self.id.encode(w, version)
179	}
180}
181
182impl<V: Copy> Decode<V> for Origin
183where
184	u64: Decode<V>,
185{
186	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
187		let id = u64::decode(r, version)?;
188		if id >= 1u64 << 62 {
189			return Err(DecodeError::InvalidValue);
190		}
191		Ok(Self { id })
192	}
193}
194
195/// Maximum number of origins (hops) an [`OriginList`] can hold.
196///
197/// Caps pathological or loop-induced announcements at a reasonable cluster
198/// diameter; appending past this limit returns [`TooManyOrigins`] rather than
199/// silently truncating.
200pub(crate) const MAX_HOPS: usize = 32;
201
202/// Bounded list of [`Origin`] entries, typically the hop chain of a broadcast.
203///
204/// Guarantees `len() <= MAX_HOPS`. Construct via [`OriginList::new`] +
205/// [`OriginList::push`], or fall back to the fallible [`TryFrom<Vec<Origin>>`].
206#[derive(Debug, Clone, Default, PartialEq, Eq)]
207pub struct OriginList(Vec<Origin>);
208
209/// Returned when an operation would grow an [`OriginList`] past its hop-count cap.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211#[non_exhaustive]
212pub struct TooManyOrigins;
213
214impl fmt::Display for TooManyOrigins {
215	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216		write!(f, "too many origins (max {MAX_HOPS})")
217	}
218}
219
220impl std::error::Error for TooManyOrigins {}
221
222impl From<TooManyOrigins> for DecodeError {
223	fn from(_: TooManyOrigins) -> Self {
224		DecodeError::BoundsExceeded
225	}
226}
227
228impl OriginList {
229	/// Create an empty list.
230	pub fn new() -> Self {
231		Self(Vec::new())
232	}
233
234	/// Append an [`Origin`]. Returns [`TooManyOrigins`] if the list is full.
235	pub fn push(&mut self, origin: Origin) -> Result<(), TooManyOrigins> {
236		if self.0.len() >= MAX_HOPS {
237			return Err(TooManyOrigins);
238		}
239		self.0.push(origin);
240		Ok(())
241	}
242
243	/// Replace the first entry equal to `target` with `replacement`, returning
244	/// true if a match was found. The length is unchanged.
245	pub fn replace_first(&mut self, target: Origin, replacement: Origin) -> bool {
246		for entry in &mut self.0 {
247			if *entry == target {
248				*entry = replacement;
249				return true;
250			}
251		}
252		false
253	}
254
255	/// Returns true if any entry matches `origin`.
256	pub fn contains(&self, origin: &Origin) -> bool {
257		self.0.contains(origin)
258	}
259
260	/// Number of entries currently in the list (always `<= MAX_HOPS`).
261	pub fn len(&self) -> usize {
262		self.0.len()
263	}
264
265	/// Whether the list contains no entries.
266	pub fn is_empty(&self) -> bool {
267		self.0.is_empty()
268	}
269
270	/// Iterate over the entries in hop order (oldest first).
271	pub fn iter(&self) -> std::slice::Iter<'_, Origin> {
272		self.0.iter()
273	}
274
275	/// Borrow the entries as a slice.
276	pub fn as_slice(&self) -> &[Origin] {
277		&self.0
278	}
279}
280
281impl TryFrom<Vec<Origin>> for OriginList {
282	type Error = TooManyOrigins;
283
284	fn try_from(v: Vec<Origin>) -> Result<Self, Self::Error> {
285		if v.len() > MAX_HOPS {
286			return Err(TooManyOrigins);
287		}
288		Ok(Self(v))
289	}
290}
291
292impl<'a> IntoIterator for &'a OriginList {
293	type Item = &'a Origin;
294	type IntoIter = std::slice::Iter<'a, Origin>;
295
296	fn into_iter(self) -> Self::IntoIter {
297		self.iter()
298	}
299}
300
301impl<V: Copy> Encode<V> for OriginList
302where
303	u64: Encode<V>,
304	Origin: Encode<V>,
305{
306	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
307		(self.0.len() as u64).encode(w, version)?;
308		for origin in &self.0 {
309			origin.encode(w, version)?;
310		}
311		Ok(())
312	}
313}
314
315impl<V: Copy> Decode<V> for OriginList
316where
317	u64: Decode<V>,
318	Origin: Decode<V>,
319{
320	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
321		let count = u64::decode(r, version)? as usize;
322		if count > MAX_HOPS {
323			return Err(DecodeError::BoundsExceeded);
324		}
325		let mut list = Vec::with_capacity(count);
326		for _ in 0..count {
327			list.push(Origin::decode(r, version)?);
328		}
329		Ok(Self(list))
330	}
331}
332
333static NEXT_CONSUMER_ID: AtomicU64 = AtomicU64::new(0);
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
336struct ConsumerId(u64);
337
338impl ConsumerId {
339	fn new() -> Self {
340		Self(NEXT_CONSUMER_ID.fetch_add(1, Ordering::Relaxed))
341	}
342}
343
344// The origin-owned broadcast at a leaf: the spliced broadcast consumers see,
345// the table of sources feeding it, and whether the path is currently announced.
346// `announced` is gated on the best source's `live` flag; a non-announced entry
347// is still returned by lookups, so an offline broadcast stays reachable for
348// subscribes and fetches.
349struct OriginBroadcast {
350	path: PathOwned,
351	/// The shared, spliced broadcast; its `consume()` is what consumers get.
352	broadcast: broadcast::Producer,
353	/// The source table, shared with every source watcher and the front task.
354	/// Also the broadcast's identity for stale-teardown checks.
355	state: kio::Producer<FrontState>,
356	announced: bool,
357}
358
359/// Ordering key used to pick the active route among broadcasts at the same path.
360///
361/// Lower wins. Shorter hop chains sort first (routing prefers the shortest path);
362/// remaining ties break on a deterministic hash of the broadcast name and hop
363/// chain. Every node in the cluster, given the same candidate routes, converges
364/// on the same winner: the hops are forwarded unchanged, and the hash is
365/// build-stable. Mixing the name in spreads equal routes across different
366/// upstreams rather than funneling onto one.
367fn route_key(name: &Path, hops: &OriginList) -> (usize, u64) {
368	(hops.len(), fnv_key(name, hops.iter().copied()))
369}
370
371/// FNV-1a over the broadcast name and a sequence of origin ids.
372///
373/// FNV-1a, not the std hasher: its output is fixed across Rust versions and
374/// builds, which matters when nodes run mismatched binaries during a rolling
375/// deploy and still need to agree on the same route. SEED is a custom basis
376/// (any nonzero u64 works, the textbook one is just as arbitrary); FNV_PRIME is
377/// the standard FNV-64 prime and should stay put.
378///
379/// Two callers, two different id sequences: [`route_key`] hashes a route's hop
380/// chain to pick among *routes*, and [`FrontState::handover_allowed`] hashes a
381/// single relay's origin to pick among *relays*. Mixing the name in spreads
382/// equal candidates across different winners rather than funneling onto one.
383fn fnv_key(name: &Path, origins: impl IntoIterator<Item = Origin>) -> u64 {
384	const SEED: u64 = 0x420C0DECB00B; // 420 C0DEC B00B
385	const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
386
387	let mut hash = SEED;
388	for &byte in name.as_str().as_bytes() {
389		hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
390	}
391	for origin in origins {
392		for &byte in &origin.id().to_le_bytes() {
393			hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
394		}
395	}
396
397	hash
398}
399
400/// Full ordering key for a [`broadcast::Route`]: announced routes first (an
401/// actively published source beats an offline one), then the marginal cost of
402/// pulling via the route, then the [`route_key`] hop ordering. Lower wins.
403///
404/// Hop length stays the tie-break below cost, so peers that never carry a cost
405/// (pre-lite-06, or a plain local publish) rank exactly as they did before route
406/// cost existed, and equal-cost warm copies resolve to the closest one, which
407/// bounds same-datacenter chains to a single hop.
408fn route_order(name: &Path, route: &broadcast::Route) -> (bool, u64, usize, u64) {
409	let (len, hash) = route_key(name, &route.hops);
410	(!route.announce, route.cost, len, hash)
411}
412
413/// One coalesced update queued for an `AnnounceConsumer`.
414///
415/// At most one entry exists per path, so a slow consumer's pending set is bounded
416/// by the number of distinct paths. `UnannounceAnnounce` preserves the signal
417/// that a broadcast genuinely went away and a different one took its place (the
418/// consumer must see the `None` before the `Some`), while a stale `Announce`
419/// cancels with a subsequent `unannounce` because the consumer has not yet
420/// observed it.
421enum PendingUpdate {
422	Announce(broadcast::Consumer),
423	Unannounce,
424	UnannounceAnnounce(broadcast::Consumer),
425}
426
427/// Pending updates keyed by path. `BTreeMap` keeps memory strictly bounded by
428/// the number of distinct paths with outstanding work (collapsed pairs are
429/// fully erased) and gives a deterministic lexicographic delivery order so
430/// tests can predict it.
431#[derive(Default)]
432struct OriginConsumerState {
433	pending: BTreeMap<PathOwned, PendingUpdate>,
434}
435
436impl OriginConsumerState {
437	fn apply_announce(&mut self, path: PathOwned, broadcast: broadcast::Consumer) {
438		let new = match self.pending.remove(&path) {
439			// First announce, or a stale announce being replaced.
440			None | Some(PendingUpdate::Announce(_)) => PendingUpdate::Announce(broadcast),
441			// Consumer needs to observe the unannounce before this announce.
442			Some(PendingUpdate::Unannounce | PendingUpdate::UnannounceAnnounce(_)) => {
443				PendingUpdate::UnannounceAnnounce(broadcast)
444			}
445		};
446		self.pending.insert(path, new);
447	}
448
449	fn apply_unannounce(&mut self, path: PathOwned) {
450		match self.pending.remove(&path) {
451			// Consumer has not seen the pending announce; drop both entirely.
452			Some(PendingUpdate::Announce(_)) => {}
453			None | Some(PendingUpdate::Unannounce) => {
454				self.pending.insert(path, PendingUpdate::Unannounce);
455			}
456			// The embedded announce cancels with this unannounce; the consumer still
457			// needs the leading unannounce.
458			Some(PendingUpdate::UnannounceAnnounce(_)) => {
459				self.pending.insert(path, PendingUpdate::Unannounce);
460			}
461		}
462	}
463
464	/// Take one update to deliver to the consumer, if any.
465	fn take(&mut self) -> Option<OriginAnnounce> {
466		let path = self.pending.keys().next()?.clone();
467		let broadcast = match self.pending.remove(&path).unwrap() {
468			PendingUpdate::Announce(broadcast) => Some(broadcast),
469			PendingUpdate::Unannounce => None,
470			PendingUpdate::UnannounceAnnounce(broadcast) => {
471				// Deliver the unannounce now; leave the trailing announce pending so
472				// the next take returns it for the same path.
473				self.pending.insert(path.clone(), PendingUpdate::Announce(broadcast));
474				None
475			}
476		};
477		Some(OriginAnnounce { path, broadcast })
478	}
479}
480
481#[derive(Clone)]
482struct AnnounceConsumerNotify {
483	root: PathOwned,
484	state: kio::Producer<OriginConsumerState>,
485}
486
487impl AnnounceConsumerNotify {
488	fn announce(&self, path: impl AsPath, broadcast: broadcast::Consumer) {
489		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
490		self.state
491			.write()
492			.ok()
493			.expect("consumer closed")
494			.apply_announce(path, broadcast);
495	}
496
497	fn unannounce(&self, path: impl AsPath) {
498		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
499		self.state.write().ok().expect("consumer closed").apply_unannounce(path);
500	}
501}
502
503struct NotifyNode {
504	parent: Option<Lock<NotifyNode>>,
505
506	// Consumers that are subscribed to this node.
507	// We store a consumer ID so we can remove it easily when it closes.
508	consumers: HashMap<ConsumerId, AnnounceConsumerNotify>,
509}
510
511impl NotifyNode {
512	fn new(parent: Option<Lock<NotifyNode>>) -> Self {
513		Self {
514			parent,
515			consumers: HashMap::new(),
516		}
517	}
518
519	fn announce(&mut self, path: impl AsPath, broadcast: &broadcast::Consumer) {
520		for consumer in self.consumers.values() {
521			consumer.announce(path.as_path(), broadcast.clone());
522		}
523
524		if let Some(parent) = &self.parent {
525			parent.lock().announce(path, broadcast);
526		}
527	}
528
529	fn unannounce(&mut self, path: impl AsPath) {
530		for consumer in self.consumers.values() {
531			consumer.unannounce(path.as_path());
532		}
533
534		if let Some(parent) = &self.parent {
535			parent.lock().unannounce(path);
536		}
537	}
538}
539
540struct OriginNode {
541	// The origin-owned broadcast published at this node, if any (see
542	// [`Producer::create_broadcast`]).
543	broadcast: Option<OriginBroadcast>,
544
545	// Nested nodes, one level down the tree.
546	nested: HashMap<String, Lock<OriginNode>>,
547
548	// Unfortunately, to notify consumers we need to traverse back up the tree.
549	notify: Lock<NotifyNode>,
550}
551
552impl OriginNode {
553	fn new(parent: Option<Lock<NotifyNode>>) -> Self {
554		Self {
555			broadcast: None,
556			nested: HashMap::new(),
557			notify: Lock::new(NotifyNode::new(parent)),
558		}
559	}
560
561	fn leaf(&mut self, path: &Path) -> Lock<OriginNode> {
562		let (dir, rest) = path.next_part().expect("leaf called with empty path");
563
564		let next = self.entry(dir);
565		if rest.is_empty() { next } else { next.lock().leaf(&rest) }
566	}
567
568	fn entry(&mut self, dir: &str) -> Lock<OriginNode> {
569		match self.nested.get(dir) {
570			Some(next) => next.clone(),
571			None => {
572				let next = Lock::new(OriginNode::new(Some(self.notify.clone())));
573				self.nested.insert(dir.to_string(), next.clone());
574				next
575			}
576		}
577	}
578
579	/// Toggle the announce state of this leaf's broadcast, notifying consumers on
580	/// a change. The identity check keeps a stale front from toggling its
581	/// successor.
582	fn set_announced(&mut self, expect: &kio::Producer<FrontState>, announce: bool) {
583		let Some(existing) = &mut self.broadcast else { return };
584		if !existing.state.same_channel(expect) || existing.announced == announce {
585			return;
586		}
587		existing.announced = announce;
588		let path = existing.path.clone();
589		let consumer = existing.broadcast.consume();
590		let mut notify = self.notify.lock();
591		if announce {
592			notify.announce(&path, &consumer);
593		} else {
594			notify.unannounce(&path);
595		}
596	}
597
598	fn consume(&mut self, id: ConsumerId, mut notify: AnnounceConsumerNotify) {
599		self.consume_initial(&mut notify);
600		self.notify.lock().consumers.insert(id, notify);
601	}
602
603	fn consume_initial(&mut self, notify: &mut AnnounceConsumerNotify) {
604		// Only announced (live) broadcasts replay; offline ones are reachable by
605		// exact path but never advertised.
606		if let Some(broadcast) = &self.broadcast
607			&& broadcast.announced
608		{
609			notify.announce(&broadcast.path, broadcast.broadcast.consume());
610		}
611
612		// Recursively subscribe to all nested nodes.
613		for nested in self.nested.values() {
614			nested.lock().consume_initial(notify);
615		}
616	}
617
618	fn consume_broadcast(&self, rest: impl AsPath) -> Option<broadcast::Consumer> {
619		let rest = rest.as_path();
620
621		if let Some((dir, rest)) = rest.next_part() {
622			let node = self.nested.get(dir)?.lock();
623			node.consume_broadcast(&rest)
624		} else {
625			self.broadcast.as_ref().map(|b| b.broadcast.consume())
626		}
627	}
628
629	fn unconsume(&mut self, id: ConsumerId) {
630		self.notify.lock().consumers.remove(&id).expect("consumer not found");
631		if self.is_empty() {
632			//tracing::warn!("TODO: empty node; memory leak");
633			// This happens when consuming a path that is not being broadcasted.
634		}
635	}
636
637	/// Remove the broadcast at `relative` if it is `expect`, unannouncing it if
638	/// needed and pruning empty nodes on the way back up. The identity check
639	/// keeps a stale teardown from clobbering a replacement.
640	fn remove(&mut self, expect: &kio::Producer<FrontState>, relative: impl AsPath) {
641		let relative = relative.as_path();
642
643		if let Some((dir, relative)) = relative.next_part() {
644			let Some(nested) = self.nested.get(dir) else { return };
645			let nested = nested.clone();
646			let mut locked = nested.lock();
647			locked.remove(expect, &relative);
648
649			if locked.is_empty() {
650				drop(locked);
651				self.nested.remove(dir);
652			}
653		} else if let Some(existing) = &self.broadcast
654			&& existing.state.same_channel(expect)
655		{
656			let existing = self.broadcast.take().expect("checked above");
657			if existing.announced {
658				self.notify.lock().unannounce(&existing.path);
659			}
660		}
661	}
662
663	fn is_empty(&self) -> bool {
664		self.broadcast.is_none() && self.nested.is_empty() && self.notify.lock().consumers.is_empty()
665	}
666}
667
668#[derive(Clone)]
669struct OriginNodes {
670	nodes: Vec<(PathOwned, Lock<OriginNode>)>,
671}
672
673impl OriginNodes {
674	// Returns nested roots that match the prefixes.
675	// PathPrefixes guarantees no duplicates or overlapping prefixes.
676	pub fn select(&self, prefixes: &PathPrefixes) -> Option<Self> {
677		let mut roots = Vec::new();
678
679		for (root, state) in &self.nodes {
680			for prefix in prefixes {
681				if root.has_prefix(prefix) {
682					// Keep the existing node if we're allowed to access it.
683					roots.push((root.to_owned(), state.clone()));
684					continue;
685				}
686
687				if let Some(suffix) = prefix.strip_prefix(root) {
688					// If the requested prefix is larger than the allowed prefix, then we further scope it.
689					let nested = state.lock().leaf(&suffix);
690					roots.push((prefix.to_owned(), nested));
691				}
692			}
693		}
694
695		if roots.is_empty() {
696			None
697		} else {
698			Some(Self { nodes: roots })
699		}
700	}
701
702	pub fn root(&self, new_root: impl AsPath) -> Option<Self> {
703		let new_root = new_root.as_path();
704		let mut roots = Vec::new();
705
706		if new_root.is_empty() {
707			return Some(self.clone());
708		}
709
710		for (root, state) in &self.nodes {
711			if let Some(suffix) = root.strip_prefix(&new_root) {
712				// If the old root is longer than the new root, shorten the keys.
713				roots.push((suffix.to_owned(), state.clone()));
714			} else if let Some(suffix) = new_root.strip_prefix(root) {
715				// If the new root is longer than the old root, add a new root.
716				// NOTE: suffix can't be empty
717				let nested = state.lock().leaf(&suffix);
718				roots.push(("".into(), nested));
719			}
720		}
721
722		if roots.is_empty() {
723			None
724		} else {
725			Some(Self { nodes: roots })
726		}
727	}
728
729	// Returns the root that has this prefix.
730	pub fn get(&self, path: impl AsPath) -> Option<(Lock<OriginNode>, PathOwned)> {
731		let path = path.as_path();
732
733		for (root, state) in &self.nodes {
734			if let Some(suffix) = path.strip_prefix(root) {
735				return Some((state.clone(), suffix.to_owned()));
736			}
737		}
738
739		None
740	}
741}
742
743impl Default for OriginNodes {
744	fn default() -> Self {
745		Self {
746			nodes: vec![("".into(), Lock::new(OriginNode::new(None)))],
747		}
748	}
749}
750
751/// A path and the broadcast now available there, delivered by [`AnnounceConsumer`].
752#[derive(Clone)]
753pub struct OriginAnnounce {
754	/// The path of the broadcast, relative to the consuming cursor's root.
755	pub path: PathOwned,
756	/// The broadcast now available at that path, or `None` if it is no longer available.
757	///
758	/// A replacement (a relay failover, or a shorter hop path arriving) is delivered as a
759	/// `None` followed by a `Some`, never as a swap in place. A route change alone is invisible here (the handles stay
760	/// valid); observe it via [`broadcast::Consumer::route_changed`].
761	pub broadcast: Option<broadcast::Consumer>,
762}
763
764/// Announces broadcasts to consumers over the network.
765#[derive(Clone)]
766pub struct Producer {
767	// Identity for this origin. Appended to broadcast hops when
768	// re-announcing so downstream relays can detect loops and prefer the
769	// shortest path.
770	info: Origin,
771
772	// The roots of the tree that we are allowed to publish.
773	// A path of "" means we can publish anything.
774	nodes: OriginNodes,
775
776	// The prefix that is automatically stripped from all paths.
777	root: PathOwned,
778
779	// Fallback request queue, shared with every derived consumer. Separate from
780	// `nodes` because dynamic broadcasts are never announced: they only resolve a
781	// consumer's `request_broadcast` when no live announcement exists.
782	dynamic: kio::Shared<OriginDynamicState>,
783
784	// The cache pool inherited by broadcasts created under this origin (sessions
785	// mint their remote broadcasts with it). Unbounded by default.
786	pool: cache::Pool,
787
788	// How long a broadcast outlives ungracefully losing its last source (see
789	// [`Info::linger`]). Zero by default.
790	linger: Duration,
791
792	// Ingress stats context. Broadcasts created through this producer are attributed
793	// to it (writes counted on the subscriber/ingress side). Empty (no-op) unless a
794	// session tagged this handle via [`Self::with_stats`].
795	stats: stats::Session,
796}
797
798impl std::ops::Deref for Producer {
799	type Target = Origin;
800
801	fn deref(&self) -> &Self::Target {
802		&self.info
803	}
804}
805
806impl Producer {
807	/// Build a producer from an [`Info`] (identity + cache pool) with no scoped
808	/// prefix and no pre-existing broadcasts. Prefer [`Info::produce`] /
809	/// [`Origin::produce`].
810	pub fn new(info: Info) -> Self {
811		Self {
812			info: info.id,
813			nodes: OriginNodes::default(),
814			root: PathOwned::default(),
815			dynamic: kio::Shared::default(),
816			pool: info.pool,
817			linger: info.linger,
818			stats: stats::Session::default(),
819		}
820	}
821
822	/// Attach an ingress stats context: broadcasts created through this handle (and
823	/// any handle derived from it) are attributed to `session` on the subscriber
824	/// (ingress) side. Pass [`stats::Session::default`] to opt out.
825	pub fn with_stats(mut self, session: stats::Session) -> Self {
826		self.stats = session;
827		self
828	}
829
830	/// Set the linger (see [`Info::linger`]) for broadcasts created through this
831	/// handle and any handle derived from it.
832	///
833	/// A broadcast adopts the window of the handle whose source *created* it (the
834	/// first source at the path), so set this before handing the producer to
835	/// whatever attaches sources through it (e.g. a session). Lets one supplier
836	/// declare its own recovery promise, like a reconnecting client lingering for
837	/// as long as its retry loop keeps trying, without reconfiguring the origin.
838	pub fn with_linger(mut self, linger: Duration) -> Self {
839		self.linger = linger;
840		self
841	}
842
843	/// This origin's [`Info`] (identity + cache pool), the parent handle a broadcast
844	/// created under this origin carries (see [`broadcast::Info::origin`]).
845	pub fn info(&self) -> Info {
846		Info {
847			id: self.info,
848			pool: self.pool.clone(),
849			linger: self.linger,
850		}
851	}
852
853	/// A producer with *no* allowed prefixes: it can't publish anything and
854	/// advertises no subscribe interest (its `allowed()` is empty, so the
855	/// subscriber issues no ANNOUNCE_PLEASE). Used to fill an unset session half
856	/// so both the publisher and subscriber loops still run.
857	pub(crate) fn empty(info: Origin) -> Self {
858		Self {
859			info,
860			nodes: OriginNodes { nodes: Vec::new() },
861			root: PathOwned::default(),
862			dynamic: kio::Shared::default(),
863			pool: cache::Pool::default(),
864			linger: Duration::ZERO,
865			stats: stats::Session::default(),
866		}
867	}
868
869	/// Create a broadcast at `path`, fed through the returned producer.
870	///
871	/// This is the sole way content enters an origin. The returned
872	/// [`broadcast::Producer`] is a route source: the origin owns the broadcast
873	/// consumers actually see, and splices its tracks across every source created
874	/// at the same path (other local publishers, or sessions attaching announces
875	/// from the network), always serving from the best [`broadcast::Route`] (live
876	/// first, then lowest cost, then shortest hops with a deterministic
877	/// tie-break). When the best source changes, tracks resume from the
878	/// replacement at the first missing group; consumers never observe the swap.
879	///
880	/// `route` is the source's initial metadata; update it with
881	/// [`broadcast::Producer::set_route`]. The [`broadcast::Route::announce`] flag
882	/// controls whether the path is announced: a non-live broadcast is invisible
883	/// to [`Consumer::announced`] but stays reachable by exact path for
884	/// subscribes and fetches (e.g. serving cached or on-demand content), so
885	/// toggling `live` announces or unannounces without touching the broadcast.
886	///
887	/// The broadcast becomes visible to consumers asynchronously, shortly after
888	/// this returns. Create tracks and register a
889	/// [`broadcast::Producer::dynamic`] handler before awaiting, so the first
890	/// consumer finds them.
891	///
892	/// End the broadcast with [`broadcast::Producer::finish`]; dropping it
893	/// without finishing also works, but logs a warning. A finish closes and
894	/// unannounces the path immediately once it was the last source. An unfinished
895	/// drop is treated as an outage: the path survives for the origin's
896	/// [`Info::linger`] (zero by default), so a replacement source attaching within
897	/// that window splices in without consumers noticing.
898	///
899	/// Fails with [`Error::Unauthorized`] if `path` is outside the prefixes this
900	/// producer may publish under (after [`scope`](Self::scope) /
901	/// [`with_root`](Self::with_root)), or [`Error::BoundsExceeded`] if the full
902	/// rooted path exceeds [`Path::MAX_PARTS`]. Must be called with a runtime
903	/// available (it spawns the broadcast's lifecycle task). Callers must not use
904	/// a route whose hop chain contains this origin's id (it would form a routing
905	/// loop); relays filter such reflections before they reach here, checked by a
906	/// `debug_assert`.
907	pub fn create_broadcast(&self, path: impl AsPath, route: broadcast::Route) -> Result<broadcast::Producer, Error> {
908		let path = path.as_path();
909
910		debug_assert!(
911			!route.hops.contains(&self.info),
912			"create_broadcast called with a looping hop chain",
913		);
914
915		let (node, rest) = self.nodes.get(&path).ok_or(Error::Unauthorized)?;
916		let full = self.root.join(&path).to_owned();
917
918		// A decoded announce prefix and suffix are each within the wire limit, but their
919		// join might not be. Enforcing here bounds the tree depth and guarantees the path
920		// can be re-encoded when forwarded.
921		if full.parts().count() > Path::MAX_PARTS {
922			return Err(BoundsExceeded.into());
923		}
924
925		// Resolve the ingress counters once, keyed by the absolute broadcast path.
926		// The source producer tags its tracks; run_source drives the announce guard
927		// off route transitions.
928		let ingress = self.stats.ingress(&full);
929
930		let mut source = broadcast::Info { origin: self.info() }
931			.produce()
932			.with_stats(ingress.clone());
933		source.set_route(route).expect("fresh producer");
934
935		web_async::spawn(run_source(self.info(), node, full, rest, source.consume(), ingress));
936
937		Ok(source)
938	}
939
940	/// Returns a new Producer restricted to publishing under one of `prefixes`.
941	///
942	/// Returns None if there are no legal prefixes (the requested prefixes are
943	/// disjoint from this producer's current scope).
944	// TODO accept PathPrefixes instead of &[Path]
945	pub fn scope(&self, prefixes: &[Path]) -> Option<Producer> {
946		let prefixes = PathPrefixes::new(prefixes);
947		Some(Producer {
948			info: self.info,
949			nodes: self.nodes.select(&prefixes)?,
950			root: self.root.clone(),
951			dynamic: self.dynamic.clone(),
952			pool: self.pool.clone(),
953			linger: self.linger,
954			stats: self.stats.clone(),
955		})
956	}
957
958	/// Create a dynamic handler that picks up [`Consumer::request_broadcast`]
959	/// calls for paths that are not announced.
960	///
961	/// This is the origin-level analogue of [`broadcast::Producer::dynamic`]: it serves
962	/// broadcasts on demand rather than tracks. Crucially the served broadcasts are
963	/// *not* announced, so [`Consumer::announced`] never sees them; they exist
964	/// only as a fallback for a consumer that asks for an exact path with no live
965	/// announcement. Drop the handler (and every clone) to reject pending requests.
966	pub fn dynamic(&self) -> Dynamic {
967		Dynamic::new(self.info, self.root.clone(), self.dynamic.clone())
968	}
969
970	/// Cheap read handle over this origin's broadcast tree.
971	///
972	/// Use [`Consumer::announced`] to register interest and start receiving
973	/// announcement events; the consumer itself does not allocate any channels.
974	pub fn consume(&self) -> Consumer {
975		// Untagged: a session tags the egress consumer separately via
976		// `origin::Consumer::with_stats` (ingress and egress are distinct sides).
977		Consumer::new(
978			self.info,
979			self.root.clone(),
980			self.nodes.clone(),
981			self.dynamic.clone(),
982			stats::Session::default(),
983		)
984	}
985
986	/// Handle to the announcement stream for this producer's subtree.
987	///
988	/// Symmetric counterpart to [`Self::consume`]; call
989	/// [`AnnounceProducer::consume`] to get an [`AnnounceConsumer`] that
990	/// receives announce / unannounce events.
991	pub fn announces(&self) -> AnnounceProducer {
992		AnnounceProducer::new(self.root.clone(), self.nodes.clone())
993	}
994
995	/// Returns a new Producer that automatically strips out the provided prefix.
996	///
997	/// Returns None if the provided root is not authorized; when [`Self::scope`]
998	/// was already used without a wildcard.
999	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
1000		let prefix = prefix.as_path();
1001
1002		Some(Self {
1003			info: self.info,
1004			root: self.root.join(&prefix).to_owned(),
1005			nodes: self.nodes.root(&prefix)?,
1006			dynamic: self.dynamic.clone(),
1007			pool: self.pool.clone(),
1008			linger: self.linger,
1009			stats: self.stats.clone(),
1010		})
1011	}
1012
1013	/// Returns the root that is automatically stripped from all paths.
1014	pub fn root(&self) -> &Path<'_> {
1015		&self.root
1016	}
1017
1018	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
1019	// TODO return PathPrefixes
1020	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
1021		self.nodes.nodes.iter().map(|(root, _)| root)
1022	}
1023
1024	/// Converts a relative path to an absolute path.
1025	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
1026		self.root.join(path)
1027	}
1028}
1029
1030/// How many times serving a single track may fail before it is spliced in (the
1031/// source rejected it, or its info never resolved) before the track is aborted
1032/// instead of retried. Bounds the retry loop against a source that keeps
1033/// rejecting one track.
1034const MAX_TRACK_RETRIES: u32 = 3;
1035
1036/// One attached source in a [`FrontState`] table.
1037struct FrontRoute {
1038	id: u64,
1039	/// The source's latest [`broadcast::Route`], mirrored from its
1040	/// `route_changed` stream; picks the active source and gates the announce.
1041	route: broadcast::Route,
1042	/// The source broadcast tracks are served from.
1043	source: broadcast::Consumer,
1044}
1045
1046/// Shared state behind a [`Front`]: the attached sources and which one is active.
1047struct FrontState {
1048	/// Absolute path of the broadcast, mixed into the route tie-break hash.
1049	path: PathOwned,
1050	/// The local origin's identity, the other half of the handover key gate.
1051	self_origin: Origin,
1052	next_route: u64,
1053	routes: Vec<FrontRoute>,
1054	/// The source tracks are dispatched to. Backups park until promoted.
1055	active: Option<u64>,
1056	/// How long the front outlives ungracefully losing its last source (see
1057	/// [`Info::linger`]). [`run_front`] arms the countdown when the table empties.
1058	linger: Duration,
1059	/// Terminal: no more sources may attach and every poller stops. Set
1060	/// synchronously by the detach that empties the table, or by [`run_front`]
1061	/// when the linger window expires without a replacement.
1062	closed: bool,
1063}
1064
1065impl FrontState {
1066	/// The source new track requests should dispatch to: live first, then lowest
1067	/// cost, then shortest hop chain with a deterministic hash tie-break.
1068	fn best_route(&self) -> Option<u64> {
1069		self.routes
1070			.iter()
1071			.min_by_key(|r| route_order(&self.path.as_path(), &r.route))
1072			.map(|r| r.id)
1073	}
1074
1075	/// Re-pick the active source after the table changed. Serve tasks watch
1076	/// `active` and re-splice on their own, so a cheaper route takes over
1077	/// seamlessly at a group boundary.
1078	///
1079	/// The one exception is the simultaneous-activation race: two nodes that
1080	/// each pulled the broadcast before seeing the other both advertise zero
1081	/// cost, so each sees the other as cheaper than its own source, and
1082	/// re-parenting onto each other at once leaves the broadcast with no
1083	/// upstream at all. That hazard only exists when both sides are actively
1084	/// carrying, so the gate is scoped to exactly that: while `carrying` (the
1085	/// front has live demand), a cheaper route whose announcing relay is itself
1086	/// carrying (it advertised zero from a chain of two or more hops; a chain of
1087	/// one is the original publisher, which can never adopt a route to its own
1088	/// broadcast) displaces an announced incumbent only when
1089	/// [`Self::handover_allowed`] says so. Every other cheaper route, e.g. a
1090	/// forwarder path or an upstream that repriced itself down, is taken
1091	/// immediately.
1092	fn reselect(&mut self, carrying: bool) {
1093		let best = self.best_route();
1094		if carrying
1095			&& let (Some(best_id), Some(cur_id)) = (best, self.active)
1096			&& best_id != cur_id
1097			&& let Some(candidate) = self.routes.iter().find(|r| r.id == best_id)
1098			&& let Some(incumbent) = self.routes.iter().find(|r| r.id == cur_id)
1099			&& incumbent.route.announce
1100			&& candidate.route.cost < incumbent.route.cost
1101			&& candidate.route.advertised == 0
1102			&& candidate.route.hops.len() >= 2
1103			&& !self.handover_allowed(&candidate.route)
1104		{
1105			// We won the key comparison: keep our source and let the peer come to us.
1106			return;
1107		}
1108		self.active = best;
1109	}
1110
1111	/// Whether re-parenting onto `route` is allowed while actively carrying: the
1112	/// announcing peer (the chain's last hop) must hash strictly below our own
1113	/// origin for this broadcast name.
1114	///
1115	/// Both sides compute the same two keys (the hash is build-stable and the
1116	/// inputs are shared), so the comparison resolves the same way everywhere:
1117	/// the lower-keyed node keeps its source, the higher-keyed one re-parents.
1118	/// A strict total order has no cycles, so mutual pulls cannot happen. Mixing
1119	/// the broadcast name in spreads ownership across a region's relays instead
1120	/// of funneling every broadcast onto the lowest-keyed one. A route with no
1121	/// hops is a local publish and always allowed.
1122	fn handover_allowed(&self, route: &broadcast::Route) -> bool {
1123		let name = self.path.as_path();
1124		match route.hops.iter().last() {
1125			Some(peer) => fnv_key(&name, [*peer]) < fnv_key(&name, [self.self_origin]),
1126			None => true,
1127		}
1128	}
1129
1130	/// The active source's route, advertised on the front's broadcast. The caller
1131	/// applies it via [`broadcast::Producer::set_route`] outside the lock.
1132	fn active_route(&self) -> Option<broadcast::Route> {
1133		let id = self.active?;
1134		self.routes.iter().find(|r| r.id == id).map(|r| r.route.clone())
1135	}
1136}
1137
1138/// Refresh the front's public face after a table change: advertise the best
1139/// source's route on the spliced broadcast and gate the path's announcement on
1140/// its `live` flag.
1141///
1142/// Re-reads the table at apply time (rather than applying a value computed under
1143/// an earlier lock) so concurrent attach/detach/update calls converge on the
1144/// latest winner regardless of the order their applies land in. An empty table
1145/// leaves the advert and announce state alone; the front task is closing and
1146/// unannounces on its way out.
1147fn sync_front(state: &kio::Producer<FrontState>, broadcast: &broadcast::Producer, leaf: &Lock<OriginNode>) {
1148	// Snapshot and apply under the leaf lock: two concurrent syncs would
1149	// otherwise race their applies, letting a stale snapshot land last and
1150	// leave the announce flag (or advert) contradicting the current table.
1151	// Lock order (leaf, then table, then broadcast) matches attach_source.
1152	let mut leaf_guard = leaf.lock();
1153	let advert = state.read().active_route();
1154	if let Some(advert) = advert {
1155		let announce = advert.announce;
1156		let _ = broadcast.clone().set_route(advert);
1157		leaf_guard.set_announced(state, announce);
1158	}
1159}
1160
1161/// Detach source `id`, promoting the next-best source; the tracks it was serving
1162/// re-splice on their own. Idempotent.
1163///
1164/// `graceful` says how the source ended: a deliberate finish (a publisher done
1165/// publishing, a peer's clean unannounce) or an abrupt loss (a session dying).
1166/// Detaching the last source *gracefully* closes the broadcast synchronously,
1167/// which guarantees a following create at the path is a *new* broadcast rather
1168/// than splicing new content into this one. An abrupt last detach instead leaves
1169/// the front open for the configured linger ([`Info::linger`]), so a source
1170/// re-attaching within the window (a reconnect) splices in seamlessly;
1171/// [`run_front`] closes it if the window expires empty. A zero linger closes
1172/// abrupt detaches synchronously too.
1173fn detach_source(
1174	state: &kio::Producer<FrontState>,
1175	broadcast: &broadcast::Producer,
1176	leaf: &Lock<OriginNode>,
1177	id: u64,
1178	graceful: bool,
1179) {
1180	let close = {
1181		let carrying = broadcast.demand().is_used();
1182		let Ok(mut s) = state.write() else { return };
1183		let Some(pos) = s.routes.iter().position(|r| r.id == id) else {
1184			return;
1185		};
1186		s.routes.remove(pos);
1187		s.reselect(carrying);
1188		if s.routes.is_empty() && !s.closed && (graceful || s.linger.is_zero()) {
1189			// Last one out: close now. The front task observes `closed` and
1190			// finishes the teardown (unpublish).
1191			s.closed = true;
1192			true
1193		} else {
1194			false
1195		}
1196	};
1197	if close {
1198		broadcast.abort_spliced(Error::Dropped);
1199	}
1200	sync_front(state, broadcast, leaf);
1201}
1202
1203/// Owns one source's lifecycle: attaches it to the front at its path on the first
1204/// route observation, forwards route updates, and detaches it when the source
1205/// closes. Spawned by [`Producer::create_broadcast`].
1206async fn run_source(
1207	origin: Info,
1208	node: Lock<OriginNode>,
1209	full: PathOwned,
1210	rest: PathOwned,
1211	mut source: broadcast::Consumer,
1212	ingress: stats::Scope,
1213) {
1214	// The first `route_changed` yields the current route immediately; nothing is
1215	// visible to consumers until this attach, giving the creator a window to set
1216	// up tracks and dynamic handlers.
1217	let Ok(route) = source.route_changed().await else {
1218		// Closed before ever attaching; nothing became visible.
1219		return;
1220	};
1221
1222	// Ingress announce guard: held while this source's route is announced. Opening
1223	// bumps `announced` + `announced_bytes`; dropping (route offline, or the source
1224	// closing below) bumps `announced_closed` + `announced_bytes`. Empty scope =
1225	// no-op.
1226	let mut announce = route.announce.then(|| ingress.announce());
1227
1228	let leaf = if rest.is_empty() {
1229		node.clone()
1230	} else {
1231		node.lock().leaf(&rest)
1232	};
1233
1234	let (state, broadcast, id) = attach_source(&origin, &node, &leaf, &full, &rest, &source, route);
1235
1236	loop {
1237		match source.route_changed().await {
1238			Ok(route) => {
1239				let announced = route.announce;
1240				{
1241					let carrying = broadcast.demand().is_used();
1242					let Ok(mut s) = state.write() else { return };
1243					let Some(entry) = s.routes.iter_mut().find(|r| r.id == id) else {
1244						return;
1245					};
1246					if entry.route == route {
1247						continue;
1248					}
1249					entry.route = route;
1250					s.reselect(carrying);
1251				}
1252				// Toggle the ingress announce guard on a live/offline transition.
1253				match (announced, announce.is_some()) {
1254					(true, false) => announce = Some(ingress.announce()),
1255					(false, true) => announce = None,
1256					_ => {}
1257				}
1258				sync_front(&state, &broadcast, &leaf);
1259			}
1260			Err(_) => {
1261				// A deliberate finish closes the front immediately; an abrupt loss
1262				// (dropped producer, dead session) may linger for a replacement.
1263				detach_source(&state, &broadcast, &leaf, id, source.is_finished());
1264				return;
1265			}
1266		}
1267	}
1268}
1269
1270/// Attach a source to the broadcast at `leaf`, creating (and publishing) the
1271/// broadcast if none is live. Returns the shared source table, the spliced
1272/// broadcast, and the source's table id. One lock acquisition covers the whole
1273/// join-or-create decision, so concurrent attaches cannot race each other.
1274fn attach_source(
1275	origin: &Info,
1276	node: &Lock<OriginNode>,
1277	leaf: &Lock<OriginNode>,
1278	full: &PathOwned,
1279	rest: &PathOwned,
1280	source: &broadcast::Consumer,
1281	route: broadcast::Route,
1282) -> (kio::Producer<FrontState>, broadcast::Producer, u64) {
1283	let mut leaf_guard = leaf.lock();
1284
1285	// Join the live broadcast if the leaf already has one. A closed one (torn
1286	// down, or awaiting teardown) is replaced below instead.
1287	if let Some(existing) = &leaf_guard.broadcast {
1288		let mut joined = None;
1289		let carrying = existing.broadcast.demand().is_used();
1290		if let Ok(mut s) = existing.state.write()
1291			&& !s.closed
1292		{
1293			let id = s.next_route;
1294			s.next_route += 1;
1295			s.routes.push(FrontRoute {
1296				id,
1297				route: route.clone(),
1298				source: source.clone(),
1299			});
1300			s.reselect(carrying);
1301			joined = Some(id);
1302		}
1303		if let Some(id) = joined {
1304			let state = existing.state.clone();
1305			let broadcast = existing.broadcast.clone();
1306			drop(leaf_guard);
1307			sync_front(&state, &broadcast, leaf);
1308			return (state, broadcast, id);
1309		}
1310	}
1311
1312	// First source: create the broadcast and publish it into the tree.
1313	let announce = route.announce;
1314	let broadcast = broadcast::Producer::new_spliced(broadcast::Info { origin: origin.clone() });
1315	let _ = broadcast.clone().set_route(route.clone());
1316	let state = kio::Producer::new(FrontState {
1317		path: full.clone(),
1318		self_origin: origin.id,
1319		next_route: 1,
1320		routes: vec![FrontRoute {
1321			id: 0,
1322			route,
1323			source: source.clone(),
1324		}],
1325		active: Some(0),
1326		linger: origin.linger,
1327		closed: false,
1328	});
1329
1330	// Replacing a stale (closed) entry counts as an unannounce, so consumers
1331	// observe the replacement rather than a silent swap; its own teardown task
1332	// then finds the slot already taken and leaves it alone.
1333	if let Some(stale) = leaf_guard.broadcast.take()
1334		&& stale.announced
1335	{
1336		leaf_guard.notify.lock().unannounce(&stale.path);
1337	}
1338	let entry = OriginBroadcast {
1339		path: full.clone(),
1340		broadcast: broadcast.clone(),
1341		state: state.clone(),
1342		announced: announce,
1343	};
1344	if entry.announced {
1345		leaf_guard.notify.lock().announce(full, &broadcast.consume());
1346	}
1347	leaf_guard.broadcast = Some(entry);
1348	drop(leaf_guard);
1349
1350	web_async::spawn(run_front(state.clone(), broadcast.clone(), node.clone(), rest.clone()));
1351
1352	(state, broadcast, 0)
1353}
1354
1355/// Owns a front's lifecycle: dispatches each requested track to a serve task
1356/// until the last source detaches, then unpublishes the broadcast.
1357async fn run_front(
1358	state: kio::Producer<FrontState>,
1359	mut broadcast: broadcast::Producer,
1360	node: Lock<OriginNode>,
1361	rest: PathOwned,
1362) {
1363	enum Step {
1364		Serve(Arc<str>, super::resume::Producer),
1365		/// The source table emptied or refilled: re-arm the linger countdown.
1366		Changed,
1367		/// The linger window expired: close if the table is still empty.
1368		Expired,
1369		Closed,
1370	}
1371
1372	let linger = state.read().linger;
1373	// Armed while the table is ungracefully empty: the instant the front gives up
1374	// waiting for a replacement source. A graceful close never gets here (the
1375	// detach sets `closed` synchronously), so a running countdown always means a
1376	// reconnect is welcome.
1377	let mut deadline: Option<web_async::time::Instant> = None;
1378
1379	loop {
1380		let empty = {
1381			let s = state.read();
1382			!s.closed && s.routes.is_empty()
1383		};
1384		deadline = match (empty, deadline) {
1385			// An unrepresentable deadline (e.g. `Duration::MAX`) lingers forever:
1386			// no timer, only a re-attach or teardown moves the front on.
1387			(true, None) => web_async::time::Instant::now().checked_add(linger),
1388			(true, at) => at,
1389			(false, _) => None,
1390		};
1391
1392		let step = {
1393			// Pending forever while no countdown is running. Fused via `fired`: a
1394			// completed future must not be polled again.
1395			let mut sleep = std::pin::pin!(async {
1396				match deadline {
1397					Some(at) => {
1398						web_async::time::sleep(at.saturating_duration_since(web_async::time::Instant::now())).await
1399					}
1400					None => std::future::pending().await,
1401				}
1402			});
1403			let mut fired = false;
1404			kio::wait(|waiter| {
1405				if let Poll::Ready((name, resume)) = broadcast.poll_spliced_assigned(waiter) {
1406					return Poll::Ready(Step::Serve(name, resume));
1407				}
1408				// Watch for the close, and for the table changing shape so the
1409				// countdown re-arms (a detach emptying it, a reconnect refilling it).
1410				match state.poll(waiter, |s| {
1411					if s.closed || s.routes.is_empty() != empty {
1412						Poll::Ready(())
1413					} else {
1414						Poll::Pending
1415					}
1416				}) {
1417					Poll::Ready(Ok(guard)) => {
1418						return Poll::Ready(if guard.closed { Step::Closed } else { Step::Changed });
1419					}
1420					Poll::Ready(Err(_)) => return Poll::Ready(Step::Closed),
1421					Poll::Pending => {}
1422				}
1423				if deadline.is_some() && !fired && waiter.poll_future(sleep.as_mut()).is_ready() {
1424					fired = true;
1425				}
1426				match fired {
1427					true => Poll::Ready(Step::Expired),
1428					false => Poll::Pending,
1429				}
1430			})
1431			.await
1432		};
1433
1434		match step {
1435			Step::Serve(name, resume) => {
1436				// Serve tasks self-terminate when the track completes or the
1437				// front closes.
1438				web_async::spawn(serve_track(state.clone(), name, resume));
1439			}
1440			Step::Changed => {}
1441			Step::Expired => {
1442				// Close only if the table is still empty: a source that re-attached
1443				// as the window expired wins the write-lock race and keeps the
1444				// broadcast alive.
1445				let close = {
1446					let Ok(mut s) = state.write() else { break };
1447					if !s.closed && s.routes.is_empty() {
1448						s.closed = true;
1449						true
1450					} else {
1451						false
1452					}
1453				};
1454				if close {
1455					break;
1456				}
1457			}
1458			Step::Closed => break,
1459		}
1460	}
1461
1462	// Abort the logical tracks (releasing their subscribers) and unpublish.
1463	broadcast.abort_spliced(Error::Dropped);
1464
1465	// Deliberate end; suppresses the dropped-without-finish warning.
1466	broadcast.finish();
1467
1468	// Remove the broadcast from the tree (identity-checked, so a replacement is
1469	// untouched) and prune empty nodes.
1470	node.lock().remove(&state, &rest);
1471}
1472
1473/// Serves one spliced logical track: splices in the best source's copy of the
1474/// track, re-splicing on handover or failure, until the track completes or the
1475/// front closes. A rejection before a successful splice (the source refused the
1476/// track, or its info never resolved) counts toward [`MAX_TRACK_RETRIES`] and
1477/// then aborts the track as [`Error::Unroutable`]; failures after a splice (a
1478/// serving session dying mid-stream) are normal failover and re-splice from the
1479/// next source at the first missing group.
1480async fn serve_track(state: kio::Producer<FrontState>, name: Arc<str>, mut resume: super::resume::Producer) {
1481	enum Step {
1482		Closed,
1483		Splice(u64, broadcast::Consumer),
1484		Complete,
1485		Failed,
1486	}
1487
1488	let mut fails = 0u32;
1489	// The source whose copy is currently spliced in, and that copy.
1490	let mut serving: Option<(u64, track::Consumer)> = None;
1491	// A source whose splice failed because it had already closed. Its watcher is
1492	// about to detach it, so wait for the table to move on rather than burning
1493	// the strike budget on a corpse (ids are never reused, so this cannot wedge).
1494	let mut dead: Option<u64> = None;
1495
1496	loop {
1497		let serving_id = serving.as_ref().map(|(id, _)| *id);
1498		let step = kio::wait(|waiter| {
1499			// Watch the source table: the front closing, or the active source
1500			// moving away from the one currently spliced in (skipping one whose
1501			// closure we already observed).
1502			match state.poll(waiter, |s| {
1503				if s.closed || matches!(s.active, Some(active) if Some(active) != serving_id && Some(active) != dead) {
1504					Poll::Ready(())
1505				} else {
1506					Poll::Pending
1507				}
1508			}) {
1509				Poll::Ready(Ok(guard)) => {
1510					if guard.closed {
1511						return Poll::Ready(Step::Closed);
1512					}
1513					let active = guard.active.expect("predicate guaranteed an active source");
1514					let source = guard
1515						.routes
1516						.iter()
1517						.find(|r| r.id == active)
1518						.expect("active source in table")
1519						.source
1520						.clone();
1521					return Poll::Ready(Step::Splice(active, source));
1522				}
1523				Poll::Ready(Err(_)) => return Poll::Ready(Step::Closed),
1524				Poll::Pending => {}
1525			}
1526
1527			// Watch the spliced copy for its end: complete means the logical
1528			// track is over; anything else means the serving source died.
1529			if let Some((_, track)) = &serving
1530				&& let Poll::Ready(result) = track.poll_complete(waiter)
1531			{
1532				return Poll::Ready(match result {
1533					Ok(()) => Step::Complete,
1534					Err(_) => Step::Failed,
1535				});
1536			}
1537			Poll::Pending
1538		})
1539		.await;
1540
1541		match step {
1542			// The front's teardown aborts the logical track.
1543			Step::Closed => return,
1544			Step::Complete => {
1545				let _ = resume.finish();
1546				return;
1547			}
1548			Step::Failed => {
1549				// The spliced copy died mid-serve: failover, not a strike.
1550				// Re-splice from the (possibly same) active source.
1551				serving = None;
1552			}
1553			Step::Splice(id, source) => {
1554				// Ask the source for its copy and wait for the info to resolve,
1555				// proving it servable, before splicing it in. Bail out early if
1556				// the table moves on while waiting.
1557				let attempt = match source.track(&name) {
1558					Ok(track) => {
1559						// `into_inner` sheds the `Pending` future wrapper so only
1560						// the pollable (which is `Sync`) is held across the await.
1561						let query = track.info().into_inner();
1562						let info = kio::wait(|waiter| {
1563							if let Poll::Ready(result) = query.poll(waiter) {
1564								return Poll::Ready(Some(result));
1565							}
1566							match state.poll(waiter, |s| {
1567								if s.closed || s.active != Some(id) {
1568									Poll::Ready(())
1569								} else {
1570									Poll::Pending
1571								}
1572							}) {
1573								Poll::Ready(_) => Poll::Ready(None),
1574								Poll::Pending => Poll::Pending,
1575							}
1576						})
1577						.await;
1578						match info {
1579							// The table changed under us; retry from the top
1580							// without a strike.
1581							None => continue,
1582							// A copy that is already aborted can't be spliced;
1583							// count a strike, or a source pinning a dead track
1584							// alive would spin this loop without ever yielding.
1585							Some(Ok(_)) => match track.poll_complete(&kio::Waiter::noop()) {
1586								Poll::Ready(Err(err)) => Err(err),
1587								_ => Ok(track),
1588							},
1589							Some(Err(err)) => Err(err),
1590						}
1591					}
1592					Err(err) => Err(err),
1593				};
1594
1595				match attempt {
1596					Ok(track) => {
1597						if resume.takeover(&track).is_err() {
1598							// The logical track already ended (finished or
1599							// aborted); nothing left to serve.
1600							return;
1601						}
1602						// A successful splice proves the track servable: reset
1603						// the strike budget.
1604						fails = 0;
1605						dead = None;
1606						serving = Some((id, track));
1607					}
1608					// The source itself closed or deliberately ended: not a
1609					// rejection, so no strike. Park until its watcher detaches
1610					// it and the table promotes a replacement.
1611					Err(_) if source.is_closing() => {
1612						dead = Some(id);
1613						serving = None;
1614					}
1615					Err(err) => {
1616						fails += 1;
1617						if fails >= MAX_TRACK_RETRIES {
1618							tracing::debug!(name = %name, %err, "aborting unservable track");
1619							let _ = resume.abort(Error::Unroutable);
1620							return;
1621						}
1622						serving = None;
1623					}
1624				}
1625			}
1626		}
1627	}
1628}
1629
1630/// Shared fallback request queue for an origin.
1631///
1632/// Lives off to the side of the announce tree because dynamically served broadcasts
1633/// are never announced. Carried in a [`kio::Shared`], so consumers enqueue and handlers
1634/// drain under one lock. Mirrors the fetch state of the track model.
1635#[derive(Default)]
1636struct OriginDynamicState {
1637	// Result channels for pending requests, keyed by absolute path so concurrent
1638	// `request_broadcast` calls for the same path coalesce onto one channel.
1639	requests: Requests<PathOwned, kio::Producer<PendingBroadcast>>,
1640
1641	// Broadcasts a handler has already served, kept weakly so a repeat request for the
1642	// same path resolves to a shared clone instead of re-invoking the handler (which would
1643	// open a duplicate upstream subscription). Weak so a served broadcast still closes once
1644	// its real consumers drop. The cache reclaims closed entries incrementally on insert, so a
1645	// long-lived origin serving many distinct one-shot paths stays bounded by the live count.
1646	served: WeakCache<PathOwned, broadcast::WeakConsumer>,
1647}
1648
1649/// One-shot result of a dynamic broadcast request.
1650///
1651/// Stays `None` until a handler [`accept`](Request::accept)s (yielding the served
1652/// broadcast) or [`reject`](Request::reject)s (yielding an error). The producer is
1653/// dropped right after writing, closing the channel; kio checks the value before the closed
1654/// flag, so an awaiting requester still observes the final result.
1655#[derive(Default)]
1656struct PendingBroadcast {
1657	resolved: Option<Result<broadcast::Consumer, Error>>,
1658}
1659
1660/// Picks up [`Consumer::request_broadcast`] calls for paths that are not announced.
1661///
1662/// The origin-level analogue of [`broadcast::Dynamic`]: where that serves tracks on
1663/// demand within a broadcast, this serves whole broadcasts on demand within an origin. A
1664/// relay uses it as a fallback router, fetching a broadcast from upstream only when a
1665/// downstream consumer asks for an exact path that nobody announced.
1666///
1667/// Served broadcasts are deliberately *not* announced, so they never appear in
1668/// [`Consumer::announced`]. Drop this handle (and every clone) to reject the
1669/// requests still waiting to be served.
1670pub struct Dynamic {
1671	info: Origin,
1672	root: PathOwned,
1673	state: kio::Shared<OriginDynamicState>,
1674}
1675
1676impl Clone for Dynamic {
1677	fn clone(&self) -> Self {
1678		// Mirror `new`: count each live handle. Without this, dropping a clone would
1679		// decrement past `new`'s increment and prematurely flip the handler count to
1680		// zero, making future `request_broadcast` calls return `Unroutable`.
1681		self.state.lock().requests.add_handler();
1682
1683		Self {
1684			info: self.info,
1685			root: self.root.clone(),
1686			state: self.state.clone(),
1687		}
1688	}
1689}
1690
1691impl Dynamic {
1692	fn new(info: Origin, root: PathOwned, state: kio::Shared<OriginDynamicState>) -> Self {
1693		state.lock().requests.add_handler();
1694
1695		Self { info, root, state }
1696	}
1697
1698	/// The origin this handler belongs to.
1699	pub fn info(&self) -> &Origin {
1700		&self.info
1701	}
1702
1703	/// Poll for the next requested broadcast, without blocking.
1704	pub fn poll_requested_broadcast(&mut self, waiter: &kio::Waiter) -> Poll<Result<Request, Error>> {
1705		let mut state = ready!(self.state.poll(waiter, |state| {
1706			if state.requests.has_queued() {
1707				Poll::Ready(())
1708			} else {
1709				Poll::Pending
1710			}
1711		}));
1712
1713		let path = state.requests.pop().expect("predicate guaranteed a request");
1714		// The popped request stays pending, so a repeat request in the window between
1715		// hand-off and accept coalesces onto it instead of re-invoking the handler. The
1716		// producer is a shared clone; `Request::{accept, reject, drop}` removes the
1717		// entry. This mirrors how `poll_requested_track` keeps a served track
1718		// discoverable via the weak cache across the same window.
1719		let producer = state.requests.get(&path).expect("popped key must be pending").clone();
1720		Poll::Ready(Ok(Request {
1721			path,
1722			producer,
1723			state: self.state.clone(),
1724		}))
1725	}
1726
1727	/// Block until a consumer requests an unannounced broadcast, returning a
1728	/// [`Request`] to serve.
1729	pub async fn requested_broadcast(&mut self) -> Result<Request, Error> {
1730		kio::wait(|waiter| self.poll_requested_broadcast(waiter)).await
1731	}
1732
1733	/// Returns the prefix that is automatically stripped from requested paths.
1734	pub fn root(&self) -> &Path<'_> {
1735		&self.root
1736	}
1737}
1738
1739impl Drop for Dynamic {
1740	fn drop(&mut self) {
1741		// Decrement and reject under one lock, so a `request_broadcast` that saw a
1742		// live handler through the same lock can't slip a request past the rejection.
1743		let mut state = self.state.lock();
1744		if state.requests.remove_handler() {
1745			// No handlers left to pop queued requests; drop them, closing their result
1746			// channels so awaiting requesters resolve to `Unroutable`. A request already
1747			// handed to a handler stays, resolved by its `Request` instead.
1748			state.requests.drain_queued();
1749		}
1750	}
1751}
1752
1753/// A pending request for a broadcast that was not announced.
1754///
1755/// Yielded by [`Dynamic::requested_broadcast`]. The requester is awaiting inside
1756/// [`Consumer::request_broadcast`]; [`accept`](Self::accept) resolves it with a live
1757/// broadcast (which the handler keeps producing into) and [`reject`](Self::reject) resolves
1758/// it with an error. Dropping the request without either rejects it.
1759pub struct Request {
1760	// Absolute path that was requested.
1761	path: PathOwned,
1762
1763	// Result channel back to the awaiting requester(s). Writing `resolved` and dropping
1764	// this wakes them with the outcome.
1765	producer: kio::Producer<PendingBroadcast>,
1766
1767	// Shared dynamic state, so `accept` can cache the served broadcast for repeat requests.
1768	state: kio::Shared<OriginDynamicState>,
1769}
1770
1771impl Request {
1772	/// The absolute path that was requested.
1773	pub fn path(&self) -> &Path<'_> {
1774		&self.path
1775	}
1776
1777	/// Accept the request, resolving every awaiting requester with `broadcast`.
1778	///
1779	/// The caller keeps producing into `broadcast` (e.g. a relay proxying tracks from
1780	/// upstream); the requesters receive a consumer for it. The broadcast is *not*
1781	/// announced.
1782	pub fn accept(self, broadcast: impl Consume<broadcast::Consumer>) {
1783		let broadcast = broadcast.consume();
1784
1785		// Move the entry out of the in-flight queue and into the weak `served` cache, so repeat
1786		// requests for this path share the same broadcast instead of asking the handler to serve
1787		// (and subscribe upstream) again. Re-check under the lock: if a live broadcast was already
1788		// served for this path while we were fetching upstream, dedup onto it and drop ours rather
1789		// than replace a good entry with a duplicate subscription.
1790		let resolved = {
1791			let mut state = self.state.lock();
1792			let existing = state.served.insert(self.path.clone(), broadcast.weak());
1793			state
1794				.requests
1795				.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
1796			existing.map(|weak| weak.consume()).unwrap_or(broadcast)
1797		};
1798
1799		if let Ok(mut pending) = self.producer.write() {
1800			pending.resolved = Some(Ok(resolved));
1801		}
1802		// `self.producer` drops here, closing the channel; the value is still observable.
1803	}
1804
1805	/// Reject the request, resolving every awaiting requester with `err`.
1806	pub fn reject(self, err: Error) {
1807		self.state
1808			.lock()
1809			.requests
1810			.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
1811		if let Ok(mut state) = self.producer.write() {
1812			state.resolved = Some(Err(err));
1813		}
1814	}
1815}
1816
1817impl Drop for Request {
1818	fn drop(&mut self) {
1819		// Handed off but neither accepted nor rejected: drop the still-pending entry so its
1820		// producer clone (plus this one) closes the channel, resolving coalesced requesters to
1821		// `Unroutable` rather than hanging.
1822		//
1823		// The identity guard matters: `accept`/`reject` already removed our entry and released
1824		// the lock before we run, so a concurrent request for the same path may have registered
1825		// a *new* one here. Removing unconditionally would clobber it, stranding its requesters.
1826		self.state
1827			.lock()
1828			.requests
1829			.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
1830	}
1831}
1832
1833/// The pollable result of [`Consumer::request_broadcast`].
1834///
1835/// Awaited via the [`kio::Pending`] wrapper; resolves to the [`broadcast::Consumer`]
1836/// immediately when the broadcast was already announced, or once an [`Dynamic`]
1837/// handler serves the request. Resolves to an error if the request is rejected or every
1838/// handler drops before serving it.
1839pub struct Requesting {
1840	inner: RequestState,
1841	// Egress scope applied to the resolved broadcast, so its reads are attributed.
1842	// Empty (no-op) for an untagged consumer.
1843	stats: stats::Scope,
1844}
1845
1846enum RequestState {
1847	// Already announced: resolves immediately with a clone of this broadcast.
1848	Ready(broadcast::Consumer),
1849	// Unroutable at request time: resolves immediately with this error. Baked in so
1850	// `request_broadcast` itself stays infallible.
1851	Failed(Error),
1852	// Awaiting a handler: resolves when the request's result channel is written.
1853	Pending(kio::Consumer<PendingBroadcast>),
1854}
1855
1856impl Requesting {
1857	fn ready(broadcast: broadcast::Consumer) -> Self {
1858		Self {
1859			inner: RequestState::Ready(broadcast),
1860			stats: stats::Scope::default(),
1861		}
1862	}
1863
1864	fn failed(error: Error) -> Self {
1865		Self {
1866			inner: RequestState::Failed(error),
1867			stats: stats::Scope::default(),
1868		}
1869	}
1870
1871	fn pending(consumer: kio::Consumer<PendingBroadcast>) -> Self {
1872		Self {
1873			inner: RequestState::Pending(consumer),
1874			stats: stats::Scope::default(),
1875		}
1876	}
1877
1878	fn with_stats(mut self, scope: stats::Scope) -> Self {
1879		self.stats = scope;
1880		self
1881	}
1882
1883	/// Poll for the requested broadcast without blocking.
1884	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<broadcast::Consumer, Error>> {
1885		match &self.inner {
1886			RequestState::Ready(broadcast) => Poll::Ready(Ok(broadcast.clone().with_stats(self.stats.clone()))),
1887			RequestState::Failed(error) => Poll::Ready(Err(error.clone())),
1888			RequestState::Pending(consumer) => Poll::Ready(
1889				match ready!(consumer.poll(waiter, |state| match &state.resolved {
1890					Some(result) => Poll::Ready(result.clone()),
1891					None => Poll::Pending,
1892				})) {
1893					Ok(result) => result.map(|broadcast| broadcast.with_stats(self.stats.clone())),
1894					// Every handler dropped without resolving: nobody could route it.
1895					Err(_closed) => Err(Error::Unroutable),
1896				},
1897			),
1898		}
1899	}
1900}
1901
1902impl kio::Pollable for Requesting {
1903	type Output = Result<broadcast::Consumer, Error>;
1904
1905	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
1906		self.poll_ok(waiter)
1907	}
1908}
1909
1910/// Derive a read view from a handle.
1911///
1912/// Lets APIs accept either a producer or a consumer (e.g.
1913/// [`Client::with_publisher`](crate::Client::with_publisher),
1914/// [`Request::accept`]). The blanket `&T` impl means you can
1915/// pass by value (`foo(x)`) to hand off ownership, or by reference (`foo(&x)`)
1916/// to keep it, without spelling out `.consume()`.
1917pub trait Consume<T> {
1918	/// Derive a read view (a consumer) from this handle.
1919	fn consume(&self) -> T;
1920}
1921
1922impl<T, U: Consume<T>> Consume<T> for &U {
1923	fn consume(&self) -> T {
1924		(**self).consume()
1925	}
1926}
1927
1928impl Consume<Consumer> for Producer {
1929	fn consume(&self) -> Consumer {
1930		// Mirrors the inherent `Producer::consume`; inlined to avoid the
1931		// inherent-vs-trait `consume` ambiguity. Untagged: egress is tagged
1932		// separately from ingress.
1933		Consumer::new(
1934			self.info,
1935			self.root.clone(),
1936			self.nodes.clone(),
1937			self.dynamic.clone(),
1938			stats::Session::default(),
1939		)
1940	}
1941}
1942
1943impl Consume<Consumer> for Consumer {
1944	fn consume(&self) -> Consumer {
1945		self.clone()
1946	}
1947}
1948
1949impl Consume<broadcast::Consumer> for broadcast::Producer {
1950	fn consume(&self) -> broadcast::Consumer {
1951		// The inherent `consume` shadows this trait method, so this delegates.
1952		self.consume()
1953	}
1954}
1955
1956impl Consume<broadcast::Consumer> for broadcast::Consumer {
1957	fn consume(&self) -> broadcast::Consumer {
1958		self.clone()
1959	}
1960}
1961
1962impl Consume<track::Consumer> for track::Producer {
1963	fn consume(&self) -> track::Consumer {
1964		self.consume()
1965	}
1966}
1967
1968impl Consume<track::Consumer> for track::Consumer {
1969	fn consume(&self) -> track::Consumer {
1970		self.clone()
1971	}
1972}
1973
1974/// Cheap read handle over an origin's broadcast tree.
1975///
1976/// Clones share the underlying tree state without allocating any per-cursor
1977/// resources. To actually receive announce / unannounce events, call
1978/// [`Self::announced`] to obtain an [`AnnounceConsumer`].
1979#[derive(Clone)]
1980pub struct Consumer {
1981	// Identity of the origin this consumer was derived from.
1982	info: Origin,
1983	nodes: OriginNodes,
1984
1985	// A prefix that is automatically stripped from all paths.
1986	root: PathOwned,
1987
1988	// Shared fallback request queue, fed to any `Dynamic` handler on the
1989	// producer side. Used only by `request_broadcast`; announced lookups ignore it.
1990	dynamic: kio::Shared<OriginDynamicState>,
1991
1992	// Egress stats context. Broadcasts handed out through this consumer (and any
1993	// handle derived from them) are attributed to it (reads counted on the
1994	// publisher/egress side). Empty (no-op) unless a session tagged this handle.
1995	stats: stats::Session,
1996}
1997
1998impl std::ops::Deref for Consumer {
1999	type Target = Origin;
2000
2001	fn deref(&self) -> &Self::Target {
2002		&self.info
2003	}
2004}
2005
2006impl Consumer {
2007	fn new(
2008		info: Origin,
2009		root: PathOwned,
2010		nodes: OriginNodes,
2011		dynamic: kio::Shared<OriginDynamicState>,
2012		stats: stats::Session,
2013	) -> Self {
2014		Self {
2015			info,
2016			nodes,
2017			root,
2018			dynamic,
2019			stats,
2020		}
2021	}
2022
2023	/// Attach an egress stats context: broadcasts handed out through this handle (and
2024	/// any handle derived from it) are attributed to `session` on the publisher
2025	/// (egress) side. Pass [`stats::Session::default`] to opt out.
2026	pub fn with_stats(mut self, session: stats::Session) -> Self {
2027		self.stats = session;
2028		self
2029	}
2030
2031	/// A clone of this consumer with its stats context cleared, so an internal
2032	/// lookup stream (e.g. [`Self::announced_broadcast`]) doesn't drive the egress
2033	/// announce guards; the caller re-attributes the result itself.
2034	fn untagged(&self) -> Self {
2035		Self {
2036			stats: stats::Session::default(),
2037			..self.clone()
2038		}
2039	}
2040
2041	/// A view with this consumer's identity and root but no broadcasts:
2042	/// [`announced`](Self::announced) yields nothing. Used to answer a peer's
2043	/// announce-interest for a prefix outside our scope by announcing nothing,
2044	/// rather than tearing the stream down.
2045	pub(crate) fn empty(&self) -> Self {
2046		Self {
2047			info: self.info,
2048			nodes: OriginNodes { nodes: Vec::new() },
2049			root: self.root.clone(),
2050			dynamic: self.dynamic.clone(),
2051			stats: self.stats.clone(),
2052		}
2053	}
2054
2055	/// Subscribe to announce / unannounce events for this consumer's subtree.
2056	///
2057	/// Allocates a per-cursor coalescing buffer, registers it with each root
2058	/// in this consumer's scope, and replays the currently active broadcast
2059	/// set as initial announcements. Drop the returned [`AnnounceConsumer`]
2060	/// to unregister.
2061	pub fn announced(&self) -> AnnounceConsumer {
2062		AnnounceConsumer::new(self.root.clone(), self.nodes.clone(), self.stats.clone())
2063	}
2064
2065	/// Returns a cheap duplicate of this read handle.
2066	pub fn consume(&self) -> Self {
2067		self.clone()
2068	}
2069
2070	/// Internal synchronous peek: the broadcast at `path` if it is *already* announced.
2071	///
2072	/// Races announcement gossip (a freshly-connected consumer sees `None` even when the
2073	/// broadcast is about to arrive), so it is not public. [`Self::request_broadcast`] is the
2074	/// public lookup: it builds on this for the announced case, then falls back to a dynamic
2075	/// handler. [`Self::announced_broadcast`] waits for a future announcement.
2076	fn get_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
2077		let path = path.as_path();
2078		let (root, rest) = self.nodes.get(&path)?;
2079		let state = root.lock();
2080		state.consume_broadcast(&rest)
2081	}
2082
2083	/// Block until a broadcast with the given path is announced and return it.
2084	///
2085	/// Returns `None` if the path is outside this consumer's allowed prefixes or if the consumer
2086	/// is closed before the broadcast is announced. The returned broadcast may itself be closed
2087	/// later. Subscribers should watch [`broadcast::Consumer::closed`] to react to that.
2088	///
2089	/// Prefer this over [`Self::request_broadcast`] when you know the exact path you want but
2090	/// cannot guarantee the announcement has already been received. With moq-lite-05 (and
2091	/// the older Lite01/02) `connect()` already blocks until the initial announce set lands,
2092	/// so [`Self::request_broadcast`] is race-free for broadcasts that were live at connect time;
2093	/// this method is still needed to wait for a broadcast that comes online *after* connect.
2094	pub async fn announced_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
2095		let path = path.as_path();
2096
2097		// Scope a fresh consumer down to this path so we only wake up for relevant announcements.
2098		let consumer = self.scope(std::slice::from_ref(&path))?;
2099
2100		// `scope` keeps narrower permissions intact: if we ask for `foo` on a consumer limited
2101		// to `foo/specific`, `scope` returns a consumer scoped to `foo/specific`. No
2102		// announcement at the exact path `foo` can ever arrive. Bail rather than loop forever.
2103		if !consumer.allowed().any(|allowed| path.has_prefix(allowed)) {
2104			return None;
2105		}
2106
2107		// Use an untagged stream: this is a lookup, not egress announce forwarding, so
2108		// it must not drive the announce guards. The matched result is attributed
2109		// with the egress scope instead.
2110		let mut announced = consumer.untagged().announced();
2111		let scope = self.stats.egress(self.root.join(&path).to_owned());
2112		loop {
2113			let OriginAnnounce {
2114				path: announced_path,
2115				broadcast,
2116			} = announced.next().await?;
2117			// `scope` narrows by prefix, but we only want an exact-path match.
2118			if announced_path.as_path() == path
2119				&& let Some(broadcast) = broadcast
2120			{
2121				return Some(broadcast.with_stats(scope));
2122			}
2123		}
2124	}
2125
2126	/// Returns a new Consumer restricted to broadcasts under one of `prefixes`.
2127	///
2128	/// Returns None if there are no legal prefixes (the requested prefixes are
2129	/// disjoint from this consumer's current scope, so it would always return None).
2130	// TODO accept PathPrefixes instead of &[Path]
2131	pub fn scope(&self, prefixes: &[Path]) -> Option<Consumer> {
2132		let prefixes = PathPrefixes::new(prefixes);
2133		Some(Consumer::new(
2134			self.info,
2135			self.root.clone(),
2136			self.nodes.select(&prefixes)?,
2137			self.dynamic.clone(),
2138			self.stats.clone(),
2139		))
2140	}
2141
2142	/// Get a broadcast by path, falling back to a dynamic request when it is not announced.
2143	///
2144	/// Returns a [`kio::Pending`] future (resolved synchronously for an announced broadcast,
2145	/// otherwise once a handler serves it), mirroring [`track::Consumer::fetch_group`](track::Consumer::fetch_group).
2146	/// The lookup order is: an already-announced broadcast resolves
2147	/// immediately; otherwise, if an [`Dynamic`] handler is live (see
2148	/// [`Producer::dynamic`]), a fallback request is registered and the future resolves
2149	/// when the handler [`accept`](Request::accept)s it (or errors if it
2150	/// [`reject`](Request::reject)s or every handler drops). Concurrent requests for
2151	/// the same unannounced path coalesce onto one handler request, and once served the
2152	/// broadcast is cached weakly so *later* requests for that path also share it (rather
2153	/// than re-invoking the handler and opening a duplicate upstream subscription) for as
2154	/// long as it stays live; a closed one is re-served on the next request.
2155	///
2156	/// The returned future resolves to [`Error::Unroutable`] when the path is not announced and no
2157	/// dynamic handler exists. A request that is registered while a handler is live but then loses
2158	/// every handler before being served also resolves to [`Error::Unroutable`]. Unlike an announced
2159	/// broadcast, a dynamically served one is never visible to [`Self::announced`].
2160	pub fn request_broadcast(&self, path: impl AsPath) -> kio::Pending<Requesting> {
2161		let path = path.as_path();
2162
2163		// Key requests by absolute path so a scoped/rooted consumer and the handler
2164		// (which may have a different root) agree on the same entry, and so the egress
2165		// counters resolve against the same broadcast the ingress side wrote.
2166		let absolute = self.root.join(&path).to_owned();
2167		let scope = self.stats.egress(&absolute);
2168
2169		// Prefer a live announcement when one is present; the dynamic queue is only a fallback.
2170		if let Some(broadcast) = self.get_broadcast(&path) {
2171			return kio::Pending::new(Requesting::ready(broadcast).with_stats(scope));
2172		}
2173
2174		let mut state = self.dynamic.lock();
2175
2176		// Reuse a still-live broadcast a handler already served for this path, so repeat
2177		// requests share one upstream subscription. A closed entry is stale; `get` drops it
2178		// and returns `None`, so we fall through and re-serve below.
2179		if let Some(weak) = state.served.get(&absolute) {
2180			return kio::Pending::new(Requesting::ready(weak.consume()).with_stats(scope));
2181		}
2182
2183		// Coalesce onto a pending request for the same path; otherwise register a new
2184		// one, unless there is no handler alive to serve it.
2185		let consumer = if let Some(producer) = state.requests.join(&absolute) {
2186			producer.consume()
2187		} else {
2188			let producer = kio::Producer::<PendingBroadcast>::default();
2189			let consumer = producer.consume();
2190			if state.requests.insert(absolute, producer).is_err() {
2191				return kio::Pending::new(Requesting::failed(Error::Unroutable));
2192			}
2193			consumer
2194		};
2195
2196		kio::Pending::new(Requesting::pending(consumer).with_stats(scope))
2197	}
2198
2199	/// Returns a new Consumer that automatically strips out the provided prefix.
2200	///
2201	/// Returns None if the provided root is not authorized; when [`Self::scope`] was
2202	/// already used without a wildcard.
2203	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
2204		let prefix = prefix.as_path();
2205
2206		Some(Self::new(
2207			self.info,
2208			self.root.join(&prefix).to_owned(),
2209			self.nodes.root(&prefix)?,
2210			self.dynamic.clone(),
2211			self.stats.clone(),
2212		))
2213	}
2214
2215	/// Returns the prefix that is automatically stripped from all paths.
2216	pub fn root(&self) -> &Path<'_> {
2217		&self.root
2218	}
2219
2220	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
2221	// TODO return PathPrefixes
2222	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
2223		self.nodes.nodes.iter().map(|(root, _)| root)
2224	}
2225
2226	/// Converts a relative path to an absolute path.
2227	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
2228		self.root.join(path)
2229	}
2230}
2231
2232/// Handle to the announcement stream for a subtree.
2233///
2234/// Symmetric counterpart of [`AnnounceConsumer`]. Cheap to clone; call
2235/// [`Self::consume`] to obtain an [`AnnounceConsumer`] that receives events.
2236#[derive(Clone)]
2237pub struct AnnounceProducer {
2238	nodes: OriginNodes,
2239	root: PathOwned,
2240}
2241
2242impl AnnounceProducer {
2243	fn new(root: PathOwned, nodes: OriginNodes) -> Self {
2244		Self { nodes, root }
2245	}
2246
2247	/// Subscribe to announce / unannounce events for this subtree.
2248	///
2249	/// Allocates a per-cursor coalescing buffer and replays the currently active broadcast set
2250	/// as initial announcements. Drop the returned [`AnnounceConsumer`] to
2251	/// unregister.
2252	pub fn consume(&self) -> AnnounceConsumer {
2253		// Untagged: `AnnounceProducer` is used for internal announce plumbing, not
2254		// egress attribution (which flows through `origin::Consumer::announced`).
2255		AnnounceConsumer::new(self.root.clone(), self.nodes.clone(), stats::Session::default())
2256	}
2257
2258	/// Returns the prefix that is automatically stripped from announced paths.
2259	pub fn root(&self) -> &Path<'_> {
2260		&self.root
2261	}
2262}
2263
2264/// Receives announce / unannounce events for a subtree.
2265///
2266/// Created by [`Consumer::announced`] or [`AnnounceProducer::consume`].
2267/// Drop to unregister.
2268pub struct AnnounceConsumer {
2269	id: ConsumerId,
2270	nodes: OriginNodes,
2271	root: PathOwned,
2272
2273	// Pending updates queued for this cursor. Coalesced so a slow consumer
2274	// can't accumulate redundant announce/unannounce pairs.
2275	state: kio::Producer<OriginConsumerState>,
2276
2277	// Egress stats context (empty for an untagged stream). Announce events drive the
2278	// per-broadcast announce guards below and tag the broadcasts handed out.
2279	stats: stats::Session,
2280
2281	// Live egress announce guards, keyed by absolute broadcast path. An announce
2282	// opens one (bumping `announced` + `announced_bytes`); the matching unannounce
2283	// drops it (bumping `announced_closed` + `announced_bytes`).
2284	guards: HashMap<PathOwned, stats::Announce>,
2285}
2286
2287impl AnnounceConsumer {
2288	fn new(root: PathOwned, nodes: OriginNodes, stats: stats::Session) -> Self {
2289		let state = kio::Producer::<OriginConsumerState>::default();
2290		let id = ConsumerId::new();
2291
2292		for (_, node) in &nodes.nodes {
2293			let notify = AnnounceConsumerNotify {
2294				root: root.clone(),
2295				state: state.clone(),
2296			};
2297			node.lock().consume(id, notify);
2298		}
2299
2300		Self {
2301			id,
2302			nodes,
2303			root,
2304			state,
2305			stats,
2306			guards: HashMap::new(),
2307		}
2308	}
2309
2310	/// Drive the egress announce guards and tag the broadcast for one update.
2311	///
2312	/// An announce opens a guard (keyed by absolute path) and tags the yielded
2313	/// broadcast with the egress scope; an unannounce drops the guard. A no-op for
2314	/// an untagged stream.
2315	fn attribute(&mut self, update: OriginAnnounce) -> OriginAnnounce {
2316		let OriginAnnounce { path, broadcast } = update;
2317		let absolute = self.root.join(&path).to_owned();
2318		match broadcast {
2319			Some(broadcast) => {
2320				let scope = self.stats.egress(&absolute);
2321				self.guards.entry(absolute).or_insert_with(|| scope.announce());
2322				OriginAnnounce {
2323					path,
2324					broadcast: Some(broadcast.with_stats(scope)),
2325				}
2326			}
2327			None => {
2328				self.guards.remove(&absolute);
2329				OriginAnnounce { path, broadcast: None }
2330			}
2331		}
2332	}
2333
2334	/// Returns the next (un)announced broadcast and its path relative to this
2335	/// cursor's root.
2336	///
2337	/// The broadcast will only be announced if it was previously unannounced.
2338	/// The same path won't be announced/unannounced twice in a row; instead it
2339	/// toggles. Returns None if the cursor is closed.
2340	pub async fn next(&mut self) -> Option<OriginAnnounce> {
2341		kio::wait(|waiter| self.poll_next(waiter)).await
2342	}
2343
2344	/// Poll for the next (un)announced broadcast, without blocking.
2345	///
2346	/// Returns `Poll::Ready(Some(_))` for an update, `Poll::Ready(None)` if the
2347	/// cursor is closed, or `Poll::Pending` after registering `waiter` to be
2348	/// notified when the next update arrives.
2349	pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<OriginAnnounce>> {
2350		let update = {
2351			let mut state = match ready!(self.state.poll(waiter, |state| {
2352				if state.pending.is_empty() {
2353					Poll::Pending
2354				} else {
2355					Poll::Ready(())
2356				}
2357			})) {
2358				Ok(state) => state,
2359				// Closed: discard the Ref so its MutexGuard doesn't escape this call.
2360				Err(_) => return Poll::Ready(None),
2361			};
2362			state.take().expect("predicate guaranteed an update")
2363		};
2364		Poll::Ready(Some(self.attribute(update)))
2365	}
2366
2367	/// Returns the next (un)announced broadcast without blocking.
2368	///
2369	/// Returns None if there is no update available; NOT because the cursor is closed.
2370	/// Use [`Self::is_closed`] to check if the cursor is closed.
2371	pub fn try_next(&mut self) -> Option<OriginAnnounce> {
2372		let update = self.state.write().ok()?.take()?;
2373		Some(self.attribute(update))
2374	}
2375
2376	/// Returns true if the cursor is closed (no more updates will arrive).
2377	pub fn is_closed(&self) -> bool {
2378		self.state.write().is_err()
2379	}
2380
2381	/// Returns the prefix that is automatically stripped from emitted paths.
2382	pub fn root(&self) -> &Path<'_> {
2383		&self.root
2384	}
2385
2386	/// Converts a relative path to an absolute path.
2387	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
2388		self.root.join(path)
2389	}
2390}
2391
2392impl Drop for AnnounceConsumer {
2393	fn drop(&mut self) {
2394		for (_, root) in &self.nodes.nodes {
2395			root.lock().unconsume(self.id);
2396		}
2397	}
2398}
2399
2400#[cfg(test)]
2401use futures::FutureExt;
2402
2403#[cfg(test)]
2404#[allow(missing_docs)] // test-only assertion helpers
2405impl AnnounceConsumer {
2406	pub fn assert_next(&mut self, expected: impl AsPath, broadcast: &broadcast::Consumer) {
2407		let expected = expected.as_path();
2408		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2409		assert_eq!(announce.path, expected, "wrong path");
2410		let announced = announce.broadcast.expect("should be an active announce");
2411		assert!(announced.is_clone(broadcast), "should be the same broadcast");
2412	}
2413
2414	/// An announce for `expected`, without asserting which broadcast backs it
2415	/// (the origin owns the announced broadcast, not the publisher). Returns the
2416	/// announced consumer.
2417	pub fn assert_next_some(&mut self, expected: impl AsPath) -> broadcast::Consumer {
2418		let expected = expected.as_path();
2419		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2420		assert_eq!(announce.path, expected, "wrong path");
2421		announce.broadcast.expect("should be an active announce")
2422	}
2423
2424	pub fn assert_try_next(&mut self, expected: impl AsPath, broadcast: &broadcast::Consumer) {
2425		let expected = expected.as_path();
2426		let announce = self.try_next().expect("no next");
2427		assert_eq!(announce.path, expected, "wrong path");
2428		let announced = announce.broadcast.expect("should be an active announce");
2429		assert!(announced.is_clone(broadcast), "should be the same broadcast");
2430	}
2431
2432	/// The `try_next` counterpart of [`Self::assert_next_some`].
2433	pub fn assert_try_next_some(&mut self, expected: impl AsPath) -> broadcast::Consumer {
2434		let expected = expected.as_path();
2435		let announce = self.try_next().expect("no next");
2436		assert_eq!(announce.path, expected, "wrong path");
2437		announce.broadcast.expect("should be an active announce")
2438	}
2439
2440	pub fn assert_next_none(&mut self, expected: impl AsPath) {
2441		let expected = expected.as_path();
2442		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2443		assert_eq!(announce.path, expected, "wrong path");
2444		assert!(announce.broadcast.is_none(), "should be unannounced");
2445	}
2446
2447	pub fn assert_next_wait(&mut self) {
2448		if let Some(res) = self.next().now_or_never() {
2449			panic!("next should block: got {:?}", res.map(|a| a.path));
2450		}
2451	}
2452
2453	/*
2454	pub fn assert_next_closed(&mut self) {
2455		assert!(
2456			self.next().now_or_never().expect("next blocked").is_none(),
2457			"next should be closed"
2458		);
2459	}
2460	*/
2461}
2462
2463#[cfg(test)]
2464mod tests {
2465	use crate::coding::Decode;
2466	use crate::group;
2467
2468	use super::*;
2469
2470	/// An announced direct route.
2471	fn announce() -> broadcast::Route {
2472		broadcast::Route::new().with_announce(true)
2473	}
2474
2475	/// The first origin whose handover key for `name` sits above (`true`) or below
2476	/// (`false`) the peer's, so tests exercising the carrying gate are
2477	/// deterministic instead of hinging on a random id winning a hash comparison.
2478	/// Starts searching above the small ids the tests use in hop chains, so the
2479	/// result never collides with a hop (a looping chain trips a debug_assert).
2480	fn origin_keyed(name: &str, peer: Origin, above: bool) -> Origin {
2481		let name = Path::new(name);
2482		let peer_key = fnv_key(&name, [peer]);
2483		(100u64..)
2484			.map(|id| Origin::new(id).unwrap())
2485			.find(|origin| (fnv_key(&name, [*origin]) > peer_key) == above)
2486			.unwrap()
2487	}
2488
2489	/// A front table for reselect tests: routes get ids in order, the first is
2490	/// the incumbent.
2491	fn front_state(self_origin: Origin, routes: Vec<broadcast::Route>) -> FrontState {
2492		let source = broadcast::Info::new().produce().consume();
2493		FrontState {
2494			path: Path::new("test").to_owned(),
2495			self_origin,
2496			next_route: routes.len() as u64,
2497			routes: routes
2498				.into_iter()
2499				.enumerate()
2500				.map(|(id, route)| FrontRoute {
2501					id: id as u64,
2502					route,
2503					source: source.clone(),
2504				})
2505				.collect(),
2506			active: Some(0),
2507			linger: Duration::ZERO,
2508			closed: false,
2509		}
2510	}
2511
2512	/// A route as a warm sibling would announce it: zero cost, chain ending at
2513	/// the announcing peer.
2514	fn sibling_route(peer: Origin) -> broadcast::Route {
2515		let hops = OriginList::try_from(vec![Origin::new(90).unwrap(), peer]).unwrap();
2516		announce().with_hops(hops)
2517	}
2518
2519	/// A route as the upstream announces it: priced, one hop.
2520	fn upstream_route(cost: u64) -> broadcast::Route {
2521		let hops = OriginList::try_from(vec![Origin::new(90).unwrap()]).unwrap();
2522		announce().with_hops(hops).with_cost(cost)
2523	}
2524
2525	/// While carrying, a strictly cheaper route from a peer that hashes above us
2526	/// must not displace the incumbent; the same table re-parents freely once
2527	/// idle, or when the peer hashes below us.
2528	#[test]
2529	fn test_carrying_gate_keys() {
2530		let peer = Origin::new(3).unwrap();
2531
2532		// We lose the key comparison: stay put while carrying, migrate when idle.
2533		let mut lost = front_state(
2534			origin_keyed("test", peer, false),
2535			vec![upstream_route(10), sibling_route(peer)],
2536		);
2537		lost.reselect(true);
2538		assert_eq!(
2539			lost.active,
2540			Some(0),
2541			"carrying front re-parented onto a higher-keyed peer"
2542		);
2543		lost.reselect(false);
2544		assert_eq!(lost.active, Some(1), "idle front must take the cheaper route");
2545
2546		// We win the key comparison: re-parent even while carrying.
2547		let mut won = front_state(
2548			origin_keyed("test", peer, true),
2549			vec![upstream_route(10), sibling_route(peer)],
2550		);
2551		won.reselect(true);
2552		assert_eq!(won.active, Some(1), "carrying front must follow a lower-keyed peer");
2553	}
2554
2555	/// The simultaneous-activation race: two relays that each pulled the same
2556	/// broadcast independently see each other's zero-cost route. Exactly one of
2557	/// them re-parents; the other keeps its upstream, so the broadcast is never
2558	/// left without a source.
2559	#[test]
2560	fn test_carrying_gate_symmetric_race() {
2561		let a = Origin::new(1).unwrap();
2562		let b = Origin::new(2).unwrap();
2563
2564		let mut a_view = front_state(a, vec![upstream_route(10), sibling_route(b)]);
2565		let mut b_view = front_state(b, vec![upstream_route(10), sibling_route(a)]);
2566		a_view.reselect(true);
2567		b_view.reselect(true);
2568
2569		let a_moved = a_view.active == Some(1);
2570		let b_moved = b_view.active == Some(1);
2571		assert!(
2572			a_moved != b_moved,
2573			"exactly one side must re-parent (a: {a_moved}, b: {b_moved})"
2574		);
2575	}
2576
2577	/// The gate is scoped to warm siblings: a cheaper route via a relay that is
2578	/// not itself carrying (advertised nonzero), or directly from the original
2579	/// publisher (single-hop chain), is taken immediately even while carrying
2580	/// and even when we would lose the key comparison.
2581	#[test]
2582	fn test_carrying_switches_to_benign_routes() {
2583		let peer = Origin::new(3).unwrap();
2584		let lost = origin_keyed("test", peer, false);
2585
2586		// A cheaper forwarder path: the relay advertised its accumulated cost.
2587		let mut forwarder = sibling_route(peer).with_cost(4);
2588		forwarder.advertised = 4;
2589		let mut state = front_state(lost, vec![upstream_route(10), forwarder]);
2590		state.reselect(true);
2591		assert_eq!(
2592			state.active,
2593			Some(1),
2594			"a cheaper forwarder path must win while carrying"
2595		);
2596
2597		// Directly from the original publisher: single-hop chain, advertised zero.
2598		let direct = announce().with_hops(OriginList::try_from(vec![peer]).unwrap());
2599		let mut state = front_state(lost, vec![upstream_route(10), direct]);
2600		state.reselect(true);
2601		assert_eq!(
2602			state.active,
2603			Some(1),
2604			"a direct publisher route must win while carrying"
2605		);
2606	}
2607
2608	/// The gate only protects an announced incumbent: one that lost its announce
2609	/// (the upstream retracted) is displaced regardless of the key comparison.
2610	#[test]
2611	fn test_carrying_gate_ignores_unannounced_incumbent() {
2612		let peer = Origin::new(3).unwrap();
2613		let unannounced = upstream_route(10).with_announce(false);
2614		let mut state = front_state(
2615			origin_keyed("test", peer, false),
2616			vec![unannounced, sibling_route(peer)],
2617		);
2618		state.reselect(true);
2619		assert_eq!(
2620			state.active,
2621			Some(1),
2622			"an unannounced incumbent must always be displaced"
2623		);
2624	}
2625
2626	/// Let the spawned origin tasks (source watchers, front dispatch) run. The
2627	/// tests pause tokio time, so this advances the clock instantly.
2628	async fn settle() {
2629		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2630	}
2631
2632	/// Serve one requested track from a source like a session would: wait for the
2633	/// origin to dispatch it, then accept with default info.
2634	async fn accept_track(dynamic: &mut broadcast::Dynamic, name: &str) -> track::Producer {
2635		let request = tokio::time::timeout(std::time::Duration::from_secs(1), dynamic.requested_track())
2636			.await
2637			.expect("timed out waiting for a track request")
2638			.expect("source closed");
2639		assert_eq!(request.name(), name, "unexpected track dispatched");
2640		request.accept(None)
2641	}
2642
2643	/// Tagging both origin handles with one context attributes the full model path:
2644	/// ingress writes on the subscriber side, egress reads on the publisher side,
2645	/// each counter landing exactly once (the model-layer silent-zero guard).
2646	#[tokio::test]
2647	async fn test_stats_tagged_end_to_end() {
2648		use crate::Timestamp;
2649		use crate::stats::{Config, Registry, Tier};
2650		use bytes::Bytes;
2651
2652		tokio::time::pause();
2653
2654		let registry = Registry::new(Config::new());
2655		let ctx = registry.tier(Tier::default()).session("acme");
2656
2657		let origin = Origin::random().produce();
2658		let ingress = origin.clone().with_stats(ctx.clone());
2659		let egress = origin.consume().with_stats(ctx.clone());
2660
2661		// Egress announce stream: this is the tagged stream that drives the egress
2662		// announce guard.
2663		let mut announced = egress.announced();
2664
2665		// Ingress publishes an announced broadcast.
2666		let source = ingress.create_broadcast("demo", announce()).unwrap();
2667		let mut dynamic = source.dynamic();
2668		settle().await;
2669		settle().await;
2670
2671		// Egress observes the announce and gets the tagged broadcast.
2672		let update = announced.next().await.unwrap();
2673		assert_eq!(update.path.as_str(), "demo");
2674		let broadcast = update.broadcast.unwrap();
2675
2676		// Egress subscribes; the ingress side serves the track on demand.
2677		let subscribing = broadcast.track("video").unwrap().subscribe(None);
2678		let mut producer = accept_track(&mut dynamic, "video").await;
2679		settle().await;
2680		let mut sub = subscribing.await.unwrap();
2681
2682		// Ingress writes one group with two 5-byte frames.
2683		let mut group = producer.append_group().unwrap();
2684		group
2685			.write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
2686			.unwrap();
2687		group
2688			.write_frame(Timestamp::ZERO, Bytes::from_static(b"world"))
2689			.unwrap();
2690		group.finish().unwrap();
2691
2692		// Egress reads the group and both frames.
2693		let mut group_c = sub.recv_group().await.unwrap().unwrap();
2694		let mut frames = 0;
2695		while let Some(frame) = group_c.read_frame().await.unwrap() {
2696			assert_eq!(frame.payload.len(), 5);
2697			frames += 1;
2698		}
2699		assert_eq!(frames, 2);
2700		settle().await;
2701
2702		let report = registry.report();
2703		let entry = report
2704			.traffic
2705			.iter()
2706			.find(|e| e.path.as_str() == "demo")
2707			.expect("demo tracked");
2708		let path_len = "demo".len() as u64;
2709
2710		// Egress (publisher side): reads out of the model.
2711		let egress = &entry.publisher;
2712		assert_eq!(egress.announced, 1, "one egress announce");
2713		assert_eq!(egress.announced_bytes, path_len);
2714		assert_eq!(egress.subscriptions, 1, "one egress subscription");
2715		assert_eq!(egress.broadcasts, 1, "one viewer");
2716		assert_eq!(egress.groups, 1);
2717		assert_eq!(egress.frames, 2);
2718		assert_eq!(egress.bytes, 10);
2719		assert_eq!(egress.fetches, 0);
2720
2721		// Ingress (subscriber side): writes into the model.
2722		let ingress = &entry.subscriber;
2723		assert_eq!(ingress.announced, 1, "one ingress announce");
2724		assert_eq!(ingress.announced_bytes, path_len);
2725		assert_eq!(ingress.subscriptions, 1, "one ingress track");
2726		assert_eq!(ingress.broadcasts, 0, "ingress has no viewer refcount");
2727		assert_eq!(ingress.groups, 1);
2728		assert_eq!(ingress.frames, 2);
2729		assert_eq!(ingress.bytes, 10);
2730
2731		// A fetch bumps only `fetches` on the egress side, plus the delivered group.
2732		let fetched = broadcast.track("video").unwrap().fetch_group(0, None).await.unwrap();
2733		let _ = fetched;
2734		settle().await;
2735		let report = registry.report();
2736		let entry = report.traffic.iter().find(|e| e.path.as_str() == "demo").unwrap();
2737		assert_eq!(entry.publisher.fetches, 1, "one fetch");
2738		assert_eq!(entry.publisher.subscriptions, 1, "fetch does not bump subscriptions");
2739		assert_eq!(entry.publisher.broadcasts, 1, "fetch does not bump the viewer refcount");
2740		// `fetches` is egress-only for the same structural reason as `broadcasts`:
2741		// only a `track::Consumer` can fetch, and the ingress scope never reaches one
2742		// (`broadcast::Producer::consume` hands out an untagged consumer).
2743		assert_eq!(entry.subscriber.fetches, 0, "ingress cannot fetch");
2744	}
2745
2746	/// `Subscriber::read_frame` collapses a group to its first frame. The paths it
2747	/// delegates to (plain and spliced) build their own *unmetered* group consumers,
2748	/// so the wrapper is the only place that can attribute the read: exactly one
2749	/// group, one frame, and the payload bytes, counted once each.
2750	#[tokio::test]
2751	async fn test_stats_read_frame_counts_once() {
2752		use crate::Timestamp;
2753		use crate::stats::{Config, Registry, Tier};
2754		use bytes::Bytes;
2755
2756		tokio::time::pause();
2757
2758		let registry = Registry::new(Config::new());
2759		let ctx = registry.tier(Tier::default()).session("acme");
2760
2761		let origin = Origin::random().produce();
2762		let ingress = origin.clone().with_stats(ctx.clone());
2763		let egress = origin.consume().with_stats(ctx.clone());
2764
2765		let mut announced = egress.announced();
2766		let source = ingress.create_broadcast("demo", announce()).unwrap();
2767		let mut dynamic = source.dynamic();
2768		settle().await;
2769		settle().await;
2770
2771		let broadcast = announced.next().await.unwrap().broadcast.unwrap();
2772		let subscribing = broadcast.track("video").unwrap().subscribe(None);
2773		let mut producer = accept_track(&mut dynamic, "video").await;
2774		settle().await;
2775		let mut sub = subscribing.await.unwrap();
2776
2777		// A single-frame group, read back through the collapsing helper.
2778		producer
2779			.write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
2780			.unwrap();
2781
2782		let frame = sub.read_frame().await.unwrap().expect("frame");
2783		assert_eq!(frame.payload.len(), 5);
2784		settle().await;
2785
2786		let report = registry.report();
2787		let entry = report
2788			.traffic
2789			.iter()
2790			.find(|e| e.path.as_str() == "demo")
2791			.expect("demo tracked");
2792		assert_eq!(entry.publisher.groups, 1, "one group, counted once");
2793		assert_eq!(entry.publisher.frames, 1, "one frame, counted once");
2794		assert_eq!(
2795			entry.publisher.bytes, 5,
2796			"payload counted once, not zero and not doubled"
2797		);
2798	}
2799
2800	/// Datagrams bypass the group/frame handles entirely, so they're metered at the
2801	/// producer (ingress write) and the subscriber (egress read). Each one counts as
2802	/// the single-frame group it stands in for, plus the `datagrams` breakout.
2803	#[tokio::test]
2804	async fn test_stats_datagrams_counted_both_sides() {
2805		use crate::Timestamp;
2806		use crate::stats::{Config, Registry, Tier};
2807
2808		tokio::time::pause();
2809
2810		let registry = Registry::new(Config::new());
2811		let ctx = registry.tier(Tier::default()).session("acme");
2812
2813		let origin = Origin::random().produce();
2814		let ingress = origin.clone().with_stats(ctx.clone());
2815		let egress = origin.consume().with_stats(ctx.clone());
2816
2817		let mut announced = egress.announced();
2818		let source = ingress.create_broadcast("demo", announce()).unwrap();
2819		let mut dynamic = source.dynamic();
2820		settle().await;
2821		settle().await;
2822
2823		let broadcast = announced.next().await.unwrap().broadcast.unwrap();
2824		let subscribing = broadcast.track("video").unwrap().subscribe(None);
2825		let mut producer = accept_track(&mut dynamic, "video").await;
2826		settle().await;
2827		let mut sub = subscribing.await.unwrap();
2828
2829		producer.append_datagram(Timestamp::ZERO, &b"hello"[..]).unwrap();
2830		let datagram = sub.recv_datagram().await.unwrap().expect("datagram");
2831		assert_eq!(&datagram.payload[..], b"hello");
2832		settle().await;
2833
2834		let report = registry.report();
2835		let entry = report
2836			.traffic
2837			.iter()
2838			.find(|e| e.path.as_str() == "demo")
2839			.expect("demo tracked");
2840
2841		for (side, traffic) in [("egress", &entry.publisher), ("ingress", &entry.subscriber)] {
2842			assert_eq!(traffic.datagrams, 1, "{side}: one datagram");
2843			assert_eq!(traffic.groups, 1, "{side}: counted as its single-frame group");
2844			assert_eq!(traffic.frames, 1, "{side}: one frame");
2845			assert_eq!(traffic.bytes, 5, "{side}: payload counted once");
2846		}
2847	}
2848
2849	#[test]
2850	fn origin_rejects_reserved_ids() {
2851		assert!(Origin::new(0).is_err());
2852		assert!(Origin::new(1u64 << 62).is_err());
2853		assert_eq!(Origin::new(1).unwrap().id(), 1);
2854
2855		let mut zero = [0u8].as_slice();
2856		assert_eq!(
2857			Origin::decode(&mut zero, crate::lite::Version::Lite05).unwrap(),
2858			Origin::UNKNOWN
2859		);
2860	}
2861
2862	#[test]
2863	fn origin_list_push_fails_at_limit() {
2864		let mut list = OriginList::new();
2865		for _ in 0..MAX_HOPS {
2866			list.push(Origin::random()).unwrap();
2867		}
2868		assert_eq!(list.len(), MAX_HOPS);
2869		assert_eq!(list.push(Origin::random()), Err(TooManyOrigins));
2870	}
2871
2872	#[test]
2873	fn origin_list_replace_first() {
2874		let mut list = OriginList::new();
2875		for _ in 0..3 {
2876			list.push(Origin::UNKNOWN).unwrap();
2877		}
2878
2879		// Rewrites only the first placeholder, keeping the length the same.
2880		assert!(list.replace_first(Origin::UNKNOWN, Origin::new(7).unwrap()));
2881		assert_eq!(
2882			list.as_slice(),
2883			&[Origin::new(7).unwrap(), Origin::UNKNOWN, Origin::UNKNOWN]
2884		);
2885
2886		// No match leaves the list untouched.
2887		assert!(!list.replace_first(Origin::new(99).unwrap(), Origin::new(8).unwrap()));
2888		assert_eq!(list.len(), 3);
2889	}
2890
2891	#[test]
2892	fn origin_list_try_from_vec_enforces_limit() {
2893		let under: Vec<Origin> = (0..MAX_HOPS).map(|_| Origin::random()).collect();
2894		assert!(OriginList::try_from(under).is_ok());
2895
2896		let over: Vec<Origin> = (0..MAX_HOPS + 1).map(|_| Origin::random()).collect();
2897		assert_eq!(OriginList::try_from(over), Err(TooManyOrigins));
2898	}
2899
2900	#[tokio::test]
2901	async fn test_announce() {
2902		tokio::time::pause();
2903
2904		let origin = Origin::random().produce();
2905
2906		let mut consumer1 = origin.consume().announced();
2907		consumer1.assert_next_wait();
2908
2909		// Publish the first broadcast; it becomes visible asynchronously.
2910		let mut broadcast1 = origin.create_broadcast("test1", announce()).unwrap();
2911		settle().await;
2912
2913		consumer1.assert_next_some("test1");
2914		consumer1.assert_next_wait();
2915
2916		// Make a new consumer that should get the existing broadcast.
2917		// But we don't consume it yet.
2918		let mut consumer2 = origin.consume().announced();
2919
2920		// Publish the second broadcast.
2921		let mut broadcast2 = origin.create_broadcast("test2", announce()).unwrap();
2922		settle().await;
2923
2924		consumer1.assert_next_some("test2");
2925		consumer1.assert_next_wait();
2926
2927		consumer2.assert_next_some("test1");
2928		consumer2.assert_next_some("test2");
2929		consumer2.assert_next_wait();
2930
2931		// Finish the first broadcast: a graceful end unannounces immediately.
2932		broadcast1.finish();
2933		settle().await;
2934
2935		// All consumers should get a None now.
2936		consumer1.assert_next_none("test1");
2937		consumer2.assert_next_none("test1");
2938		consumer1.assert_next_wait();
2939		consumer2.assert_next_wait();
2940
2941		// And a new consumer only gets the last broadcast.
2942		let mut consumer3 = origin.consume().announced();
2943		consumer3.assert_next_some("test2");
2944		consumer3.assert_next_wait();
2945
2946		broadcast2.finish();
2947		settle().await;
2948
2949		consumer1.assert_next_none("test2");
2950		consumer2.assert_next_none("test2");
2951		consumer3.assert_next_none("test2");
2952	}
2953
2954	/// Multiple sources created at one path feed a single origin-owned broadcast:
2955	/// one announce, no churn as sources come and go, and an unannounce only when
2956	/// the last source leaves.
2957	#[tokio::test]
2958	async fn test_duplicate() {
2959		tokio::time::pause();
2960
2961		let origin = Origin::random().produce();
2962		let consumer = origin.consume();
2963		let mut announced = consumer.announced();
2964
2965		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
2966		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
2967		let mut broadcast3 = origin.create_broadcast("test", announce()).unwrap();
2968		settle().await;
2969		assert!(consumer.get_broadcast("test").is_some());
2970
2971		announced.assert_next_some("test");
2972		announced.assert_next_wait();
2973
2974		// A standby source finishing changes nothing.
2975		broadcast2.finish();
2976		settle().await;
2977		assert!(consumer.get_broadcast("test").is_some());
2978		announced.assert_next_wait();
2979
2980		// The active source finishing hands over to a survivor, invisibly.
2981		broadcast1.finish();
2982		settle().await;
2983		assert!(consumer.get_broadcast("test").is_some());
2984		announced.assert_next_wait();
2985
2986		// The last source finishing unannounces and removes the broadcast.
2987		broadcast3.finish();
2988		settle().await;
2989		assert!(consumer.get_broadcast("test").is_none());
2990
2991		announced.assert_next_none("test");
2992		announced.assert_next_wait();
2993	}
2994
2995	/// A source dying mid-serve fails over: the track re-splices from the standby
2996	/// source and resumes exactly at the first missing group.
2997	#[tokio::test]
2998	async fn test_route_failover() {
2999		tokio::time::pause();
3000
3001		let origin = Origin::random().produce();
3002		let consumer = origin.consume();
3003		let mut announced = consumer.announced();
3004
3005		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3006		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3007
3008		// The first source announces the broadcast.
3009		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
3010		let mut dynamic_a = source_a.dynamic();
3011		settle().await;
3012		settle().await;
3013		let broadcast = consumer.request_broadcast("test").await.unwrap();
3014		announced.assert_next_some("test");
3015
3016		// A second (longer) source joins silently as a standby.
3017		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
3018		let mut dynamic_b = source_b.dynamic();
3019		settle().await;
3020		settle().await;
3021		announced.assert_next_wait();
3022
3023		// Subscribing dispatches the track to the best source (A).
3024		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3025		let mut producer = accept_track(&mut dynamic_a, "video").await;
3026		settle().await;
3027		dynamic_b.assert_no_request();
3028
3029		let mut sub = subscribing.await.unwrap();
3030		// Demand registers as the subscriber polls; a fresh segment carries no
3031		// boundary, so the demand is the subscriber's own.
3032		sub.assert_no_group();
3033		assert_eq!(producer.subscription().unwrap().group_start, None);
3034
3035		producer.append_group().unwrap();
3036		producer.append_group().unwrap();
3037		assert_eq!(sub.assert_group().sequence, 0);
3038		assert_eq!(sub.assert_group().sequence, 1);
3039
3040		// Source A dies (session loss): the track re-splices from B and nothing
3041		// is announced.
3042		// abort() consumes the producer, so this both aborts and drops it.
3043		producer.abort(Error::Dropped).unwrap();
3044		source_a.abort(Error::Dropped).unwrap();
3045		drop(dynamic_a);
3046		settle().await;
3047		announced.assert_next_wait();
3048
3049		// The new copy resumes one past the spliced groups: its demand starts at
3050		// the boundary, and groups the old source already delivered are filtered.
3051		let mut producer = accept_track(&mut dynamic_b, "video").await;
3052		settle().await;
3053		sub.assert_no_group();
3054		assert_eq!(producer.subscription().unwrap().group_start, Some(2));
3055		producer.create_group(group::Info { sequence: 1 }).unwrap();
3056		producer.create_group(group::Info { sequence: 2 }).unwrap();
3057		assert_eq!(sub.assert_group().sequence, 2, "groups below the boundary are filtered");
3058		sub.assert_not_closed();
3059	}
3060
3061	/// `route_changed` yields the current route first, then each change; equal
3062	/// updates coalesce, and the watch errors once every producer is gone.
3063	#[tokio::test]
3064	async fn test_broadcast_route_watch() {
3065		let mut producer = broadcast::Info::new().produce();
3066		let mut consumer = producer.consume();
3067
3068		// Initial value: the default route.
3069		assert_eq!(consumer.route_changed().await.unwrap(), broadcast::Route::default());
3070
3071		// An equal update is a no-op.
3072		producer.set_route(broadcast::Route::default()).unwrap();
3073		assert!(consumer.route_changed().now_or_never().is_none());
3074
3075		let mut hops = OriginList::new();
3076		hops.push(Origin::new(7).unwrap()).unwrap();
3077		let route = broadcast::Route::new().with_hops(hops).with_cost(3);
3078		producer.set_route(route.clone()).unwrap();
3079		assert_eq!(consumer.route_changed().await.unwrap(), route);
3080
3081		// A fresh consumer sees the current value immediately.
3082		let mut fresh = producer.consume();
3083		assert_eq!(fresh.route_changed().await.unwrap(), route);
3084
3085		drop(producer);
3086		assert!(matches!(consumer.route_changed().await.unwrap_err(), Error::Dropped));
3087	}
3088
3089	/// A cost update that flips the winning source hands live tracks over at a
3090	/// group boundary and re-advertises the broadcast's route, without announce
3091	/// churn.
3092	#[tokio::test]
3093	async fn test_route_cost_update() {
3094		tokio::time::pause();
3095
3096		// The takeover happens while a subscriber is live (carrying), so the local
3097		// origin must win the handover key comparison against B's announcing hop
3098		// (origin 3); a random id would flake on the hash.
3099		let origin = Info::new(origin_keyed("test", Origin::new(3).unwrap(), true)).produce();
3100		let consumer = origin.consume();
3101		let mut announced = consumer.announced();
3102
3103		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3104		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3105
3106		// A (shorter chain) wins at equal cost.
3107		let mut source_a = origin
3108			.create_broadcast("test", announce().with_hops(hops_a.clone()))
3109			.unwrap();
3110		let mut dynamic_a = source_a.dynamic();
3111		settle().await;
3112		let broadcast = consumer.request_broadcast("test").await.unwrap();
3113		announced.assert_next_some("test");
3114
3115		let mut watch = broadcast.clone();
3116		assert_eq!(watch.route_changed().await.unwrap().hops, hops_a);
3117
3118		let mut source_b = origin
3119			.create_broadcast("test", announce().with_hops(hops_b.clone()))
3120			.unwrap();
3121		let mut dynamic_b = source_b.dynamic();
3122		settle().await;
3123		assert!(
3124			watch.route_changed().now_or_never().is_none(),
3125			"a losing standby must not change the advertised route"
3126		);
3127
3128		// Dispatch the track to A and deliver a group.
3129		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3130		let mut producer = accept_track(&mut dynamic_a, "video").await;
3131		settle().await;
3132		let mut sub = subscribing.await.unwrap();
3133		producer.append_group().unwrap();
3134		assert_eq!(sub.assert_group().sequence, 0);
3135
3136		// A's cost rises above B's: B takes over at the boundary and the
3137		// broadcast re-advertises B's route. No announce events.
3138		source_a
3139			.set_route(announce().with_hops(hops_a.clone()).with_cost(10))
3140			.unwrap();
3141		settle().await;
3142		assert_eq!(watch.route_changed().await.unwrap().hops, hops_b);
3143		announced.assert_next_wait();
3144
3145		let mut producer_b = accept_track(&mut dynamic_b, "video").await;
3146		settle().await;
3147		// Demand registers as the subscriber polls; the new segment starts at the
3148		// splice boundary.
3149		sub.assert_no_group();
3150		assert_eq!(producer_b.subscription().unwrap().group_start, Some(1));
3151		producer_b.create_group(group::Info { sequence: 1 }).unwrap();
3152		assert_eq!(sub.assert_group().sequence, 1);
3153		sub.assert_not_closed();
3154
3155		// The active source updating its own metadata re-advertises in place.
3156		source_b
3157			.set_route(announce().with_hops(hops_b.clone()).with_cost(5))
3158			.unwrap();
3159		settle().await;
3160		let advertised = watch.route_changed().await.unwrap();
3161		assert_eq!(advertised.hops, hops_b);
3162		assert_eq!(advertised.cost, 5);
3163		announced.assert_next_wait();
3164	}
3165
3166	/// A track completed for good must survive later source churn: it is never
3167	/// re-dispatched, and late subscribers still see a clean end.
3168	#[tokio::test]
3169	async fn test_completed_track_survives_route_churn() {
3170		tokio::time::pause();
3171
3172		let origin = Origin::random().produce();
3173		let consumer = origin.consume();
3174
3175		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3176		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3177
3178		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
3179		let mut dynamic_a = source_a.dynamic();
3180		settle().await;
3181		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
3182		let mut dynamic_b = source_b.dynamic();
3183		settle().await;
3184		settle().await;
3185		let broadcast = consumer.request_broadcast("test").await.unwrap();
3186
3187		// Serve the track via A and end it for good.
3188		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3189		let mut producer = accept_track(&mut dynamic_a, "video").await;
3190		settle().await;
3191		let mut sub = subscribing.await.unwrap();
3192		producer.append_group().unwrap();
3193		assert_eq!(sub.assert_group().sequence, 0);
3194		producer.finish().unwrap();
3195		drop(producer);
3196		settle().await;
3197		sub.assert_closed();
3198
3199		// A detaching must not re-dispatch the finished track to B.
3200		source_a.abort(Error::Dropped).unwrap();
3201		drop(dynamic_a);
3202		settle().await;
3203		dynamic_b.assert_no_request();
3204
3205		// A late subscriber sees the same clean end, not an abort.
3206		let mut late = broadcast.track("video").unwrap().subscribe(None).await.unwrap();
3207		late.assert_closed();
3208	}
3209
3210	/// A successful splice resets the retry budget: transient pre-splice failures
3211	/// spread over a track's lifetime never accumulate into an abort.
3212	#[tokio::test]
3213	async fn test_serve_resets_retry_budget() {
3214		tokio::time::pause();
3215
3216		let origin = Origin::random().produce();
3217		let consumer = origin.consume();
3218
3219		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3220		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3221		let mut dynamic = source.dynamic();
3222		settle().await;
3223		settle().await;
3224		let broadcast = consumer.request_broadcast("test").await.unwrap();
3225
3226		// Queue the track; the subscription resolves once a serve finally sticks.
3227		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3228
3229		// Alternate a pre-splice failure with a successful splice, well past the
3230		// retry cap; the resets keep the track alive.
3231		for _ in 0..2 * MAX_TRACK_RETRIES {
3232			let request = tokio::time::timeout(std::time::Duration::from_secs(1), dynamic.requested_track())
3233				.await
3234				.expect("timed out waiting for a retry")
3235				.unwrap();
3236			request.reject(Error::NotFound);
3237			let producer = accept_track(&mut dynamic, "video").await;
3238			settle().await;
3239			drop(producer);
3240		}
3241
3242		let _producer = accept_track(&mut dynamic, "video").await;
3243		settle().await;
3244		let mut sub = subscribing.await.unwrap();
3245		sub.assert_not_closed();
3246	}
3247
3248	/// A better source attaching mid-subscription takes the track over at an
3249	/// explicit group boundary: the old copy's demand is capped, the new copy
3250	/// starts at the boundary, and the subscriber reads a seamless sequence.
3251	#[tokio::test]
3252	async fn test_route_handover() {
3253		tokio::time::pause();
3254
3255		let origin = Origin::random().produce();
3256		let consumer = origin.consume();
3257		let mut announced = consumer.announced();
3258
3259		let hops_long = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3260		let hops_short = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3261
3262		let source_a = origin
3263			.create_broadcast("test", announce().with_hops(hops_long))
3264			.unwrap();
3265		let mut dynamic_a = source_a.dynamic();
3266		settle().await;
3267		settle().await;
3268		let broadcast = consumer.request_broadcast("test").await.unwrap();
3269		announced.assert_next_some("test");
3270
3271		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3272		let mut producer_a = accept_track(&mut dynamic_a, "video").await;
3273		settle().await;
3274		let mut sub = subscribing.await.unwrap();
3275		producer_a.append_group().unwrap();
3276		producer_a.append_group().unwrap();
3277		assert_eq!(sub.assert_group().sequence, 0);
3278		assert_eq!(sub.assert_group().sequence, 1);
3279
3280		// A strictly shorter source attaches: the live track is handed over with
3281		// no announce churn.
3282		let source_b = origin
3283			.create_broadcast("test", announce().with_hops(hops_short))
3284			.unwrap();
3285		let mut dynamic_b = source_b.dynamic();
3286		settle().await;
3287		settle().await;
3288		announced.assert_next_wait();
3289
3290		let mut producer_b = accept_track(&mut dynamic_b, "video").await;
3291		settle().await;
3292
3293		// The old copy's demand is capped at the boundary; the new copy's starts
3294		// there. Both propagate as the subscriber polls.
3295		sub.assert_no_group();
3296		assert_eq!(producer_a.subscription().unwrap().group_end, Some(1));
3297		assert_eq!(producer_b.subscription().unwrap().group_start, Some(2));
3298
3299		// The old copy racing past its cap is filtered; the new copy serves on.
3300		producer_a.create_group(group::Info { sequence: 2 }).unwrap();
3301		producer_b.create_group(group::Info { sequence: 2 }).unwrap();
3302		producer_b.create_group(group::Info { sequence: 3 }).unwrap();
3303		assert_eq!(sub.assert_group().sequence, 2);
3304		assert_eq!(sub.assert_group().sequence, 3);
3305		sub.assert_no_group();
3306		sub.assert_not_closed();
3307	}
3308
3309	/// A graceful detach (deliberate unannounce) closes immediately: no linger, so
3310	/// the unannounce propagates promptly and a re-create is a fresh broadcast.
3311	#[tokio::test(start_paused = true)]
3312	async fn test_route_unannounce_immediate() {
3313		let origin = Origin::random().produce();
3314		let consumer = origin.consume();
3315		let mut announced = consumer.announced();
3316
3317		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3318		let mut source = origin
3319			.create_broadcast("test", announce().with_hops(hops.clone()))
3320			.unwrap();
3321		settle().await;
3322		let broadcast = consumer.request_broadcast("test").await.unwrap();
3323		announced.assert_next_some("test");
3324
3325		// The peer deliberately unannounced: no reconnect window, the broadcast is
3326		// gone as soon as the teardown task observes the close.
3327		source.finish();
3328		settle().await;
3329		announced.assert_next_none("test");
3330
3331		// A re-create at the same path is a brand-new broadcast.
3332		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3333		settle().await;
3334		let fresh = consumer.request_broadcast("test").await.unwrap();
3335		announced.assert_next_some("test");
3336		assert!(
3337			!fresh.is_clone(&broadcast),
3338			"re-create must not splice the old broadcast"
3339		);
3340	}
3341
3342	/// With the default zero linger, a dying source (a session drop, not a
3343	/// deliberate unannounce) closes the broadcast just as promptly as a graceful
3344	/// one: no reconnect window, the tracks abort, and a re-create is a fresh
3345	/// broadcast rather than a splice.
3346	#[tokio::test(start_paused = true)]
3347	async fn test_route_detach_immediate() {
3348		let origin = Origin::random().produce();
3349		let consumer = origin.consume();
3350		let mut announced = consumer.announced();
3351
3352		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3353		let source = origin
3354			.create_broadcast("test", announce().with_hops(hops.clone()))
3355			.unwrap();
3356		let mut dynamic = source.dynamic();
3357		settle().await;
3358		settle().await;
3359		let broadcast = consumer.request_broadcast("test").await.unwrap();
3360		announced.assert_next_some("test");
3361
3362		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3363		let producer = accept_track(&mut dynamic, "video").await;
3364		settle().await;
3365		let mut sub = subscribing.await.unwrap();
3366
3367		// The session dies without unannouncing.
3368		drop(producer);
3369		source.abort(Error::Dropped).unwrap();
3370		drop(dynamic);
3371
3372		settle().await;
3373		announced.assert_next_none("test");
3374		sub.assert_error();
3375
3376		// A reconnecting session gets a brand-new broadcast, not a splice into
3377		// the old one.
3378		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3379		settle().await;
3380		settle().await;
3381		let fresh = consumer.request_broadcast("test").await.unwrap();
3382		announced.assert_next_some("test");
3383		assert!(
3384			!fresh.is_clone(&broadcast),
3385			"re-create must not splice the old broadcast"
3386		);
3387	}
3388
3389	/// With a linger configured, an ungraceful source loss keeps the broadcast
3390	/// alive and announced; a source re-attaching within the window splices in and
3391	/// consumers resume at the group boundary, never observing the outage.
3392	#[tokio::test(start_paused = true)]
3393	async fn test_linger_reconnect_splices() {
3394		let origin = Info::new(Origin::random())
3395			.with_linger(Duration::from_secs(5))
3396			.produce();
3397		let consumer = origin.consume();
3398		let mut announced = consumer.announced();
3399
3400		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3401		let source = origin
3402			.create_broadcast("test", announce().with_hops(hops.clone()))
3403			.unwrap();
3404		let mut dynamic = source.dynamic();
3405		settle().await;
3406		settle().await;
3407		let broadcast = consumer.request_broadcast("test").await.unwrap();
3408		announced.assert_next_some("test");
3409
3410		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3411		let mut producer = accept_track(&mut dynamic, "video").await;
3412		settle().await;
3413		let mut sub = subscribing.await.unwrap();
3414
3415		producer.append_group().unwrap();
3416		producer.append_group().unwrap();
3417		assert_eq!(sub.assert_group().sequence, 0);
3418		assert_eq!(sub.assert_group().sequence, 1);
3419
3420		// The session dies without unannouncing: the broadcast enters the linger
3421		// window instead of closing.
3422		drop(producer);
3423		source.abort(Error::Dropped).unwrap();
3424		drop(dynamic);
3425		settle().await;
3426
3427		// No unannounce, no track error: the outage is invisible so far.
3428		announced.assert_next_wait();
3429		sub.assert_no_group();
3430		sub.assert_not_closed();
3431
3432		// A consumer arriving mid-outage still resolves the lingering broadcast.
3433		let during = consumer.request_broadcast("test").await.unwrap();
3434		assert!(during.is_clone(&broadcast), "the lingering broadcast still resolves");
3435
3436		// The session reconnects within the window: the new source splices into
3437		// the same broadcast.
3438		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3439		let mut dynamic = source.dynamic();
3440		settle().await;
3441		settle().await;
3442		announced.assert_next_wait();
3443		let again = consumer.request_broadcast("test").await.unwrap();
3444		assert!(again.is_clone(&broadcast), "the reconnect must splice, not replace");
3445
3446		// The pending track re-splices from the new source and resumes one past
3447		// the groups the old source already delivered. Demand registers as the
3448		// subscriber polls.
3449		let mut producer = accept_track(&mut dynamic, "video").await;
3450		settle().await;
3451		sub.assert_no_group();
3452		assert_eq!(producer.subscription().unwrap().group_start, Some(2));
3453		producer.create_group(group::Info { sequence: 2 }).unwrap();
3454		assert_eq!(sub.assert_group().sequence, 2);
3455		sub.assert_not_closed();
3456	}
3457
3458	/// The linger window expiring without a replacement closes the broadcast: the
3459	/// path unannounces, tracks abort, and a later re-create is a fresh broadcast.
3460	#[tokio::test(start_paused = true)]
3461	async fn test_linger_expiry_closes() {
3462		let origin = Info::new(Origin::random())
3463			.with_linger(Duration::from_secs(5))
3464			.produce();
3465		let consumer = origin.consume();
3466		let mut announced = consumer.announced();
3467
3468		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3469		let source = origin
3470			.create_broadcast("test", announce().with_hops(hops.clone()))
3471			.unwrap();
3472		let mut dynamic = source.dynamic();
3473		settle().await;
3474		settle().await;
3475		let broadcast = consumer.request_broadcast("test").await.unwrap();
3476		announced.assert_next_some("test");
3477
3478		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3479		let producer = accept_track(&mut dynamic, "video").await;
3480		settle().await;
3481		let mut sub = subscribing.await.unwrap();
3482
3483		drop(producer);
3484		source.abort(Error::Dropped).unwrap();
3485		drop(dynamic);
3486		settle().await;
3487		announced.assert_next_wait();
3488
3489		// Nobody comes back: the window expires and the broadcast closes.
3490		tokio::time::sleep(std::time::Duration::from_secs(6)).await;
3491		settle().await;
3492		announced.assert_next_none("test");
3493		sub.assert_error();
3494
3495		// A session reconnecting after the window gets a brand-new broadcast.
3496		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3497		settle().await;
3498		settle().await;
3499		let fresh = consumer.request_broadcast("test").await.unwrap();
3500		announced.assert_next_some("test");
3501		assert!(
3502			!fresh.is_clone(&broadcast),
3503			"a late re-create must not splice the expired broadcast"
3504		);
3505	}
3506
3507	/// A linger too large to represent as a deadline never expires: the broadcast
3508	/// outlives an arbitrarily long outage (a reconnect loop with no give-up
3509	/// timeout promises to retry forever).
3510	#[tokio::test(start_paused = true)]
3511	async fn test_linger_forever() {
3512		let origin = Info::new(Origin::random()).with_linger(Duration::MAX).produce();
3513		let consumer = origin.consume();
3514		let mut announced = consumer.announced();
3515
3516		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3517		let source = origin
3518			.create_broadcast("test", announce().with_hops(hops.clone()))
3519			.unwrap();
3520		settle().await;
3521		let broadcast = consumer.request_broadcast("test").await.unwrap();
3522		announced.assert_next_some("test");
3523
3524		source.abort(Error::Dropped).unwrap();
3525		settle().await;
3526
3527		// Days later the broadcast is still announced and still splices a reconnect.
3528		tokio::time::sleep(std::time::Duration::from_secs(60 * 60 * 24 * 3)).await;
3529		announced.assert_next_wait();
3530		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3531		settle().await;
3532		settle().await;
3533		let again = consumer.request_broadcast("test").await.unwrap();
3534		assert!(again.is_clone(&broadcast), "the reconnect must splice, not replace");
3535		drop(source);
3536	}
3537
3538	/// A deliberate finish never lingers, even with a linger configured: a clean
3539	/// unannounce from a peer must propagate immediately.
3540	#[tokio::test(start_paused = true)]
3541	async fn test_linger_skipped_on_finish() {
3542		let origin = Info::new(Origin::random())
3543			.with_linger(Duration::from_secs(5))
3544			.produce();
3545		let consumer = origin.consume();
3546		let mut announced = consumer.announced();
3547
3548		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3549		let mut source = origin
3550			.create_broadcast("test", announce().with_hops(hops.clone()))
3551			.unwrap();
3552		settle().await;
3553		let broadcast = consumer.request_broadcast("test").await.unwrap();
3554		announced.assert_next_some("test");
3555
3556		// A clean unannounce: the broadcast is gone as soon as the teardown task
3557		// observes the close, with no reconnect window.
3558		source.finish();
3559		settle().await;
3560		announced.assert_next_none("test");
3561
3562		// A re-create at the same path is a brand-new broadcast.
3563		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3564		settle().await;
3565		let fresh = consumer.request_broadcast("test").await.unwrap();
3566		announced.assert_next_some("test");
3567		assert!(
3568			!fresh.is_clone(&broadcast),
3569			"a finish must not leave a lingering broadcast to splice into"
3570		);
3571	}
3572
3573	/// A non-live broadcast is reachable by exact path but never announced;
3574	/// toggling `live` announces and unannounces without touching the broadcast.
3575	#[tokio::test]
3576	async fn test_announce_toggle() {
3577		tokio::time::pause();
3578
3579		let origin = Origin::random().produce();
3580		let consumer = origin.consume();
3581		let mut announced = consumer.announced();
3582
3583		let mut source = origin.create_broadcast("test", broadcast::Route::new()).unwrap();
3584		settle().await;
3585
3586		// Routable but not announced.
3587		announced.assert_next_wait();
3588		let broadcast = consumer
3589			.get_broadcast("test")
3590			.expect("offline broadcast is still routable");
3591		assert!(!broadcast.route().announce);
3592
3593		// request_broadcast resolves the offline broadcast too.
3594		let requested = consumer.request_broadcast("test").await.unwrap();
3595		assert!(requested.is_clone(&broadcast));
3596
3597		// Going live announces.
3598		source.set_route(announce()).unwrap();
3599		settle().await;
3600		let face = announced.assert_next_some("test");
3601		assert!(face.is_clone(&broadcast));
3602
3603		// A fresh consumer replays only announced broadcasts.
3604		let mut fresh = origin.consume().announced();
3605		fresh.assert_next_some("test");
3606		fresh.assert_next_wait();
3607
3608		// Going offline unannounces but stays routable.
3609		source.set_route(broadcast::Route::new()).unwrap();
3610		settle().await;
3611		announced.assert_next_none("test");
3612		assert!(consumer.get_broadcast("test").is_some());
3613		let mut fresh = origin.consume().announced();
3614		fresh.assert_next_wait();
3615
3616		source.finish();
3617		settle().await;
3618		assert!(consumer.get_broadcast("test").is_none());
3619	}
3620
3621	/// An announced source outranks a cheaper offline one, so the broadcast
3622	/// stays announced and serves from it.
3623	#[tokio::test]
3624	async fn test_announce_beats_offline() {
3625		tokio::time::pause();
3626
3627		let origin = Origin::random().produce();
3628		let consumer = origin.consume();
3629		let mut announced = consumer.announced();
3630
3631		// An unannounced source with the best cost.
3632		let _offline = origin.create_broadcast("test", broadcast::Route::new()).unwrap();
3633		settle().await;
3634		announced.assert_next_wait();
3635
3636		// An announced source with a worse cost still wins: the path announces
3637		// and advertises its route.
3638		let mut announced_source = origin.create_broadcast("test", announce().with_cost(10)).unwrap();
3639		settle().await;
3640		announced.assert_next_some("test");
3641		let face = consumer.get_broadcast("test").unwrap();
3642		assert!(face.route().announce);
3643		assert_eq!(face.route().cost, 10);
3644
3645		// The announced source leaving falls back to the offline one: the path
3646		// unannounces but stays routable.
3647		announced_source.finish();
3648		settle().await;
3649		announced.assert_next_none("test");
3650		assert!(consumer.get_broadcast("test").is_some());
3651	}
3652
3653	/// A better source attaching does not churn announces: the broadcast identity
3654	/// is origin-owned, so the swap is invisible to consumers.
3655	#[tokio::test]
3656	async fn test_better_source_no_churn() {
3657		tokio::time::pause();
3658
3659		let origin = Origin::random().produce();
3660		let mut announced = origin.consume().announced();
3661
3662		// `a` carries one hop; `b` has none, so `b` wins dispatch when it joins.
3663		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3664		let _a = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3665		settle().await;
3666		let face = announced.assert_next_some("test");
3667
3668		let _b = origin.create_broadcast("test", announce()).unwrap();
3669		settle().await;
3670		announced.assert_next_wait();
3671		let current = origin.consume().get_broadcast("test").unwrap();
3672		assert!(current.is_clone(&face), "the broadcast identity must not change");
3673		// The face now advertises the winning (hopless) route.
3674		assert!(current.route().hops.is_empty());
3675	}
3676
3677	#[tokio::test]
3678	async fn test_duplicate_reverse() {
3679		tokio::time::pause();
3680
3681		let origin = Origin::random().produce();
3682
3683		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
3684		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
3685		settle().await;
3686		assert!(origin.consume().get_broadcast("test").is_some());
3687
3688		// This is harder, finishing the newer source first.
3689		broadcast2.finish();
3690		settle().await;
3691		assert!(origin.consume().get_broadcast("test").is_some());
3692
3693		broadcast1.finish();
3694		settle().await;
3695		assert!(origin.consume().get_broadcast("test").is_none());
3696	}
3697
3698	#[tokio::test]
3699	async fn test_deterministic_tiebreak() {
3700		tokio::time::pause();
3701
3702		fn hops(ids: &[u64]) -> OriginList {
3703			OriginList::try_from(
3704				ids.iter()
3705					.copied()
3706					.map(|id| Origin::new(id).unwrap())
3707					.collect::<Vec<_>>(),
3708			)
3709			.unwrap()
3710		}
3711
3712		// Resolve the advertised route for "test" after creating both sources in
3713		// the given order.
3714		async fn winner(first: &[u64], second: &[u64]) -> OriginList {
3715			let origin = Origin::random().produce();
3716			let _a = origin
3717				.create_broadcast("test", announce().with_hops(hops(first)))
3718				.unwrap();
3719			let _b = origin
3720				.create_broadcast("test", announce().with_hops(hops(second)))
3721				.unwrap();
3722			settle().await;
3723			origin.consume().get_broadcast("test").unwrap().route().hops
3724		}
3725
3726		// Two routes with equal hop counts but distinct chains. The winner is decided by
3727		// the deterministic key, not arrival order, so both publish orders converge.
3728		let forward = winner(&[10, 20], &[30, 40]).await;
3729		let reverse = winner(&[30, 40], &[10, 20]).await;
3730		assert_eq!(forward, reverse, "tie-break must not depend on publish order");
3731
3732		// A strictly shorter chain always wins regardless of the hash.
3733		assert_eq!(winner(&[10, 20], &[30]).await.len(), 1);
3734		assert_eq!(winner(&[30], &[10, 20]).await.len(), 1);
3735	}
3736
3737	// A previous mpsc-based implementation could only deliver the first 127 broadcasts
3738	// instantly via `assert_next` (which uses `now_or_never`). The kio-backed
3739	// implementation polls synchronously and can deliver all of them without yielding.
3740	// Names are zero-padded so lexicographic delivery order matches the loop index.
3741	#[tokio::test]
3742	async fn test_many_announces() {
3743		let origin = Origin::random().produce();
3744
3745		let mut consumer = origin.consume().announced();
3746		// Held for the duration: a dropped source unannounces immediately.
3747		let mut broadcasts = Vec::new();
3748		for i in 0..256 {
3749			broadcasts.push(origin.create_broadcast(format!("test{i:03}"), announce()).unwrap());
3750			settle().await;
3751		}
3752
3753		for i in 0..256 {
3754			consumer.assert_next_some(format!("test{i:03}"));
3755		}
3756		consumer.assert_next_wait();
3757	}
3758
3759	#[tokio::test]
3760	async fn test_many_announces_try() {
3761		let origin = Origin::random().produce();
3762
3763		let mut consumer = origin.consume().announced();
3764		// Held for the duration: a dropped source unannounces immediately.
3765		let mut broadcasts = Vec::new();
3766		for i in 0..256 {
3767			broadcasts.push(origin.create_broadcast(format!("test{i:03}"), announce()).unwrap());
3768			settle().await;
3769		}
3770
3771		for i in 0..256 {
3772			consumer.assert_try_next_some(format!("test{i:03}"));
3773		}
3774	}
3775
3776	#[tokio::test]
3777	async fn test_with_root_basic() {
3778		let origin = Origin::random().produce();
3779
3780		// Create a producer with root "/foo"
3781		let foo_producer = origin.with_root("foo").expect("should create root");
3782		assert_eq!(foo_producer.root().as_str(), "foo");
3783
3784		let mut consumer = origin.consume().announced();
3785
3786		// When publishing to "bar/baz", it should actually publish to "foo/bar/baz"
3787		let _broadcast = foo_producer
3788			.create_broadcast("bar/baz", announce())
3789			.expect("publish allowed");
3790		settle().await;
3791		// The original consumer should see the full path
3792		consumer.assert_next_some("foo/bar/baz");
3793
3794		// A consumer created from the rooted producer should see the stripped path
3795		let mut foo_consumer = foo_producer.consume().announced();
3796		foo_consumer.assert_next_some("bar/baz");
3797	}
3798
3799	#[tokio::test]
3800	async fn test_with_root_nested() {
3801		let origin = Origin::random().produce();
3802
3803		// Create nested roots
3804		let foo_producer = origin.with_root("foo").expect("should create foo root");
3805		let foo_bar_producer = foo_producer.with_root("bar").expect("should create bar root");
3806		assert_eq!(foo_bar_producer.root().as_str(), "foo/bar");
3807
3808		let mut consumer = origin.consume().announced();
3809
3810		// Publishing to "baz" should actually publish to "foo/bar/baz"
3811		let _broadcast = foo_bar_producer
3812			.create_broadcast("baz", announce())
3813			.expect("publish allowed");
3814		settle().await;
3815		// The original consumer sees the full path
3816		consumer.assert_next_some("foo/bar/baz");
3817
3818		// Consumer from foo_bar_producer sees just "baz"
3819		let mut foo_bar_consumer = foo_bar_producer.consume().announced();
3820		foo_bar_consumer.assert_next_some("baz");
3821	}
3822
3823	#[tokio::test]
3824	async fn test_publish_scope_allows() {
3825		let origin = Origin::random().produce();
3826
3827		// Create a producer that can only publish to "allowed" paths
3828		let limited_producer = origin
3829			.scope(&["allowed/path1".into(), "allowed/path2".into()])
3830			.expect("should create limited producer");
3831
3832		// Should be able to publish to allowed paths
3833		let _broadcast = limited_producer
3834			.create_broadcast("allowed/path1", announce())
3835			.expect("publish allowed");
3836		let _keep2 = limited_producer
3837			.create_broadcast("allowed/path1/nested", announce())
3838			.expect("publish allowed");
3839		let _keep3 = limited_producer
3840			.create_broadcast("allowed/path2", announce())
3841			.expect("publish allowed");
3842		settle().await;
3843
3844		// Should not be able to publish to disallowed paths
3845		assert!(limited_producer.create_broadcast("notallowed", announce()).is_err());
3846		assert!(limited_producer.create_broadcast("allowed", announce()).is_err()); // Parent of allowed path
3847		assert!(limited_producer.create_broadcast("other/path", announce()).is_err());
3848	}
3849
3850	#[tokio::test]
3851	async fn test_publish_max_parts() {
3852		let origin = Origin::random().produce();
3853
3854		let at_limit = (0..Path::MAX_PARTS)
3855			.map(|i| i.to_string())
3856			.collect::<Vec<_>>()
3857			.join("/");
3858		let _broadcast = origin
3859			.create_broadcast(at_limit.as_str(), announce())
3860			.expect("publish allowed");
3861		settle().await;
3862
3863		let too_deep = format!("{at_limit}/extra");
3864		assert!(origin.create_broadcast(too_deep.as_str(), announce()).is_err());
3865
3866		// The root counts toward the limit; a joined path past 32 parts is rejected.
3867		let rooted = origin.with_root("root").expect("wildcard allows any root");
3868		assert!(rooted.create_broadcast(at_limit.as_str(), announce()).is_err());
3869	}
3870
3871	#[tokio::test]
3872	async fn test_publish_scope_empty() {
3873		let origin = Origin::random().produce();
3874
3875		// Creating a producer with no allowed paths should return None
3876		assert!(origin.scope(&[]).is_none());
3877	}
3878
3879	#[tokio::test]
3880	async fn test_consume_scope_filters() {
3881		let origin = Origin::random().produce();
3882
3883		let mut consumer = origin.consume().announced();
3884
3885		// Publish to different paths
3886		let _broadcast1 = origin.create_broadcast("allowed", announce()).unwrap();
3887		let _broadcast2 = origin.create_broadcast("allowed/nested", announce()).unwrap();
3888		let _broadcast3 = origin.create_broadcast("notallowed", announce()).unwrap();
3889		settle().await;
3890
3891		// Create a consumer that only sees "allowed" paths
3892		let mut limited_consumer = origin
3893			.consume()
3894			.scope(&["allowed".into()])
3895			.expect("should create limited consumer")
3896			.announced();
3897
3898		// Should only receive broadcasts under "allowed"
3899		limited_consumer.assert_next_some("allowed");
3900		limited_consumer.assert_next_some("allowed/nested");
3901		limited_consumer.assert_next_wait(); // Should not see "notallowed"
3902
3903		// Unscoped consumer should see all
3904		consumer.assert_next_some("allowed");
3905		consumer.assert_next_some("allowed/nested");
3906		consumer.assert_next_some("notallowed");
3907	}
3908
3909	#[tokio::test]
3910	async fn test_consume_scope_multiple_prefixes() {
3911		let origin = Origin::random().produce();
3912
3913		let _broadcast1 = origin.create_broadcast("foo/test", announce()).unwrap();
3914		let _broadcast2 = origin.create_broadcast("bar/test", announce()).unwrap();
3915		let _broadcast3 = origin.create_broadcast("baz/test", announce()).unwrap();
3916		settle().await;
3917
3918		// Consumer that only sees "foo" and "bar" paths
3919		let mut limited_consumer = origin
3920			.consume()
3921			.scope(&["foo".into(), "bar".into()])
3922			.expect("should create limited consumer")
3923			.announced();
3924
3925		// Order depends on PathPrefixes canonical sort (lexicographic for same length)
3926		limited_consumer.assert_next_some("bar/test");
3927		limited_consumer.assert_next_some("foo/test");
3928		limited_consumer.assert_next_wait(); // Should not see "baz/test"
3929	}
3930
3931	#[tokio::test]
3932	async fn test_with_root_and_publish_scope() {
3933		let origin = Origin::random().produce();
3934
3935		// User connects to /foo root
3936		let foo_producer = origin.with_root("foo").expect("should create foo root");
3937
3938		// Limit them to publish only to "bar" and "goop/pee" within /foo
3939		let limited_producer = foo_producer
3940			.scope(&["bar".into(), "goop/pee".into()])
3941			.expect("should create limited producer");
3942
3943		let mut consumer = origin.consume().announced();
3944
3945		// Should be able to publish to foo/bar and foo/goop/pee (but user sees as bar and goop/pee)
3946		let _broadcast = limited_producer
3947			.create_broadcast("bar", announce())
3948			.expect("publish allowed");
3949		let _keep2 = limited_producer
3950			.create_broadcast("bar/nested", announce())
3951			.expect("publish allowed");
3952		let _keep3 = limited_producer
3953			.create_broadcast("goop/pee", announce())
3954			.expect("publish allowed");
3955		let _keep4 = limited_producer
3956			.create_broadcast("goop/pee/nested", announce())
3957			.expect("publish allowed");
3958		settle().await;
3959
3960		// Should not be able to publish outside allowed paths
3961		assert!(limited_producer.create_broadcast("baz", announce()).is_err());
3962		assert!(limited_producer.create_broadcast("goop", announce()).is_err()); // Parent of allowed
3963		assert!(limited_producer.create_broadcast("goop/other", announce()).is_err());
3964
3965		// Original consumer sees full paths
3966		consumer.assert_next_some("foo/bar");
3967		consumer.assert_next_some("foo/bar/nested");
3968		consumer.assert_next_some("foo/goop/pee");
3969		consumer.assert_next_some("foo/goop/pee/nested");
3970	}
3971
3972	#[tokio::test]
3973	async fn test_with_root_and_consume_scope() {
3974		let origin = Origin::random().produce();
3975
3976		// Publish broadcasts
3977		let _broadcast1 = origin.create_broadcast("foo/bar/test", announce()).unwrap();
3978		let _broadcast2 = origin.create_broadcast("foo/goop/pee/test", announce()).unwrap();
3979		let _broadcast3 = origin.create_broadcast("foo/other/test", announce()).unwrap();
3980		settle().await;
3981
3982		// User connects to /foo root
3983		let foo_producer = origin.with_root("foo").expect("should create foo root");
3984
3985		// Create consumer limited to "bar" and "goop/pee" within /foo
3986		let mut limited_consumer = foo_producer
3987			.consume()
3988			.scope(&["bar".into(), "goop/pee".into()])
3989			.expect("should create limited consumer")
3990			.announced();
3991
3992		// Should only see allowed paths (without foo prefix)
3993		limited_consumer.assert_next_some("bar/test");
3994		limited_consumer.assert_next_some("goop/pee/test");
3995		limited_consumer.assert_next_wait(); // Should not see "other/test"
3996	}
3997
3998	#[tokio::test]
3999	async fn test_with_root_unauthorized() {
4000		let origin = Origin::random().produce();
4001
4002		// First limit the producer to specific paths
4003		let limited_producer = origin
4004			.scope(&["allowed".into()])
4005			.expect("should create limited producer");
4006
4007		// Trying to create a root outside allowed paths should fail
4008		assert!(limited_producer.with_root("notallowed").is_none());
4009
4010		// But creating a root within allowed paths should work
4011		let allowed_root = limited_producer
4012			.with_root("allowed")
4013			.expect("should create allowed root");
4014		assert_eq!(allowed_root.root().as_str(), "allowed");
4015	}
4016
4017	#[tokio::test]
4018	async fn test_wildcard_permission() {
4019		let origin = Origin::random().produce();
4020
4021		// Producer with root access (empty string means wildcard)
4022		let root_producer = origin.clone();
4023
4024		// Should be able to publish anywhere
4025		let _broadcast = root_producer
4026			.create_broadcast("any/path", announce())
4027			.expect("publish allowed");
4028		let _keep2 = root_producer
4029			.create_broadcast("other/path", announce())
4030			.expect("publish allowed");
4031		settle().await;
4032
4033		// Can create any root
4034		let foo_producer = root_producer.with_root("foo").expect("should create any root");
4035		assert_eq!(foo_producer.root().as_str(), "foo");
4036	}
4037
4038	#[tokio::test]
4039	async fn test_consume_broadcast_with_permissions() {
4040		let origin = Origin::random().produce();
4041
4042		let _broadcast1 = origin.create_broadcast("allowed/test", announce()).unwrap();
4043		let _broadcast2 = origin.create_broadcast("notallowed/test", announce()).unwrap();
4044		settle().await;
4045
4046		// Create limited consumer
4047		let limited_consumer = origin
4048			.consume()
4049			.scope(&["allowed".into()])
4050			.expect("should create limited consumer");
4051
4052		// Should be able to get allowed broadcast
4053		let result = limited_consumer.get_broadcast("allowed/test");
4054		assert!(result.is_some());
4055		assert!(
4056			result
4057				.unwrap()
4058				.is_clone(&origin.consume().get_broadcast("allowed/test").unwrap())
4059		);
4060
4061		// Should not be able to get disallowed broadcast
4062		assert!(limited_consumer.get_broadcast("notallowed/test").is_none());
4063
4064		// Original consumer can get both
4065		let consumer = origin.consume();
4066		assert!(consumer.get_broadcast("allowed/test").is_some());
4067		assert!(consumer.get_broadcast("notallowed/test").is_some());
4068	}
4069
4070	#[tokio::test]
4071	async fn test_nested_paths_with_permissions() {
4072		let origin = Origin::random().produce();
4073
4074		// Create producer limited to "a/b/c"
4075		let limited_producer = origin.scope(&["a/b/c".into()]).expect("should create limited producer");
4076
4077		// Should be able to publish to exact path and nested paths
4078		let _broadcast = limited_producer
4079			.create_broadcast("a/b/c", announce())
4080			.expect("publish allowed");
4081		let _keep2 = limited_producer
4082			.create_broadcast("a/b/c/d", announce())
4083			.expect("publish allowed");
4084		let _keep3 = limited_producer
4085			.create_broadcast("a/b/c/d/e", announce())
4086			.expect("publish allowed");
4087		settle().await;
4088
4089		// Should not be able to publish to parent or sibling paths
4090		assert!(limited_producer.create_broadcast("a", announce()).is_err());
4091		assert!(limited_producer.create_broadcast("a/b", announce()).is_err());
4092		assert!(limited_producer.create_broadcast("a/b/other", announce()).is_err());
4093	}
4094
4095	#[tokio::test]
4096	async fn test_multiple_consumers_with_different_permissions() {
4097		let origin = Origin::random().produce();
4098
4099		// Publish to different paths
4100		let _broadcast1 = origin.create_broadcast("foo/test", announce()).unwrap();
4101		let _broadcast2 = origin.create_broadcast("bar/test", announce()).unwrap();
4102		let _broadcast3 = origin.create_broadcast("baz/test", announce()).unwrap();
4103		settle().await;
4104
4105		// Create consumers with different permissions
4106		let mut foo_consumer = origin
4107			.consume()
4108			.scope(&["foo".into()])
4109			.expect("should create foo consumer")
4110			.announced();
4111
4112		let mut bar_consumer = origin
4113			.consume()
4114			.scope(&["bar".into()])
4115			.expect("should create bar consumer")
4116			.announced();
4117
4118		let mut foobar_consumer = origin
4119			.consume()
4120			.scope(&["foo".into(), "bar".into()])
4121			.expect("should create foobar consumer")
4122			.announced();
4123
4124		// Each consumer should only see their allowed paths
4125		foo_consumer.assert_next_some("foo/test");
4126		foo_consumer.assert_next_wait();
4127
4128		bar_consumer.assert_next_some("bar/test");
4129		bar_consumer.assert_next_wait();
4130
4131		foobar_consumer.assert_next_some("bar/test");
4132		foobar_consumer.assert_next_some("foo/test");
4133		foobar_consumer.assert_next_wait();
4134	}
4135
4136	#[tokio::test]
4137	async fn test_select_with_empty_prefix() {
4138		let origin = Origin::random().produce();
4139
4140		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
4141		let demo_producer = origin.with_root("demo").expect("should create demo root");
4142		let limited_producer = demo_producer
4143			.scope(&["worm-node".into(), "foobar".into()])
4144			.expect("should create limited producer");
4145
4146		// Publish some broadcasts
4147		let _broadcast1 = limited_producer
4148			.create_broadcast("worm-node/test", announce())
4149			.expect("publish allowed");
4150		let _broadcast2 = limited_producer
4151			.create_broadcast("foobar/test", announce())
4152			.expect("publish allowed");
4153		settle().await;
4154
4155		// scope with empty prefix should keep the exact same "worm-node" and "foobar" nodes
4156		let mut consumer = limited_producer
4157			.consume()
4158			.scope(&["".into()])
4159			.expect("should create consumer with empty prefix")
4160			.announced();
4161
4162		// Should see both broadcasts (order depends on PathPrefixes sort)
4163		let a1 = consumer.try_next().expect("expected first announcement");
4164		let a2 = consumer.try_next().expect("expected second announcement");
4165		consumer.assert_next_wait();
4166
4167		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
4168		paths.sort();
4169		assert_eq!(paths, ["foobar/test", "worm-node/test"]);
4170	}
4171
4172	#[tokio::test]
4173	async fn test_select_narrowing_scope() {
4174		let origin = Origin::random().produce();
4175
4176		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
4177		let demo_producer = origin.with_root("demo").expect("should create demo root");
4178		let limited_producer = demo_producer
4179			.scope(&["worm-node".into(), "foobar".into()])
4180			.expect("should create limited producer");
4181
4182		// Publish broadcasts at different levels
4183		let _broadcast1 = limited_producer
4184			.create_broadcast("worm-node", announce())
4185			.expect("publish allowed");
4186		let _broadcast2 = limited_producer
4187			.create_broadcast("worm-node/foo", announce())
4188			.expect("publish allowed");
4189		let _broadcast3 = limited_producer
4190			.create_broadcast("foobar/bar", announce())
4191			.expect("publish allowed");
4192		settle().await;
4193
4194		// Test 1: scope("worm-node") should result in a single "" node with contents of "worm-node" ONLY
4195		let mut worm_consumer = limited_producer
4196			.consume()
4197			.scope(&["worm-node".into()])
4198			.expect("should create worm-node consumer")
4199			.announced();
4200
4201		// Should see worm-node content with paths stripped to ""
4202		worm_consumer.assert_next_some("worm-node");
4203		worm_consumer.assert_next_some("worm-node/foo");
4204		worm_consumer.assert_next_wait(); // Should NOT see foobar content
4205
4206		// Test 2: scope("worm-node/foo") should result in a "" node with contents of "worm-node/foo"
4207		let mut foo_consumer = limited_producer
4208			.consume()
4209			.scope(&["worm-node/foo".into()])
4210			.expect("should create worm-node/foo consumer")
4211			.announced();
4212
4213		foo_consumer.assert_next_some("worm-node/foo");
4214		foo_consumer.assert_next_wait(); // Should NOT see other content
4215	}
4216
4217	#[tokio::test]
4218	async fn test_select_multiple_roots_with_empty_prefix() {
4219		let origin = Origin::random().produce();
4220
4221		// Producer with multiple allowed roots
4222		let limited_producer = origin
4223			.scope(&["app1".into(), "app2".into(), "shared".into()])
4224			.expect("should create limited producer");
4225
4226		// Publish to each root
4227		let _broadcast1 = limited_producer
4228			.create_broadcast("app1/data", announce())
4229			.expect("publish allowed");
4230		let _broadcast2 = limited_producer
4231			.create_broadcast("app2/config", announce())
4232			.expect("publish allowed");
4233		let _broadcast3 = limited_producer
4234			.create_broadcast("shared/resource", announce())
4235			.expect("publish allowed");
4236		settle().await;
4237
4238		// scope with empty prefix should maintain all roots
4239		let mut consumer = limited_producer
4240			.consume()
4241			.scope(&["".into()])
4242			.expect("should create consumer with empty prefix")
4243			.announced();
4244
4245		// Should see all broadcasts from all roots
4246		consumer.assert_next_some("app1/data");
4247		consumer.assert_next_some("app2/config");
4248		consumer.assert_next_some("shared/resource");
4249		consumer.assert_next_wait();
4250	}
4251
4252	#[tokio::test]
4253	async fn test_publish_scope_with_empty_prefix() {
4254		let origin = Origin::random().produce();
4255
4256		// Producer with specific allowed paths
4257		let limited_producer = origin
4258			.scope(&["services/api".into(), "services/web".into()])
4259			.expect("should create limited producer");
4260
4261		// scope with empty prefix should keep the same restrictions
4262		let same_producer = limited_producer
4263			.scope(&["".into()])
4264			.expect("should create producer with empty prefix");
4265
4266		// Should still have the same publishing restrictions
4267		let _broadcast = same_producer
4268			.create_broadcast("services/api", announce())
4269			.expect("publish allowed");
4270		let _keep2 = same_producer
4271			.create_broadcast("services/web", announce())
4272			.expect("publish allowed");
4273		assert!(same_producer.create_broadcast("services/db", announce()).is_err());
4274		assert!(same_producer.create_broadcast("other", announce()).is_err());
4275	}
4276
4277	#[tokio::test]
4278	async fn test_select_narrowing_to_deeper_path() {
4279		let origin = Origin::random().produce();
4280
4281		// Producer with broad permission
4282		let limited_producer = origin.scope(&["org".into()]).expect("should create limited producer");
4283
4284		// Publish at various depths
4285		let _broadcast1 = limited_producer
4286			.create_broadcast("org/team1/project1", announce())
4287			.expect("publish allowed");
4288		let _broadcast2 = limited_producer
4289			.create_broadcast("org/team1/project2", announce())
4290			.expect("publish allowed");
4291		let _broadcast3 = limited_producer
4292			.create_broadcast("org/team2/project1", announce())
4293			.expect("publish allowed");
4294		settle().await;
4295
4296		// Narrow down to team2 only
4297		let mut team2_consumer = limited_producer
4298			.consume()
4299			.scope(&["org/team2".into()])
4300			.expect("should create team2 consumer")
4301			.announced();
4302
4303		team2_consumer.assert_next_some("org/team2/project1");
4304		team2_consumer.assert_next_wait(); // Should NOT see team1 content
4305
4306		// Further narrow down to team1/project1
4307		let mut project1_consumer = limited_producer
4308			.consume()
4309			.scope(&["org/team1/project1".into()])
4310			.expect("should create project1 consumer")
4311			.announced();
4312
4313		// Should only see project1 content at root
4314		project1_consumer.assert_next_some("org/team1/project1");
4315		project1_consumer.assert_next_wait();
4316	}
4317
4318	#[tokio::test]
4319	async fn test_select_with_non_matching_prefix() {
4320		let origin = Origin::random().produce();
4321
4322		// Producer with specific allowed paths
4323		let limited_producer = origin
4324			.scope(&["allowed/path".into()])
4325			.expect("should create limited producer");
4326
4327		// Trying to scope with a completely different prefix should return None
4328		assert!(limited_producer.consume().scope(&["different/path".into()]).is_none());
4329
4330		// Similarly for scope
4331		assert!(limited_producer.scope(&["other/path".into()]).is_none());
4332	}
4333
4334	// Regression test for https://github.com/moq-dev/moq/issues/910
4335	// with_root panics when String has trailing slash (AsPath for String skips normalization)
4336	#[tokio::test]
4337	async fn test_with_root_trailing_slash_consumer() {
4338		let origin = Origin::random().produce();
4339
4340		// Use an owned String so the trailing slash is NOT normalized away.
4341		let prefix = "some_prefix/".to_string();
4342		let mut consumer = origin.consume().with_root(prefix).unwrap().announced();
4343
4344		let _b = origin.create_broadcast("some_prefix/test", announce()).unwrap();
4345		settle().await;
4346		consumer.assert_next_some("test");
4347	}
4348
4349	// Same issue but for the producer side of with_root
4350	#[tokio::test]
4351	async fn test_with_root_trailing_slash_producer() {
4352		let origin = Origin::random().produce();
4353
4354		// Use an owned String so the trailing slash is NOT normalized away.
4355		let prefix = "some_prefix/".to_string();
4356		let rooted = origin.with_root(prefix).unwrap();
4357
4358		let _b = rooted.create_broadcast("test", announce()).unwrap();
4359		settle().await;
4360
4361		let mut consumer = rooted.consume().announced();
4362		consumer.assert_next_some("test");
4363	}
4364
4365	// Verify unannounce also doesn't panic with trailing slash
4366	#[tokio::test]
4367	async fn test_with_root_trailing_slash_unannounce() {
4368		tokio::time::pause();
4369
4370		let origin = Origin::random().produce();
4371
4372		let prefix = "some_prefix/".to_string();
4373		let mut consumer = origin.consume().with_root(prefix).unwrap().announced();
4374
4375		let mut b = origin.create_broadcast("some_prefix/test", announce()).unwrap();
4376		settle().await;
4377		consumer.assert_next_some("test");
4378
4379		// Finish the broadcast to trigger an immediate unannounce.
4380		b.finish();
4381		settle().await;
4382
4383		// unannounce also calls strip_prefix(&self.root).unwrap()
4384		consumer.assert_next_none("test");
4385	}
4386
4387	#[tokio::test]
4388	async fn test_select_maintains_access_with_wider_prefix() {
4389		let origin = Origin::random().produce();
4390
4391		// Setup: user with root "demo" allowed to subscribe to specific paths
4392		let demo_producer = origin.with_root("demo").expect("should create demo root");
4393		let user_producer = demo_producer
4394			.scope(&["worm-node".into(), "foobar".into()])
4395			.expect("should create user producer");
4396
4397		// Publish some data
4398		let _broadcast1 = user_producer
4399			.create_broadcast("worm-node/data", announce())
4400			.expect("publish allowed");
4401		let _broadcast2 = user_producer
4402			.create_broadcast("foobar", announce())
4403			.expect("publish allowed");
4404		settle().await;
4405
4406		// Key test: scope with "" should maintain access to allowed roots
4407		let mut consumer = user_producer
4408			.consume()
4409			.scope(&["".into()])
4410			.expect("scope with empty prefix should not fail when user has specific permissions")
4411			.announced();
4412
4413		// Should still receive broadcasts from allowed paths (order not guaranteed)
4414		let a1 = consumer.try_next().expect("expected first announcement");
4415		let a2 = consumer.try_next().expect("expected second announcement");
4416		consumer.assert_next_wait();
4417
4418		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
4419		paths.sort();
4420		assert_eq!(paths, ["foobar", "worm-node/data"]);
4421
4422		// Also test that we can still narrow the scope
4423		let mut narrow_consumer = user_producer
4424			.consume()
4425			.scope(&["worm-node".into()])
4426			.expect("should be able to narrow scope to worm-node")
4427			.announced();
4428
4429		narrow_consumer.assert_next_some("worm-node/data");
4430		narrow_consumer.assert_next_wait(); // Should not see foobar
4431	}
4432
4433	#[tokio::test]
4434	async fn test_duplicate_prefixes_deduped() {
4435		let origin = Origin::random().produce();
4436
4437		// scope with duplicate prefixes should work (deduped internally)
4438		let producer = origin
4439			.scope(&["demo".into(), "demo".into()])
4440			.expect("should create producer");
4441
4442		let _broadcast = producer
4443			.create_broadcast("demo/stream", announce())
4444			.expect("publish allowed");
4445		settle().await;
4446
4447		let mut consumer = producer.consume().announced();
4448		consumer.assert_next_some("demo/stream");
4449		consumer.assert_next_wait();
4450	}
4451
4452	#[tokio::test]
4453	async fn test_overlapping_prefixes_deduped() {
4454		let origin = Origin::random().produce();
4455
4456		// "demo" and "demo/foo". "demo/foo" is redundant, only "demo" should remain
4457		let producer = origin
4458			.scope(&["demo".into(), "demo/foo".into()])
4459			.expect("should create producer");
4460
4461		// Can still publish under "demo/bar" since "demo" covers everything
4462		let _broadcast = producer
4463			.create_broadcast("demo/bar/stream", announce())
4464			.expect("publish allowed");
4465		settle().await;
4466
4467		let mut consumer = producer.consume().announced();
4468		consumer.assert_next_some("demo/bar/stream");
4469		consumer.assert_next_wait();
4470	}
4471
4472	#[tokio::test]
4473	async fn test_overlapping_prefixes_no_duplicate_announcements() {
4474		let origin = Origin::random().produce();
4475
4476		// Both "demo" and "demo/foo" are requested. Should only have one node
4477		let producer = origin
4478			.scope(&["demo".into(), "demo/foo".into()])
4479			.expect("should create producer");
4480
4481		let _broadcast = producer
4482			.create_broadcast("demo/foo/stream", announce())
4483			.expect("publish allowed");
4484		settle().await;
4485
4486		let mut consumer = producer.consume().announced();
4487		// Should only get ONE announcement (not two from overlapping nodes)
4488		consumer.assert_next_some("demo/foo/stream");
4489		consumer.assert_next_wait();
4490	}
4491
4492	#[tokio::test]
4493	async fn test_allowed_returns_deduped_prefixes() {
4494		let origin = Origin::random().produce();
4495
4496		let producer = origin
4497			.scope(&["demo".into(), "demo/foo".into(), "anon".into()])
4498			.expect("should create producer");
4499
4500		let allowed: Vec<_> = producer.allowed().collect();
4501		assert_eq!(allowed.len(), 2, "demo/foo should be subsumed by demo");
4502	}
4503
4504	#[tokio::test]
4505	async fn test_announced_broadcast_already_announced() {
4506		let origin = Origin::random().produce();
4507
4508		let _broadcast = origin.create_broadcast("test", announce()).unwrap();
4509		settle().await;
4510
4511		let consumer = origin.consume();
4512		let result = consumer.announced_broadcast("test").await.expect("should find it");
4513		assert!(result.is_clone(&consumer.get_broadcast("test").unwrap()));
4514	}
4515
4516	#[tokio::test]
4517	async fn test_announced_broadcast_delayed() {
4518		tokio::time::pause();
4519
4520		let origin = Origin::random().produce();
4521
4522		let consumer = origin.consume();
4523
4524		// Start waiting before it's announced.
4525		let wait = tokio::spawn({
4526			let consumer = consumer.clone();
4527			async move { consumer.announced_broadcast("test").await }
4528		});
4529
4530		// Give the spawned task a chance to subscribe.
4531		tokio::task::yield_now().await;
4532
4533		let _broadcast = origin.create_broadcast("test", announce()).unwrap();
4534		settle().await;
4535
4536		let result = wait.await.unwrap().expect("should find it");
4537		assert!(result.is_clone(&consumer.get_broadcast("test").unwrap()));
4538	}
4539
4540	#[tokio::test]
4541	async fn test_announced_broadcast_ignores_unrelated_paths() {
4542		tokio::time::pause();
4543
4544		let origin = Origin::random().produce();
4545
4546		let consumer = origin.consume();
4547
4548		let wait = tokio::spawn({
4549			let consumer = consumer.clone();
4550			async move { consumer.announced_broadcast("target").await }
4551		});
4552
4553		tokio::task::yield_now().await;
4554
4555		// Publish an unrelated broadcast first. announced_broadcast should skip it.
4556		let _other = origin.create_broadcast("other", announce()).unwrap();
4557		settle().await;
4558		tokio::task::yield_now().await;
4559		assert!(!wait.is_finished(), "must not resolve on unrelated path");
4560
4561		let _target = origin.create_broadcast("target", announce()).unwrap();
4562		settle().await;
4563		let result = wait.await.unwrap().expect("should find target");
4564		assert!(result.is_clone(&consumer.get_broadcast("target").unwrap()));
4565	}
4566
4567	#[tokio::test]
4568	async fn test_announced_broadcast_skips_nested_paths() {
4569		tokio::time::pause();
4570
4571		let origin = Origin::random().produce();
4572
4573		let consumer = origin.consume();
4574
4575		let wait = tokio::spawn({
4576			let consumer = consumer.clone();
4577			async move { consumer.announced_broadcast("foo").await }
4578		});
4579
4580		tokio::task::yield_now().await;
4581
4582		// "foo/bar" is under the prefix scope, but it's not the exact path. Skip it.
4583		let _nested = origin.create_broadcast("foo/bar", announce()).unwrap();
4584		settle().await;
4585		tokio::task::yield_now().await;
4586		assert!(!wait.is_finished(), "must not resolve on a nested path");
4587
4588		let _exact = origin.create_broadcast("foo", announce()).unwrap();
4589		settle().await;
4590		let result = wait.await.unwrap().expect("should find foo exactly");
4591		assert!(result.is_clone(&consumer.get_broadcast("foo").unwrap()));
4592	}
4593
4594	#[tokio::test]
4595	async fn test_announced_broadcast_disallowed() {
4596		let origin = Origin::random().produce();
4597		let limited = origin
4598			.consume()
4599			.scope(&["allowed".into()])
4600			.expect("should create limited");
4601
4602		// Path is outside allowed prefixes. Should return None immediately.
4603		assert!(limited.announced_broadcast("notallowed").await.is_none());
4604	}
4605
4606	#[tokio::test]
4607	async fn test_announced_broadcast_scope_too_narrow() {
4608		// Consumer's scope is narrower than the requested path: asking for `foo` on a consumer
4609		// limited to `foo/specific` can never resolve. Must return None, not loop forever.
4610		let origin = Origin::random().produce();
4611		let limited = origin
4612			.consume()
4613			.scope(&["foo/specific".into()])
4614			.expect("should create limited");
4615
4616		// now_or_never so we fail fast instead of hanging if the guard regresses.
4617		let result = limited
4618			.announced_broadcast("foo")
4619			.now_or_never()
4620			.expect("must not block");
4621		assert!(result.is_none());
4622	}
4623
4624	// Coalescing tests: a slow cursor that doesn't drain between updates
4625	// should observe a bounded number of deliveries.
4626
4627	#[tokio::test]
4628	async fn test_coalesce_announce_then_unannounce() {
4629		// announce + unannounce that the cursor hasn't observed yet collapses to nothing.
4630		tokio::time::pause();
4631
4632		let origin = Origin::random().produce();
4633		let mut announced = origin.consume().announced();
4634
4635		let mut broadcast = origin.create_broadcast("test", announce()).unwrap();
4636		settle().await;
4637		broadcast.finish();
4638
4639		settle().await;
4640
4641		announced.assert_next_wait();
4642	}
4643
4644	#[tokio::test]
4645	async fn test_coalesce_announce_unannounce_announce() {
4646		// announce, unannounce, announce that the cursor hasn't drained collapses
4647		// to a single Announce of the latest broadcast.
4648		tokio::time::pause();
4649
4650		let origin = Origin::random().produce();
4651		let mut announced = origin.consume().announced();
4652
4653		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
4654		settle().await;
4655		broadcast1.finish();
4656		settle().await;
4657		let _broadcast2 = origin.create_broadcast("test", announce()).unwrap();
4658		settle().await;
4659
4660		announced.assert_next_some("test");
4661		announced.assert_next_wait();
4662	}
4663
4664	#[tokio::test]
4665	async fn test_coalesce_unannounce_announce_preserved() {
4666		// unannounce followed by announce of a different broadcast must be preserved
4667		// as two deliveries so the cursor learns the origin changed.
4668		tokio::time::pause();
4669
4670		let origin = Origin::random().produce();
4671		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
4672		settle().await;
4673
4674		let mut announced = origin.consume().announced();
4675		announced.assert_next_some("test");
4676
4677		// Finish, then publish a fresh broadcast at the same path.
4678		broadcast1.finish();
4679		settle().await;
4680
4681		let _broadcast2 = origin.create_broadcast("test", announce()).unwrap();
4682		settle().await;
4683
4684		// The cursor must see the unannounce before the new announce.
4685		announced.assert_next_none("test");
4686		announced.assert_next_some("test");
4687		announced.assert_next_wait();
4688	}
4689
4690	#[tokio::test]
4691	async fn test_coalesce_unannounce_announce_unannounce() {
4692		// unannounce + announce + unannounce collapses to a single unannounce: the
4693		// embedded announce was never observed.
4694		tokio::time::pause();
4695
4696		let origin = Origin::random().produce();
4697		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
4698		settle().await;
4699
4700		let mut announced = origin.consume().announced();
4701		announced.assert_next_some("test");
4702
4703		broadcast1.finish();
4704		settle().await;
4705
4706		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
4707		settle().await;
4708		broadcast2.finish();
4709		settle().await;
4710
4711		announced.assert_next_none("test");
4712		announced.assert_next_wait();
4713	}
4714
4715	#[tokio::test]
4716	async fn test_coalesce_churn_bounded() {
4717		// A churn loop on a single path should keep the pending set bounded.
4718		// Backup promotion during cleanup can leave the cursor with zero or one
4719		// pending update for "test" depending on the order tasks run; we only
4720		// require that churn doesn't accumulate across iterations.
4721		tokio::time::pause();
4722
4723		let origin = Origin::random().produce();
4724		let mut announced = origin.consume().announced();
4725
4726		for _ in 0..1000 {
4727			let mut broadcast = origin.create_broadcast("test", announce()).unwrap();
4728			settle().await;
4729			broadcast.finish();
4730		}
4731		settle().await;
4732
4733		let mut collected = Vec::new();
4734		while let Some(update) = announced.try_next() {
4735			collected.push(update);
4736		}
4737		assert!(
4738			collected.len() <= 1,
4739			"expected at most one pending update, got {}",
4740			collected.len()
4741		);
4742		assert!(
4743			collected.iter().all(|a| a.path == Path::new("test")),
4744			"unexpected path in pending updates",
4745		);
4746	}
4747
4748	// Consumer should be cheap to clone: cloning must NOT drain any
4749	// other cursor's announce channel. A freshly-built AnnounceConsumer
4750	// still receives the active backlog.
4751	#[tokio::test]
4752	async fn test_consumer_clone_is_side_effect_free() {
4753		let origin = Origin::random().produce();
4754
4755		let _broadcast1 = origin.create_broadcast("test1", announce()).unwrap();
4756		let _broadcast2 = origin.create_broadcast("test2", announce()).unwrap();
4757		settle().await;
4758
4759		let consumer = origin.consume();
4760		let mut announced = consumer.announced();
4761
4762		// Cloning the Consumer many times and looking up broadcasts
4763		// must not consume any events from the existing cursor.
4764		for _ in 0..16 {
4765			let cloned = consumer.clone();
4766			assert!(cloned.get_broadcast("test1").is_some());
4767			assert!(cloned.get_broadcast("test2").is_some());
4768		}
4769
4770		// The original cursor still sees both announcements in their
4771		// natural order, undisturbed by the clones above.
4772		let a1 = announced.try_next().expect("first announcement");
4773		let a2 = announced.try_next().expect("second announcement");
4774		announced.assert_next_wait();
4775
4776		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
4777		paths.sort();
4778		assert_eq!(paths, ["test1", "test2"]);
4779
4780		// A freshly-built AnnounceConsumer still receives the active backlog.
4781		let mut fresh = consumer.announced();
4782		let b1 = fresh.try_next().expect("backlog: first");
4783		let b2 = fresh.try_next().expect("backlog: second");
4784		fresh.assert_next_wait();
4785
4786		let mut paths: Vec<_> = [&b1, &b2].iter().map(|a| a.path.to_string()).collect();
4787		paths.sort();
4788		assert_eq!(paths, ["test1", "test2"]);
4789	}
4790
4791	// With no Dynamic handler, an unannounced path resolves to Unroutable.
4792	#[tokio::test]
4793	async fn dynamic_request_unroutable_without_handler() {
4794		let origin = Origin::random().produce();
4795		let consumer = origin.consume();
4796		assert!(matches!(
4797			consumer.request_broadcast("missing").await,
4798			Err(Error::Unroutable)
4799		));
4800	}
4801
4802	// A dynamically served broadcast resolves the requester and serves tracks, but is
4803	// never announced.
4804	#[tokio::test(start_paused = true)]
4805	async fn dynamic_request_served_not_announced() {
4806		let origin = Origin::random().produce();
4807		let mut dynamic = origin.dynamic();
4808		let consumer = origin.consume();
4809
4810		// A separate announce cursor must never observe the dynamic broadcast.
4811		let mut announced = origin.consume().announced();
4812		announced.assert_next_wait();
4813
4814		let served = broadcast::Info::new().produce();
4815		// Request a path that nobody announced; the future stays pending until served.
4816		// Registration happens up front, so the handler sees the request immediately.
4817		let request_fut = consumer.request_broadcast("fallback");
4818
4819		// The handler serves it with a live broadcast it keeps producing into.
4820		let mut served_dynamic = served.dynamic();
4821
4822		let request = dynamic.requested_broadcast().await.unwrap();
4823		assert_eq!(request.path(), &Path::new("fallback"));
4824		request.accept(&served);
4825
4826		let broadcast = request_fut.await.unwrap();
4827		assert!(broadcast.is_clone(&served.consume()));
4828
4829		// The served broadcast is live: a track subscription resolves via its handler.
4830		let track_fut = broadcast.track("video").unwrap().subscribe(None);
4831		let mut producer = served_dynamic.requested_track().await.unwrap().accept(None);
4832		let mut track = track_fut.await.unwrap();
4833		producer.append_group().unwrap();
4834		track.assert_group();
4835
4836		// Still nothing announced.
4837		announced.assert_next_wait();
4838	}
4839
4840	// Concurrent requests for the same queued path coalesce onto one handler request.
4841	#[tokio::test(start_paused = true)]
4842	async fn dynamic_request_coalesces() {
4843		let origin = Origin::random().produce();
4844		let mut dynamic = origin.dynamic();
4845		let consumer = origin.consume();
4846
4847		// Both register before the handler drains either.
4848		let f1 = consumer.request_broadcast("dup");
4849		let f2 = consumer.request_broadcast("dup");
4850
4851		// Exactly one request reaches the handler.
4852		let request = dynamic.requested_broadcast().await.unwrap();
4853		assert_eq!(request.path(), &Path::new("dup"));
4854		assert!(
4855			dynamic.requested_broadcast().now_or_never().is_none(),
4856			"a coalesced request must not be served twice"
4857		);
4858
4859		// Accepting resolves both awaiting requesters with the same broadcast.
4860		let served = broadcast::Info::new().produce();
4861		request.accept(&served);
4862		assert!(f1.await.unwrap().is_clone(&served.consume()));
4863		assert!(f2.await.unwrap().is_clone(&served.consume()));
4864	}
4865
4866	// A repeat request for an already-served, still-live path shares the same broadcast
4867	// instead of asking the handler again (no duplicate upstream subscription).
4868	#[tokio::test(start_paused = true)]
4869	async fn dynamic_request_dedups_served() {
4870		let origin = Origin::random().produce();
4871		let mut dynamic = origin.dynamic();
4872		let consumer = origin.consume();
4873
4874		let request_fut = consumer.request_broadcast("fallback");
4875		let request = dynamic.requested_broadcast().await.unwrap();
4876		let served = broadcast::Info::new().produce();
4877		request.accept(&served);
4878		let first = request_fut.await.unwrap();
4879		assert!(first.is_clone(&served.consume()));
4880
4881		// The repeat resolves immediately to the same broadcast...
4882		let second = consumer.request_broadcast("fallback").await.unwrap();
4883		assert!(second.is_clone(&served.consume()));
4884
4885		// ...and the handler never sees a second request.
4886		assert!(
4887			dynamic.requested_broadcast().now_or_never().is_none(),
4888			"a still-live served broadcast must not be re-requested from the handler"
4889		);
4890	}
4891
4892	// Once a served broadcast closes, its cache entry is stale, so the next request re-serves.
4893	#[tokio::test(start_paused = true)]
4894	async fn dynamic_request_reserves_after_close() {
4895		let origin = Origin::random().produce();
4896		let mut dynamic = origin.dynamic();
4897		let consumer = origin.consume();
4898
4899		let request_fut = consumer.request_broadcast("fallback");
4900		let request = dynamic.requested_broadcast().await.unwrap();
4901		let served = broadcast::Info::new().produce();
4902		request.accept(&served);
4903		request_fut.await.unwrap();
4904
4905		// Close the first served broadcast; the weak cache entry goes stale.
4906		drop(served);
4907
4908		// A fresh request must reach the handler again and resolve to the new broadcast.
4909		let request_fut = consumer.request_broadcast("fallback");
4910		let request = dynamic.requested_broadcast().await.unwrap();
4911		assert_eq!(request.path(), &Path::new("fallback"));
4912		let served = broadcast::Info::new().produce();
4913		request.accept(&served);
4914		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
4915	}
4916
4917	// Serving many distinct one-shot paths that each close must not grow the `served` cache
4918	// unboundedly: the amortized GC on `accept` reclaims the stale entries left by closed ones.
4919	#[tokio::test(start_paused = true)]
4920	async fn dynamic_request_served_cache_bounded() {
4921		let origin = Origin::random().produce();
4922		let mut dynamic = origin.dynamic();
4923		let consumer = origin.consume();
4924
4925		for i in 0..100 {
4926			let path = format!("one-shot/{i}");
4927			let request_fut = consumer.request_broadcast(&path);
4928			let request = dynamic.requested_broadcast().await.unwrap();
4929			let served = broadcast::Info::new().produce();
4930			request.accept(&served);
4931			request_fut.await.unwrap();
4932			// Close the served broadcast; its cache entry is now stale.
4933			drop(served);
4934		}
4935
4936		// The GC keeps the map bounded by the live count (zero here) plus a small probe window,
4937		// rather than one entry per distinct path.
4938		assert!(
4939			origin.dynamic.read().served.len() <= 4,
4940			"stale served entries must be reclaimed, not accumulate per distinct path: {}",
4941			origin.dynamic.read().served.len()
4942		);
4943	}
4944
4945	// A repeat request in the window after the handler picks one up but before it accepts
4946	// coalesces onto the in-flight request instead of queuing a duplicate.
4947	#[tokio::test(start_paused = true)]
4948	async fn dynamic_request_coalesces_after_handoff() {
4949		let origin = Origin::random().produce();
4950		let mut dynamic = origin.dynamic();
4951		let consumer = origin.consume();
4952
4953		let f1 = consumer.request_broadcast("fallback");
4954		// Handler drains the request but has not accepted yet.
4955		let request = dynamic.requested_broadcast().await.unwrap();
4956
4957		// A second request in this window must not queue another handler request.
4958		let f2 = consumer.request_broadcast("fallback");
4959		assert!(
4960			dynamic.requested_broadcast().now_or_never().is_none(),
4961			"a repeat request during hand-off must coalesce, not re-queue"
4962		);
4963
4964		// Accepting resolves both awaiting requesters with the same broadcast.
4965		let served = broadcast::Info::new().produce();
4966		request.accept(&served);
4967		assert!(f1.await.unwrap().is_clone(&served.consume()));
4968		assert!(f2.await.unwrap().is_clone(&served.consume()));
4969	}
4970
4971	// Dropping a handed-off request without accept/reject rejects every coalesced requester.
4972	#[tokio::test(start_paused = true)]
4973	async fn dynamic_request_dropped_after_handoff() {
4974		let origin = Origin::random().produce();
4975		let mut dynamic = origin.dynamic();
4976		let consumer = origin.consume();
4977
4978		let f1 = consumer.request_broadcast("fallback");
4979		let request = dynamic.requested_broadcast().await.unwrap();
4980		let f2 = consumer.request_broadcast("fallback");
4981
4982		// Abandon it; both requesters resolve to Unroutable instead of hanging.
4983		drop(request);
4984		assert!(matches!(f1.await, Err(Error::Unroutable)));
4985		assert!(matches!(f2.await, Err(Error::Unroutable)));
4986	}
4987
4988	// Rejecting a request resolves the requester with the error.
4989	#[tokio::test(start_paused = true)]
4990	async fn dynamic_request_rejected() {
4991		let origin = Origin::random().produce();
4992		let mut dynamic = origin.dynamic();
4993		let consumer = origin.consume();
4994
4995		let request_fut = consumer.request_broadcast("fallback");
4996
4997		let request = dynamic.requested_broadcast().await.unwrap();
4998		request.reject(Error::Cancel);
4999
5000		assert!(matches!(request_fut.await, Err(Error::Cancel)));
5001	}
5002
5003	// After a rejected hand-off, a fresh request for the same path reaches the handler again:
5004	// the rejected `Request`'s removal + `Drop` leave the request queue consistent
5005	// (a stale/clobbered entry would strand this request or panic the handler).
5006	#[tokio::test(start_paused = true)]
5007	async fn dynamic_request_rerequest_after_reject() {
5008		let origin = Origin::random().produce();
5009		let mut dynamic = origin.dynamic();
5010		let consumer = origin.consume();
5011
5012		let f1 = consumer.request_broadcast("fallback");
5013		dynamic.requested_broadcast().await.unwrap().reject(Error::Unroutable);
5014		assert!(matches!(f1.await, Err(Error::Unroutable)));
5015
5016		let served = broadcast::Info::new().produce();
5017		// A fresh request re-reaches the handler and can be served.
5018		let f2 = consumer.request_broadcast("fallback");
5019		let request = dynamic.requested_broadcast().await.unwrap();
5020		assert_eq!(request.path(), &Path::new("fallback"));
5021		request.accept(&served);
5022		assert!(f2.await.unwrap().is_clone(&served.consume()));
5023	}
5024
5025	// Dropping the last handler resolves queued requests with an error and reverts to
5026	// resolving Unroutable.
5027	#[tokio::test(start_paused = true)]
5028	async fn dynamic_request_handler_dropped() {
5029		let origin = Origin::random().produce();
5030		let dynamic = origin.dynamic();
5031		let consumer = origin.consume();
5032
5033		let request_fut = consumer.request_broadcast("fallback");
5034		drop(dynamic);
5035		assert!(matches!(request_fut.await, Err(Error::Unroutable)));
5036
5037		// With no handler left, a fresh request resolves Unroutable.
5038		assert!(matches!(
5039			consumer.request_broadcast("again").await,
5040			Err(Error::Unroutable)
5041		));
5042	}
5043
5044	// `accept` is decoupled from the dynamic count: once a handler has picked a request up,
5045	// it can still serve it even if every handler (including itself) drops first, flipping the
5046	// count to zero. The in-flight request must not be rejected as `Unroutable`.
5047	#[tokio::test(start_paused = true)]
5048	async fn dynamic_request_accept_after_handler_dropped() {
5049		let origin = Origin::random().produce();
5050		let mut dynamic = origin.dynamic();
5051		let consumer = origin.consume();
5052
5053		let request_fut = consumer.request_broadcast("fallback");
5054
5055		// The handler picks the request up, then every handler drops (count -> 0).
5056		let request = dynamic.requested_broadcast().await.unwrap();
5057		drop(dynamic);
5058
5059		let served = broadcast::Info::new().produce();
5060		// Accept still resolves the awaiting requester with the served broadcast.
5061		request.accept(&served);
5062		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
5063	}
5064
5065	// A published broadcast wins over the dynamic fallback; no request is queued.
5066	#[tokio::test(start_paused = true)]
5067	async fn dynamic_request_prefers_announced() {
5068		let origin = Origin::random().produce();
5069		let mut dynamic = origin.dynamic();
5070		let consumer = origin.consume();
5071
5072		let _broadcast = origin.create_broadcast("live", announce()).unwrap();
5073		settle().await;
5074
5075		let got = consumer.request_broadcast("live").await.unwrap();
5076		assert!(
5077			got.is_clone(&consumer.get_broadcast("live").unwrap()),
5078			"should return the published broadcast"
5079		);
5080		assert!(
5081			dynamic.requested_broadcast().now_or_never().is_none(),
5082			"a published path must not queue a fallback request"
5083		);
5084	}
5085
5086	// Cloning a handler and dropping the clone must not flip the count to zero.
5087	#[tokio::test(start_paused = true)]
5088	async fn dynamic_clone_keeps_alive() {
5089		let origin = Origin::random().produce();
5090		let dynamic = origin.dynamic();
5091		let consumer = origin.consume();
5092
5093		drop(dynamic.clone());
5094
5095		// The original handle is still live, so the request registers (stays pending)
5096		// instead of resolving Unroutable.
5097		let request_fut = consumer.request_broadcast("fallback");
5098		assert!(
5099			request_fut.now_or_never().is_none(),
5100			"request should stay pending until served"
5101		);
5102	}
5103}