Skip to main content

moq_net/model/
origin.rs

1use crate::{broadcast, cache, stats, track};
2use kio::Pollable;
3use std::{
4	collections::{BTreeMap, HashMap},
5	fmt,
6	sync::Arc,
7	sync::atomic::{AtomicU64, Ordering},
8	task::{Poll, ready},
9	time::Duration,
10};
11
12use rand::RngExt;
13use web_async::Lock;
14
15use super::{Requests, WeakCache};
16use crate::{
17	AsPath, Error, Path, PathOwned, PathPrefixes,
18	coding::{BoundsExceeded, Decode, DecodeError, Encode, EncodeError},
19};
20
21/// A relay origin, identified by a 62-bit varint on the wire.
22///
23/// Local origins are built with [`Origin::new`] or [`Origin::random`], both of
24/// which guarantee a non-zero id so loop detection can work. Remote peers may
25/// still send `0`; it is legal on the wire but cannot be used for loop detection.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct Origin {
28	/// 62-bit identifier. Encoded as a QUIC varint on the wire.
29	id: u64,
30}
31
32/// Returned when a local origin id is zero or outside the 62-bit wire range.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[non_exhaustive]
35pub struct InvalidOrigin;
36
37impl fmt::Display for InvalidOrigin {
38	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39		write!(f, "local origin id must be non-zero and below 2^62")
40	}
41}
42
43impl std::error::Error for InvalidOrigin {}
44
45impl Origin {
46	/// Placeholder for hop entries whose actual id is not on the wire (Lite03).
47	/// Also used for remote peers that choose the legal but loop-blind id 0.
48	pub(crate) const UNKNOWN: Self = Self { id: 0 };
49
50	/// Build an origin from a stable id.
51	///
52	/// The id must be non-zero and fit in the 62-bit QUIC varint range. Wire
53	/// decode accepts remote id 0, but local origins should not use it because
54	/// downstream peers cannot exclude it for loop detection.
55	pub fn new(id: u64) -> Result<Self, InvalidOrigin> {
56		if id == 0 || id >= 1u64 << 62 {
57			return Err(InvalidOrigin);
58		}
59		Ok(Self { id })
60	}
61
62	/// Generate a fresh origin with a random non-zero id. Use this for any
63	/// origin that does not need a stable identity across restarts.
64	///
65	/// TEMPORARY: the wire format allows 62 bits, but older `@moq/lite` JS
66	/// clients decode `AnnounceInterest.exclude_hop` as a u53 (number) and
67	/// throw on anything > 2^53-1. To keep those clients alive against
68	/// fresh relays, we cap the random id at 53 bits. Restore to 62 bits
69	/// once the JS u62 fix has propagated to deployed bundles.
70	pub fn random() -> Self {
71		let mut rng = rand::rng();
72		let id = rng.random_range(1..(1u64 << 53));
73		Self { id }
74	}
75
76	/// Return the origin's wire id.
77	pub fn id(self) -> u64 {
78		self.id
79	}
80
81	/// Consume this [Origin] to create a producer that carries its id, with an
82	/// unbounded cache pool. Use [`Info::produce`] to configure the pool.
83	pub fn produce(self) -> Producer {
84		Info::new(self).produce()
85	}
86}
87
88/// An origin's identity plus the cache pool its broadcasts inherit.
89///
90/// Doubles as the construction config for an [origin `Producer`](Producer) and as the
91/// parent handle every broadcast carries ([`broadcast::Info::origin`]): the origin owns
92/// the [`cache::Pool`] every group in the tree registers with, so a relay configures one
93/// bounded pool here and every broadcast, track, and group beneath it reaches that single
94/// budget by walking up the ownership chain. Defaults to an unbounded pool
95/// ([`Origin::produce`] is the shorthand for that). Cheap to clone (a `Copy` id plus an
96/// `Arc`-handle bump), so it's stored by value rather than behind another `Arc`.
97#[derive(Clone, Debug)]
98#[non_exhaustive]
99pub struct Info {
100	/// The origin's wire identity, appended to broadcast hop chains for loop
101	/// detection and shortest-path routing.
102	pub id: Origin,
103
104	/// The cache pool broadcasts under this origin register their groups with. It
105	/// flows down the ownership chain (origin -> broadcast -> track -> group), so a
106	/// group reaches it via `track.broadcast.origin.pool`. Unbounded by default; a
107	/// relay sets a bounded one (via [`Self::with_pool`]) so cached groups across the
108	/// whole process share one memory budget.
109	pub pool: cache::Pool,
110
111	/// 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: Option<web_async::time::Instant> = None;
1417
1418	loop {
1419		let empty = {
1420			let s = state.read();
1421			!s.closed && s.routes.is_empty()
1422		};
1423		deadline = match (empty, 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			// Pending forever while no countdown is running. Fused via `fired`: a
1433			// completed future must not be polled again.
1434			let mut sleep = std::pin::pin!(async {
1435				match deadline {
1436					Some(at) => {
1437						web_async::time::sleep(at.saturating_duration_since(web_async::time::Instant::now())).await
1438					}
1439					None => std::future::pending().await,
1440				}
1441			});
1442			let mut fired = false;
1443			kio::wait(|waiter| {
1444				if let Poll::Ready((name, resume)) = broadcast.poll_spliced_assigned(waiter) {
1445					return Poll::Ready(Step::Serve(name, resume));
1446				}
1447				// Watch for the close, and for the table changing shape so the
1448				// countdown re-arms (a detach emptying it, a reconnect refilling it).
1449				match state.poll(waiter, |s| {
1450					if s.closed || s.routes.is_empty() != empty {
1451						Poll::Ready(())
1452					} else {
1453						Poll::Pending
1454					}
1455				}) {
1456					Poll::Ready(Ok(guard)) => {
1457						return Poll::Ready(if guard.closed { Step::Closed } else { Step::Changed });
1458					}
1459					Poll::Ready(Err(_)) => return Poll::Ready(Step::Closed),
1460					Poll::Pending => {}
1461				}
1462				if deadline.is_some() && !fired && waiter.poll_future(sleep.as_mut()).is_ready() {
1463					fired = true;
1464				}
1465				match fired {
1466					true => Poll::Ready(Step::Expired),
1467					false => Poll::Pending,
1468				}
1469			})
1470			.await
1471		};
1472
1473		match step {
1474			Step::Serve(name, resume) => {
1475				// Serve tasks self-terminate when the track completes or the
1476				// front closes.
1477				web_async::spawn(serve_track(state.clone(), name, resume));
1478			}
1479			Step::Changed => {}
1480			Step::Expired => {
1481				// Close only if the table is still empty: a source that re-attached
1482				// as the window expired wins the write-lock race and keeps the
1483				// broadcast alive.
1484				let close = {
1485					let Ok(mut s) = state.write() else { break };
1486					if !s.closed && s.routes.is_empty() {
1487						s.closed = true;
1488						true
1489					} else {
1490						false
1491					}
1492				};
1493				if close {
1494					break;
1495				}
1496			}
1497			Step::Closed => break,
1498		}
1499	}
1500
1501	// Abort the logical tracks (releasing their subscribers) and unpublish.
1502	broadcast.abort_spliced(Error::Dropped);
1503
1504	// Deliberate end; suppresses the dropped-without-finish warning.
1505	broadcast.finish();
1506
1507	// Remove the broadcast from the tree (identity-checked, so a replacement is
1508	// untouched) and prune empty nodes.
1509	node.lock().remove(&state, &rest);
1510}
1511
1512/// Serves one spliced logical track: splices in the best source's copy of the
1513/// track, re-splicing on handover or failure, until the track completes or the
1514/// front closes. A rejection before a successful splice (the source refused the
1515/// track, or its info never resolved) counts toward [`MAX_TRACK_RETRIES`] and
1516/// then aborts the track as [`Error::Unroutable`]; failures after a splice (a
1517/// serving session dying mid-stream) are normal failover and re-splice from the
1518/// next source at the first missing group.
1519async fn serve_track(state: kio::Producer<FrontState>, name: Arc<str>, mut resume: super::resume::Producer) {
1520	enum Step {
1521		Closed,
1522		Splice(u64, broadcast::Consumer),
1523		Complete,
1524		Failed,
1525		/// The linger expired with the track still unread: release the segment.
1526		Idle,
1527		/// A reader arrived or the last one left: recompute the demand gate.
1528		Demand,
1529	}
1530
1531	let mut fails = 0u32;
1532	// The source whose copy is currently spliced in, and that copy.
1533	let mut serving: Option<(u64, track::Consumer)> = None;
1534	// A source whose splice failed because it had already closed. Its watcher is
1535	// about to detach it, so wait for the table to move on rather than burning
1536	// the strike budget on a corpse (ids are never reused, so this cannot wedge).
1537	let mut dead: Option<u64> = None;
1538	// When the spliced segment stopped being read, starting the release countdown.
1539	let mut idle_since: Option<web_async::time::Instant> = None;
1540
1541	loop {
1542		let serving_id = serving.as_ref().map(|(id, _)| *id);
1543
1544		// Demand gates both directions: an unread track never splices a source in,
1545		// and a spliced one is released once the linger expires. Both sides use the
1546		// same signal, so a release can't immediately re-splice and spin.
1547		let used = resume.is_used();
1548		idle_since = match (serving.is_some(), used) {
1549			(true, false) => idle_since.or_else(|| Some(web_async::time::Instant::now())),
1550			_ => None,
1551		};
1552		let deadline = idle_since.and_then(|at| at.checked_add(TRACK_IDLE_LINGER));
1553
1554		let step = {
1555			// Pinned outside the closure: a future re-created on every poll would
1556			// restart the countdown each time and never fire. Fused via `fired`: a
1557			// completed future must not be polled again.
1558			let mut sleep = std::pin::pin!(async {
1559				match deadline {
1560					Some(at) => {
1561						web_async::time::sleep(at.saturating_duration_since(web_async::time::Instant::now())).await
1562					}
1563					None => std::future::pending().await,
1564				}
1565			});
1566			let mut fired = false;
1567
1568			kio::wait(|waiter| {
1569				// Watch the source table: the front closing, or the active source
1570				// moving away from the one currently spliced in (skipping one whose
1571				// closure we already observed). Splicing waits for a reader.
1572				match state.poll(waiter, |s| {
1573					if s.closed
1574						|| (used
1575							&& matches!(s.active, Some(active) if Some(active) != serving_id && Some(active) != dead))
1576					{
1577						Poll::Ready(())
1578					} else {
1579						Poll::Pending
1580					}
1581				}) {
1582					Poll::Ready(Ok(guard)) => {
1583						if guard.closed {
1584							return Poll::Ready(Step::Closed);
1585						}
1586						let active = guard.active.expect("predicate guaranteed an active source");
1587						let source = guard
1588							.routes
1589							.iter()
1590							.find(|r| r.id == active)
1591							.expect("active source in table")
1592							.source
1593							.clone();
1594						return Poll::Ready(Step::Splice(active, source));
1595					}
1596					Poll::Ready(Err(_)) => return Poll::Ready(Step::Closed),
1597					Poll::Pending => {}
1598				}
1599
1600				// Watch the demand edge in whichever direction is unmet. This has to end
1601				// the wait, not just wake it: `used` and the countdown are computed by
1602				// the outer loop, so a wake that stayed inside would re-poll with the
1603				// stale value and never arm (or cancel) the linger.
1604				let edge = match used {
1605					true => resume.poll_unused(waiter),
1606					false => resume.poll_used(waiter),
1607				};
1608				if edge.is_ready() {
1609					return Poll::Ready(Step::Demand);
1610				}
1611
1612				// Watch the spliced copy for its end: complete means the logical
1613				// track is over; anything else means the serving source died.
1614				if let Some((_, track)) = &serving
1615					&& let Poll::Ready(result) = track.poll_complete(waiter)
1616				{
1617					return Poll::Ready(match result {
1618						Ok(()) => Step::Complete,
1619						Err(_) => Step::Failed,
1620					});
1621				}
1622
1623				if deadline.is_some() && !fired && waiter.poll_future(sleep.as_mut()).is_ready() {
1624					fired = true;
1625				}
1626				if fired {
1627					return Poll::Ready(Step::Idle);
1628				}
1629				Poll::Pending
1630			})
1631			.await
1632		};
1633
1634		match step {
1635			// The front's teardown aborts the logical track.
1636			Step::Closed => return,
1637			Step::Complete => {
1638				let _ = resume.finish();
1639				return;
1640			}
1641			Step::Failed => {
1642				// The spliced copy died mid-serve: failover, not a strike.
1643				// Re-splice from the (possibly same) active source.
1644				serving = None;
1645			}
1646			// The outer loop recomputes `used` and the countdown on the next pass.
1647			Step::Demand => {}
1648			Step::Idle => {
1649				// Nobody has read the track for the linger: drop the source's copy so
1650				// its session can release the track (and the cached `track::Info` that
1651				// came with it). The logical track stays alive and re-splices on the
1652				// next reader, so a returning viewer or a follow-up fetch resumes.
1653				if resume.release().is_err() {
1654					// Finished or aborted meanwhile; the track is over either way.
1655					return;
1656				}
1657				serving = None;
1658			}
1659			Step::Splice(id, source) => {
1660				// Ask the source for its copy and wait for the info to resolve,
1661				// proving it servable, before splicing it in. Bail out early if
1662				// the table moves on while waiting.
1663				let attempt = match source.track(&name) {
1664					Ok(track) => {
1665						// `into_inner` sheds the `Pending` future wrapper so only
1666						// the pollable (which is `Sync`) is held across the await.
1667						let query = track.info().into_inner();
1668						let info = kio::wait(|waiter| {
1669							if let Poll::Ready(result) = query.poll(waiter) {
1670								return Poll::Ready(Some(result));
1671							}
1672							match state.poll(waiter, |s| {
1673								if s.closed || s.active != Some(id) {
1674									Poll::Ready(())
1675								} else {
1676									Poll::Pending
1677								}
1678							}) {
1679								Poll::Ready(_) => Poll::Ready(None),
1680								Poll::Pending => Poll::Pending,
1681							}
1682						})
1683						.await;
1684						match info {
1685							// The table changed under us; retry from the top
1686							// without a strike.
1687							None => continue,
1688							// A copy that is already aborted can't be spliced;
1689							// count a strike, or a source pinning a dead track
1690							// alive would spin this loop without ever yielding.
1691							Some(Ok(_)) => match track.poll_complete(&kio::Waiter::noop()) {
1692								Poll::Ready(Err(err)) => Err(err),
1693								_ => Ok(track),
1694							},
1695							Some(Err(err)) => Err(err),
1696						}
1697					}
1698					Err(err) => Err(err),
1699				};
1700
1701				match attempt {
1702					Ok(track) => {
1703						if resume.takeover(&track).is_err() {
1704							// The logical track already ended (finished or
1705							// aborted); nothing left to serve.
1706							return;
1707						}
1708						// A successful splice proves the track servable: reset
1709						// the strike budget.
1710						fails = 0;
1711						dead = None;
1712						serving = Some((id, track));
1713					}
1714					// The source itself closed or deliberately ended: not a
1715					// rejection, so no strike. Park until its watcher detaches
1716					// it and the table promotes a replacement.
1717					Err(_) if source.is_closing() => {
1718						dead = Some(id);
1719						serving = None;
1720					}
1721					Err(err) => {
1722						fails += 1;
1723						if fails >= MAX_TRACK_RETRIES {
1724							tracing::debug!(name = %name, %err, "aborting unservable track");
1725							let _ = resume.abort(Error::Unroutable);
1726							return;
1727						}
1728						serving = None;
1729					}
1730				}
1731			}
1732		}
1733	}
1734}
1735
1736/// Shared fallback request queue for an origin.
1737///
1738/// Lives off to the side of the announce tree because dynamically served broadcasts
1739/// are never announced. Carried in a [`kio::Shared`], so consumers enqueue and handlers
1740/// drain under one lock. Mirrors the fetch state of the track model.
1741#[derive(Default)]
1742struct OriginDynamicState {
1743	// Result channels for pending requests, keyed by absolute path so concurrent
1744	// `request_broadcast` calls for the same path coalesce onto one channel.
1745	requests: Requests<PathOwned, kio::Producer<PendingBroadcast>>,
1746
1747	// Broadcasts a handler has already served, kept weakly so a repeat request for the
1748	// same path resolves to a shared clone instead of re-invoking the handler (which would
1749	// open a duplicate upstream subscription). Weak so a served broadcast still closes once
1750	// its real consumers drop. The cache reclaims closed entries incrementally on insert, so a
1751	// long-lived origin serving many distinct one-shot paths stays bounded by the live count.
1752	served: WeakCache<PathOwned, broadcast::WeakConsumer>,
1753}
1754
1755/// One-shot result of a dynamic broadcast request.
1756///
1757/// Stays `None` until a handler [`accept`](Request::accept)s (yielding the served
1758/// broadcast) or [`reject`](Request::reject)s (yielding an error). The producer is
1759/// dropped right after writing, closing the channel; kio checks the value before the closed
1760/// flag, so an awaiting requester still observes the final result.
1761#[derive(Default)]
1762struct PendingBroadcast {
1763	resolved: Option<Result<broadcast::Consumer, Error>>,
1764}
1765
1766/// Picks up [`Consumer::request_broadcast`] calls for paths that are not announced.
1767///
1768/// The origin-level analogue of [`broadcast::Dynamic`]: where that serves tracks on
1769/// demand within a broadcast, this serves whole broadcasts on demand within an origin. A
1770/// relay uses it as a fallback router, fetching a broadcast from upstream only when a
1771/// downstream consumer asks for an exact path that nobody announced.
1772///
1773/// Served broadcasts are deliberately *not* announced, so they never appear in
1774/// [`Consumer::announced`]. Drop this handle (and every clone) to reject the
1775/// requests still waiting to be served.
1776pub struct Dynamic {
1777	info: Origin,
1778	root: PathOwned,
1779	state: kio::Shared<OriginDynamicState>,
1780}
1781
1782impl Clone for Dynamic {
1783	fn clone(&self) -> Self {
1784		// Mirror `new`: count each live handle. Without this, dropping a clone would
1785		// decrement past `new`'s increment and prematurely flip the handler count to
1786		// zero, making future `request_broadcast` calls return `Unroutable`.
1787		self.state.lock().requests.add_handler();
1788
1789		Self {
1790			info: self.info,
1791			root: self.root.clone(),
1792			state: self.state.clone(),
1793		}
1794	}
1795}
1796
1797impl Dynamic {
1798	fn new(info: Origin, root: PathOwned, state: kio::Shared<OriginDynamicState>) -> Self {
1799		state.lock().requests.add_handler();
1800
1801		Self { info, root, state }
1802	}
1803
1804	/// The origin this handler belongs to.
1805	pub fn info(&self) -> &Origin {
1806		&self.info
1807	}
1808
1809	/// Poll for the next requested broadcast, without blocking.
1810	pub fn poll_requested_broadcast(&mut self, waiter: &kio::Waiter) -> Poll<Result<Request, Error>> {
1811		let mut state = ready!(self.state.poll(waiter, |state| {
1812			if state.requests.has_queued() {
1813				Poll::Ready(())
1814			} else {
1815				Poll::Pending
1816			}
1817		}));
1818
1819		let path = state.requests.pop().expect("predicate guaranteed a request");
1820		// The popped request stays pending, so a repeat request in the window between
1821		// hand-off and accept coalesces onto it instead of re-invoking the handler. The
1822		// producer is a shared clone; `Request::{accept, reject, drop}` removes the
1823		// entry. This mirrors how `poll_requested_track` keeps a served track
1824		// discoverable via the weak cache across the same window.
1825		let producer = state.requests.get(&path).expect("popped key must be pending").clone();
1826		Poll::Ready(Ok(Request {
1827			path,
1828			producer,
1829			state: self.state.clone(),
1830		}))
1831	}
1832
1833	/// Block until a consumer requests an unannounced broadcast, returning a
1834	/// [`Request`] to serve.
1835	pub async fn requested_broadcast(&mut self) -> Result<Request, Error> {
1836		kio::wait(|waiter| self.poll_requested_broadcast(waiter)).await
1837	}
1838
1839	/// Returns the prefix that is automatically stripped from requested paths.
1840	pub fn root(&self) -> &Path<'_> {
1841		&self.root
1842	}
1843}
1844
1845impl Drop for Dynamic {
1846	fn drop(&mut self) {
1847		// Decrement and reject under one lock, so a `request_broadcast` that saw a
1848		// live handler through the same lock can't slip a request past the rejection.
1849		let mut state = self.state.lock();
1850		if state.requests.remove_handler() {
1851			// No handlers left to pop queued requests; drop them, closing their result
1852			// channels so awaiting requesters resolve to `Unroutable`. A request already
1853			// handed to a handler stays, resolved by its `Request` instead.
1854			state.requests.drain_queued();
1855		}
1856	}
1857}
1858
1859/// A pending request for a broadcast that was not announced.
1860///
1861/// Yielded by [`Dynamic::requested_broadcast`]. The requester is awaiting inside
1862/// [`Consumer::request_broadcast`]; [`accept`](Self::accept) resolves it with a live
1863/// broadcast (which the handler keeps producing into) and [`reject`](Self::reject) resolves
1864/// it with an error. Dropping the request without either rejects it.
1865pub struct Request {
1866	// Absolute path that was requested.
1867	path: PathOwned,
1868
1869	// Result channel back to the awaiting requester(s). Writing `resolved` and dropping
1870	// this wakes them with the outcome.
1871	producer: kio::Producer<PendingBroadcast>,
1872
1873	// Shared dynamic state, so `accept` can cache the served broadcast for repeat requests.
1874	state: kio::Shared<OriginDynamicState>,
1875}
1876
1877impl Request {
1878	/// The absolute path that was requested.
1879	pub fn path(&self) -> &Path<'_> {
1880		&self.path
1881	}
1882
1883	/// Accept the request, resolving every awaiting requester with `broadcast`.
1884	///
1885	/// The caller keeps producing into `broadcast` (e.g. a relay proxying tracks from
1886	/// upstream); the requesters receive a consumer for it. The broadcast is *not*
1887	/// announced.
1888	pub fn accept(self, broadcast: impl Consume<broadcast::Consumer>) {
1889		let broadcast = broadcast.consume();
1890
1891		// Move the entry out of the in-flight queue and into the weak `served` cache, so repeat
1892		// requests for this path share the same broadcast instead of asking the handler to serve
1893		// (and subscribe upstream) again. Re-check under the lock: if a live broadcast was already
1894		// served for this path while we were fetching upstream, dedup onto it and drop ours rather
1895		// than replace a good entry with a duplicate subscription.
1896		let resolved = {
1897			let mut state = self.state.lock();
1898			let existing = state.served.insert(self.path.clone(), broadcast.weak());
1899			state
1900				.requests
1901				.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
1902			existing.map(|weak| weak.consume()).unwrap_or(broadcast)
1903		};
1904
1905		if let Ok(mut pending) = self.producer.write() {
1906			pending.resolved = Some(Ok(resolved));
1907		}
1908		// `self.producer` drops here, closing the channel; the value is still observable.
1909	}
1910
1911	/// Reject the request, resolving every awaiting requester with `err`.
1912	pub fn reject(self, err: Error) {
1913		self.state
1914			.lock()
1915			.requests
1916			.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
1917		if let Ok(mut state) = self.producer.write() {
1918			state.resolved = Some(Err(err));
1919		}
1920	}
1921}
1922
1923impl Drop for Request {
1924	fn drop(&mut self) {
1925		// Handed off but neither accepted nor rejected: drop the still-pending entry so its
1926		// producer clone (plus this one) closes the channel, resolving coalesced requesters to
1927		// `Unroutable` rather than hanging.
1928		//
1929		// The identity guard matters: `accept`/`reject` already removed our entry and released
1930		// the lock before we run, so a concurrent request for the same path may have registered
1931		// a *new* one here. Removing unconditionally would clobber it, stranding its requesters.
1932		self.state
1933			.lock()
1934			.requests
1935			.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
1936	}
1937}
1938
1939/// The pollable result of [`Consumer::request_broadcast`].
1940///
1941/// Awaited via the [`kio::Pending`] wrapper; resolves to the [`broadcast::Consumer`]
1942/// immediately when the broadcast was already announced, or once an [`Dynamic`]
1943/// handler serves the request. Resolves to an error if the request is rejected or every
1944/// handler drops before serving it.
1945pub struct Requesting {
1946	inner: RequestState,
1947	// Egress scope applied to the resolved broadcast, so its reads are attributed.
1948	// Empty (no-op) for an untagged consumer.
1949	stats: stats::Scope,
1950}
1951
1952enum RequestState {
1953	// Already announced: resolves immediately with a clone of this broadcast.
1954	Ready(broadcast::Consumer),
1955	// Unroutable at request time: resolves immediately with this error. Baked in so
1956	// `request_broadcast` itself stays infallible.
1957	Failed(Error),
1958	// Awaiting a handler: resolves when the request's result channel is written.
1959	Pending(kio::Consumer<PendingBroadcast>),
1960}
1961
1962impl Requesting {
1963	fn ready(broadcast: broadcast::Consumer) -> Self {
1964		Self {
1965			inner: RequestState::Ready(broadcast),
1966			stats: stats::Scope::default(),
1967		}
1968	}
1969
1970	fn failed(error: Error) -> Self {
1971		Self {
1972			inner: RequestState::Failed(error),
1973			stats: stats::Scope::default(),
1974		}
1975	}
1976
1977	fn pending(consumer: kio::Consumer<PendingBroadcast>) -> Self {
1978		Self {
1979			inner: RequestState::Pending(consumer),
1980			stats: stats::Scope::default(),
1981		}
1982	}
1983
1984	fn with_stats(mut self, scope: stats::Scope) -> Self {
1985		self.stats = scope;
1986		self
1987	}
1988
1989	/// Poll for the requested broadcast without blocking.
1990	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<broadcast::Consumer, Error>> {
1991		match &self.inner {
1992			RequestState::Ready(broadcast) => Poll::Ready(Ok(broadcast.clone().with_stats(self.stats.clone()))),
1993			RequestState::Failed(error) => Poll::Ready(Err(error.clone())),
1994			RequestState::Pending(consumer) => Poll::Ready(
1995				match ready!(consumer.poll(waiter, |state| match &state.resolved {
1996					Some(result) => Poll::Ready(result.clone()),
1997					None => Poll::Pending,
1998				})) {
1999					Ok(result) => result.map(|broadcast| broadcast.with_stats(self.stats.clone())),
2000					// Every handler dropped without resolving: nobody could route it.
2001					Err(_closed) => Err(Error::Unroutable),
2002				},
2003			),
2004		}
2005	}
2006}
2007
2008impl kio::Pollable for Requesting {
2009	type Output = Result<broadcast::Consumer, Error>;
2010
2011	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
2012		self.poll_ok(waiter)
2013	}
2014}
2015
2016/// Derive a read view from a handle.
2017///
2018/// Lets APIs accept either a producer or a consumer (e.g.
2019/// [`Client::with_publisher`](crate::Client::with_publisher),
2020/// [`Request::accept`]). The blanket `&T` impl means you can
2021/// pass by value (`foo(x)`) to hand off ownership, or by reference (`foo(&x)`)
2022/// to keep it, without spelling out `.consume()`.
2023pub trait Consume<T> {
2024	/// Derive a read view (a consumer) from this handle.
2025	fn consume(&self) -> T;
2026}
2027
2028impl<T, U: Consume<T>> Consume<T> for &U {
2029	fn consume(&self) -> T {
2030		(**self).consume()
2031	}
2032}
2033
2034impl Consume<Consumer> for Producer {
2035	fn consume(&self) -> Consumer {
2036		// Mirrors the inherent `Producer::consume`; inlined to avoid the
2037		// inherent-vs-trait `consume` ambiguity. Untagged: egress is tagged
2038		// separately from ingress.
2039		Consumer::new(
2040			self.info,
2041			self.root.clone(),
2042			self.nodes.clone(),
2043			self.dynamic.clone(),
2044			stats::Session::default(),
2045		)
2046	}
2047}
2048
2049impl Consume<Consumer> for Consumer {
2050	fn consume(&self) -> Consumer {
2051		self.clone()
2052	}
2053}
2054
2055impl Consume<broadcast::Consumer> for broadcast::Producer {
2056	fn consume(&self) -> broadcast::Consumer {
2057		// The inherent `consume` shadows this trait method, so this delegates.
2058		self.consume()
2059	}
2060}
2061
2062impl Consume<broadcast::Consumer> for broadcast::Consumer {
2063	fn consume(&self) -> broadcast::Consumer {
2064		self.clone()
2065	}
2066}
2067
2068impl Consume<track::Consumer> for track::Producer {
2069	fn consume(&self) -> track::Consumer {
2070		self.consume()
2071	}
2072}
2073
2074impl Consume<track::Consumer> for track::Consumer {
2075	fn consume(&self) -> track::Consumer {
2076		self.clone()
2077	}
2078}
2079
2080/// Cheap read handle over an origin's broadcast tree.
2081///
2082/// Clones share the underlying tree state without allocating any per-cursor
2083/// resources. To actually receive announce / unannounce events, call
2084/// [`Self::announced`] to obtain an [`AnnounceConsumer`].
2085#[derive(Clone)]
2086pub struct Consumer {
2087	// Identity of the origin this consumer was derived from.
2088	info: Origin,
2089	nodes: OriginNodes,
2090
2091	// A prefix that is automatically stripped from all paths.
2092	root: PathOwned,
2093
2094	// Shared fallback request queue, fed to any `Dynamic` handler on the
2095	// producer side. Used only by `request_broadcast`; announced lookups ignore it.
2096	dynamic: kio::Shared<OriginDynamicState>,
2097
2098	// Egress stats context. Broadcasts handed out through this consumer (and any
2099	// handle derived from them) are attributed to it (reads counted on the
2100	// publisher/egress side). Empty (no-op) unless a session tagged this handle.
2101	stats: stats::Session,
2102}
2103
2104impl std::ops::Deref for Consumer {
2105	type Target = Origin;
2106
2107	fn deref(&self) -> &Self::Target {
2108		&self.info
2109	}
2110}
2111
2112impl Consumer {
2113	fn new(
2114		info: Origin,
2115		root: PathOwned,
2116		nodes: OriginNodes,
2117		dynamic: kio::Shared<OriginDynamicState>,
2118		stats: stats::Session,
2119	) -> Self {
2120		Self {
2121			info,
2122			nodes,
2123			root,
2124			dynamic,
2125			stats,
2126		}
2127	}
2128
2129	/// Attach an egress stats context: broadcasts handed out through this handle (and
2130	/// any handle derived from it) are attributed to `session` on the publisher
2131	/// (egress) side. Pass [`stats::Session::default`] to opt out.
2132	pub fn with_stats(mut self, session: stats::Session) -> Self {
2133		self.stats = session;
2134		self
2135	}
2136
2137	/// A clone of this consumer with its stats context cleared, so an internal
2138	/// lookup stream (e.g. [`Self::announced_broadcast`]) doesn't drive the egress
2139	/// announce guards; the caller re-attributes the result itself.
2140	fn untagged(&self) -> Self {
2141		Self {
2142			stats: stats::Session::default(),
2143			..self.clone()
2144		}
2145	}
2146
2147	/// A view with this consumer's identity and root but no broadcasts:
2148	/// [`announced`](Self::announced) yields nothing. Used to answer a peer's
2149	/// announce-interest for a prefix outside our scope by announcing nothing,
2150	/// rather than tearing the stream down.
2151	pub(crate) fn empty(&self) -> Self {
2152		Self {
2153			info: self.info,
2154			nodes: OriginNodes { nodes: Vec::new() },
2155			root: self.root.clone(),
2156			dynamic: self.dynamic.clone(),
2157			stats: self.stats.clone(),
2158		}
2159	}
2160
2161	/// Subscribe to announce / unannounce events for this consumer's subtree.
2162	///
2163	/// Allocates a per-cursor coalescing buffer, registers it with each root
2164	/// in this consumer's scope, and replays the currently active broadcast
2165	/// set as initial announcements. Drop the returned [`AnnounceConsumer`]
2166	/// to unregister.
2167	pub fn announced(&self) -> AnnounceConsumer {
2168		AnnounceConsumer::new(self.root.clone(), self.nodes.clone(), self.stats.clone())
2169	}
2170
2171	/// Returns a cheap duplicate of this read handle.
2172	pub fn consume(&self) -> Self {
2173		self.clone()
2174	}
2175
2176	/// Internal synchronous peek: the broadcast at `path` if it is *already* announced.
2177	///
2178	/// Races announcement gossip (a freshly-connected consumer sees `None` even when the
2179	/// broadcast is about to arrive), so it is not public. [`Self::request_broadcast`] is the
2180	/// public lookup: it builds on this for the announced case, then falls back to a dynamic
2181	/// handler. [`Self::announced_broadcast`] waits for a future announcement.
2182	fn get_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
2183		let path = path.as_path();
2184		let (root, rest) = self.nodes.get(&path)?;
2185		let state = root.lock();
2186		state.consume_broadcast(&rest)
2187	}
2188
2189	/// Block until a broadcast with the given path is announced and return it.
2190	///
2191	/// Returns `None` if the path is outside this consumer's allowed prefixes or if the consumer
2192	/// is closed before the broadcast is announced. The returned broadcast may itself be closed
2193	/// later. Subscribers should watch [`broadcast::Consumer::closed`] to react to that.
2194	///
2195	/// Prefer this over [`Self::request_broadcast`] when you know the exact path you want but
2196	/// cannot guarantee the announcement has already been received. With moq-lite-05 (and
2197	/// the older Lite01/02) `connect()` already blocks until the initial announce set lands,
2198	/// so [`Self::request_broadcast`] is race-free for broadcasts that were live at connect time;
2199	/// this method is still needed to wait for a broadcast that comes online *after* connect.
2200	pub async fn announced_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
2201		let path = path.as_path();
2202
2203		// Scope a fresh consumer down to this path so we only wake up for relevant announcements.
2204		let consumer = self.scope(std::slice::from_ref(&path))?;
2205
2206		// `scope` keeps narrower permissions intact: if we ask for `foo` on a consumer limited
2207		// to `foo/specific`, `scope` returns a consumer scoped to `foo/specific`. No
2208		// announcement at the exact path `foo` can ever arrive. Bail rather than loop forever.
2209		if !consumer.allowed().any(|allowed| path.has_prefix(allowed)) {
2210			return None;
2211		}
2212
2213		// Use an untagged stream: this is a lookup, not egress announce forwarding, so
2214		// it must not drive the announce guards. The matched result is attributed
2215		// with the egress scope instead.
2216		let mut announced = consumer.untagged().announced();
2217		let scope = self.stats.egress(self.root.join(&path).to_owned());
2218		loop {
2219			let OriginAnnounce {
2220				path: announced_path,
2221				broadcast,
2222			} = announced.next().await?;
2223			// `scope` narrows by prefix, but we only want an exact-path match.
2224			if announced_path.as_path() == path
2225				&& let Some(broadcast) = broadcast
2226			{
2227				return Some(broadcast.with_stats(scope));
2228			}
2229		}
2230	}
2231
2232	/// Returns a new Consumer restricted to broadcasts under one of `prefixes`.
2233	///
2234	/// Returns None if there are no legal prefixes (the requested prefixes are
2235	/// disjoint from this consumer's current scope, so it would always return None).
2236	// TODO accept PathPrefixes instead of &[Path]
2237	pub fn scope(&self, prefixes: &[Path]) -> Option<Consumer> {
2238		let prefixes = PathPrefixes::new(prefixes);
2239		Some(Consumer::new(
2240			self.info,
2241			self.root.clone(),
2242			self.nodes.select(&prefixes)?,
2243			self.dynamic.clone(),
2244			self.stats.clone(),
2245		))
2246	}
2247
2248	/// Get a broadcast by path, falling back to a dynamic request when it is not announced.
2249	///
2250	/// Returns a [`kio::Pending`] future (resolved synchronously for an announced broadcast,
2251	/// otherwise once a handler serves it), mirroring [`track::Consumer::fetch_group`](track::Consumer::fetch_group).
2252	/// The lookup order is: an already-announced broadcast resolves
2253	/// immediately; otherwise, if an [`Dynamic`] handler is live (see
2254	/// [`Producer::dynamic`]), a fallback request is registered and the future resolves
2255	/// when the handler [`accept`](Request::accept)s it (or errors if it
2256	/// [`reject`](Request::reject)s or every handler drops). Concurrent requests for
2257	/// the same unannounced path coalesce onto one handler request, and once served the
2258	/// broadcast is cached weakly so *later* requests for that path also share it (rather
2259	/// than re-invoking the handler and opening a duplicate upstream subscription) for as
2260	/// long as it stays live; a closed one is re-served on the next request.
2261	///
2262	/// The returned future resolves to [`Error::Unroutable`] when the path is not announced and no
2263	/// dynamic handler exists. A request that is registered while a handler is live but then loses
2264	/// every handler before being served also resolves to [`Error::Unroutable`]. Unlike an announced
2265	/// broadcast, a dynamically served one is never visible to [`Self::announced`].
2266	pub fn request_broadcast(&self, path: impl AsPath) -> kio::Pending<Requesting> {
2267		let path = path.as_path();
2268
2269		// Key requests by absolute path so a scoped/rooted consumer and the handler
2270		// (which may have a different root) agree on the same entry, and so the egress
2271		// counters resolve against the same broadcast the ingress side wrote.
2272		let absolute = self.root.join(&path).to_owned();
2273		let scope = self.stats.egress(&absolute);
2274
2275		// Prefer a live announcement when one is present; the dynamic queue is only a fallback.
2276		if let Some(broadcast) = self.get_broadcast(&path) {
2277			return kio::Pending::new(Requesting::ready(broadcast).with_stats(scope));
2278		}
2279
2280		let mut state = self.dynamic.lock();
2281
2282		// Reuse a still-live broadcast a handler already served for this path, so repeat
2283		// requests share one upstream subscription. A closed entry is stale; `get` drops it
2284		// and returns `None`, so we fall through and re-serve below.
2285		if let Some(weak) = state.served.get(&absolute) {
2286			return kio::Pending::new(Requesting::ready(weak.consume()).with_stats(scope));
2287		}
2288
2289		// Coalesce onto a pending request for the same path; otherwise register a new
2290		// one, unless there is no handler alive to serve it.
2291		let consumer = if let Some(producer) = state.requests.join(&absolute) {
2292			producer.consume()
2293		} else {
2294			let producer = kio::Producer::<PendingBroadcast>::default();
2295			let consumer = producer.consume();
2296			if state.requests.insert(absolute, producer).is_err() {
2297				return kio::Pending::new(Requesting::failed(Error::Unroutable));
2298			}
2299			consumer
2300		};
2301
2302		kio::Pending::new(Requesting::pending(consumer).with_stats(scope))
2303	}
2304
2305	/// Returns a new Consumer that automatically strips out the provided prefix.
2306	///
2307	/// Returns None if the provided root is not authorized; when [`Self::scope`] was
2308	/// already used without a wildcard.
2309	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
2310		let prefix = prefix.as_path();
2311
2312		Some(Self::new(
2313			self.info,
2314			self.root.join(&prefix).to_owned(),
2315			self.nodes.root(&prefix)?,
2316			self.dynamic.clone(),
2317			self.stats.clone(),
2318		))
2319	}
2320
2321	/// Returns the prefix that is automatically stripped from all paths.
2322	pub fn root(&self) -> &Path<'_> {
2323		&self.root
2324	}
2325
2326	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
2327	// TODO return PathPrefixes
2328	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
2329		self.nodes.nodes.iter().map(|(root, _)| root)
2330	}
2331
2332	/// Converts a relative path to an absolute path.
2333	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
2334		self.root.join(path)
2335	}
2336}
2337
2338/// Handle to the announcement stream for a subtree.
2339///
2340/// Symmetric counterpart of [`AnnounceConsumer`]. Cheap to clone; call
2341/// [`Self::consume`] to obtain an [`AnnounceConsumer`] that receives events.
2342#[derive(Clone)]
2343pub struct AnnounceProducer {
2344	nodes: OriginNodes,
2345	root: PathOwned,
2346}
2347
2348impl AnnounceProducer {
2349	fn new(root: PathOwned, nodes: OriginNodes) -> Self {
2350		Self { nodes, root }
2351	}
2352
2353	/// Subscribe to announce / unannounce events for this subtree.
2354	///
2355	/// Allocates a per-cursor coalescing buffer and replays the currently active broadcast set
2356	/// as initial announcements. Drop the returned [`AnnounceConsumer`] to
2357	/// unregister.
2358	pub fn consume(&self) -> AnnounceConsumer {
2359		// Untagged: `AnnounceProducer` is used for internal announce plumbing, not
2360		// egress attribution (which flows through `origin::Consumer::announced`).
2361		AnnounceConsumer::new(self.root.clone(), self.nodes.clone(), stats::Session::default())
2362	}
2363
2364	/// Returns the prefix that is automatically stripped from announced paths.
2365	pub fn root(&self) -> &Path<'_> {
2366		&self.root
2367	}
2368}
2369
2370/// Receives announce / unannounce events for a subtree.
2371///
2372/// Created by [`Consumer::announced`] or [`AnnounceProducer::consume`].
2373/// Drop to unregister.
2374pub struct AnnounceConsumer {
2375	id: ConsumerId,
2376	nodes: OriginNodes,
2377	root: PathOwned,
2378
2379	// Pending updates queued for this cursor. Coalesced so a slow consumer
2380	// can't accumulate redundant announce/unannounce pairs.
2381	state: kio::Producer<OriginConsumerState>,
2382
2383	// Egress stats context (empty for an untagged stream). Announce events drive the
2384	// per-broadcast announce guards below and tag the broadcasts handed out.
2385	stats: stats::Session,
2386
2387	// Live egress announce guards, keyed by absolute broadcast path. An announce
2388	// opens one (bumping `announced` + `announced_bytes`); the matching unannounce
2389	// drops it (bumping `announced_closed` + `announced_bytes`).
2390	guards: HashMap<PathOwned, stats::Announce>,
2391}
2392
2393impl AnnounceConsumer {
2394	fn new(root: PathOwned, nodes: OriginNodes, stats: stats::Session) -> Self {
2395		let state = kio::Producer::<OriginConsumerState>::default();
2396		let id = ConsumerId::new();
2397
2398		for (_, node) in &nodes.nodes {
2399			let notify = AnnounceConsumerNotify {
2400				root: root.clone(),
2401				state: state.clone(),
2402			};
2403			node.lock().consume(id, notify);
2404		}
2405
2406		Self {
2407			id,
2408			nodes,
2409			root,
2410			state,
2411			stats,
2412			guards: HashMap::new(),
2413		}
2414	}
2415
2416	/// Drive the egress announce guards and tag the broadcast for one update.
2417	///
2418	/// An announce opens a guard (keyed by absolute path) and tags the yielded
2419	/// broadcast with the egress scope; an unannounce drops the guard. A no-op for
2420	/// an untagged stream.
2421	fn attribute(&mut self, update: OriginAnnounce) -> OriginAnnounce {
2422		let OriginAnnounce { path, broadcast } = update;
2423		let absolute = self.root.join(&path).to_owned();
2424		match broadcast {
2425			Some(broadcast) => {
2426				let scope = self.stats.egress(&absolute);
2427				self.guards.entry(absolute).or_insert_with(|| scope.announce());
2428				OriginAnnounce {
2429					path,
2430					broadcast: Some(broadcast.with_stats(scope)),
2431				}
2432			}
2433			None => {
2434				self.guards.remove(&absolute);
2435				OriginAnnounce { path, broadcast: None }
2436			}
2437		}
2438	}
2439
2440	/// Returns the next (un)announced broadcast and its path relative to this
2441	/// cursor's root.
2442	///
2443	/// The broadcast will only be announced if it was previously unannounced.
2444	/// The same path won't be announced/unannounced twice in a row; instead it
2445	/// toggles. Returns None if the cursor is closed.
2446	pub async fn next(&mut self) -> Option<OriginAnnounce> {
2447		kio::wait(|waiter| self.poll_next(waiter)).await
2448	}
2449
2450	/// Poll for the next (un)announced broadcast, without blocking.
2451	///
2452	/// Returns `Poll::Ready(Some(_))` for an update, `Poll::Ready(None)` if the
2453	/// cursor is closed, or `Poll::Pending` after registering `waiter` to be
2454	/// notified when the next update arrives.
2455	pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<OriginAnnounce>> {
2456		let update = {
2457			let mut state = match ready!(self.state.poll(waiter, |state| {
2458				if state.pending.is_empty() {
2459					Poll::Pending
2460				} else {
2461					Poll::Ready(())
2462				}
2463			})) {
2464				Ok(state) => state,
2465				// Closed: discard the Ref so its MutexGuard doesn't escape this call.
2466				Err(_) => return Poll::Ready(None),
2467			};
2468			state.take().expect("predicate guaranteed an update")
2469		};
2470		Poll::Ready(Some(self.attribute(update)))
2471	}
2472
2473	/// Returns the next (un)announced broadcast without blocking.
2474	///
2475	/// Returns None if there is no update available; NOT because the cursor is closed.
2476	/// Use [`Self::is_closed`] to check if the cursor is closed.
2477	pub fn try_next(&mut self) -> Option<OriginAnnounce> {
2478		let update = self.state.write().ok()?.take()?;
2479		Some(self.attribute(update))
2480	}
2481
2482	/// Returns true if the cursor is closed (no more updates will arrive).
2483	pub fn is_closed(&self) -> bool {
2484		self.state.write().is_err()
2485	}
2486
2487	/// Returns the prefix that is automatically stripped from emitted paths.
2488	pub fn root(&self) -> &Path<'_> {
2489		&self.root
2490	}
2491
2492	/// Converts a relative path to an absolute path.
2493	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
2494		self.root.join(path)
2495	}
2496}
2497
2498impl Drop for AnnounceConsumer {
2499	fn drop(&mut self) {
2500		for (_, root) in &self.nodes.nodes {
2501			root.lock().unconsume(self.id);
2502		}
2503	}
2504}
2505
2506#[cfg(test)]
2507use futures::FutureExt;
2508
2509#[cfg(test)]
2510#[allow(missing_docs)] // test-only assertion helpers
2511impl AnnounceConsumer {
2512	pub fn assert_next(&mut self, expected: impl AsPath, broadcast: &broadcast::Consumer) {
2513		let expected = expected.as_path();
2514		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2515		assert_eq!(announce.path, expected, "wrong path");
2516		let announced = announce.broadcast.expect("should be an active announce");
2517		assert!(announced.is_clone(broadcast), "should be the same broadcast");
2518	}
2519
2520	/// An announce for `expected`, without asserting which broadcast backs it
2521	/// (the origin owns the announced broadcast, not the publisher). Returns the
2522	/// announced consumer.
2523	pub fn assert_next_some(&mut self, expected: impl AsPath) -> broadcast::Consumer {
2524		let expected = expected.as_path();
2525		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2526		assert_eq!(announce.path, expected, "wrong path");
2527		announce.broadcast.expect("should be an active announce")
2528	}
2529
2530	pub fn assert_try_next(&mut self, expected: impl AsPath, broadcast: &broadcast::Consumer) {
2531		let expected = expected.as_path();
2532		let announce = self.try_next().expect("no next");
2533		assert_eq!(announce.path, expected, "wrong path");
2534		let announced = announce.broadcast.expect("should be an active announce");
2535		assert!(announced.is_clone(broadcast), "should be the same broadcast");
2536	}
2537
2538	/// The `try_next` counterpart of [`Self::assert_next_some`].
2539	pub fn assert_try_next_some(&mut self, expected: impl AsPath) -> broadcast::Consumer {
2540		let expected = expected.as_path();
2541		let announce = self.try_next().expect("no next");
2542		assert_eq!(announce.path, expected, "wrong path");
2543		announce.broadcast.expect("should be an active announce")
2544	}
2545
2546	pub fn assert_next_none(&mut self, expected: impl AsPath) {
2547		let expected = expected.as_path();
2548		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2549		assert_eq!(announce.path, expected, "wrong path");
2550		assert!(announce.broadcast.is_none(), "should be unannounced");
2551	}
2552
2553	pub fn assert_next_wait(&mut self) {
2554		if let Some(res) = self.next().now_or_never() {
2555			panic!("next should block: got {:?}", res.map(|a| a.path));
2556		}
2557	}
2558
2559	/*
2560	pub fn assert_next_closed(&mut self) {
2561		assert!(
2562			self.next().now_or_never().expect("next blocked").is_none(),
2563			"next should be closed"
2564		);
2565	}
2566	*/
2567}
2568
2569#[cfg(test)]
2570mod tests {
2571	use crate::coding::Decode;
2572	use crate::group;
2573
2574	use super::*;
2575
2576	/// An announced direct route.
2577	fn announce() -> broadcast::Route {
2578		broadcast::Route::new().with_announce(true)
2579	}
2580
2581	/// The first origin whose handover key for `name` sits above (`true`) or below
2582	/// (`false`) the peer's, so tests exercising the carrying gate are
2583	/// deterministic instead of hinging on a random id winning a hash comparison.
2584	/// Starts searching above the small ids the tests use in hop chains, so the
2585	/// result never collides with a hop (a looping chain trips a debug_assert).
2586	fn origin_keyed(name: &str, peer: Origin, above: bool) -> Origin {
2587		let name = Path::new(name);
2588		let peer_key = fnv_key(&name, [peer]);
2589		(100u64..)
2590			.map(|id| Origin::new(id).unwrap())
2591			.find(|origin| (fnv_key(&name, [*origin]) > peer_key) == above)
2592			.unwrap()
2593	}
2594
2595	/// A front table for reselect tests: routes get ids in order, the first is
2596	/// the incumbent.
2597	fn front_state(self_origin: Origin, routes: Vec<broadcast::Route>) -> FrontState {
2598		let source = broadcast::Info::new().produce().consume();
2599		FrontState {
2600			path: Path::new("test").to_owned(),
2601			self_origin,
2602			next_route: routes.len() as u64,
2603			routes: routes
2604				.into_iter()
2605				.enumerate()
2606				.map(|(id, route)| FrontRoute {
2607					id: id as u64,
2608					route,
2609					source: source.clone(),
2610				})
2611				.collect(),
2612			active: Some(0),
2613			linger: Duration::ZERO,
2614			closed: false,
2615		}
2616	}
2617
2618	/// A route as a warm sibling would announce it: zero cost, chain ending at
2619	/// the announcing peer.
2620	fn sibling_route(peer: Origin) -> broadcast::Route {
2621		let hops = OriginList::try_from(vec![Origin::new(90).unwrap(), peer]).unwrap();
2622		announce().with_hops(hops)
2623	}
2624
2625	/// A route as the upstream announces it: priced, one hop.
2626	fn upstream_route(cost: u64) -> broadcast::Route {
2627		let hops = OriginList::try_from(vec![Origin::new(90).unwrap()]).unwrap();
2628		announce().with_hops(hops).with_cost(cost)
2629	}
2630
2631	/// While carrying, a strictly cheaper route from a peer that hashes above us
2632	/// must not displace the incumbent; the same table re-parents freely once
2633	/// idle, or when the peer hashes below us.
2634	#[test]
2635	fn test_carrying_gate_keys() {
2636		let peer = Origin::new(3).unwrap();
2637
2638		// We lose the key comparison: stay put while carrying, migrate when idle.
2639		let mut lost = front_state(
2640			origin_keyed("test", peer, false),
2641			vec![upstream_route(10), sibling_route(peer)],
2642		);
2643		lost.reselect(true);
2644		assert_eq!(
2645			lost.active,
2646			Some(0),
2647			"carrying front re-parented onto a higher-keyed peer"
2648		);
2649		lost.reselect(false);
2650		assert_eq!(lost.active, Some(1), "idle front must take the cheaper route");
2651
2652		// We win the key comparison: re-parent even while carrying.
2653		let mut won = front_state(
2654			origin_keyed("test", peer, true),
2655			vec![upstream_route(10), sibling_route(peer)],
2656		);
2657		won.reselect(true);
2658		assert_eq!(won.active, Some(1), "carrying front must follow a lower-keyed peer");
2659	}
2660
2661	/// The simultaneous-activation race: two relays that each pulled the same
2662	/// broadcast independently see each other's zero-cost route. Exactly one of
2663	/// them re-parents; the other keeps its upstream, so the broadcast is never
2664	/// left without a source.
2665	#[test]
2666	fn test_carrying_gate_symmetric_race() {
2667		let a = Origin::new(1).unwrap();
2668		let b = Origin::new(2).unwrap();
2669
2670		let mut a_view = front_state(a, vec![upstream_route(10), sibling_route(b)]);
2671		let mut b_view = front_state(b, vec![upstream_route(10), sibling_route(a)]);
2672		a_view.reselect(true);
2673		b_view.reselect(true);
2674
2675		let a_moved = a_view.active == Some(1);
2676		let b_moved = b_view.active == Some(1);
2677		assert!(
2678			a_moved != b_moved,
2679			"exactly one side must re-parent (a: {a_moved}, b: {b_moved})"
2680		);
2681	}
2682
2683	/// The gate is scoped to warm siblings: a cheaper route via a relay that is
2684	/// not itself carrying (advertised nonzero), or directly from the original
2685	/// publisher (single-hop chain), is taken immediately even while carrying
2686	/// and even when we would lose the key comparison.
2687	#[test]
2688	fn test_carrying_switches_to_benign_routes() {
2689		let peer = Origin::new(3).unwrap();
2690		let lost = origin_keyed("test", peer, false);
2691
2692		// A cheaper forwarder path: the relay advertised its accumulated cost.
2693		let mut forwarder = sibling_route(peer).with_cost(4);
2694		forwarder.advertised = 4;
2695		let mut state = front_state(lost, vec![upstream_route(10), forwarder]);
2696		state.reselect(true);
2697		assert_eq!(
2698			state.active,
2699			Some(1),
2700			"a cheaper forwarder path must win while carrying"
2701		);
2702
2703		// Directly from the original publisher: single-hop chain, advertised zero.
2704		let direct = announce().with_hops(OriginList::try_from(vec![peer]).unwrap());
2705		let mut state = front_state(lost, vec![upstream_route(10), direct]);
2706		state.reselect(true);
2707		assert_eq!(
2708			state.active,
2709			Some(1),
2710			"a direct publisher route must win while carrying"
2711		);
2712	}
2713
2714	/// The gate only protects an announced incumbent: one that lost its announce
2715	/// (the upstream retracted) is displaced regardless of the key comparison.
2716	#[test]
2717	fn test_carrying_gate_ignores_unannounced_incumbent() {
2718		let peer = Origin::new(3).unwrap();
2719		let unannounced = upstream_route(10).with_announce(false);
2720		let mut state = front_state(
2721			origin_keyed("test", peer, false),
2722			vec![unannounced, sibling_route(peer)],
2723		);
2724		state.reselect(true);
2725		assert_eq!(
2726			state.active,
2727			Some(1),
2728			"an unannounced incumbent must always be displaced"
2729		);
2730	}
2731
2732	/// Let the spawned origin tasks (source watchers, front dispatch) run. The
2733	/// tests pause tokio time, so this advances the clock instantly.
2734	async fn settle() {
2735		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
2736	}
2737
2738	/// Serve one requested track from a source like a session would: wait for the
2739	/// origin to dispatch it, then accept with default info.
2740	async fn accept_track(dynamic: &mut broadcast::Dynamic, name: &str) -> track::Producer {
2741		let request = tokio::time::timeout(std::time::Duration::from_secs(1), dynamic.requested_track())
2742			.await
2743			.expect("timed out waiting for a track request")
2744			.expect("source closed");
2745		assert_eq!(request.name(), name, "unexpected track dispatched");
2746		request.accept(None)
2747	}
2748
2749	/// Tagging both origin handles with one context attributes the full model path:
2750	/// ingress writes on the subscriber side, egress reads on the publisher side,
2751	/// each counter landing exactly once (the model-layer silent-zero guard).
2752	#[tokio::test]
2753	async fn test_stats_tagged_end_to_end() {
2754		use crate::Timestamp;
2755		use crate::stats::{Config, Registry, Tier};
2756		use bytes::Bytes;
2757
2758		tokio::time::pause();
2759
2760		let registry = Registry::new(Config::new());
2761		let ctx = registry.tier(Tier::default()).session("acme");
2762
2763		let origin = Origin::random().produce();
2764		let ingress = origin.clone().with_stats(ctx.clone());
2765		let egress = origin.consume().with_stats(ctx.clone());
2766
2767		// Egress announce stream: this is the tagged stream that drives the egress
2768		// announce guard.
2769		let mut announced = egress.announced();
2770
2771		// Ingress publishes an announced broadcast.
2772		let source = ingress.create_broadcast("demo", announce()).unwrap();
2773		let mut dynamic = source.dynamic();
2774		settle().await;
2775		settle().await;
2776
2777		// Egress observes the announce and gets the tagged broadcast.
2778		let update = announced.next().await.unwrap();
2779		assert_eq!(update.path.as_str(), "demo");
2780		let broadcast = update.broadcast.unwrap();
2781
2782		// Egress subscribes; the ingress side serves the track on demand.
2783		let subscribing = broadcast.track("video").unwrap().subscribe(None);
2784		let mut producer = accept_track(&mut dynamic, "video").await;
2785		settle().await;
2786		let mut sub = subscribing.await.unwrap();
2787
2788		// Ingress writes one group with two 5-byte frames.
2789		let mut group = producer.append_group().unwrap();
2790		group
2791			.write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
2792			.unwrap();
2793		group
2794			.write_frame(Timestamp::ZERO, Bytes::from_static(b"world"))
2795			.unwrap();
2796		group.finish().unwrap();
2797
2798		// Egress reads the group and both frames.
2799		let mut group_c = sub.recv_group().await.unwrap().unwrap();
2800		let mut frames = 0;
2801		while let Some(frame) = group_c.read_frame().await.unwrap() {
2802			assert_eq!(frame.payload.len(), 5);
2803			frames += 1;
2804		}
2805		assert_eq!(frames, 2);
2806		settle().await;
2807
2808		let report = registry.report();
2809		let entry = report
2810			.traffic
2811			.iter()
2812			.find(|e| e.path.as_str() == "demo")
2813			.expect("demo tracked");
2814		let path_len = "demo".len() as u64;
2815
2816		// Egress (publisher side): reads out of the model.
2817		let egress = &entry.publisher;
2818		assert_eq!(egress.announced, 1, "one egress announce");
2819		assert_eq!(egress.announced_bytes, path_len);
2820		assert_eq!(egress.subscriptions, 1, "one egress subscription");
2821		assert_eq!(egress.broadcasts, 1, "one viewer");
2822		assert_eq!(egress.groups, 1);
2823		assert_eq!(egress.frames, 2);
2824		assert_eq!(egress.bytes, 10);
2825		assert_eq!(egress.fetches, 0);
2826
2827		// Ingress (subscriber side): writes into the model.
2828		let ingress = &entry.subscriber;
2829		assert_eq!(ingress.announced, 1, "one ingress announce");
2830		assert_eq!(ingress.announced_bytes, path_len);
2831		assert_eq!(ingress.subscriptions, 1, "one ingress track");
2832		assert_eq!(ingress.broadcasts, 0, "ingress has no viewer refcount");
2833		assert_eq!(ingress.groups, 1);
2834		assert_eq!(ingress.frames, 2);
2835		assert_eq!(ingress.bytes, 10);
2836
2837		// A fetch bumps only `fetches` on the egress side, plus the delivered group.
2838		let fetched = broadcast.track("video").unwrap().fetch_group(0, None).await.unwrap();
2839		let _ = fetched;
2840		settle().await;
2841		let report = registry.report();
2842		let entry = report.traffic.iter().find(|e| e.path.as_str() == "demo").unwrap();
2843		assert_eq!(entry.publisher.fetches, 1, "one fetch");
2844		assert_eq!(entry.publisher.subscriptions, 1, "fetch does not bump subscriptions");
2845		assert_eq!(entry.publisher.broadcasts, 1, "fetch does not bump the viewer refcount");
2846		// `fetches` is egress-only for the same structural reason as `broadcasts`:
2847		// only a `track::Consumer` can fetch, and the ingress scope never reaches one
2848		// (`broadcast::Producer::consume` hands out an untagged consumer).
2849		assert_eq!(entry.subscriber.fetches, 0, "ingress cannot fetch");
2850	}
2851
2852	/// `Subscriber::read_frame` collapses a group to its first frame. The paths it
2853	/// delegates to (plain and spliced) build their own *unmetered* group consumers,
2854	/// so the wrapper is the only place that can attribute the read: exactly one
2855	/// group, one frame, and the payload bytes, counted once each.
2856	#[tokio::test]
2857	async fn test_stats_read_frame_counts_once() {
2858		use crate::Timestamp;
2859		use crate::stats::{Config, Registry, Tier};
2860		use bytes::Bytes;
2861
2862		tokio::time::pause();
2863
2864		let registry = Registry::new(Config::new());
2865		let ctx = registry.tier(Tier::default()).session("acme");
2866
2867		let origin = Origin::random().produce();
2868		let ingress = origin.clone().with_stats(ctx.clone());
2869		let egress = origin.consume().with_stats(ctx.clone());
2870
2871		let mut announced = egress.announced();
2872		let source = ingress.create_broadcast("demo", announce()).unwrap();
2873		let mut dynamic = source.dynamic();
2874		settle().await;
2875		settle().await;
2876
2877		let broadcast = announced.next().await.unwrap().broadcast.unwrap();
2878		let subscribing = broadcast.track("video").unwrap().subscribe(None);
2879		let mut producer = accept_track(&mut dynamic, "video").await;
2880		settle().await;
2881		let mut sub = subscribing.await.unwrap();
2882
2883		// A single-frame group, read back through the collapsing helper.
2884		producer
2885			.write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
2886			.unwrap();
2887
2888		let frame = sub.read_frame().await.unwrap().expect("frame");
2889		assert_eq!(frame.payload.len(), 5);
2890		settle().await;
2891
2892		let report = registry.report();
2893		let entry = report
2894			.traffic
2895			.iter()
2896			.find(|e| e.path.as_str() == "demo")
2897			.expect("demo tracked");
2898		assert_eq!(entry.publisher.groups, 1, "one group, counted once");
2899		assert_eq!(entry.publisher.frames, 1, "one frame, counted once");
2900		assert_eq!(
2901			entry.publisher.bytes, 5,
2902			"payload counted once, not zero and not doubled"
2903		);
2904	}
2905
2906	/// Datagrams bypass the group/frame handles entirely, so they're metered at the
2907	/// producer (ingress write) and the subscriber (egress read). Each one counts as
2908	/// the single-frame group it stands in for, plus the `datagrams` breakout.
2909	#[tokio::test]
2910	async fn test_stats_datagrams_counted_both_sides() {
2911		use crate::Timestamp;
2912		use crate::stats::{Config, Registry, Tier};
2913
2914		tokio::time::pause();
2915
2916		let registry = Registry::new(Config::new());
2917		let ctx = registry.tier(Tier::default()).session("acme");
2918
2919		let origin = Origin::random().produce();
2920		let ingress = origin.clone().with_stats(ctx.clone());
2921		let egress = origin.consume().with_stats(ctx.clone());
2922
2923		let mut announced = egress.announced();
2924		let source = ingress.create_broadcast("demo", announce()).unwrap();
2925		let mut dynamic = source.dynamic();
2926		settle().await;
2927		settle().await;
2928
2929		let broadcast = announced.next().await.unwrap().broadcast.unwrap();
2930		let subscribing = broadcast.track("video").unwrap().subscribe(None);
2931		let mut producer = accept_track(&mut dynamic, "video").await;
2932		settle().await;
2933		let mut sub = subscribing.await.unwrap();
2934
2935		producer.append_datagram(Timestamp::ZERO, &b"hello"[..]).unwrap();
2936		let datagram = sub.recv_datagram().await.unwrap().expect("datagram");
2937		assert_eq!(&datagram.payload[..], b"hello");
2938		settle().await;
2939
2940		let report = registry.report();
2941		let entry = report
2942			.traffic
2943			.iter()
2944			.find(|e| e.path.as_str() == "demo")
2945			.expect("demo tracked");
2946
2947		for (side, traffic) in [("egress", &entry.publisher), ("ingress", &entry.subscriber)] {
2948			assert_eq!(traffic.datagrams, 1, "{side}: one datagram");
2949			assert_eq!(traffic.groups, 1, "{side}: counted as its single-frame group");
2950			assert_eq!(traffic.frames, 1, "{side}: one frame");
2951			assert_eq!(traffic.bytes, 5, "{side}: payload counted once");
2952		}
2953	}
2954
2955	#[test]
2956	fn origin_rejects_reserved_ids() {
2957		assert!(Origin::new(0).is_err());
2958		assert!(Origin::new(1u64 << 62).is_err());
2959		assert_eq!(Origin::new(1).unwrap().id(), 1);
2960
2961		let mut zero = [0u8].as_slice();
2962		assert_eq!(
2963			Origin::decode(&mut zero, crate::lite::Version::Lite05).unwrap(),
2964			Origin::UNKNOWN
2965		);
2966	}
2967
2968	#[test]
2969	fn origin_list_push_fails_at_limit() {
2970		let mut list = OriginList::new();
2971		for _ in 0..MAX_HOPS {
2972			list.push(Origin::random()).unwrap();
2973		}
2974		assert_eq!(list.len(), MAX_HOPS);
2975		assert_eq!(list.push(Origin::random()), Err(TooManyOrigins));
2976	}
2977
2978	#[test]
2979	fn origin_list_replace_first() {
2980		let mut list = OriginList::new();
2981		for _ in 0..3 {
2982			list.push(Origin::UNKNOWN).unwrap();
2983		}
2984
2985		// Rewrites only the first placeholder, keeping the length the same.
2986		assert!(list.replace_first(Origin::UNKNOWN, Origin::new(7).unwrap()));
2987		assert_eq!(
2988			list.as_slice(),
2989			&[Origin::new(7).unwrap(), Origin::UNKNOWN, Origin::UNKNOWN]
2990		);
2991
2992		// No match leaves the list untouched.
2993		assert!(!list.replace_first(Origin::new(99).unwrap(), Origin::new(8).unwrap()));
2994		assert_eq!(list.len(), 3);
2995	}
2996
2997	#[test]
2998	fn origin_list_try_from_vec_enforces_limit() {
2999		let under: Vec<Origin> = (0..MAX_HOPS).map(|_| Origin::random()).collect();
3000		assert!(OriginList::try_from(under).is_ok());
3001
3002		let over: Vec<Origin> = (0..MAX_HOPS + 1).map(|_| Origin::random()).collect();
3003		assert_eq!(OriginList::try_from(over), Err(TooManyOrigins));
3004	}
3005
3006	#[tokio::test]
3007	async fn test_announce() {
3008		tokio::time::pause();
3009
3010		let origin = Origin::random().produce();
3011
3012		let mut consumer1 = origin.consume().announced();
3013		consumer1.assert_next_wait();
3014
3015		// Publish the first broadcast; it becomes visible asynchronously.
3016		let mut broadcast1 = origin.create_broadcast("test1", announce()).unwrap();
3017		settle().await;
3018
3019		consumer1.assert_next_some("test1");
3020		consumer1.assert_next_wait();
3021
3022		// Make a new consumer that should get the existing broadcast.
3023		// But we don't consume it yet.
3024		let mut consumer2 = origin.consume().announced();
3025
3026		// Publish the second broadcast.
3027		let mut broadcast2 = origin.create_broadcast("test2", announce()).unwrap();
3028		settle().await;
3029
3030		consumer1.assert_next_some("test2");
3031		consumer1.assert_next_wait();
3032
3033		consumer2.assert_next_some("test1");
3034		consumer2.assert_next_some("test2");
3035		consumer2.assert_next_wait();
3036
3037		// Finish the first broadcast: a graceful end unannounces immediately.
3038		broadcast1.finish();
3039		settle().await;
3040
3041		// All consumers should get a None now.
3042		consumer1.assert_next_none("test1");
3043		consumer2.assert_next_none("test1");
3044		consumer1.assert_next_wait();
3045		consumer2.assert_next_wait();
3046
3047		// And a new consumer only gets the last broadcast.
3048		let mut consumer3 = origin.consume().announced();
3049		consumer3.assert_next_some("test2");
3050		consumer3.assert_next_wait();
3051
3052		broadcast2.finish();
3053		settle().await;
3054
3055		consumer1.assert_next_none("test2");
3056		consumer2.assert_next_none("test2");
3057		consumer3.assert_next_none("test2");
3058	}
3059
3060	/// Multiple sources created at one path feed a single origin-owned broadcast:
3061	/// one announce, no churn as sources come and go, and an unannounce only when
3062	/// the last source leaves.
3063	#[tokio::test]
3064	async fn test_duplicate() {
3065		tokio::time::pause();
3066
3067		let origin = Origin::random().produce();
3068		let consumer = origin.consume();
3069		let mut announced = consumer.announced();
3070
3071		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
3072		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
3073		let mut broadcast3 = origin.create_broadcast("test", announce()).unwrap();
3074		settle().await;
3075		assert!(consumer.get_broadcast("test").is_some());
3076
3077		announced.assert_next_some("test");
3078		announced.assert_next_wait();
3079
3080		// A standby source finishing changes nothing.
3081		broadcast2.finish();
3082		settle().await;
3083		assert!(consumer.get_broadcast("test").is_some());
3084		announced.assert_next_wait();
3085
3086		// The active source finishing hands over to a survivor, invisibly.
3087		broadcast1.finish();
3088		settle().await;
3089		assert!(consumer.get_broadcast("test").is_some());
3090		announced.assert_next_wait();
3091
3092		// The last source finishing unannounces and removes the broadcast.
3093		broadcast3.finish();
3094		settle().await;
3095		assert!(consumer.get_broadcast("test").is_none());
3096
3097		announced.assert_next_none("test");
3098		announced.assert_next_wait();
3099	}
3100
3101	/// A source dying mid-serve fails over: the track re-splices from the standby
3102	/// source and resumes exactly at the first missing group.
3103	#[tokio::test]
3104	async fn test_route_failover() {
3105		tokio::time::pause();
3106
3107		let origin = Origin::random().produce();
3108		let consumer = origin.consume();
3109		let mut announced = consumer.announced();
3110
3111		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3112		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3113
3114		// The first source announces the broadcast.
3115		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
3116		let mut dynamic_a = source_a.dynamic();
3117		settle().await;
3118		settle().await;
3119		let broadcast = consumer.request_broadcast("test").await.unwrap();
3120		announced.assert_next_some("test");
3121
3122		// A second (longer) source joins silently as a standby.
3123		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
3124		let mut dynamic_b = source_b.dynamic();
3125		settle().await;
3126		settle().await;
3127		announced.assert_next_wait();
3128
3129		// Subscribing dispatches the track to the best source (A).
3130		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3131		let mut producer = accept_track(&mut dynamic_a, "video").await;
3132		settle().await;
3133		dynamic_b.assert_no_request();
3134
3135		let mut sub = subscribing.await.unwrap();
3136		// Demand registers as the subscriber polls; a fresh segment carries no
3137		// boundary, so the demand is the subscriber's own.
3138		sub.assert_no_group();
3139		assert_eq!(producer.subscription().unwrap().group_start, None);
3140
3141		producer.append_group().unwrap();
3142		producer.append_group().unwrap();
3143		assert_eq!(sub.assert_group().sequence, 0);
3144		assert_eq!(sub.assert_group().sequence, 1);
3145
3146		// Source A dies (session loss): the track re-splices from B and nothing
3147		// is announced.
3148		// abort() consumes the producer, so this both aborts and drops it.
3149		producer.abort(Error::Dropped).unwrap();
3150		source_a.abort(Error::Dropped).unwrap();
3151		drop(dynamic_a);
3152		settle().await;
3153		announced.assert_next_wait();
3154
3155		// The new copy resumes one past the spliced groups: its demand starts at
3156		// the boundary, and groups the old source already delivered are filtered.
3157		let mut producer = accept_track(&mut dynamic_b, "video").await;
3158		settle().await;
3159		sub.assert_no_group();
3160		assert_eq!(producer.subscription().unwrap().group_start, Some(2));
3161		producer.create_group(group::Info { sequence: 1 }).unwrap();
3162		producer.create_group(group::Info { sequence: 2 }).unwrap();
3163		assert_eq!(sub.assert_group().sequence, 2, "groups below the boundary are filtered");
3164		sub.assert_not_closed();
3165	}
3166
3167	/// `route_changed` yields the current route first, then each change; equal
3168	/// updates coalesce, and the watch errors once every producer is gone.
3169	#[tokio::test]
3170	async fn test_broadcast_route_watch() {
3171		let mut producer = broadcast::Info::new().produce();
3172		let mut consumer = producer.consume();
3173
3174		// Initial value: the default route.
3175		assert_eq!(consumer.route_changed().await.unwrap(), broadcast::Route::default());
3176
3177		// An equal update is a no-op.
3178		producer.set_route(broadcast::Route::default()).unwrap();
3179		assert!(consumer.route_changed().now_or_never().is_none());
3180
3181		let mut hops = OriginList::new();
3182		hops.push(Origin::new(7).unwrap()).unwrap();
3183		let route = broadcast::Route::new().with_hops(hops).with_cost(3);
3184		producer.set_route(route.clone()).unwrap();
3185		assert_eq!(consumer.route_changed().await.unwrap(), route);
3186
3187		// A fresh consumer sees the current value immediately.
3188		let mut fresh = producer.consume();
3189		assert_eq!(fresh.route_changed().await.unwrap(), route);
3190
3191		drop(producer);
3192		assert!(matches!(consumer.route_changed().await.unwrap_err(), Error::Dropped));
3193	}
3194
3195	/// A cost update that flips the winning source hands live tracks over at a
3196	/// group boundary and re-advertises the broadcast's route, without announce
3197	/// churn.
3198	#[tokio::test]
3199	async fn test_route_cost_update() {
3200		tokio::time::pause();
3201
3202		// The takeover happens while a subscriber is live (carrying), so the local
3203		// origin must win the handover key comparison against B's announcing hop
3204		// (origin 3); a random id would flake on the hash.
3205		let origin = Info::new(origin_keyed("test", Origin::new(3).unwrap(), true)).produce();
3206		let consumer = origin.consume();
3207		let mut announced = consumer.announced();
3208
3209		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3210		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3211
3212		// A (shorter chain) wins at equal cost.
3213		let mut source_a = origin
3214			.create_broadcast("test", announce().with_hops(hops_a.clone()))
3215			.unwrap();
3216		let mut dynamic_a = source_a.dynamic();
3217		settle().await;
3218		let broadcast = consumer.request_broadcast("test").await.unwrap();
3219		announced.assert_next_some("test");
3220
3221		let mut watch = broadcast.clone();
3222		assert_eq!(watch.route_changed().await.unwrap().hops, hops_a);
3223
3224		let mut source_b = origin
3225			.create_broadcast("test", announce().with_hops(hops_b.clone()))
3226			.unwrap();
3227		let mut dynamic_b = source_b.dynamic();
3228		settle().await;
3229		assert!(
3230			watch.route_changed().now_or_never().is_none(),
3231			"a losing standby must not change the advertised route"
3232		);
3233
3234		// Dispatch the track to A and deliver a group.
3235		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3236		let mut producer = accept_track(&mut dynamic_a, "video").await;
3237		settle().await;
3238		let mut sub = subscribing.await.unwrap();
3239		producer.append_group().unwrap();
3240		assert_eq!(sub.assert_group().sequence, 0);
3241
3242		// A's cost rises above B's: B takes over at the boundary and the
3243		// broadcast re-advertises B's route. No announce events.
3244		source_a
3245			.set_route(announce().with_hops(hops_a.clone()).with_cost(10))
3246			.unwrap();
3247		settle().await;
3248		assert_eq!(watch.route_changed().await.unwrap().hops, hops_b);
3249		announced.assert_next_wait();
3250
3251		let mut producer_b = accept_track(&mut dynamic_b, "video").await;
3252		settle().await;
3253		// Demand registers as the subscriber polls; the new segment starts at the
3254		// splice boundary.
3255		sub.assert_no_group();
3256		assert_eq!(producer_b.subscription().unwrap().group_start, Some(1));
3257		producer_b.create_group(group::Info { sequence: 1 }).unwrap();
3258		assert_eq!(sub.assert_group().sequence, 1);
3259		sub.assert_not_closed();
3260
3261		// The active source updating its own metadata re-advertises in place.
3262		source_b
3263			.set_route(announce().with_hops(hops_b.clone()).with_cost(5))
3264			.unwrap();
3265		settle().await;
3266		let advertised = watch.route_changed().await.unwrap();
3267		assert_eq!(advertised.hops, hops_b);
3268		assert_eq!(advertised.cost, 5);
3269		announced.assert_next_wait();
3270	}
3271
3272	/// A track completed for good must survive later source churn: it is never
3273	/// re-dispatched, and late subscribers still see a clean end.
3274	#[tokio::test]
3275	async fn test_completed_track_survives_route_churn() {
3276		tokio::time::pause();
3277
3278		let origin = Origin::random().produce();
3279		let consumer = origin.consume();
3280
3281		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3282		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3283
3284		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
3285		let mut dynamic_a = source_a.dynamic();
3286		settle().await;
3287		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
3288		let mut dynamic_b = source_b.dynamic();
3289		settle().await;
3290		settle().await;
3291		let broadcast = consumer.request_broadcast("test").await.unwrap();
3292
3293		// Serve the track via A and end it for good.
3294		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3295		let mut producer = accept_track(&mut dynamic_a, "video").await;
3296		settle().await;
3297		let mut sub = subscribing.await.unwrap();
3298		producer.append_group().unwrap();
3299		assert_eq!(sub.assert_group().sequence, 0);
3300		producer.finish().unwrap();
3301		drop(producer);
3302		settle().await;
3303		sub.assert_closed();
3304
3305		// A detaching must not re-dispatch the finished track to B.
3306		source_a.abort(Error::Dropped).unwrap();
3307		drop(dynamic_a);
3308		settle().await;
3309		dynamic_b.assert_no_request();
3310
3311		// A late subscriber sees the same clean end, not an abort.
3312		let mut late = broadcast.track("video").unwrap().subscribe(None).await.unwrap();
3313		late.assert_closed();
3314	}
3315
3316	/// A successful splice resets the retry budget: transient pre-splice failures
3317	/// spread over a track's lifetime never accumulate into an abort.
3318	#[tokio::test]
3319	async fn test_serve_resets_retry_budget() {
3320		tokio::time::pause();
3321
3322		let origin = Origin::random().produce();
3323		let consumer = origin.consume();
3324
3325		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3326		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3327		let mut dynamic = source.dynamic();
3328		settle().await;
3329		settle().await;
3330		let broadcast = consumer.request_broadcast("test").await.unwrap();
3331
3332		// Queue the track; the subscription resolves once a serve finally sticks.
3333		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3334
3335		// Alternate a pre-splice failure with a successful splice, well past the
3336		// retry cap; the resets keep the track alive.
3337		for _ in 0..2 * MAX_TRACK_RETRIES {
3338			let request = tokio::time::timeout(std::time::Duration::from_secs(1), dynamic.requested_track())
3339				.await
3340				.expect("timed out waiting for a retry")
3341				.unwrap();
3342			request.reject(Error::NotFound);
3343			let producer = accept_track(&mut dynamic, "video").await;
3344			settle().await;
3345			drop(producer);
3346		}
3347
3348		let _producer = accept_track(&mut dynamic, "video").await;
3349		settle().await;
3350		let mut sub = subscribing.await.unwrap();
3351		sub.assert_not_closed();
3352	}
3353
3354	/// A better source attaching mid-subscription takes the track over at an
3355	/// explicit group boundary: the old copy's demand is capped, the new copy
3356	/// starts at the boundary, and the subscriber reads a seamless sequence.
3357	#[tokio::test]
3358	async fn test_route_handover() {
3359		tokio::time::pause();
3360
3361		let origin = Origin::random().produce();
3362		let consumer = origin.consume();
3363		let mut announced = consumer.announced();
3364
3365		let hops_long = OriginList::try_from(vec![Origin::new(2).unwrap(), Origin::new(3).unwrap()]).unwrap();
3366		let hops_short = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3367
3368		let source_a = origin
3369			.create_broadcast("test", announce().with_hops(hops_long))
3370			.unwrap();
3371		let mut dynamic_a = source_a.dynamic();
3372		settle().await;
3373		settle().await;
3374		let broadcast = consumer.request_broadcast("test").await.unwrap();
3375		announced.assert_next_some("test");
3376
3377		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3378		let mut producer_a = accept_track(&mut dynamic_a, "video").await;
3379		settle().await;
3380		let mut sub = subscribing.await.unwrap();
3381		producer_a.append_group().unwrap();
3382		producer_a.append_group().unwrap();
3383		assert_eq!(sub.assert_group().sequence, 0);
3384		assert_eq!(sub.assert_group().sequence, 1);
3385
3386		// A strictly shorter source attaches: the live track is handed over with
3387		// no announce churn.
3388		let source_b = origin
3389			.create_broadcast("test", announce().with_hops(hops_short))
3390			.unwrap();
3391		let mut dynamic_b = source_b.dynamic();
3392		settle().await;
3393		settle().await;
3394		announced.assert_next_wait();
3395
3396		let mut producer_b = accept_track(&mut dynamic_b, "video").await;
3397		settle().await;
3398
3399		// The old copy's demand is capped at the boundary; the new copy's starts
3400		// there. Both propagate as the subscriber polls.
3401		sub.assert_no_group();
3402		assert_eq!(producer_a.subscription().unwrap().group_end, Some(1));
3403		assert_eq!(producer_b.subscription().unwrap().group_start, Some(2));
3404
3405		// The old copy racing past its cap is filtered; the new copy serves on.
3406		producer_a.create_group(group::Info { sequence: 2 }).unwrap();
3407		producer_b.create_group(group::Info { sequence: 2 }).unwrap();
3408		producer_b.create_group(group::Info { sequence: 3 }).unwrap();
3409		assert_eq!(sub.assert_group().sequence, 2);
3410		assert_eq!(sub.assert_group().sequence, 3);
3411		sub.assert_no_group();
3412		sub.assert_not_closed();
3413	}
3414
3415	/// A graceful detach (deliberate unannounce) closes immediately: no linger, so
3416	/// the unannounce propagates promptly and a re-create is a fresh broadcast.
3417	#[tokio::test(start_paused = true)]
3418	async fn test_route_unannounce_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 mut source = origin
3425			.create_broadcast("test", announce().with_hops(hops.clone()))
3426			.unwrap();
3427		settle().await;
3428		let broadcast = consumer.request_broadcast("test").await.unwrap();
3429		announced.assert_next_some("test");
3430
3431		// The peer deliberately unannounced: no reconnect window, the broadcast is
3432		// gone as soon as the teardown task observes the close.
3433		source.finish();
3434		settle().await;
3435		announced.assert_next_none("test");
3436
3437		// A re-create at the same path is a brand-new broadcast.
3438		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3439		settle().await;
3440		let fresh = consumer.request_broadcast("test").await.unwrap();
3441		announced.assert_next_some("test");
3442		assert!(
3443			!fresh.is_clone(&broadcast),
3444			"re-create must not splice the old broadcast"
3445		);
3446	}
3447
3448	/// With the default zero linger, a dying source (a session drop, not a
3449	/// deliberate unannounce) closes the broadcast just as promptly as a graceful
3450	/// one: no reconnect window, the tracks abort, and a re-create is a fresh
3451	/// broadcast rather than a splice.
3452	#[tokio::test(start_paused = true)]
3453	async fn test_route_detach_immediate() {
3454		let origin = Origin::random().produce();
3455		let consumer = origin.consume();
3456		let mut announced = consumer.announced();
3457
3458		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3459		let source = origin
3460			.create_broadcast("test", announce().with_hops(hops.clone()))
3461			.unwrap();
3462		let mut dynamic = source.dynamic();
3463		settle().await;
3464		settle().await;
3465		let broadcast = consumer.request_broadcast("test").await.unwrap();
3466		announced.assert_next_some("test");
3467
3468		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3469		let producer = accept_track(&mut dynamic, "video").await;
3470		settle().await;
3471		let mut sub = subscribing.await.unwrap();
3472
3473		// The session dies without unannouncing.
3474		drop(producer);
3475		source.abort(Error::Dropped).unwrap();
3476		drop(dynamic);
3477
3478		settle().await;
3479		announced.assert_next_none("test");
3480		sub.assert_error();
3481
3482		// A reconnecting session gets a brand-new broadcast, not a splice into
3483		// the old one.
3484		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3485		settle().await;
3486		settle().await;
3487		let fresh = consumer.request_broadcast("test").await.unwrap();
3488		announced.assert_next_some("test");
3489		assert!(
3490			!fresh.is_clone(&broadcast),
3491			"re-create must not splice the old broadcast"
3492		);
3493	}
3494
3495	/// A track nobody reads keeps the source's copy for [`TRACK_IDLE_LINGER`], then
3496	/// releases it. Crucially, the release must not immediately re-splice: the same
3497	/// demand signal gates both directions, so an idle track settles instead of
3498	/// re-requesting the track (and its info) every linger.
3499	#[tokio::test(start_paused = true)]
3500	async fn test_idle_track_releases_without_respinning() {
3501		let origin = Info::new(Origin::random()).produce();
3502		let consumer = origin.consume();
3503
3504		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3505		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3506		let mut dynamic = source.dynamic();
3507		settle().await;
3508		let broadcast = consumer.request_broadcast("test").await.unwrap();
3509
3510		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3511		let producer = accept_track(&mut dynamic, "video").await;
3512		settle().await;
3513		let sub = subscribing.await.unwrap();
3514
3515		// The reader leaves, but the copy stays warm inside the window so a viewer
3516		// coming back (or a follow-up fetch) reuses it.
3517		drop(sub);
3518		tokio::time::sleep(TRACK_IDLE_LINGER / 2).await;
3519		settle().await;
3520		assert!(
3521			producer.poll_unused(&kio::Waiter::noop()).is_pending(),
3522			"the copy must stay spliced inside the linger",
3523		);
3524
3525		// Past the window the segment is released, so the serving session sees its
3526		// copy go unused and can drop it (along with the track info).
3527		tokio::time::sleep(TRACK_IDLE_LINGER).await;
3528		settle().await;
3529		assert!(
3530			producer.poll_unused(&kio::Waiter::noop()).is_ready(),
3531			"an idle copy must be released after the linger",
3532		);
3533
3534		// The anti-spin property: the release must not re-arm the splice. Ungated,
3535		// the loop re-attaches the copy immediately and drops it again every linger,
3536		// re-requesting the track (and its info) from the session each time it dies.
3537		for _ in 0..3 {
3538			tokio::time::sleep(TRACK_IDLE_LINGER).await;
3539			settle().await;
3540			assert!(
3541				producer.poll_unused(&kio::Waiter::noop()).is_ready(),
3542				"an unread copy must stay released, not be re-spliced",
3543			);
3544		}
3545		assert!(
3546			dynamic.requested_track().now_or_never().is_none(),
3547			"an unread track must not be re-requested",
3548		);
3549		drop(producer);
3550
3551		// A returning reader re-splices: the origin asks the source for a fresh copy.
3552		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3553		let mut producer = accept_track(&mut dynamic, "video").await;
3554		settle().await;
3555		let mut sub = subscribing.await.unwrap();
3556		producer.append_group().unwrap();
3557		assert_eq!(sub.assert_group().sequence, 0);
3558	}
3559
3560	/// Back-to-back fetches reuse the source's copy: only the first asks the source
3561	/// for the track, so a fetch-driven consumer (HLS pulling segment after segment)
3562	/// doesn't re-request the track, and its `TRACK_INFO`, for every group.
3563	#[tokio::test(start_paused = true)]
3564	async fn test_back_to_back_fetches_reuse_the_track() {
3565		let origin = Info::new(Origin::random()).produce();
3566		let consumer = origin.consume();
3567
3568		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3569		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3570		let mut dynamic = source.dynamic();
3571		settle().await;
3572		let broadcast = consumer.request_broadcast("test").await.unwrap();
3573
3574		// The first fetch has to ask the source for the track.
3575		let fetching = broadcast.track("video").unwrap().fetch_group(0, None);
3576		let mut producer = accept_track(&mut dynamic, "video").await;
3577		producer.append_group().unwrap().finish().unwrap();
3578		settle().await;
3579		let first = fetching.await.expect("first fetch");
3580		drop(first);
3581
3582		// A second fetch inside the linger reuses the copy already spliced in.
3583		settle().await;
3584		let fetching = broadcast.track("video").unwrap().fetch_group(0, None);
3585		settle().await;
3586		assert!(
3587			dynamic.requested_track().now_or_never().is_none(),
3588			"a fetch inside the linger must reuse the track, not re-request it",
3589		);
3590		drop(fetching.await.expect("second fetch"));
3591
3592		// Once the fetches stop, the copy is released like any other idle track.
3593		tokio::time::sleep(TRACK_IDLE_LINGER * 2).await;
3594		settle().await;
3595		assert!(
3596			producer.poll_unused(&kio::Waiter::noop()).is_ready(),
3597			"the copy must be released once the fetches stop",
3598		);
3599		drop(producer);
3600
3601		// And a later fetch re-requests it: `accept_track` times out if it doesn't.
3602		settle().await;
3603		let fetching = broadcast.track("video").unwrap().fetch_group(0, None);
3604		let mut producer = accept_track(&mut dynamic, "video").await;
3605		producer.append_group().unwrap().finish().unwrap();
3606		settle().await;
3607		fetching.await.expect("fetch after the linger");
3608	}
3609
3610	/// With a linger configured, an ungraceful source loss keeps the broadcast
3611	/// alive and announced; a source re-attaching within the window splices in and
3612	/// consumers resume at the group boundary, never observing the outage.
3613	#[tokio::test(start_paused = true)]
3614	async fn test_linger_reconnect_splices() {
3615		let origin = Info::new(Origin::random())
3616			.with_linger(Duration::from_secs(5))
3617			.produce();
3618		let consumer = origin.consume();
3619		let mut announced = consumer.announced();
3620
3621		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3622		let source = origin
3623			.create_broadcast("test", announce().with_hops(hops.clone()))
3624			.unwrap();
3625		let mut dynamic = source.dynamic();
3626		settle().await;
3627		settle().await;
3628		let broadcast = consumer.request_broadcast("test").await.unwrap();
3629		announced.assert_next_some("test");
3630
3631		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3632		let mut producer = accept_track(&mut dynamic, "video").await;
3633		settle().await;
3634		let mut sub = subscribing.await.unwrap();
3635
3636		producer.append_group().unwrap();
3637		producer.append_group().unwrap();
3638		assert_eq!(sub.assert_group().sequence, 0);
3639		assert_eq!(sub.assert_group().sequence, 1);
3640
3641		// The session dies without unannouncing: the broadcast enters the linger
3642		// window instead of closing.
3643		drop(producer);
3644		source.abort(Error::Dropped).unwrap();
3645		drop(dynamic);
3646		settle().await;
3647
3648		// No unannounce, no track error: the outage is invisible so far.
3649		announced.assert_next_wait();
3650		sub.assert_no_group();
3651		sub.assert_not_closed();
3652
3653		// A consumer arriving mid-outage still resolves the lingering broadcast.
3654		let during = consumer.request_broadcast("test").await.unwrap();
3655		assert!(during.is_clone(&broadcast), "the lingering broadcast still resolves");
3656
3657		// The session reconnects within the window: the new source splices into
3658		// the same broadcast.
3659		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3660		let mut dynamic = source.dynamic();
3661		settle().await;
3662		settle().await;
3663		announced.assert_next_wait();
3664		let again = consumer.request_broadcast("test").await.unwrap();
3665		assert!(again.is_clone(&broadcast), "the reconnect must splice, not replace");
3666
3667		// The pending track re-splices from the new source and resumes one past
3668		// the groups the old source already delivered. Demand registers as the
3669		// subscriber polls.
3670		let mut producer = accept_track(&mut dynamic, "video").await;
3671		settle().await;
3672		sub.assert_no_group();
3673		assert_eq!(producer.subscription().unwrap().group_start, Some(2));
3674		producer.create_group(group::Info { sequence: 2 }).unwrap();
3675		assert_eq!(sub.assert_group().sequence, 2);
3676		sub.assert_not_closed();
3677	}
3678
3679	/// The linger window expiring without a replacement closes the broadcast: the
3680	/// path unannounces, tracks abort, and a later re-create is a fresh broadcast.
3681	#[tokio::test(start_paused = true)]
3682	async fn test_linger_expiry_closes() {
3683		let origin = Info::new(Origin::random())
3684			.with_linger(Duration::from_secs(5))
3685			.produce();
3686		let consumer = origin.consume();
3687		let mut announced = consumer.announced();
3688
3689		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3690		let source = origin
3691			.create_broadcast("test", announce().with_hops(hops.clone()))
3692			.unwrap();
3693		let mut dynamic = source.dynamic();
3694		settle().await;
3695		settle().await;
3696		let broadcast = consumer.request_broadcast("test").await.unwrap();
3697		announced.assert_next_some("test");
3698
3699		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3700		let producer = accept_track(&mut dynamic, "video").await;
3701		settle().await;
3702		let mut sub = subscribing.await.unwrap();
3703
3704		drop(producer);
3705		source.abort(Error::Dropped).unwrap();
3706		drop(dynamic);
3707		settle().await;
3708		announced.assert_next_wait();
3709
3710		// Nobody comes back: the window expires and the broadcast closes.
3711		tokio::time::sleep(std::time::Duration::from_secs(6)).await;
3712		settle().await;
3713		announced.assert_next_none("test");
3714		sub.assert_error();
3715
3716		// A session reconnecting after the window gets a brand-new broadcast.
3717		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3718		settle().await;
3719		settle().await;
3720		let fresh = consumer.request_broadcast("test").await.unwrap();
3721		announced.assert_next_some("test");
3722		assert!(
3723			!fresh.is_clone(&broadcast),
3724			"a late re-create must not splice the expired broadcast"
3725		);
3726	}
3727
3728	/// A linger too large to represent as a deadline never expires: the broadcast
3729	/// outlives an arbitrarily long outage (a reconnect loop with no give-up
3730	/// timeout promises to retry forever).
3731	#[tokio::test(start_paused = true)]
3732	async fn test_linger_forever() {
3733		let origin = Info::new(Origin::random()).with_linger(Duration::MAX).produce();
3734		let consumer = origin.consume();
3735		let mut announced = consumer.announced();
3736
3737		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3738		let source = origin
3739			.create_broadcast("test", announce().with_hops(hops.clone()))
3740			.unwrap();
3741		settle().await;
3742		let broadcast = consumer.request_broadcast("test").await.unwrap();
3743		announced.assert_next_some("test");
3744
3745		source.abort(Error::Dropped).unwrap();
3746		settle().await;
3747
3748		// Days later the broadcast is still announced and still splices a reconnect.
3749		tokio::time::sleep(std::time::Duration::from_secs(60 * 60 * 24 * 3)).await;
3750		announced.assert_next_wait();
3751		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3752		settle().await;
3753		settle().await;
3754		let again = consumer.request_broadcast("test").await.unwrap();
3755		assert!(again.is_clone(&broadcast), "the reconnect must splice, not replace");
3756		drop(source);
3757	}
3758
3759	/// A deliberate finish never lingers, even with a linger configured: a clean
3760	/// unannounce from a peer must propagate immediately.
3761	#[tokio::test(start_paused = true)]
3762	async fn test_linger_skipped_on_finish() {
3763		let origin = Info::new(Origin::random())
3764			.with_linger(Duration::from_secs(5))
3765			.produce();
3766		let consumer = origin.consume();
3767		let mut announced = consumer.announced();
3768
3769		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3770		let mut source = origin
3771			.create_broadcast("test", announce().with_hops(hops.clone()))
3772			.unwrap();
3773		settle().await;
3774		let broadcast = consumer.request_broadcast("test").await.unwrap();
3775		announced.assert_next_some("test");
3776
3777		// A clean unannounce: the broadcast is gone as soon as the teardown task
3778		// observes the close, with no reconnect window.
3779		source.finish();
3780		settle().await;
3781		announced.assert_next_none("test");
3782
3783		// A re-create at the same path is a brand-new broadcast.
3784		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3785		settle().await;
3786		let fresh = consumer.request_broadcast("test").await.unwrap();
3787		announced.assert_next_some("test");
3788		assert!(
3789			!fresh.is_clone(&broadcast),
3790			"a finish must not leave a lingering broadcast to splice into"
3791		);
3792	}
3793
3794	/// A non-live broadcast is reachable by exact path but never announced;
3795	/// toggling `live` announces and unannounces without touching the broadcast.
3796	#[tokio::test]
3797	async fn test_announce_toggle() {
3798		tokio::time::pause();
3799
3800		let origin = Origin::random().produce();
3801		let consumer = origin.consume();
3802		let mut announced = consumer.announced();
3803
3804		let mut source = origin.create_broadcast("test", broadcast::Route::new()).unwrap();
3805		settle().await;
3806
3807		// Routable but not announced.
3808		announced.assert_next_wait();
3809		let broadcast = consumer
3810			.get_broadcast("test")
3811			.expect("offline broadcast is still routable");
3812		assert!(!broadcast.route().announce);
3813
3814		// request_broadcast resolves the offline broadcast too.
3815		let requested = consumer.request_broadcast("test").await.unwrap();
3816		assert!(requested.is_clone(&broadcast));
3817
3818		// Going live announces.
3819		source.set_route(announce()).unwrap();
3820		settle().await;
3821		let face = announced.assert_next_some("test");
3822		assert!(face.is_clone(&broadcast));
3823
3824		// A fresh consumer replays only announced broadcasts.
3825		let mut fresh = origin.consume().announced();
3826		fresh.assert_next_some("test");
3827		fresh.assert_next_wait();
3828
3829		// Going offline unannounces but stays routable.
3830		source.set_route(broadcast::Route::new()).unwrap();
3831		settle().await;
3832		announced.assert_next_none("test");
3833		assert!(consumer.get_broadcast("test").is_some());
3834		let mut fresh = origin.consume().announced();
3835		fresh.assert_next_wait();
3836
3837		source.finish();
3838		settle().await;
3839		assert!(consumer.get_broadcast("test").is_none());
3840	}
3841
3842	/// An announced source outranks a cheaper offline one, so the broadcast
3843	/// stays announced and serves from it.
3844	#[tokio::test]
3845	async fn test_announce_beats_offline() {
3846		tokio::time::pause();
3847
3848		let origin = Origin::random().produce();
3849		let consumer = origin.consume();
3850		let mut announced = consumer.announced();
3851
3852		// An unannounced source with the best cost.
3853		let _offline = origin.create_broadcast("test", broadcast::Route::new()).unwrap();
3854		settle().await;
3855		announced.assert_next_wait();
3856
3857		// An announced source with a worse cost still wins: the path announces
3858		// and advertises its route.
3859		let mut announced_source = origin.create_broadcast("test", announce().with_cost(10)).unwrap();
3860		settle().await;
3861		announced.assert_next_some("test");
3862		let face = consumer.get_broadcast("test").unwrap();
3863		assert!(face.route().announce);
3864		assert_eq!(face.route().cost, 10);
3865
3866		// The announced source leaving falls back to the offline one: the path
3867		// unannounces but stays routable.
3868		announced_source.finish();
3869		settle().await;
3870		announced.assert_next_none("test");
3871		assert!(consumer.get_broadcast("test").is_some());
3872	}
3873
3874	/// A better source attaching does not churn announces: the broadcast identity
3875	/// is origin-owned, so the swap is invisible to consumers.
3876	#[tokio::test]
3877	async fn test_better_source_no_churn() {
3878		tokio::time::pause();
3879
3880		let origin = Origin::random().produce();
3881		let mut announced = origin.consume().announced();
3882
3883		// `a` carries one hop; `b` has none, so `b` wins dispatch when it joins.
3884		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3885		let _a = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3886		settle().await;
3887		let face = announced.assert_next_some("test");
3888
3889		let _b = origin.create_broadcast("test", announce()).unwrap();
3890		settle().await;
3891		announced.assert_next_wait();
3892		let current = origin.consume().get_broadcast("test").unwrap();
3893		assert!(current.is_clone(&face), "the broadcast identity must not change");
3894		// The face now advertises the winning (hopless) route.
3895		assert!(current.route().hops.is_empty());
3896	}
3897
3898	#[tokio::test]
3899	async fn test_duplicate_reverse() {
3900		tokio::time::pause();
3901
3902		let origin = Origin::random().produce();
3903
3904		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
3905		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
3906		settle().await;
3907		assert!(origin.consume().get_broadcast("test").is_some());
3908
3909		// This is harder, finishing the newer source first.
3910		broadcast2.finish();
3911		settle().await;
3912		assert!(origin.consume().get_broadcast("test").is_some());
3913
3914		broadcast1.finish();
3915		settle().await;
3916		assert!(origin.consume().get_broadcast("test").is_none());
3917	}
3918
3919	#[tokio::test]
3920	async fn test_deterministic_tiebreak() {
3921		tokio::time::pause();
3922
3923		fn hops(ids: &[u64]) -> OriginList {
3924			OriginList::try_from(
3925				ids.iter()
3926					.copied()
3927					.map(|id| Origin::new(id).unwrap())
3928					.collect::<Vec<_>>(),
3929			)
3930			.unwrap()
3931		}
3932
3933		// Resolve the advertised route for "test" after creating both sources in
3934		// the given order.
3935		async fn winner(first: &[u64], second: &[u64]) -> OriginList {
3936			let origin = Origin::random().produce();
3937			let _a = origin
3938				.create_broadcast("test", announce().with_hops(hops(first)))
3939				.unwrap();
3940			let _b = origin
3941				.create_broadcast("test", announce().with_hops(hops(second)))
3942				.unwrap();
3943			settle().await;
3944			origin.consume().get_broadcast("test").unwrap().route().hops
3945		}
3946
3947		// Two routes with equal hop counts but distinct chains. The winner is decided by
3948		// the deterministic key, not arrival order, so both publish orders converge.
3949		let forward = winner(&[10, 20], &[30, 40]).await;
3950		let reverse = winner(&[30, 40], &[10, 20]).await;
3951		assert_eq!(forward, reverse, "tie-break must not depend on publish order");
3952
3953		// A strictly shorter chain always wins regardless of the hash.
3954		assert_eq!(winner(&[10, 20], &[30]).await.len(), 1);
3955		assert_eq!(winner(&[30], &[10, 20]).await.len(), 1);
3956	}
3957
3958	// A previous mpsc-based implementation could only deliver the first 127 broadcasts
3959	// instantly via `assert_next` (which uses `now_or_never`). The kio-backed
3960	// implementation polls synchronously and can deliver all of them without yielding.
3961	// Names are zero-padded so lexicographic delivery order matches the loop index.
3962	#[tokio::test]
3963	async fn test_many_announces() {
3964		let origin = Origin::random().produce();
3965
3966		let mut consumer = origin.consume().announced();
3967		// Held for the duration: a dropped source unannounces immediately.
3968		let mut broadcasts = Vec::new();
3969		for i in 0..256 {
3970			broadcasts.push(origin.create_broadcast(format!("test{i:03}"), announce()).unwrap());
3971			settle().await;
3972		}
3973
3974		for i in 0..256 {
3975			consumer.assert_next_some(format!("test{i:03}"));
3976		}
3977		consumer.assert_next_wait();
3978	}
3979
3980	#[tokio::test]
3981	async fn test_many_announces_try() {
3982		let origin = Origin::random().produce();
3983
3984		let mut consumer = origin.consume().announced();
3985		// Held for the duration: a dropped source unannounces immediately.
3986		let mut broadcasts = Vec::new();
3987		for i in 0..256 {
3988			broadcasts.push(origin.create_broadcast(format!("test{i:03}"), announce()).unwrap());
3989			settle().await;
3990		}
3991
3992		for i in 0..256 {
3993			consumer.assert_try_next_some(format!("test{i:03}"));
3994		}
3995	}
3996
3997	#[tokio::test]
3998	async fn test_with_root_basic() {
3999		let origin = Origin::random().produce();
4000
4001		// Create a producer with root "/foo"
4002		let foo_producer = origin.with_root("foo").expect("should create root");
4003		assert_eq!(foo_producer.root().as_str(), "foo");
4004
4005		let mut consumer = origin.consume().announced();
4006
4007		// When publishing to "bar/baz", it should actually publish to "foo/bar/baz"
4008		let _broadcast = foo_producer
4009			.create_broadcast("bar/baz", announce())
4010			.expect("publish allowed");
4011		settle().await;
4012		// The original consumer should see the full path
4013		consumer.assert_next_some("foo/bar/baz");
4014
4015		// A consumer created from the rooted producer should see the stripped path
4016		let mut foo_consumer = foo_producer.consume().announced();
4017		foo_consumer.assert_next_some("bar/baz");
4018	}
4019
4020	#[tokio::test]
4021	async fn test_with_root_nested() {
4022		let origin = Origin::random().produce();
4023
4024		// Create nested roots
4025		let foo_producer = origin.with_root("foo").expect("should create foo root");
4026		let foo_bar_producer = foo_producer.with_root("bar").expect("should create bar root");
4027		assert_eq!(foo_bar_producer.root().as_str(), "foo/bar");
4028
4029		let mut consumer = origin.consume().announced();
4030
4031		// Publishing to "baz" should actually publish to "foo/bar/baz"
4032		let _broadcast = foo_bar_producer
4033			.create_broadcast("baz", announce())
4034			.expect("publish allowed");
4035		settle().await;
4036		// The original consumer sees the full path
4037		consumer.assert_next_some("foo/bar/baz");
4038
4039		// Consumer from foo_bar_producer sees just "baz"
4040		let mut foo_bar_consumer = foo_bar_producer.consume().announced();
4041		foo_bar_consumer.assert_next_some("baz");
4042	}
4043
4044	#[tokio::test]
4045	async fn test_publish_scope_allows() {
4046		let origin = Origin::random().produce();
4047
4048		// Create a producer that can only publish to "allowed" paths
4049		let limited_producer = origin
4050			.scope(&["allowed/path1".into(), "allowed/path2".into()])
4051			.expect("should create limited producer");
4052
4053		// Should be able to publish to allowed paths
4054		let _broadcast = limited_producer
4055			.create_broadcast("allowed/path1", announce())
4056			.expect("publish allowed");
4057		let _keep2 = limited_producer
4058			.create_broadcast("allowed/path1/nested", announce())
4059			.expect("publish allowed");
4060		let _keep3 = limited_producer
4061			.create_broadcast("allowed/path2", announce())
4062			.expect("publish allowed");
4063		settle().await;
4064
4065		// Should not be able to publish to disallowed paths
4066		assert!(limited_producer.create_broadcast("notallowed", announce()).is_err());
4067		assert!(limited_producer.create_broadcast("allowed", announce()).is_err()); // Parent of allowed path
4068		assert!(limited_producer.create_broadcast("other/path", announce()).is_err());
4069	}
4070
4071	#[tokio::test]
4072	async fn test_publish_max_parts() {
4073		let origin = Origin::random().produce();
4074
4075		let at_limit = (0..Path::MAX_PARTS)
4076			.map(|i| i.to_string())
4077			.collect::<Vec<_>>()
4078			.join("/");
4079		let _broadcast = origin
4080			.create_broadcast(at_limit.as_str(), announce())
4081			.expect("publish allowed");
4082		settle().await;
4083
4084		let too_deep = format!("{at_limit}/extra");
4085		assert!(origin.create_broadcast(too_deep.as_str(), announce()).is_err());
4086
4087		// The root counts toward the limit; a joined path past 32 parts is rejected.
4088		let rooted = origin.with_root("root").expect("wildcard allows any root");
4089		assert!(rooted.create_broadcast(at_limit.as_str(), announce()).is_err());
4090	}
4091
4092	#[tokio::test]
4093	async fn test_publish_scope_empty() {
4094		let origin = Origin::random().produce();
4095
4096		// Creating a producer with no allowed paths should return None
4097		assert!(origin.scope(&[]).is_none());
4098	}
4099
4100	#[tokio::test]
4101	async fn test_consume_scope_filters() {
4102		let origin = Origin::random().produce();
4103
4104		let mut consumer = origin.consume().announced();
4105
4106		// Publish to different paths
4107		let _broadcast1 = origin.create_broadcast("allowed", announce()).unwrap();
4108		let _broadcast2 = origin.create_broadcast("allowed/nested", announce()).unwrap();
4109		let _broadcast3 = origin.create_broadcast("notallowed", announce()).unwrap();
4110		settle().await;
4111
4112		// Create a consumer that only sees "allowed" paths
4113		let mut limited_consumer = origin
4114			.consume()
4115			.scope(&["allowed".into()])
4116			.expect("should create limited consumer")
4117			.announced();
4118
4119		// Should only receive broadcasts under "allowed"
4120		limited_consumer.assert_next_some("allowed");
4121		limited_consumer.assert_next_some("allowed/nested");
4122		limited_consumer.assert_next_wait(); // Should not see "notallowed"
4123
4124		// Unscoped consumer should see all
4125		consumer.assert_next_some("allowed");
4126		consumer.assert_next_some("allowed/nested");
4127		consumer.assert_next_some("notallowed");
4128	}
4129
4130	#[tokio::test]
4131	async fn test_consume_scope_multiple_prefixes() {
4132		let origin = Origin::random().produce();
4133
4134		let _broadcast1 = origin.create_broadcast("foo/test", announce()).unwrap();
4135		let _broadcast2 = origin.create_broadcast("bar/test", announce()).unwrap();
4136		let _broadcast3 = origin.create_broadcast("baz/test", announce()).unwrap();
4137		settle().await;
4138
4139		// Consumer that only sees "foo" and "bar" paths
4140		let mut limited_consumer = origin
4141			.consume()
4142			.scope(&["foo".into(), "bar".into()])
4143			.expect("should create limited consumer")
4144			.announced();
4145
4146		// Order depends on PathPrefixes canonical sort (lexicographic for same length)
4147		limited_consumer.assert_next_some("bar/test");
4148		limited_consumer.assert_next_some("foo/test");
4149		limited_consumer.assert_next_wait(); // Should not see "baz/test"
4150	}
4151
4152	#[tokio::test]
4153	async fn test_with_root_and_publish_scope() {
4154		let origin = Origin::random().produce();
4155
4156		// User connects to /foo root
4157		let foo_producer = origin.with_root("foo").expect("should create foo root");
4158
4159		// Limit them to publish only to "bar" and "goop/pee" within /foo
4160		let limited_producer = foo_producer
4161			.scope(&["bar".into(), "goop/pee".into()])
4162			.expect("should create limited producer");
4163
4164		let mut consumer = origin.consume().announced();
4165
4166		// Should be able to publish to foo/bar and foo/goop/pee (but user sees as bar and goop/pee)
4167		let _broadcast = limited_producer
4168			.create_broadcast("bar", announce())
4169			.expect("publish allowed");
4170		let _keep2 = limited_producer
4171			.create_broadcast("bar/nested", announce())
4172			.expect("publish allowed");
4173		let _keep3 = limited_producer
4174			.create_broadcast("goop/pee", announce())
4175			.expect("publish allowed");
4176		let _keep4 = limited_producer
4177			.create_broadcast("goop/pee/nested", announce())
4178			.expect("publish allowed");
4179		settle().await;
4180
4181		// Should not be able to publish outside allowed paths
4182		assert!(limited_producer.create_broadcast("baz", announce()).is_err());
4183		assert!(limited_producer.create_broadcast("goop", announce()).is_err()); // Parent of allowed
4184		assert!(limited_producer.create_broadcast("goop/other", announce()).is_err());
4185
4186		// Original consumer sees full paths
4187		consumer.assert_next_some("foo/bar");
4188		consumer.assert_next_some("foo/bar/nested");
4189		consumer.assert_next_some("foo/goop/pee");
4190		consumer.assert_next_some("foo/goop/pee/nested");
4191	}
4192
4193	#[tokio::test]
4194	async fn test_with_root_and_consume_scope() {
4195		let origin = Origin::random().produce();
4196
4197		// Publish broadcasts
4198		let _broadcast1 = origin.create_broadcast("foo/bar/test", announce()).unwrap();
4199		let _broadcast2 = origin.create_broadcast("foo/goop/pee/test", announce()).unwrap();
4200		let _broadcast3 = origin.create_broadcast("foo/other/test", announce()).unwrap();
4201		settle().await;
4202
4203		// User connects to /foo root
4204		let foo_producer = origin.with_root("foo").expect("should create foo root");
4205
4206		// Create consumer limited to "bar" and "goop/pee" within /foo
4207		let mut limited_consumer = foo_producer
4208			.consume()
4209			.scope(&["bar".into(), "goop/pee".into()])
4210			.expect("should create limited consumer")
4211			.announced();
4212
4213		// Should only see allowed paths (without foo prefix)
4214		limited_consumer.assert_next_some("bar/test");
4215		limited_consumer.assert_next_some("goop/pee/test");
4216		limited_consumer.assert_next_wait(); // Should not see "other/test"
4217	}
4218
4219	#[tokio::test]
4220	async fn test_with_root_unauthorized() {
4221		let origin = Origin::random().produce();
4222
4223		// First limit the producer to specific paths
4224		let limited_producer = origin
4225			.scope(&["allowed".into()])
4226			.expect("should create limited producer");
4227
4228		// Trying to create a root outside allowed paths should fail
4229		assert!(limited_producer.with_root("notallowed").is_none());
4230
4231		// But creating a root within allowed paths should work
4232		let allowed_root = limited_producer
4233			.with_root("allowed")
4234			.expect("should create allowed root");
4235		assert_eq!(allowed_root.root().as_str(), "allowed");
4236	}
4237
4238	#[tokio::test]
4239	async fn test_wildcard_permission() {
4240		let origin = Origin::random().produce();
4241
4242		// Producer with root access (empty string means wildcard)
4243		let root_producer = origin.clone();
4244
4245		// Should be able to publish anywhere
4246		let _broadcast = root_producer
4247			.create_broadcast("any/path", announce())
4248			.expect("publish allowed");
4249		let _keep2 = root_producer
4250			.create_broadcast("other/path", announce())
4251			.expect("publish allowed");
4252		settle().await;
4253
4254		// Can create any root
4255		let foo_producer = root_producer.with_root("foo").expect("should create any root");
4256		assert_eq!(foo_producer.root().as_str(), "foo");
4257	}
4258
4259	#[tokio::test]
4260	async fn test_consume_broadcast_with_permissions() {
4261		let origin = Origin::random().produce();
4262
4263		let _broadcast1 = origin.create_broadcast("allowed/test", announce()).unwrap();
4264		let _broadcast2 = origin.create_broadcast("notallowed/test", announce()).unwrap();
4265		settle().await;
4266
4267		// Create limited consumer
4268		let limited_consumer = origin
4269			.consume()
4270			.scope(&["allowed".into()])
4271			.expect("should create limited consumer");
4272
4273		// Should be able to get allowed broadcast
4274		let result = limited_consumer.get_broadcast("allowed/test");
4275		assert!(result.is_some());
4276		assert!(
4277			result
4278				.unwrap()
4279				.is_clone(&origin.consume().get_broadcast("allowed/test").unwrap())
4280		);
4281
4282		// Should not be able to get disallowed broadcast
4283		assert!(limited_consumer.get_broadcast("notallowed/test").is_none());
4284
4285		// Original consumer can get both
4286		let consumer = origin.consume();
4287		assert!(consumer.get_broadcast("allowed/test").is_some());
4288		assert!(consumer.get_broadcast("notallowed/test").is_some());
4289	}
4290
4291	#[tokio::test]
4292	async fn test_nested_paths_with_permissions() {
4293		let origin = Origin::random().produce();
4294
4295		// Create producer limited to "a/b/c"
4296		let limited_producer = origin.scope(&["a/b/c".into()]).expect("should create limited producer");
4297
4298		// Should be able to publish to exact path and nested paths
4299		let _broadcast = limited_producer
4300			.create_broadcast("a/b/c", announce())
4301			.expect("publish allowed");
4302		let _keep2 = limited_producer
4303			.create_broadcast("a/b/c/d", announce())
4304			.expect("publish allowed");
4305		let _keep3 = limited_producer
4306			.create_broadcast("a/b/c/d/e", announce())
4307			.expect("publish allowed");
4308		settle().await;
4309
4310		// Should not be able to publish to parent or sibling paths
4311		assert!(limited_producer.create_broadcast("a", announce()).is_err());
4312		assert!(limited_producer.create_broadcast("a/b", announce()).is_err());
4313		assert!(limited_producer.create_broadcast("a/b/other", announce()).is_err());
4314	}
4315
4316	#[tokio::test]
4317	async fn test_multiple_consumers_with_different_permissions() {
4318		let origin = Origin::random().produce();
4319
4320		// Publish to different paths
4321		let _broadcast1 = origin.create_broadcast("foo/test", announce()).unwrap();
4322		let _broadcast2 = origin.create_broadcast("bar/test", announce()).unwrap();
4323		let _broadcast3 = origin.create_broadcast("baz/test", announce()).unwrap();
4324		settle().await;
4325
4326		// Create consumers with different permissions
4327		let mut foo_consumer = origin
4328			.consume()
4329			.scope(&["foo".into()])
4330			.expect("should create foo consumer")
4331			.announced();
4332
4333		let mut bar_consumer = origin
4334			.consume()
4335			.scope(&["bar".into()])
4336			.expect("should create bar consumer")
4337			.announced();
4338
4339		let mut foobar_consumer = origin
4340			.consume()
4341			.scope(&["foo".into(), "bar".into()])
4342			.expect("should create foobar consumer")
4343			.announced();
4344
4345		// Each consumer should only see their allowed paths
4346		foo_consumer.assert_next_some("foo/test");
4347		foo_consumer.assert_next_wait();
4348
4349		bar_consumer.assert_next_some("bar/test");
4350		bar_consumer.assert_next_wait();
4351
4352		foobar_consumer.assert_next_some("bar/test");
4353		foobar_consumer.assert_next_some("foo/test");
4354		foobar_consumer.assert_next_wait();
4355	}
4356
4357	#[tokio::test]
4358	async fn test_select_with_empty_prefix() {
4359		let origin = Origin::random().produce();
4360
4361		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
4362		let demo_producer = origin.with_root("demo").expect("should create demo root");
4363		let limited_producer = demo_producer
4364			.scope(&["worm-node".into(), "foobar".into()])
4365			.expect("should create limited producer");
4366
4367		// Publish some broadcasts
4368		let _broadcast1 = limited_producer
4369			.create_broadcast("worm-node/test", announce())
4370			.expect("publish allowed");
4371		let _broadcast2 = limited_producer
4372			.create_broadcast("foobar/test", announce())
4373			.expect("publish allowed");
4374		settle().await;
4375
4376		// scope with empty prefix should keep the exact same "worm-node" and "foobar" nodes
4377		let mut consumer = limited_producer
4378			.consume()
4379			.scope(&["".into()])
4380			.expect("should create consumer with empty prefix")
4381			.announced();
4382
4383		// Should see both broadcasts (order depends on PathPrefixes sort)
4384		let a1 = consumer.try_next().expect("expected first announcement");
4385		let a2 = consumer.try_next().expect("expected second announcement");
4386		consumer.assert_next_wait();
4387
4388		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
4389		paths.sort();
4390		assert_eq!(paths, ["foobar/test", "worm-node/test"]);
4391	}
4392
4393	#[tokio::test]
4394	async fn test_select_narrowing_scope() {
4395		let origin = Origin::random().produce();
4396
4397		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
4398		let demo_producer = origin.with_root("demo").expect("should create demo root");
4399		let limited_producer = demo_producer
4400			.scope(&["worm-node".into(), "foobar".into()])
4401			.expect("should create limited producer");
4402
4403		// Publish broadcasts at different levels
4404		let _broadcast1 = limited_producer
4405			.create_broadcast("worm-node", announce())
4406			.expect("publish allowed");
4407		let _broadcast2 = limited_producer
4408			.create_broadcast("worm-node/foo", announce())
4409			.expect("publish allowed");
4410		let _broadcast3 = limited_producer
4411			.create_broadcast("foobar/bar", announce())
4412			.expect("publish allowed");
4413		settle().await;
4414
4415		// Test 1: scope("worm-node") should result in a single "" node with contents of "worm-node" ONLY
4416		let mut worm_consumer = limited_producer
4417			.consume()
4418			.scope(&["worm-node".into()])
4419			.expect("should create worm-node consumer")
4420			.announced();
4421
4422		// Should see worm-node content with paths stripped to ""
4423		worm_consumer.assert_next_some("worm-node");
4424		worm_consumer.assert_next_some("worm-node/foo");
4425		worm_consumer.assert_next_wait(); // Should NOT see foobar content
4426
4427		// Test 2: scope("worm-node/foo") should result in a "" node with contents of "worm-node/foo"
4428		let mut foo_consumer = limited_producer
4429			.consume()
4430			.scope(&["worm-node/foo".into()])
4431			.expect("should create worm-node/foo consumer")
4432			.announced();
4433
4434		foo_consumer.assert_next_some("worm-node/foo");
4435		foo_consumer.assert_next_wait(); // Should NOT see other content
4436	}
4437
4438	#[tokio::test]
4439	async fn test_select_multiple_roots_with_empty_prefix() {
4440		let origin = Origin::random().produce();
4441
4442		// Producer with multiple allowed roots
4443		let limited_producer = origin
4444			.scope(&["app1".into(), "app2".into(), "shared".into()])
4445			.expect("should create limited producer");
4446
4447		// Publish to each root
4448		let _broadcast1 = limited_producer
4449			.create_broadcast("app1/data", announce())
4450			.expect("publish allowed");
4451		let _broadcast2 = limited_producer
4452			.create_broadcast("app2/config", announce())
4453			.expect("publish allowed");
4454		let _broadcast3 = limited_producer
4455			.create_broadcast("shared/resource", announce())
4456			.expect("publish allowed");
4457		settle().await;
4458
4459		// scope with empty prefix should maintain all roots
4460		let mut consumer = limited_producer
4461			.consume()
4462			.scope(&["".into()])
4463			.expect("should create consumer with empty prefix")
4464			.announced();
4465
4466		// Should see all broadcasts from all roots
4467		consumer.assert_next_some("app1/data");
4468		consumer.assert_next_some("app2/config");
4469		consumer.assert_next_some("shared/resource");
4470		consumer.assert_next_wait();
4471	}
4472
4473	#[tokio::test]
4474	async fn test_publish_scope_with_empty_prefix() {
4475		let origin = Origin::random().produce();
4476
4477		// Producer with specific allowed paths
4478		let limited_producer = origin
4479			.scope(&["services/api".into(), "services/web".into()])
4480			.expect("should create limited producer");
4481
4482		// scope with empty prefix should keep the same restrictions
4483		let same_producer = limited_producer
4484			.scope(&["".into()])
4485			.expect("should create producer with empty prefix");
4486
4487		// Should still have the same publishing restrictions
4488		let _broadcast = same_producer
4489			.create_broadcast("services/api", announce())
4490			.expect("publish allowed");
4491		let _keep2 = same_producer
4492			.create_broadcast("services/web", announce())
4493			.expect("publish allowed");
4494		assert!(same_producer.create_broadcast("services/db", announce()).is_err());
4495		assert!(same_producer.create_broadcast("other", announce()).is_err());
4496	}
4497
4498	#[tokio::test]
4499	async fn test_select_narrowing_to_deeper_path() {
4500		let origin = Origin::random().produce();
4501
4502		// Producer with broad permission
4503		let limited_producer = origin.scope(&["org".into()]).expect("should create limited producer");
4504
4505		// Publish at various depths
4506		let _broadcast1 = limited_producer
4507			.create_broadcast("org/team1/project1", announce())
4508			.expect("publish allowed");
4509		let _broadcast2 = limited_producer
4510			.create_broadcast("org/team1/project2", announce())
4511			.expect("publish allowed");
4512		let _broadcast3 = limited_producer
4513			.create_broadcast("org/team2/project1", announce())
4514			.expect("publish allowed");
4515		settle().await;
4516
4517		// Narrow down to team2 only
4518		let mut team2_consumer = limited_producer
4519			.consume()
4520			.scope(&["org/team2".into()])
4521			.expect("should create team2 consumer")
4522			.announced();
4523
4524		team2_consumer.assert_next_some("org/team2/project1");
4525		team2_consumer.assert_next_wait(); // Should NOT see team1 content
4526
4527		// Further narrow down to team1/project1
4528		let mut project1_consumer = limited_producer
4529			.consume()
4530			.scope(&["org/team1/project1".into()])
4531			.expect("should create project1 consumer")
4532			.announced();
4533
4534		// Should only see project1 content at root
4535		project1_consumer.assert_next_some("org/team1/project1");
4536		project1_consumer.assert_next_wait();
4537	}
4538
4539	#[tokio::test]
4540	async fn test_select_with_non_matching_prefix() {
4541		let origin = Origin::random().produce();
4542
4543		// Producer with specific allowed paths
4544		let limited_producer = origin
4545			.scope(&["allowed/path".into()])
4546			.expect("should create limited producer");
4547
4548		// Trying to scope with a completely different prefix should return None
4549		assert!(limited_producer.consume().scope(&["different/path".into()]).is_none());
4550
4551		// Similarly for scope
4552		assert!(limited_producer.scope(&["other/path".into()]).is_none());
4553	}
4554
4555	// Regression test for https://github.com/moq-dev/moq/issues/910
4556	// with_root panics when String has trailing slash (AsPath for String skips normalization)
4557	#[tokio::test]
4558	async fn test_with_root_trailing_slash_consumer() {
4559		let origin = Origin::random().produce();
4560
4561		// Use an owned String so the trailing slash is NOT normalized away.
4562		let prefix = "some_prefix/".to_string();
4563		let mut consumer = origin.consume().with_root(prefix).unwrap().announced();
4564
4565		let _b = origin.create_broadcast("some_prefix/test", announce()).unwrap();
4566		settle().await;
4567		consumer.assert_next_some("test");
4568	}
4569
4570	// Same issue but for the producer side of with_root
4571	#[tokio::test]
4572	async fn test_with_root_trailing_slash_producer() {
4573		let origin = Origin::random().produce();
4574
4575		// Use an owned String so the trailing slash is NOT normalized away.
4576		let prefix = "some_prefix/".to_string();
4577		let rooted = origin.with_root(prefix).unwrap();
4578
4579		let _b = rooted.create_broadcast("test", announce()).unwrap();
4580		settle().await;
4581
4582		let mut consumer = rooted.consume().announced();
4583		consumer.assert_next_some("test");
4584	}
4585
4586	// Verify unannounce also doesn't panic with trailing slash
4587	#[tokio::test]
4588	async fn test_with_root_trailing_slash_unannounce() {
4589		tokio::time::pause();
4590
4591		let origin = Origin::random().produce();
4592
4593		let prefix = "some_prefix/".to_string();
4594		let mut consumer = origin.consume().with_root(prefix).unwrap().announced();
4595
4596		let mut b = origin.create_broadcast("some_prefix/test", announce()).unwrap();
4597		settle().await;
4598		consumer.assert_next_some("test");
4599
4600		// Finish the broadcast to trigger an immediate unannounce.
4601		b.finish();
4602		settle().await;
4603
4604		// unannounce also calls strip_prefix(&self.root).unwrap()
4605		consumer.assert_next_none("test");
4606	}
4607
4608	#[tokio::test]
4609	async fn test_select_maintains_access_with_wider_prefix() {
4610		let origin = Origin::random().produce();
4611
4612		// Setup: user with root "demo" allowed to subscribe to specific paths
4613		let demo_producer = origin.with_root("demo").expect("should create demo root");
4614		let user_producer = demo_producer
4615			.scope(&["worm-node".into(), "foobar".into()])
4616			.expect("should create user producer");
4617
4618		// Publish some data
4619		let _broadcast1 = user_producer
4620			.create_broadcast("worm-node/data", announce())
4621			.expect("publish allowed");
4622		let _broadcast2 = user_producer
4623			.create_broadcast("foobar", announce())
4624			.expect("publish allowed");
4625		settle().await;
4626
4627		// Key test: scope with "" should maintain access to allowed roots
4628		let mut consumer = user_producer
4629			.consume()
4630			.scope(&["".into()])
4631			.expect("scope with empty prefix should not fail when user has specific permissions")
4632			.announced();
4633
4634		// Should still receive broadcasts from allowed paths (order not guaranteed)
4635		let a1 = consumer.try_next().expect("expected first announcement");
4636		let a2 = consumer.try_next().expect("expected second announcement");
4637		consumer.assert_next_wait();
4638
4639		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
4640		paths.sort();
4641		assert_eq!(paths, ["foobar", "worm-node/data"]);
4642
4643		// Also test that we can still narrow the scope
4644		let mut narrow_consumer = user_producer
4645			.consume()
4646			.scope(&["worm-node".into()])
4647			.expect("should be able to narrow scope to worm-node")
4648			.announced();
4649
4650		narrow_consumer.assert_next_some("worm-node/data");
4651		narrow_consumer.assert_next_wait(); // Should not see foobar
4652	}
4653
4654	#[tokio::test]
4655	async fn test_duplicate_prefixes_deduped() {
4656		let origin = Origin::random().produce();
4657
4658		// scope with duplicate prefixes should work (deduped internally)
4659		let producer = origin
4660			.scope(&["demo".into(), "demo".into()])
4661			.expect("should create producer");
4662
4663		let _broadcast = producer
4664			.create_broadcast("demo/stream", announce())
4665			.expect("publish allowed");
4666		settle().await;
4667
4668		let mut consumer = producer.consume().announced();
4669		consumer.assert_next_some("demo/stream");
4670		consumer.assert_next_wait();
4671	}
4672
4673	#[tokio::test]
4674	async fn test_overlapping_prefixes_deduped() {
4675		let origin = Origin::random().produce();
4676
4677		// "demo" and "demo/foo". "demo/foo" is redundant, only "demo" should remain
4678		let producer = origin
4679			.scope(&["demo".into(), "demo/foo".into()])
4680			.expect("should create producer");
4681
4682		// Can still publish under "demo/bar" since "demo" covers everything
4683		let _broadcast = producer
4684			.create_broadcast("demo/bar/stream", announce())
4685			.expect("publish allowed");
4686		settle().await;
4687
4688		let mut consumer = producer.consume().announced();
4689		consumer.assert_next_some("demo/bar/stream");
4690		consumer.assert_next_wait();
4691	}
4692
4693	#[tokio::test]
4694	async fn test_overlapping_prefixes_no_duplicate_announcements() {
4695		let origin = Origin::random().produce();
4696
4697		// Both "demo" and "demo/foo" are requested. Should only have one node
4698		let producer = origin
4699			.scope(&["demo".into(), "demo/foo".into()])
4700			.expect("should create producer");
4701
4702		let _broadcast = producer
4703			.create_broadcast("demo/foo/stream", announce())
4704			.expect("publish allowed");
4705		settle().await;
4706
4707		let mut consumer = producer.consume().announced();
4708		// Should only get ONE announcement (not two from overlapping nodes)
4709		consumer.assert_next_some("demo/foo/stream");
4710		consumer.assert_next_wait();
4711	}
4712
4713	#[tokio::test]
4714	async fn test_allowed_returns_deduped_prefixes() {
4715		let origin = Origin::random().produce();
4716
4717		let producer = origin
4718			.scope(&["demo".into(), "demo/foo".into(), "anon".into()])
4719			.expect("should create producer");
4720
4721		let allowed: Vec<_> = producer.allowed().collect();
4722		assert_eq!(allowed.len(), 2, "demo/foo should be subsumed by demo");
4723	}
4724
4725	#[tokio::test]
4726	async fn test_announced_broadcast_already_announced() {
4727		let origin = Origin::random().produce();
4728
4729		let _broadcast = origin.create_broadcast("test", announce()).unwrap();
4730		settle().await;
4731
4732		let consumer = origin.consume();
4733		let result = consumer.announced_broadcast("test").await.expect("should find it");
4734		assert!(result.is_clone(&consumer.get_broadcast("test").unwrap()));
4735	}
4736
4737	#[tokio::test]
4738	async fn test_announced_broadcast_delayed() {
4739		tokio::time::pause();
4740
4741		let origin = Origin::random().produce();
4742
4743		let consumer = origin.consume();
4744
4745		// Start waiting before it's announced.
4746		let wait = tokio::spawn({
4747			let consumer = consumer.clone();
4748			async move { consumer.announced_broadcast("test").await }
4749		});
4750
4751		// Give the spawned task a chance to subscribe.
4752		tokio::task::yield_now().await;
4753
4754		let _broadcast = origin.create_broadcast("test", announce()).unwrap();
4755		settle().await;
4756
4757		let result = wait.await.unwrap().expect("should find it");
4758		assert!(result.is_clone(&consumer.get_broadcast("test").unwrap()));
4759	}
4760
4761	#[tokio::test]
4762	async fn test_announced_broadcast_ignores_unrelated_paths() {
4763		tokio::time::pause();
4764
4765		let origin = Origin::random().produce();
4766
4767		let consumer = origin.consume();
4768
4769		let wait = tokio::spawn({
4770			let consumer = consumer.clone();
4771			async move { consumer.announced_broadcast("target").await }
4772		});
4773
4774		tokio::task::yield_now().await;
4775
4776		// Publish an unrelated broadcast first. announced_broadcast should skip it.
4777		let _other = origin.create_broadcast("other", announce()).unwrap();
4778		settle().await;
4779		tokio::task::yield_now().await;
4780		assert!(!wait.is_finished(), "must not resolve on unrelated path");
4781
4782		let _target = origin.create_broadcast("target", announce()).unwrap();
4783		settle().await;
4784		let result = wait.await.unwrap().expect("should find target");
4785		assert!(result.is_clone(&consumer.get_broadcast("target").unwrap()));
4786	}
4787
4788	#[tokio::test]
4789	async fn test_announced_broadcast_skips_nested_paths() {
4790		tokio::time::pause();
4791
4792		let origin = Origin::random().produce();
4793
4794		let consumer = origin.consume();
4795
4796		let wait = tokio::spawn({
4797			let consumer = consumer.clone();
4798			async move { consumer.announced_broadcast("foo").await }
4799		});
4800
4801		tokio::task::yield_now().await;
4802
4803		// "foo/bar" is under the prefix scope, but it's not the exact path. Skip it.
4804		let _nested = origin.create_broadcast("foo/bar", announce()).unwrap();
4805		settle().await;
4806		tokio::task::yield_now().await;
4807		assert!(!wait.is_finished(), "must not resolve on a nested path");
4808
4809		let _exact = origin.create_broadcast("foo", announce()).unwrap();
4810		settle().await;
4811		let result = wait.await.unwrap().expect("should find foo exactly");
4812		assert!(result.is_clone(&consumer.get_broadcast("foo").unwrap()));
4813	}
4814
4815	#[tokio::test]
4816	async fn test_announced_broadcast_disallowed() {
4817		let origin = Origin::random().produce();
4818		let limited = origin
4819			.consume()
4820			.scope(&["allowed".into()])
4821			.expect("should create limited");
4822
4823		// Path is outside allowed prefixes. Should return None immediately.
4824		assert!(limited.announced_broadcast("notallowed").await.is_none());
4825	}
4826
4827	#[tokio::test]
4828	async fn test_announced_broadcast_scope_too_narrow() {
4829		// Consumer's scope is narrower than the requested path: asking for `foo` on a consumer
4830		// limited to `foo/specific` can never resolve. Must return None, not loop forever.
4831		let origin = Origin::random().produce();
4832		let limited = origin
4833			.consume()
4834			.scope(&["foo/specific".into()])
4835			.expect("should create limited");
4836
4837		// now_or_never so we fail fast instead of hanging if the guard regresses.
4838		let result = limited
4839			.announced_broadcast("foo")
4840			.now_or_never()
4841			.expect("must not block");
4842		assert!(result.is_none());
4843	}
4844
4845	// Coalescing tests: a slow cursor that doesn't drain between updates
4846	// should observe a bounded number of deliveries.
4847
4848	#[tokio::test]
4849	async fn test_coalesce_announce_then_unannounce() {
4850		// announce + unannounce that the cursor hasn't observed yet collapses to nothing.
4851		tokio::time::pause();
4852
4853		let origin = Origin::random().produce();
4854		let mut announced = origin.consume().announced();
4855
4856		let mut broadcast = origin.create_broadcast("test", announce()).unwrap();
4857		settle().await;
4858		broadcast.finish();
4859
4860		settle().await;
4861
4862		announced.assert_next_wait();
4863	}
4864
4865	#[tokio::test]
4866	async fn test_coalesce_announce_unannounce_announce() {
4867		// announce, unannounce, announce that the cursor hasn't drained collapses
4868		// to a single Announce of the latest broadcast.
4869		tokio::time::pause();
4870
4871		let origin = Origin::random().produce();
4872		let mut announced = origin.consume().announced();
4873
4874		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
4875		settle().await;
4876		broadcast1.finish();
4877		settle().await;
4878		let _broadcast2 = origin.create_broadcast("test", announce()).unwrap();
4879		settle().await;
4880
4881		announced.assert_next_some("test");
4882		announced.assert_next_wait();
4883	}
4884
4885	#[tokio::test]
4886	async fn test_coalesce_unannounce_announce_preserved() {
4887		// unannounce followed by announce of a different broadcast must be preserved
4888		// as two deliveries so the cursor learns the origin changed.
4889		tokio::time::pause();
4890
4891		let origin = Origin::random().produce();
4892		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
4893		settle().await;
4894
4895		let mut announced = origin.consume().announced();
4896		announced.assert_next_some("test");
4897
4898		// Finish, then publish a fresh broadcast at the same path.
4899		broadcast1.finish();
4900		settle().await;
4901
4902		let _broadcast2 = origin.create_broadcast("test", announce()).unwrap();
4903		settle().await;
4904
4905		// The cursor must see the unannounce before the new announce.
4906		announced.assert_next_none("test");
4907		announced.assert_next_some("test");
4908		announced.assert_next_wait();
4909	}
4910
4911	#[tokio::test]
4912	async fn test_coalesce_unannounce_announce_unannounce() {
4913		// unannounce + announce + unannounce collapses to a single unannounce: the
4914		// embedded announce was never observed.
4915		tokio::time::pause();
4916
4917		let origin = Origin::random().produce();
4918		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
4919		settle().await;
4920
4921		let mut announced = origin.consume().announced();
4922		announced.assert_next_some("test");
4923
4924		broadcast1.finish();
4925		settle().await;
4926
4927		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
4928		settle().await;
4929		broadcast2.finish();
4930		settle().await;
4931
4932		announced.assert_next_none("test");
4933		announced.assert_next_wait();
4934	}
4935
4936	#[tokio::test]
4937	async fn test_coalesce_churn_bounded() {
4938		// A churn loop on a single path should keep the pending set bounded.
4939		// Backup promotion during cleanup can leave the cursor with zero or one
4940		// pending update for "test" depending on the order tasks run; we only
4941		// require that churn doesn't accumulate across iterations.
4942		tokio::time::pause();
4943
4944		let origin = Origin::random().produce();
4945		let mut announced = origin.consume().announced();
4946
4947		for _ in 0..1000 {
4948			let mut broadcast = origin.create_broadcast("test", announce()).unwrap();
4949			settle().await;
4950			broadcast.finish();
4951		}
4952		settle().await;
4953
4954		let mut collected = Vec::new();
4955		while let Some(update) = announced.try_next() {
4956			collected.push(update);
4957		}
4958		assert!(
4959			collected.len() <= 1,
4960			"expected at most one pending update, got {}",
4961			collected.len()
4962		);
4963		assert!(
4964			collected.iter().all(|a| a.path == Path::new("test")),
4965			"unexpected path in pending updates",
4966		);
4967	}
4968
4969	// Consumer should be cheap to clone: cloning must NOT drain any
4970	// other cursor's announce channel. A freshly-built AnnounceConsumer
4971	// still receives the active backlog.
4972	#[tokio::test]
4973	async fn test_consumer_clone_is_side_effect_free() {
4974		let origin = Origin::random().produce();
4975
4976		let _broadcast1 = origin.create_broadcast("test1", announce()).unwrap();
4977		let _broadcast2 = origin.create_broadcast("test2", announce()).unwrap();
4978		settle().await;
4979
4980		let consumer = origin.consume();
4981		let mut announced = consumer.announced();
4982
4983		// Cloning the Consumer many times and looking up broadcasts
4984		// must not consume any events from the existing cursor.
4985		for _ in 0..16 {
4986			let cloned = consumer.clone();
4987			assert!(cloned.get_broadcast("test1").is_some());
4988			assert!(cloned.get_broadcast("test2").is_some());
4989		}
4990
4991		// The original cursor still sees both announcements in their
4992		// natural order, undisturbed by the clones above.
4993		let a1 = announced.try_next().expect("first announcement");
4994		let a2 = announced.try_next().expect("second announcement");
4995		announced.assert_next_wait();
4996
4997		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
4998		paths.sort();
4999		assert_eq!(paths, ["test1", "test2"]);
5000
5001		// A freshly-built AnnounceConsumer still receives the active backlog.
5002		let mut fresh = consumer.announced();
5003		let b1 = fresh.try_next().expect("backlog: first");
5004		let b2 = fresh.try_next().expect("backlog: second");
5005		fresh.assert_next_wait();
5006
5007		let mut paths: Vec<_> = [&b1, &b2].iter().map(|a| a.path.to_string()).collect();
5008		paths.sort();
5009		assert_eq!(paths, ["test1", "test2"]);
5010	}
5011
5012	// With no Dynamic handler, an unannounced path resolves to Unroutable.
5013	#[tokio::test]
5014	async fn dynamic_request_unroutable_without_handler() {
5015		let origin = Origin::random().produce();
5016		let consumer = origin.consume();
5017		assert!(matches!(
5018			consumer.request_broadcast("missing").await,
5019			Err(Error::Unroutable)
5020		));
5021	}
5022
5023	// A dynamically served broadcast resolves the requester and serves tracks, but is
5024	// never announced.
5025	#[tokio::test(start_paused = true)]
5026	async fn dynamic_request_served_not_announced() {
5027		let origin = Origin::random().produce();
5028		let mut dynamic = origin.dynamic();
5029		let consumer = origin.consume();
5030
5031		// A separate announce cursor must never observe the dynamic broadcast.
5032		let mut announced = origin.consume().announced();
5033		announced.assert_next_wait();
5034
5035		let served = broadcast::Info::new().produce();
5036		// Request a path that nobody announced; the future stays pending until served.
5037		// Registration happens up front, so the handler sees the request immediately.
5038		let request_fut = consumer.request_broadcast("fallback");
5039
5040		// The handler serves it with a live broadcast it keeps producing into.
5041		let mut served_dynamic = served.dynamic();
5042
5043		let request = dynamic.requested_broadcast().await.unwrap();
5044		assert_eq!(request.path(), &Path::new("fallback"));
5045		request.accept(&served);
5046
5047		let broadcast = request_fut.await.unwrap();
5048		assert!(broadcast.is_clone(&served.consume()));
5049
5050		// The served broadcast is live: a track subscription resolves via its handler.
5051		let track_fut = broadcast.track("video").unwrap().subscribe(None);
5052		let mut producer = served_dynamic.requested_track().await.unwrap().accept(None);
5053		let mut track = track_fut.await.unwrap();
5054		producer.append_group().unwrap();
5055		track.assert_group();
5056
5057		// Still nothing announced.
5058		announced.assert_next_wait();
5059	}
5060
5061	// Concurrent requests for the same queued path coalesce onto one handler request.
5062	#[tokio::test(start_paused = true)]
5063	async fn dynamic_request_coalesces() {
5064		let origin = Origin::random().produce();
5065		let mut dynamic = origin.dynamic();
5066		let consumer = origin.consume();
5067
5068		// Both register before the handler drains either.
5069		let f1 = consumer.request_broadcast("dup");
5070		let f2 = consumer.request_broadcast("dup");
5071
5072		// Exactly one request reaches the handler.
5073		let request = dynamic.requested_broadcast().await.unwrap();
5074		assert_eq!(request.path(), &Path::new("dup"));
5075		assert!(
5076			dynamic.requested_broadcast().now_or_never().is_none(),
5077			"a coalesced request must not be served twice"
5078		);
5079
5080		// Accepting resolves both awaiting requesters with the same broadcast.
5081		let served = broadcast::Info::new().produce();
5082		request.accept(&served);
5083		assert!(f1.await.unwrap().is_clone(&served.consume()));
5084		assert!(f2.await.unwrap().is_clone(&served.consume()));
5085	}
5086
5087	// A repeat request for an already-served, still-live path shares the same broadcast
5088	// instead of asking the handler again (no duplicate upstream subscription).
5089	#[tokio::test(start_paused = true)]
5090	async fn dynamic_request_dedups_served() {
5091		let origin = Origin::random().produce();
5092		let mut dynamic = origin.dynamic();
5093		let consumer = origin.consume();
5094
5095		let request_fut = consumer.request_broadcast("fallback");
5096		let request = dynamic.requested_broadcast().await.unwrap();
5097		let served = broadcast::Info::new().produce();
5098		request.accept(&served);
5099		let first = request_fut.await.unwrap();
5100		assert!(first.is_clone(&served.consume()));
5101
5102		// The repeat resolves immediately to the same broadcast...
5103		let second = consumer.request_broadcast("fallback").await.unwrap();
5104		assert!(second.is_clone(&served.consume()));
5105
5106		// ...and the handler never sees a second request.
5107		assert!(
5108			dynamic.requested_broadcast().now_or_never().is_none(),
5109			"a still-live served broadcast must not be re-requested from the handler"
5110		);
5111	}
5112
5113	// Once a served broadcast closes, its cache entry is stale, so the next request re-serves.
5114	#[tokio::test(start_paused = true)]
5115	async fn dynamic_request_reserves_after_close() {
5116		let origin = Origin::random().produce();
5117		let mut dynamic = origin.dynamic();
5118		let consumer = origin.consume();
5119
5120		let request_fut = consumer.request_broadcast("fallback");
5121		let request = dynamic.requested_broadcast().await.unwrap();
5122		let served = broadcast::Info::new().produce();
5123		request.accept(&served);
5124		request_fut.await.unwrap();
5125
5126		// Close the first served broadcast; the weak cache entry goes stale.
5127		drop(served);
5128
5129		// A fresh request must reach the handler again and resolve to the new broadcast.
5130		let request_fut = consumer.request_broadcast("fallback");
5131		let request = dynamic.requested_broadcast().await.unwrap();
5132		assert_eq!(request.path(), &Path::new("fallback"));
5133		let served = broadcast::Info::new().produce();
5134		request.accept(&served);
5135		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
5136	}
5137
5138	// Serving many distinct one-shot paths that each close must not grow the `served` cache
5139	// unboundedly: the amortized GC on `accept` reclaims the stale entries left by closed ones.
5140	#[tokio::test(start_paused = true)]
5141	async fn dynamic_request_served_cache_bounded() {
5142		let origin = Origin::random().produce();
5143		let mut dynamic = origin.dynamic();
5144		let consumer = origin.consume();
5145
5146		for i in 0..100 {
5147			let path = format!("one-shot/{i}");
5148			let request_fut = consumer.request_broadcast(&path);
5149			let request = dynamic.requested_broadcast().await.unwrap();
5150			let served = broadcast::Info::new().produce();
5151			request.accept(&served);
5152			request_fut.await.unwrap();
5153			// Close the served broadcast; its cache entry is now stale.
5154			drop(served);
5155		}
5156
5157		// The GC keeps the map bounded by the live count (zero here) plus a small probe window,
5158		// rather than one entry per distinct path.
5159		assert!(
5160			origin.dynamic.read().served.len() <= 4,
5161			"stale served entries must be reclaimed, not accumulate per distinct path: {}",
5162			origin.dynamic.read().served.len()
5163		);
5164	}
5165
5166	// A repeat request in the window after the handler picks one up but before it accepts
5167	// coalesces onto the in-flight request instead of queuing a duplicate.
5168	#[tokio::test(start_paused = true)]
5169	async fn dynamic_request_coalesces_after_handoff() {
5170		let origin = Origin::random().produce();
5171		let mut dynamic = origin.dynamic();
5172		let consumer = origin.consume();
5173
5174		let f1 = consumer.request_broadcast("fallback");
5175		// Handler drains the request but has not accepted yet.
5176		let request = dynamic.requested_broadcast().await.unwrap();
5177
5178		// A second request in this window must not queue another handler request.
5179		let f2 = consumer.request_broadcast("fallback");
5180		assert!(
5181			dynamic.requested_broadcast().now_or_never().is_none(),
5182			"a repeat request during hand-off must coalesce, not re-queue"
5183		);
5184
5185		// Accepting resolves both awaiting requesters with the same broadcast.
5186		let served = broadcast::Info::new().produce();
5187		request.accept(&served);
5188		assert!(f1.await.unwrap().is_clone(&served.consume()));
5189		assert!(f2.await.unwrap().is_clone(&served.consume()));
5190	}
5191
5192	// Dropping a handed-off request without accept/reject rejects every coalesced requester.
5193	#[tokio::test(start_paused = true)]
5194	async fn dynamic_request_dropped_after_handoff() {
5195		let origin = Origin::random().produce();
5196		let mut dynamic = origin.dynamic();
5197		let consumer = origin.consume();
5198
5199		let f1 = consumer.request_broadcast("fallback");
5200		let request = dynamic.requested_broadcast().await.unwrap();
5201		let f2 = consumer.request_broadcast("fallback");
5202
5203		// Abandon it; both requesters resolve to Unroutable instead of hanging.
5204		drop(request);
5205		assert!(matches!(f1.await, Err(Error::Unroutable)));
5206		assert!(matches!(f2.await, Err(Error::Unroutable)));
5207	}
5208
5209	// Rejecting a request resolves the requester with the error.
5210	#[tokio::test(start_paused = true)]
5211	async fn dynamic_request_rejected() {
5212		let origin = Origin::random().produce();
5213		let mut dynamic = origin.dynamic();
5214		let consumer = origin.consume();
5215
5216		let request_fut = consumer.request_broadcast("fallback");
5217
5218		let request = dynamic.requested_broadcast().await.unwrap();
5219		request.reject(Error::Cancel);
5220
5221		assert!(matches!(request_fut.await, Err(Error::Cancel)));
5222	}
5223
5224	// After a rejected hand-off, a fresh request for the same path reaches the handler again:
5225	// the rejected `Request`'s removal + `Drop` leave the request queue consistent
5226	// (a stale/clobbered entry would strand this request or panic the handler).
5227	#[tokio::test(start_paused = true)]
5228	async fn dynamic_request_rerequest_after_reject() {
5229		let origin = Origin::random().produce();
5230		let mut dynamic = origin.dynamic();
5231		let consumer = origin.consume();
5232
5233		let f1 = consumer.request_broadcast("fallback");
5234		dynamic.requested_broadcast().await.unwrap().reject(Error::Unroutable);
5235		assert!(matches!(f1.await, Err(Error::Unroutable)));
5236
5237		let served = broadcast::Info::new().produce();
5238		// A fresh request re-reaches the handler and can be served.
5239		let f2 = consumer.request_broadcast("fallback");
5240		let request = dynamic.requested_broadcast().await.unwrap();
5241		assert_eq!(request.path(), &Path::new("fallback"));
5242		request.accept(&served);
5243		assert!(f2.await.unwrap().is_clone(&served.consume()));
5244	}
5245
5246	// Dropping the last handler resolves queued requests with an error and reverts to
5247	// resolving Unroutable.
5248	#[tokio::test(start_paused = true)]
5249	async fn dynamic_request_handler_dropped() {
5250		let origin = Origin::random().produce();
5251		let dynamic = origin.dynamic();
5252		let consumer = origin.consume();
5253
5254		let request_fut = consumer.request_broadcast("fallback");
5255		drop(dynamic);
5256		assert!(matches!(request_fut.await, Err(Error::Unroutable)));
5257
5258		// With no handler left, a fresh request resolves Unroutable.
5259		assert!(matches!(
5260			consumer.request_broadcast("again").await,
5261			Err(Error::Unroutable)
5262		));
5263	}
5264
5265	// `accept` is decoupled from the dynamic count: once a handler has picked a request up,
5266	// it can still serve it even if every handler (including itself) drops first, flipping the
5267	// count to zero. The in-flight request must not be rejected as `Unroutable`.
5268	#[tokio::test(start_paused = true)]
5269	async fn dynamic_request_accept_after_handler_dropped() {
5270		let origin = Origin::random().produce();
5271		let mut dynamic = origin.dynamic();
5272		let consumer = origin.consume();
5273
5274		let request_fut = consumer.request_broadcast("fallback");
5275
5276		// The handler picks the request up, then every handler drops (count -> 0).
5277		let request = dynamic.requested_broadcast().await.unwrap();
5278		drop(dynamic);
5279
5280		let served = broadcast::Info::new().produce();
5281		// Accept still resolves the awaiting requester with the served broadcast.
5282		request.accept(&served);
5283		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
5284	}
5285
5286	// A published broadcast wins over the dynamic fallback; no request is queued.
5287	#[tokio::test(start_paused = true)]
5288	async fn dynamic_request_prefers_announced() {
5289		let origin = Origin::random().produce();
5290		let mut dynamic = origin.dynamic();
5291		let consumer = origin.consume();
5292
5293		let _broadcast = origin.create_broadcast("live", announce()).unwrap();
5294		settle().await;
5295
5296		let got = consumer.request_broadcast("live").await.unwrap();
5297		assert!(
5298			got.is_clone(&consumer.get_broadcast("live").unwrap()),
5299			"should return the published broadcast"
5300		);
5301		assert!(
5302			dynamic.requested_broadcast().now_or_never().is_none(),
5303			"a published path must not queue a fallback request"
5304		);
5305	}
5306
5307	// Cloning a handler and dropping the clone must not flip the count to zero.
5308	#[tokio::test(start_paused = true)]
5309	async fn dynamic_clone_keeps_alive() {
5310		let origin = Origin::random().produce();
5311		let dynamic = origin.dynamic();
5312		let consumer = origin.consume();
5313
5314		drop(dynamic.clone());
5315
5316		// The original handle is still live, so the request registers (stays pending)
5317		// instead of resolving Unroutable.
5318		let request_fut = consumer.request_broadcast("fallback");
5319		assert!(
5320			request_fut.now_or_never().is_none(),
5321			"request should stay pending until served"
5322		);
5323	}
5324}