From d26eeaf2c01b107be3deb27a03da0198dab7b4c7 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:08:36 -0700 Subject: [PATCH 01/15] wip: Path loses its lifetime --- quest/m2/audit-compile-time.md | 2 +- rs/hang/src/catalog/archive.rs | 4 +- rs/hang/src/catalog/audio/mod.rs | 2 +- rs/hang/src/catalog/binary.rs | 2 +- rs/hang/src/catalog/consumer.rs | 12 +- rs/hang/src/catalog/echo.rs | 4 +- rs/hang/src/catalog/json.rs | 2 +- rs/hang/src/catalog/reference.rs | 4 +- rs/hang/src/catalog/root.rs | 4 +- rs/hang/src/catalog/text/mod.rs | 2 +- rs/hang/src/catalog/video/mod.rs | 2 +- rs/hang/src/health.rs | 8 +- rs/moq-c/src/consume.rs | 4 +- rs/moq-c/src/origin.rs | 2 +- rs/moq-c/src/test.rs | 2 +- rs/moq-cli/src/archive.rs | 26 +- rs/moq-cli/src/args.rs | 2 +- rs/moq-cli/src/play/media.rs | 2 +- rs/moq-cli/src/play/playback.rs | 2 +- rs/moq-cli/src/report.rs | 20 +- rs/moq-cli/src/subscribe.rs | 4 +- rs/moq-cli/src/transcode.rs | 8 +- rs/moq-cli/tests/integration/epochs.rs | 2 +- rs/moq-e2ee/src/credential.rs | 4 +- rs/moq-ffi/src/media.rs | 4 +- rs/moq-gst/src/source/imp.rs | 12 +- rs/moq-hls/src/export/mod.rs | 18 +- rs/moq-hls/src/export/rendition.rs | 4 +- rs/moq-hls/src/server/routes.rs | 4 +- rs/moq-hls/tests/integration/server.rs | 2 +- rs/moq-mux/src/binary.rs | 2 +- rs/moq-mux/src/catalog/hang/consumer.rs | 8 +- rs/moq-mux/src/container/source.rs | 6 +- rs/moq-mux/src/container/test_util.rs | 2 +- rs/moq-mux/src/container/ts/follower.rs | 2 +- rs/moq-mux/src/container/ts/programs.rs | 6 +- rs/moq-mux/src/follow.rs | 2 +- rs/moq-mux/src/json.rs | 2 +- rs/moq-mux/src/source.rs | 30 +- rs/moq-net/Cargo.toml | 7 +- rs/moq-net/MIGRATE.md | 10 + rs/moq-net/benches/announce.rs | 10 +- rs/moq-net/benches/path.rs | 197 +++++ rs/moq-net/src/auth.rs | 32 +- rs/moq-net/src/discovery.rs | 36 +- rs/moq-net/src/fuzz.rs | 60 +- rs/moq-net/src/ietf/adapter.rs | 30 +- rs/moq-net/src/ietf/fetch.rs | 4 +- rs/moq-net/src/ietf/namespace.rs | 12 +- rs/moq-net/src/ietf/publish.rs | 2 +- rs/moq-net/src/ietf/publish_namespace.rs | 16 +- rs/moq-net/src/ietf/publisher.rs | 118 +-- rs/moq-net/src/ietf/publisher/tracks.rs | 54 +- rs/moq-net/src/ietf/request_stream.rs | 4 +- rs/moq-net/src/ietf/subscribe.rs | 2 +- rs/moq-net/src/ietf/subscribe_namespace.rs | 34 +- rs/moq-net/src/ietf/subscribe_tracks.rs | 10 +- rs/moq-net/src/ietf/subscriber.rs | 198 ++--- rs/moq-net/src/ietf/subscriber/namespaces.rs | 6 +- rs/moq-net/src/ietf/subscriber/tracks.rs | 35 +- rs/moq-net/src/ietf/track.rs | 2 +- rs/moq-net/src/lib.rs | 2 +- rs/moq-net/src/lite/announce.rs | 79 +- rs/moq-net/src/lite/compress.rs | 48 +- rs/moq-net/src/lite/fetch.rs | 4 +- rs/moq-net/src/lite/publisher.rs | 65 +- rs/moq-net/src/lite/subscribe.rs | 4 +- rs/moq-net/src/lite/subscriber.rs | 142 ++-- rs/moq-net/src/lite/track.rs | 6 +- rs/moq-net/src/lite/tracks.rs | 10 +- rs/moq-net/src/model/broadcast.rs | 6 +- rs/moq-net/src/model/follow.rs | 10 +- rs/moq-net/src/model/origin.rs | 376 ++++----- rs/moq-net/src/path/mod.rs | 782 ++++++++---------- rs/moq-net/src/stats.rs | 74 +- rs/moq-net/src/transport.rs | 2 +- rs/moq-relay/src/auth.rs | 30 +- rs/moq-relay/src/cluster.rs | 4 +- rs/moq-relay/src/stats.rs | 4 +- rs/moq-relay/tests/integration/smoke.rs | 8 +- rs/moq-room/src/claims.rs | 6 +- rs/moq-room/src/path.rs | 19 +- rs/moq-room/src/room.rs | 16 +- rs/moq-rtc/src/client/mod.rs | 2 +- rs/moq-rtc/src/client/whip.rs | 2 +- rs/moq-rtc/src/egress.rs | 16 +- rs/moq-rtc/src/server/whep.rs | 6 +- rs/moq-rtc/src/server/whip.rs | 4 +- rs/moq-rtmp/src/dial.rs | 8 +- rs/moq-rtmp/src/server.rs | 8 +- rs/moq-srt/src/dial.rs | 8 +- rs/moq-srt/src/server.rs | 8 +- rs/moq-stats/src/aggregate.rs | 46 +- rs/moq-stats/src/consume.rs | 12 +- rs/moq-stats/src/lib.rs | 44 +- rs/moq-stats/src/produce.rs | 66 +- rs/moq-stats/src/produce_bench.rs | 8 +- rs/moq-tokio/examples/clock.rs | 2 +- rs/moq-transcode/README.md | 2 +- rs/moq-transcode/examples/transcode.rs | 4 +- rs/moq-transcode/src/catalog.rs | 10 +- rs/moq-transcode/src/config.rs | 4 +- rs/moq-transcode/src/lib.rs | 4 +- .../relay-withdraws-lost-publisher.patch | 2 +- .../subscriber-leaks-broadcasts.patch | 2 +- 105 files changed, 1571 insertions(+), 1503 deletions(-) create mode 100644 rs/moq-net/benches/path.rs diff --git a/quest/m2/audit-compile-time.md b/quest/m2/audit-compile-time.md index 7681bf67b77..4f73f9aae90 100644 --- a/quest/m2/audit-compile-time.md +++ b/quest/m2/audit-compile-time.md @@ -106,7 +106,7 @@ instantiating the whole map. in clean builds; the crypto provider stays (m0). usage-rs derive output scales with config fields and is external code. serde visitors per deserializer are inherent to TOML config. moq-rtmp, moq-srt and moq-rtc - entry points generic over `impl AsPath` instantiate their async bodies in + entry points generic over `impl Into` instantiate their async bodies in the `moq` bin (20,327 lines with drop glue, 1.8%); a non-generic inner function would move them, but adds code for little. The catalog JSON diff instantiated per crate is [catalog publish once](/quest/m2/catalog-publish-once.md). diff --git a/rs/hang/src/catalog/archive.rs b/rs/hang/src/catalog/archive.rs index c988e6b007b..c7ffd787682 100644 --- a/rs/hang/src/catalog/archive.rs +++ b/rs/hang/src/catalog/archive.rs @@ -52,7 +52,7 @@ pub struct Archive { /// The MoQ broadcast the archive is served back from, relative to this catalog, if any. #[serde(default)] - pub replay: Option, + pub replay: Option, /// The publisher instance [`replay`](Self::replay) names, when known. Absent accepts the /// first one a reader resolves; see [`Reference::epoch`](super::Reference::epoch). @@ -122,7 +122,7 @@ mod test { #[test] fn recording_roundtrips_replay_store_and_version() { let mut archive = live(); - archive.replay = Some(moq_net::path::RelativeOwned::new("./recordings/clip")); + archive.replay = Some(moq_net::path::Relative::new("./recordings/clip")); archive.store = Some("https://objects.example/rec/".parse().unwrap()); archive.version = Some(Archive::VERSION); diff --git a/rs/hang/src/catalog/audio/mod.rs b/rs/hang/src/catalog/audio/mod.rs index cfdb3d214f3..73f1ad250dc 100644 --- a/rs/hang/src/catalog/audio/mod.rs +++ b/rs/hang/src/catalog/audio/mod.rs @@ -101,7 +101,7 @@ pub struct AudioConfig { /// Resolve it with [`Path::resolve`](moq_net::Path::resolve): a reference that walks /// above the root names no broadcast, so the catalog is rejected. #[serde(default)] - pub broadcast: Option, + pub broadcast: Option, /// The track's name in its broadcast, when it differs from this entry's ID: its key in the /// catalog. Absent means the ID is the track name, as in every catalog before this field. diff --git a/rs/hang/src/catalog/binary.rs b/rs/hang/src/catalog/binary.rs index f0b08dab552..7d24416fa39 100644 --- a/rs/hang/src/catalog/binary.rs +++ b/rs/hang/src/catalog/binary.rs @@ -71,7 +71,7 @@ pub struct BinaryConfig { /// broadcast that served this catalog (e.g. `./source`). If unset, the track lives in the same /// broadcast as the catalog. #[serde(default)] - pub broadcast: Option, + pub broadcast: Option, /// The track's name in its broadcast, when it differs from this entry's ID: its key in the /// catalog. Absent means the ID is the track name, as in every catalog before this field. diff --git a/rs/hang/src/catalog/consumer.rs b/rs/hang/src/catalog/consumer.rs index 114c75b1080..72fd399b78a 100644 --- a/rs/hang/src/catalog/consumer.rs +++ b/rs/hang/src/catalog/consumer.rs @@ -2,7 +2,7 @@ use std::task::{Poll, ready}; -use moq_net::PathOwned; +use moq_net::Path; use serde::de::DeserializeOwned; use super::Catalog; @@ -12,7 +12,7 @@ use crate::Result; pub struct Consumer { // Decoded in two steps, so a catalog needing a newer reader fails with a typed error. inner: moq_json::snapshot::Consumer, - base: PathOwned, + base: Path, _ext: std::marker::PhantomData, } @@ -75,7 +75,7 @@ impl Consumer { /// /// Every section carrying a relative broadcast path must be included here; a section left out /// silently exempts its paths from the containment check. -fn check_resolvable(base: &PathOwned, catalog: &Catalog) -> Result<()> { +fn check_resolvable(base: &Path, catalog: &Catalog) -> Result<()> { let video = catalog.video.renditions.iter(); let audio = catalog.audio.renditions.iter(); let text = catalog.text.renditions.iter(); @@ -110,13 +110,13 @@ mod test { #[test] fn echo_prefix_cannot_escape_the_catalog_root() { - let base = moq_net::PathOwned::from("room/live"); + let base = moq_net::Path::from("room/live"); let mut catalog = Catalog::<()>::from_str(include_str!("../../fixtures/catalog-stats-echo.json")).unwrap(); assert!(check_resolvable(&base, &catalog).is_ok()); let echo = catalog.echo.as_mut().unwrap(); - echo.path = moq_net::path::RelativeOwned::from("../viewers"); + echo.path = moq_net::path::Relative::from("../viewers"); assert!(check_resolvable(&base, &catalog).is_ok()); - catalog.echo.as_mut().unwrap().path = moq_net::path::RelativeOwned::from("../../viewers"); + catalog.echo.as_mut().unwrap().path = moq_net::path::Relative::from("../../viewers"); assert!(matches!( check_resolvable(&base, &catalog), Err(crate::Error::EscapingBroadcast(_)) diff --git a/rs/hang/src/catalog/echo.rs b/rs/hang/src/catalog/echo.rs index a56863c40a9..a1a7e87724f 100644 --- a/rs/hang/src/catalog/echo.rs +++ b/rs/hang/src/catalog/echo.rs @@ -5,12 +5,12 @@ use serde::{Deserialize, Serialize}; #[non_exhaustive] pub struct Echo { /// The feedback prefix relative to the broadcast serving this catalog. - pub path: moq_net::path::RelativeOwned, + pub path: moq_net::path::Relative, } impl Echo { /// Solicit feedback under a prefix relative to the catalog's broadcast. - pub fn new(path: impl Into) -> Self { + pub fn new(path: impl Into) -> Self { Self { path: path.into() } } } diff --git a/rs/hang/src/catalog/json.rs b/rs/hang/src/catalog/json.rs index 10478082256..48b0307464b 100644 --- a/rs/hang/src/catalog/json.rs +++ b/rs/hang/src/catalog/json.rs @@ -69,7 +69,7 @@ pub struct JsonConfig { /// broadcast that served this catalog (e.g. `./source`). If unset, the track lives in the same /// broadcast as the catalog. #[serde(default)] - pub broadcast: Option, + pub broadcast: Option, /// The track's name in its broadcast, when it differs from this entry's ID: its key in the /// catalog. Absent means the ID is the track name, as in every catalog before this field. diff --git a/rs/hang/src/catalog/reference.rs b/rs/hang/src/catalog/reference.rs index 40f982854c6..2647c1ee3d5 100644 --- a/rs/hang/src/catalog/reference.rs +++ b/rs/hang/src/catalog/reference.rs @@ -8,7 +8,7 @@ use super::{AudioConfig, BinaryConfig, JsonConfig, TextConfig, VideoConfig}; /// optionally names the publisher instance the broadcast must be. pub trait Reference { /// The broadcast publishing the track, relative to the catalog's; `None` is the catalog's own. - fn broadcast(&self) -> Option<&moq_net::path::RelativeOwned>; + fn broadcast(&self) -> Option<&moq_net::path::Relative>; /// The publisher instance the broadcast must be; `None` accepts the first one resolved. fn epoch(&self) -> Option<&moq_net::Epoch>; @@ -20,7 +20,7 @@ pub trait Reference { macro_rules! reference { ($($config:ty),*) => {$( impl Reference for $config { - fn broadcast(&self) -> Option<&moq_net::path::RelativeOwned> { + fn broadcast(&self) -> Option<&moq_net::path::Relative> { self.broadcast.as_ref() } diff --git a/rs/hang/src/catalog/root.rs b/rs/hang/src/catalog/root.rs index 533afa8f1e6..ebb3422fd97 100644 --- a/rs/hang/src/catalog/root.rs +++ b/rs/hang/src/catalog/root.rs @@ -1113,7 +1113,7 @@ mod test { "video".to_string(), crate::catalog::Timeline::new("video.timeline.z", 2_000), ); - archive.replay = Some(moq_net::path::RelativeOwned::new("recordings/clip")); + archive.replay = Some(moq_net::path::Relative::new("recordings/clip")); archive.version = Some(crate::catalog::Archive::VERSION); let catalog = Catalog::<()> { @@ -1209,7 +1209,7 @@ mod test { chat.schema = Some("https://example.com/chat.schema.json".to_string()); let mut status = JsonConfig::new(Mode::Snapshot); - status.broadcast = Some(moq_net::path::RelativeOwned::new("source")); + status.broadcast = Some(moq_net::path::Relative::new("source")); let mut thumbnail = BinaryConfig::new(Mode::Snapshot); thumbnail.mime = Some("image/jpeg".to_string()); diff --git a/rs/hang/src/catalog/text/mod.rs b/rs/hang/src/catalog/text/mod.rs index fb0de18a2be..a0b7b49b34d 100644 --- a/rs/hang/src/catalog/text/mod.rs +++ b/rs/hang/src/catalog/text/mod.rs @@ -77,7 +77,7 @@ pub struct TextConfig { /// broadcast that served this catalog (e.g. `../source`). If unset, the track lives in the same /// broadcast as the catalog. #[serde(default)] - pub broadcast: Option, + pub broadcast: Option, /// The track's name in its broadcast, when it differs from this entry's ID: its key in the /// catalog. Absent means the ID is the track name, as in every catalog before this field. diff --git a/rs/hang/src/catalog/video/mod.rs b/rs/hang/src/catalog/video/mod.rs index 3de3607c62a..c894813bebe 100644 --- a/rs/hang/src/catalog/video/mod.rs +++ b/rs/hang/src/catalog/video/mod.rs @@ -188,7 +188,7 @@ pub struct VideoConfig { /// Resolve it with [`Path::resolve`](moq_net::Path::resolve): a reference that walks /// above the root names no broadcast, so the catalog is rejected. #[serde(default)] - pub broadcast: Option, + pub broadcast: Option, /// The track's name in its broadcast, when it differs from this entry's ID: its key in the /// catalog. Absent means the ID is the track name, as in every catalog before this field. diff --git a/rs/hang/src/health.rs b/rs/hang/src/health.rs index 9310eb77006..36c8e44c419 100644 --- a/rs/hang/src/health.rs +++ b/rs/hang/src/health.rs @@ -40,7 +40,7 @@ pub enum Role { #[non_exhaustive] pub struct Observer { /// The publisher's media broadcast, or the viewer's echo broadcast. - pub path: moq_net::PathOwned, + pub path: moq_net::Path, /// The reporting broadcast's publisher epoch, when its route carries one. #[serde_as(as = "Option")] #[serde(skip_serializing_if = "Option::is_none")] @@ -49,7 +49,7 @@ pub struct Observer { impl Observer { /// Name the broadcast a report was read from. - pub fn new(path: moq_net::PathOwned, epoch: Option) -> Self { + pub fn new(path: moq_net::Path, epoch: Option) -> Self { Self { path, epoch } } } @@ -642,7 +642,7 @@ mod tests { fn report(fixture: &Fixture) -> Report { let epoch = fixture.observer.epoch.as_deref().map(|epoch| epoch.parse().unwrap()); - let observer = Observer::new(moq_net::Path::new(&fixture.observer.path).to_owned(), epoch); + let observer = Observer::new(moq_net::Path::new(&fixture.observer.path).clone(), epoch); Report::new(observer, serde_json::from_value(fixture.snapshot.clone()).unwrap()) } @@ -721,7 +721,7 @@ mod tests { ..Default::default() } }; - let observer = Observer::new(moq_net::Path::new("room/live").to_owned(), None); + let observer = Observer::new(moq_net::Path::new("room/live").clone(), None); let previous = Report::new(observer.clone(), snapshot(0, 0)); let current = Report::new(observer, snapshot(u64::MAX, u64::MAX)); let sample = Sample::new(¤t, Duration::ZERO).with_previous(&previous, Duration::from_secs(1)); diff --git a/rs/moq-c/src/consume.rs b/rs/moq-c/src/consume.rs index b54ff3f6cc6..e151f2736c1 100644 --- a/rs/moq-c/src/consume.rs +++ b/rs/moq-c/src/consume.rs @@ -116,7 +116,7 @@ struct ConsumeBroadcast { async fn resolve( broadcast: moq_net::broadcast::Consumer, origin: Option, - reference: Option, + reference: Option, ) -> Result { let Some(reference) = reference.filter(|reference| !reference.is_empty()) else { return Ok(broadcast); @@ -128,7 +128,7 @@ async fn resolve( // first instance resolved, without the epoch's guarantee. origin .driver - .watch(async { Ok(source.resolve(Some(&reference.borrow()), None).await?) }) + .watch(async { Ok(source.resolve(Some(&reference), None).await?) }) .await } diff --git a/rs/moq-c/src/origin.rs b/rs/moq-c/src/origin.rs index e58dd2a1e63..049a03f2bdc 100644 --- a/rs/moq-c/src/origin.rs +++ b/rs/moq-c/src/origin.rs @@ -321,7 +321,7 @@ impl Origin { /// Create an unannounced broadcast at `path` on an origin. /// /// Errors with [`Error::Moq`] if the path is outside the origin's scope. - pub fn create_broadcast( + pub fn create_broadcast>( &self, origin: Id, path: P, diff --git a/rs/moq-c/src/test.rs b/rs/moq-c/src/test.rs index 5a0c5e9c29d..0b304c34532 100644 --- a/rs/moq-c/src/test.rs +++ b/rs/moq-c/src/test.rs @@ -3701,7 +3701,7 @@ fn consume_audio_follows_a_sibling_broadcast_reference() { .renditions .get_mut(&name) .unwrap() - .broadcast = Some(moq_net::path::Relative::new("./source").into_owned()); + .broadcast = Some(moq_net::path::Relative::new("./source")); } let consume = request_broadcast(origin, b"a/pub"); diff --git a/rs/moq-cli/src/archive.rs b/rs/moq-cli/src/archive.rs index 2946db80704..8e3e5b0f11b 100644 --- a/rs/moq-cli/src/archive.rs +++ b/rs/moq-cli/src/archive.rs @@ -47,7 +47,7 @@ pub struct ImportArgs { } /// One publisher instance being recorded: its path, and its epoch when its route has one. -type Instance = (moq_net::PathOwned, Option); +type Instance = (moq_net::Path, Option); /// Record every broadcast announced at or under `prefix` into `args.store`, each publisher /// instance under `/@`, until the announcements end. @@ -68,7 +68,7 @@ pub async fn export(origin: moq_net::origin::Consumer, prefix: String, args: Exp // Announcements leave hidden routes out unless asked, and a prefix naming one asks. let hidden = prefix.split('/').any(|segment| segment.starts_with('.')); - let prefix = moq_net::Path::new(&prefix).to_owned(); + let prefix = moq_net::Path::new(&prefix).clone(); let mut announced = origin.clone().with_hidden(hidden).announced(); let mut recordings = tokio::task::JoinSet::new(); let mut running = Running::default(); @@ -115,11 +115,11 @@ pub async fn export(origin: moq_net::origin::Consumer, prefix: String, args: Exp /// The broadcasts being recorded. Announcements read after another instance took over all /// resolve that one, so it is recorded once. #[derive(Clone, Default)] -struct Claims(Arc>>>); +struct Claims(Arc>>>); impl Claims { /// Claim `broadcast` for recording, or `None` when it is recorded already. - fn claim(&self, path: &moq_net::PathOwned, broadcast: &moq_net::broadcast::Consumer) -> Option { + fn claim(&self, path: &moq_net::Path, broadcast: &moq_net::broadcast::Consumer) -> Option { let mut claims = self.0.lock().unwrap(); let claimed = claims.entry(path.clone()).or_default(); if claimed.iter().any(|other| other.is_clone(broadcast)) { @@ -137,7 +137,7 @@ impl Claims { /// A broadcast being recorded, released on drop. struct Claim { claims: Claims, - path: moq_net::PathOwned, + path: moq_net::Path, broadcast: moq_net::broadcast::Consumer, } @@ -442,12 +442,12 @@ mod tests { #[test] fn a_re_announced_instance_is_recorded() { let mut running = Running::default(); - let epochless = (moq_net::Path::new("live/a").to_owned(), None); + let epochless = (moq_net::Path::new("live/a").clone(), None); assert!(running.start(&epochless)); assert!(running.start(&epochless)); assert!(!running.done(&epochless)); - let pinned = (moq_net::Path::new("live/b").to_owned(), Some(moq_net::Epoch::mint())); + let pinned = (moq_net::Path::new("live/b").clone(), Some(moq_net::Epoch::mint())); assert!(running.start(&pinned)); assert!(!running.start(&pinned)); assert!(running.done(&pinned)); @@ -460,7 +460,7 @@ mod tests { #[tokio::test] async fn a_resolved_broadcast_is_recorded_once() { let origin = moq_tokio::origin::spawn(); - let path = moq_net::Path::new("live/a").to_owned(); + let path = moq_net::Path::new("live/a").clone(); let newest = origin.create_broadcast(&path).unwrap(); let older = origin.create_broadcast(&path).unwrap(); @@ -479,14 +479,14 @@ mod tests { /// share a key prefix. #[test] fn an_epochless_instance_gets_a_fresh_key() { - let instance = (moq_net::Path::new("live/a").to_owned(), None); + let instance = (moq_net::Path::new("live/a").clone(), None); let first = key(&Path::from("rec"), &instance).unwrap(); let second = key(&Path::from("rec"), &instance).unwrap(); assert_ne!(first, second); assert!(first.as_ref().starts_with("rec/live/a/"), "{first}"); let epoch = moq_net::Epoch::mint(); - let pinned = (moq_net::Path::new("live/a").to_owned(), Some(epoch.clone())); + let pinned = (moq_net::Path::new("live/a").clone(), Some(epoch.clone())); assert_eq!( key(&Path::from("rec"), &pinned).unwrap().as_ref(), format!("rec/live/a/@{epoch}") @@ -500,13 +500,13 @@ mod tests { let epoch = moq_net::Epoch::mint(); let victim = key( &Path::from("rec"), - &(moq_net::Path::new("live/a").to_owned(), Some(epoch.clone())), + &(moq_net::Path::new("live/a").clone(), Some(epoch.clone())), ) .unwrap(); let hostile = format!("live/a/@{epoch}"); let nested = key( &Path::from("rec"), - &(moq_net::Path::new(&hostile).to_owned(), Some(moq_net::Epoch::mint())), + &(moq_net::Path::new(&hostile).clone(), Some(moq_net::Epoch::mint())), ) .unwrap(); assert!(nested.prefix_match(&victim).is_none(), "{nested} is under {victim}"); @@ -519,7 +519,7 @@ mod tests { let literal = key( &Path::from("rec"), &( - moq_net::Path::new(&format!("live/a/%40{epoch}")).to_owned(), + moq_net::Path::new(&format!("live/a/%40{epoch}")).clone(), Some(epoch), ), ) diff --git a/rs/moq-cli/src/args.rs b/rs/moq-cli/src/args.rs index b850ad2c92f..f7700e6377e 100644 --- a/rs/moq-cli/src/args.rs +++ b/rs/moq-cli/src/args.rs @@ -344,7 +344,7 @@ impl Invocation { }; // An origin refuses a broadcast a pattern could not name, so no viewer could // publish its `.echo` beneath this. - let feedback = prefix.join("viewer.echo"); + let feedback = prefix.join(&hang::moq_net::Path::from_static("viewer.echo")); if let Err(err) = hang::moq_net::Pattern::literal(feedback.as_str()) { anyhow::bail!( "--echo-path `{path}` resolves to `{prefix}`, where no viewer can publish its feedback: {err}" diff --git a/rs/moq-cli/src/play/media.rs b/rs/moq-cli/src/play/media.rs index b5eed18d985..1e250239445 100644 --- a/rs/moq-cli/src/play/media.rs +++ b/rs/moq-cli/src/play/media.rs @@ -1822,7 +1822,7 @@ mod tests { let source = moq_mux::Source::new(origin.consume(), broadcast.consume()); let mut config = AudioConfig::new(AudioCodec::Pcm, SAMPLE_RATE, 1); let absent = Track::of(&source, &config, "audio"); - config.broadcast = Some(moq_net::path::RelativeOwned::new("")); + config.broadcast = Some(moq_net::path::Relative::new("")); assert_eq!(Track::of(&source, &config, "audio"), absent); } diff --git a/rs/moq-cli/src/play/playback.rs b/rs/moq-cli/src/play/playback.rs index 6bed70c3bfb..4fea9011e66 100644 --- a/rs/moq-cli/src/play/playback.rs +++ b/rs/moq-cli/src/play/playback.rs @@ -17,7 +17,7 @@ pub(super) enum Kind { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(super) struct Track { /// Resolved rather than as written, since several references name one broadcast. - broadcast: Option, + broadcast: Option, name: String, } diff --git a/rs/moq-cli/src/report.rs b/rs/moq-cli/src/report.rs index f04ae7a5145..91723b199aa 100644 --- a/rs/moq-cli/src/report.rs +++ b/rs/moq-cli/src/report.rs @@ -263,7 +263,7 @@ pub struct EchoTarget { /// The feedback broadcast in force: where it is published and its snapshot tracks. struct Feedback { - path: moq_net::PathOwned, + path: moq_net::Path, // Held for the route: dropping it retracts the announcement. _broadcast: moq_net::origin::Publication, tracks: Pair>, @@ -334,7 +334,7 @@ impl Echo { if prefix.is_none() { tracing::warn!(path = %echo.path.as_str(), "the catalog solicits feedback above the connection root"); } - Some(prefix?.join(format!("{}{}", self.target.name.0, hang::echo::SUFFIX).as_str())) + Some(prefix?.join(&hang::moq_net::Path::from(format!("{}{}", self.target.name.0, hang::echo::SUFFIX)))) }); if self.feedback.as_ref().map(|feedback| &feedback.path) == target.as_ref() { return; @@ -353,7 +353,7 @@ impl Echo { } } - fn publish(&self, path: &moq_net::PathOwned) -> anyhow::Result { + fn publish(&self, path: &moq_net::Path) -> anyhow::Result { let broadcast = self.target.origin.create_broadcast(path)?; let tracks = Pair::create(&mut broadcast.broadcast().clone(), hang::echo::TRACK)?; // A fresh instance, so a reader sees a replaced feedback broadcast as a restart. @@ -488,7 +488,7 @@ async fn write_echo( ) -> anyhow::Result<()> { // Announcements leave hidden routes out unless asked, and a prefix naming one asks. let hidden = prefix.split('/').any(|segment| segment.starts_with('.')); - let prefix = moq_net::Path::new(&prefix).to_owned(); + let prefix = moq_net::Path::new(&prefix).clone(); let mut announced = origin.clone().with_hidden(hidden).announced(); let (lines, mut written) = tokio::sync::mpsc::channel::(LINES); let mut readers = Readers::default(); @@ -527,14 +527,14 @@ async fn write_echo( /// The `export echo` reader of each feedback broadcast, live or draining after its `End`. #[derive(Default)] struct Readers { - running: HashMap>, + running: HashMap>, /// One timer per `End`, yielding the path and the reader it ended once `DRAIN` passes. - drains: tokio::task::JoinSet<(moq_net::PathOwned, tokio::task::Id)>, + drains: tokio::task::JoinSet<(moq_net::Path, tokio::task::Id)>, } impl Readers { /// Read `path` with `reader`, replacing any reader still on it. - fn start(&mut self, path: moq_net::PathOwned, reader: tokio::task::JoinHandle<()>) { + fn start(&mut self, path: moq_net::Path, reader: tokio::task::JoinHandle<()>) { if let Some(previous) = self.running.insert(path, reader) { previous.abort(); } @@ -542,7 +542,7 @@ impl Readers { /// Let the reader of `path` drain, not abort it: the final snapshot can still be in flight. It /// stays so a start at the path replaces it, and goes after `DRAIN` even if its track stays open. - fn end(&mut self, path: moq_net::PathOwned) { + fn end(&mut self, path: moq_net::Path) { let Some(reader) = self.running.get(&path) else { return; }; @@ -575,7 +575,7 @@ impl Readers { /// Send a line for each feedback snapshot the broadcast at `path` publishes, until it ends. async fn read_echo( origin: moq_net::origin::Consumer, - path: moq_net::PathOwned, + path: moq_net::Path, lines: tokio::sync::mpsc::Sender, ) { let read = async { @@ -669,7 +669,7 @@ mod tests { #[tokio::test(start_paused = true)] async fn a_drained_reader_is_forgotten() { let mut readers = Readers::default(); - let path = moq_net::Path::new("viewer.echo").to_owned(); + let path = moq_net::Path::new("viewer.echo").clone(); readers.start(path.clone(), tokio::spawn(std::future::pending())); readers.end(path.clone()); diff --git a/rs/moq-cli/src/subscribe.rs b/rs/moq-cli/src/subscribe.rs index ffa3f2f7e24..8b13d40d86c 100644 --- a/rs/moq-cli/src/subscribe.rs +++ b/rs/moq-cli/src/subscribe.rs @@ -250,7 +250,7 @@ impl SubscribeArgs { /// Exports one broadcast from the Origin to stdout in the requested format. pub struct Subscribe { origin: moq_net::origin::Consumer, - path: moq_net::PathOwned, + path: moq_net::Path, source: moq_mux::Source, catalog: CatalogFormat, args: SubscribeArgs, @@ -281,7 +281,7 @@ impl Subscribe { catalog: CatalogFormat, args: SubscribeArgs, ) -> anyhow::Result { - let path = moq_net::Path::new(path).to_owned(); + let path = moq_net::Path::new(path).clone(); let broadcast = origin .request_broadcast(&path, None) .await diff --git a/rs/moq-cli/src/transcode.rs b/rs/moq-cli/src/transcode.rs index 8a22ec0a890..fd74deca854 100644 --- a/rs/moq-cli/src/transcode.rs +++ b/rs/moq-cli/src/transcode.rs @@ -106,7 +106,7 @@ pub async fn run(moq: MoqSide, args: Args, net: Net) -> anyhow::Result<()> { }; config.resize.output = args.frames.0; - let source_path = moq_net::PathOwned::from( + let source_path = moq_net::Path::from( moq.broadcast .clone() .context("`transcode` requires the source broadcast: pass --broadcast ")?, @@ -114,7 +114,7 @@ pub async fn run(moq: MoqSide, args: Args, net: Net) -> anyhow::Result<()> { if source_path.is_empty() { anyhow::bail!("`transcode` requires the source broadcast: pass --broadcast "); } - let output_path = moq_net::PathOwned::from( + let output_path = moq_net::Path::from( args.output .clone() .unwrap_or_else(|| format!("{source_path}/transcode.hang")), @@ -158,8 +158,8 @@ pub async fn run(moq: MoqSide, args: Args, net: Net) -> anyhow::Result<()> { struct Following { origin: moq_net::origin::Consumer, publish: moq_net::origin::Producer, - source_path: moq_net::PathOwned, - output_path: moq_net::PathOwned, + source_path: moq_net::Path, + output_path: moq_net::Path, config: moq_transcode::Config, } diff --git a/rs/moq-cli/tests/integration/epochs.rs b/rs/moq-cli/tests/integration/epochs.rs index cabf046269c..6a3f69c1c3b 100644 --- a/rs/moq-cli/tests/integration/epochs.rs +++ b/rs/moq-cli/tests/integration/epochs.rs @@ -95,7 +95,7 @@ fn publish(origin: &origin::Producer, epoch: &Epoch, tag: &str) -> Published { catalog .mutate(|catalog| { let mut audio = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2); - audio.broadcast = Some(moq_net::path::Relative::new("./media").into_owned()); + audio.broadcast = Some(moq_net::path::Relative::new("./media")); audio.epoch = Some(epoch.clone()); catalog.audio.renditions.insert("audio".into(), audio); }) diff --git a/rs/moq-e2ee/src/credential.rs b/rs/moq-e2ee/src/credential.rs index 26f6940cb4a..06c98c6f422 100644 --- a/rs/moq-e2ee/src/credential.rs +++ b/rs/moq-e2ee/src/credential.rs @@ -83,9 +83,9 @@ impl Credential { /// # Errors /// /// [`Error::Identity`](crate::Error::Identity) if `semantic` exceeds 65535 bytes. - pub fn path(&self, semantic: &str) -> Result { + pub fn path(&self, semantic: &str) -> Result { let material: [u8; PATH_LEN] = self.expand(&self.path_info(semantic)?); - Ok(moq_net::Path::new(&URL_SAFE_NO_PAD.encode(material)).into_owned()) + Ok(moq_net::Path::from(URL_SAFE_NO_PAD.encode(material))) } /// Mint a fresh epoch and a producing generation whose clones share track claims. diff --git a/rs/moq-ffi/src/media.rs b/rs/moq-ffi/src/media.rs index 29d17f806dc..a72adf808ef 100644 --- a/rs/moq-ffi/src/media.rs +++ b/rs/moq-ffi/src/media.rs @@ -625,14 +625,14 @@ mod test { let epoch: moq_net::Epoch = crate::origin::moq_mint_epoch().parse().unwrap(); let mut video = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8); - video.broadcast = Some(moq_net::path::Relative::new("./source").into_owned()); + video.broadcast = Some(moq_net::path::Relative::new("./source")); video.epoch = Some(epoch.clone()); catalog.video.renditions.insert("video".to_string(), video); let local = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8); catalog.video.renditions.insert("local".to_string(), local); let mut audio = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2); - audio.broadcast = Some(moq_net::path::Relative::new("../elsewhere").into_owned()); + audio.broadcast = Some(moq_net::path::Relative::new("../elsewhere")); catalog.audio.renditions.insert("audio".to_string(), audio); let converted = convert_catalog(&catalog); diff --git a/rs/moq-gst/src/source/imp.rs b/rs/moq-gst/src/source/imp.rs index b88ce22eb5e..bf49205b196 100644 --- a/rs/moq-gst/src/source/imp.rs +++ b/rs/moq-gst/src/source/imp.rs @@ -390,7 +390,7 @@ struct Shape { caps: gst::Caps, container: hang::catalog::Container, track: String, - broadcast: Option, + broadcast: Option, epoch: Option, } @@ -728,7 +728,7 @@ async fn follow_catalog( shutdown: shutdown.clone(), stop: stopped, }; - let mut followers: HashMap = HashMap::new(); + let mut followers: HashMap = HashMap::new(); // Woken when a follower runs out of renditions, so the run can let it go without waiting for // another catalog update. let emptied = Arc::new(tokio::sync::Notify::new()); @@ -799,7 +799,7 @@ fn reconcile( catalog: &moq_mux::catalog::hang::Catalog, source: &moq_mux::Source, playback: &Playback, - followers: &mut HashMap, + followers: &mut HashMap, tasks: &mut tokio::task::JoinSet<()>, emptied: &Arc, cancel: &watch::Receiver, @@ -871,7 +871,7 @@ fn reconcile( tasks.spawn_on(STREAMING.scope(playback.element.clone(), retire), RUNTIME.handle()); } - let mut plans: HashMap = HashMap::new(); + let mut plans: HashMap = HashMap::new(); for (rendition, shape) in desired { let Some(path) = source.resolve_reference(shape.broadcast.as_ref()) else { gst::warning!( @@ -888,7 +888,7 @@ fn reconcile( // one playing out its tail. Its follower stays reachable, with its finished records, while a // disabled rendition still names it or a delisted one drains, so a re-enable or relist reaches // it rather than a new follower. Once it plays nothing, the run lets it go. - let held: HashSet = disabled + let held: HashSet = disabled .iter() .filter_map(|(_, reference)| source.resolve_reference(reference.broadcast())) .collect(); @@ -998,7 +998,7 @@ struct Plan { /// rendition is reading, idle, or playing out its tail is its own state rather than a task's. struct Follower { playback: Playback, - path: moq_net::PathOwned, + path: moq_net::Path, renditions: HashMap, readers: tokio::task::JoinSet<(Rendition, u64, Exit)>, /// The instance last started. diff --git a/rs/moq-hls/src/export/mod.rs b/rs/moq-hls/src/export/mod.rs index 9667da1f6ad..ed53c68f00d 100644 --- a/rs/moq-hls/src/export/mod.rs +++ b/rs/moq-hls/src/export/mod.rs @@ -777,7 +777,7 @@ mod tests { settle().await; let upstream = crate::resolved(&origin, "a/pub").await; let mut config = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8); - config.broadcast = Some(moq_net::path::Relative::new("../../source").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("../../source").clone()); let mut catalog = moq_mux::catalog::hang::Catalog::default(); catalog.archive = Some(archive(&["video0", "audio0"])); catalog.video.renditions.insert("video".to_string(), config); @@ -3059,7 +3059,7 @@ mod tests { let source = crate::resolved(&origin, "live").await; let mut config = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8); if sibling { - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); } let (rendition, watcher) = export(&source, &config); rendition.playable().await; @@ -3323,7 +3323,7 @@ mod tests { // A relative reference replaces the base's last segment, so "media" is a sibling of the // catalog broadcast "live". let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let (rendition, watcher) = export(&upstream, &config); tokio::time::timeout(Duration::from_secs(5), rendition.playable()) @@ -3444,7 +3444,7 @@ mod tests { let upstream = crate::resolved(&origin, "live").await; let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let mut snapshot = moq_mux::catalog::hang::Catalog::default(); snapshot.video.renditions.insert("video0".to_string(), config.clone()); @@ -3520,7 +3520,7 @@ mod tests { let upstream = crate::resolved(&origin, "live").await; let mut config = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let mut snapshot = moq_mux::catalog::hang::Catalog::default(); snapshot.video.renditions.insert("video0".to_string(), config); @@ -3579,7 +3579,7 @@ mod tests { let upstream = crate::resolved(&origin, "live").await; let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let (rendition, watcher) = export(&upstream, &config); tokio::time::timeout(Duration::from_secs(5), rendition.playable()) @@ -3618,7 +3618,7 @@ mod tests { let upstream = crate::resolved(&origin, "live").await; let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let (rendition, watcher) = export(&upstream, &config); old_media.announce(sibling_route(10)).unwrap(); @@ -3678,7 +3678,7 @@ mod tests { let upstream = crate::resolved(&origin, "live").await; let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let (rendition, watcher) = export(&upstream, &config); media.announce(sibling_route(10)).unwrap(); tokio::time::timeout(Duration::from_secs(5), rendition.playable()) @@ -3728,7 +3728,7 @@ mod tests { let upstream = crate::resolved(&origin, "live").await; let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("media").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("media").clone()); let (rendition, watcher) = export(&upstream, &config); tokio::time::timeout(Duration::from_secs(5), rendition.playable()) diff --git a/rs/moq-hls/src/export/rendition.rs b/rs/moq-hls/src/export/rendition.rs index e6630cd7ec5..a81895457b8 100644 --- a/rs/moq-hls/src/export/rendition.rs +++ b/rs/moq-hls/src/export/rendition.rs @@ -1362,11 +1362,11 @@ mod tests { let dynamic = origin.dynamic("room/secret").unwrap(); let source = moq_mux::Source::new(origin.consume(), broadcast.consume()); let mut config = VideoConfig::new(hang::catalog::VideoCodec::VP8); - config.broadcast = Some(moq_net::path::Relative::new("./secret").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("./secret").clone()); let rendition = Arc::new(Rendition::video("video".into(), &config, &source, Archive::default(), None).unwrap()); let request = dynamic.requested_broadcast().await.unwrap(); let other = origin.dynamic("room/hidden").unwrap(); - config.broadcast = Some(moq_net::path::Relative::new("./hidden").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("./hidden").clone()); let resolving = Arc::new(Rendition::video("video".into(), &config, &source, Archive::default(), None).unwrap()); let other_request = other.requested_broadcast().await.unwrap(); let row = |reference: &str| segments::Row { diff --git a/rs/moq-hls/src/server/routes.rs b/rs/moq-hls/src/server/routes.rs index d5109355688..50b1fac106d 100644 --- a/rs/moq-hls/src/server/routes.rs +++ b/rs/moq-hls/src/server/routes.rs @@ -528,7 +528,7 @@ mod tests { let catalog = moq_mux::catalog::Producer::new(&mut broadcast.broadcast().clone(), Default::default()).unwrap(); let reserved = catalog.reserve(); let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("./source").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("./source").clone()); let mut allowed = reserved .video( source.create_track("video0", timed()).unwrap(), @@ -537,7 +537,7 @@ mod tests { ) .unwrap(); let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("./secret").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("./secret").clone()); let mut denied = reserved .video( secret.create_track("video1", timed()).unwrap(), diff --git a/rs/moq-hls/tests/integration/server.rs b/rs/moq-hls/tests/integration/server.rs index 1cc94e6c780..1ccfdbdf47a 100644 --- a/rs/moq-hls/tests/integration/server.rs +++ b/rs/moq-hls/tests/integration/server.rs @@ -204,7 +204,7 @@ async fn a_disconnected_sibling_publisher_answers_404() { moq_mux::catalog::Producer::new(&mut catalog_broadcast.broadcast().clone(), Default::default()).unwrap(); let reserved = catalog.reserve(); let mut config = video_config(); - config.broadcast = Some(moq_net::path::Relative::new("./source").to_owned()); + config.broadcast = Some(moq_net::path::Relative::new("./source").clone()); let track = media_broadcast.create_track(TRACK, timed()).unwrap(); let mut media = reserved .video( diff --git a/rs/moq-mux/src/binary.rs b/rs/moq-mux/src/binary.rs index 54aa92d1109..0878cabc70f 100644 --- a/rs/moq-mux/src/binary.rs +++ b/rs/moq-mux/src/binary.rs @@ -899,7 +899,7 @@ mod test { fn a_broadcast_reference_is_refused() { let (mut broadcast, catalog) = catalog(); let mut entry = mavlink(1); - entry.config.broadcast = Some(moq_net::path::RelativeOwned::new("source")); + entry.config.broadcast = Some(moq_net::path::Relative::new("source")); assert!(matches!( catalog.binary_stream(track(&mut broadcast, "telemetry"), entry), diff --git a/rs/moq-mux/src/catalog/hang/consumer.rs b/rs/moq-mux/src/catalog/hang/consumer.rs index 513abcd4b7b..8a14667f4e8 100644 --- a/rs/moq-mux/src/catalog/hang/consumer.rs +++ b/rs/moq-mux/src/catalog/hang/consumer.rs @@ -119,7 +119,7 @@ mod test { fn referencing(rel: &str) -> hang::catalog::AudioConfig { let mut config = opus(); - config.broadcast = Some(moq_net::path::Relative::new(rel).into_owned()); + config.broadcast = Some(moq_net::path::Relative::new(rel)); config } @@ -216,7 +216,7 @@ mod test { fn soliciting(reference: &str) -> Catalog { let (mut catalog, _) = catalog_payload("here"); catalog.echo = Some(hang::catalog::Echo::new( - moq_net::path::Relative::new(reference).into_owned(), + moq_net::path::Relative::new(reference), )); catalog } @@ -342,7 +342,7 @@ mod test { published.audio.renditions.insert("here".to_string(), opus()); let mut text = hang::catalog::TextConfig::new(hang::catalog::TextFormat::Vtt); - text.broadcast = Some(moq_net::path::Relative::new("../../../elsewhere").into_owned()); + text.broadcast = Some(moq_net::path::Relative::new("../../../elsewhere")); published.text.renditions.insert("captions".to_string(), text); match publish_catalog(published) { @@ -361,7 +361,7 @@ mod test { let mut published = Catalog::<()>::default(); published.audio.renditions.insert("here".to_string(), opus()); - let escaping = moq_net::path::Relative::new("../../../elsewhere").into_owned(); + let escaping = moq_net::path::Relative::new("../../../elsewhere"); match section { "json" => { let mut config = hang::catalog::JsonConfig::new(hang::catalog::Mode::Stream); diff --git a/rs/moq-mux/src/container/source.rs b/rs/moq-mux/src/container/source.rs index 2fb0653bad3..4b872ea370d 100644 --- a/rs/moq-mux/src/container/source.rs +++ b/rs/moq-mux/src/container/source.rs @@ -73,7 +73,7 @@ enum SourceState { /// be, and the track's name there. #[derive(Clone, Debug, PartialEq, Eq)] struct Target { - path: moq_net::PathOwned, + path: moq_net::Path, epoch: Option, track: String, } @@ -790,14 +790,14 @@ mod tests { inline: true, }); config.container = Container::Legacy; - config.broadcast = broadcast.map(|b| Relative::new(b).into_owned()); + config.broadcast = broadcast.map(|b| Relative::new(b)); config } fn audio(broadcast: Option<&str>) -> AudioConfig { let mut config = AudioConfig::new(AudioCodec::Opus, 48_000, 2); config.container = Container::Legacy; - config.broadcast = broadcast.map(|b| Relative::new(b).into_owned()); + config.broadcast = broadcast.map(|b| Relative::new(b)); config } diff --git a/rs/moq-mux/src/container/test_util.rs b/rs/moq-mux/src/container/test_util.rs index e68924d0524..5e9760c4292 100644 --- a/rs/moq-mux/src/container/test_util.rs +++ b/rs/moq-mux/src/container/test_util.rs @@ -172,7 +172,7 @@ impl Moving { config.description = Some(crate::codec::h264::build_avcc(&[Bytes::from_static(sps)], &[Bytes::from_static(PPS)]).unwrap()); config.track = Some(track.to_string()); - config.broadcast = broadcast.map(|broadcast| moq_net::path::Relative::new(broadcast).into_owned()); + config.broadcast = broadcast.map(|broadcast| moq_net::path::Relative::new(broadcast)); config } } diff --git a/rs/moq-mux/src/container/ts/follower.rs b/rs/moq-mux/src/container/ts/follower.rs index f38b1d829fa..d3f70a8d6b2 100644 --- a/rs/moq-mux/src/container/ts/follower.rs +++ b/rs/moq-mux/src/container/ts/follower.rs @@ -87,7 +87,7 @@ pub struct Follower { export: Export>, state: State, instances: Instances, - path: moq_net::PathOwned, + path: moq_net::Path, serving: Serving, /// Taking the instances on hand when built, which replay the route the export resolved. priming: bool, diff --git a/rs/moq-mux/src/container/ts/programs.rs b/rs/moq-mux/src/container/ts/programs.rs index 462550276c2..37184e38eb1 100644 --- a/rs/moq-mux/src/container/ts/programs.rs +++ b/rs/moq-mux/src/container/ts/programs.rs @@ -24,7 +24,7 @@ use crate::catalog; /// and each program reads only the packets on the PIDs its PAT entry and PMT claim. pub struct Programs { origin: moq_net::origin::Producer, - name: moq_net::PathOwned, + name: moq_net::Path, config: catalog::Config, demux: Demux, /// The latest whole PAT, from the first that lists a program, which seeds every program. @@ -92,10 +92,10 @@ impl Program { impl Programs { /// Split the multiplex into broadcasts named after `name`, each catalog built from `config`. - pub fn new(origin: moq_net::origin::Producer, name: impl moq_net::AsPath, config: catalog::Config) -> Self { + pub fn new(origin: moq_net::origin::Producer, name: impl Into, config: catalog::Config) -> Self { Self { origin, - name: name.as_path().to_owned(), + name: name.into(), config, demux: Demux::new(true), pat: None, diff --git a/rs/moq-mux/src/follow.rs b/rs/moq-mux/src/follow.rs index 9823777dd5d..0aa081f8fe2 100644 --- a/rs/moq-mux/src/follow.rs +++ b/rs/moq-mux/src/follow.rs @@ -41,7 +41,7 @@ impl Instances { /// [`Source`](crate::Source) for each instance. pub fn new( origin: &moq_net::origin::Consumer, - path: impl moq_net::AsPath, + path: impl Into, epoch: Option<&moq_net::Epoch>, ) -> crate::Result { let announced = origin.announced_broadcast(path, None)?; diff --git a/rs/moq-mux/src/json.rs b/rs/moq-mux/src/json.rs index 1eafafe8587..501e2e5856c 100644 --- a/rs/moq-mux/src/json.rs +++ b/rs/moq-mux/src/json.rs @@ -705,7 +705,7 @@ mod test { let mut broadcast = moq_net::broadcast::Info::new().produce(); let mut existing = JsonConfig::new(Mode::Snapshot); - existing.broadcast = Some(moq_net::path::RelativeOwned::new("source")); + existing.broadcast = Some(moq_net::path::Relative::new("source")); let mut seed = crate::catalog::hang::Catalog::<()>::default(); seed.json.tracks.insert("chat".to_string(), existing.clone()); diff --git a/rs/moq-mux/src/source.rs b/rs/moq-mux/src/source.rs index f68afe5663a..bdf04b1f2d5 100644 --- a/rs/moq-mux/src/source.rs +++ b/rs/moq-mux/src/source.rs @@ -14,7 +14,7 @@ use std::sync::{Arc, Mutex}; use std::task::{Poll, ready}; /// The broadcast held for each path, shared by a source's clones and its requests. -type Pins = Arc>>; +type Pins = Arc>>; /// The subscription side of an export: an origin and the catalog broadcast that drives it. /// @@ -42,7 +42,7 @@ type Pins = Arc> #[derive(Clone)] pub struct Source { origin: moq_net::origin::Consumer, - path: moq_net::PathOwned, + path: moq_net::Path, pins: Pins, /// What each rendition an export reads through this source has received. See /// [`stats`](Self::stats). @@ -110,7 +110,7 @@ impl Source { &self.origin } - pub(crate) fn path(&self) -> &moq_net::PathOwned { + pub(crate) fn path(&self) -> &moq_net::Path { &self.path } @@ -139,7 +139,7 @@ impl Source { /// The broadcast `path` is pinned to, or a lookup that waits for the path's first /// announcement and pins its answer, either checked against `epoch` on every read. - pub(crate) fn request_path(&self, path: &moq_net::PathOwned, epoch: Option<&moq_net::Epoch>) -> Binding { + pub(crate) fn request_path(&self, path: &moq_net::Path, epoch: Option<&moq_net::Epoch>) -> Binding { let bound = match self.pins.lock().expect("pins poisoned").get(path) { Some(pinned) => Bound::Ready(pinned.clone()), // Unpinned even with an epoch: an epochless route still answers, best effort, and @@ -167,7 +167,7 @@ impl Source { /// A missing or empty reference returns the catalog broadcast path. A valid reference /// may return the empty root path, which still names a broadcast. `None` means the /// reference walked above the root and names nothing. - pub fn resolve_reference(&self, rel: Option<&moq_net::path::Relative<'_>>) -> Option { + pub fn resolve_reference(&self, rel: Option<&moq_net::path::Relative>) -> Option { match rel.filter(|rel| !rel.is_empty()) { Some(rel) => self.path.try_resolve(rel), None => Some(self.path.clone()), @@ -178,7 +178,7 @@ impl Source { /// /// The erroring counterpart to [`Self::resolve_reference`], for the consumer side, where /// an escaping reference is a fault to report rather than a rendition to drop. - fn target(&self, rel: Option<&moq_net::path::Relative<'_>>) -> crate::Result { + fn target(&self, rel: Option<&moq_net::path::Relative>) -> crate::Result { self.resolve_reference(rel).ok_or_else(|| { let rel = rel.map_or("", |rel| rel.as_str()); tracing::error!(rel, catalog = %self.path, "broadcast reference escapes the root"); @@ -239,7 +239,7 @@ impl Source { /// no broadcast, so there is nothing to resolve. pub async fn resolve( &self, - rel: Option<&moq_net::path::Relative<'_>>, + rel: Option<&moq_net::path::Relative>, epoch: Option<&moq_net::Epoch>, ) -> crate::Result { self.bind(rel, epoch)?.broadcast().await @@ -262,7 +262,7 @@ impl Source { /// `epoch` on every read. pub fn bind( &self, - rel: Option<&moq_net::path::Relative<'_>>, + rel: Option<&moq_net::path::Relative>, epoch: Option<&moq_net::Epoch>, ) -> crate::Result { Ok(self.request_path(&self.target(rel)?, epoch)) @@ -283,7 +283,7 @@ impl Source { /// use it to honor cross-broadcast renditions without reimplementing the path math. pub async fn subscribe_track( &self, - rel: Option<&moq_net::path::Relative<'_>>, + rel: Option<&moq_net::path::Relative>, epoch: Option<&moq_net::Epoch>, name: &str, ) -> crate::Result { @@ -306,7 +306,7 @@ impl Source { /// Rejects an escaping reference exactly as [`resolve`](Self::resolve) does. pub fn follow( &self, - rel: Option<&moq_net::path::Relative<'_>>, + rel: Option<&moq_net::path::Relative>, epoch: Option<&moq_net::Epoch>, ) -> crate::Result { let path = self.target(rel)?; @@ -365,7 +365,7 @@ enum Bound { routing: Box>, /// Where the answer is pinned, the first for its path winning. pins: Pins, - path: moq_net::PathOwned, + path: moq_net::Path, }, } @@ -890,9 +890,9 @@ mod tests { level: 0x1e, inline: false, }); - escaped.broadcast = Some(Relative::new("../../source").to_owned()); + escaped.broadcast = Some(Relative::new("../../source").clone()); let mut sibling = escaped.clone(); - sibling.broadcast = Some(Relative::new("./source").to_owned()); + sibling.broadcast = Some(Relative::new("./source").clone()); let mut catalog = hang::Catalog::<()>::default(); catalog.video.renditions.insert("escaped".to_string(), escaped); @@ -938,9 +938,9 @@ mod tests { let source = created(&origin); let mut escaped = hang::catalog::TextConfig::new(hang::catalog::TextFormat::Vtt); - escaped.broadcast = Some(Relative::new("../../source").to_owned()); + escaped.broadcast = Some(Relative::new("../../source").clone()); let mut sibling = escaped.clone(); - sibling.broadcast = Some(Relative::new("./source").to_owned()); + sibling.broadcast = Some(Relative::new("./source").clone()); let mut catalog = hang::Catalog::<()>::default(); catalog.text.renditions.insert("escaped".to_string(), escaped); diff --git a/rs/moq-net/Cargo.toml b/rs/moq-net/Cargo.toml index f842b0e3a24..c15aa019436 100644 --- a/rs/moq-net/Cargo.toml +++ b/rs/moq-net/Cargo.toml @@ -20,7 +20,7 @@ default = ["async"] # Runtime-backed helpers and Lite/IETF sessions; explicit-time model drivers stay available. async = ["dep:futures", "moq-clock/runtime"] -# Exposes the wire codecs to the `fuzz/` harness and the announce, codec, varint, and frame benches, +# Exposes the wire codecs to the `fuzz/` harness and the announce, codec, path, varint, and frame benches, # and session views of an origin to the viewers bench, through the hidden `fuzz` module. Off by # default and hidden from the docs. fuzz = ["async"] @@ -208,5 +208,10 @@ name = "memory" harness = false required-features = ["async"] +[[bench]] +name = "path" +harness = false +required-features = ["fuzz"] + [lints] workspace = true diff --git a/rs/moq-net/MIGRATE.md b/rs/moq-net/MIGRATE.md index b0d41ad16ac..0eaf7e0a5e8 100644 --- a/rs/moq-net/MIGRATE.md +++ b/rs/moq-net/MIGRATE.md @@ -4,6 +4,16 @@ How to update your code for each breaking change, newest first. ## Unreleased +### `Path` and `Relative` have no lifetime + +There is one owned `Path` type: a short path is stored inline and a long one shares a reference-counted buffer, so cloning stays cheap. + +- `PathOwned`, `Path<'a>`, and `Path<'static>` are all `Path`; `RelativeOwned` and `Relative<'a>` are `Relative`. +- `Path::{to_owned, into_owned, borrow}` and the `Relative` equivalents are gone: drop the call, or `clone()` a `&Path` you need to keep. +- `AsPath` is gone. Functions that took `impl AsPath` take `impl Into`, which still accepts `&str`, `String`, `&String`, `Path`, and `&Path`. +- `Path::{has_prefix, strip_prefix, join, relative}` take `&Path`: write `path.has_prefix(&Path::new("room"))`. +- `Path::new` copies its argument. Use `Path::from_static("room")` for a literal, which borrows it. + ### Exports without a consumer are gone These had no caller outside tests; replace them as follows. diff --git a/rs/moq-net/benches/announce.rs b/rs/moq-net/benches/announce.rs index 42b4c870891..84b8d283f19 100644 --- a/rs/moq-net/benches/announce.rs +++ b/rs/moq-net/benches/announce.rs @@ -18,7 +18,7 @@ use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use moq_net::{ - Hop, Hops, PathOwned, announce, + Hop, Hops, Path, announce, fuzz::{AnnounceWriter, Announced, decode_announces, encode_announces}, }; @@ -106,7 +106,7 @@ fn hops(ids: &[u64]) -> Hops { } /// Announce `routes` routes, then replace `churn` of them, oldest first. -fn stream(routes: u64, churn: u64, route: impl Fn(u64) -> (PathOwned, Hops)) -> Vec { +fn stream(routes: u64, churn: u64, route: impl Fn(u64) -> (Path, Hops)) -> Vec { let mut announced: Vec<_> = (0..routes) .map(|n| { let (path, hops) = route(n); @@ -121,7 +121,7 @@ fn stream(routes: u64, churn: u64, route: impl Fn(u64) -> (PathOwned, Hops)) -> announced } -fn health(n: u64) -> (PathOwned, Hops) { +fn health(n: u64) -> (Path, Hops) { let origin = n % ORIGINS; let path = format!( "{:x}/private/channel_{}/stream-health-{}", @@ -132,14 +132,14 @@ fn health(n: u64) -> (PathOwned, Hops) { (path.into(), hops(&[id(origin), id(100), id(101)])) } -fn unique(n: u64) -> (PathOwned, Hops) { +fn unique(n: u64) -> (Path, Hops) { ( format!("{:x}", id(n)).into(), hops(&[id(n + 1_000_000), id(n + 2_000_000)]), ) } -type Workload = (&'static str, fn(u64) -> (PathOwned, Hops)); +type Workload = (&'static str, fn(u64) -> (Path, Hops)); const WORKLOADS: [Workload; 2] = [("health", health), ("unique", unique)]; fn report() { diff --git a/rs/moq-net/benches/path.rs b/rs/moq-net/benches/path.rs new file mode 100644 index 00000000000..f5f22f4f6db --- /dev/null +++ b/rs/moq-net/benches/path.rs @@ -0,0 +1,197 @@ +//! Allocations per path-heavy operation: building and slicing paths, decoding the +//! messages that carry them, resolving requests on an origin, and fanning +//! announcements out to its cursors. +//! +//! Counts are deterministic, so this prints them instead of timing: a regression +//! shows as a higher number, not as noise. Paths span the inline limit: short (10 +//! bytes), medium (22, the most that fits inline), and long (47). +//! +//! Run with `cargo bench -p moq-net --features fuzz --bench path`. + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::hint::black_box; +use std::sync::atomic::{AtomicU64, Ordering}; + +use futures::FutureExt; +use moq_net::{ + Hop, Hops, Path, Pattern, Patterns, announce, + fuzz::{Announced, Messages, decode_announces, decode_namespaces, encode_announces, encode_namespaces}, + kio, origin, +}; + +struct Counting; + +static ALLOCS: AtomicU64 = AtomicU64::new(0); + +// SAFETY: forwards every call to the system allocator unchanged, only counting. +unsafe impl GlobalAlloc for Counting { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + ALLOCS.fetch_add(1, Ordering::Relaxed); + // SAFETY: the caller upholds `alloc`'s contract. + unsafe { System.alloc(layout) } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: the caller upholds `dealloc`'s contract. + unsafe { System.dealloc(ptr, layout) } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new: usize) -> *mut u8 { + ALLOCS.fetch_add(1, Ordering::Relaxed); + // SAFETY: the caller upholds `realloc`'s contract. + unsafe { System.realloc(ptr, layout, new) } + } +} + +#[global_allocator] +static GLOBAL: Counting = Counting; + +/// Print the average allocations per item over `n` calls of `f`, each handling `per` items. +fn allocs(name: &str, n: u64, per: u64, mut f: impl FnMut() -> R) { + // Warm lazily built state first. + for _ in 0..16 { + black_box(f()); + } + let before = ALLOCS.load(Ordering::Relaxed); + for _ in 0..n { + black_box(f()); + } + let total = ALLOCS.load(Ordering::Relaxed) - before; + println!("{name:<40} {:>8.2}", total as f64 / (n * per) as f64); +} + +const SHORT: &str = "room/alice"; +const MEDIUM: &str = "anon/7f3a9c2e1b/camera"; +const LONG: &str = "1234/private/channel_7/stream-health-1700000000"; + +fn hops(ids: &[u64]) -> Hops { + Hops::try_from(ids.iter().map(|id| Hop::new(*id).unwrap()).collect::>()).unwrap() +} + +fn paths() { + for (name, s) in [("short", SHORT), ("medium", MEDIUM), ("long", LONG)] { + let path = Path::new(s); + let prefix = Path::new("room"); + let cam = Path::new("cam"); + allocs(&format!("path/new/{name}"), 10_000, 1, || Path::new(black_box(s))); + allocs(&format!("path/clone/{name}"), 10_000, 1, || path.clone()); + allocs(&format!("path/strip_prefix/{name}"), 10_000, 1, || { + path.strip_prefix(black_box(&prefix)) + }); + allocs(&format!("path/join/{name}"), 10_000, 1, || path.join(black_box(&cam))); + } +} + +fn decode() { + let messages = Messages::default(); + let mut lite = Vec::new(); + messages.encode_lite(&mut lite); + let mut ietf = Vec::new(); + messages.encode_ietf(&mut ietf); + allocs("decode/lite_mix", 10_000, 1, || messages.decode_lite(&lite)); + allocs("decode/ietf_mix", 10_000, 1, || messages.decode_ietf(&ietf)); + + let shapes: [(&str, fn(u64) -> String); 3] = [ + ("short", |i| format!("room/{i}")), + ("medium", |i| format!("anon/{:010x}/camera", i * 0x9E37_79B9)), + ("long", |i| { + format!( + "{}/private/channel_{}/stream-health-{}", + 1000 + i % 8, + i, + 1_700_000_000 + i + ) + }), + ]; + for (name, shape) in shapes { + let paths: Vec = (0..1000).map(|i| Path::from(shape(i))).collect(); + let stream: Vec = paths + .iter() + .enumerate() + .map(|(i, path)| Announced::Start(path.clone(), hops(&[0x1111 + i as u64, 0x2222]))) + .collect(); + for compress in [false, true] { + let data = encode_announces(&stream, compress); + let version = if compress { "lite07" } else { "lite06" }; + allocs(&format!("decode/announce_{version}/{name}"), 50, 1000, || { + decode_announces(&data, compress) + }); + } + let data = encode_namespaces(&paths); + allocs(&format!("decode/announce_ietf/{name}"), 50, 1000, || decode_namespaces(&data)); + } +} + +fn requests() { + for (name, s) in [("short", SHORT), ("long", LONG)] { + let (producer, mut driver) = origin::Producer::new(origin::Config::default()); + let consumer = producer.consume(); + let _pubs: Vec<_> = (0..1000) + .map(|i| producer.publish(format!("{s}/{i}"), origin::Route::default()).unwrap()) + .collect(); + let _covering = producer.publish(s, origin::Route::default()).unwrap(); + let waiter = kio::Waiter::noop(); + + let mut local = |request: &dyn Fn() -> kio::Pending| { + let pending = request(); + driver.poll(moq_net::time::Instant::now(), &waiter).unwrap(); + pending.now_or_never().expect("resolves").expect("local broadcast") + }; + + let target = format!("{s}/0"); + let target_path = Path::new(&target); + allocs(&format!("origin/request_str/{name}"), 2_000, 1, || { + local(&|| consumer.request_broadcast(target.as_str(), None)) + }); + allocs(&format!("origin/request_path/{name}"), 2_000, 1, || { + local(&|| consumer.request_broadcast(&target_path, None)) + }); + + // A rooted handle joins its root onto every path it is given. + let (root, rest) = s.split_once('/').unwrap(); + let rooted = consumer.scope(root, &Patterns::from(Pattern::all())).unwrap(); + let rooted_target = format!("{rest}/0"); + allocs(&format!("origin/request_rooted_str/{name}"), 2_000, 1, || { + local(&|| rooted.request_broadcast(rooted_target.as_str(), None)) + }); + + let missing = format!("{s}/missing"); + allocs(&format!("origin/unroutable_str/{name}"), 2_000, 1, || { + let result = consumer.request_broadcast(missing.as_str(), None).now_or_never(); + assert!(matches!(result, Some(Err(moq_net::Error::Unroutable)))); + }); + } +} + +fn fanout() { + for (name, s) in [("short", "room/incoming"), ("long", LONG)] { + let (producer, _driver) = origin::Producer::new(origin::Config::default()); + let consumer = producer.consume(); + let _pubs: Vec<_> = (0..100) + .map(|i| producer.publish(format!("room/{i}"), origin::Route::default()).unwrap()) + .collect(); + let mut cursors: Vec = (0..100).map(|_| consumer.announced()).collect(); + for cursor in &mut cursors { + while cursor.next().now_or_never().flatten().is_some() {} + } + + // Per cursor, per publish and retract. + allocs(&format!("origin/announce_fanout/{name}"), 500, 100, || { + let publication = producer.publish(s, origin::Route::default()).unwrap(); + for cursor in &mut cursors { + cursor.next().now_or_never().flatten().expect("announce delivered"); + } + drop(publication); + for cursor in &mut cursors { + cursor.next().now_or_never().flatten().expect("retract delivered"); + } + }); + } +} + +fn main() { + paths(); + decode(); + requests(); + fanout(); +} diff --git a/rs/moq-net/src/auth.rs b/rs/moq-net/src/auth.rs index b6ad83f0fe8..6acb5387cad 100644 --- a/rs/moq-net/src/auth.rs +++ b/rs/moq-net/src/auth.rs @@ -1900,7 +1900,7 @@ struct Held { grant: Grant, deadline: crate::time::Deadline, /// The request this grant must keep covering: a replacement that no longer does ends it. - path: crate::PathOwned, + path: crate::Path, kind: RequestKind, /// The request's slot, shared with a renewal's verification. hold: Option, @@ -1917,7 +1917,7 @@ impl RequestGrant { clock: &crate::time::Deadlines, verdict: RequestVerdict, res: std::result::Result, - path: crate::PathOwned, + path: crate::Path, kind: RequestKind, hold: Option, ) -> std::result::Result { @@ -2143,7 +2143,7 @@ pub(crate) enum Direction { /// only re-matched when the union or the limit changes. pub(crate) struct Gate { handle: Link, - path: crate::PathOwned, + path: crate::Path, direction: Direction, epoch: u64, part: Part, @@ -2155,7 +2155,7 @@ pub(crate) struct Gate { /// Log the whole wait once, including when its request is cancelled. struct Waiting { - path: crate::PathOwned, + path: crate::Path, started: Instant, } @@ -2176,7 +2176,7 @@ enum Part { } impl Gate { - pub(crate) fn new(handle: Link, path: crate::PathOwned, direction: Direction) -> Self { + pub(crate) fn new(handle: Link, path: crate::Path, direction: Direction) -> Self { let within_limit = handle.within_limit(direction, path.as_str()); Self { handle, @@ -2200,7 +2200,7 @@ impl Gate { /// the announce stands in for the scope, as its own token may have authorized it. pub(crate) fn announced( handle: Link, - path: crate::PathOwned, + path: crate::Path, direction: Direction, live: kio::Consumer<()>, ) -> Self { @@ -2362,7 +2362,7 @@ pub(crate) struct Enforce { epoch: u64, permit: Option, /// Every broadcast admitted so far, still announced. - live: std::collections::HashSet, + live: std::collections::HashSet, setup: bool, /// Broadcasts outside the union, still announced, waiting on tokens that might cover /// them, in announcement order. @@ -2377,7 +2377,7 @@ impl Enforce { handle: &Link, announced: &mut crate::announce::Consumer, waiter: &kio::Waiter, - ) -> Poll> { + ) -> Poll> { if !self.setup { ready_or!(handle.poll_setup_answered(waiter)); self.setup = true; @@ -2448,7 +2448,7 @@ mod tests { let token = handle.present(Bytes::from_static(b"new"), false).unwrap(); let mut gate = Gate::new( handle.clone(), - crate::Path::new("room").to_owned(), + crate::Path::new("room").clone(), Direction::Subscribe, ); let waiter = kio::Waiter::noop(); @@ -2472,7 +2472,7 @@ mod tests { let busy = Error::Session(SessionError::TooManyRequests); assert_eq!(handle.retry(id, &busy), Some(RETRY_INITIAL)); - let mut gate = Gate::new(handle.clone(), crate::Path::new("room").to_owned(), Direction::Publish); + let mut gate = Gate::new(handle.clone(), crate::Path::new("room").clone(), Direction::Publish); let waiter = kio::Waiter::noop(); assert!( gate.poll_ready(&waiter).is_pending(), @@ -2495,7 +2495,7 @@ mod tests { let route = kio::Producer::<()>::default(); let mut gate = Gate::announced( handle, - crate::Path::new("room").to_owned(), + crate::Path::new("room").clone(), Direction::Subscribe, route.consume(), ); @@ -2510,7 +2510,7 @@ mod tests { fn an_empty_token_never_holds_a_request() { let handle = Link::new(true); let _setup = handle.present(Bytes::new(), true).unwrap(); - let mut gate = Gate::new(handle, crate::Path::new("room").to_owned(), Direction::Subscribe); + let mut gate = Gate::new(handle, crate::Path::new("room").clone(), Direction::Subscribe); assert!(gate.poll_ready(&kio::Waiter::noop()).is_ready()); } @@ -2520,7 +2520,7 @@ mod tests { let first = handle.present(Bytes::from_static(b"first"), false).unwrap(); handle.granted(first.presented.id, Grant::all()); let _unanswered = handle.present(Bytes::from_static(b"renewal"), false).unwrap(); - let mut gate = Gate::new(handle, crate::Path::new("room").to_owned(), Direction::Subscribe); + let mut gate = Gate::new(handle, crate::Path::new("room").clone(), Direction::Subscribe); assert!(gate.poll_ready(&kio::Waiter::noop()).is_ready()); } @@ -2942,7 +2942,7 @@ mod tests { assert_eq!(link.retry(id, &lost), Some(RETRY_INITIAL)); assert_eq!(auth.grant().peek(), None, "the lost stream's grant is gone"); assert_eq!(link.token(&auth).as_deref(), Some(&b"jwt"[..])); - let mut gate = Gate::new(link.clone(), crate::Path::new("room").to_owned(), Direction::Publish); + let mut gate = Gate::new(link.clone(), crate::Path::new("room").clone(), Direction::Publish); assert!(gate.poll_ready(&kio::Waiter::noop()).is_pending()); assert_eq!(link.reopen(id).unwrap().as_ref(), b"jwt"); @@ -3022,8 +3022,8 @@ mod request_token_tests { } } - fn subscribe_path() -> crate::PathOwned { - crate::Path::new("room/alice").to_owned() + fn subscribe_path() -> crate::Path { + crate::Path::new("room/alice").clone() } /// A request token reaches the app's acceptor like any other token, and the grant the diff --git a/rs/moq-net/src/discovery.rs b/rs/moq-net/src/discovery.rs index 299603420c8..f42393def8b 100644 --- a/rs/moq-net/src/discovery.rs +++ b/rs/moq-net/src/discovery.rs @@ -15,7 +15,7 @@ use std::{ }; use crate::{ - Path, PathOwned, + Path, model::{InterestChange, Interests, Snapshot}, origin, time::{Deadline, Deadlines, Instant}, @@ -29,9 +29,9 @@ pub(crate) const LINGER: Duration = Duration::from_secs(2); pub(crate) enum Change { /// Ask the peer about the prefix, holding the snapshot until the request's /// initial set is in. - Open(PathOwned, Snapshot), + Open(Path, Snapshot), /// Stop asking about the prefix: end its request, retracting what it reported. - Close(PathOwned), + Close(Path), } /// The announce requests a session keeps open, as a covering set of what is wanted. @@ -52,14 +52,14 @@ pub(crate) struct Discovery { /// Decided but not yet handed to the session, in order. changes: VecDeque, /// How many wants name each prefix: overlapping scopes can name one twice. - wanted: HashMap, + wanted: HashMap, /// How many wanted prefixes sit at or beneath each prefix, so a request learns in a /// walk up its path whether anything beneath it is still wanted. - beneath: HashMap, + beneath: HashMap, /// The requests the session holds open, by prefix. - requests: HashMap, + requests: HashMap, /// The requests nothing beneath wants any more, by when they close. - lingering: BTreeSet<(Instant, PathOwned)>, + lingering: BTreeSet<(Instant, Path)>, /// Fires for the first of `lingering`. timer: Deadline, } @@ -69,7 +69,7 @@ struct Request { /// When it closes, while nothing at or beneath its prefix is wanted. linger: Option, /// Narrower requests nothing wants any more, kept open until this one goes. - held: HashSet, + held: HashSet, } impl Discovery { @@ -146,7 +146,7 @@ impl Discovery { } /// The request at `prefix` ended on its own: the peer finished it. - pub fn ended(&mut self, prefix: &PathOwned) { + pub fn ended(&mut self, prefix: &Path) { if let Some(request) = self.requests.remove(prefix) { self.forget(prefix, request); } @@ -209,7 +209,7 @@ impl Discovery { /// A new request at `prefix` replaces every request open beneath it, wanted or /// lingering, which closes first. - fn replace_beneath(&mut self, prefix: &PathOwned) -> Request { + fn replace_beneath(&mut self, prefix: &Path) -> Request { let beneath: Vec<_> = self .requests .keys() @@ -228,7 +228,7 @@ impl Discovery { /// A new request at `prefix` holds the narrower ones still lingering beneath it, as it /// would had they been released after it opened, so none closes before it has synced. - fn hold_lingering(&mut self, prefix: &PathOwned) -> Request { + fn hold_lingering(&mut self, prefix: &Path) -> Request { let mut request = Request::default(); let beneath: Vec<_> = self .lingering @@ -248,7 +248,7 @@ impl Discovery { } /// Keep the request at `prefix` open: something beneath it is wanted again. - fn stay(&mut self, prefix: &PathOwned) { + fn stay(&mut self, prefix: &Path) { let Some(request) = self.requests.get_mut(prefix) else { return; }; @@ -260,7 +260,7 @@ impl Discovery { /// Settle a request nothing beneath it wants any more. A broader open request that /// is still wanted holds it until that one goes; otherwise it lingers out. - fn release(&mut self, prefix: PathOwned) { + fn release(&mut self, prefix: Path) { for above in covering(&prefix).into_iter().skip(1) { if !self.beneath.contains_key(&above) { continue; @@ -283,7 +283,7 @@ impl Discovery { } /// Close a request that lingered out. - fn expire(&mut self, prefix: PathOwned) { + fn expire(&mut self, prefix: Path) { let Some(request) = self.requests.remove(&prefix) else { return; }; @@ -292,9 +292,9 @@ impl Discovery { } /// What a request that went held now settles on its own, unless it is wanted again. - fn forget(&mut self, prefix: &PathOwned, request: Request) { + fn forget(&mut self, prefix: &Path, request: Request) { if let Some(at) = request.linger { - self.lingering.remove(&(at, prefix.to_owned())); + self.lingering.remove(&(at, prefix.clone())); } for held in request.held { if self.requests.contains_key(&held) && !self.beneath.contains_key(&held) { @@ -305,10 +305,10 @@ impl Discovery { } /// `path` and every prefix above it, up to the root, narrowest first. -fn covering(path: &Path) -> Vec { +fn covering(path: &Path) -> Vec { std::iter::successors(Some(path.as_str()), |path| { (!path.is_empty()).then(|| path.rfind('/').map_or("", |slash| &path[..slash])) }) - .map(|path| Path::new(path).to_owned()) + .map(|path| Path::new(path).clone()) .collect() } diff --git a/rs/moq-net/src/fuzz.rs b/rs/moq-net/src/fuzz.rs index bb348e6dfb8..6d2f4989c07 100644 --- a/rs/moq-net/src/fuzz.rs +++ b/rs/moq-net/src/fuzz.rs @@ -13,7 +13,7 @@ use bytes::Bytes; use crate::{ - Hops, Path, PathOwned, Pattern, broadcast, cache, coding, + Hops, Path, Pattern, broadcast, cache, coding, coding::{Decode, Decoder, Encode, Encoder, Form, varint}, frame, group, ietf, lite, path::Relative, @@ -204,10 +204,10 @@ pub fn lite_wire(data: &[u8]) -> bool { match kind { 0 => roundtrip::(rest, version), 1 => roundtrip::(rest, version), - 2 => roundtrip::, _>(rest, version), - 3 => roundtrip::, _>(rest, version), + 2 => roundtrip::(rest, version), + 3 => roundtrip::(rest, version), 4 => roundtrip::(rest, version), - 5 => roundtrip::, _>(rest, version), + 5 => roundtrip::(rest, version), 6 => roundtrip::, _>(rest, version), 7 => roundtrip::(rest, version), 8 => roundtrip::(rest, version), @@ -232,7 +232,7 @@ pub fn lite_wire(data: &[u8]) -> bool { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Announced { /// ANNOUNCE_START: a route's suffix and wire hop chain, taking the next id. - Start(PathOwned, Hops), + Start(Path, Hops), /// ANNOUNCE_UPDATE: a live id's new wire hop chain. Update(u64, Hops), /// ANNOUNCE_END: a live id retired. @@ -305,7 +305,7 @@ pub fn excluding(origin: crate::origin::Consumer, peer: crate::Hop) -> crate::or /// Land a claim with a peer's wire route, as a session does, for the origin bench. pub fn dynamic_route( origin: &crate::origin::Producer, - prefix: impl crate::AsPath, + prefix: impl Into, route: crate::origin::Route, ) -> Result { origin.dynamic_route(prefix, route) @@ -356,7 +356,7 @@ impl Interests { /// drops the entry unless some guard still holds it. Not a public API. pub fn bump_publisher_bytes( registry: &crate::stats::Registry, - path: impl crate::AsPath, + path: impl Into, tier: &crate::stats::Tier, n: u64, ) { @@ -439,6 +439,30 @@ pub fn decode_announces(data: &[u8], compress: bool) -> Vec { resolved } +/// Encode a moq-transport NAMESPACE message for each of `paths`. +pub fn encode_namespaces(paths: &[Path]) -> Vec { + let mut data = Vec::new(); + let w = &mut Encoder::new(&mut data, BENCH_IETF.into()); + for path in paths { + let msg = ietf::Namespace { + suffix: path.clone(), + params: None, + }; + msg.encode(w, BENCH_IETF).expect("a namespace that fits"); + } + data +} + +/// Decode the NAMESPACE messages [`encode_namespaces`] wrote. +pub fn decode_namespaces(data: &[u8]) -> Vec { + let r = &mut Decoder::new(data, BENCH_IETF.into()); + let mut paths = Vec::new(); + while !r.is_empty() { + paths.push(ietf::Namespace::decode(r, BENCH_IETF).expect("our own encoding").suffix); + } + paths +} + /// A varint-heavy lite wire object, as the varint bench times it: what a publisher /// pays per frame, per group, and per request. #[derive(Clone, Copy, Debug)] @@ -597,10 +621,10 @@ pub fn ietf_wire(data: &[u8]) -> bool { 4 => roundtrip::(rest, version), 5 => roundtrip::(rest, version), 6 => roundtrip::, _>(rest, version), - 7 => roundtrip::, _>(rest, version), + 7 => roundtrip::(rest, version), 8 => roundtrip::(rest, version), 9 => roundtrip::, _>(rest, version), - 10 => roundtrip::, _>(rest, version), + 10 => roundtrip::(rest, version), 11 => roundtrip::, _>(rest, version), 12 => roundtrip::, _>(rest, version), 13 => roundtrip::, _>(rest, version), @@ -613,13 +637,13 @@ pub fn ietf_wire(data: &[u8]) -> bool { 20 => roundtrip::, _>(rest, version), 21 => roundtrip::(rest, version), 22 => roundtrip::(rest, version), - 23 => roundtrip::, _>(rest, version), - 24 => roundtrip::, _>(rest, version), + 23 => roundtrip::(rest, version), + 24 => roundtrip::(rest, version), 25 => roundtrip::(rest, version), 26 => roundtrip::, _>(rest, version), - 27 => roundtrip::, _>(rest, version), - 28 => roundtrip::, _>(rest, version), - 29 => roundtrip::, _>(rest, version), + 27 => roundtrip::(rest, version), + 28 => roundtrip::(rest, version), + 29 => roundtrip::(rest, version), 30 => roundtrip::(rest, version), 31 => roundtrip::(rest, version), 32 => roundtrip::(rest, version), @@ -630,7 +654,7 @@ pub fn ietf_wire(data: &[u8]) -> bool { 37 => roundtrip::(rest, version), 38 => roundtrip::(rest, version), 39 => roundtrip::(rest, version), - 40 => roundtrip::, _>(rest, version), + 40 => roundtrip::(rest, version), // The unified SETUP replaced the client/server pair at draft-17. The pair refuses a // later draft itself, but the unified one treats an earlier draft as unreachable. 41 if matches!( @@ -899,8 +923,6 @@ pub fn path(data: &[u8]) -> bool { // Normalization is a fixed point: re-parsing what `as_str` printed changes nothing, // or every path that made a round trip through the wire would drift. assert_eq!(Path::new(target.as_str()), target, "normalization is not idempotent"); - assert_eq!(target.to_owned(), target, "owning a path changed it"); - assert_eq!(target.borrow(), target, "borrowing a path changed it"); assert_eq!(target.parts().collect::>().join("/"), target.as_str()); // A prefix is a prefix whichever way you ask. @@ -923,7 +945,7 @@ pub fn path(data: &[u8]) -> bool { assert_eq!(base.resolve(&rel), target, "relative did not invert resolve"); assert_eq!( base.try_resolve(&rel), - Some(target.to_owned()), + Some(target.clone()), "relative escaped the root" ); } @@ -1195,7 +1217,7 @@ pub fn seeds() -> Vec { let mut announced = Vec::new(); let hop = |id: u64| crate::Hop::new(id).expect("a valid hop"); for n in 0..12u64 { - let suffix = PathOwned::from(format!("pid{}/private/channel_{}/health-{n}", n % 3, n % 2)); + let suffix = Path::from(format!("pid{}/private/channel_{}/health-{n}", n % 3, n % 2)); let hops = Hops::try_from(vec![hop(100 + n % 3), hop(1 << 40), hop(1 << 41)]).expect("valid hops"); announced.push(Announced::Start(suffix, hops)); if n % 4 == 3 { diff --git a/rs/moq-net/src/ietf/adapter.rs b/rs/moq-net/src/ietf/adapter.rs index fa970a55a5c..735aa793761 100644 --- a/rs/moq-net/src/ietf/adapter.rs +++ b/rs/moq-net/src/ietf/adapter.rs @@ -9,7 +9,7 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; use crate::{ - Error, PathOwned, + Error, Path, coding::{Decode, Decoder, Encoder, Reader, Writer}, ietf::{self, RequestId}, }; @@ -645,9 +645,9 @@ struct Namespaces { #[derive(Default)] struct NamespacesState { - outgoing: HashMap, - incoming: HashMap, - subscribed: HashMap, + outgoing: HashMap, + incoming: HashMap, + subscribed: HashMap, /// request_id → the namespace that request advertised, one entry per request /// including queued duplicates, so [`Namespaces::forget`] removes only that @@ -663,13 +663,13 @@ struct NamespaceRequests { struct NamespaceRegistration { direction: Direction, - namespace: PathOwned, + namespace: Path, previous: Option, next: Option, } impl NamespacesState { - fn map(&mut self, direction: Direction) -> &mut HashMap { + fn map(&mut self, direction: Direction) -> &mut HashMap { match direction { Direction::Outgoing => &mut self.outgoing, Direction::Incoming => &mut self.incoming, @@ -685,7 +685,7 @@ impl Namespaces { /// advertises the same namespace twice would otherwise move the name onto the second /// request, and the withdrawal it eventually names would close that one while the /// first stayed open, advertised for the life of the session. - fn register(&self, direction: Direction, namespace: PathOwned, request_id: RequestId) { + fn register(&self, direction: Direction, namespace: Path, request_id: RequestId) { let mut state = self.state.lock().unwrap(); if state.by_request.contains_key(&request_id) { return; @@ -717,13 +717,13 @@ impl Namespaces { } /// The namespace the peer's request `request_id` advertises, if it is a PUBLISH_NAMESPACE. - fn incoming(&self, request_id: RequestId) -> Option { + fn incoming(&self, request_id: RequestId) -> Option { let state = self.state.lock().unwrap(); let registration = state.by_request.get(&request_id)?; (registration.direction == Direction::Incoming).then(|| registration.namespace.clone()) } - fn get(&self, direction: Direction, namespace: &PathOwned) -> Option { + fn get(&self, direction: Direction, namespace: &Path) -> Option { self.state .lock() .unwrap() @@ -796,9 +796,9 @@ struct Shared { /// its stream, which it does after releasing the route. Bounded by the peer's requests. #[derive(Default)] struct Readvertised { - namespaces: HashMap, + namespaces: HashMap, /// Each withdrawn request a handler still holds, to the namespace it advertised. - withdrawn: HashMap, + withdrawn: HashMap, } #[derive(Default)] @@ -1242,7 +1242,7 @@ fn classify(type_id: u64, body: &Bytes, version: Version, namespaces: &Namespace let mut r = Decoder::new(body, version.into()); let id = RequestId::decode(&mut r, version)?; if let Ok(prefix) = crate::ietf::namespace::decode_namespace(&mut r) { - namespaces.register(Direction::Subscribed, prefix.into_owned(), id); + namespaces.register(Direction::Subscribed, prefix, id); } Ok(Route::NewRequest(id)) } @@ -1617,7 +1617,7 @@ fn decode_response_request_id(body: &Bytes, version: Version) -> Result Result { +fn decode_publish_namespace_body(body: &Bytes, version: Version) -> Result { let mut r = Decoder::new(body, version.into()); // Skip request_id let _request_id = RequestId::decode(&mut r, version)?; @@ -1626,7 +1626,7 @@ fn decode_publish_namespace_body(body: &Bytes, version: Version) -> Result ietf::PublishNamespace<'_> { + fn publish_namespace(request_id: RequestId, namespace: &str) -> ietf::PublishNamespace { ietf::PublishNamespace { request_id, track_namespace: crate::Path::new(namespace), diff --git a/rs/moq-net/src/ietf/fetch.rs b/rs/moq-net/src/ietf/fetch.rs index c47fd893a4c..6b178839131 100644 --- a/rs/moq-net/src/ietf/fetch.rs +++ b/rs/moq-net/src/ietf/fetch.rs @@ -22,7 +22,7 @@ use super::Version; pub enum FetchType<'a> { /// An inclusive range of a track, through draft-18. Standalone { - namespace: Path<'a>, + namespace: Path, track: Cow<'a, str>, start: Location, end: Location, @@ -38,7 +38,7 @@ pub enum FetchType<'a> { /// A track, with the range as a LOCATION_FILTER, from draft-22 on. Unfiltered is /// everything from `{0, 0}` up to Largest Object. Filtered { - namespace: Path<'a>, + namespace: Path, track: Cow<'a, str>, filter: Filter, }, diff --git a/rs/moq-net/src/ietf/namespace.rs b/rs/moq-net/src/ietf/namespace.rs index 40ca4fc46bd..b62cc2a6a63 100644 --- a/rs/moq-net/src/ietf/namespace.rs +++ b/rs/moq-net/src/ietf/namespace.rs @@ -28,7 +28,7 @@ fn to_tuple(namespace: &Path) -> Vec { parts } -fn from_tuple(parts: &[String]) -> Path<'static> { +fn from_tuple(parts: &[String]) -> Path { let mut path = String::new(); for (i, part) in parts.iter().enumerate() { if i > 0 { @@ -69,7 +69,7 @@ const MAX_BYTES: usize = 4096; /// /// Every limit here closes the session as a PROTOCOL_VIOLATION (draft-22 section 8.7): /// more than 32 fields, an empty field, or more than 4096 bytes. -fn decode_fields(r: &mut Decoder<'_>) -> Result<(Path<'static>, usize), DecodeError> { +fn decode_fields(r: &mut Decoder<'_>) -> Result<(Path, usize), DecodeError> { let count = r.varint()?; if count == 0 { @@ -99,12 +99,12 @@ fn decode_fields(r: &mut Decoder<'_>) -> Result<(Path<'static>, usize), DecodeEr } /// Decode a Track Namespace. -pub fn decode_namespace(r: &mut Decoder<'_>) -> Result, DecodeError> { +pub fn decode_namespace(r: &mut Decoder<'_>) -> Result { Ok(decode_fields(r)?.0) } /// Decode a Full Track Name: a Track Namespace, then a Track Name, at most 4096 bytes together. -pub fn decode_track(r: &mut Decoder<'_>) -> Result<(Path<'static>, String), DecodeError> { +pub fn decode_track(r: &mut Decoder<'_>) -> Result<(Path, String), DecodeError> { let (namespace, size) = decode_fields(r)?; let name = r.string()?; if size + name.len() > MAX_BYTES { @@ -176,13 +176,13 @@ mod tests { encode_path(&Path::from(path.to_string())) } - fn encode_path(path: &Path<'_>) -> Vec { + fn encode_path(path: &Path) -> Vec { let mut buf = Vec::new(); encode_namespace(&mut Encoder::new(&mut buf, FORM), path).unwrap(); buf } - fn decode_ns(bytes: &[u8]) -> Path<'static> { + fn decode_ns(bytes: &[u8]) -> Path { decode_namespace(&mut Decoder::new(bytes, FORM)).unwrap() } diff --git a/rs/moq-net/src/ietf/publish.rs b/rs/moq-net/src/ietf/publish.rs index 55a6dcd1851..f529ed0156d 100644 --- a/rs/moq-net/src/ietf/publish.rs +++ b/rs/moq-net/src/ietf/publish.rs @@ -223,7 +223,7 @@ impl Message for PublishDone<'_> { #[derive(Debug)] pub struct Publish<'a> { pub request_id: RequestId, - pub track_namespace: Path<'a>, + pub track_namespace: Path, pub track_name: Cow<'a, str>, pub track_alias: u64, pub largest_location: Option, diff --git a/rs/moq-net/src/ietf/publish_namespace.rs b/rs/moq-net/src/ietf/publish_namespace.rs index 6643182bd19..39837c035d2 100644 --- a/rs/moq-net/src/ietf/publish_namespace.rs +++ b/rs/moq-net/src/ietf/publish_namespace.rs @@ -43,9 +43,9 @@ pub struct AdvertParams { /// PublishNamespace message (0x06) /// Sent by the publisher to announce the availability of a namespace. #[derive(Clone, Debug)] -pub struct PublishNamespace<'a> { +pub struct PublishNamespace { pub request_id: RequestId, - pub track_namespace: Path<'a>, + pub track_namespace: Path, /// The extension parameters, which a session that negotiated none leaves empty. pub params: AdvertParams, @@ -55,7 +55,7 @@ pub struct PublishNamespace<'a> { pub authorization_token: Option, } -impl PublishNamespace<'_> { +impl PublishNamespace { /// Decode the message body, expecting the parameters of the extensions the session /// negotiated. /// @@ -92,7 +92,7 @@ impl PublishNamespace<'_> { } } -impl Message for PublishNamespace<'_> { +impl Message for PublishNamespace { const ID: u64 = 0x06; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -330,14 +330,14 @@ impl Message for PublishNamespaceError<'_> { /// PublishNamespaceDone message (0x09) /// v14/v15: uses track_namespace. v16: uses request_id. #[derive(Clone, Debug)] -pub struct PublishNamespaceDone<'a> { +pub struct PublishNamespaceDone { /// v14/v15: the namespace being unannounced - pub track_namespace: Path<'a>, + pub track_namespace: Path, /// v16: the request ID of the original PublishNamespace pub request_id: RequestId, } -impl Message for PublishNamespaceDone<'_> { +impl Message for PublishNamespaceDone { const ID: u64 = 0x09; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -379,7 +379,7 @@ impl Message for PublishNamespaceDone<'_> { #[derive(Clone, Debug)] pub struct PublishNamespaceCancel<'a> { /// v14/v15: the namespace being cancelled - pub track_namespace: Path<'a>, + pub track_namespace: Path, /// v16: the request ID of the original PublishNamespace pub request_id: RequestId, pub error_code: u64, diff --git a/rs/moq-net/src/ietf/publisher.rs b/rs/moq-net/src/ietf/publisher.rs index 23e73f0d42c..9c31222a97a 100644 --- a/rs/moq-net/src/ietf/publisher.rs +++ b/rs/moq-net/src/ietf/publisher.rs @@ -17,7 +17,7 @@ use crate::transport::poll::SendGroup as _; use crate::transport::poll::{GroupOf, SendOptions}; use crate::{ - AsPath, DecodeError, Error, Timescale, Timestamp, + DecodeError, Error, Timescale, Timestamp, coding::{Encode as _, Encoder, Stream, Writer}, ietf::{self, Control, EndLocation, FetchHeader, FetchType, Filter, GroupOrder, Location, RequestId}, priority::{Priority, PriorityHandle, PriorityQueue}, @@ -681,18 +681,18 @@ struct Namespaces { peer: cluster::Peer, target: Target, /// Every announced broadcast under this loop's prefix. - watched: HashMap, + watched: HashMap, /// The open PUBLISH_NAMESPACE request carrying each advertised namespace. Empty when /// the entries ride a SUBSCRIBE_NAMESPACE stream inline. - requests: HashMap, + requests: HashMap, /// Requests with an update to send, in the order they asked for a request ID. Each /// waits for credit beside the loop's other work and computes its update when it gets /// one, so a later change supersedes an earlier one that is still waiting. - updates: VecDeque, + updates: VecDeque, /// Requests with an update in flight, each answered on its own stream, by when each /// gives up. Sent in order with one timeout, so the front is the earliest; an entry /// whose request moved on is skipped when it reaches the front. - inflight: VecDeque<(crate::time::Instant, crate::PathOwned)>, + inflight: VecDeque<(crate::time::Instant, crate::Path)>, /// Requests whose stream woke the loop, so a wake polls those rather than every /// update in flight. woken: Arc, @@ -819,7 +819,7 @@ enum NamespaceEvent { /// The queued update at the front got this request ID. Renew(RequestId), /// What came back for an update in flight. - Answered(crate::PathOwned, Result), + Answered(crate::Path, Result), } /// What came back for a PUBLISH_NAMESPACE or an update in flight. @@ -835,14 +835,14 @@ enum Answer { /// The requests whose stream woke an announce loop since it last looked. #[derive(Default)] struct Woken { - suffixes: std::sync::Mutex>, + suffixes: std::sync::Mutex>, /// The loop, woken in turn. owner: std::sync::Mutex>, } impl Woken { /// A waker that marks `suffix` woken. - fn waker(self: &Arc, suffix: crate::PathOwned) -> std::task::Waker { + fn waker(self: &Arc, suffix: crate::Path) -> std::task::Waker { std::task::Waker::from(Arc::new(RequestWaker { woken: self.clone(), suffix, @@ -852,7 +852,7 @@ impl Woken { struct RequestWaker { woken: Arc, - suffix: crate::PathOwned, + suffix: crate::Path, } impl std::task::Wake for RequestWaker { @@ -930,7 +930,7 @@ enum Joined { end: Location, cache: track::Consumer, /// The subscription's namespace, which the fetch is held to the grant on. - namespace: crate::PathOwned, + namespace: crate::Path, /// The subscription's own request token authorized it, so the limit does not bound /// the fetch either. token: bool, @@ -1127,7 +1127,7 @@ impl Publisher { tracing::debug!(message = ?msg, "received subscribe"); let gate = crate::auth::Gate::new( this.auth.clone(), - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::Direction::Publish, ); let task = this.serve_subscribe_stream(stream, msg, gate); @@ -1210,7 +1210,7 @@ impl Publisher { tracing::debug!(message = ?msg, "received subscribe_namespace"); let gate = crate::auth::Gate::new( this.auth.clone(), - msg.namespace.to_owned(), + msg.namespace.clone(), crate::auth::Direction::Publish, ); async move { @@ -1270,7 +1270,7 @@ impl Publisher { tracing::debug!(message = ?msg, "received track_status"); let gate = crate::auth::Gate::new( this.auth.clone(), - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::Direction::Publish, ); async move { @@ -1317,7 +1317,7 @@ impl Publisher { ) -> impl std::future::Future> { let gate = crate::auth::Gate::new( self.auth.clone(), - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::Direction::Publish, ); self.serve_subscribe_stream(stream, msg, gate) @@ -1358,7 +1358,7 @@ impl Publisher { }; let request_id = msg.request_id; let track_name = msg.track_name.clone(); - let absolute = self.origin.absolute(&msg.track_namespace).to_owned(); + let absolute = self.origin.absolute(&msg.track_namespace).clone(); tracing::debug!(id = %request_id, broadcast = %absolute, track = %track_name, "subscribe started"); @@ -1429,7 +1429,7 @@ impl Publisher { &self.runtime, verdict, verified, - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::RequestKind::Subscribe, Some(slot.clone()), ); @@ -1523,7 +1523,7 @@ impl Publisher { // the same boundary used by the subscription so the two streams never overlap, // again on every resume. let filter = msg.filter; - let namespace = msg.track_namespace.to_owned(); + let namespace = msg.track_namespace.clone(); let token = request_grant.is_some(); let save_join = |largest: Option, cache: &crate::track::Consumer, @@ -2823,7 +2823,7 @@ impl Publisher { fn fetch_gate(&self, msg: &ietf::Fetch<'_>) -> FetchGate { match &msg.fetch_type { FetchType::Standalone { namespace, .. } | FetchType::Filtered { namespace, .. } => FetchGate::Standalone( - crate::auth::Gate::new(self.auth.clone(), namespace.to_owned(), crate::auth::Direction::Publish), + crate::auth::Gate::new(self.auth.clone(), namespace.clone(), crate::auth::Direction::Publish), ), FetchType::RelativeJoining { subscriber_request_id, .. @@ -3311,7 +3311,7 @@ impl Publisher { ( Location, track::Consumer, - crate::PathOwned, + crate::Path, Ranking>, bool, ), @@ -3399,7 +3399,7 @@ impl Publisher { ) -> impl std::future::Future> { let gate = crate::auth::Gate::new( self.auth.clone(), - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::Direction::Publish, ); self.serve_track_status_stream(stream, msg, gate) @@ -3557,8 +3557,8 @@ impl Publisher { async fn sync_namespace( &self, ns: &mut Namespaces, - suffix: &crate::PathOwned, - path: &crate::PathOwned, + suffix: &crate::Path, + path: &crate::Path, ) -> Result<(), Error> { let Some((advert, auth)) = self.offer(ns, suffix, path) else { return Ok(()); @@ -3628,7 +3628,7 @@ impl Publisher { return Ok(()); } - let absolute = self.origin.absolute(path).to_owned(); + let absolute = self.origin.absolute(path).clone(); // Only a PUBLISH_NAMESPACE request or an update to one can be refused; everything // else below either rides a stream the peer already accepted or says nothing at all. let mut refused = watch.refused; @@ -3693,7 +3693,7 @@ impl Publisher { stream .writer .encode_message(&ietf::NamespaceDone { - suffix: suffix.as_path(), + suffix: suffix.clone(), }) .await?; } @@ -3701,7 +3701,7 @@ impl Publisher { stream .writer .encode_message(&ietf::Namespace { - suffix: suffix.as_path(), + suffix: suffix.clone(), params: negotiated.extended().then(|| advert.params()), }) .await?; @@ -3711,7 +3711,7 @@ impl Publisher { stream .writer .encode_message(&ietf::NamespaceDone { - suffix: suffix.as_path(), + suffix: suffix.clone(), }) .await?; } @@ -3743,10 +3743,10 @@ impl Publisher { /// Returns what the refusal, if any, said about coming back. async fn advertise_namespace( &self, - requests: &mut HashMap, + requests: &mut HashMap, watch: &mut Watched, - path: &crate::PathOwned, - suffix: crate::PathOwned, + path: &crate::Path, + suffix: crate::Path, params: ietf::AdvertParams, auth: Option, ) -> Result { @@ -3766,7 +3766,7 @@ impl Publisher { .writer .encode_message(&ietf::PublishNamespace { request_id, - track_namespace: path.as_path(), + track_namespace: path.clone(), params, authorization_token: auth.as_ref().and_then(|auth| self.request_token(auth)), }) @@ -3866,8 +3866,8 @@ impl Publisher { fn offer( &self, ns: &Namespaces, - suffix: &crate::PathOwned, - path: &crate::PathOwned, + suffix: &crate::Path, + path: &crate::Path, ) -> Option<(Advert, Option)> { let watch = ns.watched.get(suffix)?; let advert = match ns.permitted(path) { @@ -3897,7 +3897,7 @@ impl Publisher { /// Drop queued updates that no longer have anything to send, so the next one to take /// a request ID is one that will use it: drafts before 17 require the next ID. - fn prune_updates(&self, ns: &mut Namespaces, prefix: &crate::PathOwned) { + fn prune_updates(&self, ns: &mut Namespaces, prefix: &crate::Path) { while let Some(suffix) = ns.updates.front() { let wants = ns.requests.get(suffix).is_some_and(|request| { request.queued @@ -3930,7 +3930,7 @@ impl Publisher { async fn send_update( &self, ns: &mut Namespaces, - prefix: &crate::PathOwned, + prefix: &crate::Path, request_id: RequestId, ) -> Result<(), Error> { let suffix = ns.updates.pop_front().expect("allocated for a queued update"); @@ -3984,13 +3984,13 @@ impl Publisher { /// What came back for an update in flight, polling only the requests that woke. fn poll_answer( - requests: &mut HashMap, - inflight: &mut VecDeque<(crate::time::Instant, crate::PathOwned)>, + requests: &mut HashMap, + inflight: &mut VecDeque<(crate::time::Instant, crate::Path)>, woken: &Arc, now: crate::time::Instant, version: Version, cx: &mut std::task::Context<'_>, - ) -> Poll<(crate::PathOwned, Result)> { + ) -> Poll<(crate::Path, Result)> { // Registered before the take, so a wake landing between them is not lost. *woken.owner.lock().unwrap() = Some(cx.waker().clone()); let mut suffixes = std::mem::take(&mut *woken.suffixes.lock().unwrap()); @@ -4044,15 +4044,15 @@ impl Publisher { async fn answered( &self, ns: &mut Namespaces, - prefix: &crate::PathOwned, - suffix: crate::PathOwned, + prefix: &crate::Path, + suffix: crate::Path, answer: Result, ) -> Result<(), Error> { let Some(request) = ns.requests.get_mut(&suffix) else { return Ok(()); }; let idle = request.pending.take().is_none(); - let absolute = self.origin.absolute(&request.path).to_owned(); + let absolute = self.origin.absolute(&request.path).clone(); // A reset stream ends only this request, like a FIN; a malformed answer is the peer's // violation and still ends the loop. @@ -4238,8 +4238,8 @@ impl Publisher { async fn withdraw_namespace( &self, target: &mut Target, - requests: &mut HashMap, - suffix: crate::PathOwned, + requests: &mut HashMap, + suffix: crate::Path, ) -> Result<(), Error> { match target { Target::Requests(_) => { @@ -4252,7 +4252,7 @@ impl Publisher { stream .writer .encode_message(&ietf::NamespaceDone { - suffix: suffix.as_path(), + suffix: suffix.clone(), }) .await?; } @@ -4264,7 +4264,7 @@ impl Publisher { async fn end_request( &self, mut stream: Stream, - path: &crate::PathOwned, + path: &crate::Path, request_id: RequestId, ) { // Draft-17+ withdraws by cancelling the request in both directions; a FIN is only @@ -4278,7 +4278,7 @@ impl Publisher { let _ = stream .writer .encode_message(&ietf::PublishNamespaceDone { - track_namespace: path.as_path(), + track_namespace: path.clone(), request_id, }) .await; @@ -4290,8 +4290,8 @@ impl Publisher { /// Close out every open PUBLISH_NAMESPACE request. A no-op for a loop whose entries /// ride the SUBSCRIBE_NAMESPACE stream itself, which retracts them by ending. - async fn withdraw_requests(&self, target: &mut Target, requests: &mut HashMap) { - let suffixes: Vec = requests.keys().cloned().collect(); + async fn withdraw_requests(&self, target: &mut Target, requests: &mut HashMap) { + let suffixes: Vec = requests.keys().cloned().collect(); for suffix in suffixes { let _ = self.withdraw_namespace(target, requests, suffix).await; } @@ -4325,7 +4325,7 @@ impl Publisher { let mut ns = Namespaces::new(peer, Target::Requests(None)); ns.epochs = self.peer_setup.get().await.epoch; - self.run_namespaces(origin.announced(), crate::Path::empty().to_owned(), ns, Vec::new()) + self.run_namespaces(origin.announced(), crate::Path::empty().clone(), ns, Vec::new()) .await } @@ -4341,11 +4341,11 @@ impl Publisher { async fn run_subscribe_namespace_stream( self, stream: Stream, - msg: ietf::SubscribeNamespace<'_>, + msg: ietf::SubscribeNamespace, ) -> Result<(), Error> { let gate = crate::auth::Gate::new( self.auth.clone(), - msg.namespace.to_owned(), + msg.namespace.clone(), crate::auth::Direction::Publish, ); self.serve_subscribe_namespace_stream(stream, msg, gate).await @@ -4354,13 +4354,13 @@ impl Publisher { async fn serve_subscribe_namespace_stream( self, mut stream: Stream, - msg: ietf::SubscribeNamespace<'_>, + msg: ietf::SubscribeNamespace, gate: crate::auth::Gate, ) -> Result<(), Error> { if let Some(error) = super::namespace::refusal(&msg.namespace, self.version) { return super::request_stream::reject(stream, error).await; } - let prefix = msg.namespace.to_owned(); + let prefix = msg.namespace.clone(); tracing::debug!(prefix = %self.origin.absolute(&prefix), "subscribe_namespace stream"); @@ -4521,7 +4521,7 @@ impl Publisher { async fn run_namespaces( &self, mut announced: crate::announce::Consumer, - mut prefix: crate::PathOwned, + mut prefix: crate::Path, mut ns: Namespaces, initial: Vec, ) -> Result<(), Error> { @@ -4708,7 +4708,7 @@ impl Publisher { // A minimum wait the peer named is enforced by `sync_namespace`, which // every path goes through, so a namespace still inside one simply // makes no offer this turn. The next sweep is at most RETRY_MAX away. - let deferred: Vec = ns + let deferred: Vec = ns .watched .iter() .filter(|(_, watch)| watch.deferred) @@ -4722,7 +4722,7 @@ impl Publisher { } NamespaceEvent::Regrant(permit) => { ns.permit = permit; - let suffixes: Vec = ns.watched.keys().cloned().collect(); + let suffixes: Vec = ns.watched.keys().cloned().collect(); for suffix in suffixes { let path = prefix.join(&suffix); self.sync_namespace(&mut ns, &suffix, &path).await?; @@ -4773,7 +4773,7 @@ impl Publisher { async fn apply_update( &self, ns: &mut Namespaces, - prefix: &crate::PathOwned, + prefix: &crate::Path, update: crate::announce::Announce, active: bool, ) -> Result<(), Error> { @@ -5973,7 +5973,7 @@ fn buffer_object( /// One draft-14/15 advertisement: the PUBLISH_NAMESPACE request it rode on and /// what closes it out with PUBLISH_NAMESPACE_DONE. struct NamespaceRequest { - path: crate::PathOwned, + path: crate::Path, request_id: RequestId, stream: Stream, messages: super::request_stream::Messages, @@ -7097,7 +7097,7 @@ mod serve_tests { let stream = Stream::open(&mut crate::driver::Transport::new(session.clone()), version) .await .unwrap(); - let suffix: crate::PathOwned = "room".into(); + let suffix: crate::Path = "room".into(); let mut requests = HashMap::new(); requests.insert( suffix.clone(), @@ -16043,7 +16043,7 @@ mod tests { // The state a refused or failed offer leaves behind: the peer holds nothing, and // the loop is coming back to it on a timer. - let suffix: crate::PathOwned = crate::Path::new("from/peer").to_owned(); + let suffix: crate::Path = crate::Path::new("from/peer").clone(); let mut watch = Watched::new(crate::origin::Route::default().with_hops(hops)); watch.deferred = true; diff --git a/rs/moq-net/src/ietf/publisher/tracks.rs b/rs/moq-net/src/ietf/publisher/tracks.rs index ebf06232fbf..71e40e64859 100644 --- a/rs/moq-net/src/ietf/publisher/tracks.rs +++ b/rs/moq-net/src/ietf/publisher/tracks.rs @@ -21,13 +21,13 @@ use crate::util::TaskSet; #[derive(Default)] pub(super) struct Listings { /// The prefix of every established SUBSCRIBE_TRACKS, by a key its guard removes. - prefixes: HashMap, + prefixes: HashMap, next: u64, /// On draft-18, the tracks a PUBLISH of ours subscribes the peer to, and how many /// SUBSCRIBEs of the peer's each track has: draft-18 allows the peer one subscription /// per track (section 5.1), so a PUBLISH joins neither kind. - published: HashMap<(crate::PathOwned, Arc), usize>, - subscribed: HashMap<(crate::PathOwned, Arc), usize>, + published: HashMap<(crate::Path, Arc), usize>, + subscribed: HashMap<(crate::Path, Arc), usize>, } /// Holds a SUBSCRIBE_TRACKS prefix as established until dropped. @@ -40,7 +40,7 @@ impl Established { /// Establish `prefix`, unless it overlaps an established request (section 10.19): two /// would match the same namespaces, and keeping them apart is also what lists each /// broadcast at most once per session. - fn new(listings: &kio::Shared, prefix: &crate::PathOwned) -> Option { + fn new(listings: &kio::Shared, prefix: &crate::Path) -> Option { let mut state = listings.lock(); if state .prefixes @@ -69,7 +69,7 @@ impl Drop for Established { /// until dropped. pub(super) struct Subscribed { listings: kio::Shared, - key: (crate::PathOwned, Arc), + key: (crate::Path, Arc), /// A SUBSCRIBE of the peer's, rather than a PUBLISH of ours. direct: bool, } @@ -77,17 +77,17 @@ pub(super) struct Subscribed { impl Subscribed { /// Claim `namespace`'s `track` for a PUBLISH of ours, unless the peer is subscribed to /// it already, by its SUBSCRIBE or another PUBLISH. - fn publish(listings: &kio::Shared, namespace: crate::PathOwned, track: Arc) -> Option { + fn publish(listings: &kio::Shared, namespace: crate::Path, track: Arc) -> Option { Self::claim(listings, (namespace, track), false) } /// Count the peer's SUBSCRIBE to `namespace`'s `track`, unless a PUBLISH of ours /// subscribes it already. pub(super) fn subscribe(listings: &kio::Shared, namespace: &crate::Path, track: &str) -> Option { - Self::claim(listings, (namespace.to_owned(), Arc::from(track)), true) + Self::claim(listings, (namespace.clone(), Arc::from(track)), true) } - fn claim(listings: &kio::Shared, key: (crate::PathOwned, Arc), direct: bool) -> Option { + fn claim(listings: &kio::Shared, key: (crate::Path, Arc), direct: bool) -> Option { let mut locked = listings.lock(); let Listings { published, subscribed, .. @@ -142,14 +142,14 @@ struct Params { /// (suffix, name) in listing order. A track removed before its skip went out cancels it. /// A FORWARD=1 listing adds one only once this is empty, so a peer that stops reading /// leaves the rest unread in each broadcast's listing. -type Skips = kio::Shared), ()>>; +type Skips = kio::Shared), ()>>; /// What a request's listing shares with the tasks listing each broadcast. #[derive(Clone)] struct Request { origin: origin::Consumer, /// The request's prefix, which every PUBLISH namespace starts with. - prefix: crate::PathOwned, + prefix: crate::Path, /// Whether the request asked for every track's objects, which we answer with skips. forward: bool, params: Params, @@ -184,14 +184,14 @@ struct Entries { } impl Entries { - fn skip(&mut self, skips: &Skips, suffix: &crate::PathOwned, name: Arc) { + fn skip(&mut self, skips: &Skips, suffix: &crate::Path, name: Arc) { skips.lock().insert((suffix.clone(), name.clone()), ()); self.skipped.insert(name); } /// `name` is no longer listed: drop whatever it got to, ending its PUBLISH with /// TRACK_ENDED if it `finished`. - fn remove(&mut self, skips: &Skips, suffix: &crate::PathOwned, name: Arc, finished: bool) { + fn remove(&mut self, skips: &Skips, suffix: &crate::Path, name: Arc, finished: bool) { if self.staged.as_ref().is_some_and(|staged| staged.name == name) { self.staged = None; } @@ -208,7 +208,7 @@ impl Entries { } /// Nothing more is PUBLISHed or skipped, while the PUBLISHes already sent stay listed. - fn halt(&mut self, skips: &Skips, suffix: &crate::PathOwned) { + fn halt(&mut self, skips: &Skips, suffix: &crate::Path) { self.pending.clear(); self.staged = None; let mut skips = skips.lock(); @@ -218,7 +218,7 @@ impl Entries { } /// Every track is no longer listed. - fn unlist(&mut self, skips: &Skips, suffix: &crate::PathOwned) { + fn unlist(&mut self, skips: &Skips, suffix: &crate::Path) { self.halt(skips, suffix); self.listed.clear(); } @@ -239,7 +239,7 @@ struct Listed { stream: Stream, track: track::Consumer, /// The track's full namespace, as the PUBLISH names it. - namespace: crate::PathOwned, + namespace: crate::Path, name: Arc, properties: ietf::Properties, /// Closes once the track is no longer listed, holding whether it finished. @@ -283,7 +283,7 @@ impl Publisher { pub(super) async fn serve_subscribe_tracks_stream( self, mut stream: Stream, - msg: ietf::SubscribeTracks<'_>, + msg: ietf::SubscribeTracks, ) -> Result<(), Error> { if let Some(error) = super::super::namespace::refusal(&msg.prefix, self.version) { return super::super::request_stream::reject(stream, error).await; @@ -310,7 +310,7 @@ impl Publisher { let error = refuse(request::NOT_SUPPORTED, "SUBSCRIBE_TRACKS needs FORWARD=0"); return super::super::request_stream::reject(stream, error).await; } - let prefix = msg.prefix.to_owned(); + let prefix = msg.prefix.clone(); let Some(established) = Established::new(&self.listings, &prefix) else { let error = refuse(request::PREFIX_OVERLAP, "overlaps another SUBSCRIBE_TRACKS"); return super::super::request_stream::reject(stream, error).await; @@ -365,8 +365,8 @@ impl Publisher { live: live.consume(), }; // Each listed broadcast's route, closed once its announcement goes; see [`Route`]. - let mut routes: HashMap> = HashMap::new(); - let withdraw = |routes: &mut HashMap>, prefix: &crate::PathOwned| { + let mut routes: HashMap> = HashMap::new(); + let withdraw = |routes: &mut HashMap>, prefix: &crate::Path| { if let Some(route) = routes.remove(prefix) && let Ok(mut route) = route.write() { @@ -598,12 +598,12 @@ impl Publisher { async fn run_listed_broadcast( self, request: Request, - suffix: crate::PathOwned, + suffix: crate::Path, epoch: Option, route: kio::Consumer, ) { - let namespace = request.prefix.join(&suffix).to_owned(); - let absolute = self.origin.absolute(&namespace).to_owned(); + let namespace = request.prefix.join(&suffix).clone(); + let absolute = self.origin.absolute(&namespace).clone(); let flate = self.peer_setup.get().await.flate; // Resolving may wait on a route for the broadcast, which its going ends. @@ -868,7 +868,7 @@ impl Publisher { // An update may ask for a new group only if the PUBLISH sent DYNAMIC_GROUPS, which // INCLUDE_PROPERTIES=0 leaves out. let dynamic = properties.dynamic_groups; - let absolute = self.origin.absolute(&namespace).to_owned(); + let absolute = self.origin.absolute(&namespace).clone(); // Every wait on the peer, for a Request ID or the PUBLISH's bytes, lasts only while // the track is listed and the request stands. A Request ID abandoned unsent leaves a @@ -1167,7 +1167,7 @@ impl Publisher { &self, stream: &mut Stream, track: &track::Consumer, - namespace: &crate::PathOwned, + namespace: &crate::Path, request_id: RequestId, params: &mut Params, how: Resume, @@ -1405,7 +1405,7 @@ mod tests { } } - fn listing(forward: bool) -> ietf::SubscribeTracks<'static> { + fn listing(forward: bool) -> ietf::SubscribeTracks { ietf::SubscribeTracks { request_id: RequestId(0), prefix: crate::Path::new("room"), @@ -1909,7 +1909,7 @@ mod tests { let route = kio::Producer::new(Route::Replaced); let request = Request { origin: h._origin.consume(), - prefix: crate::Path::new("").to_owned(), + prefix: crate::Path::new("").clone(), forward: true, params: Params { priority: crate::ietf::priority::DEFAULT_WIRE, @@ -1924,7 +1924,7 @@ mod tests { }; moq_clock::sim::spawn(h.publisher.clone().run_listed_broadcast( request, - crate::Path::new("room/cam").to_owned(), + crate::Path::new("room/cam").clone(), None, route.consume(), )); diff --git a/rs/moq-net/src/ietf/request_stream.rs b/rs/moq-net/src/ietf/request_stream.rs index c0a379e89b4..aad6608b65a 100644 --- a/rs/moq-net/src/ietf/request_stream.rs +++ b/rs/moq-net/src/ietf/request_stream.rs @@ -515,12 +515,12 @@ impl Decode for Update { #[derive(Debug)] pub(super) struct NamespaceUpdate { pub(super) request_id: super::RequestId, - pub(super) prefix: Option, + pub(super) prefix: Option, pub(super) refused: Option, } #[derive(Debug)] -struct NamespacePrefix(crate::PathOwned); +struct NamespacePrefix(crate::Path); impl super::parameters::Param for NamespacePrefix { fn param_encode(&self, w: &mut crate::coding::Encoder<'_>, _: Version) -> Result<(), crate::coding::EncodeError> { diff --git a/rs/moq-net/src/ietf/subscribe.rs b/rs/moq-net/src/ietf/subscribe.rs index b73b6924a46..243aaa44f96 100644 --- a/rs/moq-net/src/ietf/subscribe.rs +++ b/rs/moq-net/src/ietf/subscribe.rs @@ -21,7 +21,7 @@ use super::Version; #[derive(Clone, Debug)] pub struct Subscribe<'a> { pub request_id: RequestId, - pub track_namespace: Path<'a>, + pub track_namespace: Path, pub track_name: Cow<'a, str>, pub subscriber_priority: u8, pub group_order: GroupOrder, diff --git a/rs/moq-net/src/ietf/subscribe_namespace.rs b/rs/moq-net/src/ietf/subscribe_namespace.rs index 15ccfc51f96..82d3a060773 100644 --- a/rs/moq-net/src/ietf/subscribe_namespace.rs +++ b/rs/moq-net/src/ietf/subscribe_namespace.rs @@ -26,9 +26,9 @@ fn is_legacy_version(version: Version) -> bool { /// Options field when it split SUBSCRIBE_TRACKS (0x51) off into its own message /// type (#1542). Draft-14 through draft-17 use [`SubscribeNamespaceLegacy`]. #[derive(Clone, Debug)] -pub struct SubscribeNamespace<'a> { +pub struct SubscribeNamespace { pub request_id: RequestId, - pub namespace: Path<'a>, + pub namespace: Path, /// MoQ Hidden: also advertise hidden namespaces. Only sent to a peer that declared /// the extension; absent means false. pub hidden: bool, @@ -48,7 +48,7 @@ fn hidden_from_param(value: Option) -> Result { } } -impl Message for SubscribeNamespace<'_> { +impl Message for SubscribeNamespace { const ID: u64 = 0x50; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -124,16 +124,16 @@ impl SubscribeOptions { /// Required Request ID delta (removed in draft-18 per #1615). Draft-18+ uses /// [`SubscribeNamespace`]. #[derive(Clone, Debug)] -pub struct SubscribeNamespaceLegacy<'a> { +pub struct SubscribeNamespaceLegacy { pub request_id: RequestId, - pub namespace: Path<'a>, + pub namespace: Path, /// Encoded on v16/v17 only; earlier drafts always mean [`SubscribeOptions::Namespace`]. pub subscribe_options: SubscribeOptions, /// MoQ Hidden: see [`SubscribeNamespace::hidden`]. pub hidden: bool, } -impl Message for SubscribeNamespaceLegacy<'_> { +impl Message for SubscribeNamespaceLegacy { const ID: u64 = 0x11; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -243,14 +243,14 @@ impl Message for SubscribeNamespaceError<'_> { /// UnsubscribeNamespace message (0x14): v14/v15 only (v16 uses stream close). /// v14 names the prefix it subscribed, v15 the request. #[derive(Clone, Debug)] -pub struct UnsubscribeNamespace<'a> { +pub struct UnsubscribeNamespace { /// v14: the Track Namespace Prefix of the SUBSCRIBE_NAMESPACE it cancels. - pub prefix: Path<'a>, + pub prefix: Path, /// v15: the Request ID of the SUBSCRIBE_NAMESPACE it cancels. pub request_id: RequestId, } -impl Message for UnsubscribeNamespace<'_> { +impl Message for UnsubscribeNamespace { const ID: u64 = 0x14; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -285,15 +285,15 @@ impl Message for UnsubscribeNamespace<'_> { /// field for both, so the advertisement can carry its HOP_PATH and ROUTE_COST (see /// [`super::cluster`]) and its EPOCH (see [`super::epoch`]). #[derive(Clone, Debug)] -pub struct Namespace<'a> { - pub suffix: Path<'a>, +pub struct Namespace { + pub suffix: Path, /// The extension parameters. `Some` selects the extended form; `None` the base one. /// An endpoint must not append them on a session that negotiated neither extension. pub params: Option, } -impl Namespace<'_> { +impl Namespace { /// Decode the message body, expecting the extended form when the session negotiated /// an extension that defines it. See [`super::PublishNamespace::decode_body`]. pub fn decode_body(r: &mut Decoder<'_>, version: Version, negotiated: Negotiated) -> Result { @@ -310,7 +310,7 @@ impl Namespace<'_> { } } -impl Message for Namespace<'_> { +impl Message for Namespace { const ID: u64 = 0x08; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -329,11 +329,11 @@ impl Message for Namespace<'_> { /// NAMESPACE_DONE message (0x0E): v16 only, sent on SUBSCRIBE_NAMESPACE bidi stream /// Indicates a namespace suffix matching the subscribed prefix is no longer active. #[derive(Clone, Debug)] -pub struct NamespaceDone<'a> { - pub suffix: Path<'a>, +pub struct NamespaceDone { + pub suffix: Path, } -impl Message for NamespaceDone<'_> { +impl Message for NamespaceDone { const ID: u64 = 0x0E; fn encode_msg(&self, w: &mut Encoder<'_>, _: Version) -> Result<(), EncodeError> { @@ -381,7 +381,7 @@ mod tests { bytes: Vec, version: Version, negotiated: Negotiated, - ) -> Result<(Namespace<'static>, bool), DecodeError> { + ) -> Result<(Namespace, bool), DecodeError> { let mut buf = bytes::Bytes::from(bytes); let decoded = crate::coding::decode_buf(&mut buf, version, |r, v| Namespace::decode_body(r, v, negotiated))?; Ok((decoded, buf.is_empty())) diff --git a/rs/moq-net/src/ietf/subscribe_tracks.rs b/rs/moq-net/src/ietf/subscribe_tracks.rs index 96cfe4bed77..89b81c28417 100644 --- a/rs/moq-net/src/ietf/subscribe_tracks.rs +++ b/rs/moq-net/src/ietf/subscribe_tracks.rs @@ -19,9 +19,9 @@ use super::token::RequestToken; /// /// Its subscription parameters are the defaults of each PUBLISH it produces. #[derive(Clone, Debug)] -pub struct SubscribeTracks<'a> { +pub struct SubscribeTracks { pub request_id: RequestId, - pub prefix: Path<'a>, + pub prefix: Path, /// FORWARD: absent means 1, which asks each PUBLISH to push its objects at once. pub forward: bool, /// The default subscriber priority of each PUBLISH subscription. @@ -44,7 +44,7 @@ pub struct SubscribeTracks<'a> { pub authorization_token: Option, } -impl Message for SubscribeTracks<'_> { +impl Message for SubscribeTracks { const ID: u64 = 0x51; fn encode_msg(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { @@ -142,7 +142,7 @@ pub(crate) fn has_subscribe_tracks(version: Version) -> bool { #[derive(Clone, Debug)] pub struct PublishSkipped<'a> { /// The track's namespace below the request's prefix. - pub suffix: Path<'a>, + pub suffix: Path, pub track_name: Cow<'a, str>, } @@ -190,7 +190,7 @@ mod tests { crate::coding::decode_buf(&mut buf, version, M::decode_msg) } - fn paused() -> SubscribeTracks<'static> { + fn paused() -> SubscribeTracks { SubscribeTracks { request_id: RequestId(4), prefix: Path::new("room"), diff --git a/rs/moq-net/src/ietf/subscriber.rs b/rs/moq-net/src/ietf/subscriber.rs index d3da9daee7e..b46f0e5682c 100644 --- a/rs/moq-net/src/ietf/subscriber.rs +++ b/rs/moq-net/src/ietf/subscriber.rs @@ -8,7 +8,7 @@ use std::{ }; use crate::{ - Error, Path, PathOwned, SessionError, Timescale, broadcast, + Error, Path, SessionError, Timescale, broadcast, coding::{Decode, DecodeError, Decoder, Reader, Stream, Writer}, frame, group, ietf::{self, Control, FetchType, Filter, GroupOrder, RequestId}, @@ -202,7 +202,7 @@ struct State { aliases: TrackAliases, // Each broadcast created by a PUBLISH_NAMESPACE message. - broadcasts: HashMap, + broadcasts: HashMap, // Names each advertisement the peer makes, so one that a newer advertisement of the // same namespace replaced cannot overwrite it. @@ -727,7 +727,7 @@ struct TrackState { // it names, it forms the full track name, which is what decides whether a repeated // alias is the fatal collision (one alias, two tracks) or the legal sharing of an alias // across subscriptions. - broadcast: PathOwned, + broadcast: Path, epoch: Option, // Units for this track's object Timestamps, from the TIMESCALE Track Property in @@ -744,13 +744,13 @@ struct TrackState { impl TrackState { #[cfg(test)] - fn new(producer: track::Producer, broadcast: PathOwned, fill: kio::Producer) -> Self { + fn new(producer: track::Producer, broadcast: Path, fill: kio::Producer) -> Self { let mut state = Self::pending(producer.name().to_owned(), broadcast, fill); state.producer = Some(producer); state } - fn pending(name: String, broadcast: PathOwned, fill: kio::Producer) -> Self { + fn pending(name: String, broadcast: Path, fill: kio::Producer) -> Self { Self { producer: None, pending: None, @@ -798,8 +798,8 @@ struct BroadcastState { } /// A namespace request and the authorization selected at dispatch. -struct IncomingNamespace<'a> { - msg: ietf::PublishNamespace<'a>, +struct IncomingNamespace { + msg: ietf::PublishNamespace, peer: cluster::Peer, declared: Option, gate: crate::auth::Gate, @@ -965,13 +965,13 @@ pub(super) struct Subscriber { struct NamespaceRequest { stream: Stream, messages: super::request_stream::Messages, - prefix: PathOwned, + prefix: Path, /// Set once the request goes out. request_id: Option, /// The suffixes this stream advertised, with the advertisement each one holds, so a /// repeat is recognized as an update to it rather than a second one (which would /// leak the refcount). - live: HashMap, + live: HashMap, } /// The initial set of a SUBSCRIBE_NAMESPACE still to read, under MoQ Active Count. @@ -1102,7 +1102,7 @@ impl Subscriber { } /// Serve the requests beneath one attached namespace on its own task. - fn serve_route(&self, path: PathOwned, generation: u64) { + fn serve_route(&self, path: Path, generation: u64) { let this = self.clone(); self.tasks.push(async move { // stop_announce is the authoritative remover: it drops the entry @@ -1324,7 +1324,7 @@ impl Subscriber { async fn run_subscribe_namespace( &mut self, stream: Stream, - prefix: PathOwned, + prefix: Path, snapshot: crate::model::Snapshot, live: kio::Consumer<()>, ) -> Result { @@ -1730,7 +1730,7 @@ impl Subscriber { tracing::debug!(message = ?msg, "received publish_namespace"); let gate = crate::auth::Gate::new( this.auth.clone(), - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::Direction::Subscribe, ); let incoming = IncomingNamespace { @@ -1818,13 +1818,13 @@ impl Subscriber { async fn run_publish_namespace_stream( &mut self, stream: Stream, - msg: ietf::PublishNamespace<'_>, + msg: ietf::PublishNamespace, peer: cluster::Peer, declared: Option, ) -> Result<(), Error> { let gate = crate::auth::Gate::new( self.auth.clone(), - msg.track_namespace.to_owned(), + msg.track_namespace.clone(), crate::auth::Direction::Subscribe, ); self.serve_publish_namespace_stream( @@ -1842,7 +1842,7 @@ impl Subscriber { async fn serve_publish_namespace_stream( &mut self, mut stream: Stream, - incoming: IncomingNamespace<'_>, + incoming: IncomingNamespace, ) -> Result<(), Error> { let IncomingNamespace { msg, @@ -1854,7 +1854,7 @@ impl Subscriber { return super::request_stream::reject(stream, error).await; } let request_id = msg.request_id; - let path = msg.track_namespace.to_owned(); + let path = msg.track_namespace.clone(); if self.unsolicited_is_a_violation(declared) { tracing::warn!(%path, "unsolicited publish_namespace from a peer that implements MoQ Solicit"); @@ -2034,7 +2034,7 @@ impl Subscriber { async fn run_publish_namespace_updates( &mut self, stream: &mut Stream, - path: &PathOwned, + path: &Path, held: Held, peer: cluster::Peer, attached: &mut Option, @@ -2349,7 +2349,7 @@ impl Subscriber { /// Retract an advertisement's route ahead of a withdrawal that waits on the peer, so a peer /// that stops reading cannot keep it routed. `attached` makes it once only. - fn release_announce(&mut self, path: &PathOwned, attached: &mut Option, token: bool) -> Result<(), Error> { + fn release_announce(&mut self, path: &Path, attached: &mut Option, token: bool) -> Result<(), Error> { if let Some(id) = attached.take() { self.stop_announce(path.clone(), id, token)?; } @@ -2362,7 +2362,7 @@ impl Subscriber { &self, stream: &mut Stream, request_id: RequestId, - path: &PathOwned, + path: &Path, err: &Error, reason: &str, ) -> Result<(), Error> { @@ -2517,7 +2517,7 @@ impl Subscriber { /// this advertisement. /// /// Pair with [`Self::stop_announce`]. - fn start_announce(&mut self, path: PathOwned, advert: Advertised, admit: Admit) -> Result { + fn start_announce(&mut self, path: Path, advert: Advertised, admit: Admit) -> Result { let mut state = self.state.lock(); let (token, slot) = match admit { Admit::Session(slot) => (false, slot), @@ -2548,7 +2548,7 @@ impl Subscriber { /// Attach the route of a namespace the limit withheld, returning the generation /// to serve. `None` when it is attached already. - fn reattach(&self, path: &PathOwned, entry: &mut BroadcastState) -> Result, Error> { + fn reattach(&self, path: &Path, entry: &mut BroadcastState) -> Result, Error> { if entry.dynamic.is_some() { return Ok(None); } @@ -2566,7 +2566,7 @@ impl Subscriber { /// /// An update replaces the advertisement atomically: the refcount does not move, /// and no subscription is torn down merely because one arrived. - fn update_announce(&mut self, path: PathOwned, id: u64, advert: Advertised) -> Result<(), Error> { + fn update_announce(&mut self, path: Path, id: u64, advert: Advertised) -> Result<(), Error> { let mut state = self.state.lock(); if !state.broadcasts.contains_key(&path) { return Err(Error::NotFound); @@ -2587,7 +2587,7 @@ impl Subscriber { fn attach( &self, state: &mut State, - path: PathOwned, + path: Path, id: u64, advert: Advertised, slot: Option, @@ -2680,7 +2680,7 @@ impl Subscriber { /// Release advertisement `id` of `path`, closing its sources when it was the last. /// `token` says its own request token authorized it. - fn stop_announce(&mut self, path: PathOwned, id: u64, token: bool) -> Result<(), Error> { + fn stop_announce(&mut self, path: Path, id: u64, token: bool) -> Result<(), Error> { let mut state = self.state.lock(); match state.broadcasts.entry(path.clone()) { @@ -2742,7 +2742,7 @@ impl Subscriber { /// per requested path and serve its track requests until the route is /// retracted or the session dies. Tracks in flight at a retraction run to /// their own end, unless the announce was what authorized them. - async fn run_route(&self, path: PathOwned, generation: u64) { + async fn run_route(&self, path: Path, generation: u64) { let mut broadcasts = TaskSet::owned(); let mut closed_session = self.session.clone(); let mut epoch = 0; @@ -2806,7 +2806,7 @@ impl Subscriber { // The request path is absolute; the wire (and our origin handle) speak // paths relative to the session's root. let requested = match request.path().strip_prefix(self.origin.root()) { - Some(requested) => requested.to_owned(), + Some(requested) => requested.clone(), None => continue, }; // The source serves the instance the route names now: every request for it names @@ -2850,7 +2850,7 @@ impl Subscriber { this.validity = dynamic.validity().clone(); this.epoch = epoch; broadcasts.push(async move { - if let Err(err) = this.run_broadcast(requested.borrow(), source, dynamic, route).await { + if let Err(err) = this.run_broadcast(requested, source, dynamic, route).await { tracing::debug!(%err, "error running broadcast"); } }); @@ -2860,7 +2860,7 @@ impl Subscriber { /// Re-price one attached route to a draining cost (the peer sent a GOAWAY): /// every other candidate outranks it while it stays selectable as the last /// path. Idempotent, since the signal stays set. - fn drain_route(&self, path: &PathOwned) { + fn drain_route(&self, path: &Path) { let mut state = self.state.lock(); let Some(entry) = state.broadcasts.get_mut(path) else { return; @@ -2882,7 +2882,7 @@ impl Subscriber { /// selecting its instance, nothing holds it any more, or the session dies. async fn run_broadcast( &self, - path: Path<'_>, + path: Path, source: crate::model::broadcast::SourceGuard, mut broadcast: broadcast::Dynamic, route: Option>, @@ -2938,7 +2938,7 @@ impl Subscriber { let mut this = self.clone(); - let path = path.to_owned(); + let path = path.clone(); let broadcast = broadcast.clone(); let route = route.clone(); subscribes.push(async move { @@ -2953,7 +2953,7 @@ impl Subscriber { /// it; a failed listing ends its readers, which ask again. fn poll_listing( &self, - path: &Path<'_>, + path: &Path, lister: &std::sync::Arc, listening: &mut Option, waiter: &kio::Waiter, @@ -2969,7 +2969,7 @@ impl Subscriber { lister.abort(Error::Unsupported); continue; } - *listening = Some(self.listen(path.to_owned(), lister.clone())); + *listening = Some(self.listen(path.clone(), lister.clone())); } Some(_) if lister.poll_unused(waiter).is_ready() => { *listening = None; @@ -2988,7 +2988,7 @@ impl Subscriber { async fn run_subscribe( &mut self, - broadcast_path: Path<'_>, + broadcast_path: Path, // Held for the subscription's lifetime but never watched: the broadcast ending // is a retraction, which does not disturb a subscription the scope allows. _broadcast: broadcast::Dynamic, @@ -3003,7 +3003,7 @@ impl Subscriber { // does, leaving the rest of the session alone. let (auth, path, direction) = ( self.auth.clone(), - broadcast_path.to_owned(), + broadcast_path.clone(), crate::auth::Direction::Subscribe, ); let mut gate = match route { @@ -3079,7 +3079,7 @@ impl Subscriber { /// `None` once it failed. async fn subscribe_once( &mut self, - broadcast_path: &Path<'_>, + broadcast_path: &Path, track_name: &str, target: Target, group_fetches: &mut TaskSet, @@ -3101,7 +3101,7 @@ impl Subscriber { // A listed track's paused subscription is already this track's upstream copy, so // resume it rather than open a second one. - let key: tracks::TrackKey = (broadcast_path.to_owned(), track_name.into()); + let key: tracks::TrackKey = (broadcast_path.clone(), track_name.into()); let adopted = match &self.want(&key) { None => None, Some(handoff) => { @@ -3214,7 +3214,7 @@ impl Subscriber { epoch: self.epoch.clone(), alias, tail, - ..TrackState::pending(track_name.to_owned(), broadcast_path.to_owned(), fill.clone()) + ..TrackState::pending(track_name.to_owned(), broadcast_path.clone(), fill.clone()) }, ); } @@ -3256,7 +3256,7 @@ impl Subscriber { max_age, new_group, request_id, - track_namespace: broadcast_path.to_owned(), + track_namespace: broadcast_path.clone(), track_name: track_name.into(), subscriber_priority: super::priority::to_wire(priority), group_order: order.into(), @@ -3765,7 +3765,7 @@ impl Subscriber { } End::Fill(requested) => { let fetch = self.clone().serve_fill( - broadcast_path.to_owned(), + broadcast_path.clone(), track_name.to_owned(), requested, timescale, @@ -3915,7 +3915,7 @@ impl Subscriber { /// it through the linger, or the session ended. A finished copy never subscribes again. async fn linger( &mut self, - broadcast_path: &Path<'_>, + broadcast_path: &Path, track_name: &str, mut idle: Idle, group_fetches: &mut TaskSet, @@ -3976,7 +3976,7 @@ impl Subscriber { Step::Fill(fill) => { let fetch = self.clone() - .serve_fill(broadcast_path.to_owned(), track_name.to_owned(), fill, idle.timescale); + .serve_fill(broadcast_path.clone(), track_name.to_owned(), fill, idle.timescale); group_fetches.push(fetch); } Step::Subscribe => break true, @@ -4351,7 +4351,7 @@ impl Subscriber { /// refused or abandoned: a refused status fails the query-only or fetch-only request. async fn track_status( &mut self, - broadcast_path: &Path<'_>, + broadcast_path: &Path, track_name: &str, request: track::Request, gate: &mut crate::auth::Gate, @@ -4396,7 +4396,7 @@ impl Subscriber { .writer .encode_message(&ietf::TrackStatus { request_id, - track_namespace: broadcast_path.to_owned(), + track_namespace: broadcast_path.clone(), track_name: track_name.into(), properties_wanted: true, epoch: self.epoch.clone(), @@ -5389,7 +5389,7 @@ impl Subscriber { /// group always goes alone: a run including the group its source is still writing would /// hold the earlier groups until it finishes, or be cut short at the Largest Object by a /// publisher following draft-22 section 9.11. - async fn serve_fill(self, broadcast: PathOwned, name: String, fill: track::Fill, timescale: Option) { + async fn serve_fill(self, broadcast: Path, name: String, fill: track::Fill, timescale: Option) { let fill = std::sync::Arc::new(fill); let mut last = None; while fill.is_used() @@ -5425,7 +5425,7 @@ impl Subscriber { #[cfg(test)] async fn run_group_fetch( self, - broadcast: PathOwned, + broadcast: Path, name: String, fill: track::Fill, timescale: Option, @@ -5445,7 +5445,7 @@ impl Subscriber { /// fetch stream, which [`Self::recv_fill`] routes to [`Self::recv_group_fetch`]. async fn run_range_fetch( self, - broadcast: PathOwned, + broadcast: Path, name: String, fill: std::sync::Arc, run: std::ops::Range, @@ -6435,7 +6435,7 @@ mod tests { pending: Some(request), ..TrackState::pending( "video".to_string(), - crate::Path::new("bcast").to_owned(), + crate::Path::new("bcast").clone(), kio::Producer::new(Fill::Done), ) }, @@ -6588,7 +6588,7 @@ mod tests { let mut namespaces = crate::model::interest_prefixes(&subscriber.origin.allowed()); assert_eq!( namespaces, - vec![crate::Path::new("cam").to_owned()], + vec![crate::Path::new("cam").clone()], "one SUBSCRIBE_NAMESPACE per permitted prefix, relative to the root", ); let prefix = namespaces.pop().unwrap(); @@ -6946,7 +6946,7 @@ mod tests { let res = subscriber .run_subscribe_namespace( stream, - crate::Path::new("").to_owned(), + crate::Path::new("").clone(), crate::model::Snapshot::landed(), live, ) @@ -7049,7 +7049,7 @@ mod tests { .unwrap(); let mut run = std::pin::pin!(subscriber.run_subscribe_namespace( stream, - crate::Path::new("").to_owned(), + crate::Path::new("").clone(), snapshot, live )); @@ -7077,7 +7077,7 @@ mod tests { .await .unwrap(); let mut run = - std::pin::pin!(subscriber.run_subscribe_namespace(stream, crate::Path::new("").to_owned(), snapshot, live)); + std::pin::pin!(subscriber.run_subscribe_namespace(stream, crate::Path::new("").clone(), snapshot, live)); for _ in 0..10 { assert!(futures::poll!(run.as_mut()).is_pending()); settle().await; @@ -7101,7 +7101,7 @@ mod tests { .await .unwrap(); let res = subscriber - .run_subscribe_namespace(stream, crate::Path::new("").to_owned(), snapshot, live) + .run_subscribe_namespace(stream, crate::Path::new("").clone(), snapshot, live) .await; assert!(matches!(res, Err(Error::ProtocolViolation)), "{entries:?}: {res:?}"); } @@ -7223,7 +7223,7 @@ mod tests { *name, crate::track::Info::timed(), ), - Path::new(broadcast).to_owned(), + Path::new(broadcast).clone(), kio::Producer::new(Fill::Done), ), ); @@ -7941,7 +7941,7 @@ mod tests { subscriber.validity = crate::model::broadcast::RequestValidity::token(Some(route.consume())); let mut gate = crate::auth::Gate::new( auth.clone(), - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), crate::auth::Direction::Subscribe, ); let waiter = kio::Waiter::noop(); @@ -7988,7 +7988,7 @@ mod tests { group = Some(pending); accepted = Some(track); Box::pin(subscriber.clone().run_group_fetch( - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), "video".into(), request, None, @@ -8158,7 +8158,7 @@ mod tests { Allocated::TrackStatus => moq_clock::sim::spawn(async move { let mut gate = crate::auth::Gate::new( subscriber.auth.clone(), - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), crate::auth::Direction::Subscribe, ); assert!( @@ -8180,7 +8180,7 @@ mod tests { let group = requested.requested_fill().await.expect("group request"); fetch = Some((track, pending)); moq_clock::sim::spawn(subscriber.run_group_fetch( - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), "video".into(), group, None, @@ -8563,7 +8563,7 @@ mod tests { "video", crate::track::Info::timed(), ), - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), kio::Producer::new(Fill::Done), ) }, @@ -9083,8 +9083,8 @@ mod tests { subscriber.announces = crate::session::Slots::new(1); let advert = |subscriber: &Subscriber| subscriber.route(None, &cluster::Peer::default()).expect("route"); - let a = crate::Path::new("a").to_owned(); - let b = crate::Path::new("b").to_owned(); + let a = crate::Path::new("a").clone(); + let b = crate::Path::new("b").clone(); let (first, repeat, over, last) = ( advert(&subscriber), advert(&subscriber), @@ -9141,7 +9141,7 @@ mod tests { let advert = subscriber.route(None, &cluster::Peer::default()).expect("route"); subscriber - .start_announce(crate::Path::new("room/host").to_owned(), advert, Admit::Session(None)) + .start_announce(crate::Path::new("room/host").clone(), advert, Admit::Session(None)) .unwrap(); let mut announced = consumer.announced(); @@ -9201,7 +9201,7 @@ mod tests { ); let advert = subscriber.route(None, &cluster::Peer::default()).expect("route"); subscriber - .start_announce(crate::Path::new("room/host").to_owned(), advert, Admit::Session(None)) + .start_announce(crate::Path::new("room/host").clone(), advert, Admit::Session(None)) .unwrap(); // No announce churn: the upstream route stays the best one, on both cursors. @@ -9241,7 +9241,7 @@ mod tests { ); let advert = subscriber.route(None, &cluster::Peer::default()).expect("route"); subscriber - .start_announce(crate::Path::new("room/host").to_owned(), advert, Admit::Session(None)) + .start_announce(crate::Path::new("room/host").clone(), advert, Admit::Session(None)) .unwrap(); subscriber }; @@ -9330,7 +9330,7 @@ mod tests { let mut subscriber = subscriber; subscriber .start_announce( - crate::Path::new("room/host").to_owned(), + crate::Path::new("room/host").clone(), advertised, Admit::Session(None), ) @@ -9434,7 +9434,7 @@ mod tests { subscriber .run_subscribe_namespace( stream, - crate::Path::new("").to_owned(), + crate::Path::new("").clone(), crate::model::Snapshot::landed(), live, ) @@ -9524,7 +9524,7 @@ mod tests { .unwrap(); let mut run = std::pin::pin!(subscriber.run_subscribe_namespace( stream, - crate::Path::new("").to_owned(), + crate::Path::new("").clone(), crate::model::Snapshot::landed(), live )); @@ -9550,7 +9550,7 @@ mod tests { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let advert = subscriber.route(None, &cluster::Peer::default()).expect("route"); let id = subscriber .start_announce(path.clone(), advert, Admit::Session(None)) @@ -9623,7 +9623,7 @@ mod tests { async fn a_publish_namespace_done_retracts_without_faulting_the_session() { const VERSION: Version = Version::Draft14; - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let log = crate::lite::test_transport::Log::default(); let mut writer = crate::coding::Writer::new( crate::transport::boxed::SendStream::new(crate::lite::test_transport::SinkSend::new(log.clone())), @@ -9631,7 +9631,7 @@ mod tests { ); writer .encode_message(&ietf::PublishNamespaceDone { - track_namespace: path.borrow(), + track_namespace: path.clone(), request_id: RequestId(0), }) .await @@ -9662,7 +9662,7 @@ mod tests { .unwrap(); let msg = ietf::PublishNamespace { request_id: RequestId(0), - track_namespace: path.borrow(), + track_namespace: path.clone(), params: Default::default(), authorization_token: None, }; @@ -9684,7 +9684,7 @@ mod tests { #[moq_clock::test] async fn a_legacy_update_on_an_advertisement_is_answered() { for version in [Version::Draft14, Version::Draft15] { - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); // Own Request ID 10, aimed at the advertisement (0). Draft 14 carries its fixed // start, end group, priority and forward before the parameters. let body: &[u8] = match version { @@ -9700,7 +9700,7 @@ mod tests { ); writer .encode_message(&ietf::PublishNamespaceDone { - track_namespace: path.borrow(), + track_namespace: path.clone(), request_id: RequestId(0), }) .await @@ -9730,7 +9730,7 @@ mod tests { .unwrap(); let msg = ietf::PublishNamespace { request_id: RequestId(0), - track_namespace: path.borrow(), + track_namespace: path.clone(), params: Default::default(), authorization_token: None, }; @@ -9797,13 +9797,13 @@ mod tests { Default::default(), ); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let stream = Stream::open(&mut crate::driver::Transport::new(session.clone()), VERSION) .await .unwrap(); let msg = ietf::PublishNamespace { request_id: RequestId(0), - track_namespace: path.borrow(), + track_namespace: path.clone(), params: Default::default(), authorization_token: None, }; @@ -9832,7 +9832,7 @@ mod tests { async fn the_last_owner_out_decides_the_detach() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let peer = cluster::Peer::default(); let mut ids = Vec::new(); @@ -9929,7 +9929,7 @@ mod tests { async fn cluster_update_replaces_in_place() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let first = Advertised { route: crate::origin::Route::default() @@ -9970,7 +9970,7 @@ mod tests { async fn anonymous_publisher_survives_a_repricing_update() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); // A free link, so the route cost is exactly what the peer advertised. let peer = cluster::Peer { hop: Some(crate::Hop::new(9).unwrap()), @@ -10020,7 +10020,7 @@ mod tests { async fn a_reordered_replacement_keeps_the_new_instance() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let peer = cluster::Peer::default(); let (old, new) = (crate::Epoch::mint(), crate::Epoch::mint()); let advert = |subscriber: &Subscriber, epoch: &crate::Epoch| { @@ -10068,7 +10068,7 @@ mod tests { async fn a_repeat_under_another_epoch_becomes_the_newest() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let peer = cluster::Peer::default(); let (old, new) = (crate::Epoch::mint(), crate::Epoch::mint()); let advert = |subscriber: &Subscriber, epoch: &crate::Epoch| { @@ -10110,7 +10110,7 @@ mod tests { async fn separate_adverts_refcount_the_route() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let peer = cluster::Peer { hop: Some(crate::Hop::new(9).unwrap()), cost: None, @@ -10148,7 +10148,7 @@ mod tests { async fn pathless_adverts_never_replace_the_source() { let (mut subscriber, origin) = cluster_subscriber(crate::Hop::new(1).unwrap()); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let peer = cluster::Peer::default(); // What a PUBLISH_NAMESPACE with no cluster parameters resolves to. @@ -10180,7 +10180,7 @@ mod tests { let self_origin = crate::Hop::new(5).unwrap(); let (mut subscriber, origin) = cluster_subscriber(self_origin); let consumer = origin.consume(); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let peer = cluster::Peer { hop: Some(crate::Hop::new(9).unwrap()), cost: None, @@ -10280,7 +10280,7 @@ mod tests { Default::default(), ); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let advert = subscriber.route(Some(attached), peer).expect("route"); let id = subscriber.start_announce(path, advert, Admit::Session(None)).unwrap(); assert_eq!(id, 0, "the harness advertisement is the first"); @@ -10396,7 +10396,7 @@ mod tests { let (mut subscriber, consumer, mut stream, log) = reflected_harness(self_origin, &peer, &clean, &[looped]).await; - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let mut attached = Some(0); { let mut run = std::pin::pin!(subscriber.run_publish_namespace_updates( @@ -10446,7 +10446,7 @@ mod tests { let (mut subscriber, consumer, mut stream, _log) = reflected_harness(self_origin, &peer, &clean, &[looped, clean.clone()]).await; - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let mut attached = Some(0); { let mut run = std::pin::pin!(subscriber.run_publish_namespace_updates( @@ -10509,7 +10509,7 @@ mod tests { subscriber.announces = crate::session::Slots::new(1); // Admitted by its own token, which holds the one slot for the request's life. - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let hold: crate::auth::Hold = std::sync::Arc::new(subscriber.announces.acquire().unwrap()); let auth = crate::auth::Link::new(true); let mut requests = auth.handle().requests(); @@ -10610,7 +10610,7 @@ mod tests { std::mem::forget(driver); assert_eq!(routed_now(&consumer, "room/host").expect("routed").cost.value(), 4); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let mut attached = Some(0); { let mut run = std::pin::pin!(subscriber.run_publish_namespace_updates( @@ -10661,7 +10661,7 @@ mod tests { // repriced, which is the one way an in-place update fails. drop(driver); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let mut attached = Some(0); let mut result = None; { @@ -10716,7 +10716,7 @@ mod tests { update_harness(self_origin, &peer, &clean, script).await; std::mem::forget(driver); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let mut attached = Some(0); { let mut run = std::pin::pin!(subscriber.run_publish_namespace_updates( @@ -10786,7 +10786,7 @@ mod tests { update_harness(self_origin, &peer, &clean, script).await; std::mem::forget(driver); - let path = crate::Path::new("room/host").to_owned(); + let path = crate::Path::new("room/host").clone(); let mut attached = Some(0); let mut run = std::pin::pin!(subscriber.run_publish_namespace_updates( &mut stream, @@ -10824,7 +10824,7 @@ mod tests { let mut live = std::collections::HashMap::new(); for path in ["room/a", "room/b"] { - let path = crate::Path::new(path).to_owned(); + let path = crate::Path::new(path).clone(); let advert = subscriber.route(None, &peer).expect("route"); let id = subscriber .start_announce(path.clone(), advert, Admit::Session(None)) @@ -11045,7 +11045,7 @@ mod tests { }) } - fn token_publish_namespace() -> ietf::PublishNamespace<'static> { + fn token_publish_namespace() -> ietf::PublishNamespace { ietf::PublishNamespace { request_id: RequestId(1), track_namespace: crate::Path::new("room/alice"), @@ -13246,7 +13246,7 @@ mod stitch_tests { TrackState { alias: Some(ALIAS), timescale, - ..TrackState::new(track.clone(), Path::new("broadcast").to_owned(), fill.clone()) + ..TrackState::new(track.clone(), Path::new("broadcast").clone(), fill.clone()) }, ); insert_track_alias(&state.aliases, ALIAS, REQUEST).unwrap(); @@ -16316,7 +16316,7 @@ mod joining_fetch_tests { RequestId(99), TrackState::new( track.clone(), - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), kio::Producer::new(Fill::Requested), ), ); @@ -16829,7 +16829,7 @@ mod joining_fetch_tests { let request = dynamic.requested_fill().await.expect("group request"); let outcome = request.joined(); let mut run = Box::pin(subscriber.clone().run_group_fetch( - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), "video".into(), request, None, @@ -17108,7 +17108,7 @@ mod joining_fetch_tests { subscriber .clone() - .run_group_fetch(Path::new("broadcast").to_owned(), "video".into(), request, None) + .run_group_fetch(Path::new("broadcast").clone(), "video".into(), request, None) .await; assert!(fetch.await.is_err(), "the accepted group was aborted"); @@ -17180,7 +17180,7 @@ mod joining_fetch_tests { subscriber .clone() - .run_group_fetch(Path::new("broadcast").to_owned(), "video".into(), request, None) + .run_group_fetch(Path::new("broadcast").clone(), "video".into(), request, None) .await; assert_eq!( @@ -17321,7 +17321,7 @@ mod joining_fetch_tests { let request = dynamic.requested_fill().await.expect("no group requested"); let serving = moq_clock::sim::spawn(subscriber.clone().run_group_fetch( - Path::new("broadcast").to_owned(), + Path::new("broadcast").clone(), "video".into(), request, None, diff --git a/rs/moq-net/src/ietf/subscriber/namespaces.rs b/rs/moq-net/src/ietf/subscriber/namespaces.rs index 15b8f03f347..1e855d4bc1a 100644 --- a/rs/moq-net/src/ietf/subscriber/namespaces.rs +++ b/rs/moq-net/src/ietf/subscriber/namespaces.rs @@ -26,7 +26,7 @@ pub(super) enum Ended { /// A request from when discovery opens it until it is off the wire. struct Request { - prefix: PathOwned, + prefix: Path, /// Dropped to end it; `None` once the session did. live: Option>, /// Closes once it is off the wire, so an overlapping one may go out. @@ -59,7 +59,7 @@ impl Subscriber { }; let mut requests: HashMap = HashMap::new(); // The request discovery holds open for each prefix. - let mut open_at: HashMap = HashMap::new(); + let mut open_at: HashMap = HashMap::new(); let mut running = futures::stream::FuturesUnordered::new(); let mut next = 0u64; @@ -147,7 +147,7 @@ impl Subscriber { async fn namespace_request( mut self, key: u64, - prefix: PathOwned, + prefix: Path, snapshot: crate::model::Snapshot, pending: Pending, ) -> (u64, Result) diff --git a/rs/moq-net/src/ietf/subscriber/tracks.rs b/rs/moq-net/src/ietf/subscriber/tracks.rs index 8c4a037dbf3..12bd3fea094 100644 --- a/rs/moq-net/src/ietf/subscriber/tracks.rs +++ b/rs/moq-net/src/ietf/subscriber/tracks.rs @@ -22,11 +22,10 @@ use crate::driver::Transport; use std::sync::Arc; use super::*; -use crate::AsPath as _; use crate::ietf::request_stream::{self, Framed, Messages}; /// A listed track, by its namespace and name. -pub(super) type TrackKey = (PathOwned, Arc); +pub(super) type TrackKey = (Path, Arc); /// How a PUBLISH_DONE leaves the listing: only TRACK_ENDED removes the track. Any other /// status, INTERNAL_ERROR included, fails the listing, so it asks again rather than hide a @@ -89,7 +88,7 @@ impl Drop for Entry { /// A broadcast listed through this session. struct Listener { - path: PathOwned, + path: Path, lister: Arc, /// Set to the error that fails the listing. failed: kio::Producer>, @@ -97,7 +96,7 @@ struct Listener { /// One SUBSCRIBE_TRACKS, from when it is wanted until the peer has closed it. struct Request { - prefix: PathOwned, + prefix: Path, /// Dropped to cancel it; `None` once cancelled, while it leaves the wire. live: Option>, /// Closes once it is off the wire, so an overlapping one may go out. @@ -116,7 +115,7 @@ pub(super) struct Listings { /// A SUBSCRIBE_TRACKS for its task to send. struct Opening { key: u64, - prefix: PathOwned, + prefix: Path, /// The overlapping requests that must leave the wire first. blockers: Vec>, live: kio::Consumer<()>, @@ -130,14 +129,14 @@ impl Listings { } /// The standing request that covers `namespace`; at most one does. - fn covering(&self, namespace: &PathOwned) -> Option { + fn covering(&self, namespace: &Path) -> Option { self.requests .iter() .find(|(_, request)| request.live.is_some() && namespace.has_prefix(&request.prefix)) .map(|(key, _)| *key) } - fn listers<'a>(&'a self, namespace: &'a PathOwned) -> impl Iterator + 'a { + fn listers<'a>(&'a self, namespace: &'a Path) -> impl Iterator + 'a { self.listeners .values() .filter(move |listener| &listener.path == namespace) @@ -146,7 +145,7 @@ impl Listings { fn listen( &mut self, - path: PathOwned, + path: Path, lister: Arc, ) -> (u64, kio::Consumer>, Option) { // What a broader listing already holds is listed at once. @@ -224,7 +223,7 @@ impl Listings { request.live = None; } } - let standing: Vec = self + let standing: Vec = self .requests .values() .filter(|request| request.live.is_some()) @@ -236,7 +235,7 @@ impl Listings { /// Fail every listing the request covering `namespace` serves with `err`, and cancel it: /// they ask again rather than miss a track that may still be live. - fn fail(&mut self, namespace: &PathOwned, err: Error) { + fn fail(&mut self, namespace: &Path, err: Error) { let Some(key) = self.covering(namespace) else { return; }; @@ -348,7 +347,7 @@ enum Offer { impl Subscriber { /// List `path`'s tracks into `lister` until the returned guard drops, through a /// SUBSCRIBE_TRACKS covering it. - pub(super) fn listen(&self, path: PathOwned, lister: Arc) -> Listening { + pub(super) fn listen(&self, path: Path, lister: Arc) -> Listening { let (key, failed, opening) = self.listings.lock().listen(path, lister); if let Some(opening) = opening { let this = self.clone(); @@ -390,7 +389,7 @@ impl Subscriber { async fn subscribe_tracks( &self, - prefix: &PathOwned, + prefix: &Path, blockers: Vec>, live: &kio::Consumer<()>, ) -> Result<(), Error> { @@ -433,7 +432,7 @@ impl Subscriber { ietf::SubscribeTracks::ID, &ietf::SubscribeTracks { request_id, - prefix: prefix.as_path(), + prefix: prefix.clone(), forward: false, subscriber_priority: None, group_order: None, @@ -533,7 +532,7 @@ impl Subscriber { } /// A fresh subscription of ours to `name` in `namespace`, and its info once accepted. - fn subscribed(&self, namespace: &PathOwned, name: &str) -> Option<(RequestId, Option)> { + fn subscribed(&self, namespace: &Path, name: &str) -> Option<(RequestId, Option)> { let state = self.state.lock(); let aliases = state.aliases.read(); // A paused one is on its way out with the entry it held. @@ -551,7 +550,7 @@ impl Subscriber { /// The peer skipped a listed track: list it through a subscription of ours, opening /// one paused if we have none. - fn skipped(&self, namespace: PathOwned, name: Arc) { + fn skipped(&self, namespace: Path, name: Arc) { let key = (namespace, name); let mut listings = self.listings.lock(); if listings.entries.contains_key(&key) || listings.covering(&key.0).is_none() { @@ -638,7 +637,7 @@ impl Subscriber { max_age: None, new_group: None, request_id, - track_namespace: key.0.as_path(), + track_namespace: key.0.clone(), track_name: key.1.as_ref().into(), subscriber_priority: super::super::priority::DEFAULT_WIRE, // No update can change it, so it takes the order a reader gets by default. @@ -749,7 +748,7 @@ impl Subscriber { mut stream: Stream, msg: ietf::Publish<'_>, ) -> Result<(), Error> { - let key: TrackKey = (msg.track_namespace.to_owned(), msg.track_name.as_ref().into()); + let key: TrackKey = (msg.track_namespace.clone(), msg.track_name.as_ref().into()); let info = accepted_info(&msg.properties, self.version); let flate = self.peer_setup.get().await.flate; let offer = { @@ -1280,7 +1279,7 @@ mod tests { let broadcast = crate::broadcast::Info::default().produce(); let lister = Arc::new(broadcast.dynamic().lister().unwrap()); let tracks = broadcast.consume().tracks().unwrap(); - let listening = subscriber.listen(PathOwned::from("bcast"), lister); + let listening = subscriber.listen(Path::from("bcast"), lister); Harness { subscriber, peer, diff --git a/rs/moq-net/src/ietf/track.rs b/rs/moq-net/src/ietf/track.rs index fbe67a8fb7c..cebab706e36 100644 --- a/rs/moq-net/src/ietf/track.rs +++ b/rs/moq-net/src/ietf/track.rs @@ -21,7 +21,7 @@ use super::Version; #[derive(Clone, Debug)] pub struct TrackStatus<'a> { pub request_id: RequestId, - pub track_namespace: Path<'a>, + pub track_namespace: Path, pub track_name: Cow<'a, str>, /// Whether the requester wants Track Properties on the answer (INCLUDE_PROPERTIES, /// draft-22). diff --git a/rs/moq-net/src/lib.rs b/rs/moq-net/src/lib.rs index 9cb5a4c8f1b..4c0b6951a00 100644 --- a/rs/moq-net/src/lib.rs +++ b/rs/moq-net/src/lib.rs @@ -140,7 +140,7 @@ pub use driver::Driver; pub use epoch::{Epoch, InvalidEpoch}; pub use error::*; pub use model::*; -pub use path::{AsPath, InvalidPattern, Path, PathOwned, Pattern, Patterns}; +pub use path::{InvalidPattern, Path, Pattern, Patterns}; #[cfg(feature = "async")] pub use server::Server; #[cfg(feature = "async")] diff --git a/rs/moq-net/src/lite/announce.rs b/rs/moq-net/src/lite/announce.rs index 834ed8423ac..2f17fe7d082 100644 --- a/rs/moq-net/src/lite/announce.rs +++ b/rs/moq-net/src/lite/announce.rs @@ -44,19 +44,19 @@ pub fn update_supported(version: Version) -> bool { /// base announcement, resolved against the stream's history by /// [`AnnounceDecoder`](super::AnnounceDecoder). #[derive(Clone, Debug, PartialEq, Eq)] -pub enum AnnounceBroadcast<'a> { +pub enum AnnounceBroadcast { /// ANNOUNCE_START (lite-06) / active (older): a broadcast is now available. /// Carries the path suffix, the hop chain, and (lite-06+) the static route /// cost, and assigns the next announce id. The epoch (lite-07+) changes only /// with a `Restart`. Active { - suffix: PathRef<'a>, + suffix: PathRef, epoch: Option, hops: HopsRef, cost: Cost, }, /// Pre-lite-06: a broadcast is no longer available, retracted by path. - Ended { suffix: Path<'a>, hops: Hops }, + Ended { suffix: Path, hops: Hops }, /// ANNOUNCE_END (lite-06+): a broadcast is no longer available, retracted by /// announce id. The id is retired; referencing it again is a protocol violation. EndedId { id: u64 }, @@ -87,20 +87,20 @@ pub enum AnnounceBroadcast<'a> { /// so 1 is the latest ANNOUNCE_START, and 0 names no base (`keep` must be 0 too). Only /// lite-07 carries a base; every other version is `rest` alone. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct PathRef<'a> { +pub struct PathRef { pub base: u64, pub keep: u64, - pub rest: Path<'a>, + pub rest: Path, } -impl<'a> PathRef<'a> { +impl<'a> PathRef { /// The whole suffix, with no base. - pub fn literal(rest: Path<'a>) -> Self { + pub fn literal(rest: Path) -> Self { Self { base: 0, keep: 0, rest } } } -impl Encode for PathRef<'_> { +impl Encode for PathRef { fn encode(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { if version.has_announce_compression() { w.varint(self.base)?; @@ -112,7 +112,7 @@ impl Encode for PathRef<'_> { } } -impl Decode for PathRef<'_> { +impl Decode for PathRef { fn decode(buf: &mut Decoder<'_>, version: Version) -> Result { if !version.has_announce_compression() { return Ok(Self::literal(Path::decode(buf, version)?)); @@ -179,39 +179,6 @@ impl Decode for HopsRef { } } -impl AnnounceBroadcast<'_> { - /// Re-own a decoded message so it can outlive the decode buffer. - #[cfg(test)] - pub fn into_owned(self) -> AnnounceBroadcast<'static> { - match self { - Self::Active { - suffix, - epoch, - hops, - cost, - } => AnnounceBroadcast::Active { - suffix: PathRef { - base: suffix.base, - keep: suffix.keep, - rest: suffix.rest.into_owned(), - }, - epoch, - hops, - cost, - }, - Self::Ended { suffix, hops } => AnnounceBroadcast::Ended { - suffix: suffix.into_owned(), - hops, - }, - Self::EndedId { id } => AnnounceBroadcast::EndedId { id }, - Self::Update { id, hops, cost } => AnnounceBroadcast::Update { id, hops, cost }, - Self::Restart { id, epoch, hops, cost } => AnnounceBroadcast::Restart { id, epoch, hops, cost }, - Self::Skipped => AnnounceBroadcast::Skipped, - Self::Synced => AnnounceBroadcast::Synced, - } - } -} - impl Encode for Cost { fn encode(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { if !version.has_route_cost() { @@ -242,7 +209,7 @@ impl Decode for Cost { } } -impl Encode for AnnounceBroadcast<'_> { +impl Encode for AnnounceBroadcast { fn encode(&self, w: &mut Encoder<'_>, version: Version) -> Result<(), EncodeError> { w.control = Some(crate::stats::Control::Announce); if version.has_announce_id() { @@ -331,7 +298,7 @@ impl Encode for AnnounceBroadcast<'_> { } } -impl Decode for AnnounceBroadcast<'_> { +impl Decode for AnnounceBroadcast { fn decode(buf: &mut Decoder<'_>, version: Version) -> Result { buf.control = Some(crate::stats::Control::Announce); if version.has_announce_id() { @@ -383,7 +350,7 @@ impl Decode for AnnounceBroadcast<'_> { } } -impl AnnounceBroadcast<'_> { +impl AnnounceBroadcast { /// Decode the body of a pre-lite-06 ANNOUNCE_BROADCAST (inner status + path + hops). fn decode_legacy(r: &mut Decoder<'_>, version: Version) -> Result { let status = AnnounceStatus::decode(r, version)?; @@ -442,9 +409,9 @@ fn encode_hops(w: &mut Encoder<'_>, version: Version, hops: &Hops) -> Result<(), /// ANNOUNCE_REQUEST: sent by the subscriber to request ANNOUNCE_BROADCAST messages /// for a path prefix. Renamed from ANNOUNCE_INTEREST in lite-05. #[derive(Clone, Debug)] -pub struct AnnounceRequest<'a> { +pub struct AnnounceRequest { // Request tracks with this prefix. - pub prefix: Path<'a>, + pub prefix: Path, // Lite04/05 only: unless unknown, the publisher SHOULD skip announces whose hop IDs // contain this value. Not on the wire elsewhere, so the value set here is ignored // when encoding for another version and decodes as unknown; lite-06 carries the @@ -456,7 +423,7 @@ pub struct AnnounceRequest<'a> { pub hidden: bool, } -impl Message for AnnounceRequest<'_> { +impl Message for AnnounceRequest { const CONTROL: Option = Some(crate::stats::Control::Announce); fn decode_msg(r: &mut Decoder<'_>, version: Version) -> Result { let prefix = Path::decode(r, version)?; @@ -520,12 +487,12 @@ impl Encode for AnnounceStatus { /// /// Used by Draft01/Draft02 only. Draft03 uses individual Announce messages instead. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct AnnounceInit<'a> { +pub struct AnnounceInit { /// List of currently active broadcasts, encoded as suffixes to be combined with the prefix. - pub suffixes: Vec>, + pub suffixes: Vec, } -impl AnnounceInit<'_> { +impl AnnounceInit { /// Decode one size-prefixed ANNOUNCE_INIT, refusing one that lists more than `budget` /// suffixes before decoding any, so the set grows no larger than what may be announced. pub(crate) fn decode_within(r: &mut Decoder<'_>, version: Version, budget: usize) -> Result { @@ -558,7 +525,7 @@ impl AnnounceInit<'_> { } } -impl Message for AnnounceInit<'_> { +impl Message for AnnounceInit { const CONTROL: Option = Some(crate::stats::Control::Announce); // Published lite01/02 carry the whole initial set in one message. const MAX_SIZE: usize = 64 * 1024 * 1024; @@ -766,14 +733,14 @@ mod tests { assert_eq!(round_trip(&msg), msg); } - fn broadcast_round_trip(msg: &AnnounceBroadcast, version: Version) -> AnnounceBroadcast<'static> { + fn broadcast_round_trip(msg: &AnnounceBroadcast, version: Version) -> AnnounceBroadcast { let mut buf = Vec::new(); msg.encode(&mut Encoder::new(&mut buf, version.into()), version) .unwrap(); let mut slice = &buf[..]; let got = crate::coding::decode_buf(&mut slice, version, AnnounceBroadcast::decode).unwrap(); assert!(slice.is_empty(), "trailing bytes after decode"); - got.into_owned() + got } #[test] @@ -1042,7 +1009,7 @@ mod tests { assert_eq!(buf.len(), 3); } - fn request_round_trip(msg: &AnnounceRequest, version: Version) -> AnnounceRequest<'static> { + fn request_round_trip(msg: &AnnounceRequest, version: Version) -> AnnounceRequest { let mut buf = Vec::new(); msg.encode(&mut Encoder::new(&mut buf, version.into()), version) .unwrap(); @@ -1050,7 +1017,7 @@ mod tests { let got = crate::coding::decode_buf(&mut slice, version, AnnounceRequest::decode).unwrap(); assert!(slice.is_empty(), "trailing bytes after decode"); AnnounceRequest { - prefix: got.prefix.to_owned(), + prefix: got.prefix.clone(), exclude_hop: got.exclude_hop, hidden: got.hidden, } diff --git a/rs/moq-net/src/lite/compress.rs b/rs/moq-net/src/lite/compress.rs index 282a898da88..fa42eb8dede 100644 --- a/rs/moq-net/src/lite/compress.rs +++ b/rs/moq-net/src/lite/compress.rs @@ -14,7 +14,7 @@ use std::{ }; use crate::{ - Error, Hop, Hops, Path, PathOwned, + Error, Hop, Hops, Path, coding::{Form, varint}, }; @@ -24,7 +24,7 @@ use super::{HopsRef, PathRef, Version}; /// (the one excluding ANNOUNCE_OK's Hop ID). #[derive(Clone, Debug)] struct Entry { - suffix: PathOwned, + suffix: Path, hops: Hops, } @@ -45,9 +45,9 @@ impl Live { self.entries.get(&id).map(Some).ok_or(Error::ProtocolViolation) } - fn resolve_path(&self, path: PathRef<'_>) -> Result { + fn resolve_path(&self, path: PathRef) -> Result { let Some(base) = self.base(path.base)? else { - return Ok(path.rest.into_owned()); + return Ok(path.rest); }; let keep = usize::try_from(path.keep).map_err(|_| Error::ProtocolViolation)?; let head = head(&base.suffix, keep).ok_or(Error::ProtocolViolation)?; @@ -74,7 +74,7 @@ impl Live { } /// The first `keep` segments of `path`, or `None` if it has fewer. -fn head<'a>(path: &'a Path<'_>, keep: usize) -> Option> { +fn head<'a>(path: &'a Path, keep: usize) -> Option { if keep == 0 { return Some(Path::empty()); } @@ -95,7 +95,7 @@ pub(crate) struct AnnounceDecoder { impl AnnounceDecoder { /// An ANNOUNCE_START: resolve it and assign it the next id. - pub fn start(&mut self, suffix: PathRef<'_>, hops: HopsRef) -> Result<(PathOwned, Hops), Error> { + pub fn start(&mut self, suffix: PathRef, hops: HopsRef) -> Result<(Path, Hops), Error> { let suffix = self.live.resolve_path(suffix)?; let hops = self.live.resolve_hops(hops)?; let id = self.live.next; @@ -112,7 +112,7 @@ impl AnnounceDecoder { /// An ANNOUNCE_UPDATE: resolve its chain before replacing the old one, which it /// may itself be based on. - pub fn update(&mut self, id: u64, hops: HopsRef) -> Result<(PathOwned, Hops), Error> { + pub fn update(&mut self, id: u64, hops: HopsRef) -> Result<(Path, Hops), Error> { let hops = self.live.resolve_hops(hops)?; let entry = self.live.entries.get_mut(&id).ok_or(Error::ProtocolViolation)?; entry.hops = hops.clone(); @@ -120,7 +120,7 @@ impl AnnounceDecoder { } /// An ANNOUNCE_END: retire the id, returning its suffix. - pub fn end(&mut self, id: u64) -> Result { + pub fn end(&mut self, id: u64) -> Result { let entry = self.live.entries.remove(&id).ok_or(Error::ProtocolViolation)?; Ok(entry.suffix) } @@ -245,7 +245,7 @@ impl AnnounceEncoder { } /// An ANNOUNCE_START: its id (on versions that assign one) and its wire form. - pub fn start(&mut self, suffix: PathOwned, hops: Hops) -> (Option, PathRef<'static>, HopsRef) { + pub fn start(&mut self, suffix: Path, hops: Hops) -> (Option, PathRef, HopsRef) { if !self.version.has_announce_id() { return (None, PathRef::literal(suffix), HopsRef::literal(hops)); } @@ -291,7 +291,7 @@ impl AnnounceEncoder { } /// The wire form of `suffix`, sized from its parts so only the winner is built. - fn path_ref(&self, suffix: &PathOwned, next: u64) -> PathRef<'static> { + fn path_ref(&self, suffix: &Path, next: u64) -> PathRef { let form: Form = self.version.into(); let literal = || PathRef::literal(suffix.clone()); let Some((keep, id)) = self.heads.longest(suffix.parts()) else { @@ -300,7 +300,7 @@ impl AnnounceEncoder { // Where the segments after `keep` start, sharing the suffix's buffer. let s = suffix.as_str(); let start = s.match_indices('/').nth(keep - 1).map_or(s.len(), |(i, _)| i + 1); - let rest = suffix.slice_from(start).to_owned(); + let rest = suffix.slice_from(start).clone(); let base = next - id; let keep = keep as u64; @@ -347,7 +347,7 @@ fn varint_size(value: u64, form: Form) -> usize { } /// The bytes `path` takes on the wire: a varint length, then the string. -fn string_size(path: &Path<'_>, form: Form) -> usize { +fn string_size(path: &Path, form: Form) -> usize { let len = path.as_str().len(); varint_size(len as u64, form) + len } @@ -363,11 +363,11 @@ mod tests { Hops::try_from(ids.iter().map(|id| Hop::from_wire(*id)).collect::>()).unwrap() } - fn path(base: u64, keep: u64, rest: &str) -> PathRef<'static> { + fn path(base: u64, keep: u64, rest: &str) -> PathRef { PathRef { base, keep, - rest: Path::new(rest).to_owned(), + rest: Path::new(rest).clone(), } } @@ -379,7 +379,7 @@ mod tests { } } - fn literal(suffix: &str, ids: &[u64]) -> (PathRef<'static>, HopsRef) { + fn literal(suffix: &str, ids: &[u64]) -> (PathRef, HopsRef) { (path(0, 0, suffix), chain(0, ids, 0)) } @@ -511,7 +511,7 @@ mod tests { /// Encode a message with `encoder`, decode it with `decoder`, and check that the /// resolved values match the input. fn start(encoder: &mut AnnounceEncoder, decoder: &mut AnnounceDecoder, suffix: &str, ids: &[u64]) -> (u64, usize) { - let (id, wire_path, wire_hops) = encoder.start(Path::new(suffix).to_owned(), hops(ids)); + let (id, wire_path, wire_hops) = encoder.start(Path::new(suffix).clone(), hops(ids)); let size = wire_path.encode_bytes(VERSION).unwrap().len() + wire_hops.encode_bytes(VERSION).unwrap().len(); let (got_path, got_hops) = decoder.start(wire_path, wire_hops).unwrap(); assert_eq!(got_path.as_str(), suffix); @@ -565,7 +565,7 @@ mod tests { // Shares a byte prefix with `a/b/d`, but no second segment. start(&mut encoder, &mut decoder, "a/b-z", &[3]); - let (_, wire_path, wire_hops) = encoder.start(Path::new("a/b/d").to_owned(), hops(&[5, 1u64 << 51])); + let (_, wire_path, wire_hops) = encoder.start(Path::new("a/b/d").clone(), hops(&[5, 1u64 << 51])); assert_eq!(wire_path, path(2, 2, "d")); assert_eq!(wire_hops, chain(2, &[5], 1)); } @@ -594,7 +594,7 @@ mod tests { let suffix = format!("f{n}"); start(&mut encoder, &mut decoder, &suffix, &[1]); } - let (_, wire_path, wire_hops) = encoder.start(Path::new("again").to_owned(), hops(&[100])); + let (_, wire_path, wire_hops) = encoder.start(Path::new("again").clone(), hops(&[100])); assert_eq!(wire_hops, HopsRef::literal(hops(&[100]))); let (got_path, got_hops) = decoder.start(wire_path, wire_hops).unwrap(); assert_eq!(got_path.as_str(), "again"); @@ -606,7 +606,7 @@ mod tests { let mut encoder = AnnounceEncoder::new(VERSION); let mut decoder = AnnounceDecoder::default(); start(&mut encoder, &mut decoder, "a", &[1]); - let (_, wire_path, wire_hops) = encoder.start(Path::new("b").to_owned(), hops(&[2])); + let (_, wire_path, wire_hops) = encoder.start(Path::new("b").clone(), hops(&[2])); assert_eq!(wire_path, PathRef::literal(Path::new("b"))); assert_eq!(wire_hops, HopsRef::literal(hops(&[2]))); } @@ -614,9 +614,9 @@ mod tests { #[test] fn lite06_assigns_ids_without_bases() { let mut encoder = AnnounceEncoder::new(Version::Lite06); - let (id, _, _) = encoder.start(Path::new("a/b").to_owned(), hops(&[1])); + let (id, _, _) = encoder.start(Path::new("a/b").clone(), hops(&[1])); assert_eq!(id, Some(0)); - let (id, wire_path, wire_hops) = encoder.start(Path::new("a/c").to_owned(), hops(&[1])); + let (id, wire_path, wire_hops) = encoder.start(Path::new("a/c").clone(), hops(&[1])); assert_eq!(id, Some(1)); assert_eq!(wire_path, PathRef::literal(Path::new("a/c"))); assert_eq!(wire_hops, HopsRef::literal(hops(&[1]))); @@ -632,11 +632,11 @@ mod tests { fn golden() -> Vec { use crate::fuzz::Announced; vec![ - Announced::Start(Path::new("room/a/cam").to_owned(), hops(&[0x1111, 0x2222])), - Announced::Start(Path::new("room/a/mic").to_owned(), hops(&[0x3333, 0x2222])), + Announced::Start(Path::new("room/a/cam").clone(), hops(&[0x1111, 0x2222])), + Announced::Start(Path::new("room/a/mic").clone(), hops(&[0x3333, 0x2222])), Announced::Update(0, hops(&[0x4444, 0x2222])), Announced::End(1), - Announced::Start(Path::new("room/b").to_owned(), hops(&[0x5555, 0x2222])), + Announced::Start(Path::new("room/b").clone(), hops(&[0x5555, 0x2222])), ] } diff --git a/rs/moq-net/src/lite/fetch.rs b/rs/moq-net/src/lite/fetch.rs index a793517dd8d..0389d7829cf 100644 --- a/rs/moq-net/src/lite/fetch.rs +++ b/rs/moq-net/src/lite/fetch.rs @@ -12,7 +12,7 @@ use super::{Message, Version}; /// Lite03+ only. #[derive(Clone, Debug)] pub struct Fetch<'a> { - pub broadcast: Path<'a>, + pub broadcast: Path, /// The publisher instance the subscriber expects; see [`crate::origin::Route::epoch`]. /// Lite07+ only. pub epoch: Option, @@ -112,7 +112,7 @@ mod test { fn fetch_sample() -> Fetch<'static> { Fetch { epoch: None, - broadcast: Path::new("room").to_owned(), + broadcast: Path::new("room").clone(), track: Cow::Borrowed("video"), priority: 3, max_delay: None, diff --git a/rs/moq-net/src/lite/publisher.rs b/rs/moq-net/src/lite/publisher.rs index 5208ba6bebd..fbfcf5ae2d6 100644 --- a/rs/moq-net/src/lite/publisher.rs +++ b/rs/moq-net/src/lite/publisher.rs @@ -131,7 +131,7 @@ impl Shared { /// Watches whether the session still lets us serve `broadcast` to the peer: our /// grant, and the ceiling on what the peer may subscribe to. fn gate(&self, broadcast: &crate::Path) -> crate::auth::Gate { - crate::auth::Gate::new(self.auth.clone(), broadcast.to_owned(), crate::auth::Direction::Publish) + crate::auth::Gate::new(self.auth.clone(), broadcast.clone(), crate::auth::Direction::Publish) } /// The origin to resolve a peer-requested broadcast from: excludes routes @@ -274,9 +274,9 @@ impl Publisher { version: Version, auth: Option, ) -> Result<(), Error> { - let mut run = AnnounceRun::new(crate::PathOwned::default(), self_origin, version); + let mut run = AnnounceRun::new(crate::Path::default(), self_origin, version); run.auth = auth.map(|handle| { - crate::auth::Gate::new(handle, crate::Path::empty().to_owned(), crate::auth::Direction::Publish) + crate::auth::Gate::new(handle, crate::Path::empty().clone(), crate::auth::Direction::Publish) }); kio::wait(|waiter| run.poll(stream, origin, announced, waiter)).await } @@ -852,7 +852,7 @@ enum AnnounceState { Decode, /// Waiting on the peer's SETUP for the session-wide excluded origin /// (lite-05, whose wire carries no per-stream exclude_hop). - ExcludeHop { prefix: crate::PathOwned, hidden: bool }, + ExcludeHop { prefix: crate::Path, hidden: bool }, /// Streaming announce updates. Run { origin: origin::Consumer, @@ -878,7 +878,7 @@ impl AnnounceServe { let stream = self.stream.as_mut().expect("stream present"); let mut cx = waiter.context(); let interest = ready!(stream.reader.poll_decode::(&mut cx))?; - let prefix = interest.prefix.to_owned(); + let prefix = interest.prefix.clone(); let hidden = interest.hidden; // The identity whose routes we filter out. Lite-04/05 carry it per @@ -931,7 +931,7 @@ impl AnnounceServe { } } - fn start(&mut self, prefix: crate::PathOwned, exclude_hop: Hop, hidden: bool) { + fn start(&mut self, prefix: crate::Path, exclude_hop: Hop, hidden: bool) { // If the requested prefix is outside our scope (an empty origin, or a token // that doesn't grant it), we simply have nothing to announce. Respond with an // empty set and keep the stream open (the subscriber treats a FIN here as a @@ -981,7 +981,7 @@ impl AnnounceServe { struct AnnounceRun { // The requested prefix. Updates arrive named relative to it, but the grant // matches paths relative to the origin's root. - prefix: crate::PathOwned, + prefix: crate::Path, self_origin: Hop, version: Version, // Lite06+: announce ids. Every `active` we send implicitly assigns the next @@ -991,7 +991,7 @@ struct AnnounceRun { encoder: lite::AnnounceEncoder, // The routes the peer currently holds, keyed by the suffix under the requested // prefix. - live: HashMap, + live: HashMap, phase: AnnouncePhase, initial: Option, replay: bool, @@ -1023,7 +1023,7 @@ enum AnnouncePhase { } impl AnnounceRun { - fn new(prefix: crate::PathOwned, self_origin: Hop, version: Version) -> Self { + fn new(prefix: crate::Path, self_origin: Hop, version: Version) -> Self { Self { prefix, self_origin, @@ -1090,7 +1090,7 @@ impl AnnounceRun { // whatever it has pending for the routes the peer holds (an update, a restart, an end // still waiting out its hold) arrives through the loop as usual. let mut snapshot = origin.announced(); - let mut current: HashMap = HashMap::new(); + let mut current: HashMap = HashMap::new(); while let Some(event) = snapshot.try_next() { match event { announce::Event::Start(update) | announce::Event::Update(update) | announce::Event::Restart(update) => { @@ -1119,7 +1119,7 @@ impl AnnounceRun { fn advertise( &mut self, stream: &mut Stream, - suffix: crate::PathOwned, + suffix: crate::Path, hops: Hops, cost: crate::origin::Cost, epoch: Option, @@ -1195,7 +1195,7 @@ impl AnnounceRun { fn start( &mut self, stream: &mut Stream, - suffix: crate::PathOwned, + suffix: crate::Path, hops: Hops, cost: crate::origin::Cost, // Fixed while the peer holds the advertisement: a new one is a restart. @@ -1218,7 +1218,7 @@ impl AnnounceRun { fn restart( &mut self, stream: &mut Stream, - suffix: crate::PathOwned, + suffix: crate::Path, hops: Hops, cost: crate::origin::Cost, epoch: Option, @@ -1254,7 +1254,7 @@ impl AnnounceRun { fn retract( &mut self, stream: &mut Stream, - suffix: crate::PathOwned, + suffix: crate::Path, absolute: &crate::Path, ) -> Result<(), Error> { let Some(advertised) = self.live.remove(&suffix) else { @@ -1499,7 +1499,7 @@ trait Request: Sized { type Update: Decode + std::fmt::Debug; /// The broadcast and track the request names. - fn target(msg: &Self::Message) -> (&crate::Path<'static>, &str); + fn target(msg: &Self::Message) -> (&crate::Path, &str); /// The publisher instance the request names, if any. fn epoch(msg: &Self::Message) -> Option<&crate::Epoch>; @@ -1551,7 +1551,7 @@ struct RequestServe { /// Ends the request, even mid-reply, once the session stops allowing its broadcast. gate: Option, // Log context, filled in after the decode. - absolute: crate::PathOwned, + absolute: crate::Path, track: String, // A reply that always has another group ready still lets the session's other tasks run. budget: kio::coop::Budget, @@ -1659,7 +1659,7 @@ impl RequestServe { RequestState::Decode => { let msg = ready!(stream.reader.poll_decode::(&mut cx))?; let (broadcast, track) = R::target(&msg); - self.absolute = self.shared.origin.absolute(broadcast).to_owned(); + self.absolute = self.shared.origin.absolute(broadcast).clone(); self.track = track.to_string(); if R::SUBSCRIPTION { self._subscription = Some(self.shared.subscriptions.hold()); @@ -1807,7 +1807,7 @@ impl Request for TrackInfoServe { type Message = lite::Track<'static>; type Update = NoUpdate; - fn target(msg: &Self::Message) -> (&crate::Path<'static>, &str) { + fn target(msg: &Self::Message) -> (&crate::Path, &str) { (&msg.broadcast, &msg.track) } @@ -1858,10 +1858,10 @@ impl Request for TracksServe { const KIND: &'static str = "tracks"; const GRACEFUL: bool = false; const HOLDS: bool = false; - type Message = lite::Tracks<'static>; + type Message = lite::Tracks; type Update = NoUpdate; - fn target(msg: &Self::Message) -> (&crate::Path<'static>, &str) { + fn target(msg: &Self::Message) -> (&crate::Path, &str) { (&msg.broadcast, "") } @@ -1955,7 +1955,7 @@ impl Request for SubscribeServe { type Message = lite::Subscribe<'static>; type Update = lite::SubscribeUpdate; - fn target(msg: &Self::Message) -> (&crate::Path<'static>, &str) { + fn target(msg: &Self::Message) -> (&crate::Path, &str) { (&msg.broadcast, &msg.track) } @@ -2195,7 +2195,7 @@ impl Request for FetchServe { type Message = lite::Fetch<'static>; type Update = NoUpdate; - fn target(msg: &Self::Message) -> (&crate::Path<'static>, &str) { + fn target(msg: &Self::Message) -> (&crate::Path, &str) { (&msg.broadcast, &msg.track) } @@ -2618,7 +2618,7 @@ mod announce_test { #[test] fn initial_replay_keeps_the_last_event_and_its_order() { let update = |prefix: &'static str, cost| announce::Announce { - prefix: crate::PathOwned::from(prefix), + prefix: crate::Path::from(prefix), captures: None, route: origin::Route::default().with_cost(cost), }; @@ -2663,7 +2663,7 @@ mod announce_test { ), reader: Reader::new(crate::transport::boxed::RecvStream::new(PendingRecv), Version::Lite07), }; - let mut run = AnnounceRun::new(crate::Path::new("").to_owned(), consumer.hop(), Version::Lite07); + let mut run = AnnounceRun::new(crate::Path::new("").clone(), consumer.hop(), Version::Lite07); let waiter = kio::Waiter::noop(); assert!(run.poll(&mut stream, &consumer, &mut announced, &waiter).is_pending()); drop(removed); @@ -2705,7 +2705,7 @@ mod announce_test { ), reader: Reader::new(crate::transport::boxed::RecvStream::new(PendingRecv), Version::Lite07), }; - let mut run = AnnounceRun::new(crate::Path::new("").to_owned(), consumer.hop(), Version::Lite07); + let mut run = AnnounceRun::new(crate::Path::new("").clone(), consumer.hop(), Version::Lite07); let waiter = kio::Waiter::noop(); assert!(run.poll(&mut stream, &consumer, &mut announced, &waiter).is_pending()); let auth = crate::auth::Link::new(false); @@ -2747,7 +2747,7 @@ mod announce_test { ), reader: Reader::new(crate::transport::boxed::RecvStream::new(PendingRecv), Version::Lite07), }; - let mut run = AnnounceRun::new(crate::Path::new("").to_owned(), consumer.hop(), Version::Lite07); + let mut run = AnnounceRun::new(crate::Path::new("").clone(), consumer.hop(), Version::Lite07); let waiter = kio::Waiter::noop(); assert!(run.poll(&mut stream, &consumer, &mut announced, &waiter).is_pending()); let auth = crate::auth::Link::new(false); @@ -2803,7 +2803,7 @@ mod announce_test { ), reader: Reader::new(crate::transport::boxed::RecvStream::new(PendingRecv), Version::Lite07), }; - let mut run = AnnounceRun::new(crate::Path::new("").to_owned(), consumer.hop(), Version::Lite07); + let mut run = AnnounceRun::new(crate::Path::new("").clone(), consumer.hop(), Version::Lite07); let waiter = kio::Waiter::noop(); assert!(run.poll(&mut stream, &consumer, &mut announced, &waiter).is_pending()); drop(unseen); @@ -2843,7 +2843,7 @@ mod announce_test { ), reader: Reader::new(crate::transport::boxed::RecvStream::new(PendingRecv), Version::Lite07), }; - let mut run = AnnounceRun::new(crate::Path::new("").to_owned(), consumer.hop(), Version::Lite07); + let mut run = AnnounceRun::new(crate::Path::new("").clone(), consumer.hop(), Version::Lite07); assert!( run.poll(&mut stream, &consumer, &mut announced, &kio::Waiter::noop()) .is_pending() @@ -2881,15 +2881,14 @@ mod announce_test { } /// Decode every announce message written since the last call. - fn take_announces(&mut self) -> Vec> { + fn take_announces(&mut self) -> Vec { let buf = self.pending(); let mut slice = &buf[..]; let mut msgs = Vec::new(); while !slice.is_empty() { msgs.push( crate::coding::decode_buf(&mut slice, self.version, lite::AnnounceBroadcast::decode) - .expect("announce message") - .into_owned(), + .expect("announce message"), ); } self.cursor += buf.len(); @@ -3095,7 +3094,7 @@ mod announce_test { // A restart waits out the update hold like an update. moq_clock::sleep(crate::origin::Config::default().update_hold).await; settle().await; - let restarted = |msgs: Vec>| match (version, msgs.as_slice()) { + let restarted = |msgs: Vec| match (version, msgs.as_slice()) { ( Version::Lite07, [ @@ -7242,7 +7241,7 @@ mod tests { let consumer = origin.consume(); let mut announced = consumer.announced(); - let mut run = AnnounceRun::new(crate::PathOwned::default(), self_origin, Version::Lite01); + let mut run = AnnounceRun::new(crate::Path::default(), self_origin, Version::Lite01); run.withdraw(&mut stream, &consumer, &mut announced).unwrap(); let _ = kio::wait(|waiter| Poll::Ready(run.poll(&mut stream, &consumer, &mut announced, waiter))).await; diff --git a/rs/moq-net/src/lite/subscribe.rs b/rs/moq-net/src/lite/subscribe.rs index 7404fb6bed3..e80243617f2 100644 --- a/rs/moq-net/src/lite/subscribe.rs +++ b/rs/moq-net/src/lite/subscribe.rs @@ -16,7 +16,7 @@ use crate::track::Order; #[derive(Clone, Debug)] pub struct Subscribe<'a> { pub id: u64, - pub broadcast: Path<'a>, + pub broadcast: Path, /// The publisher instance the subscriber expects; see [`crate::origin::Route::epoch`]. /// Lite07+ only. pub epoch: Option, @@ -912,7 +912,7 @@ mod test { order: crate::track::Order::Descending, epoch: None, id: 1, - broadcast: Path::new("room").to_owned(), + broadcast: Path::new("room").clone(), track: Cow::Borrowed("video"), priority: 3, max_delay: std::time::Duration::from_millis(250), diff --git a/rs/moq-net/src/lite/subscriber.rs b/rs/moq-net/src/lite/subscriber.rs index 5593cb0ab9f..dc33ab96b5f 100644 --- a/rs/moq-net/src/lite/subscriber.rs +++ b/rs/moq-net/src/lite/subscriber.rs @@ -9,7 +9,7 @@ use std::{ }; use crate::{ - AsPath, Error, Path, PathOwned, Timescale, Timestamp, bandwidth, + Error, Path, Timescale, Timestamp, bandwidth, coding::{Reader, Stream, varint::MAX_QUIC}, lite, track::{Anchor, Position, Subscription}, @@ -177,8 +177,8 @@ impl Subscriber { /// bookkeeping in `run`. fn handle_announce( &mut self, - prefix: &PathOwned, - announce: lite::AnnounceBroadcast<'_>, + prefix: &Path, + announce: lite::AnnounceBroadcast, run: &mut PrefixRun, ) -> Result<(), Error> { match announce { @@ -192,7 +192,7 @@ impl Subscriber { // Every `active` assigns the next ordinal, even ones we drop locally. true => run.decoder.start(suffix, hops)?, // Nothing references an announcement here, so there are no bases. - false => (suffix.rest.into_owned(), hops.literal), + false => (suffix.rest, hops.literal), }; let path = prefix.join(&suffix); if lite::update_supported(self.version) @@ -276,7 +276,7 @@ impl Subscriber { )] fn start_announce( &mut self, - path: PathOwned, + path: Path, // The publisher instance the peer announced, fixed until it retracts. epoch: Option, hops: crate::Hops, @@ -329,7 +329,7 @@ impl Subscriber { /// since it never changes the content. fn announced_route( &self, - path: &PathOwned, + path: &Path, hops: crate::Hops, cost: crate::origin::Cost, link_cost: u64, @@ -374,7 +374,7 @@ impl Subscriber { /// route is now gone), `Ok(true)` otherwise. fn update_announce( &mut self, - path: PathOwned, + path: Path, hops: crate::Hops, // The route cost off the wire and this link's price. See `start_announce`. cost: crate::origin::Cost, @@ -416,7 +416,7 @@ impl Subscriber { /// route is now gone), `Ok(true)` otherwise. fn restart_announce( &mut self, - path: PathOwned, + path: Path, hops: crate::Hops, // See `update_announce`. cost: crate::origin::Cost, @@ -439,7 +439,7 @@ impl Subscriber { /// reflected or exceeds the origin's hop limit. fn chain( &self, - path: &PathOwned, + path: &Path, mut hops: crate::Hops, responder_origin: Option, ) -> Option { @@ -533,8 +533,8 @@ impl Subscriber { Ok(()) } - fn log_path(&self, path: impl AsPath) -> Path<'_> { - self.origin.root().join(path) + fn log_path(&self, path: impl Into) -> Path { + self.origin.root().join(&path.into()) } } @@ -587,7 +587,7 @@ pub(super) struct SubscriberDriver { discovery: crate::discovery::Discovery, /// One machine per prefix asked about. Only an error ends the session; a /// prefix finishing cleanly (publisher FIN) just retires. - prefixes: HashMap, + prefixes: HashMap, uni: UniAccept, /// PROBE feedback; finishes quietly when unsupported or given up on. bandwidth: Option, @@ -1564,7 +1564,7 @@ impl ProbeStream { /// session; a publisher FIN is a clean end for the prefix alone. struct AnnouncePrefix { subscriber: Subscriber, - prefix: PathOwned, + prefix: Path, state: PrefixState, /// Holds the origin's announce consumers under the prefix unsynced /// until the peer's initial set is in. Dropped at the boundary the version @@ -1621,7 +1621,7 @@ struct PrefixRun { impl AnnouncePrefix { /// `snapshot` was taken when the prefix was first wanted, so a consumer of the /// origin never reads as synced before the request even goes out. - fn new(subscriber: Subscriber, prefix: PathOwned, snapshot: crate::model::Snapshot) -> Self { + fn new(subscriber: Subscriber, prefix: Path, snapshot: crate::model::Snapshot) -> Self { Self { subscriber, prefix, @@ -1659,7 +1659,7 @@ impl AnnouncePrefix { // the origin, and each local reader opts in on its own // (`origin::Consumer::with_hidden`). stream.writer.buffer(&lite::AnnounceRequest { - prefix: self.prefix.as_path(), + prefix: self.prefix.clone(), exclude_hop: self.subscriber.self_origin, hidden: true, })?; @@ -1835,7 +1835,7 @@ impl AnnouncePrefix { /// A source the announce half minted for one requested path, for the driver to serve. struct MintedSource { - path: PathOwned, + path: Path, /// The publisher instance the route announced, asked for on every request. epoch: Option, source: crate::model::broadcast::SourceGuard, @@ -1853,7 +1853,7 @@ struct MintedSource { /// dying drops them. struct SourceServe { subscriber: Subscriber, - path: PathOwned, + path: Path, epoch: Option, source: crate::model::broadcast::SourceGuard, dynamic: crate::broadcast::Dynamic, @@ -1914,7 +1914,7 @@ impl TracksRecv { stream = stream.with_stats(source.subscriber.origin.stats()); stream.writer.buffer(&lite::ControlType::Tracks)?; stream.writer.buffer(&lite::Tracks { - broadcast: source.path.as_path(), + broadcast: source.path.clone(), epoch: source.epoch.clone(), })?; self.stream.insert(stream) @@ -3421,7 +3421,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }; @@ -3493,7 +3493,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }; @@ -3563,7 +3563,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }; @@ -3622,7 +3622,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }; @@ -3806,7 +3806,7 @@ mod tests { validity: Default::default(), route: None, subscriber: subscriber.clone(), - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }, @@ -3880,7 +3880,7 @@ mod tests { validity: crate::model::broadcast::RequestValidity::token(Some(route.consume())), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".into(), }; @@ -3953,7 +3953,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }, @@ -3999,7 +3999,7 @@ mod tests { let serve = SourceServe::new( subscriber, MintedSource { - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, source: crate::model::broadcast::SourceGuard::new(broadcast), dynamic, @@ -4127,7 +4127,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".to_string(), }, @@ -4178,7 +4178,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room/host").to_owned(), + path: Path::new("room/host").clone(), epoch: None, name: "catalog.json".to_string(), }; @@ -4245,7 +4245,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room/host").to_owned(), + path: Path::new("room/host").clone(), epoch: None, name: "audio".to_string(), }; @@ -4314,7 +4314,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room/host").to_owned(), + path: Path::new("room/host").clone(), epoch: None, name: "catalog.json".to_string(), }, @@ -4380,7 +4380,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".into(), }; @@ -4434,7 +4434,7 @@ mod tests { validity: Default::default(), route: None, subscriber, - path: Path::new("room").to_owned(), + path: Path::new("room").clone(), epoch: None, name: "video".into(), }; @@ -4784,7 +4784,7 @@ mod tests { auth: crate::auth::Link::new(false), }); - let path = Path::new("room/host").to_owned(); + let path = Path::new("room/host").clone(); let mut announced = Announced::default(); assert!( subscriber @@ -4844,7 +4844,7 @@ mod tests { assert!( subscriber .start_announce( - Path::new(&format!("room/{i}")).to_owned(), + Path::new(&format!("room/{i}")).clone(), None, crate::Hops::new(), crate::origin::Cost::default(), @@ -4892,7 +4892,7 @@ mod tests { going_away: Default::default(), auth: crate::auth::Link::new(false), }); - let path = Path::new("pool").to_owned(); + let path = Path::new("pool").clone(); let mut announced = Announced::default(); subscriber .start_announce( @@ -4964,7 +4964,7 @@ mod tests { let mut announced = Announced::default(); subscriber .start_announce( - Path::new("pool").to_owned(), + Path::new("pool").clone(), None, crate::Hops::new(), crate::origin::Cost::default(), @@ -5018,7 +5018,7 @@ mod tests { auth: crate::auth::Link::new(false), }); - let path = Path::new("room/host").to_owned(); + let path = Path::new("room/host").clone(); let mut announced = Announced::default(); // A chain that already names us is a reflection, declined but still recorded. @@ -5077,7 +5077,7 @@ mod tests { auth: crate::auth::Link::new(false), }); - let path = Path::new("room/host").to_owned(); + let path = Path::new("room/host").clone(); let mut announced = Announced::default(); let mut reflected = crate::Hops::new(); reflected.push(assigned).unwrap(); @@ -5143,7 +5143,7 @@ mod tests { let mut announced = Announced::default(); let accepted = subscriber .start_announce( - Path::new("room/host").to_owned(), + Path::new("room/host").clone(), None, hops, crate::origin::Cost::default(), @@ -5209,7 +5209,7 @@ mod tests { let mut announced = Announced::default(); let accepted = subscriber .start_announce( - Path::new("room/host").to_owned(), + Path::new("room/host").clone(), None, hops, crate::origin::Cost::default(), @@ -5268,7 +5268,7 @@ mod tests { let mut announced = Announced::default(); let accepted = subscriber .start_announce( - Path::new("room/host").to_owned(), + Path::new("room/host").clone(), None, crate::Hops::new(), crate::origin::Cost::UNKNOWN, @@ -5315,7 +5315,7 @@ mod tests { assert!( subscriber .start_announce( - Path::new("room/host").to_owned(), + Path::new("room/host").clone(), None, hops, crate::origin::Cost::UNKNOWN, @@ -5343,7 +5343,7 @@ mod tests { let mut announced = Announced::default(); subscriber .start_announce( - Path::new("room/host").to_owned(), + Path::new("room/host").clone(), None, crate::Hops::new(), crate::origin::Cost::UNKNOWN, @@ -5362,7 +5362,7 @@ mod tests { // A reprice from the same unnamed publisher stays anonymous and in place. subscriber .update_announce( - Path::new("room/host").to_owned(), + Path::new("room/host").clone(), crate::Hops::new(), crate::origin::Cost::UNKNOWN, 4, @@ -5401,7 +5401,7 @@ mod tests { let (mut subscriber, consumer) = restart_subscriber(SinkSession::new(Default::default())); let mut announced = Announced::default(); - let path = Path::new("room/host").to_owned(); + let path = Path::new("room/host").clone(); subscriber .start_announce( path.clone(), @@ -5442,7 +5442,7 @@ mod tests { let auth = subscriber.auth.clone(); let mut announced = Announced::default(); - let path = Path::new("room/host").to_owned(); + let path = Path::new("room/host").clone(); subscriber .start_announce( path.clone(), @@ -5501,7 +5501,7 @@ mod tests { auth: crate::auth::Link::new(false), }); - let path = Path::new("room/host").to_owned(); + let path = Path::new("room/host").clone(); let hops = crate::Hops::try_from(vec![crate::Hop::new(7).unwrap()]).unwrap(); let mut announced = Announced::default(); subscriber @@ -5586,7 +5586,7 @@ mod tests { }); let mut prefix = AnnouncePrefix::new( subscriber.clone(), - Path::new("").to_owned(), + Path::new("").clone(), subscriber.origin.snapshot(""), ); let mut cursor = consumer.announced(); @@ -5655,7 +5655,7 @@ mod tests { ( AnnouncePrefix::new( subscriber.clone(), - Path::new("").to_owned(), + Path::new("").clone(), subscriber.origin.snapshot(""), ), consumer, @@ -5834,7 +5834,7 @@ mod tests { subscriber.announces = slots.clone(); let mut prefix = AnnouncePrefix::new( subscriber.clone(), - Path::new("").to_owned(), + Path::new("").clone(), subscriber.origin.snapshot(""), ); @@ -5984,17 +5984,17 @@ enum Sub { /// A declined advertisement remains present with no route because the peer still /// owns its path and announce id until it retracts or restarts it. struct Announced { - routes: HashMap, + routes: HashMap, /// The session's announce cap, and one slot taken from it per entry in `routes`. slots: crate::session::Slots, held: Vec, /// The epoch each advertisement named, kept while it stands even when declined, /// so a restart that attaches it later serves the same broadcast. - epochs: HashMap, + epochs: HashMap, /// Attached routes whose request queue woke since the last serve pass. The /// driver wakes for every group the session carries, so a pass must cost what /// was requested, not every route the peer announced. - ready: kio::Queue, + ready: kio::Queue, } /// What we made of one advertisement the peer holds. @@ -6026,12 +6026,12 @@ impl Announced { } } - fn contains(&self, path: &PathOwned) -> bool { + fn contains(&self, path: &Path) -> bool { self.routes.contains_key(path) } /// Attach a route, queued for its first serve pass. - fn attach(&mut self, path: PathOwned, route: crate::origin::Route, dynamic: crate::origin::Dynamic) { + fn attach(&mut self, path: Path, route: crate::origin::Route, dynamic: crate::origin::Dynamic) { let wake = crate::util::QueueWake::new(path.clone(), self.ready.clone()); let route = AnnouncedRoute::new(route, dynamic, wake); route.waker.wake_by_ref(); @@ -6041,7 +6041,7 @@ impl Announced { /// Put the peer's route into the origin, so paths under it resolve through this /// session on demand, or hold it back when the session's limit does not cover it. /// Returns whether it attached. - fn offer(&mut self, subscriber: &Subscriber, path: PathOwned, route: crate::origin::Route) -> bool { + fn offer(&mut self, subscriber: &Subscriber, path: Path, route: crate::origin::Route) -> bool { if !subscriber .auth .within_limit(crate::auth::Direction::Subscribe, path.as_str()) @@ -6063,7 +6063,7 @@ impl Announced { } } - fn declined(&mut self, path: PathOwned) { + fn declined(&mut self, path: Path) { // Dropping a replaced route closes its sources. self.routes.insert(path, Held::Declined); } @@ -6079,7 +6079,7 @@ impl Announced { /// with [`Self::contains`]. Overwriting an attached route is [`Self::declined`]'s job. /// /// Fails with [`Error::TooManyRequests`] once the session holds as many as it allows. - fn reserve(&mut self, path: PathOwned, epoch: Option) -> Result<(), Error> { + fn reserve(&mut self, path: Path, epoch: Option) -> Result<(), Error> { debug_assert!(!self.routes.contains_key(&path), "reserved a prefix already advertised"); self.held.push(self.slots.acquire()?); if let Some(epoch) = epoch { @@ -6090,19 +6090,19 @@ impl Announced { } /// The epoch the advertisement at `path` named, if any. - fn epoch(&self, path: &PathOwned) -> Option { + fn epoch(&self, path: &Path) -> Option { self.epochs.get(path).cloned() } /// Record the epoch a restart of the live advertisement at `path` names. - fn restart(&mut self, path: &PathOwned, epoch: Option) { + fn restart(&mut self, path: &Path, epoch: Option) { match epoch { Some(epoch) => self.epochs.insert(path.clone(), epoch), None => self.epochs.remove(path), }; } - fn attached(&mut self, path: &PathOwned) -> Option<&mut AnnouncedRoute> { + fn attached(&mut self, path: &Path) -> Option<&mut AnnouncedRoute> { match self.routes.get_mut(path)? { Held::Attached(route) => Some(route.as_mut()), _ => None, @@ -6111,7 +6111,7 @@ impl Announced { /// Retire this session's advertisement without invalidating another live /// session from the same peer. Dropping its sources closes their requests. - fn withdraw(&mut self, path: &PathOwned) { + fn withdraw(&mut self, path: &Path) { self.epochs.remove(path); let Some(held) = self.routes.remove(path) else { return; @@ -6126,7 +6126,7 @@ impl Announced { /// path, answer the requester with its consumer, and hand the source to the /// driver's serve machines, which own it from then on. fn poll_serve(&mut self, subscriber: &Subscriber, waiter: &kio::Waiter) { - let root = subscriber.origin.root().to_owned(); + let root = subscriber.origin.root().clone(); while let Poll::Ready(Ok(path)) = self.ready.poll_pop(waiter) { // A route retired since it woke has nothing left to serve. let Some(Held::Attached(entry)) = self.routes.get_mut(&path) else { @@ -6143,7 +6143,7 @@ impl Announced { // Outside our root: nothing we could name on the wire. continue; }; - let path = path.to_owned(); + let path = path.clone(); // A request naming the epoch reaches only that instance, so only a source // without one must stop once the peer no longer selects what minted it. let validity = if entry.route.epoch.is_some() { @@ -6173,7 +6173,7 @@ impl Announced { /// its sources (tracks in flight end on their own gates, with `Unauthorized`), and /// attach every withheld one it now does. fn limit(&mut self, permit: &crate::auth::Permit, subscriber: &Subscriber) { - let changed: Vec = self + let changed: Vec = self .routes .iter() .filter(|(path, held)| match held { @@ -6225,7 +6225,7 @@ struct AnnouncedRoute { /// Whether the GOAWAY drain already re-priced this route. drained: bool, /// Queues this route on its prefix's ready set when its request queue wakes. - wake: Arc>, + wake: Arc>, waker: std::task::Waker, /// Keeps this route's registration on its request queue alive between passes. park: kio::Park, @@ -6235,7 +6235,7 @@ impl AnnouncedRoute { fn new( route: crate::origin::Route, dynamic: crate::origin::Dynamic, - wake: Arc>, + wake: Arc>, ) -> Self { Self { route, @@ -6290,7 +6290,7 @@ struct TrackServe { /// The advertisement that minted this source, absent for standalone test sources. route: Option>, subscriber: Subscriber, - path: PathOwned, + path: Path, /// The publisher instance to ask for; the peer refuses a request for another. epoch: Option, name: String, @@ -6604,7 +6604,7 @@ impl Establish { let msg = lite::Subscribe { epoch: self.serve.epoch.clone(), id: self.id, - broadcast: self.serve.path.as_path(), + broadcast: self.serve.path.clone(), track: self.serve.name.as_str().into(), priority: self.subscription.priority, max_delay: wire_max_delay(self.subscription.max_delay), @@ -6948,7 +6948,7 @@ impl TrackInfoFetch { stream.writer.buffer(&lite::ControlType::Track)?; stream.writer.buffer(&lite::Track { epoch: serve.epoch.clone(), - broadcast: serve.path.as_path(), + broadcast: serve.path.clone(), track: serve.name.as_str().into(), })?; self.state = TrackInfoState::Send { stream }; @@ -7845,7 +7845,7 @@ impl kio::Task for FetchServeRun { let msg = lite::Fetch { epoch: self.serve.epoch.clone(), - broadcast: self.serve.path.as_path(), + broadcast: self.serve.path.clone(), track: self.serve.name.as_str().into(), priority: request.priority(), // Older wires cannot carry it; the local cache judges it instead. diff --git a/rs/moq-net/src/lite/track.rs b/rs/moq-net/src/lite/track.rs index 0143105cdf5..289d4fbea76 100644 --- a/rs/moq-net/src/lite/track.rs +++ b/rs/moq-net/src/lite/track.rs @@ -16,7 +16,7 @@ const LEGACY_UNLIMITED: u64 = (1u64 << 53) - 1; /// Lite05+ only. #[derive(Clone, Debug)] pub struct Track<'a> { - pub broadcast: Path<'a>, + pub broadcast: Path, /// The publisher instance the subscriber expects; see [`crate::origin::Route::epoch`]. /// Lite07+ only. pub epoch: Option, @@ -483,7 +483,7 @@ mod test { fn track_request_roundtrips_on_lite05() { let msg = Track { epoch: None, - broadcast: Path::new("room").to_owned(), + broadcast: Path::new("room").clone(), track: Cow::Borrowed("video"), }; let mut buf = Vec::new(); @@ -499,7 +499,7 @@ mod test { fn track_request_errors_before_lite05() { let msg = Track { epoch: None, - broadcast: Path::new("room").to_owned(), + broadcast: Path::new("room").clone(), track: Cow::Borrowed("video"), }; let mut buf = Vec::new(); diff --git a/rs/moq-net/src/lite/tracks.rs b/rs/moq-net/src/lite/tracks.rs index 4287476e211..ea0d4d5f7e8 100644 --- a/rs/moq-net/src/lite/tracks.rs +++ b/rs/moq-net/src/lite/tracks.rs @@ -17,13 +17,13 @@ const SYNCED: u64 = 0x4; /// /// Lite07+ only. #[derive(Clone, Debug)] -pub struct Tracks<'a> { - pub broadcast: Path<'a>, +pub struct Tracks { + pub broadcast: Path, /// The publisher instance the subscriber expects; see [`crate::origin::Route::epoch`]. pub epoch: Option, } -impl Message for Tracks<'_> { +impl Message for Tracks { const CONTROL: Option = Some(crate::stats::Control::TrackInfo); fn decode_msg(r: &mut Decoder<'_>, version: Version) -> Result { if !version.has_tracks() { @@ -196,7 +196,7 @@ mod test { fn the_request_roundtrips() { let epoch: crate::Epoch = "01900000-0000-7000-8000-000000000001".parse().unwrap(); let msg = Tracks { - broadcast: Path::new("room/alice").to_owned(), + broadcast: Path::new("room/alice").clone(), epoch: Some(epoch.clone()), }; let buf = msg.encode_bytes(Version::Lite07).unwrap(); @@ -208,7 +208,7 @@ mod test { #[test] fn older_versions_have_no_tracks_stream() { let msg = Tracks { - broadcast: Path::new("room").to_owned(), + broadcast: Path::new("room").clone(), epoch: None, }; assert!(msg.encode_bytes(Version::Lite06).is_err()); diff --git a/rs/moq-net/src/model/broadcast.rs b/rs/moq-net/src/model/broadcast.rs index 12474e48c46..e0af9fe0268 100644 --- a/rs/moq-net/src/model/broadcast.rs +++ b/rs/moq-net/src/model/broadcast.rs @@ -56,7 +56,7 @@ pub struct Info { /// /// Empty (the default) for a standalone broadcast with no origin, which is then its own /// root: any `..` reference escapes. - pub path: crate::PathOwned, + pub path: crate::Path, /// The epoch of the route a [`Consumer`] was resolved through, naming the publisher /// instance it reads. @@ -75,7 +75,7 @@ impl Default for Info { Self { pool: cache::Pool::new(cache::Config::default().with_expiry(cache::DEFAULT_EXPIRY)), cache_duration: std::time::Duration::MAX, - path: crate::PathOwned::default(), + path: crate::Path::default(), epoch: None, frames: Default::default(), } @@ -871,7 +871,7 @@ impl Consumer { /// references resolve against is where the *reader* found the broadcast, not where its /// producer happened to create it. Free when the two already agree, which is the case /// for a broadcast created at the path an unrooted cursor asks for. - pub(super) fn with_path(mut self, path: crate::PathOwned) -> Self { + pub(super) fn with_path(mut self, path: crate::Path) -> Self { if self.info.path != path { let mut info = (*self.info).clone(); info.path = path; diff --git a/rs/moq-net/src/model/follow.rs b/rs/moq-net/src/model/follow.rs index e26d92af015..cdb2d35b734 100644 --- a/rs/moq-net/src/model/follow.rs +++ b/rs/moq-net/src/model/follow.rs @@ -2,7 +2,7 @@ use std::task::{Poll, ready}; use super::WeakEntry; use super::origin_impl::{Announce, AnnounceConsumer, AnnounceEvent, Consumer, Routing}; -use crate::{Epoch, Error, PathOwned, broadcast}; +use crate::{Epoch, Error, Path, broadcast}; /// The announcements covering one path, as one broadcast coming online, being replaced, and /// going offline. Created by [`Consumer::follow`](crate::origin::Consumer::follow). @@ -15,7 +15,7 @@ use crate::{Epoch, Error, PathOwned, broadcast}; /// ignored. pub struct Follow { announced: AnnounceConsumer, - path: PathOwned, + path: Path, /// Every route standing over the path. covering: Vec, /// How many events the cursor has delivered. @@ -40,7 +40,7 @@ struct Standing { } impl Follow { - pub(super) fn new(announced: AnnounceConsumer, path: PathOwned) -> Self { + pub(super) fn new(announced: AnnounceConsumer, path: Path) -> Self { Self { announced, path, @@ -195,7 +195,7 @@ impl Follow { /// [`Consumer::announced_broadcast`](crate::origin::Consumer::announced_broadcast). pub struct AnnouncedBroadcast { consumer: Consumer, - path: PathOwned, + path: Path, /// The only instance handed out, if pinned. epoch: Option, follow: Follow, @@ -209,7 +209,7 @@ pub struct AnnouncedBroadcast { } impl AnnouncedBroadcast { - pub(super) fn new(consumer: Consumer, path: PathOwned, epoch: Option, follow: Follow) -> Self { + pub(super) fn new(consumer: Consumer, path: Path, epoch: Option, follow: Follow) -> Self { Self { consumer, path, diff --git a/rs/moq-net/src/model/origin.rs b/rs/moq-net/src/model/origin.rs index 9ac88f57f8a..852096eeb34 100644 --- a/rs/moq-net/src/model/origin.rs +++ b/rs/moq-net/src/model/origin.rs @@ -18,7 +18,7 @@ use super::{ front::{Action, Candidate, Event, Front, Refusal}, }; use crate::{ - AsPath, Epoch, Error, InvalidPattern, Path, PathOwned, Pattern, Patterns, + Epoch, Error, InvalidPattern, Path, Pattern, Patterns, coding::{BoundsExceeded, Decode, DecodeError, Decoder, Encode, EncodeError, Encoder, Form, varint}, path::Segment, time::{Deadlines, Instant}, @@ -874,18 +874,18 @@ struct Pending { /// what changed or came due since the last one, never the entries it holds back. #[derive(Default)] struct OriginConsumerState { - pending: HashMap, + pending: HashMap, /// The entries a poll looks at next, in delivery order: queued or changed since, /// or come due. - due: BTreeMap, + due: BTreeMap, /// The entries held back, by when a poll looks at them again. - parked: BTreeMap<(Instant, u64), PathOwned>, + parked: BTreeMap<(Instant, u64), Path>, next_seq: u64, /// Prefixes whose most recently delivered update was an announce. A pending /// `Announce` is ambiguous on its own: it is an unseen initial announce (a /// retraction cancels it entirely) or a metadata update on a route the /// consumer already observed (a retraction must still be delivered). - delivered: BTreeSet, + delivered: BTreeSet, /// Set by the origin's teardown: the cursor drains `pending`, then reports /// the end instead of parking forever on a table that can never fire again. ended: bool, @@ -928,7 +928,7 @@ impl OriginConsumerState { /// Queue `update` at `prefix`, or replace the one queued there in its place. A /// replaced entry is looked at again on the next poll. - fn put(&mut self, prefix: PathOwned, update: PendingUpdate) { + fn put(&mut self, prefix: Path, update: PendingUpdate) { let Some(pending) = self.pending.get_mut(&prefix) else { self.insert(prefix, update, false); return; @@ -941,7 +941,7 @@ impl OriginConsumerState { } /// Queue `update` at `prefix`, which has nothing queued, last in delivery order. - fn insert(&mut self, prefix: PathOwned, update: PendingUpdate, owed: bool) { + fn insert(&mut self, prefix: Path, update: PendingUpdate, owed: bool) { let seq = self.next_seq; self.next_seq += 1; self.due.insert(seq, prefix.clone()); @@ -958,7 +958,7 @@ impl OriginConsumerState { } /// Unqueue the entry at `prefix`. - fn remove(&mut self, prefix: &PathOwned) -> Option { + fn remove(&mut self, prefix: &Path) -> Option { let pending = self.pending.remove(prefix)?; match pending.wake { Some(wake) => self.parked.remove(&(wake, pending.seq)), @@ -976,7 +976,7 @@ impl OriginConsumerState { } } - fn apply_announce(&mut self, prefix: PathOwned, meta: RouteMeta, captures: Option>) { + fn apply_announce(&mut self, prefix: Path, meta: RouteMeta, captures: Option>) { let meta = (meta, captures); let new = match self.pending.get_mut(&prefix).and_then(|pending| pending.update.take()) { // First announce, a stale announce being replaced, or a metadata update. @@ -996,7 +996,7 @@ impl OriginConsumerState { /// unless this one goes at once. fn apply_restart( &mut self, - prefix: PathOwned, + prefix: Path, meta: RouteMeta, captures: Option>, release: Option, @@ -1016,7 +1016,7 @@ impl OriginConsumerState { self.put(prefix, new); } - fn apply_unannounce(&mut self, prefix: PathOwned, last: RouteMeta, captures: Option>) { + fn apply_unannounce(&mut self, prefix: Path, last: RouteMeta, captures: Option>) { let last = (last, captures); match self.pending.get_mut(&prefix).and_then(|pending| pending.update.take()) { // The pending announce was never delivered and neither was any earlier @@ -1063,10 +1063,10 @@ impl OriginConsumerState { /// `retracted`, settled against `now`. fn scan( &mut self, - retracted: &HashMap, + retracted: &HashMap, now: Instant, hold: Duration, - ) -> Result> { + ) -> Result> { while let Some(entry) = self.parked.first_entry() && entry.key().0 <= now { @@ -1127,7 +1127,7 @@ impl OriginConsumerState { } /// Take the pending update at `prefix` for delivery. - fn take_prefix(&mut self, prefix: PathOwned) -> AnnounceEvent { + fn take_prefix(&mut self, prefix: Path) -> AnnounceEvent { let (event, owed) = self.take_update(prefix); let (AnnounceEvent::Start(announce) | AnnounceEvent::Update(announce) @@ -1140,7 +1140,7 @@ impl OriginConsumerState { event } - fn take_update(&mut self, prefix: PathOwned) -> (AnnounceEvent, bool) { + fn take_update(&mut self, prefix: Path) -> (AnnounceEvent, bool) { let pending = self.remove(&prefix).unwrap(); let owed = pending.owed; let ((meta, captures), kind): (_, fn(Announce) -> AnnounceEvent) = match pending.update.unwrap() { @@ -1223,7 +1223,7 @@ struct RouteEntry { epoch: Option, /// Names the content behind the route when it has no epoch; see [`Instance`]. generation: u64, - prefix: PathOwned, + prefix: Path, /// The absolute patterns the announcing producer may serve. The prefix is /// only the wire-visible covering claim; this scope remains authoritative. scope: Patterns, @@ -1376,14 +1376,14 @@ fn prefix_claim(prefix: &Path) -> Result { struct ServeState { // Result channels for pending requests, keyed by absolute path so concurrent // `request_broadcast` calls for the same path coalesce onto one channel. - requests: Requests>, + requests: Requests>, // Broadcasts the handler has already served, kept weakly so a repeat request for the // same path resolves to a shared clone instead of re-invoking the handler (which would // open a duplicate upstream subscription). Weak so a served broadcast still closes once // its real consumers drop. The cache reclaims closed entries incrementally on insert, so a // long-lived origin serving many distinct one-shot paths stays bounded by the live count. - served: WeakCache, + served: WeakCache, // How many times the route changed instance: a front compares it to the count when // it asked to tell a released request from an answered one. Shared outside the lock, @@ -1401,7 +1401,7 @@ struct ServeState { /// a route flowing back through one of its readers, nor a local view a peer's /// route. Every other requester shares the plain front, so fronts scale with /// the peers in the path's route chains rather than with viewer sessions. -type FrontKey = (PathOwned, Horizon); +type FrontKey = (Path, Horizon); /// Which routes a reader sees: the split-horizon exclusion and the local-only view. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] @@ -1498,7 +1498,7 @@ struct CursorRoute { /// Whether it serves requests: part of the dedupe key, but never delivered. served: bool, /// The absolute prefix it was announced at, which decides `captures`. - prefix: PathOwned, + prefix: Path, /// What the cursor's scope captured from `prefix`. captures: Option>, /// [`OriginState::next_route`] as it started. @@ -1519,13 +1519,13 @@ impl WeakEntry for RemoteFront { /// re-rooted, and the per-cursor delivery buffer. struct TableCursor { /// The prefix stripped from every delivered path. - root: PathOwned, + root: Path, /// The absolute patterns this cursor is scoped to (its token / scope). allowed: Patterns, /// Where the cursor hangs in the [`RouteTable`]: the literal heads of /// `allowed`. A route the cursor can see sits at or under one of them, or on /// the walk down to one. - heads: Vec, + heads: Vec, /// The routes this cursor may see (control-plane split horizon). horizon: Horizon, /// Which routes beneath a hidden segment are reported. @@ -1535,10 +1535,10 @@ struct TableCursor { state: kio::Producer, /// Where this cursor's presented prefixes land in the delivery buffer: empty, /// or the mount the cursor reads for, relative to the consumer's root. - under: PathOwned, + under: Path, /// The absolute prefixes a mount shadows: routes at or beneath them are not /// this cursor's to present. - holes: Vec, + holes: Vec, /// For a cursor reading through a mount: the mount and the handle's own /// patterns, so a wildcard captures what the handle names rather than the target. named: Option<(Mount, Patterns)>, @@ -1546,7 +1546,7 @@ struct TableCursor { interested: bool, /// The last delivered best route per presented (relative) prefix, for change /// detection. Servability is part of the dedupe key but never leaves the model. - current: HashMap, + current: HashMap, } impl TableCursor { @@ -1555,15 +1555,15 @@ impl TableCursor { /// `claim` is the prefix's [`prefix_claim`], which the caller already holds: /// building one allocates, and the sweeps below ask this per route per /// cursor. - fn presented(&self, prefix: &Path, claim: &Pattern) -> Option { + fn presented(&self, prefix: &Path, claim: &Pattern) -> Option { if !self.allowed.overlaps(claim) { return None; } if let Some(relative) = prefix.strip_prefix(&self.root) { - return Some(relative.to_owned()); + return Some(relative.clone()); } - self.root.has_prefix(prefix).then(PathOwned::default) + self.root.has_prefix(prefix).then(Path::default) } /// What the cursor's most specific matching scope member captures from an @@ -1609,7 +1609,7 @@ impl TableCursor { /// Deliver a changed winner at this presented prefix, as of `now`, the /// [`OriginState::next_route`]. - fn update(&mut self, presented: &PathOwned, best: Option<&RouteEntry>, now: u64, hold: &Hold) { + fn update(&mut self, presented: &Path, best: Option<&RouteEntry>, now: u64, hold: &Hold) { let Some(entry) = best else { if let Some(last) = self.current.remove(presented) && let Ok(mut state) = self.state.write() @@ -1709,22 +1709,22 @@ struct OriginScope { /// `target/rest`. Both absolute. #[derive(Clone, Debug)] struct Mount { - at: PathOwned, - target: PathOwned, + at: Path, + target: Path, } impl Mount { /// Where the handle-side absolute `path` resolves, when it is under this mount /// and the result fits [`Path::MAX_PARTS`]. - fn resolve(&self, path: &Path) -> Option { - let resolved = self.target.join(path.strip_prefix(&self.at)?); + fn resolve(&self, path: &Path) -> Option { + let resolved = self.target.join(&path.strip_prefix(&self.at)?); (resolved.parts().count() <= Path::MAX_PARTS).then_some(resolved) } /// Where the origin-side absolute `path` shows on the handle, when it is at or /// beneath the target. A route covering the target has no handle-side name. - fn name(&self, path: &Path) -> Option { - Some(self.at.join(path.strip_prefix(&self.target)?)) + fn name(&self, path: &Path) -> Option { + Some(self.at.join(&path.strip_prefix(&self.target)?)) } /// Whether the origin-side absolute `path` has a handle-side name within @@ -1763,9 +1763,9 @@ impl Mount { /// A handle-side interest head as an origin-side one: a head beneath the mount /// moves under the target, and one above it hangs at the target itself. - fn translate_head(&self, head: &Path) -> Option { + fn translate_head(&self, head: &Path) -> Option { match head.strip_prefix(&self.at) { - Some(rest) => Some(self.target.join(rest)), + Some(rest) => Some(self.target.join(&rest)), None => self.at.has_prefix(head).then(|| self.target.clone()), } } @@ -1801,10 +1801,10 @@ impl OriginScope { /// Where the absolute `path` resolves on the origin: itself, or its mount /// target. `None` when the mount would resolve it past [`Path::MAX_PARTS`]. - fn resolve<'a>(&self, path: &'a Path<'a>) -> Option> { + fn resolve(&self, path: &Path) -> Option { match self.mount(path) { Some(mount) => mount.resolve(path), - None => Some(path.borrow()), + None => Some(path.clone()), } } @@ -1836,10 +1836,10 @@ impl Default for OriginScope { /// The announce-interest prefixes that cover a pattern scope on a prefix-only /// wire: each member's literal head, minus heads another already covers. -pub(crate) fn interest_prefixes(allowed: &Patterns) -> Vec { - let mut heads: Vec = allowed +pub(crate) fn interest_prefixes(allowed: &Patterns) -> Vec { + let mut heads: Vec = allowed .iter() - .map(|pattern| Path::new(pattern.head()).to_owned()) + .map(|pattern| Path::new(pattern.head()).clone()) .collect(); heads.sort(); heads.dedup(); @@ -1859,22 +1859,22 @@ pub(super) struct Hidden { /// Report hidden routes too. include: bool, /// Measure visibility from the request prefix instead of authorization heads. - from: Option, + from: Option, /// Report only what a feed scoped to these heads hides, for a stream that tops /// up a feed already carrying everything visible from them. - beyond: Option>, + beyond: Option>, } impl Hidden { /// Whether a cursor hanging at `heads` reports a route at `prefix`. - fn discovers(&self, heads: &[PathOwned], prefix: &Path) -> bool { + fn discovers(&self, heads: &[Path], prefix: &Path) -> bool { self.reaches(heads, prefix) && self.beyond.as_ref().is_none_or(|outer| hides(outer, prefix)) } /// Whether a cursor hanging at `heads` could report a route at or beneath `prefix`. /// A hidden segment hides everything beneath it, while a top-up feed's rule turns /// on the rest of the path, so only the first can rule a prefix out. - fn reaches(&self, heads: &[PathOwned], prefix: &Path) -> bool { + fn reaches(&self, heads: &[Path], prefix: &Path) -> bool { self.include || !hides(self.from.as_ref().map(std::slice::from_ref).unwrap_or(heads), prefix) } @@ -1895,7 +1895,7 @@ impl Hidden { /// Whether a segment of `prefix` below the head it sits under starts with `.`. /// A route at or above a head has nothing below it, so it never hides. -fn hides(heads: &[PathOwned], prefix: &Path) -> bool { +fn hides(heads: &[Path], prefix: &Path) -> bool { heads .iter() .any(|head| prefix.strip_prefix(head).is_some_and(|below| below.is_hidden())) @@ -1929,7 +1929,7 @@ pub enum AnnounceEvent { #[derive(Clone, Debug)] pub struct Announce { /// The prefix the route covers, relative to the consuming cursor's root. - pub prefix: PathOwned, + pub prefix: Path, /// What the scope's wildcards stood for when the announced prefix pins all of /// them. `None` for an overlap-only route or a scope without a complete match. pub captures: Option>, @@ -1950,7 +1950,7 @@ pub struct Producer { scope: OriginScope, // The prefix that is automatically stripped from all paths. - root: PathOwned, + root: Path, // The origin's shared state: the route table, announce cursors, and the // remotely-served fronts. Shared with every derived consumer. @@ -2006,7 +2006,7 @@ impl Producer { hop: config.hop, max_hops: config.max_hops, scope: scope.clone(), - root: PathOwned::default(), + root: Path::default(), shared: shared.clone(), pool: config.pool, cache_duration: config.cache_duration, @@ -2113,7 +2113,7 @@ impl Producer { hop, scope: OriginScope::empty(), max_hops: HopLimit::default(), - root: PathOwned::default(), + root: Path::default(), shared: SendCell::new(kio::Shared::new(OriginState::new( DEFAULT_UPDATE_HOLD, Deadlines::new(Instant::ZERO), @@ -2161,8 +2161,8 @@ impl Producer { /// pattern can spell (`*` or `**`), [`Error::NavigationSegment`] for a literal `.` or `..`, /// or [`Error::Closed`] once the origin's /// [`Driver`] has been dropped. - pub fn create_broadcast(&self, path: impl AsPath) -> Result { - let full = self.root.join(path.as_path()).to_owned(); + pub fn create_broadcast(&self, path: impl Into) -> Result { + let full = self.root.join(&path.into()); let (broadcast, announcer) = self.create_withheld(full)?; Ok(Publication { broadcast: broadcast.with_announcer(announcer), @@ -2171,7 +2171,7 @@ impl Producer { /// A new broadcast at `full` with its announcer withheld, so it cannot announce /// until the announcer is handed back. - fn create_withheld(&self, full: PathOwned) -> Result<(broadcast::Producer, Announcer), Error> { + fn create_withheld(&self, full: Path) -> Result<(broadcast::Producer, Announcer), Error> { publish_path(&full)?; if !self.scope.permits(&full) || !self.scope.publishes(&full) { return Err(Error::Unauthorized); @@ -2229,7 +2229,7 @@ impl Producer { } /// Create and advertise a broadcast in one call. - pub fn publish(&self, path: impl AsPath, route: Route) -> Result { + pub fn publish(&self, path: impl Into, route: Route) -> Result { let broadcast = self.create_broadcast(path)?; broadcast.announce(route)?; Ok(broadcast) @@ -2244,11 +2244,11 @@ impl Producer { #[cfg_attr(not(feature = "async"), allow(dead_code, reason = "async-only"))] pub(crate) fn create_source( &self, - path: impl AsPath, + path: impl Into, validity: crate::model::broadcast::RequestValidity, ) -> broadcast::Producer { - let path = path.as_path(); - let full = self.root.join(&path).to_owned(); + let path: Path = path.into(); + let full = self.root.join(&path).clone(); let ingress = self.stats.ingress(&full); broadcast::Info { pool: self.pool.clone(), @@ -2271,7 +2271,7 @@ impl Producer { /// advertises through a broadcast ([`Publication::announce`]) or a /// [`Dynamic`] handler, which serve what they claim. #[cfg(test)] - pub(crate) fn announce(&self, prefix: impl AsPath, route: Route) -> Result { + pub(crate) fn announce(&self, prefix: impl Into, route: Route) -> Result { Announcing::new(self, prefix)?.announce( route, Serving { @@ -2286,8 +2286,8 @@ impl Producer { /// synced until the returned guard drops. Taken before the request goes out, so /// nothing reads as synced in between. #[cfg_attr(not(feature = "async"), allow(dead_code, reason = "async-only"))] - pub(crate) fn snapshot(&self, prefix: impl AsPath) -> Snapshot { - let prefix = self.root.join(prefix.as_path()).to_owned(); + pub(crate) fn snapshot(&self, prefix: impl Into) -> Snapshot { + let prefix = self.root.join(&prefix.into()); self.shared.lock().snapshot(&self.shared, prefix, self.link) } @@ -2361,17 +2361,17 @@ impl Producer { /// /// The prefix must overlap this producer's pattern scope. Individual requests /// remain authoritative and are refused when they do not match the scope. - pub fn dynamic(&self, prefix: impl AsPath) -> Result { + pub fn dynamic(&self, prefix: impl Into) -> Result { self.dynamic_inner(prefix, Route::default(), true) } /// Land a peer's claim with its wire route metadata. #[cfg(any(feature = "async", test))] - pub(crate) fn dynamic_route(&self, prefix: impl AsPath, route: Route) -> Result { + pub(crate) fn dynamic_route(&self, prefix: impl Into, route: Route) -> Result { self.dynamic_inner(prefix, route, false) } - fn dynamic_inner(&self, prefix: impl AsPath, route: Route, publishes: bool) -> Result { + fn dynamic_inner(&self, prefix: impl Into, route: Route, publishes: bool) -> Result { let announcing = Announcing::new(self, prefix)?; let serve = kio::Shared::::default(); serve.lock().requests.add_handler(); @@ -2397,8 +2397,8 @@ impl Producer { /// the new root. Returns [`Error::Unauthorized`] when the requested scope has /// no overlap with this producer's scope, or [`Error::BoundsExceeded`] when /// rooting the patterns would exceed the path limit. - pub fn scope(&self, root: impl AsPath, patterns: &Patterns) -> Result { - let root = self.root.join(root).to_owned(); + pub fn scope(&self, root: impl Into, patterns: &Patterns) -> Result { + let root = self.root.join(&root.into()); let rooted = patterns.rooted(root.as_str()).map_err(|_| BoundsExceeded)?; let scope = self.scope.narrow(&rooted).ok_or(Error::Unauthorized)?; Ok(Producer { @@ -2436,9 +2436,9 @@ impl Producer { /// so a target is always read as the origin holds it. Mounts may share a target. /// [`Error::BoundsExceeded`] if either rooted path exceeds [`Path::MAX_PARTS`], /// and [`Error::InvalidPath`] if `at` holds a segment no pattern can spell. - pub fn mount(&self, at: impl AsPath, target: impl AsPath) -> Result { - let at = self.root.join(at).to_owned(); - let target = self.root.join(target).to_owned(); + pub fn mount(&self, at: impl Into, target: impl Into) -> Result { + let at = self.root.join(&at.into()); + let target = self.root.join(&target.into()); if [&at, &target] .into_iter() .any(|path| path.parts().count() > Path::MAX_PARTS) @@ -2494,7 +2494,7 @@ impl Producer { } /// Returns the root that is automatically stripped from all paths. - pub fn root(&self) -> &Path<'_> { + pub fn root(&self) -> &Path { &self.root } @@ -2504,8 +2504,8 @@ impl Producer { } /// Converts a relative path to an absolute path. - pub fn absolute(&self, path: impl AsPath) -> Path<'_> { - self.root.join(path) + pub fn absolute(&self, path: impl Into) -> Path { + self.root.join(&path.into()) } } @@ -2516,11 +2516,11 @@ struct Announcing { hop: Hop, shared: SendCell>, /// The absolute advertised prefix, which also keys the ingress announce counters. - requested: PathOwned, + requested: Path, /// The prefix inserted into the table, with its [`prefix_claim`]. Pattern /// scopes decide visibility and request authorization without changing the /// route's prefix shape. - prefixes: Vec<(PathOwned, Pattern)>, + prefixes: Vec<(Path, Pattern)>, /// The absolute paths the producer is authorized to serve. scope: Patterns, local: bool, @@ -2533,8 +2533,8 @@ struct Announcing { impl Announcing { /// The requested prefix as-is, refused when its subtree does not overlap the scope. - fn new(producer: &Producer, prefix: impl AsPath) -> Result { - let requested = producer.root.join(prefix.as_path()).to_owned(); + fn new(producer: &Producer, prefix: impl Into) -> Result { + let requested = producer.root.join(&prefix.into()); publish_path(&requested)?; if requested.parts().count() > Path::MAX_PARTS { return Err(BoundsExceeded.into()); @@ -2717,7 +2717,7 @@ pub(crate) struct AnnounceProducer { shared: SendCell>, /// The table entries this advertisement created, by prefix and id. A prefix /// remains unchanged; pattern scopes only filter its visibility and requests. - entries: Vec<(PathOwned, u64)>, + entries: Vec<(Path, u64)>, /// Ingress announce stats guard, held only while the entries are advertised. guard: Option, } @@ -3029,7 +3029,7 @@ struct FrontTask { /// The broadcast the front serves. broadcast: broadcast::Producer, /// Absolute path of the front. - path: PathOwned, + path: Path, /// The requesters' horizon, applied to every (re)selection. horizon: Horizon, /// Wakes the front when a route covering its path changes. @@ -3302,9 +3302,9 @@ async fn serve_front(task: FrontTask) -> Vec { } // Read alongside the decision, under the lock a poke takes first. *seen = watch.seen(); - front.retain_routes(|route| table.routes.covers(&path.as_path(), route)); + front.retain_routes(|route| table.routes.covers(&path, route)); let instance = resolved.as_ref().map(|resolved| &resolved.instance); - let best = pick(&table, &path.as_path(), horizon, front, instance); + let best = pick(&table, &path, horizon, front, instance); // Under the table's lock, which a request reads it under too: a request that // sees another winner from here on mints its own front rather than join this one. if resolved.is_none() @@ -3330,7 +3330,7 @@ async fn serve_front(task: FrontTask) -> Vec { let joined = joined.load(Ordering::Acquire); let renew = resolved .as_mut() - .and_then(|resolved| resolved.moved(&table, &path.as_path(), horizon, joined)); + .and_then(|resolved| resolved.moved(&table, &path, horizon, joined)); drop(table); if let Some(prefix) = renew { // Checked under the lock that renews: a restart after the read covers the move. @@ -3352,7 +3352,7 @@ async fn serve_front(task: FrontTask) -> Vec { let standing = |front: &Front, route: u64, resolved: &Option| { let table = shared.read(); let instance = resolved.as_ref().map(|resolved| &resolved.instance); - pick(&table, &path.as_path(), horizon, front, instance).is_some_and(|best| best.id == route) + pick(&table, &path, horizon, front, instance).is_some_and(|best| best.id == route) }; // Whether an answer `route` gave for `asked` would serve another instance than the @@ -3360,7 +3360,7 @@ async fn serve_front(task: FrontTask) -> Vec { // instance won the path while the request was in flight. let stale = |front: &Front, route: u64, asked: &Instance, resolved: &Option| { let table = shared.read(); - let Some(entry) = table.routes.covering(&path.as_path()).find(|entry| entry.id == route) else { + let Some(entry) = table.routes.covering(&path).find(|entry| entry.id == route) else { return false; }; if entry.instance() != *asked { @@ -3369,7 +3369,7 @@ async fn serve_front(task: FrontTask) -> Vec { match resolved { Some(resolved) => resolved.instance != *asked, None => table - .best_route(&path.as_path(), horizon, front.excluded_routes(), |_| true) + .best_route(&path, horizon, front.excluded_routes(), |_| true) .is_some_and(|best| best.instance() != *asked), } }; @@ -3393,7 +3393,7 @@ async fn serve_front(task: FrontTask) -> Vec { let table = shared.read(); table .routes - .covering(&path.as_path()) + .covering(&path) .find(|entry| entry.id == route && entry.live()) .map(|entry| (entry.source.clone(), entry.server.clone(), entry.instance())) }; @@ -3520,7 +3520,7 @@ async fn serve_front(task: FrontTask) -> Vec { let prefix = shared .read() .routes - .covering(&path.as_path()) + .covering(&path) .find(|entry| entry.id == route) .map(|entry| entry.prefix.clone()); resolved = Some(Resolution { @@ -4000,7 +4000,7 @@ async fn serve_front(task: FrontTask) -> Vec { shared .read() .routes - .covering(&path.as_path()) + .covering(&path) .find(|entry| entry.id == route) .is_some_and(|entry| entry.prefix != path) }); @@ -4135,11 +4135,11 @@ enum WatchProducer { } struct Selected { - path: PathOwned, + path: Path, via: Hop, instance: Instance, /// The prefix of the route the instance came through. - prefix: PathOwned, + prefix: Path, /// The selection period, closed when the peer stops selecting the instance. current: Option>, /// The [`Release`] that ends `current`: the peer replaced the instance with a @@ -4200,7 +4200,7 @@ impl Selected { #[cfg_attr(not(feature = "async"), allow(dead_code, reason = "async-only"))] pub(crate) struct Selection { shared: SendCell>, - path: PathOwned, + path: Path, id: u64, signal: kio::Consumer, } @@ -4243,7 +4243,7 @@ impl Drop for Selection { /// holding it. struct Watch { shared: SendCell>, - path: PathOwned, + path: Path, id: u64, signal: kio::Consumer, } @@ -4281,7 +4281,7 @@ struct SnapshotEntry { /// The prefix's [`prefix_claim`], so a cursor whose patterns miss it never waits on it. claim: Pattern, /// The absolute prefix, so a cursor that could see nothing beneath it never waits on it. - prefix: PathOwned, + prefix: Path, } /// A session still delivering its peer's initial announcements under a prefix. @@ -4291,7 +4291,7 @@ struct SnapshotEntry { /// has inserted the announcements its peer counted, or it gave up on them. pub(crate) struct Snapshot { shared: Option>>, - prefix: PathOwned, + prefix: Path, id: u64, } @@ -4301,7 +4301,7 @@ impl Snapshot { pub(crate) fn landed() -> Self { Self { shared: None, - prefix: PathOwned::default(), + prefix: Path::default(), id: 0, } } @@ -4320,12 +4320,12 @@ impl Drop for Snapshot { /// Dropping it takes the table lock: never drop one while holding it. struct Interest { shared: SendCell>, - prefix: PathOwned, + prefix: Path, } impl Interest { /// Count `prefix` under the held table lock. - fn new(state: &mut OriginState, shared: &SendCell>, prefix: PathOwned) -> Self { + fn new(state: &mut OriginState, shared: &SendCell>, prefix: Path) -> Self { state.add_interest(shared, &prefix); Self { shared: shared.clone(), @@ -4344,9 +4344,9 @@ impl Drop for Interest { struct Listener { id: u64, /// The session's root, absolute: what it asks about is named relative to it. - root: PathOwned, + root: Path, /// The broadest prefixes the session may ask about, relative to `root`. - heads: Vec, + heads: Vec, /// The absolute patterns the session may publish under. allowed: Patterns, /// The link the session's routes arrive on, which its snapshots wait for. @@ -4358,7 +4358,7 @@ impl Listener { /// What the session asks about, relative to its root, for an interest at the /// absolute `prefix`: each head an interest above it covers, since the scope /// may ask for no more, or the prefix itself inside a head. - fn asks(&self, prefix: &Path) -> Vec { + fn asks(&self, prefix: &Path) -> Vec { let Some(relative) = prefix.strip_prefix(&self.root) else { return match self.root.has_prefix(prefix) { true => self.heads.clone(), @@ -4372,7 +4372,7 @@ impl Listener { } else if relative.has_prefix(head) { let reaches = Pattern::subtree(prefix.as_str()).is_ok_and(|subtree| self.allowed.overlaps(&subtree)); return match reaches { - true => vec![relative.to_owned()], + true => vec![relative.clone()], false => Vec::new(), }; } @@ -4412,7 +4412,7 @@ impl Drop for Interests { )] pub(crate) struct InterestChange { /// The prefix, relative to the session's root. - pub prefix: PathOwned, + pub prefix: Path, /// Whether readers now want the prefix discovered, or no longer do. pub wanted: bool, /// Set when wanted: holds the readers under the prefix short of synced from @@ -4588,12 +4588,12 @@ impl RouteNode { /// Collect the broadest interests strictly beneath this node, which sits at /// `at`: each one no other interest beneath this node covers. - fn collect_interests(&self, at: &Path, out: &mut Vec) { + fn collect_interests(&self, at: &Path, out: &mut Vec) { for (part, child) in &self.children { if child.interests_below == 0 { continue; } - let path = at.join(part.as_str()); + let path = at.join(&Path::new(part)); match child.interests > 0 { true => out.push(path), false => child.collect_interests(&path, out), @@ -4775,12 +4775,12 @@ impl RouteTable { /// The interests at `path` or, failing that, the broadest beneath it: what a /// session joining now asks about under `path`. - fn interests_from(&self, path: &Path) -> Vec { + fn interests_from(&self, path: &Path) -> Vec { let Some(node) = self.root.find(path.parts()) else { return Vec::new(); }; if node.interests > 0 { - return vec![path.to_owned()]; + return vec![path.clone()]; } let mut out = Vec::new(); node.collect_interests(path, &mut out); @@ -4850,7 +4850,7 @@ struct OriginState { // Peers that withdrew a prefix while routes there still passed through them, // which are stale. See [`Dynamic::withdrawn`]. - withdrawn: HashMap>, + withdrawn: HashMap>, // Set when the origin's driver dropped: new requests fail with `Closed` // immediately and handlers observe the end instead of parking forever. @@ -4909,7 +4909,7 @@ impl OriginState { /// A route at `prefix` was announced or restarted with `hops`. When its sender, /// the chain's last hop, had withdrawn the prefix, routes through it are live /// once more. - fn reannounced(&mut self, prefix: &PathOwned, hops: &Hops) { + fn reannounced(&mut self, prefix: &Path, hops: &Hops) { if let Some(sender) = hops.iter().last() && self.withdrawn.get_mut(prefix).is_some_and(|peers| peers.remove(sender)) { @@ -4919,7 +4919,7 @@ impl OriginState { } /// Forget the withdrawals at `prefix` that no longer hide a route. - fn prune_withdrawn(&mut self, prefix: &PathOwned) { + fn prune_withdrawn(&mut self, prefix: &Path) { if self.withdrawn.is_empty() { return; } @@ -5006,7 +5006,7 @@ impl OriginState { } /// Start a [`Snapshot`] at the absolute `prefix` for the routes arriving on `link`. - fn snapshot(&mut self, shared: &SendCell>, prefix: PathOwned, link: Link) -> Snapshot { + fn snapshot(&mut self, shared: &SendCell>, prefix: Path, link: Link) -> Snapshot { // A prefix past the depth limit can carry no route, so nothing waits on it. let Ok(claim) = prefix_claim(&prefix) else { return Snapshot::landed(); @@ -5108,7 +5108,7 @@ impl OriginState { let signal = self.routes.add_watch(path, id); Watch { shared: shared.clone(), - path: path.to_owned(), + path: path.clone(), id, signal, } @@ -5116,7 +5116,7 @@ impl OriginState { /// Recompute the best visible route presenting at `presented` (relative) for /// one cursor and deliver the change, if any, as of `now`. - fn sync_cursor(routes: &RouteTable, cursor: &mut TableCursor, presented: &PathOwned, now: u64, hold: &Hold) { + fn sync_cursor(routes: &RouteTable, cursor: &mut TableCursor, presented: &Path, now: u64, hold: &Hold) { // The entries presenting here are the ones announced at the absolute // prefix, or, for the cursor's own root, at the root and every prefix // above it (all of which present as the empty path). Among them, the @@ -5150,7 +5150,7 @@ impl OriginState { // The routes a cursor can see sit on the walk down to one of its heads or // somewhere beneath it, so only those subtrees are replayed. - let mut presented: BTreeSet = BTreeSet::new(); + let mut presented: BTreeSet = BTreeSet::new(); for head in &cursor.heads { let (above, at) = self.routes.split(head); let mut nodes = above; @@ -5328,7 +5328,7 @@ struct Resolution { instance: Instance, route: u64, /// That route's prefix, when it was still in the table as the front resolved. - prefix: Option, + prefix: Option, /// The [`RemoteFront::joined`] the prefix last restarted for the path's move, by /// a renewal or on its own; see [`Self::moved`]. settled: Option, @@ -5344,7 +5344,7 @@ impl Resolution { /// /// Settled once per join: a renewal leaves the front's instance behind, so /// nobody joins it after. - fn moved(&mut self, table: &OriginState, path: &Path, horizon: Horizon, joined: u64) -> Option { + fn moved(&mut self, table: &OriginState, path: &Path, horizon: Horizon, joined: u64) -> Option { if self.settled == Some(joined) || !matches!(self.instance, Instance::Route(_)) { return None; } @@ -5410,7 +5410,7 @@ impl Dynamic { .best_route(path, Horizon::default(), &HashSet::new(), |entry| entry.via == via) .is_some_and(|entry| entry.instance() == instance); let producer = kio::Producer::new(Selected { - path: path.to_owned(), + path: path.clone(), via, instance, prefix, @@ -5428,7 +5428,7 @@ impl Dynamic { .push((id, WatchProducer::Selection(producer))); Some(Selection { shared: shared.clone(), - path: path.to_owned(), + path: path.clone(), id, signal, }) @@ -5530,7 +5530,7 @@ impl ServeState { /// still held. fn resolve( shared: &kio::Shared, - path: &PathOwned, + path: &Path, producer: &kio::Producer, result: Result, ) -> bool { @@ -5549,7 +5549,7 @@ impl ServeState { /// its requesters resolve with, or `None` once it is no longer ours to answer. fn answer( &mut self, - path: &PathOwned, + path: &Path, producer: &kio::Producer, result: Result, ) -> Option> { @@ -5598,7 +5598,7 @@ impl ServeState { } /// Drop the still-pending entry, if it is still ours. - fn forget(shared: &kio::Shared, path: &PathOwned, producer: &kio::Producer) { + fn forget(shared: &kio::Shared, path: &Path, producer: &kio::Producer) { shared.lock().requests.remove_if(path, |p| p.same_channel(producer)); } } @@ -5619,7 +5619,7 @@ impl ServeState { /// ``` pub struct Request { // Absolute path that was requested. - path: PathOwned, + path: Path, // Result channel back to the awaiting requester(s). Writing `resolved` and dropping // this wakes them with the outcome. @@ -5636,7 +5636,7 @@ pub struct Request { impl Request { /// The absolute path that was requested. - pub fn path(&self) -> &Path<'_> { + pub fn path(&self) -> &Path { &self.path } @@ -5740,7 +5740,7 @@ pub struct Requesting { // The path the requester asked for, relative to its cursor's root. Stamped on the // resolved broadcast (see [`broadcast::Info::path`]) because a handler is free to // serve a broadcast created somewhere else entirely, or at no path at all. - path: PathOwned, + path: Path, // Egress scope applied to the resolved broadcast, so its reads are attributed. // Empty (no-op) for an untagged consumer. stats: stats::Scope, @@ -5785,7 +5785,7 @@ impl Requesting { fn new(inner: RequestState) -> Self { Self { inner, - path: PathOwned::default(), + path: Path::default(), stats: stats::Scope::default(), epoch: None, } @@ -5797,7 +5797,7 @@ impl Requesting { self } - fn with_path(mut self, path: PathOwned) -> Self { + fn with_path(mut self, path: Path) -> Self { self.path = path; self } @@ -5851,7 +5851,7 @@ impl kio::Task for Requesting { /// The pollable result of [`Consumer::routed_broadcast`]. pub struct Routing { consumer: Consumer, - path: PathOwned, + path: Path, // Asks sessions to discover the path for as long as this waits on it. interest: Option, // The covering routes, watched since the generation the last ask saw. @@ -5897,14 +5897,14 @@ impl kio::Task for Routing { let watch = table.watch(&self.consumer.shared, &resolved); let seen = watch.seen(); if self.interest.is_none() && self.consumer.interested { - let resolved = resolved.to_owned(); + let resolved = resolved.clone(); self.interest = Some(Interest::new(&mut table, &self.consumer.shared, resolved)); } drop(table); self.watch = Some((watch, seen)); self.request = Some( self.consumer - .request(self.path.as_path(), self.epoch.as_ref()) + .request(self.path.clone(), self.epoch.as_ref()) .into_inner(), ); } @@ -5983,7 +5983,7 @@ pub struct Consumer { scope: OriginScope, // A prefix that is automatically stripped from all paths. - root: PathOwned, + root: Path, // The origin's shared state: the route table, announce cursors, and the // remotely-served fronts. @@ -6178,12 +6178,12 @@ impl Consumer { /// Drop the returned [`AnnounceConsumer`] to unregister. pub fn announced(&self) -> AnnounceConsumer { let state = kio::Producer::::default(); - let cursor = |root: PathOwned, + let cursor = |root: Path, allowed: Patterns, hidden: Hidden, mount: Option<&Mount>, - under: PathOwned, - holes: Vec| TableCursor { + under: Path, + holes: Vec| TableCursor { root, heads: interest_prefixes(&allowed), allowed, @@ -6216,7 +6216,7 @@ impl Consumer { mount.translate(&self.scope.allowed), self.hidden.translate(mount), Some(mount), - PathOwned::default(), + Path::default(), Vec::new(), )]; return AnnounceConsumer::new( @@ -6255,7 +6255,7 @@ impl Consumer { allowed, self.hidden.translate(mount), Some(mount), - under.to_owned(), + under.clone(), Vec::new(), )); } @@ -6266,7 +6266,7 @@ impl Consumer { self.scope.allowed.clone(), self.hidden.clone(), None, - PathOwned::default(), + Path::default(), holes, ), ); @@ -6288,8 +6288,8 @@ impl Consumer { /// The newest broadcast published on this origin at exactly `path`, if any. /// Test-only: a request goes through the table like any other. #[cfg(test)] - pub(crate) fn get_broadcast(&self, path: impl AsPath) -> Option { - let full = self.root.join(path).to_owned(); + pub(crate) fn get_broadcast(&self, path: impl Into) -> Option { + let full = self.root.join(&path.into()); if !self.scope.permits(&full) { return None; } @@ -6313,7 +6313,7 @@ impl Consumer { /// [`Self::routed_broadcast`]: pairing this with [`Self::request_broadcast`] /// leaves a gap where the covering route can retract. #[cfg(feature = "async")] - pub async fn routed(&self, path: impl AsPath) -> Option { + pub async fn routed(&self, path: impl Into) -> Option { // A follower's first event is always a start. match self.follow(path).ok()?.next().await? { AnnounceEvent::Start(announce) | AnnounceEvent::Update(announce) | AnnounceEvent::Restart(announce) => { @@ -6332,8 +6332,8 @@ impl Consumer { /// over, which subscriptions already ride out. Fails with [`Error::Unauthorized`] when /// this consumer's scope can never cover the path, and with [`Error::InvalidPath`] when no /// pattern can spell it (a segment containing `*`). - pub fn follow(&self, path: impl AsPath) -> Result { - let path = path.as_path(); + pub fn follow(&self, path: impl Into) -> Result { + let path: Path = path.into(); // Scope down to the path itself, which still sees every route whose claim covers it: // the rest of the origin never wakes it, and a route claiming only paths beneath it @@ -6351,7 +6351,7 @@ impl Consumer { // the announce guards. Hiding narrows discovery, not lookup, so a hidden path // follows like any other. let announced = consumer.untagged().with_hidden(true).announced(); - Ok(crate::announce::Follow::new(announced, path.to_owned())) + Ok(crate::announce::Follow::new(announced, path.clone())) } /// Resolve `path` to a broadcast: [`Self::request_broadcast`], retried @@ -6367,12 +6367,12 @@ impl Consumer { /// [`Error::Unauthorized`] for a path outside this consumer's scope, /// [`Error::Closed`] once the origin closes, and any other resolution /// failure as-is. Await it, or poll it through [`kio::Task`]. - pub fn routed_broadcast(&self, path: impl AsPath) -> kio::Pending { - kio::Pending::new(self.routing(path.as_path().to_owned(), None)) + pub fn routed_broadcast(&self, path: impl Into) -> kio::Pending { + kio::Pending::new(self.routing(path.into(), None)) } /// [`Self::routed_broadcast`], refusing any instance but `epoch` when one is pinned. - pub(super) fn routing(&self, path: PathOwned, epoch: Option) -> Routing { + pub(super) fn routing(&self, path: Path, epoch: Option) -> Routing { // `allowed` keeps narrower permissions intact: if the whole path is not // reachable, no route can ever cover it, so bail rather than loop forever. let request = (!self.allowed().matches(path.as_str())).then(|| Requesting::failed(Error::Unauthorized)); @@ -6402,14 +6402,14 @@ impl Consumer { /// cover the path, and with [`Error::InvalidPath`] when no pattern can spell it. pub fn announced_broadcast( &self, - path: impl AsPath, + path: impl Into, epoch: impl Into>, ) -> Result { - let path = path.as_path(); + let path: Path = path.into(); let follow = self.follow(&path)?; Ok(AnnouncedBroadcast::new( self.clone(), - path.to_owned(), + path.clone(), epoch.into(), follow, )) @@ -6421,8 +6421,8 @@ impl Consumer { /// the new root. Returns [`Error::Unauthorized`] when the requested scope has /// no overlap with this consumer's scope, or [`Error::BoundsExceeded`] when /// rooting the patterns would exceed the path limit. - pub fn scope(&self, root: impl AsPath, patterns: &Patterns) -> Result { - let root = self.root.join(root).to_owned(); + pub fn scope(&self, root: impl Into, patterns: &Patterns) -> Result { + let root = self.root.join(&root.into()); let rooted = patterns.rooted(root.as_str()).map_err(|_| BoundsExceeded)?; let scope = self.scope.narrow(&rooted).ok_or(Error::Unauthorized)?; Ok(Consumer { @@ -6475,26 +6475,26 @@ impl Consumer { /// [`Error::NotFound`] on its tracks instead. pub fn request_broadcast( &self, - path: impl AsPath, + path: impl Into, epoch: impl Into>, ) -> kio::Pending { let epoch = epoch.into(); - self.request(path.as_path(), epoch.as_ref()) + self.request(path.into(), epoch.as_ref()) } /// [`Self::request_broadcast`], refused with [`Error::Unroutable`] unless the /// winning route serves `epoch`: a peer asking for one publisher instance must /// not be handed another. - pub(crate) fn request(&self, path: Path<'_>, epoch: Option<&crate::Epoch>) -> kio::Pending { + pub(crate) fn request(&self, path: Path, epoch: Option<&crate::Epoch>) -> kio::Pending { // The path as this handle names it, absolute: what its scope authorizes and // what its egress is counted under, so a read through a mount bills where // the reader asked. - let named = self.root.join(&path).to_owned(); + let named = self.root.join(&path).clone(); let scope = self.stats.egress(&named); // The resolved handle is named by what *this* cursor asked for, not by the absolute // path: a rooted cursor cannot name anything above its own root, so that is what a // catalog it reads may reference. - let requested = path.to_owned(); + let requested = path.clone(); // Routes only cover paths within this consumer's scope. if !self.scope.permits(&named) { @@ -6503,7 +6503,7 @@ impl Consumer { // Key requests by the absolute path on the origin, past any mount, so scoped, // rooted, and mounted consumers and handlers agree on the same entry and front. - let Some(absolute) = self.scope.resolve(&named).map(|path| path.to_owned()) else { + let Some(absolute) = self.scope.resolve(&named).map(|path| path.clone()) else { return kio::Pending::new(Requesting::failed(BoundsExceeded.into())); }; @@ -6514,12 +6514,12 @@ impl Consumer { return kio::Pending::new(Requesting::failed(Error::Closed)); } - let horizon = self.horizon.effective(&state.routes, &absolute.as_path()); + let horizon = self.horizon.effective(&state.routes, &absolute); // Nothing serves the path: no announced broadcast and no served route. // Checked before joining a front, so a front still draining after its // route retracted takes no newcomers. - let best = match state.best_route(&absolute.as_path(), horizon, &HashSet::new(), |_| true) { + let best = match state.best_route(&absolute, horizon, &HashSet::new(), |_| true) { Some(best) if epoch.is_none_or(|epoch| best.epoch.as_ref() == Some(epoch)) => best.instance(), _ => return kio::Pending::new(Requesting::failed(Error::Unroutable)), }; @@ -6614,7 +6614,7 @@ impl Consumer { } /// Returns the prefix that is automatically stripped from all paths. - pub fn root(&self) -> &Path<'_> { + pub fn root(&self) -> &Path { &self.root } @@ -6624,8 +6624,8 @@ impl Consumer { } /// Converts a relative path to an absolute path. - pub fn absolute(&self, path: impl AsPath) -> Path<'_> { - self.root.join(path) + pub fn absolute(&self, path: impl Into) -> Path { + self.root.join(&path.into()) } } @@ -6638,7 +6638,7 @@ pub struct AnnounceConsumer { // consumer reads through a mount. ids: Vec, shared: SendCell>, - root: PathOwned, + root: Path, // Pending updates queued for this cursor. Coalesced so a slow consumer // can't accumulate redundant announce/retract pairs. @@ -6651,7 +6651,7 @@ pub struct AnnounceConsumer { // Live egress announce guards, keyed by absolute prefix. An announce // opens one (bumping `announces_started` + `announced_bytes`); the matching retraction // drops it (bumping `announces_ended` + `announced_bytes`). - guards: HashMap, + guards: HashMap, // Holds the waiter a `Stream` poll registered; disjoint from `state` so the // borrow never collides with the body's. @@ -6663,9 +6663,9 @@ pub struct AnnounceConsumer { update_hold: Duration, // Quarantines by prefix, and their retractions in clock order: first waiting // for a fresh reading (by the stale one), then quarantined (by when each ends). - retracted: HashMap, - anchoring: VecDeque<(Instant, PathOwned)>, - expiring: VecDeque<(Instant, PathOwned)>, + retracted: HashMap, + anchoring: VecDeque<(Instant, Path)>, + expiring: VecDeque<(Instant, Path)>, timers: Deadlines, hold: crate::time::Deadline, } @@ -6687,16 +6687,16 @@ impl AnnounceConsumer { } #[cfg(test)] - pub(crate) async fn expect_next_active(&mut self, expected: impl AsPath) -> Route { + pub(crate) async fn expect_next_active(&mut self, expected: impl Into) -> Route { let event = self.expect_next().await; - Self::active(event, &expected.as_path()) + Self::active(event, &expected.into()) } #[cfg(test)] - pub(crate) async fn expect_next_restarted(&mut self, expected: impl AsPath) -> Route { + pub(crate) async fn expect_next_restarted(&mut self, expected: impl Into) -> Route { match self.expect_next().await { AnnounceEvent::Restart(announce) => { - assert_eq!(announce.prefix, expected.as_path(), "wrong prefix"); + assert_eq!(announce.prefix, expected.into(), "wrong prefix"); announce.route } other => panic!("should be a restart: got {other:?}"), @@ -6704,15 +6704,15 @@ impl AnnounceConsumer { } #[cfg(test)] - pub(crate) async fn expect_next_ended(&mut self, expected: impl AsPath) { + pub(crate) async fn expect_next_ended(&mut self, expected: impl Into) { match self.expect_next().await { - AnnounceEvent::End(announce) => assert_eq!(announce.prefix, expected.as_path(), "wrong prefix"), + AnnounceEvent::End(announce) => assert_eq!(announce.prefix, expected.into(), "wrong prefix"), other => panic!("should be a retraction: got {other:?}"), } } fn new( - root: PathOwned, + root: Path, cursors: Vec, state: kio::Producer, stats: stats::Session, @@ -6982,8 +6982,8 @@ impl AnnounceConsumer { #[cfg(feature = "async")] pub(crate) fn replay<'a>( &mut self, - advertised: impl IntoIterator, - revoked: &HashSet, + advertised: impl IntoIterator, + revoked: &HashSet, ) { let advertised: HashSet<_> = advertised.into_iter().collect(); let table = self.shared.lock(); @@ -7020,13 +7020,13 @@ impl AnnounceConsumer { } /// Returns the root that is automatically stripped from emitted prefixes. - pub fn root(&self) -> &Path<'_> { + pub fn root(&self) -> &Path { &self.root } /// Converts an emitted prefix back to one rooted at the origin. - pub fn absolute(&self, prefix: impl AsPath) -> Path<'_> { - self.root.join(prefix) + pub fn absolute(&self, prefix: impl Into) -> Path { + self.root.join(&prefix.into()) } } @@ -7075,29 +7075,29 @@ impl AnnounceConsumer { } /// The next update must be an active route at `expected`; returns it. - pub fn assert_next_active(&mut self, expected: impl AsPath) -> Route { + pub fn assert_next_active(&mut self, expected: impl Into) -> Route { let event = kio::wait(|waiter| self.poll_next(waiter)) .now_or_never() .expect("next blocked") .expect("no next"); - Self::active(event, &expected.as_path()) + Self::active(event, &expected.into()) } /// The `try_next` counterpart of [`Self::assert_next_active`]. - pub fn assert_try_next_active(&mut self, expected: impl AsPath) -> Route { + pub fn assert_try_next_active(&mut self, expected: impl Into) -> Route { let event = self.try_next().expect("no next"); - Self::active(event, &expected.as_path()) + Self::active(event, &expected.into()) } /// The next update must be a restart at `expected`; returns its route. - pub fn assert_next_restarted(&mut self, expected: impl AsPath) -> Route { + pub fn assert_next_restarted(&mut self, expected: impl Into) -> Route { match kio::wait(|waiter| self.poll_next(waiter)) .now_or_never() .expect("next blocked") .expect("no next") { AnnounceEvent::Restart(announce) => { - assert_eq!(announce.prefix, expected.as_path(), "wrong prefix"); + assert_eq!(announce.prefix, expected.into(), "wrong prefix"); announce.route } other => panic!("should be a restart: got {other:?}"), @@ -7105,13 +7105,13 @@ impl AnnounceConsumer { } /// The next update must be a retraction at `expected`. - pub fn assert_next_ended(&mut self, expected: impl AsPath) { + pub fn assert_next_ended(&mut self, expected: impl Into) { match kio::wait(|waiter| self.poll_next(waiter)) .now_or_never() .expect("next blocked") .expect("no next") { - AnnounceEvent::End(announce) => assert_eq!(announce.prefix, expected.as_path(), "wrong prefix"), + AnnounceEvent::End(announce) => assert_eq!(announce.prefix, expected.into(), "wrong prefix"), other => panic!("should be a retraction: got {other:?}"), } } @@ -8100,9 +8100,9 @@ mod tests { let table = producer.shared.read(); (0..PATHS) .map(|i| { - let path = Path::new(&format!("pool/job-{i}")).to_owned(); + let path = Path::new(&format!("pool/job-{i}")).clone(); let entry = table - .best_route(&path.as_path(), Horizon::default(), &HashSet::new(), |_| true) + .best_route(&path, Horizon::default(), &HashSet::new(), |_| true) .expect("the pool serves every path"); entry.hops.iter().next().copied().unwrap() }) @@ -9317,7 +9317,7 @@ mod tests { let producer = origin(1).produce(); let consumer = producer.consume(); let _published = producer.publish("room/alice", Route::default()).unwrap(); - let path = consumer.absolute("room/alice").to_owned(); + let path = consumer.absolute("room/alice").clone(); let broadcast = broadcast::Info::new().produce(); // Held, so the front waits on its holder rather than retiring on its own. let holder = broadcast.consume(); @@ -11464,7 +11464,7 @@ mod tests { matches!(pinned.await, Err(Error::Unroutable)), "a request pinned to the old epoch resolved" ); - let cached = dynamic.state.lock().served.get(&Path::new("room/alice").to_owned()); + let cached = dynamic.state.lock().served.get(&Path::new("room/alice").clone()); assert!( cached.is_some_and(|cached| cached.consume().is_clone(&fresh.consume())), "the old answer was cached" diff --git a/rs/moq-net/src/path/mod.rs b/rs/moq-net/src/path/mod.rs index 7280b916ffe..414cb207d20 100644 --- a/rs/moq-net/src/path/mod.rs +++ b/rs/moq-net/src/path/mod.rs @@ -10,66 +10,51 @@ pub use moq_pattern::{InvalidPattern, Pattern, Patterns, Segment, Specificity}; -use std::borrow::Cow; use std::fmt::{self, Display}; use std::sync::Arc; use crate::coding::{Decode, DecodeError, Decoder, Encode, EncodeError, Encoder}; -/// An owned version of [`Path`] with a `'static` lifetime. -pub type PathOwned = Path<'static>; +// Paths up to this many bytes live inline, which keeps `Path` at 24 bytes. +const INLINE: usize = 22; -/// A trait for types that can be converted to a `Path`. +/// Inline bytes, a static literal, or a suffix of a shared reference-counted buffer. /// -/// When providing a String/str, any leading/trailing slashes are trimmed and multiple consecutive slashes are collapsed. -/// When already a Path, normalization is skipped and the underlying buffer is reused without copying. -pub trait AsPath { - /// Borrow `self` as a [`Path`], normalizing slashes only when needed. - fn as_path(&self) -> Path<'_>; -} - -impl<'a> AsPath for &'a str { - fn as_path(&self) -> Path<'a> { - Path::new(self) - } -} - -impl<'a> AsPath for &'a Path<'a> { - fn as_path(&self) -> Path<'a> { - // We don't normalize again nor do we copy the bytes. - self.borrow() - } +/// Short paths copy inline, so cloning one is a memcpy with no atomics. A long path shares +/// one allocation: cloning bumps a refcount and suffix operations like `strip_prefix` only +/// advance `start`, so one allocation serves every copy of a path as it fans out. +#[derive(Clone)] +enum Repr { + Inline { len: u8, buf: [u8; INLINE] }, + Static(&'static str), + Shared { start: u32, buf: Arc }, } -impl AsPath for Path<'_> { - fn as_path(&self) -> Path<'_> { - self.borrow() - } -} +const _: () = assert!(std::mem::size_of::() == 24); -impl AsPath for String { - fn as_path(&self) -> Path<'_> { - Path::new(self) +impl Repr { + fn copy(s: &str) -> Self { + if s.len() <= INLINE { + let mut buf = [0; INLINE]; + buf[..s.len()].copy_from_slice(s.as_bytes()); + Repr::Inline { + len: s.len() as u8, + buf, + } + } else { + Repr::Shared { start: 0, buf: s.into() } + } } -} -impl<'a> AsPath for &'a String { - fn as_path(&self) -> Path<'a> { - Path::new(self) + fn string(s: String) -> Self { + if s.len() <= INLINE { + Self::copy(&s) + } else { + Repr::Shared { start: 0, buf: s.into() } + } } } -/// A borrowed slice of the path, or a suffix of a shared reference-counted buffer. -/// -/// The `Shared` variant is what makes owned paths cheap: cloning bumps a refcount and -/// suffix operations (strip_prefix) only advance `start`, so one allocation -/// serves every copy of a path as it fans out to consumers. -#[derive(Clone)] -enum Repr<'a> { - Borrowed(&'a str), - Shared { buf: Arc, start: usize }, -} - /// A broadcast path that provides safe prefix matching operations. /// /// This type wraps a string but provides path-aware operations that respect @@ -81,31 +66,28 @@ enum Repr<'a> { /// an exact broadcast and the prefix a route or announcement covers, so a broadcast's /// own path is the prefix it announces. See [`Relative`] for `..`-style references. /// -/// Owned paths ([`PathOwned`]) share one reference-counted allocation: cloning, converting -/// a shared path with [`Path::to_owned`], and suffix operations like [`Path::strip_prefix`] -/// do not copy the underlying bytes. +/// Cloning is cheap: a short path is stored inline, and a long one shares a single +/// reference-counted allocation that suffix operations like [`Path::strip_prefix`] reuse. /// /// # Examples /// ``` -/// use moq_net::{Path}; +/// use moq_net::Path; /// /// // Creation automatically trims slashes /// let path1 = Path::new("/foo/bar/"); /// let path2 = Path::new("foo/bar"); /// assert_eq!(path1, path2); /// -/// // Methods accept both &str and Path /// let base = Path::new("api/v1"); -/// assert!(base.has_prefix("api")); -/// assert!(base.has_prefix(&Path::new("api/v1"))); +/// assert!(base.has_prefix(&Path::new("api"))); /// -/// let joined = base.join("users"); +/// let joined = base.join(&Path::new("users")); /// assert_eq!(joined.as_str(), "api/v1/users"); /// ``` #[derive(Clone)] -pub struct Path<'a>(Repr<'a>); +pub struct Path(Repr); -impl<'a> Path<'a> { +impl Path { /// Maximum number of slash-separated parts in a path. /// /// Matches the IETF moq-transport limit of 32 fields in a namespace tuple. @@ -113,51 +95,43 @@ impl<'a> Path<'a> { /// and publishing one to an origin is rejected. pub const MAX_PARTS: usize = 32; - /// Create a new Path from a string slice. + /// Create a new Path by copying a string slice. /// /// Leading and trailing slashes are automatically trimmed. /// Multiple consecutive internal slashes are collapsed to single slashes. - pub fn new(s: &'a str) -> Self { + pub fn new(s: &str) -> Self { let trimmed = s.trim_start_matches('/').trim_end_matches('/'); + match normalize(trimmed) { + Some(normalized) => Self(Repr::string(normalized)), + None => Self(Repr::copy(trimmed)), + } + } - // Check if we need to normalize (has multiple consecutive slashes) - if trimmed.contains("//") { - // Only allocate if we actually need to normalize - let normalized = trimmed - .split('/') - .filter(|s| !s.is_empty()) - .collect::>() - .join("/"); - Self(Repr::Shared { - buf: normalized.into(), - start: 0, - }) - } else { - // No normalization needed - use borrowed string - Self(Repr::Borrowed(trimmed)) + /// Create a new Path from a string literal without copying it, normalized like [`Path::new`]. + pub fn from_static(s: &'static str) -> Self { + let trimmed = s.trim_start_matches('/').trim_end_matches('/'); + match normalize(trimmed) { + Some(normalized) => Self(Repr::string(normalized)), + None => Self(Repr::Static(trimmed)), } } #[cfg_attr(not(feature = "async"), allow(dead_code, reason = "async-only"))] - pub(crate) fn from_escaped(s: String) -> PathOwned { - if s.is_empty() { - Path::empty() - } else { - Path(Repr::Shared { - buf: s.into(), - start: 0, - }) - } + pub(crate) fn from_escaped(s: String) -> Path { + Path(Repr::string(s)) } // A copy of this path skipping the first `n` bytes, reusing the shared buffer when possible. - pub(crate) fn slice_from(&'a self, n: usize) -> Path<'a> { + pub(crate) fn slice_from(&self, n: usize) -> Path { + let rest = &self.as_str()[n..]; match &self.0 { - Repr::Borrowed(s) => Path(Repr::Borrowed(&s[n..])), - Repr::Shared { buf, start } => Path(Repr::Shared { - buf: buf.clone(), - start: start + n, - }), + Repr::Static(s) => Path(Repr::Static(&s[n..])), + Repr::Shared { buf, start } if rest.len() > INLINE => match u32::try_from(*start as usize + n) { + Ok(start) => Path(Repr::Shared { buf: buf.clone(), start }), + // An offset past 4 GiB doesn't fit, so copy the rest instead. + Err(_) => Path(Repr::copy(rest)), + }, + _ => Path(Repr::copy(rest)), } } @@ -174,62 +148,45 @@ impl<'a> Path<'a> { /// use moq_net::Path; /// /// let path = Path::new("foo/bar"); - /// assert!(path.has_prefix("foo")); /// assert!(path.has_prefix(&Path::new("foo"))); - /// assert!(path.has_prefix("foo/")); - /// assert!(!path.has_prefix("fo")); + /// assert!(path.has_prefix(&Path::new("foo/"))); + /// assert!(!path.has_prefix(&Path::new("fo"))); /// /// let path = Path::new("foobar"); - /// assert!(!path.has_prefix("foo")); + /// assert!(!path.has_prefix(&Path::new("foo"))); /// ``` - pub fn has_prefix(&self, prefix: impl AsPath) -> bool { - let prefix = prefix.as_path(); + pub fn has_prefix(&self, prefix: &Path) -> bool { + self.prefix_len(prefix).is_some() + } + // The bytes to skip past `prefix` and its delimiter, if it is a whole-segment prefix. + fn prefix_len(&self, prefix: &Path) -> Option { + let prefix = prefix.as_str(); if prefix.is_empty() { - return true; + return Some(0); } let s = self.as_str(); - if !s.starts_with(prefix.as_str()) { - return false; + if !s.starts_with(prefix) { + return None; } // Check if the prefix is the exact match if s.len() == prefix.len() { - return true; + return Some(s.len()); } // Otherwise, ensure the character after the prefix is a delimiter - s.as_bytes().get(prefix.len()) == Some(&b'/') + (s.as_bytes().get(prefix.len()) == Some(&b'/')).then_some(prefix.len() + 1) } /// The remainder after removing `prefix`, or `None` if it isn't a prefix. /// /// Only whole segments match: `a/bc` is not prefixed by `a/b`. An empty prefix /// returns the whole path. - pub fn strip_prefix(&'a self, prefix: impl AsPath) -> Option> { - let prefix = prefix.as_path(); - - if prefix.is_empty() { - return Some(self.borrow()); - } - - let s = self.as_str(); - if !s.starts_with(prefix.as_str()) { - return None; - } - - // Check if the prefix is the exact match - if s.len() == prefix.len() { - return Some(Path::empty()); - } - - // Otherwise, ensure the character after the prefix is a delimiter - if s.as_bytes().get(prefix.len()) != Some(&b'/') { - return None; - } - - Some(self.slice_from(prefix.len() + 1)) + pub fn strip_prefix(&self, prefix: &Path) -> Option { + let n = self.prefix_len(prefix)?; + Some(self.slice_from(n)) } /// Iterate over the slash-separated parts of the path. @@ -259,14 +216,17 @@ impl<'a> Path<'a> { /// The normalized path as a string, with no leading or trailing slash. pub fn as_str(&self) -> &str { match &self.0 { - Repr::Borrowed(s) => s, - Repr::Shared { buf, start } => &buf[*start..], + // SAFETY: `Repr::copy` and `join` fill `buf[..len]` only from whole `&str`s and an + // ASCII `/`, so those bytes are valid UTF-8. + Repr::Inline { len, buf } => unsafe { std::str::from_utf8_unchecked(&buf[..*len as usize]) }, + Repr::Static(s) => s, + Repr::Shared { buf, start } => &buf[*start as usize..], } } /// The empty path, which prefixes every other path. - pub fn empty() -> Path<'static> { - Path(Repr::Borrowed("")) + pub const fn empty() -> Path { + Path(Repr::Static("")) } /// Returns `true` if this is the empty path. @@ -279,38 +239,6 @@ impl<'a> Path<'a> { self.as_str().len() } - /// Clone into a `'static` path, sharing the existing buffer when there is one. - pub fn to_owned(&self) -> PathOwned { - match &self.0 { - Repr::Borrowed("") => Path::empty(), - Repr::Borrowed(s) => Path(Repr::Shared { - buf: Arc::from(*s), - start: 0, - }), - Repr::Shared { buf, start } => Path(Repr::Shared { - buf: buf.clone(), - start: *start, - }), - } - } - - /// Consume into a `'static` path, reusing the existing buffer when there is one. - pub fn into_owned(self) -> PathOwned { - match self.0 { - Repr::Borrowed("") => Path::empty(), - Repr::Borrowed(s) => Path(Repr::Shared { - buf: Arc::from(s), - start: 0, - }), - Repr::Shared { buf, start } => Path(Repr::Shared { buf, start }), - } - } - - /// A copy of this path bound to `self`'s lifetime, without copying the underlying bytes. - pub fn borrow(&'a self) -> Path<'a> { - self.slice_from(0) - } - /// Join this path with another path component. /// /// # Examples @@ -318,25 +246,30 @@ impl<'a> Path<'a> { /// use moq_net::Path; /// /// let base = Path::new("foo"); - /// let joined = base.join("bar"); - /// assert_eq!(joined.as_str(), "foo/bar"); - /// /// let joined = base.join(&Path::new("bar")); /// assert_eq!(joined.as_str(), "foo/bar"); /// ``` - pub fn join(&self, other: impl AsPath) -> PathOwned { - let other = other.as_path(); - + pub fn join(&self, other: &Path) -> Path { if self.is_empty() { - other.to_owned() + other.clone() } else if other.is_empty() { - self.to_owned() + self.clone() } else { // Since paths are trimmed, we always need to add a slash - Path(Repr::Shared { - buf: format!("{}/{}", self.as_str(), other.as_str()).into(), - start: 0, - }) + let (a, b) = (self.as_str(), other.as_str()); + let len = a.len() + 1 + b.len(); + if len <= INLINE { + let mut buf = [0; INLINE]; + buf[..a.len()].copy_from_slice(a.as_bytes()); + buf[a.len()] = b'/'; + buf[a.len() + 1..len].copy_from_slice(b.as_bytes()); + Path(Repr::Inline { len: len as u8, buf }) + } else { + Path(Repr::Shared { + buf: format!("{a}/{b}").into(), + start: 0, + }) + } } } @@ -345,7 +278,7 @@ impl<'a> Path<'a> { /// A non-empty reference replaces the last segment of the base, matching relative URL /// resolution. `..` segments then pop another segment; other segments are appended. /// Excess `..` is a no-op once the base is empty (subsequent named segments still append). - /// An empty `rel` returns this path as an owned copy. + /// An empty `rel` returns this path unchanged. /// /// [`Relative::new`] strips empty and redundant `.` segments, but preserves a lone `.` /// so it can reference the base's parent. @@ -359,9 +292,9 @@ impl<'a> Path<'a> { /// assert_eq!(base.resolve(&Relative::new(".")).as_str(), "a/b"); /// assert_eq!(base.resolve(&Relative::new("../d")).as_str(), "a/d"); /// ``` - pub fn resolve(&self, rel: &Relative<'_>) -> PathOwned { + pub fn resolve(&self, rel: &Relative) -> Path { if rel.is_empty() { - return self.to_owned(); + return self.clone(); } let mut segments: Vec<&str> = self.parts().collect(); @@ -377,15 +310,7 @@ impl<'a> Path<'a> { } } - let path = segments.join("/"); - if path.is_empty() { - Path::empty() - } else { - Path(Repr::Shared { - buf: path.into(), - start: 0, - }) - } + Path(Repr::string(segments.join("/"))) } /// Resolve a [`Relative`], returning `None` if it escapes above the root. @@ -393,9 +318,9 @@ impl<'a> Path<'a> { /// Unlike [`Path::resolve`], this distinguishes a valid reference to the empty root /// path from excess `..` segments. Use it when an untrusted relative reference must /// not be clamped to the root. - pub fn try_resolve(&self, rel: &Relative<'_>) -> Option { + pub fn try_resolve(&self, rel: &Relative) -> Option { if rel.is_empty() { - return Some(self.to_owned()); + return Some(self.clone()); } let mut segments: Vec<&str> = self.parts().collect(); @@ -411,15 +336,7 @@ impl<'a> Path<'a> { } } - let path = segments.join("/"); - if path.is_empty() { - Some(Path::empty()) - } else { - Some(Path(Repr::Shared { - buf: path.into(), - start: 0, - })) - } + Some(Path(Repr::string(segments.join("/")))) } /// Express this path relative to `base`: the inverse of [`Path::resolve`]. @@ -455,12 +372,10 @@ impl<'a> Path<'a> { /// // A segment named `..` is a legal path component but an unnameable target. /// assert!(Path::new("a/..").relative(&base).is_none()); /// ``` - pub fn relative(&self, base: impl AsPath) -> Option { - let base = base.as_path(); - + pub fn relative(&self, base: &Path) -> Option { // Only the empty reference can name a base whose last segment is itself `.` or `..`, // since resolution replaces that segment rather than emitting it. - if *self == base { + if self == base { return Some(Relative::empty()); } @@ -485,88 +400,112 @@ impl<'a> Path<'a> { return Some(Relative::new(".")); } - Some(RelativeOwned::from(rel.join("/"))) + Some(Relative::from(rel.join("/"))) } } -// Comparisons, ordering, and hashing all go through `as_str()` so a borrowed and a -// shared path with the same content behave identically (e.g. as map keys). -impl<'b> PartialEq> for Path<'_> { - fn eq(&self, other: &Path<'b>) -> bool { +// The normalized copy when `trimmed` holds consecutive slashes, else `None`. +fn normalize(trimmed: &str) -> Option { + trimmed.contains("//").then(|| { + trimmed + .split('/') + .filter(|s| !s.is_empty()) + .collect::>() + .join("/") + }) +} + +// Comparisons, ordering, and hashing all go through `as_str()` so paths with the same +// content behave identically whatever their representation (e.g. as map keys). +impl PartialEq for Path { + fn eq(&self, other: &Path) -> bool { self.as_str() == other.as_str() } } -impl Eq for Path<'_> {} +impl Eq for Path {} -impl PartialOrd for Path<'_> { +impl PartialOrd for Path { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Path<'_> { +impl Ord for Path { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_str().cmp(other.as_str()) } } -impl std::hash::Hash for Path<'_> { +impl std::hash::Hash for Path { fn hash(&self, state: &mut H) { self.as_str().hash(state) } } -impl fmt::Debug for Path<'_> { +/// Look up a `Path`-keyed map by `&str` without building a `Path`. The key must be normalized. +impl std::borrow::Borrow for Path { + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl fmt::Debug for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Path").field(&self.as_str()).finish() } } -impl serde::Serialize for Path<'_> { +impl serde::Serialize for Path { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(self.as_str()) } } -impl<'a> From<&'a str> for Path<'a> { - fn from(s: &'a str) -> Self { +impl From<&str> for Path { + fn from(s: &str) -> Self { Self::new(s) } } -impl<'a> From<&'a String> for Path<'a> { - fn from(s: &'a String) -> Self { - // TODO avoid making a copy here +impl From<&String> for Path { + fn from(s: &String) -> Self { Self::new(s) } } -impl Default for Path<'_> { +impl From<&Path> for Path { + fn from(p: &Path) -> Self { + p.clone() + } +} + +impl Default for Path { fn default() -> Self { Path::empty() } } -impl From for Path<'_> { +impl From for Path { fn from(s: String) -> Self { - Path::new(&s).into_owned() + // `Arc` can't adopt a `String`'s buffer, so this copies like `new`. + Self::new(&s) } } -impl AsRef for Path<'_> { +impl AsRef for Path { fn as_ref(&self) -> &str { self.as_str() } } -impl Display for Path<'_> { +impl Display for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } -impl Decode for Path<'_> { +impl Decode for Path { fn decode(r: &mut Decoder<'_>, _: V) -> Result { let path: Path = r.string()?.into(); if path.parts().count() > Path::MAX_PARTS { @@ -576,7 +515,7 @@ impl Decode for Path<'_> { } } -impl Encode for Path<'_> { +impl Encode for Path { fn encode(&self, w: &mut Encoder<'_>, _: V) -> Result<(), EncodeError> { if self.parts().count() > Path::MAX_PARTS { return Err(EncodeError::BoundsExceeded); @@ -617,9 +556,6 @@ impl Decode for Patterns { } } -/// An owned version of [`Relative`] with a `'static` lifetime. -pub type RelativeOwned = Relative<'static>; - /// A relative broadcast path, used to reference one broadcast from another broadcast's content. /// /// Unlike [`Path`] (which is a complete reference within the broadcast namespace), @@ -647,32 +583,38 @@ pub type RelativeOwned = Relative<'static>; /// assert_eq!(Relative::new("./a/./b").as_str(), "a/b"); /// assert_eq!(Relative::new(".").as_str(), "."); /// ``` -#[derive(Debug, PartialEq, Eq, Hash, Clone, serde::Serialize)] -pub struct Relative<'a>(Cow<'a, str>); +#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)] +pub struct Relative(Path); -impl<'a> Relative<'a> { +impl serde::Serialize for Relative { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize(serializer) + } +} + +impl Relative { /// Create a new `Relative` from a string slice. /// /// Leading and trailing slashes are trimmed, consecutive internal slashes collapse to one, /// and redundant `.` segments are stripped. See the type-level doc for the full rules. - pub fn new(s: &'a str) -> Self { + pub fn new(s: &str) -> Self { let trimmed = s.trim_start_matches('/').trim_end_matches('/'); if needs_normalize_relative(trimmed) { - Self(Cow::Owned(normalize_relative_segments(trimmed))) + Self(Path(Repr::string(normalize_relative_segments(trimmed)))) } else { - Self(Cow::Borrowed(trimmed)) + Self(Path(Repr::copy(trimmed))) } } /// The normalized path as a string slice. pub fn as_str(&self) -> &str { - &self.0 + self.0.as_str() } /// The empty relative path, which resolves to the base path itself. - pub fn empty() -> Relative<'static> { - Relative(Cow::Borrowed("")) + pub const fn empty() -> Relative { + Relative(Path::empty()) } /// True if the path is empty (resolves to the base path itself). @@ -684,46 +626,23 @@ impl<'a> Relative<'a> { pub fn len(&self) -> usize { self.0.len() } - - /// Copy into an owned version with a `'static` lifetime. - pub fn to_owned(&self) -> RelativeOwned { - Relative(Cow::Owned(self.0.to_string())) - } - - /// Convert into an owned version with a `'static` lifetime. - pub fn into_owned(self) -> RelativeOwned { - Relative(Cow::Owned(self.0.into_owned())) - } - - /// Reborrow without copying. - pub fn borrow(&'a self) -> Relative<'a> { - Relative(Cow::Borrowed(&self.0)) - } } -impl<'a> From<&'a str> for Relative<'a> { - fn from(s: &'a str) -> Self { +impl From<&str> for Relative { + fn from(s: &str) -> Self { Self::new(s) } } -impl<'a> From<&'a String> for Relative<'a> { - fn from(s: &'a String) -> Self { +impl From<&String> for Relative { + fn from(s: &String) -> Self { Self::new(s) } } -impl From for Relative<'_> { +impl From for Relative { fn from(s: String) -> Self { - let trimmed = s.trim_start_matches('/').trim_end_matches('/'); - - if needs_normalize_relative(trimmed) { - Self(Cow::Owned(normalize_relative_segments(trimmed))) - } else if trimmed == s { - Self(Cow::Owned(s)) - } else { - Self(Cow::Owned(trimmed.to_string())) - } + Self::new(&s) } } @@ -745,28 +664,19 @@ fn normalize_relative_segments(trimmed: &str) -> String { } } -impl Default for Relative<'_> { - fn default() -> Self { - Self(Cow::Borrowed("")) - } -} - -impl AsRef for Relative<'_> { +impl AsRef for Relative { fn as_ref(&self) -> &str { - &self.0 + self.as_str() } } -impl Display for Relative<'_> { +impl Display for Relative { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + write!(f, "{}", self.as_str()) } } -// Owned-only deserialization. We use `String::deserialize` so that owned deserializers -// (e.g. `serde_json::from_slice`) work. The borrowed form `<&str>::deserialize` requires -// `'de: 'a`, which is unsatisfiable when `'a = 'static`. -impl<'de> serde::Deserialize<'de> for Relative<'static> { +impl<'de> serde::Deserialize<'de> for Relative { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -785,25 +695,25 @@ mod tests { let path = Path::new("foo/bar/baz"); // Valid prefixes - test with both &str and &Path - assert!(path.has_prefix("")); - assert!(path.has_prefix("foo")); - assert!(path.has_prefix(Path::new("foo"))); - assert!(path.has_prefix("foo/")); - assert!(path.has_prefix("foo/bar")); - assert!(path.has_prefix(Path::new("foo/bar/"))); - assert!(path.has_prefix("foo/bar/baz")); + assert!(path.has_prefix(&Path::new(""))); + assert!(path.has_prefix(&Path::new("foo"))); + assert!(path.has_prefix(&Path::new("foo"))); + assert!(path.has_prefix(&Path::new("foo/"))); + assert!(path.has_prefix(&Path::new("foo/bar"))); + assert!(path.has_prefix(&Path::new("foo/bar/"))); + assert!(path.has_prefix(&Path::new("foo/bar/baz"))); // Invalid prefixes - should not match partial components - assert!(!path.has_prefix("f")); - assert!(!path.has_prefix(Path::new("fo"))); - assert!(!path.has_prefix("foo/b")); - assert!(!path.has_prefix("foo/ba")); - assert!(!path.has_prefix(Path::new("foo/bar/ba"))); + assert!(!path.has_prefix(&Path::new("f"))); + assert!(!path.has_prefix(&Path::new("fo"))); + assert!(!path.has_prefix(&Path::new("foo/b"))); + assert!(!path.has_prefix(&Path::new("foo/ba"))); + assert!(!path.has_prefix(&Path::new("foo/bar/ba"))); // Edge case: "foobar" should not match "foo" let path = Path::new("foobar"); - assert!(!path.has_prefix("foo")); - assert!(path.has_prefix(Path::new("foobar"))); + assert!(!path.has_prefix(&Path::new("foo"))); + assert!(path.has_prefix(&Path::new("foobar"))); } #[test] @@ -811,25 +721,25 @@ mod tests { let path = Path::new("foo/bar/baz"); // Test with both &str and &Path - assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz"); - assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz"); - assert_eq!(path.strip_prefix(Path::new("foo/")).unwrap().as_str(), "bar/baz"); - assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz"); - assert_eq!(path.strip_prefix(Path::new("foo/bar/")).unwrap().as_str(), "baz"); - assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), ""); + assert_eq!(path.strip_prefix(&Path::new("")).unwrap().as_str(), "foo/bar/baz"); + assert_eq!(path.strip_prefix(&Path::new("foo")).unwrap().as_str(), "bar/baz"); + assert_eq!(path.strip_prefix(&Path::new("foo/")).unwrap().as_str(), "bar/baz"); + assert_eq!(path.strip_prefix(&Path::new("foo/bar")).unwrap().as_str(), "baz"); + assert_eq!(path.strip_prefix(&Path::new("foo/bar/")).unwrap().as_str(), "baz"); + assert_eq!(path.strip_prefix(&Path::new("foo/bar/baz")).unwrap().as_str(), ""); // Should fail for invalid prefixes - assert!(path.strip_prefix("fo").is_none()); - assert!(path.strip_prefix(Path::new("bar")).is_none()); + assert!(path.strip_prefix(&Path::new("fo")).is_none()); + assert!(path.strip_prefix(&Path::new("bar")).is_none()); } #[test] fn test_join() { // Test with both &str and &Path - assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar"); - assert_eq!(Path::new("foo/").join(Path::new("bar")).as_str(), "foo/bar"); - assert_eq!(Path::new("").join("bar").as_str(), "bar"); - assert_eq!(Path::new("foo/bar").join(Path::new("baz")).as_str(), "foo/bar/baz"); + assert_eq!(Path::new("foo").join(&Path::new("bar")).as_str(), "foo/bar"); + assert_eq!(Path::new("foo/").join(&Path::new("bar")).as_str(), "foo/bar"); + assert_eq!(Path::new("").join(&Path::new("bar")).as_str(), "bar"); + assert_eq!(Path::new("foo/bar").join(&Path::new("baz")).as_str(), "foo/bar/baz"); } #[test] @@ -916,45 +826,45 @@ mod tests { fn test_prefix_has_prefix() { // Test empty prefix (should match everything) let prefix = Path::new("foo/bar"); - assert!(prefix.has_prefix("")); + assert!(prefix.has_prefix(&Path::new(""))); // Test exact matches let prefix = Path::new("foo/bar"); - assert!(prefix.has_prefix("foo/bar")); + assert!(prefix.has_prefix(&Path::new("foo/bar"))); // Test valid prefixes - assert!(prefix.has_prefix("foo")); - assert!(prefix.has_prefix("foo/")); + assert!(prefix.has_prefix(&Path::new("foo"))); + assert!(prefix.has_prefix(&Path::new("foo/"))); // Test invalid prefixes - partial matches should fail - assert!(!prefix.has_prefix("f")); - assert!(!prefix.has_prefix("fo")); - assert!(!prefix.has_prefix("foo/b")); - assert!(!prefix.has_prefix("foo/ba")); + assert!(!prefix.has_prefix(&Path::new("f"))); + assert!(!prefix.has_prefix(&Path::new("fo"))); + assert!(!prefix.has_prefix(&Path::new("foo/b"))); + assert!(!prefix.has_prefix(&Path::new("foo/ba"))); // Test edge cases let prefix = Path::new("foobar"); - assert!(!prefix.has_prefix("foo")); - assert!(prefix.has_prefix("foobar")); + assert!(!prefix.has_prefix(&Path::new("foo"))); + assert!(prefix.has_prefix(&Path::new("foobar"))); // Test trailing slash handling let prefix = Path::new("foo/bar/"); - assert!(prefix.has_prefix("foo")); - assert!(prefix.has_prefix("foo/")); - assert!(prefix.has_prefix("foo/bar")); - assert!(prefix.has_prefix("foo/bar/")); + assert!(prefix.has_prefix(&Path::new("foo"))); + assert!(prefix.has_prefix(&Path::new("foo/"))); + assert!(prefix.has_prefix(&Path::new("foo/bar"))); + assert!(prefix.has_prefix(&Path::new("foo/bar/"))); // Test single component let prefix = Path::new("foo"); - assert!(prefix.has_prefix("")); - assert!(prefix.has_prefix("foo")); - assert!(prefix.has_prefix("foo/")); // "foo/" becomes "foo" after trimming - assert!(!prefix.has_prefix("f")); + assert!(prefix.has_prefix(&Path::new(""))); + assert!(prefix.has_prefix(&Path::new("foo"))); + assert!(prefix.has_prefix(&Path::new("foo/"))); // "foo/" becomes "foo" after trimming + assert!(!prefix.has_prefix(&Path::new("f"))); // Test empty prefix let prefix = Path::new(""); - assert!(prefix.has_prefix("")); - assert!(!prefix.has_prefix("foo")); + assert!(prefix.has_prefix(&Path::new(""))); + assert!(!prefix.has_prefix(&Path::new("foo"))); } #[test] @@ -962,52 +872,52 @@ mod tests { // Basic joining let prefix = Path::new("foo"); let suffix = Path::new("bar"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar"); // Trailing slash on prefix let prefix = Path::new("foo/"); let suffix = Path::new("bar"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar"); // Leading slash on suffix let prefix = Path::new("foo"); let suffix = Path::new("/bar"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar"); // Trailing slash on suffix let prefix = Path::new("foo"); let suffix = Path::new("bar/"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); // trailing slash is trimmed + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar"); // trailing slash is trimmed // Both have slashes let prefix = Path::new("foo/"); let suffix = Path::new("/bar"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar"); // Empty suffix let prefix = Path::new("foo"); let suffix = Path::new(""); - assert_eq!(prefix.join(suffix).as_str(), "foo"); + assert_eq!(prefix.join(&suffix).as_str(), "foo"); // Empty prefix let prefix = Path::new(""); let suffix = Path::new("bar"); - assert_eq!(prefix.join(suffix).as_str(), "bar"); + assert_eq!(prefix.join(&suffix).as_str(), "bar"); // Both empty let prefix = Path::new(""); let suffix = Path::new(""); - assert_eq!(prefix.join(suffix).as_str(), ""); + assert_eq!(prefix.join(&suffix).as_str(), ""); // Complex paths let prefix = Path::new("foo/bar"); let suffix = Path::new("baz/qux"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar/baz/qux"); // Complex paths with slashes let prefix = Path::new("foo/bar/"); let suffix = Path::new("/baz/qux/"); - assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); // all slashes are trimmed + assert_eq!(prefix.join(&suffix).as_str(), "foo/bar/baz/qux"); // all slashes are trimmed } #[test] @@ -1031,7 +941,7 @@ mod tests { // Test that Path methods work with PathRef let path2 = Path::new("foo/bar/baz"); assert!(path2.has_prefix(&path_ref)); - assert_eq!(path2.strip_prefix(path_ref).unwrap().as_str(), "baz"); + assert_eq!(path2.strip_prefix(&path_ref).unwrap().as_str(), "baz"); // Test empty PathRef let empty = Path::new(""); @@ -1078,156 +988,114 @@ mod tests { // Test that operations work correctly with normalized paths let path_with_slashes = Path::new("foo//bar///baz"); - assert!(path_with_slashes.has_prefix("foo/bar")); - assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz"); - assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux"); - - // Test PathRef to Path conversion - let path_ref = Path::new("foo//bar///baz"); - assert_eq!(path_ref.as_str(), "foo/bar/baz"); // PathRef now normalizes too - let path_from_ref = path_ref.to_owned(); - assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); // Both are normalized - } - - #[test] - fn test_path_ref_multiple_slashes() { - // PathRef now normalizes multiple slashes using Cow - let path_ref = Path::new("//foo//bar///baz//"); - assert_eq!(path_ref.as_str(), "foo/bar/baz"); // Fully normalized - - // Various multiple slash scenarios are normalized in PathRef - assert_eq!(Path::new("foo//bar").as_str(), "foo/bar"); - assert_eq!(Path::new("foo///bar").as_str(), "foo/bar"); - assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d"); - - // Conversion to Path maintains normalized form - assert_eq!(Path::new("foo//bar").to_owned().as_str(), "foo/bar"); - assert_eq!(Path::new("foo///bar").to_owned().as_str(), "foo/bar"); - assert_eq!(Path::new("a//b//c//d").to_owned().as_str(), "a/b/c/d"); - - // Edge cases - assert_eq!(Path::new("//").as_str(), ""); - assert_eq!(Path::new("////").as_str(), ""); - assert_eq!(Path::new("//").to_owned().as_str(), ""); - assert_eq!(Path::new("////").to_owned().as_str(), ""); - - // Test that PathRef avoids allocation when no normalization needed - let normal_path = Path::new("foo/bar/baz"); - assert_eq!(normal_path.as_str(), "foo/bar/baz"); - // This should use Cow::Borrowed internally (no allocation) - - let needs_norm = Path::new("foo//bar"); - assert_eq!(needs_norm.as_str(), "foo/bar"); - // This should use Cow::Owned internally (allocation only when needed) - } - - #[test] - fn test_ergonomic_conversions() { - // Test that all these work ergonomically in function calls - fn takes_path_ref<'a>(p: impl Into>) -> String { - p.into().as_str().to_string() - } - - // Alternative API using the trait alias for better error messages - fn takes_path_ref_with_trait<'a>(p: impl Into>) -> String { - p.into().as_str().to_string() - } - - // String literal - assert_eq!(takes_path_ref("foo//bar"), "foo/bar"); - - // String (owned) - this should now work without & - let owned_string = String::from("foo//bar///baz"); - assert_eq!(takes_path_ref(owned_string), "foo/bar/baz"); - - // &String - let string_ref = String::from("foo//bar"); - assert_eq!(takes_path_ref(string_ref), "foo/bar"); - - // PathRef - let path_ref = Path::new("foo//bar"); - assert_eq!(takes_path_ref(path_ref), "foo/bar"); - - // Path - let path = Path::new("foo//bar"); - assert_eq!(takes_path_ref(path), "foo/bar"); - - // Test that Path::new works with all these types - let _path1 = Path::new("foo/bar"); // &str - let _path2 = Path::new("foo/bar"); // String - should now work - let _path3 = Path::new("foo/bar"); // &String - let _path4 = Path::new("foo/bar"); // PathRef - - // Test the trait alias version works the same - assert_eq!(takes_path_ref_with_trait("foo//bar"), "foo/bar"); - assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar"); + assert!(path_with_slashes.has_prefix(&Path::new("foo/bar"))); + assert_eq!(path_with_slashes.strip_prefix(&Path::new("foo")).unwrap().as_str(), "bar/baz"); + assert_eq!(path_with_slashes.join(&Path::new("qux")).as_str(), "foo/bar/baz/qux"); } #[test] fn test_prefix_strip_prefix() { // Test basic stripping let prefix = Path::new("foo/bar/baz"); - assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "foo/bar/baz"); - assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar/baz"); - assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar/baz"); - assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "baz"); - assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "baz"); - assert_eq!(prefix.strip_prefix("foo/bar/baz").unwrap().as_str(), ""); + assert_eq!(prefix.strip_prefix(&Path::new("")).unwrap().as_str(), "foo/bar/baz"); + assert_eq!(prefix.strip_prefix(&Path::new("foo")).unwrap().as_str(), "bar/baz"); + assert_eq!(prefix.strip_prefix(&Path::new("foo/")).unwrap().as_str(), "bar/baz"); + assert_eq!(prefix.strip_prefix(&Path::new("foo/bar")).unwrap().as_str(), "baz"); + assert_eq!(prefix.strip_prefix(&Path::new("foo/bar/")).unwrap().as_str(), "baz"); + assert_eq!(prefix.strip_prefix(&Path::new("foo/bar/baz")).unwrap().as_str(), ""); // Test invalid prefixes - assert!(prefix.strip_prefix("fo").is_none()); - assert!(prefix.strip_prefix("bar").is_none()); - assert!(prefix.strip_prefix("foo/ba").is_none()); + assert!(prefix.strip_prefix(&Path::new("fo")).is_none()); + assert!(prefix.strip_prefix(&Path::new("bar")).is_none()); + assert!(prefix.strip_prefix(&Path::new("foo/ba")).is_none()); // Test edge cases let prefix = Path::new("foobar"); - assert!(prefix.strip_prefix("foo").is_none()); - assert_eq!(prefix.strip_prefix("foobar").unwrap().as_str(), ""); + assert!(prefix.strip_prefix(&Path::new("foo")).is_none()); + assert_eq!(prefix.strip_prefix(&Path::new("foobar")).unwrap().as_str(), ""); // Test empty prefix let prefix = Path::new(""); - assert_eq!(prefix.strip_prefix("").unwrap().as_str(), ""); - assert!(prefix.strip_prefix("foo").is_none()); + assert_eq!(prefix.strip_prefix(&Path::new("")).unwrap().as_str(), ""); + assert!(prefix.strip_prefix(&Path::new("foo")).is_none()); // Test single component let prefix = Path::new("foo"); - assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), ""); - assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), ""); // "foo/" becomes "foo" after trimming + assert_eq!(prefix.strip_prefix(&Path::new("foo")).unwrap().as_str(), ""); + assert_eq!(prefix.strip_prefix(&Path::new("foo/")).unwrap().as_str(), ""); // "foo/" becomes "foo" after trimming // Test trailing slash handling let prefix = Path::new("foo/bar/"); - assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar"); - assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar"); - assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), ""); - assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), ""); + assert_eq!(prefix.strip_prefix(&Path::new("foo")).unwrap().as_str(), "bar"); + assert_eq!(prefix.strip_prefix(&Path::new("foo/")).unwrap().as_str(), "bar"); + assert_eq!(prefix.strip_prefix(&Path::new("foo/bar")).unwrap().as_str(), ""); + assert_eq!(prefix.strip_prefix(&Path::new("foo/bar/")).unwrap().as_str(), ""); } - // Pointer-equality checks that owned paths share one allocation through the - // clone / to_owned / strip_prefix flow used by origin announce fan-out. + // A long path shares one allocation through the clone and strip_prefix flow used by + // announce fan-out. A short one is stored inline instead, so its copies share nothing. #[test] - fn test_owned_paths_share_allocation() { - let path = Path::new("customer/room/broadcast").to_owned(); + fn test_long_paths_share_allocation() { + let path = Path::new("customer/room-0123456789/broadcast"); - // Cloning an owned path shares the buffer. + // Cloning shares the buffer. let cloned = path.clone(); assert_eq!(path.as_str().as_ptr(), cloned.as_str().as_ptr()); - // as_path + to_owned (how notify queues a path per consumer) shares too. - let requeued = path.as_path().to_owned(); - assert_eq!(path.as_str().as_ptr(), requeued.as_str().as_ptr()); - - // Stripping a prefix from an owned path is offset arithmetic, not a copy. - let stripped = path.strip_prefix("customer").unwrap().to_owned(); - assert_eq!(stripped.as_str(), "room/broadcast"); + // Stripping a prefix leaving a long rest is offset arithmetic, not a copy. + let stripped = path.strip_prefix(&Path::new("customer")).unwrap(); + assert_eq!(stripped.as_str(), "room-0123456789/broadcast"); assert_eq!(stripped.as_str().as_ptr(), path.as_str()["customer/".len()..].as_ptr()); - // join produces an owned path whose clones share. - let joined = path.join("alice"); + // A short rest moves inline rather than keeping the long buffer alive. + let short = path.strip_prefix(&Path::new("customer/room-0123456789")).unwrap(); + assert_eq!(short.as_str(), "broadcast"); + assert!(matches!(short.0, Repr::Inline { .. })); + + // join produces a path whose clones share. + let joined = path.join(&Path::new("alice")); let joined2 = joined.clone(); - assert_eq!(joined.as_str(), "customer/room/broadcast/alice"); + assert_eq!(joined.as_str(), "customer/room-0123456789/broadcast/alice"); assert_eq!(joined.as_str().as_ptr(), joined2.as_str().as_ptr()); } + #[test] + fn test_inline_boundary() { + let fits = "a".repeat(INLINE); + let spills = "a".repeat(INLINE + 1); + assert!(matches!(Path::new(&fits).0, Repr::Inline { .. })); + assert!(matches!(Path::new(&spills).0, Repr::Shared { .. })); + + // A join exactly at the limit stays inline; one byte more spills. + let half = Path::new(&"b".repeat(INLINE / 2 - 1)); + let joined = half.join(&Path::new(&"c".repeat(INLINE / 2))); + assert_eq!(joined.len(), INLINE); + assert!(matches!(joined.0, Repr::Inline { .. })); + assert_eq!(joined.as_str(), format!("{}/{}", "b".repeat(INLINE / 2 - 1), "c".repeat(INLINE / 2))); + assert!(matches!(joined.join(&Path::new("d")).0, Repr::Shared { .. })); + + // Multi-byte characters survive the inline copy. + let wide = Path::new("caf\u{e9}/\u{1f3a5}"); + assert!(matches!(wide.0, Repr::Inline { .. })); + assert_eq!(wide.as_str(), "caf\u{e9}/\u{1f3a5}"); + assert_eq!(wide.strip_prefix(&Path::new("caf\u{e9}")).unwrap().as_str(), "\u{1f3a5}"); + } + + #[test] + fn test_from_static_borrows() { + const LITERAL: &str = "/static/path/that/is/longer/than/inline/"; + let path = Path::from_static(LITERAL); + assert_eq!(path.as_str(), "static/path/that/is/longer/than/inline"); + assert_eq!(path.as_str().as_ptr(), LITERAL[1..].as_ptr()); + + // A suffix of a static path borrows the literal too. + let rest = path.strip_prefix(&Path::new("static")).unwrap(); + assert_eq!(rest.as_str().as_ptr(), LITERAL["/static/".len()..].as_ptr()); + + // Normalizing has to copy. + assert_eq!(Path::from_static("a//b").as_str(), "a/b"); + } + #[test] fn test_parts() { assert_eq!(Path::empty().parts().count(), 0); @@ -1273,12 +1141,12 @@ mod tests { #[test] fn test_owned_empty_paths() { // Empty paths never allocate and stay well-behaved. - let empty = Path::new("").to_owned(); + let empty = Path::new(""); assert!(empty.is_empty()); assert_eq!(empty, Path::empty()); - let path = Path::new("foo").to_owned(); - let rest = path.strip_prefix("foo").unwrap().to_owned(); + let path = Path::new("foo"); + let rest = path.strip_prefix(&Path::new("foo")).unwrap(); assert!(rest.is_empty()); } @@ -1381,7 +1249,7 @@ mod tests { #[test] fn test_relative() { - let rel = |target: &str, base: &str| Path::new(target).relative(base).unwrap(); + let rel = |target: &str, base: &str| Path::new(target).relative(&Path::new(base)).unwrap(); // Nested under the base: the base's own last segment is replaced, so it repeats. assert_eq!(rel("foo/bar/baz", "foo/bar").as_str(), "bar/baz"); @@ -1406,15 +1274,15 @@ mod tests { fn test_relative_rejects_unnameable_targets() { // A segment literally named `.` or `..` is a legal path component, but resolution // would walk on it instead of naming it. - assert!(Path::new("a/../b").relative("").is_none()); - assert!(Path::new("x/./y").relative("x/z").is_none()); - assert!(Path::new("a/..").relative("a/b").is_none()); + assert!(Path::new("a/../b").relative(&Path::new("")).is_none()); + assert!(Path::new("x/./y").relative(&Path::new("x/z")).is_none()); + assert!(Path::new("a/..").relative(&Path::new("a/b")).is_none()); // A base is always nameable by itself, however its last segment is spelled. - assert_eq!(Path::new("a/..").relative("a/..").unwrap().as_str(), ""); + assert_eq!(Path::new("a/..").relative(&Path::new("a/..")).unwrap().as_str(), ""); // Dot segments inside the shared prefix are never emitted, so they are fine. - let rel = Path::new("a/../b/x").relative("a/../b/c").unwrap(); + let rel = Path::new("a/../b/x").relative(&Path::new("a/../b/c")).unwrap(); assert_eq!(rel.as_str(), "x"); assert_eq!(Path::new("a/../b/c").resolve(&rel).as_str(), "a/../b/x"); } diff --git a/rs/moq-net/src/stats.rs b/rs/moq-net/src/stats.rs index d283a20e9be..5f76370b4ca 100644 --- a/rs/moq-net/src/stats.rs +++ b/rs/moq-net/src/stats.rs @@ -144,7 +144,7 @@ use std::{ use kio::Lock; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::{AsPath, PathOwned, Pattern, Patterns, util::SendCell}; +use crate::{Path, Pattern, Patterns, util::SendCell}; mod outcomes; pub use outcomes::{Failure, Failures, RefusalKind, Refusals}; @@ -815,16 +815,16 @@ impl Presence { /// `Tier::new("region/sjc")` records under `"region/sjc"`. The label is an /// arbitrary path chosen by business logic; an empty label is the default tier. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct Tier(PathOwned); +pub struct Tier(Path); impl Tier { /// A tier with the given label. An empty label is the default tier. - pub fn new(label: impl Into) -> Self { + pub fn new(label: impl Into) -> Self { Self(label.into()) } /// The tier label, empty for the default tier. - pub fn label(&self) -> &PathOwned { + pub fn label(&self) -> &Path { &self.0 } @@ -1017,7 +1017,7 @@ pub struct Report { #[non_exhaustive] pub struct TrafficEntry { /// The broadcast path the counters are keyed by. - pub path: PathOwned, + pub path: Path, /// The tier the counters recorded under. pub tier: Tier, /// Egress counters (this node publishing to peers). @@ -1036,7 +1036,7 @@ pub struct SessionEntry { /// The tier the sessions recorded under. pub tier: Tier, /// The label the sessions record under, empty for sessions without one. - pub label: PathOwned, + pub label: Path, /// Cumulative request admissions and connect/disconnect gauge. pub presence: Presence, /// This is the label's final readout: its last session or request ended, so the @@ -1091,12 +1091,12 @@ struct Shared { /// Completed entries folded by tier before pruning. Lock before either map /// so a snapshot sees each entry either here or live, never both or neither. retired: Lock, - entries: Lock>>, + entries: Lock>>, /// Connected-session gauges keyed by `(tier, label)`. Independent of any /// broadcast; surfaced on the per-tier session tracks and in the totals and /// presence tracks. A tier's inner map is /// created the first time a session records under it. - sessions: Lock>>>, + sessions: Lock>>>, wire: Lock>>, } @@ -1185,7 +1185,7 @@ impl Registry { /// unless a guard still holds it. #[cfg(any(test, feature = "fuzz"))] #[cfg(feature = "async")] - pub(crate) fn bump_publisher_bytes(&self, path: impl AsPath, tier: &Tier, n: u64) { + pub(crate) fn bump_publisher_bytes(&self, path: impl Into, tier: &Tier, n: u64) { let Some(entry) = self.entry(path) else { return; }; @@ -1195,7 +1195,7 @@ impl Registry { /// Resolve the pre-label payload meter used as the stats write benchmark's baseline. #[cfg(any(test, feature = "fuzz"))] #[cfg(feature = "async")] - pub(crate) fn publisher_meter(&self, path: impl AsPath, tier: &Tier) -> Meter { + pub(crate) fn publisher_meter(&self, path: impl Into, tier: &Tier) -> Meter { Meter { counters: self.entry(path).map(|entry| entry.tier(tier)), traffic: None, @@ -1203,17 +1203,17 @@ impl Registry { } } - fn entry(&self, path: impl AsPath) -> Option> { + fn entry(&self, path: impl Into) -> Option> { // A disabled registry never allocates state. let shared = self.shared.as_ref()?; - let path = path.as_path(); + let path: Path = path.into(); // Skip excluded paths (our own stats broadcasts and any sibling category // under the same prefix) so serving a stats broadcast doesn't generate // more stats. if self.exclude.matches(path.as_str()) { return None; } - let owned = path.to_owned(); + let owned = path.clone(); let mut entries = shared.entries.lock(); Some( entries @@ -1226,9 +1226,9 @@ impl Registry { /// Get-or-create the session gauge for `label` on `tier`. `None` for a /// disabled registry. Unlike [`Self::entry`], labels key sessions rather than /// broadcasts, so no cycle-breaking filter is needed. - fn session_counters(&self, tier: &Tier, label: impl AsPath) -> Option> { + fn session_counters(&self, tier: &Tier, label: impl Into) -> Option> { let shared = self.shared.as_ref()?; - let owned = label.as_path().to_owned(); + let owned = label.into(); let mut sessions = shared.sessions.lock(); Some( sessions @@ -1375,7 +1375,7 @@ pub struct Handle { impl Handle { /// Count a session refused outside relay admission under its resolved label and tier. - pub fn refuse(&self, label: impl AsPath, reason: RefusalKind) { + pub fn refuse(&self, label: impl Into, reason: RefusalKind) { if let Some(presence) = self.stats.session_counters(&self.tier, label) { presence .sessions_refused @@ -1390,19 +1390,19 @@ impl Handle { /// session that merely connects is still billable. Surfaced on the session /// track for this tier, keyed by `label`, in its totals, and in `label`'s /// presence track. - pub fn session(&self, label: impl AsPath) -> Session { + pub fn session(&self, label: impl Into) -> Session { self.context(label, Context::Session) } /// Count one admitted request under `label` and meter its traffic without session presence. /// /// A request has no session end to classify, so it never records a failure. - pub fn request(&self, label: impl AsPath) -> Session { + pub fn request(&self, label: impl Into) -> Session { self.context(label, Context::Request) } - fn context(&self, label: impl AsPath, context: Context) -> Session { - let label = label.as_path().to_owned(); + fn context(&self, label: impl Into, context: Context) -> Session { + let label = label.into(); let presence = self.stats.session_counters(&self.tier, &label); if let Some(presence) = &presence { match context { @@ -1581,7 +1581,7 @@ struct SessionInner { /// Egress viewer refcount, keyed by absolute broadcast path: the first active /// subscription this context opens for a broadcast bumps `broadcasts_started`, the last /// to close bumps `broadcasts_ended` on the same counters, even across a tier change. - viewers: Mutex>, + viewers: Mutex>, /// What this session holds against each cap right now, so a tier change can seed the /// new tier's peaks with slots it still holds. held: Mutex)>>, @@ -1593,7 +1593,7 @@ struct SessionInner { struct Current { failure: Option, tier: Tier, - label: PathOwned, + label: Path, /// The presence gauge for `(tier, label)`, or `None` for a disabled registry. presence: Option>, } @@ -1667,9 +1667,9 @@ impl Session { } /// Record later traffic and session presence under `(tier, label)`; a request stays counted where admitted. - pub fn retag(&self, tier: Tier, label: impl AsPath) { + pub fn retag(&self, tier: Tier, label: impl Into) { let Some(inner) = &self.inner else { return }; - let label = label.as_path().to_owned(); + let label = label.into(); let mut current = inner.current.lock().unwrap_or_else(PoisonError::into_inner); if current.tier == tier && current.label == label { return; @@ -1740,20 +1740,20 @@ impl Session { /// Egress (publisher / reads) scope for a broadcast path. The path is the /// absolute broadcast name. - pub(crate) fn egress(&self, path: impl AsPath) -> Scope { + pub(crate) fn egress(&self, path: impl Into) -> Scope { self.scope(path, Side::Publisher) } /// Ingress (subscriber / writes) scope for a broadcast path. - pub(crate) fn ingress(&self, path: impl AsPath) -> Scope { + pub(crate) fn ingress(&self, path: impl Into) -> Scope { self.scope(path, Side::Subscriber) } - fn scope(&self, path: impl AsPath, side: Side) -> Scope { + fn scope(&self, path: impl Into, side: Side) -> Scope { let Some(inner) = &self.inner else { return Scope::default(); }; - let path = path.as_path().to_owned(); + let path = path.into(); let resolved = inner.resolve(&path); Scope { session: self.clone(), @@ -1765,7 +1765,7 @@ impl Session { /// Register one active egress subscription to `path` recording on `counters`. /// The first bumps `broadcasts_started` there. - fn viewer_open(&self, path: &PathOwned, counters: &Arc) { + fn viewer_open(&self, path: &Path, counters: &Arc) { let Some(inner) = &self.inner else { return }; let mut viewers = inner.viewers.lock().unwrap_or_else(PoisonError::into_inner); let viewer = viewers.entry(path.clone()).or_insert_with(|| { @@ -1780,7 +1780,7 @@ impl Session { /// Release one active egress subscription to `path`. The last bumps /// `broadcasts_ended` on the counters the first opened on. - fn viewer_close(&self, path: &PathOwned) { + fn viewer_close(&self, path: &Path) { let Some(inner) = &self.inner else { return }; let mut viewers = inner.viewers.lock().unwrap_or_else(PoisonError::into_inner); let Some(viewer) = viewers.get_mut(path) else { return }; @@ -1801,7 +1801,7 @@ impl Session { impl SessionInner { /// The counters `path` records on under the current tier, tagged with the /// generation they were resolved at. - fn resolve(&self, path: &PathOwned) -> Resolved { + fn resolve(&self, path: &Path) -> Resolved { let (generation, tier) = { let current = self.current.lock().unwrap_or_else(PoisonError::into_inner); (self.generation.load(Ordering::Relaxed), current.tier.clone()) @@ -1945,7 +1945,7 @@ pub(crate) struct Scope { side: Side, /// Absolute broadcast path, used to key the viewer refcount and as the /// `announced_bytes` length. - path: PathOwned, + path: Path, } /// A [`Scope`]'s counters and the session tier generation they belong to. @@ -2058,7 +2058,7 @@ pub(crate) struct Subscription { counters: Option>, side: Side, /// `Some((session, path))` on the egress side, to release the viewer refcount. - viewer: Option<(Session, PathOwned)>, + viewer: Option<(Session, Path)>, } impl Drop for Subscription { @@ -2267,7 +2267,7 @@ mod tests { .shared() .entries .lock() - .get(&PathOwned::from(path.to_string())) + .get(&Path::from(path.to_string())) .expect("entry") .tier(tier) } @@ -2279,7 +2279,7 @@ mod tests { .sessions .lock() .get(tier) - .and_then(|labels| labels.get(&PathOwned::from(label.to_string())).map(|c| c.snapshot().0)) + .and_then(|labels| labels.get(&Path::from(label.to_string())).map(|c| c.snapshot().0)) } fn test_stats() -> Registry { @@ -2458,7 +2458,7 @@ mod tests { // entry across drains while live, and prunes it on the first drain // after the last guard drops (returning the final values that once). let stats = test_stats(); - let key = PathOwned::from("foo/bar"); + let key = Path::from("foo/bar"); let ctx = stats.tier(Tier::default()).session("root"); let scope = ctx.egress("foo/bar"); let sub = scope.subscribe(); @@ -2506,7 +2506,7 @@ mod tests { // the registry indefinitely: announces_started != announces_ended means a // subscription could still begin at any moment. let stats = test_stats(); - let key = PathOwned::from("foo/bar"); + let key = Path::from("foo/bar"); let ctx = stats.tier(Tier::default()).session("root"); let scope = ctx.egress("foo/bar"); let guard = scope.announce(); diff --git a/rs/moq-net/src/transport.rs b/rs/moq-net/src/transport.rs index 7a44b10e87b..0b7f2d96a50 100644 --- a/rs/moq-net/src/transport.rs +++ b/rs/moq-net/src/transport.rs @@ -184,7 +184,7 @@ impl Stats for StatsUnavailable {} pub const MIN_RECEIVE_WINDOW: u64 = { // A type and a size prefix, each a varint of at most 8 bytes. const PREFIXES: u64 = 16; - let message = as crate::lite::Message>::MAX_SIZE as u64 + PREFIXES; + let message = ::MAX_SIZE as u64 + PREFIXES; let frame = crate::group::MAX_CACHE_BYTES; if message > frame { message } else { frame } }; diff --git a/rs/moq-relay/src/auth.rs b/rs/moq-relay/src/auth.rs index ae91683ac1a..ad6627c36b0 100644 --- a/rs/moq-relay/src/auth.rs +++ b/rs/moq-relay/src/auth.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use axum::http; use moq_auth::{Bytes, Grant, Request, lease}; use moq_auth::{Pattern, Patterns}; -use moq_net::{Path, PathOwned, stats::Tier}; +use moq_net::{Path, stats::Tier}; use serde::{Deserialize, Serialize}; use serde_with::{OneOrMany, serde_as}; use tokio::sync::{mpsc, oneshot}; @@ -427,10 +427,10 @@ pub struct Token { /// The path the session dialed, which a grant without `root` is relative to. pub(crate) path: String, /// The root the session is scoped to: the grant's `root` alias, else the dialed path. - pub root: PathOwned, + pub root: Path, /// Subtrees read from elsewhere on the origin: each path, relative to `root`, /// resolves at the absolute path it maps to. - pub mounts: Vec<(PathOwned, PathOwned)>, + pub mounts: Vec<(Path, Path)>, /// Patterns the session may subscribe to, relative to `root`. pub subscribe: moq_auth::Patterns, /// Patterns the session may publish, relative to `root`. @@ -438,7 +438,7 @@ pub struct Token { /// The tier this session's stats record under. pub tier: Tier, /// The path this session's stats record under, empty when the grant names none. - pub label: PathOwned, + pub label: Path, /// Whether the session is a cluster peer, so its routes entered elsewhere. pub peer: bool, /// Whether the peer is upstream, so it is never offered another upstream's routes. @@ -455,16 +455,16 @@ impl Token { let root = grant.root.as_deref().unwrap_or(path); Self { path: path.to_string(), - root: Path::new(root).to_owned(), + root: Path::new(root).clone(), mounts: grant .mounts .iter() - .map(|(at, target)| (Path::new(at).to_owned(), Path::new(target).to_owned())) + .map(|(at, target)| (Path::new(at).clone(), Path::new(target).clone())) .collect(), subscribe: grant.subscribe.clone(), publish: grant.publish.clone(), tier: crate::configured_tier(grant.tier.clone()), - label: Path::new(grant.label.as_deref().unwrap_or_default()).to_owned(), + label: Path::new(grant.label.as_deref().unwrap_or_default()).clone(), peer: grant.peer, upstream: grant.upstream, } @@ -1233,7 +1233,7 @@ mod tests { ("/rooms", "rooms", &[], &["chat"]), ] { let lease = auth.admit(auth.request(moq_auth::Transport::Quic, path)).await.unwrap(); - assert_eq!(lease.token().root, Path::new(root).to_owned(), "{path}"); + assert_eq!(lease.token().root, Path::new(root).clone(), "{path}"); assert_eq!(lease.token().publish, patterns(publish), "{path}"); assert_eq!(lease.token().subscribe, patterns(subscribe), "{path}"); } @@ -1293,7 +1293,7 @@ mod tests { }); for request in [anonymous, certificate] { let lease = auth.admit(request).await.unwrap(); - assert_eq!(lease.token().root, Path::new("anon/room").to_owned()); + assert_eq!(lease.token().root, Path::new("anon/room").clone()); assert_eq!(lease.token().subscribe, patterns(&["**"])); assert_eq!(lease.token().tier, Tier::default()); } @@ -1358,7 +1358,7 @@ mod tests { }; let admit = async { let lease = auth.admit(request()).await.expect("granted"); - assert_eq!(lease.token().root, Path::new("anon/room").to_owned()); + assert_eq!(lease.token().root, Path::new("anon/room").clone()); assert!(matches!(auth.admit(request()).await, Err(Error::Refused(_)))); assert!(matches!(auth.admit(request()).await, Err(Error::Refused(_)))); assert!(matches!(auth.admit(request()).await, Err(Error::Unavailable(_)))); @@ -1432,11 +1432,11 @@ mod tests { grant.tier = Some("gold".into()); grant.label = Some("project/key/123".into()); let token = Token::new("/vanity/room", &grant); - assert_eq!(token.root, Path::new("pid/room").to_owned()); + assert_eq!(token.root, Path::new("pid/room").clone()); assert_eq!(token.publish, patterns(&["alice/**"])); assert_eq!(token.subscribe, patterns(&["**"])); assert_eq!(token.tier, Tier::new("gold")); - assert_eq!(token.label, Path::new("project/key/123").to_owned()); + assert_eq!(token.label, Path::new("project/key/123").clone()); let patterns = patterns(&["*/chat", "alice", ""]); let token = Token::new("/", &Grant::new(patterns.clone(), Patterns::new())); @@ -1457,7 +1457,7 @@ mod tests { let mounted = Token::new("/room", &mounted); assert_eq!( mounted.mounts, - [(Path::new(".svc").to_owned(), Path::new(".svc/room").to_owned())] + [(Path::new(".svc").clone(), Path::new(".svc/room").clone())] ); assert!(wide.covered_by(&wide)); assert!(narrow.covered_by(&wide)); @@ -1498,12 +1498,12 @@ mod tests { let mut aliased = everything(); aliased.root = Some("pid/room".into()); let token = Token::new("/vanity/room", &aliased); - assert_eq!(token.root, Path::new("pid/room").to_owned()); + assert_eq!(token.root, Path::new("pid/room").clone()); // The same alias still names the same root. assert_eq!(token.recheck(&aliased).root, token.root); // A grant without the alias is relative to what was dialed, not to the old root. - assert_eq!(token.recheck(&everything()).root, Path::new("vanity/room").to_owned()); + assert_eq!(token.recheck(&everything()).root, Path::new("vanity/room").clone()); } } diff --git a/rs/moq-relay/src/cluster.rs b/rs/moq-relay/src/cluster.rs index e941f83bb1b..84989cb7913 100644 --- a/rs/moq-relay/src/cluster.rs +++ b/rs/moq-relay/src/cluster.rs @@ -2481,7 +2481,7 @@ mod tests { let stats = config.build(cluster.origin.clone()).expect("stats"); let cluster = cluster.with_stats(stats); - let path = moq_net::Path::new(".stats").join("node").join("test"); + let path = moq_net::Path::new(".stats/node/test"); let consumer = cluster.origin.consume(); let route = tokio::time::timeout(std::time::Duration::from_secs(5), consumer.routed(&path)) .await @@ -3090,7 +3090,7 @@ mod tests { .expect("broadcast resolves") .expect("broadcast present"); - let stats_path = moq_net::Path::new(".stats").join("node").join("test"); + let stats_path = moq_net::Path::new(".stats/node/test"); tokio::time::timeout(Duration::from_secs(5), consumer.routed(&stats_path)) .await .expect("stats announced") diff --git a/rs/moq-relay/src/stats.rs b/rs/moq-relay/src/stats.rs index 30615688aaf..f29257463ed 100644 --- a/rs/moq-relay/src/stats.rs +++ b/rs/moq-relay/src/stats.rs @@ -6,7 +6,7 @@ use std::time::Duration; -use moq_net::PathOwned; +use moq_net::Path; use moq_net::origin; use serde::{Deserialize, Serialize}; @@ -125,7 +125,7 @@ impl Config { let prefix = self.prefix.clone(); let interval = self.interval(); anyhow::ensure!(interval != Some(Duration::ZERO), "stats.interval must be non-zero"); - let node = self.node.clone().map(PathOwned::from); + let node = self.node.clone().map(Path::from); let depth = self.depth; let linger = self.linger(); tracing::info!(prefix, ?interval, node = ?node, depth, ?linger, "stats publishing enabled"); diff --git a/rs/moq-relay/tests/integration/smoke.rs b/rs/moq-relay/tests/integration/smoke.rs index 4dc925b4f72..74f727ed918 100644 --- a/rs/moq-relay/tests/integration/smoke.rs +++ b/rs/moq-relay/tests/integration/smoke.rs @@ -186,7 +186,7 @@ async fn relay_websocket_round_trip_uses_newest_version() { .await .expect("announcement timeout") .expect("origin closed"); - let path = moq_net::Path::new(update.prefix.as_str()).to_owned(); + let path = moq_net::Path::new(update.prefix.as_str()).clone(); assert!(active, "expected announce, got retraction"); // Auth root for `/smoke` is "smoke"; the broadcast "test" announces underneath. assert_eq!(path.as_str(), "test"); @@ -519,7 +519,7 @@ async fn relay_websocket_root_path_upgrades() { .await .expect("announcement timeout") .expect("origin closed"); - let path = moq_net::Path::new(update.prefix.as_str()).to_owned(); + let path = moq_net::Path::new(update.prefix.as_str()).clone(); assert!(active, "expected announce, got retraction"); assert_eq!(path.as_str(), "test"); let bc = sub_consumer @@ -657,7 +657,7 @@ async fn internal_tcp_round_trip() { .await .expect("announcement timeout") .expect("origin closed"); - let path = moq_net::Path::new(update.prefix.as_str()).to_owned(); + let path = moq_net::Path::new(update.prefix.as_str()).clone(); assert!(active, "expected announce, got retraction"); assert_eq!(path.as_str(), "test"); let bc = sub_consumer @@ -841,7 +841,7 @@ async fn path_round_trip(version: moq_net::Version, pub_url: url::Url, sub_url: .await .expect("announcement timeout") .expect("origin closed"); - let path = moq_net::Path::new(update.prefix.as_str()).to_owned(); + let path = moq_net::Path::new(update.prefix.as_str()).clone(); drop(track); drop(bc); diff --git a/rs/moq-room/src/claims.rs b/rs/moq-room/src/claims.rs index 038273ab370..08c42529392 100644 --- a/rs/moq-room/src/claims.rs +++ b/rs/moq-room/src/claims.rs @@ -9,9 +9,9 @@ /// and `publish` is `/**` so a participant cannot publish at anyone /// else's paths. An identity that is empty after normalization or contains `*` is /// refused, since a wildcard would let the participant publish as someone else. -pub fn claims(room: impl moq_net::AsPath, identity: impl moq_net::AsPath) -> Result { - let room = room.as_path(); - let identity = identity.as_path(); +pub fn claims(room: impl Into, identity: impl Into) -> Result { + let room: moq_net::Path = room.into(); + let identity: moq_net::Path = identity.into(); if identity.is_empty() { return Err(crate::Error::EmptyIdentity); } diff --git a/rs/moq-room/src/path.rs b/rs/moq-room/src/path.rs index 4bd2bc15ebe..70538616163 100644 --- a/rs/moq-room/src/path.rs +++ b/rs/moq-room/src/path.rs @@ -4,7 +4,7 @@ //! `alice/camera`, `alice/camera.hang`, and `guest/uuid/screen` are all valid. //! A `.hang` suffix on the kind is optional and equivalent. -use moq_net::{AsPath, Path, PathOwned}; +use moq_net::Path; /// The two broadcasts each participant may publish. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -29,7 +29,7 @@ impl Kind { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Parsed { /// Path prefix identifying the participant; may be more than one segment. - pub identity: PathOwned, + pub identity: Path, /// `camera` (camera + mic) or `screen` (screenshare). pub kind: Kind, } @@ -47,29 +47,30 @@ pub fn kind_from_segment(segment: &str) -> Option { /// Split a room-relative broadcast path into identity and kind. /// /// Returns `None` when there is no identity, or the last segment is not a kind. -pub fn parse(path: impl AsPath) -> Option { - let path = path.as_path(); +pub fn parse(path: impl Into) -> Option { + let path: Path = path.into(); let mut parts: Vec<&str> = path.parts().collect(); let last = parts.pop()?; if parts.is_empty() { return None; } let kind = kind_from_segment(last)?; - let identity = Path::new(&parts.join("/")).to_owned(); + let identity = Path::new(&parts.join("/")).clone(); Some(Parsed { identity, kind }) } /// The broadcast path a participant publishes for `kind`. -pub fn broadcast_path(identity: impl AsPath, kind: Kind) -> PathOwned { - identity.as_path().join(format!("{}.hang", kind.as_str())) +pub fn broadcast_path(identity: impl Into, kind: Kind) -> Path { + let identity: Path = identity.into(); + identity.join(&Path::from(format!("{}.hang", kind.as_str()))) } #[cfg(test)] mod tests { use super::*; - fn p(s: &str) -> PathOwned { - Path::new(s).to_owned() + fn p(s: &str) -> Path { + Path::new(s).clone() } #[test] diff --git a/rs/moq-room/src/room.rs b/rs/moq-room/src/room.rs index 41217cee014..8f08aba0da0 100644 --- a/rs/moq-room/src/room.rs +++ b/rs/moq-room/src/room.rs @@ -3,7 +3,7 @@ use std::task::{Poll, ready}; use moq_net::{ - PathOwned, announce, broadcast, + Path, announce, broadcast, origin::{self, Requesting}, }; @@ -13,11 +13,11 @@ use crate::path::{Kind, parse}; #[derive(Clone)] pub struct Event { /// Participant identity (everything before `camera`/`screen`). - pub identity: PathOwned, + pub identity: Path, /// `camera` or `screen`. pub kind: Kind, /// Broadcast path relative to the room prefix. - pub path: PathOwned, + pub path: Path, /// The live broadcast, or `None` when it went offline. pub broadcast: Option, } @@ -35,9 +35,9 @@ impl std::fmt::Debug for Event { /// An in-flight request for the broadcast an active announcement named. struct Inflight { - identity: PathOwned, + identity: Path, kind: Kind, - path: PathOwned, + path: Path, request: Requesting, } @@ -48,7 +48,7 @@ struct Inflight { pub struct Room { origin: origin::Consumer, announced: announce::Consumer, - local: Option, + local: Option, inflight: Option, } @@ -62,7 +62,7 @@ impl std::fmt::Debug for Room { impl Room { /// Watch announcements on `origin`, skipping `local` when set. - pub fn new(origin: &origin::Consumer, local: Option) -> Self { + pub fn new(origin: &origin::Consumer, local: Option) -> Self { Self { announced: origin.announced(), origin: origin.clone(), @@ -171,7 +171,7 @@ mod tests { #[tokio::test] async fn yields_camera_and_skips_local_and_unknown() { let origin = origin(); - let local = Path::new("alice").to_owned(); + let local = Path::new("alice").clone(); let mut room = Room::new(&origin.consume(), Some(local)); let _alice = publish(&origin, "alice/camera"); diff --git a/rs/moq-rtc/src/client/mod.rs b/rs/moq-rtc/src/client/mod.rs index 390d957c23b..e7995c89b46 100644 --- a/rs/moq-rtc/src/client/mod.rs +++ b/rs/moq-rtc/src/client/mod.rs @@ -84,7 +84,7 @@ impl Client { &self, url: Url, origin: moq_net::origin::Consumer, - path: impl moq_net::AsPath, + path: impl Into, ) -> crate::Result<()> { whip::dial(self, url, origin, path).await } diff --git a/rs/moq-rtc/src/client/whip.rs b/rs/moq-rtc/src/client/whip.rs index 021e672515c..50ae83a13ba 100644 --- a/rs/moq-rtc/src/client/whip.rs +++ b/rs/moq-rtc/src/client/whip.rs @@ -21,7 +21,7 @@ pub(crate) async fn dial( client: &Client, url: Url, origin: moq_net::origin::Consumer, - path: impl moq_net::AsPath, + path: impl Into, ) -> Result<()> { let broadcast = origin.request_broadcast(path, None).await?; let source = EgressSource::new(moq_mux::Source::new(origin, broadcast)).await?; diff --git a/rs/moq-rtc/src/egress.rs b/rs/moq-rtc/src/egress.rs index 30876d16aae..c5985214f7e 100644 --- a/rs/moq-rtc/src/egress.rs +++ b/rs/moq-rtc/src/egress.rs @@ -213,7 +213,7 @@ async fn follow_catalog(mut consumer: moq_mux::catalog::Consumer<()>, tx: watch: } } -fn valid_reference(source: &moq_mux::Source, broadcast: Option<&moq_net::path::Relative<'_>>) -> bool { +fn valid_reference(source: &moq_mux::Source, broadcast: Option<&moq_net::path::Relative>) -> bool { source.resolve_reference(broadcast).is_some() } @@ -338,7 +338,7 @@ fn pick_audio<'a>( /// must be, and the track's name there. A catalog update that changes it moves the rendition. #[derive(Clone, Debug, PartialEq, Eq)] struct Target { - path: Option, + path: Option, epoch: Option, track: String, } @@ -626,7 +626,7 @@ mod tests { let mut catalog = Catalog::default(); let mut escaped_audio = AudioConfig::new(AudioCodec::Opus, 48_000, 2); - escaped_audio.broadcast = Some(Relative::new("../../source").to_owned()); + escaped_audio.broadcast = Some(Relative::new("../../source").clone()); catalog.audio.renditions.insert("opus".to_string(), escaped_audio); let mut escaped_video = VideoConfig::new(H264 { @@ -635,11 +635,11 @@ mod tests { level: 0x1e, inline: false, }); - escaped_video.broadcast = Some(Relative::new("../../source").to_owned()); + escaped_video.broadcast = Some(Relative::new("../../source").clone()); catalog.video.renditions.insert("h264".to_string(), escaped_video); let mut valid_video = VideoConfig::new(VideoCodec::VP8); - valid_video.broadcast = Some(Relative::new("./source").to_owned()); + valid_video.broadcast = Some(Relative::new("./source").clone()); catalog.video.renditions.insert("vp8".to_string(), valid_video); let (writes_tx, writes_rx) = mpsc::channel(1); @@ -767,7 +767,7 @@ mod tests { // A higher-bitrate Opus this source cannot reach loses to the best one it can. let mut escaped = opus(Some(512_000)); - escaped.broadcast = Some(Relative::new("../../source").to_owned()); + escaped.broadcast = Some(Relative::new("../../source").clone()); catalog.audio.renditions.insert("d".to_string(), escaped); let (name, _) = pick_audio(&source, &catalog).unwrap(); assert_eq!(name, "c"); @@ -919,7 +919,7 @@ mod tests { let mut config = VideoConfig::new(VideoCodec::VP8); config.container = hang::catalog::Container::Legacy; config.track = Some(track.to_string()); - config.broadcast = broadcast.map(|broadcast| Relative::new(broadcast).into_owned()); + config.broadcast = broadcast.map(|broadcast| Relative::new(broadcast)); catalog .modify() .unwrap() @@ -1249,7 +1249,7 @@ mod tests { config.container = hang::catalog::Container::Legacy; config.coded_width = Some(1920); config.coded_height = Some(1080); - config.broadcast = Some(Relative::new("./missing").to_owned()); + config.broadcast = Some(Relative::new("./missing").clone()); catalog .modify() .unwrap() diff --git a/rs/moq-rtc/src/server/whep.rs b/rs/moq-rtc/src/server/whep.rs index e13472808f6..09efd76078d 100644 --- a/rs/moq-rtc/src/server/whep.rs +++ b/rs/moq-rtc/src/server/whep.rs @@ -132,12 +132,12 @@ async fn delete(State(state): State, Path(path): Path) -> S pub async fn accept( server: &Server, subscriber: &moq_net::origin::Consumer, - broadcast: impl moq_net::AsPath, + broadcast: impl Into, offer: &str, ) -> Result { let offer = sdp::parse_offer(offer)?; - let source = resolve(subscriber, broadcast.as_path()).await?; + let source = resolve(subscriber, broadcast.into()).await?; let codecs = source.catalog_codecs(); if codecs.is_empty() { return Err(Error::NoRenditions); @@ -199,7 +199,7 @@ fn is_sdp(headers: &HeaderMap) -> bool { /// Both waits share one bound, or an unannounced broadcast, or an announced-but-catalog-less /// one, would park the handler forever. No route by the deadline is /// [`moq_net::Error::Unroutable`], so the WHEP client sees a 404 and retries (typical). -async fn resolve(subscriber: &moq_net::origin::Consumer, path: moq_net::Path<'_>) -> Result { +async fn resolve(subscriber: &moq_net::origin::Consumer, path: moq_net::Path) -> Result { let deadline = moq_clock::now() + CATALOG_TIMEOUT; let broadcast = moq_clock::timeout_at(deadline, subscriber.routed_broadcast(path)) .await diff --git a/rs/moq-rtc/src/server/whip.rs b/rs/moq-rtc/src/server/whip.rs index 4bd691f66fb..fadf11b0bb2 100644 --- a/rs/moq-rtc/src/server/whip.rs +++ b/rs/moq-rtc/src/server/whip.rs @@ -114,11 +114,11 @@ async fn delete(State(state): State, Path(path): Path) -> S pub async fn accept( server: &Server, publisher: &moq_net::origin::Producer, - broadcast: impl moq_net::AsPath, + broadcast: impl Into, offer: &str, ) -> Result { let offer = sdp::parse_offer(offer)?; - let broadcast = broadcast.as_path(); + let broadcast: moq_net::Path = broadcast.into(); // Create the broadcast hidden up front, so an out-of-scope offer is refused before // any mux work. It announces only once negotiation succeeds (below). diff --git a/rs/moq-rtmp/src/dial.rs b/rs/moq-rtmp/src/dial.rs index 2296f8cf436..ad32dfd3079 100644 --- a/rs/moq-rtmp/src/dial.rs +++ b/rs/moq-rtmp/src/dial.rs @@ -226,9 +226,9 @@ impl Client { mut self, stream_key: &str, origin: origin::Consumer, - path: impl moq_net::AsPath, + path: impl Into, ) -> Result<()> { - let path = path.as_path(); + let path: moq_net::Path = path.into(); let request = self .session .request_publishing(stream_key.to_string(), PublishRequestType::Live) @@ -313,8 +313,8 @@ impl Client { /// ends cleanly: the finished broadcast stays up for the [linger](Self::with_linger), and /// the future resolves after that. Dropping the future ends the linger early. A /// connection that drops first closes the broadcast at once. - pub async fn pull(mut self, stream_key: &str, origin: &origin::Producer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn pull(mut self, stream_key: &str, origin: &origin::Producer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); let request = self .session .request_playback(stream_key.to_string()) diff --git a/rs/moq-rtmp/src/server.rs b/rs/moq-rtmp/src/server.rs index cd6df1cd260..8bdf982d03e 100644 --- a/rs/moq-rtmp/src/server.rs +++ b/rs/moq-rtmp/src/server.rs @@ -630,8 +630,8 @@ impl Publish { /// stays up for the [linger](Self::with_linger), and the future resolves after that. /// Dropping the future ends the linger early. A connection that drops without /// unpublishing closes the broadcast at once. - pub async fn accept(mut self, origin: &origin::Producer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn accept(mut self, origin: &origin::Producer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); // Reserve the broadcast path before telling the client the publish succeeded: // if the origin refuses `path`, reject cleanly instead of accepting and then // dropping the connection a moment later. @@ -789,8 +789,8 @@ impl Play { /// before the publisher), cancelling cleanly if the viewer disconnects first. /// This future resolves when playback ends, so callers usually run it on its /// own task. - pub async fn accept(mut self, origin: &origin::Consumer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn accept(mut self, origin: &origin::Consumer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); // Wait for the broadcast before telling the client playback started. Feed the // client's bytes through the session (not discard them) so its deserializer // stays in sync for everything `play_pump` parses next. diff --git a/rs/moq-srt/src/dial.rs b/rs/moq-srt/src/dial.rs index cc84e22a2c5..98e150cecbe 100644 --- a/rs/moq-srt/src/dial.rs +++ b/rs/moq-srt/src/dial.rs @@ -151,8 +151,8 @@ impl Client { } /// Push a MoQ broadcast out to the remote as MPEG-TS until the broadcast ends. - pub async fn publish(&self, origin: &origin::Consumer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn publish(&self, origin: &origin::Consumer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); let socket = self.call(Mode::Publish).await?; let options = crate::ts::Options { latency: self.max_delay, @@ -166,8 +166,8 @@ impl Client { /// /// The finished broadcast then stays up for the [linger](Self::with_linger), as /// [`Publish::accept`](crate::Publish::accept) describes, and the future resolves after that. - pub async fn pull(&self, origin: &origin::Producer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn pull(&self, origin: &origin::Producer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); let socket = self.call(Mode::Request).await?; let ingest = crate::server::Ingest { config: moq_mux::catalog::Config::default() diff --git a/rs/moq-srt/src/server.rs b/rs/moq-srt/src/server.rs index 9e7f47c5471..4216b6945ff 100644 --- a/rs/moq-srt/src/server.rs +++ b/rs/moq-srt/src/server.rs @@ -418,8 +418,8 @@ impl Publish { /// Once the stream ends, the finished broadcast stays up for the /// [linger](Self::with_linger), and the future resolves after that. Dropping the future /// ends the linger early. A socket or demux error closes the broadcast at once. - pub async fn accept(self, origin: &origin::Producer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn accept(self, origin: &origin::Producer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); let socket = self.0.request.accept(None).await?; tracing::info!(peer = %self.0.peer, %path, "SRT publish accepted"); let config = moq_mux::catalog::Config::default() @@ -493,8 +493,8 @@ impl Subscribe { /// Waits for the broadcast to be announced (so a caller may connect before the /// publisher), cancelling cleanly if the caller disconnects first. This future /// resolves when playback ends, so callers usually run it on its own task. - pub async fn accept(self, origin: &origin::Consumer, path: impl moq_net::AsPath) -> Result<()> { - let path = path.as_path(); + pub async fn accept(self, origin: &origin::Consumer, path: impl Into) -> Result<()> { + let path: moq_net::Path = path.into(); let socket = self.0.request.accept(None).await?; tracing::info!(peer = %self.0.peer, %path, "SRT subscribe accepted"); let options = crate::ts::Options { diff --git a/rs/moq-stats/src/aggregate.rs b/rs/moq-stats/src/aggregate.rs index 5362551d7f0..b0415aaac62 100644 --- a/rs/moq-stats/src/aggregate.rs +++ b/rs/moq-stats/src/aggregate.rs @@ -15,7 +15,7 @@ use moq_clock::Instant; use moq_net::kio::{self, Pending, Waiter}; use moq_net::stats::{Presence, Traffic}; use moq_net::track::Subscribing; -use moq_net::{AsPath, Epoch, PathOwned, broadcast, origin}; +use moq_net::{Epoch, Path, broadcast, origin}; use crate::{ Encoding, Group, GroupFrame, Prefix, PrefixFrame, PresenceFrame, Result, Totals, TotalsFrame, Usage, group_track, @@ -34,7 +34,7 @@ use crate::{ pub struct Config { /// Top-level path stats are published under (default `.stats`). Must match /// the producer's prefix. - pub prefix: PathOwned, + pub prefix: Path, /// Which encoding of the totals, presence, and prefix tracks to read. /// [`Encoding::Compressed`] carries the same data for a fraction of the /// bytes. Defaults to [`Encoding::Plain`]. A group's track compresses @@ -56,7 +56,7 @@ impl Config { } /// Override the top-level prefix (default `.stats`). Must match the producer. - pub fn with_prefix(mut self, prefix: impl Into) -> Self { + pub fn with_prefix(mut self, prefix: impl Into) -> Self { self.prefix = prefix.into(); self } @@ -77,7 +77,7 @@ impl Config { impl Default for Config { fn default() -> Self { Self { - prefix: PathOwned::from(".stats"), + prefix: Path::from(".stats"), encoding: Encoding::Plain, // Well above reconnect times; a longer grace only costs memory. grace: Duration::from_secs(600), @@ -149,7 +149,7 @@ impl Consumer { /// A merged reader over group `group`'s track on every node broadcast that /// lists it; see [`Self::totals`]. - pub fn group(&self, group: impl AsPath) -> GroupConsumer { + pub fn group(&self, group: impl Into) -> GroupConsumer { GroupConsumer { inner: self.merged(group_track(group), Encoding::Plain), } @@ -157,7 +157,7 @@ impl Consumer { /// A merged reader over `label`'s presence track, holding it on every /// node broadcast; see [`Self::totals`]. - pub fn presence(&self, label: impl AsPath) -> PresenceConsumer { + pub fn presence(&self, label: impl Into) -> PresenceConsumer { PresenceConsumer { inner: self.merged(presence_track(label, self.config.encoding), self.config.encoding), } @@ -165,7 +165,7 @@ impl Consumer { /// A merged reader over broadcast prefix `path`'s track, holding it on /// every node broadcast; see [`Self::totals`]. - pub fn prefix(&self, path: impl AsPath) -> PrefixConsumer { + pub fn prefix(&self, path: impl Into) -> PrefixConsumer { PrefixConsumer { inner: self.merged(prefix_track(path, self.config.encoding), self.config.encoding), } @@ -740,7 +740,7 @@ struct Node { offered: bool, /// The announced path, relative to the announce cursor: what a re-resolve /// after the reader ends requests again. - path: PathOwned, + path: Path, /// The publisher instance every initial and retry resolution must serve. epoch: Option, /// The last known publisher identity, retained while route metadata vanishes. @@ -789,15 +789,15 @@ struct Merged { /// Resolves announced node paths into broadcasts. origin: origin::Consumer, announce: moq_net::announce::Consumer, - prefix: PathOwned, + prefix: Path, /// Track name subscribed on each node broadcast. name: String, config: moq_json::snapshot::consumer::Config, /// One entry per live or recently departed node broadcast, keyed by /// absolute announced path and route epoch, so each epoch is its own entry. - nodes: HashMap<(PathOwned, Option), Node>, + nodes: HashMap<(Path, Option), Node>, /// The immediately outgoing identity, including while it remains in grace. - last_announced: HashMap>, + last_announced: HashMap>, /// How long a departed node waits before folding into `retired`. grace: Duration, /// The summed last frames of every node whose grace elapsed. @@ -845,7 +845,7 @@ impl Merged { // new one starts, so its reader resolves afresh. Poll::Ready(Some(moq_net::announce::Event::Restart(update))) => { let mut ended = update.clone(); - let absolute = self.announce.absolute(&update.prefix).to_owned(); + let absolute = self.announce.absolute(&update.prefix).clone(); ended.route.epoch = self.last_announced.get(&absolute).cloned().flatten(); changed |= self.apply_announce(ended, false); changed |= self.apply_announce(update, true); @@ -895,7 +895,7 @@ impl Merged { /// leaving, does). fn apply_announce(&mut self, update: moq_net::announce::Announce, active: bool) -> bool { let path = update.prefix; - let absolute = self.announce.absolute(&path).to_owned(); + let absolute = self.announce.absolute(&path).clone(); // Only fold node-category routes; skip sibling categories a producer // may publish under the same prefix. A route names a prefix, and node @@ -1159,7 +1159,7 @@ fn advance( } /// Start resolving `path` (relative to the announce cursor) into a broadcast. -fn resolve(origin: &origin::Consumer, path: &PathOwned, epoch: Option) -> Reader { +fn resolve(origin: &origin::Consumer, path: &Path, epoch: Option) -> Reader { let pending = origin.request_broadcast(path, epoch); let queued = pending.is_queued(); Reader::Resolving { pending, queued } @@ -1197,7 +1197,7 @@ mod tests { use std::time::Duration; - use moq_net::{PathOwned, Timestamp, announce, broadcast, origin, track}; + use moq_net::{Path, Timestamp, announce, broadcast, origin, track}; use moq_net::stats::Tier; @@ -1287,7 +1287,7 @@ mod tests { Producer::new( produce::Config::new() .with_origin(origin.clone()) - .with_node(PathOwned::from(node.to_string())) + .with_node(Path::from(node.to_string())) .with_depth(1), ) } @@ -1580,7 +1580,7 @@ mod tests { #[test] fn outgoing_epoch_metadata_migrates_without_choosing_a_grace_entry() { let origin = produce_origin(); - let path = PathOwned::from(".stats/node/a"); + let path = Path::from(".stats/node/a"); let a: Epoch = "01900000-0000-7000-8000-000000000001".parse().unwrap(); let b: Epoch = "01900000-0000-7000-8000-000000000002".parse().unwrap(); let c: Epoch = "01900000-0000-7000-8000-000000000003".parse().unwrap(); @@ -1721,7 +1721,7 @@ mod tests { .unwrap(); assert_eq!(unversioned[""].paths["acme/room"].publisher.bytes, 100); assert!(matches!( - merged.nodes[&(PathOwned::from(".stats/node/a"), None)].reader, + merged.nodes[&(Path::from(".stats/node/a"), None)].reader, Reader::Active(_) )); } @@ -1761,7 +1761,7 @@ mod tests { (new, front, warm, driver) }); ready.wait(); - let key = (PathOwned::from(".stats/node/a"), (!drop_epoch).then_some(epoch_a)); + let key = (Path::from(".stats/node/a"), (!drop_epoch).then_some(epoch_a)); let node = merged.nodes.get_mut(&key).unwrap(); advance(node, &merged.origin, &merged.config, &name, &Waiter::noop()); let paths = node.shape.paths.last.as_ref().unwrap(); @@ -2169,7 +2169,7 @@ mod tests { let node = Producer::new( produce::Config::new() .with_origin(origin.clone()) - .with_node(PathOwned::from("a")) + .with_node(Path::from("a")) .with_depth(1) .with_linger(Duration::ZERO), ); @@ -2263,9 +2263,9 @@ mod tests { /// A node announced at `.stats/node/a` with no epoch, ready to observe /// frames by hand. - fn hand_merged() -> (Merged>, (PathOwned, Option)) { + fn hand_merged() -> (Merged>, (Path, Option)) { let origin = produce_origin(); - let path = PathOwned::from(".stats/node/a"); + let path = Path::from(".stats/node/a"); let mut merged = Merged::>::new(origin.consume(), &Config::new(), "p".into(), Encoding::Plain); merged.apply_announce( moq_net::announce::Announce { @@ -2287,7 +2287,7 @@ mod tests { value.sessions_failed.transport = failed; value }; - let path = PathOwned::from(".stats/node/b"); + let path = Path::from(".stats/node/b"); merged.apply_announce( moq_net::announce::Announce { prefix: path.clone(), diff --git a/rs/moq-stats/src/consume.rs b/rs/moq-stats/src/consume.rs index 2e24af21abf..978c8349f81 100644 --- a/rs/moq-stats/src/consume.rs +++ b/rs/moq-stats/src/consume.rs @@ -1,6 +1,6 @@ //! The consuming half: typed readers over one published stats broadcast. -use moq_net::{AsPath, broadcast}; +use moq_net::{Path, broadcast}; use crate::{ Encoding, GroupFrame, PrefixFrame, PresenceFrame, Result, TotalsFrame, group_track, prefix_track, presence_track, @@ -62,7 +62,7 @@ impl Consumer { /// Subscribe to the presence track for `label`, awaiting the /// subscription handshake. - pub async fn presence(&self, label: impl AsPath) -> Result { + pub async fn presence(&self, label: impl Into) -> Result { let name = presence_track(label, self.config.encoding); Ok(PresenceConsumer { inner: self.subscribe(&name).await?, @@ -71,7 +71,7 @@ impl Consumer { /// Subscribe to the prefix track for broadcast prefix `path`, awaiting the /// subscription handshake. - pub async fn prefix(&self, path: impl AsPath) -> Result { + pub async fn prefix(&self, path: impl Into) -> Result { let name = prefix_track(path, self.config.encoding); Ok(PrefixConsumer { inner: self.subscribe(&name).await?, @@ -82,7 +82,7 @@ impl Consumer { /// The track compresses itself, so [`Config::encoding`] does not apply. /// Fails with [`NotFound`](moq_net::Error::NotFound) unless the group is /// listed. - pub async fn group(&self, group: impl AsPath) -> Result { + pub async fn group(&self, group: impl Into) -> Result { let track = self.broadcast.track(&group_track(group))?.subscribe(None).await?; Ok(GroupConsumer { inner: moq_json::snapshot::Consumer::new(track, Default::default()), @@ -185,7 +185,7 @@ mod tests { use std::time::Duration; - use moq_net::{Consume, PathOwned, Timestamp, announce, origin, track}; + use moq_net::{Consume, Path, Timestamp, announce, origin, track}; use crate::{Encoding, Producer, Tier, produce}; @@ -196,7 +196,7 @@ mod tests { let producer = Producer::new( produce::Config::new() .with_origin(origin.clone()) - .with_node(PathOwned::from("sjc")), + .with_node(Path::from("sjc")), ); (producer, origin) } diff --git a/rs/moq-stats/src/lib.rs b/rs/moq-stats/src/lib.rs index 3e094a72946..878cf5ea471 100644 --- a/rs/moq-stats/src/lib.rs +++ b/rs/moq-stats/src/lib.rs @@ -49,7 +49,7 @@ use std::collections::BTreeMap; /// can depend on this crate alone. pub use moq_net::stats::{Failure, Failures, Handle, Presence, RefusalKind, Refusals, Registry, Role, Tier, Traffic}; -use moq_net::{AsPath, Epoch, Path, PathOwned}; +use moq_net::{Epoch, Path}; /// One frame off the totals track: each tier's [`Totals`], keyed by tier label /// (`""` for the default tier). @@ -223,27 +223,27 @@ pub fn totals_track(encoding: Encoding) -> String { /// The presence track name for a stats label: `