Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dart/moq_ffi/lib/src/moq.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3832,7 +3832,7 @@ class FfiConverterMoqVideoFormat {
}
}

enum MoqTransport { quic, iroh, webSocket, tcp, unix }
enum MoqTransport { quic, iroh, webSocket, tcp, unix, webTransport }

class FfiConverterMoqTransport {
static LiftRetVal<MoqTransport> read(Uint8List buf) {
Expand All @@ -3848,6 +3848,8 @@ class FfiConverterMoqTransport {
return LiftRetVal(MoqTransport.tcp, 4);
case 5:
return LiftRetVal(MoqTransport.unix, 4);
case 6:
return LiftRetVal(MoqTransport.webTransport, 4);
default:
throw UniffiInternalError(
UniffiInternalError.unexpectedEnumCase,
Expand Down
3 changes: 2 additions & 1 deletion doc/concept/transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ connection (`wss://`) and keep whichever wins. A small multiplexer,
[qmux](/draft/qmux-websocket), carries MoQ streams over the socket. It works
everywhere but cannot escape TCP head-of-line blocking, so priority and resets
only help once bytes leave the TCP queue. The fallback is automatic in every
client; the relay enables it with `[web.https]`.
client; the relay enables it with `[web.https]`. A native Rust client that lands on
WebSocket keeps dialing QUIC and moves the session onto it if that dial completes.

## Raw QUIC (native)

Expand Down
4 changes: 3 additions & 1 deletion doc/lib/rs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ URLs may be `https://` (WebTransport, with raw QUIC preferred for native),
`moql://`/`moqt://` (raw QUIC), or `iroh://`. A `?jwt=` query carries the
token. `http://` is for a relay on localhost only: it fetches the certificate
fingerprint unauthenticated before upgrading, so never send a token over it. Connections race
QUIC against WebSocket and remember which won.
QUIC against WebSocket and remember which won. When WebSocket wins, a `Connection` keeps
dialing QUIC and moves onto it once it lands, handing live tracks over at a group boundary
and draining the WebSocket session; `Connection::transport()` reports which is live.

The `Default::default()` above is the QUIC transport section, and the same value
serves a dial and a listener:
Expand Down
2 changes: 2 additions & 0 deletions go/wrapper/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const (
TransportTCP = ffi.MoqTransportTcp
// TransportUnix is a session that arrived over a Unix domain socket.
TransportUnix = ffi.MoqTransportUnix
// TransportWebTransport is a session that arrived over WebTransport (HTTP/3).
TransportWebTransport = ffi.MoqTransportWebTransport
)

// Request is an incoming session that can be accepted (Accept) or rejected (Reject).
Expand Down
6 changes: 3 additions & 3 deletions quest/m1/transport-upgrade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ requires it.
Shared decisions:

- The race returns the winner plus the still-pending QUIC dial when WebSocket
wins. The QUIC handshake timeout bounds that dial; no extra deadline.
wins. The deadline that already bounds the race attempt (the connect
timeout in Rust) bounds that dial too; no extra deadline.
- On a successful upgrade the "WebSocket won" memo (`WEBSOCKET_WON` in
`moq-tokio`, `websocketWon` in `js/net`) forgets the URL: QUIC works on this
network, so the head start comes back. Otherwise a network where WebSocket
narrowly beats QUIC would open two connections on every reconnect.
- The old session gets `Goaway::same()` with the configured handover cap before
- The old session gets `Goaway::new()` with the configured handover cap before
it enters draining. The relay refuses new requests on it from then on; the
splice ends its subscriptions at the boundary.
- One-shot `connect()` returns one session and never upgrades; every
Expand All @@ -57,7 +58,6 @@ Shared decisions:

## Quests

- [Rust](/quest/m1/transport-upgrade/rust.md) - moq-tokio keeps the QUIC dial after WebSocket wins and migrates through the existing Draining path
- [JavaScript](/quest/m1/transport-upgrade/js.md) - js/net keeps the WebTransport dial after WebSocket wins and migrates through the client-goaway handover

## Related
Expand Down
49 changes: 0 additions & 49 deletions quest/m1/transport-upgrade/rust.md

This file was deleted.

12 changes: 6 additions & 6 deletions rs/moq-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,9 @@ async fn serve_client(
}

/// Whether ordinary clients may use this transport on the shared LAN server.
fn is_public_transport(transport: moq_tokio::server::Transport, public_quic: bool) -> bool {
fn is_public_transport(transport: moq_tokio::Transport, public_quic: bool) -> bool {
match transport {
moq_tokio::server::Transport::Tcp | moq_tokio::server::Transport::Unix => true,
moq_tokio::Transport::Tcp | moq_tokio::Transport::Unix => true,
_ => public_quic,
}
}
Expand Down Expand Up @@ -949,9 +949,9 @@ mod tests {

#[test]
fn explicit_stream_listeners_are_public_without_exposing_mesh_quic() {
assert!(is_public_transport(moq_tokio::server::Transport::Tcp, false));
assert!(is_public_transport(moq_tokio::server::Transport::Unix, false));
assert!(!is_public_transport(moq_tokio::server::Transport::Quic, false));
assert!(is_public_transport(moq_tokio::server::Transport::Quic, true));
assert!(is_public_transport(moq_tokio::Transport::Tcp, false));
assert!(is_public_transport(moq_tokio::Transport::Unix, false));
assert!(!is_public_transport(moq_tokio::Transport::Quic, false));
assert!(is_public_transport(moq_tokio::Transport::Quic, true));
}
}
25 changes: 16 additions & 9 deletions rs/moq-ffi/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ struct RequestState {
/// The network transport carrying an incoming session.
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MoqTransport {
/// QUIC, either directly or through WebTransport over HTTP/3.
/// Raw QUIC, negotiating a MoQ ALPN directly.
Quic,
/// An Iroh QUIC connection.
Iroh,
Expand All @@ -212,18 +212,21 @@ pub enum MoqTransport {
Tcp,
/// A Unix domain socket using qmux framing.
Unix,
/// WebTransport over HTTP/3 on QUIC.
WebTransport,
Comment on lines +215 to +216

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Export WebTransport from the Go wrapper

When a WebTransport request reaches the FFI server, MoqRequest::transport() can now return this sixth value, but go/wrapper/server.go still exports constants only through TransportUnix. The ergonomic Go package therefore has no moq.TransportWebTransport symbol, forcing callers to import the raw FFI package or leaving transport handling unable to name the returned value. Add the documented wrapper constant alongside this enum addition. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L94-L96

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. The other bindings alias the generated enum, but the Go wrapper re-exports each transport by hand and stopped at TransportUnix. Added moq.TransportWebTransport in 6bd6ee8.

(Written by Grok 4.7)

}

impl TryFrom<moq_tokio::server::Transport> for MoqTransport {
impl TryFrom<moq_tokio::Transport> for MoqTransport {
type Error = MoqError;

fn try_from(value: moq_tokio::server::Transport) -> Result<Self, Self::Error> {
fn try_from(value: moq_tokio::Transport) -> Result<Self, Self::Error> {
Ok(match value {
moq_tokio::server::Transport::Quic => Self::Quic,
moq_tokio::server::Transport::Iroh => Self::Iroh,
moq_tokio::server::Transport::WebSocket => Self::WebSocket,
moq_tokio::server::Transport::Tcp => Self::Tcp,
moq_tokio::server::Transport::Unix => Self::Unix,
moq_tokio::Transport::Quic => Self::Quic,
moq_tokio::Transport::Iroh => Self::Iroh,
moq_tokio::Transport::WebSocket => Self::WebSocket,
moq_tokio::Transport::Tcp => Self::Tcp,
moq_tokio::Transport::Unix => Self::Unix,
moq_tokio::Transport::WebTransport => Self::WebTransport,
_ => return Err(MoqError::Unsupported),
})
}
Expand All @@ -232,7 +235,7 @@ impl TryFrom<moq_tokio::server::Transport> for MoqTransport {
#[cfg(test)]
mod transport_tests {
use super::MoqTransport;
use moq_tokio::server::Transport;
use moq_tokio::Transport;

#[test]
fn converts_supported_transports() {
Expand All @@ -244,6 +247,10 @@ mod transport_tests {
);
assert_eq!(MoqTransport::try_from(Transport::Tcp).unwrap(), MoqTransport::Tcp);
assert_eq!(MoqTransport::try_from(Transport::Unix).unwrap(), MoqTransport::Unix);
assert_eq!(
MoqTransport::try_from(Transport::WebTransport).unwrap(),
MoqTransport::WebTransport
);
}
}

Expand Down
11 changes: 6 additions & 5 deletions rs/moq-relay/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,11 +488,12 @@ impl Admission {
/// transport knows, nothing parsed on the server's behalf.
pub fn request_for(auth: &Auth, request: &moq_tokio::server::Request) -> Request {
let transport = match request.transport() {
moq_tokio::server::Transport::Quic => moq_auth::Transport::Quic,
moq_tokio::server::Transport::Iroh => moq_auth::Transport::Iroh,
moq_tokio::server::Transport::WebSocket => moq_auth::Transport::WebSocket,
moq_tokio::server::Transport::Tcp => moq_auth::Transport::Tcp,
moq_tokio::server::Transport::Unix => moq_auth::Transport::Unix,
// The auth contract names QUIC either way; WebTransport is QUIC underneath.
moq_tokio::Transport::Quic | moq_tokio::Transport::WebTransport => moq_auth::Transport::Quic,
moq_tokio::Transport::Iroh => moq_auth::Transport::Iroh,
moq_tokio::Transport::WebSocket => moq_auth::Transport::WebSocket,
moq_tokio::Transport::Tcp => moq_auth::Transport::Tcp,
moq_tokio::Transport::Unix => moq_auth::Transport::Unix,
// A transport this build does not know is still a session on the wire; the
// server sees the same facts either way.
other => unreachable!("unknown transport {other}"),
Expand Down
2 changes: 1 addition & 1 deletion rs/moq-relay/src/uring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ async fn serve_connection(
});
let node_connection = peer_hop.map(|origin| serve.cluster.nodes.connect_inbound(id, origin));

tracing::info!(id, version = %session.version(), transport = %moq_tokio::server::Transport::Quic, "negotiated");
tracing::info!(id, version = %session.version(), transport = %moq_tokio::Transport::Quic, "negotiated");

// The session handle is Send + Sync however its transport is driven, so
// its lifecycle (credential expiry, GOAWAY drain) lives with the timers
Expand Down
Loading
Loading