The MoQT transport that joule-code-collab's network rung needs lived in the private joulesperbit repo (as inv-moq + jwp), so a public consumer couldn't depend on it. Port both into the public open-standards joule-code workspace, self-contained (concrete dep versions, not workspace-inherited) so a bare git clone builds them: - crates/jwp: Joule Wire Protocol — energy-aware binary framing (CBOR, zstd/lz4, TCP + optional QUIC). Self-contained, external deps only. - crates/joule-moq: MoQT (Media-over-QUIC) wire protocol + JWP signed-receipt extension for CommunicationOS — Publisher/Subscriber/Relay traits, MockFabric (in-process) + a production QUIC path (quic feature). Renamed from inv-moq to drop the invisible-* namespace and match the joule-code branding; depends only on jwp. Both build (default + quic) and pass their tests (joule-moq 29, jwp 152) in the new home. The transport-moqt example now path-deps the public joule-moq instead of reaching into joulesperbit, and its end-to-end BFT-ops-over-MoQT-with-signed- receipts tests pass. This unblocks public consumers (agent-zed) from git-depping the transport for cross-machine fleet coordination. Co-Authored-By: Claude Opus 4.8 <[email protected]>
129 lines
4.5 KiB
Rust
129 lines
4.5 KiB
Rust
//! Endpoint trait surface.
|
|
//!
|
|
//! Three role-specific traits compose the public API:
|
|
//!
|
|
//! - [`Publisher`] — announces tracks, produces signed groups, emits objects.
|
|
//! - [`Subscriber`] — subscribes to tracks, receives objects, verifies receipts.
|
|
//! - [`Relay`] — forwards objects between peers, optionally re-signs receipts
|
|
//! when serving as the publisher's authorized agent.
|
|
//!
|
|
//! [`MoqEndpoint`] is the cross-cutting supertrait used by code that doesn't
|
|
//! care which role a peer plays.
|
|
//!
|
|
//! All operations are async because production transports are network-bound.
|
|
|
|
use crate::error::MoqResult;
|
|
use crate::messages::ObjectHeader;
|
|
use crate::receipt::{GroupReceipt, ObjectReceipt};
|
|
use crate::track::{GroupId, ObjectId, Track, TrackAlias, TrackName, TrackNamespace};
|
|
use async_trait::async_trait;
|
|
use bytes::Bytes;
|
|
use tokio::sync::mpsc;
|
|
|
|
/// Cross-role supertrait: every endpoint can identify itself and report
|
|
/// session-level state.
|
|
#[async_trait]
|
|
pub trait MoqEndpoint: Send + Sync {
|
|
/// Negotiated MoQT draft version after handshake.
|
|
fn version(&self) -> Option<u32>;
|
|
|
|
/// Whether this endpoint is a publisher, subscriber, pubsub, or relay.
|
|
fn role_label(&self) -> &'static str;
|
|
|
|
/// Whether the JWP receipt extension was negotiated successfully.
|
|
fn jwp_receipts_enabled(&self) -> bool;
|
|
|
|
/// Initiate a graceful close.
|
|
async fn close(&mut self) -> MoqResult<()>;
|
|
}
|
|
|
|
/// Publisher: announces and produces tracks.
|
|
#[async_trait]
|
|
pub trait Publisher: MoqEndpoint {
|
|
/// Announce a namespace as a producer.
|
|
async fn announce(&mut self, namespace: TrackNamespace) -> MoqResult<()>;
|
|
|
|
/// Open a new group on a track. Returns a [`GroupHandle`] used to push
|
|
/// objects and finalize the group's signed receipt.
|
|
async fn open_group(
|
|
&mut self,
|
|
namespace: &TrackNamespace,
|
|
track_name: &TrackName,
|
|
group_id: GroupId,
|
|
) -> MoqResult<Box<dyn GroupHandle>>;
|
|
|
|
/// Catalog all locally-known tracks for ANNOUNCE flows.
|
|
fn local_tracks(&self) -> Vec<Track>;
|
|
}
|
|
|
|
/// Group-scoped handle for publishers. Push objects, then `finalize` to
|
|
/// emit the group receipt covering all of them.
|
|
#[async_trait]
|
|
pub trait GroupHandle: Send + Sync {
|
|
/// Push an object payload. Returns the (object_id, content_hash, joules).
|
|
async fn push_object(
|
|
&mut self,
|
|
payload: Bytes,
|
|
joules_micro: u64,
|
|
) -> MoqResult<(ObjectId, [u8; 32])>;
|
|
|
|
/// Finalize the group: emit the signed [`GroupReceipt`] covering every
|
|
/// pushed object and close the data stream.
|
|
async fn finalize(self: Box<Self>) -> MoqResult<GroupReceipt>;
|
|
}
|
|
|
|
/// Subscriber: requests tracks and receives objects with receipts.
|
|
#[async_trait]
|
|
pub trait Subscriber: MoqEndpoint {
|
|
/// Subscribe to a track. Returns a handle whose receiver yields
|
|
/// `(ObjectHeader, payload, ObjectReceipt)` triples.
|
|
async fn subscribe(
|
|
&mut self,
|
|
namespace: TrackNamespace,
|
|
track_name: TrackName,
|
|
require_receipts: bool,
|
|
) -> MoqResult<SubscriptionHandle>;
|
|
|
|
/// Cancel an active subscription.
|
|
async fn unsubscribe(&mut self, subscribe_id: u64) -> MoqResult<()>;
|
|
}
|
|
|
|
/// Returned to the subscriber. The caller drains `rx` to receive objects.
|
|
pub struct SubscriptionHandle {
|
|
pub subscribe_id: u64,
|
|
pub track_alias: TrackAlias,
|
|
pub rx: mpsc::Receiver<DeliveredObject>,
|
|
}
|
|
|
|
/// One delivered object plus its verifiable receipt.
|
|
#[derive(Clone, Debug)]
|
|
pub struct DeliveredObject {
|
|
pub header: ObjectHeader,
|
|
pub payload: Bytes,
|
|
pub receipt: Option<ObjectReceipt>,
|
|
}
|
|
|
|
/// Relay: forwards objects between publishers and subscribers.
|
|
///
|
|
/// In the JWP-MoQ extension, a relay is a *receipt-preserving* forwarder
|
|
/// by default — it MUST NOT modify object payloads, and it MAY append its
|
|
/// own observation receipt (witnessing forwarding without altering the
|
|
/// original publisher's signed root).
|
|
#[async_trait]
|
|
pub trait Relay: MoqEndpoint {
|
|
/// Register a downstream subscriber. The relay handles fan-out to all
|
|
/// downstream subscribers of an upstream track.
|
|
async fn register_downstream(
|
|
&mut self,
|
|
namespace: TrackNamespace,
|
|
track_name: TrackName,
|
|
downstream: mpsc::Sender<DeliveredObject>,
|
|
) -> MoqResult<u64>;
|
|
|
|
/// Unregister a downstream by handle.
|
|
async fn unregister_downstream(&mut self, handle: u64) -> MoqResult<()>;
|
|
|
|
/// Joules consumed by this relay forwarding objects (used for joule
|
|
/// receipts on egress).
|
|
fn relay_joules_consumed_micro(&self) -> u64;
|
|
}
|