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