// Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::{ cell::RefCell, fmt::{self, Display, Formatter}, ops::Deref, rc::Rc, time::Instant, }; use neqo_common::{Bytes, Encoder, Header, qdebug, qinfo, qtrace, to_u64}; use neqo_transport::{ Connection, DatagramTracking, Error as TransportError, StreamId, StreamType, recv_stream, send_stream, server::ConnectionRef, streams::SendOrder, }; use crate::{ Error, Http3Client, Http3OrWebTransportStream, Http3ServerEvent, Http3State, Http3StreamInfo, Http3StreamType, Res, SendGroupId, SessionAcceptAction, connection::Http3Connection, connection_server::Http3ServerHandler, features::extended_connect, request_target::RequestTarget, server_events::{Http3ServerEvents, StreamHandler}, }; pub trait ClientSession { /// Whether WebTransport has been enabled at the connection level. #[must_use] fn webtransport_enabled(&self) -> bool; /// Get the negotiated subprotocol for a WebTransport session. /// /// Returns the parsed protocol string from the server's `wt-protocol` response header /// (an [RFC 8941 Item](https://www.rfc-editor.org/rfc/rfc8941.html#name-items)), /// or `None` if the server did not include a `wt-protocol` header (or its value was /// not a valid sf-string). /// /// **Note:** this returns the server's selected protocol without validating it against the /// list of protocols offered by the client. Callers are responsible for checking that the /// returned protocol was among those originally offered. /// /// # Errors /// /// Returns error if the session ID is invalid. fn webtransport_session_protocol(&self, session_id: StreamId) -> Res>; /// Returns the current max size of a datagram that can fit into a packet. /// The value will change over time depending on the encoded size of the /// packet number, ack frames, etc. /// /// # Errors /// /// The function returns `NotAvailable` if datagrams are not enabled. /// /// # Panics /// /// This cannot panic. The max varint length is 8. fn webtransport_max_datagram_size(&self, session_id: StreamId) -> Res; /// Sets the `SendOrder` for a given stream /// /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. fn webtransport_set_sendorder( &mut self, stream_id: StreamId, sendorder: Option, ) -> Res<()>; /// Sets the [`SendGroupId`] for a given WebTransport stream. /// /// # Errors /// /// It may return [`Error::InvalidStreamId`] if a stream does not exist anymore, /// or [`Error::Unavailable`] if the stream is not a WebTransport send stream. fn webtransport_set_sendgroup( &mut self, stream_id: StreamId, sendgroup: SendGroupId, ) -> Res<()>; /// Clears the [`SendGroupId`] for a given WebTransport stream. /// /// # Errors /// /// It may return [`Error::InvalidStreamId`] if a stream does not exist anymore, /// or [`Error::Unavailable`] if the stream is not a WebTransport send stream. fn webtransport_clear_sendgroup(&mut self, stream_id: StreamId) -> Res<()>; /// Sets the `Fairness` for a given stream /// /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. // // TODO: Currently not called in neqo or gecko. It should likely be called at least from gecko. fn webtransport_set_fairness(&mut self, stream_id: StreamId, fairness: bool) -> Res<()>; /// Returns the current `send_stream::Stats` of a `WebTransportSendStream`. /// /// # Errors /// /// `InvalidStreamId` if the stream does not exist. fn webtransport_send_stream_stats(&mut self, stream_id: StreamId) -> Res; /// Returns the current `recv_stream::Stats` of a `WebTransportRecvStream`. /// /// # Errors /// /// `InvalidStreamId` if the stream does not exist. fn webtransport_recv_stream_stats(&mut self, stream_id: StreamId) -> Res; /// Export WebTransport keying material per /// [draft-ietf-webtrans-http3 §4.8](https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-15.html#section-4.8). /// /// Derives keying material scoped to a specific WebTransport session /// by calling the TLS exporter with label `"EXPORTER-WebTransport"` /// and a context struct that binds the session ID, application label, /// and application context together. /// /// # Errors /// /// Returns `Error::InvalidStreamId` if `session_id` does not /// correspond to an active WebTransport session, /// `Error::InvalidInput` if `out` is empty or `label`/`context` /// exceed 255 bytes, or `Error::Transport` on TLS export failure. fn webtransport_export_keying_material( &self, session_id: StreamId, label: &[u8], context: &[u8], out: &mut [u8], ) -> Res<()>; /// Create a `WebTransport` session. /// /// # Errors /// /// If `WebTransport` cannot be created, e.g. the `WebTransport` support is /// not negotiated or the HTTP/3 connection is closed. fn webtransport_create_session( &mut self, now: Instant, target: T, headers: &[Header], ) -> Res; /// Close a `WebTransport` session cleanly. /// /// # Errors /// /// `InvalidStreamId` if the stream does not exist, /// `TransportStreamDoesNotExist` if the transport stream does not exist (this may happen if /// `process_output` has not been called when needed, and HTTP3 layer has not picked up the /// info that the stream has been closed.) `InvalidInput` if an empty buffer has been /// supplied. fn webtransport_close_session( &mut self, session_id: StreamId, error: u32, message: &str, now: Instant, ) -> Res<()>; /// Create a `WebTransport` stream. /// /// # Errors /// /// This may return an error if the particular session does not exist /// or the connection is not in the active state. fn webtransport_create_stream( &mut self, session_id: StreamId, stream_type: StreamType, ) -> Res; /// Send a `WebTransport` datagram. /// /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. /// The function returns `TooMuchData` if the supply buffer is bigger than /// the allowed remote datagram size. fn webtransport_send_datagram>( &mut self, session_id: StreamId, buf: &[u8], id: I, now: Instant, ) -> Res<()>; } impl ClientSession for Http3Client { fn webtransport_enabled(&self) -> bool { self.handler().webtransport_enabled() } fn webtransport_session_protocol(&self, session_id: StreamId) -> Res> { self.handler().webtransport_session_protocol(session_id) } fn webtransport_max_datagram_size(&self, session_id: StreamId) -> Res { let qsid_len = Encoder::varint_len(session_id.as_u64() >> 2); Ok(self .connection() .max_datagram_size()? .saturating_sub(to_u64(qsid_len))) } fn webtransport_set_sendorder( &mut self, stream_id: StreamId, sendorder: Option, ) -> Res<()> { Http3Connection::stream_set_sendorder(self.connection_mut(), stream_id, sendorder) } fn webtransport_set_sendgroup( &mut self, stream_id: StreamId, sendgroup: SendGroupId, ) -> Res<()> { let (conn, handler) = self.connection_and_handler(); // Update the HTTP3 layer first: it owns the group-registration check, so a // rejection here must not leave the transport scheduler already mutated (that // would persistently diverge the layers — sendOrder would route through the // grouped transport path the HTTP3 layer rejected). If the subsequent // transport update fails (e.g. the stream was closed), that is self-healing: // stream teardown clears both layers. handler.stream_set_sendgroup(stream_id, sendgroup)?; conn.stream_sendgroup(stream_id, Some(sendgroup)) .map_err(|_| Error::InvalidStreamId) } fn webtransport_clear_sendgroup(&mut self, stream_id: StreamId) -> Res<()> { let (conn, handler) = self.connection_and_handler(); // See comment in webtransport_set_sendgroup for ordering rationale. handler.stream_clear_sendgroup(stream_id)?; conn.stream_sendgroup(stream_id, None) .map_err(|_| Error::InvalidStreamId) } fn webtransport_set_fairness(&mut self, stream_id: StreamId, fairness: bool) -> Res<()> { Http3Connection::stream_set_fairness(self.connection_mut(), stream_id, fairness) } fn webtransport_send_stream_stats(&mut self, stream_id: StreamId) -> Res { let (conn, handler) = self.connection_and_handler(); handler .send_streams_mut() .get_mut(&stream_id) .ok_or(Error::InvalidStreamId)? .stats(conn) } fn webtransport_recv_stream_stats(&mut self, stream_id: StreamId) -> Res { let (conn, handler) = self.connection_and_handler(); handler .recv_streams_mut() .get_mut(&stream_id) .ok_or(Error::InvalidStreamId)? .stats(conn) } fn webtransport_export_keying_material( &self, session_id: StreamId, label: &[u8], context: &[u8], out: &mut [u8], ) -> Res<()> { self.handler() .validate_extended_connect_session(session_id)?; self.connection() .webtransport_export_keying_material(session_id, label, context, out) } fn webtransport_create_session( &mut self, now: Instant, target: T, headers: &[Header], ) -> Res { let events = Box::new(self.client_events().clone()); let output = { let (conn, handler) = self.connection_and_handler(); handler.webtransport_create_session(conn, events, target, headers) }; if let Err(e) = &output && e.connection_error() { self.close(now, e.code(), ""); } output } fn webtransport_close_session( &mut self, session_id: StreamId, error: u32, message: &str, now: Instant, ) -> Res<()> { let (conn, handler) = self.connection_and_handler(); handler.webtransport_close_session(conn, session_id, error, message, now) } fn webtransport_create_stream( &mut self, session_id: StreamId, stream_type: StreamType, ) -> Res { let send_events = Box::new(self.client_events().clone()); let recv_events = Box::new(self.client_events().clone()); let (conn, handler) = self.connection_and_handler(); handler.webtransport_create_stream_local( conn, session_id, stream_type, send_events, recv_events, ) } fn webtransport_send_datagram>( &mut self, session_id: StreamId, buf: &[u8], id: I, now: Instant, ) -> Res<()> { qtrace!("webtransport_send_datagram session:{session_id:?}"); let (conn, handler) = self.connection_and_handler(); handler.webtransport_send_datagram(session_id, conn, buf, id, now) } } trait ExportKeyingMaterial { /// Export keying material for WebTransport. /// /// # Errors /// When the input is invalid or the underlying connection fails to export. fn webtransport_export_keying_material( &self, session_id: StreamId, label: &[u8], context: &[u8], out: &mut [u8], ) -> Res<()>; } impl ExportKeyingMaterial for Connection { fn webtransport_export_keying_material( &self, session_id: StreamId, label: &[u8], context: &[u8], out: &mut [u8], ) -> Res<()> { // encode_vec(1, …) uses a 1-byte length prefix, so max 255 bytes. if out.is_empty() || label.len() > 255 || context.len() > 255 { return Err(Error::InvalidInput); } let mut wt_context = Encoder::with_capacity( Encoder::varint_len(session_id.as_u64()) + 1 + label.len() + 1 + context.len(), ); wt_context.encode_varint(session_id.as_u64()); wt_context.encode_vec(1, label); wt_context.encode_vec(1, context); self.export_keying_material("EXPORTER-WebTransport", wt_context.as_ref(), out) .map_err(|e| match e { TransportError::InvalidInput => Error::InvalidInput, other => Error::Transport(other), }) } } /// Connection-level `WebTransport` operations shared by the client and server. trait Handler { fn webtransport_create_session( &mut self, conn: &mut Connection, events: Box, target: T, headers: &[Header], ) -> Res; fn webtransport_session_accept( &mut self, conn: &mut Connection, stream_id: StreamId, events: Box, accept_res: &SessionAcceptAction, now: Instant, ) -> Res<()>; fn webtransport_close_session( &mut self, conn: &mut Connection, session_id: StreamId, error: u32, message: &str, now: Instant, ) -> Res<()>; fn webtransport_send_datagram>( &self, session_id: StreamId, conn: &mut Connection, buf: &[u8], id: I, now: Instant, ) -> Res<()>; } impl Handler for Http3Connection { fn webtransport_create_session( &mut self, conn: &mut Connection, events: Box, target: T, headers: &[Header], ) -> Res { qinfo!("[{self}] Create WebTransport"); if !self.webtransport_enabled() { return Err(Error::Unavailable); } self.extended_connect_create_session( conn, events, target, headers, extended_connect::ExtendedConnectType::WebTransport, ) } fn webtransport_session_accept( &mut self, conn: &mut Connection, stream_id: StreamId, events: Box, accept_res: &SessionAcceptAction, now: Instant, ) -> Res<()> { qtrace!("Respond to WebTransport session with accept={accept_res}"); if !self.webtransport_enabled() { return Err(Error::Unavailable); } self.extended_connect_session_accept( conn, stream_id, events, accept_res, extended_connect::ExtendedConnectType::WebTransport, now, ) } fn webtransport_close_session( &mut self, conn: &mut Connection, session_id: StreamId, error: u32, message: &str, now: Instant, ) -> Res<()> { qtrace!("Close WebTransport session {session_id:?}"); self.extended_connect_close_session(conn, session_id, error, message, now) } fn webtransport_send_datagram>( &self, session_id: StreamId, conn: &mut Connection, buf: &[u8], id: I, now: Instant, ) -> Res<()> { self.extended_connect_send_datagram(session_id, conn, buf, id, now) } } /// Server-handler `WebTransport` operations, exposed on [`Http3ServerHandler`]. pub(crate) trait ServerHandler { fn webtransport_session_accept( &mut self, conn: &mut Connection, stream_id: StreamId, accept: &SessionAcceptAction, now: Instant, ) -> Res<()>; fn webtransport_close_session( &mut self, conn: &mut Connection, session_id: StreamId, error: u32, message: &str, now: Instant, ) -> Res<()>; fn webtransport_create_stream( &mut self, conn: &mut Connection, session_id: StreamId, stream_type: StreamType, ) -> Res; fn webtransport_send_datagram>( &mut self, conn: &mut Connection, session_id: StreamId, buf: &[u8], id: I, now: Instant, ) -> Res<()>; } impl ServerHandler for Http3ServerHandler { fn webtransport_session_accept( &mut self, conn: &mut Connection, stream_id: StreamId, accept: &SessionAcceptAction, now: Instant, ) -> Res<()> { self.mark_needs_processing(); let events = Box::new(self.server_events().clone()); self.base_handler_mut() .webtransport_session_accept(conn, stream_id, events, accept, now) } fn webtransport_close_session( &mut self, conn: &mut Connection, session_id: StreamId, error: u32, message: &str, now: Instant, ) -> Res<()> { self.mark_needs_processing(); self.base_handler_mut() .webtransport_close_session(conn, session_id, error, message, now) } fn webtransport_create_stream( &mut self, conn: &mut Connection, session_id: StreamId, stream_type: StreamType, ) -> Res { self.mark_needs_processing(); let send_events = Box::new(self.server_events().clone()); let recv_events = Box::new(self.server_events().clone()); self.base_handler_mut().webtransport_create_stream_local( conn, session_id, stream_type, send_events, recv_events, ) } fn webtransport_send_datagram>( &mut self, conn: &mut Connection, session_id: StreamId, buf: &[u8], id: I, now: Instant, ) -> Res<()> { self.mark_needs_processing(); self.base_handler_mut() .webtransport_send_datagram(session_id, conn, buf, id, now) } } #[derive(Debug, Clone)] pub struct ServerSession { stream_handler: StreamHandler, } impl Display for ServerSession { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "WebTransport session {}", self.stream_handler) } } impl ServerSession { pub(crate) const fn new( conn: ConnectionRef, handler: Rc>, stream_id: StreamId, ) -> Self { Self { stream_handler: StreamHandler { conn, handler, stream_info: Http3StreamInfo::new(stream_id, Http3StreamType::Http), }, } } #[must_use] pub fn state(&self) -> Http3State { self.stream_handler.handler.borrow().state() } /// Respond to a `WebTransport` session request. /// /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. pub fn response(&self, accept: &SessionAcceptAction, now: Instant) -> Res<()> { qdebug!("[{self}] Set a response for a WebTransport session"); self.stream_handler .handler .borrow_mut() .webtransport_session_accept( &mut self.stream_handler.conn.borrow_mut(), self.stream_handler.stream_info.stream_id(), accept, now, ) } /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. /// Also return an error if the stream was closed on the transport layer, /// but that information is not yet consumed on the http/3 layer. pub fn close_session(&self, error: u32, message: &str, now: Instant) -> Res<()> { self.stream_handler .handler .borrow_mut() .webtransport_close_session( &mut self.stream_handler.conn.borrow_mut(), self.stream_handler.stream_info.stream_id(), error, message, now, ) } #[must_use] pub const fn stream_id(&self) -> StreamId { self.stream_handler.stream_id() } /// Create `WebTransport` stream. /// /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. pub fn create_stream(&self, stream_type: StreamType) -> Res { let session_id = self.stream_handler.stream_id(); let id = self .stream_handler .handler .borrow_mut() .webtransport_create_stream( &mut self.stream_handler.conn.borrow_mut(), session_id, stream_type, )?; Ok(Http3OrWebTransportStream::new( self.stream_handler.conn.clone(), Rc::clone(&self.stream_handler.handler), Http3StreamInfo::new(id, Http3StreamType::WebTransport(session_id)), )) } /// Send `WebTransport` datagram. /// /// # Errors /// /// It may return `InvalidStreamId` if a stream does not exist anymore. /// The function returns `TooMuchData` if the supply buffer is bigger than /// the allowed remote datagram size. pub fn send_datagram>( &self, buf: &[u8], id: I, now: Instant, ) -> Res<()> { let session_id = self.stream_handler.stream_id(); self.stream_handler .handler .borrow_mut() .webtransport_send_datagram( &mut self.stream_handler.conn.borrow_mut(), session_id, buf, id, now, ) } // TODO: Currently not called in neqo or gecko. It should likely be called at least from gecko. #[must_use] pub fn remote_datagram_size(&self) -> u64 { self.stream_handler.conn.borrow().remote_datagram_size() } /// Returns the current max size of a datagram that can fit into a packet. /// The value will change over time depending on the encoded size of the /// packet number, ack frames, etc. /// /// # Errors /// /// The function returns `NotAvailable` if datagrams are not enabled. /// /// # Panics /// /// This cannot panic. The max varint length is 8. pub fn max_datagram_size(&self) -> Res { let qsid_len = Encoder::varint_len(self.stream_handler.stream_id().as_u64() >> 2); Ok(self .stream_handler .conn .borrow() .max_datagram_size()? .saturating_sub(to_u64(qsid_len))) } /// Export keying material for this WebTransport session /// (draft-ietf-webtrans-http3 §4.8). /// /// # Errors /// /// Returns `Error::InvalidStreamId` if the session is no longer active, /// `Error::InvalidInput` if `out` is empty or `label`/`context` /// exceed 255 bytes, or `Error::Transport` if the connection is not ready /// or the TLS export fails. pub fn export_keying_material(&self, label: &[u8], context: &[u8], out: &mut [u8]) -> Res<()> { let session_id = self.stream_handler.stream_id(); self.stream_handler .handler .borrow() .validate_extended_connect_session(session_id)?; self.stream_handler .conn .borrow() .webtransport_export_keying_material(session_id, label, context, out) } } impl Deref for ServerSession { type Target = StreamHandler; fn deref(&self) -> &Self::Target { &self.stream_handler } } #[derive(Debug, Clone)] pub enum ServerEvent { NewSession { session: ServerSession, headers: Vec
, }, SessionClosed { session: ServerSession, reason: extended_connect::session::CloseReason, headers: Option>, }, NewStream(Http3OrWebTransportStream), Datagram { session: ServerSession, datagram: Bytes, }, } pub(crate) trait ServerEvents { fn webtransport_new_session(&self, session: ServerSession, headers: Vec
); fn webtransport_session_closed( &self, session: ServerSession, reason: extended_connect::session::CloseReason, headers: Option>, ); fn webtransport_new_stream(&self, stream: Http3OrWebTransportStream); fn webtransport_datagram(&self, session: ServerSession, datagram: Bytes); } impl ServerEvents for Http3ServerEvents { fn webtransport_new_session(&self, session: ServerSession, headers: Vec
) { self.insert(Http3ServerEvent::WebTransport(ServerEvent::NewSession { session, headers, })); } fn webtransport_session_closed( &self, session: ServerSession, reason: extended_connect::session::CloseReason, headers: Option>, ) { self.insert(Http3ServerEvent::WebTransport(ServerEvent::SessionClosed { session, reason, headers, })); } fn webtransport_new_stream(&self, stream: Http3OrWebTransportStream) { self.insert(Http3ServerEvent::WebTransport(ServerEvent::NewStream( stream, ))); } fn webtransport_datagram(&self, session: ServerSession, datagram: Bytes) { self.insert(Http3ServerEvent::WebTransport(ServerEvent::Datagram { session, datagram, })); } }