wtransport

repository·master·Indexed 20 days ago

https://github.com/biagiofesta/wtransport

A pure-Rust, async-friendly implementation of the WebTransport (over HTTP3) protocol. It enables low-latency, bidirectional, and multiplexed communication between clients and servers. The library provides tools for implementing WebTransport servers and clients in Rust, as well as low-level protocol primitives including HTTP3 frame handling, capsule parsing, and datagram serialization.

Tokens
22.1K
Snippets
68
Records
100
Agent score
71%

What's inside wtransport

  1. Overview of WTransport

    master

    WTransport is a pure-Rust, async-friendly implementation of the WebTransport protocol. It enables low-latency, bidirectional communication between clients and servers over the web, supporting features like multiplexing multiple streams over a single connection.

    Note: WebTransport is currently a draft specification and not yet standardized. The WTransport library is functional but not considered completely production-ready and may undergo changes as the specification evolves.

  2. Run the Full Echo Example

    master

    The examples/full.rs file provides a complete minimal server example that acts as an echo server and includes an integrated HTTP server.

    To run it:

    1. Clone the repository.
    2. Use cargo run --example full.
    3. Navigate to http://127.0.0.1:8080 in a supported web browser.
    git clone https://github.com/BiagioFesta/wtransport.git
    cd wtransport/
    cargo run --example full
  3. How stream upgrades work in wtransport

    master

    Streams in wtransport follow a lifecycle of upgrades, moving from QUIC to HTTP/3 and potentially to WebTransport.

    • Remote Bidirectional Streams: Start as StreamBiRemoteQuic (via accept_bi()), can be upgraded to StreamBiRemoteH3 via .upgrade(), and finally to StreamBiRemoteWT via .upgrade(session_id) (only if the first frame received is a FrameKind::WebTransport).
    • Local Bidirectional Streams: Start as StreamBiLocalQuic (via open_bi()), upgrade to StreamBiLocalH3 via .upgrade(), and then to StreamBiLocalWT via .upgrade(session_id, writer). Note that for local streams, .upgrade() will panic if any I/O has already been performed on the stream.
    • Unidirectional Streams: Can be upgraded from QUIC to HTTP/3 and then to WebTransport depending on whether they are local or remote.
    // Example: Upgrading a remote bidirectional stream
    let mut stream = StreamBiRemoteQuic::accept_bi();
    let mut h3_stream = stream.upgrade();
    // ... perform H3 operations ...
    let wt_stream = h3_stream.upgrade(session_id);
  4. Understand WebTransport stream types

    master

    The wtransport-proto crate defines several stream types that categorize how streams behave and how they were initialized. These types are used to distinguish between bidirectional and unidirectional streams, and whether they were initiated locally or by a remote peer.

    Stream Directionality

    • Bi: Bidirectional stream.
    • Uni: Unidirectional stream.

    Initialization Origin

    • Local: Stream initiated by the local side.
    • Remote: Stream initiated by the remote side.

    Combined Stream Types

    These types combine directionality and origin:

    • BiLocal(Bi, Local): A bidirectional stream initiated locally.
    • BiRemote(Bi, Remote): A bidirectional stream initiated by the remote peer.
    • UniLocal(Uni, Local): A unidirectional stream initiated locally.
    • UniRemote(Uni, Remote): A unidirectional stream initiated by the remote peer.
  5. Manage WebTransport connections with `Connection`

    master

    The Connection struct is the primary interface for managing a WebTransport session. It allows you to manage data exchange through two main mechanisms: Streams (ordered byte-streams) and Datagrams (unreliable, message-based data).

    Connection can be cloned to obtain multiple handles to the same underlying connection.

    use wtransport::Connection;
    
    // Connection can be cloned to share access
    let connection_handle = connection.clone();
  6. How WebTransport streams and datagrams work

    master

    WebTransport provides two distinct communication channels for data exchange:

    Streams

    Streams are used for ordered and reliable data transfer. They are flow-controlled and secure. Streams can be either uni-directional or bi-directional. Because they are independent, the order or reliability of one stream does not affect others within the same session.

    Datagrams

    Datagrams are used for unordered, lightweight communication. They prioritize speed over reliability, meaning there is no guarantee of delivery, no sequencing, and no flow control. Like streams, they are secure and independent.

  7. How ServerConfig and ClientConfig builders work

    master

    Both ServerConfig and ClientConfig use a state-based builder pattern to ensure required configuration steps are completed at compile time.

    ServerConfig States

    1. WantsBindAddress: Requires a binding method (e.g., with_bind_default).
    2. WantsIdentity: Requires TLS configuration (e.g., with_identity).
    3. WantsTransportConfigServer: Allows optional transport tuning (e.g., max_idle_timeout) before calling .build().

    ClientConfig States

    1. WantsBindAddress: Requires a binding method.
    2. WantsRootStore: Requires TLS validation configuration (e.g., with_native_certs).
    3. WantsTransportConfigClient: Allows optional transport tuning before calling .build().
  8. Identify HTTP3 frame kinds

    master

    The FrameKind enum represents the type of HTTP3 frame. Supported kinds include:

    • Data: A standard data frame.
    • Headers: A frame containing headers.
    • Settings: A frame containing connection settings.
    • WebTransport: A frame specifically for WebTransport, which includes a SessionId instead of a standard payload.
    • Exercise(VarInt): A frame used for testing/exercises, identified by a specific ID pattern.
  9. Configure the WebTransport Client

    master

    Use ClientConfig::builder() to create a client configuration. The builder follows a state-based pattern requiring a binding address and a TLS root store configuration.

    Configuration Workflow

    1. Binding Address: Specify the local binding using with_bind_default(), with_bind_config(ip_bind_config), with_bind_address(addr), with_bind_address_v6(addr, dual_stack), or with_bind_socket(socket).
    2. Root Store (TLS): Configure how to validate servers using with_native_certs() (default), with_server_certificate_hashes(...), with_no_cert_validation() (insecure, requires dangerous-configuration feature), with_custom_tls(...), with_custom_transport(...), or with_custom_tls_and_transport(...).
    3. Transport Settings: Optionally tune max_idle_timeout, keep_alive_interval, or dns_resolver before calling .build().
    use wtransport::ClientConfig;
    
    let client_config = ClientConfig::builder()
        .with_bind_default()
        .with_native_certs()
        .max_idle_timeout(Some(std::time::Duration::from_secs(30)))
        .unwrap()
        .keep_alive_interval(Some(std::time::Duration::from_secs(3)))
        .build();
  10. Open new WebTransport streams

    master

    You can initiate new streams on a Connection. There are two types:

    1. Unidirectional Streams: Data flows in one direction (from initiator to peer). Use open_uni().
    2. Bidirectional Streams: Data flows in both directions. Use open_bi().

    Note on Asynchronous Behavior: Both open_uni() and open_bi() involve two await points. The first await is for initial resource allocation/flow control. The second await occurs when using the returned OpeningUniStream or OpeningBiStream to initialize the WebTransport stream. Cancelling the second await may result in the stream being closed during initialization.

    use wtransport::Connection;
    
    // Open a bi-directional stream
    // Note the double await: first for flow control, second for stream initialization
    let (mut send_stream, mut recv_stream) = connection.open_bi().await?.await?;
    
    // Send data on the stream
    send_stream.write_all(b"Hello, wtransport!").await?;
    
    // Open an uni-directional stream
    let mut send_stream = connection.open_uni().await?.await?;
    send_stream.write_all(b"Hello, wtransport!").await?;
  11. Send and receive WebTransport datagrams

    master

    Datagrams are message-based and do not guarantee order or delivery, making them suitable for low-latency, unreliable data exchange.

    • send_datagram(payload): Sends a datagram. The payload must implement AsRef<[u8]>.
    • receive_datagram(): Asynchronously waits for and returns the next available Datagram.
    use wtransport::Connection;
    
    // Send datagram message
    connection.send_datagram(b"Hello, wtransport!")?;
    
    // Receive a datagram message
    let message = connection.receive_datagram().await?;
  12. Generate a self-signed Identity using SelfSignedIdentityBuilder

    master

    Use SelfSignedIdentityBuilder to create a new Identity containing a self-signed X.509v3 certificate and an ECDSA P-256 private key. The builder uses a type-state pattern to ensure all required fields (Subject Alternative Names and validity period) are provided before calling .build().

    Builder Workflow

    1. Initialize: Use SelfSignedIdentityBuilder::new() or Identity::self_signed_builder().
    2. Set SANs: Call .subject_alt_names(&[...]) with hostnames or IP addresses.
    3. Set Validity:
      • Use .from_now_utc() to set the start time to the current UTC time.
      • Use .validity_days(u32) to set the duration from the start time.
      • Or manually set .not_before(OffsetDateTime) and .not_after(OffsetDateTime).
    4. Build: Call .build() to get a Result<Identity, InvalidSan>.
    use wtransport::tls::self_signed::SelfSignedIdentityBuilder;
    
    let identity = SelfSignedIdentityBuilder::new()
        .subject_alt_names(&["localhost", "127.0.0.1"])
        .from_now_utc()
        .validity_days(7)
        .build()
        .unwrap();