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