diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/neqo-http3/src/features | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/neqo-http3/src/features')
10 files changed, 3552 insertions, 0 deletions
diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs b/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs new file mode 100644 index 0000000000..6be92dabba --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/mod.rs @@ -0,0 +1,114 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +pub(crate) mod webtransport_session; +pub(crate) mod webtransport_streams; + +use crate::client_events::Http3ClientEvents; +use crate::features::NegotiationState; +use crate::settings::{HSettingType, HSettings}; +use crate::{CloseType, Http3StreamInfo, Http3StreamType}; +use neqo_common::Header; +use neqo_transport::{AppError, StreamId}; +use std::fmt::Debug; +pub(crate) use webtransport_session::WebTransportSession; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SessionCloseReason { + Error(AppError), + Status(u16), + Clean { error: u32, message: String }, +} + +impl From<CloseType> for SessionCloseReason { + fn from(close_type: CloseType) -> SessionCloseReason { + match close_type { + CloseType::ResetApp(e) | CloseType::ResetRemote(e) | CloseType::LocalError(e) => { + SessionCloseReason::Error(e) + } + CloseType::Done => SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + } + } +} + +pub(crate) trait ExtendedConnectEvents: Debug { + fn session_start( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + status: u16, + headers: Vec<Header>, + ); + fn session_end( + &self, + connect_type: ExtendedConnectType, + stream_id: StreamId, + reason: SessionCloseReason, + headers: Option<Vec<Header>>, + ); + fn extended_connect_new_stream(&self, stream_info: Http3StreamInfo); + fn new_datagram(&self, session_id: StreamId, datagram: Vec<u8>); +} + +#[derive(Debug, PartialEq, Copy, Clone, Eq)] +pub(crate) enum ExtendedConnectType { + WebTransport, +} + +impl ExtendedConnectType { + #[must_use] + #[allow(clippy::unused_self)] // This will change when we have more features using ExtendedConnectType. + pub fn string(&self) -> &str { + "webtransport" + } + + #[allow(clippy::unused_self)] // This will change when we have more features using ExtendedConnectType. + #[must_use] + pub fn get_stream_type(self, session_id: StreamId) -> Http3StreamType { + Http3StreamType::WebTransport(session_id) + } +} + +impl From<ExtendedConnectType> for HSettingType { + fn from(_type: ExtendedConnectType) -> Self { + // This will change when we have more features using ExtendedConnectType. + HSettingType::EnableWebTransport + } +} + +#[derive(Debug)] +pub(crate) struct ExtendedConnectFeature { + feature_negotiation: NegotiationState, +} + +impl ExtendedConnectFeature { + #[must_use] + pub fn new(connect_type: ExtendedConnectType, enable: bool) -> Self { + Self { + feature_negotiation: NegotiationState::new(enable, HSettingType::from(connect_type)), + } + } + + pub fn set_listener(&mut self, new_listener: Http3ClientEvents) { + self.feature_negotiation.set_listener(new_listener); + } + + pub fn handle_settings(&mut self, settings: &HSettings) { + self.feature_negotiation.handle_settings(settings); + } + + #[must_use] + pub fn enabled(&self) -> bool { + self.feature_negotiation.enabled() + } +} +#[cfg(test)] +mod tests; diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs new file mode 100644 index 0000000000..a21f63dbf8 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/mod.rs @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod webtransport; diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs new file mode 100644 index 0000000000..1b9511b255 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/datagrams.rs @@ -0,0 +1,142 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::features::extended_connect::tests::webtransport::{ + wt_default_parameters, WtTest, DATAGRAM_SIZE, +}; +use crate::{Error, Http3Parameters, WebTransportRequest}; +use neqo_common::Encoder; +use neqo_transport::Error as TransportError; +use std::convert::TryFrom; + +const DGRAM: &[u8] = &[0, 100]; + +#[test] +fn no_datagrams() { + let mut wt = WtTest::new_with_params( + Http3Parameters::default().webtransport(true), + Http3Parameters::default().webtransport(true), + ); + let mut wt_session = wt.create_wt_session(); + + assert_eq!( + wt_session.max_datagram_size(), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + + assert_eq!( + wt_session.send_datagram(DGRAM, None), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + assert_eq!( + wt.send_datagram(wt_session.stream_id(), DGRAM), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + + wt.exchange_packets(); + wt.check_no_datagram_received_client(); + wt.check_no_datagram_received_server(); +} + +fn do_datagram_test(wt: &mut WtTest, wt_session: &mut WebTransportRequest) { + assert_eq!( + wt_session.max_datagram_size(), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + + assert_eq!(wt_session.send_datagram(DGRAM, None), Ok(())); + assert_eq!(wt.send_datagram(wt_session.stream_id(), DGRAM), Ok(())); + + wt.exchange_packets(); + wt.check_datagram_received_client(wt_session.stream_id(), DGRAM); + wt.check_datagram_received_server(wt_session, DGRAM); +} + +#[test] +fn datagrams() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + do_datagram_test(&mut wt, &mut wt_session); +} + +#[test] +fn datagrams_server_only() { + let mut wt = WtTest::new_with_params( + Http3Parameters::default().webtransport(true), + wt_default_parameters(), + ); + let mut wt_session = wt.create_wt_session(); + + assert_eq!( + wt_session.max_datagram_size(), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + + assert_eq!( + wt_session.send_datagram(DGRAM, None), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + assert_eq!(wt.send_datagram(wt_session.stream_id(), DGRAM), Ok(())); + + wt.exchange_packets(); + wt.check_datagram_received_server(&wt_session, DGRAM); + wt.check_no_datagram_received_client(); +} + +#[test] +fn datagrams_client_only() { + let mut wt = WtTest::new_with_params( + wt_default_parameters(), + Http3Parameters::default().webtransport(true), + ); + let mut wt_session = wt.create_wt_session(); + + assert_eq!( + wt_session.max_datagram_size(), + Ok(DATAGRAM_SIZE + - u64::try_from(Encoder::varint_len(wt_session.stream_id().as_u64())).unwrap()) + ); + assert_eq!( + wt.max_datagram_size(wt_session.stream_id()), + Err(Error::TransportError(TransportError::NotAvailable)) + ); + + assert_eq!(wt_session.send_datagram(DGRAM, None), Ok(())); + assert_eq!( + wt.send_datagram(wt_session.stream_id(), DGRAM), + Err(Error::TransportError(TransportError::TooMuchData)) + ); + + wt.exchange_packets(); + wt.check_datagram_received_client(wt_session.stream_id(), DGRAM); + wt.check_no_datagram_received_server(); +} + +#[test] +fn datagrams_multiple_session() { + let mut wt = WtTest::new(); + + let mut wt_session1 = wt.create_wt_session(); + do_datagram_test(&mut wt, &mut wt_session1); + + let mut wt_session_2 = wt.create_wt_session(); + do_datagram_test(&mut wt, &mut wt_session_2); +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs new file mode 100644 index 0000000000..58ebfd8a6c --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/mod.rs @@ -0,0 +1,648 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod datagrams; +mod negotiation; +mod sessions; +mod streams; +use neqo_common::event::Provider; + +use crate::{ + features::extended_connect::SessionCloseReason, Error, Header, Http3Client, Http3ClientEvent, + Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent, Http3State, + WebTransportEvent, WebTransportRequest, WebTransportServerEvent, + WebTransportSessionAcceptAction, +}; +use neqo_crypto::AuthenticationStatus; +use neqo_transport::{ConnectionParameters, StreamId, StreamType}; +use std::cell::RefCell; +use std::rc::Rc; + +use test_fixture::{ + addr, anti_replay, fixture_init, now, CountingConnectionIdGenerator, DEFAULT_ALPN_H3, + DEFAULT_KEYS, DEFAULT_SERVER_NAME, +}; + +const DATAGRAM_SIZE: u64 = 1200; + +pub fn wt_default_parameters() -> Http3Parameters { + Http3Parameters::default() + .webtransport(true) + .connection_parameters(ConnectionParameters::default().datagram_size(DATAGRAM_SIZE)) +} + +pub fn default_http3_client(client_params: Http3Parameters) -> Http3Client { + fixture_init(); + Http3Client::new( + DEFAULT_SERVER_NAME, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + client_params, + now(), + ) + .expect("create a default client") +} + +pub fn default_http3_server(server_params: Http3Parameters) -> Http3Server { + Http3Server::new( + now(), + DEFAULT_KEYS, + DEFAULT_ALPN_H3, + anti_replay(), + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + server_params, + None, + ) + .expect("create a server") +} + +fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) { + let mut out = None; + loop { + out = client.process(out, now()).dgram(); + out = server.process(out, now()).dgram(); + if out.is_none() { + break; + } + } +} + +// Perform only Quic transport handshake. +fn connect_with(client: &mut Http3Client, server: &mut Http3Server) { + assert_eq!(client.state(), Http3State::Initializing); + let out = client.process(None, now()); + assert_eq!(client.state(), Http3State::Initializing); + + let out = server.process(out.dgram(), now()); + let out = client.process(out.dgram(), now()); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_none()); + + let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded); + assert!(client.events().any(authentication_needed)); + client.authenticated(AuthenticationStatus::Ok, now()); + + let out = client.process(out.dgram(), now()); + let connected = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::Connected)); + assert!(client.events().any(connected)); + + assert_eq!(client.state(), Http3State::Connected); + + // Exchange H3 setttings + let out = server.process(out.dgram(), now()); + let out = client.process(out.dgram(), now()); + let out = server.process(out.dgram(), now()); + let out = client.process(out.dgram(), now()); + std::mem::drop(server.process(out.dgram(), now())); +} + +fn connect( + client_params: Http3Parameters, + server_params: Http3Parameters, +) -> (Http3Client, Http3Server) { + let mut client = default_http3_client(client_params); + let mut server = default_http3_server(server_params); + connect_with(&mut client, &mut server); + (client, server) +} + +struct WtTest { + client: Http3Client, + server: Http3Server, +} + +impl WtTest { + pub fn new() -> Self { + let (client, server) = connect(wt_default_parameters(), wt_default_parameters()); + Self { client, server } + } + + pub fn new_with_params(client_params: Http3Parameters, server_params: Http3Parameters) -> Self { + let (client, server) = connect(client_params, server_params); + Self { client, server } + } + + pub fn new_with(mut client: Http3Client, mut server: Http3Server) -> Self { + connect_with(&mut client, &mut server); + Self { client, server } + } + fn negotiate_wt_session( + &mut self, + accept: &WebTransportSessionAcceptAction, + ) -> (StreamId, Option<WebTransportRequest>) { + let wt_session_id = self + .client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + self.exchange_packets(); + + let mut wt_server_session = None; + while let Some(event) = self.server.next_event() { + match event { + Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + mut session, + headers, + }) => { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + session.response(accept).unwrap(); + wt_server_session = Some(session); + } + Http3ServerEvent::Data { .. } => { + panic!("There should not be any data events!"); + } + _ => {} + } + } + + self.exchange_packets(); + (wt_session_id, wt_server_session) + } + + fn create_wt_session(&mut self) -> WebTransportRequest { + let (wt_session_id, wt_server_session) = + self.negotiate_wt_session(&WebTransportSessionAcceptAction::Accept); + let wt_session_negotiated_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Session{ + stream_id, + status, + headers, + }) if ( + stream_id == wt_session_id && + status == 200 && + headers.contains(&Header::new(":status", "200")) + ) + ) + }; + assert!(self.client.events().any(wt_session_negotiated_event)); + + let wt_server_session = wt_server_session.unwrap(); + assert_eq!(wt_session_id, wt_server_session.stream_id()); + wt_server_session + } + + fn exchange_packets(&mut self) { + let mut out = None; + loop { + out = self.client.process(out, now()).dgram(); + out = self.server.process(out, now()).dgram(); + if out.is_none() { + break; + } + } + } + + pub fn cancel_session_client(&mut self, wt_stream_id: StreamId) { + self.client + .cancel_fetch(wt_stream_id, Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn session_closed_client( + e: &Http3ClientEvent, + id: StreamId, + expected_reason: &SessionCloseReason, + expected_headers: &Option<Vec<Header>>, + ) -> bool { + if let Http3ClientEvent::WebTransport(WebTransportEvent::SessionClosed { + stream_id, + reason, + headers, + }) = e + { + *stream_id == id && reason == expected_reason && headers == expected_headers + } else { + false + } + } + + pub fn check_session_closed_event_client( + &mut self, + wt_session_id: StreamId, + expected_reason: &SessionCloseReason, + expected_headers: &Option<Vec<Header>>, + ) { + let mut event_found = false; + + while let Some(event) = self.client.next_event() { + event_found = WtTest::session_closed_client( + &event, + wt_session_id, + expected_reason, + expected_headers, + ); + if event_found { + break; + } + } + assert!(event_found); + } + + pub fn cancel_session_server(&mut self, wt_session: &mut WebTransportRequest) { + wt_session.cancel_fetch(Error::HttpNoError.code()).unwrap(); + self.exchange_packets(); + } + + fn session_closed_server( + e: &Http3ServerEvent, + id: StreamId, + expected_reason: &SessionCloseReason, + ) -> bool { + if let Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed { + session, + reason, + headers, + }) = e + { + session.stream_id() == id && reason == expected_reason && headers.is_none() + } else { + false + } + } + + pub fn check_session_closed_event_server( + &mut self, + wt_session: &mut WebTransportRequest, + expected_reeason: &SessionCloseReason, + ) { + let event = self.server.next_event().unwrap(); + assert!(WtTest::session_closed_server( + &event, + wt_session.stream_id(), + expected_reeason + )); + } + + fn create_wt_stream_client( + &mut self, + wt_session_id: StreamId, + stream_type: StreamType, + ) -> StreamId { + self.client + .webtransport_create_stream(wt_session_id, stream_type) + .unwrap() + } + + fn send_data_client(&mut self, wt_stream_id: StreamId, data: &[u8]) { + assert_eq!( + self.client.send_data(wt_stream_id, data).unwrap(), + data.len() + ); + self.exchange_packets(); + } + + fn receive_data_client( + &mut self, + expected_stream_id: StreamId, + new_stream: bool, + expected_data: &[u8], + expected_fin: bool, + ) { + let mut new_stream_received = false; + let mut data_received = false; + while let Some(event) = self.client.next_event() { + match event { + Http3ClientEvent::WebTransport(WebTransportEvent::NewStream { + stream_id, .. + }) => { + assert_eq!(stream_id, expected_stream_id); + new_stream_received = true; + } + Http3ClientEvent::DataReadable { stream_id } => { + assert_eq!(stream_id, expected_stream_id); + let mut buf = [0; 100]; + let (amount, fin) = self.client.read_data(now(), stream_id, &mut buf).unwrap(); + assert_eq!(fin, expected_fin); + assert_eq!(amount, expected_data.len()); + assert_eq!(&buf[..amount], expected_data); + data_received = true; + } + _ => {} + } + } + assert!(data_received); + assert_eq!(new_stream, new_stream_received); + } + + fn close_stream_sending_client(&mut self, wt_stream_id: StreamId) { + self.client.stream_close_send(wt_stream_id).unwrap(); + self.exchange_packets(); + } + + fn reset_stream_client(&mut self, wt_stream_id: StreamId) { + self.client + .stream_reset_send(wt_stream_id, Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn receive_reset_client(&mut self, expected_stream_id: StreamId) { + let wt_reset_event = |e| { + matches!( + e, + Http3ClientEvent::Reset { + stream_id, + error, + local + } if stream_id == expected_stream_id && error == Error::HttpNoError.code() && !local + ) + }; + assert!(self.client.events().any(wt_reset_event)); + } + + fn stream_stop_sending_client(&mut self, stream_id: StreamId) { + self.client + .stream_stop_sending(stream_id, Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn receive_stop_sending_client(&mut self, expected_stream_id: StreamId) { + let wt_stop_sending_event = |e| { + matches!( + e, + Http3ClientEvent::StopSending { + stream_id, + error + } if stream_id == expected_stream_id && error == Error::HttpNoError.code() + ) + }; + assert!(self.client.events().any(wt_stop_sending_event)); + } + + fn check_events_after_closing_session_client( + &mut self, + expected_reset_ids: &[StreamId], + expected_error_stream_reset: Option<u64>, + expected_stop_sending_ids: &[StreamId], + expected_error_stream_stop_sending: Option<u64>, + expected_local: bool, + expected_session_close: &Option<(StreamId, SessionCloseReason)>, + ) { + let mut reset_ids_count = 0; + let mut stop_sending_ids_count = 0; + let mut close_event = false; + while let Some(event) = self.client.next_event() { + match event { + Http3ClientEvent::Reset { + stream_id, + error, + local, + } => { + assert!(expected_reset_ids.contains(&stream_id)); + assert_eq!(expected_error_stream_reset.unwrap(), error); + assert_eq!(expected_local, local); + reset_ids_count += 1; + } + Http3ClientEvent::StopSending { stream_id, error } => { + assert!(expected_stop_sending_ids.contains(&stream_id)); + assert_eq!(expected_error_stream_stop_sending.unwrap(), error); + stop_sending_ids_count += 1; + } + Http3ClientEvent::WebTransport(WebTransportEvent::SessionClosed { + stream_id, + reason, + headers, + }) => { + close_event = true; + assert_eq!(stream_id, expected_session_close.as_ref().unwrap().0); + assert_eq!(expected_session_close.as_ref().unwrap().1, reason); + assert!(headers.is_none()); + } + _ => {} + } + } + assert_eq!(reset_ids_count, expected_reset_ids.len()); + assert_eq!(stop_sending_ids_count, expected_stop_sending_ids.len()); + assert_eq!(close_event, expected_session_close.is_some()); + } + + fn create_wt_stream_server( + wt_server_session: &mut WebTransportRequest, + stream_type: StreamType, + ) -> Http3OrWebTransportStream { + wt_server_session.create_stream(stream_type).unwrap() + } + + fn send_data_server(&mut self, wt_stream: &mut Http3OrWebTransportStream, data: &[u8]) { + assert_eq!(wt_stream.send_data(data).unwrap(), data.len()); + self.exchange_packets(); + } + + fn receive_data_server( + &mut self, + stream_id: StreamId, + new_stream: bool, + expected_data: &[u8], + expected_fin: bool, + ) -> Http3OrWebTransportStream { + self.exchange_packets(); + let mut new_stream_received = false; + let mut data_received = false; + let mut wt_stream = None; + let mut stream_closed = false; + let mut recv_data = Vec::new(); + while let Some(event) = self.server.next_event() { + match event { + Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(request)) => { + assert_eq!(stream_id, request.stream_id()); + new_stream_received = true; + } + Http3ServerEvent::Data { + mut data, + fin, + stream, + } => { + recv_data.append(&mut data); + stream_closed = fin; + data_received = true; + wt_stream = Some(stream); + } + _ => {} + } + } + assert_eq!(&recv_data[..], expected_data); + assert!(data_received); + assert_eq!(new_stream, new_stream_received); + assert_eq!(stream_closed, expected_fin); + wt_stream.unwrap() + } + + fn close_stream_sending_server(&mut self, wt_stream: &mut Http3OrWebTransportStream) { + wt_stream.stream_close_send().unwrap(); + self.exchange_packets(); + } + + fn reset_stream_server(&mut self, wt_stream: &mut Http3OrWebTransportStream) { + wt_stream + .stream_reset_send(Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn stream_stop_sending_server(&mut self, wt_stream: &mut Http3OrWebTransportStream) { + wt_stream + .stream_stop_sending(Error::HttpNoError.code()) + .unwrap(); + self.exchange_packets(); + } + + fn receive_reset_server(&mut self, expected_stream_id: StreamId, expected_error: u64) { + let stream_reset = |e| { + matches!( + e, + Http3ServerEvent::StreamReset { + stream, + error + } if stream.stream_id() == expected_stream_id && error == expected_error + ) + }; + assert!(self.server.events().any(stream_reset)); + } + + fn receive_stop_sending_server(&mut self, expected_stream_id: StreamId, expected_error: u64) { + let stop_sending = |e| { + matches!( + e, + Http3ServerEvent::StreamStopSending { + stream, + error + } if stream.stream_id() == expected_stream_id && error == expected_error + ) + }; + assert!(self.server.events().any(stop_sending)); + } + + fn check_events_after_closing_session_server( + &mut self, + expected_reset_ids: &[StreamId], + expected_error_stream_reset: Option<u64>, + expected_stop_sending_ids: &[StreamId], + expected_error_stream_stop_sending: Option<u64>, + expected_session_close: &Option<(StreamId, SessionCloseReason)>, + ) { + let mut reset_ids_count = 0; + let mut stop_sending_ids_count = 0; + let mut close_event = false; + while let Some(event) = self.server.next_event() { + match event { + Http3ServerEvent::StreamReset { stream, error } => { + assert!(expected_reset_ids.contains(&stream.stream_id())); + assert_eq!(expected_error_stream_reset.unwrap(), error); + reset_ids_count += 1; + } + Http3ServerEvent::StreamStopSending { stream, error } => { + assert!(expected_stop_sending_ids.contains(&stream.stream_id())); + assert_eq!(expected_error_stream_stop_sending.unwrap(), error); + stop_sending_ids_count += 1; + } + Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed { + session, + reason, + headers, + }) => { + close_event = true; + assert_eq!( + session.stream_id(), + expected_session_close.as_ref().unwrap().0 + ); + assert_eq!(expected_session_close.as_ref().unwrap().1, reason); + assert!(headers.is_none()); + } + _ => {} + } + } + assert_eq!(reset_ids_count, expected_reset_ids.len()); + assert_eq!(stop_sending_ids_count, expected_stop_sending_ids.len()); + assert_eq!(close_event, expected_session_close.is_some()); + } + + pub fn session_close_frame_client(&mut self, session_id: StreamId, error: u32, message: &str) { + self.client + .webtransport_close_session(session_id, error, message) + .unwrap(); + } + + pub fn session_close_frame_server( + wt_session: &mut WebTransportRequest, + error: u32, + message: &str, + ) { + wt_session.close_session(error, message).unwrap(); + } + + fn max_datagram_size(&self, stream_id: StreamId) -> Result<u64, Error> { + self.client.webtransport_max_datagram_size(stream_id) + } + + fn send_datagram(&mut self, stream_id: StreamId, buf: &[u8]) -> Result<(), Error> { + self.client.webtransport_send_datagram(stream_id, buf, None) + } + + fn check_datagram_received_client( + &mut self, + expected_stream_id: StreamId, + expected_dgram: &[u8], + ) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Datagram { + session_id, + datagram + }) if session_id == expected_stream_id && datagram == expected_dgram + ) + }; + assert!(self.client.events().any(wt_datagram_event)); + } + + fn check_datagram_received_server( + &mut self, + expected_session: &WebTransportRequest, + expected_dgram: &[u8], + ) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram { + session, + datagram + }) if session.stream_id() == expected_session.stream_id() && datagram == expected_dgram + ) + }; + assert!(self.server.events().any(wt_datagram_event)); + } + + fn check_no_datagram_received_client(&mut self) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Datagram { .. }) + ) + }; + assert!(!self.client.events().any(wt_datagram_event)); + } + + fn check_no_datagram_received_server(&mut self) { + let wt_datagram_event = |e| { + matches!( + e, + Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram { .. }) + ) + }; + assert!(!self.server.events().any(wt_datagram_event)); + } +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs new file mode 100644 index 0000000000..23784e5609 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/negotiation.rs @@ -0,0 +1,278 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::{connect, default_http3_client, default_http3_server, exchange_packets}; +use crate::{ + settings::{HSetting, HSettingType, HSettings}, + Error, HFrame, Http3Client, Http3ClientEvent, Http3Parameters, Http3Server, Http3State, + WebTransportEvent, +}; +use neqo_common::{event::Provider, Encoder}; +use neqo_crypto::AuthenticationStatus; +use neqo_transport::{Connection, ConnectionError, StreamType}; +use std::time::Duration; +use test_fixture::{default_server_h3, now}; + +fn check_wt_event(client: &mut Http3Client, wt_enable_client: bool, wt_enable_server: bool) { + let wt_event = client.events().find_map(|e| { + if let Http3ClientEvent::WebTransport(WebTransportEvent::Negotiated(neg)) = e { + Some(neg) + } else { + None + } + }); + + assert_eq!(wt_event.is_some(), wt_enable_client); + if let Some(wt) = wt_event { + assert_eq!(wt, wt_enable_client && wt_enable_server); + } +} + +fn connect_wt(wt_enabled_client: bool, wt_enabled_server: bool) -> (Http3Client, Http3Server) { + connect( + Http3Parameters::default().webtransport(wt_enabled_client), + Http3Parameters::default().webtransport(wt_enabled_server), + ) +} + +#[test] +fn negotiate_wt() { + let (mut client, _server) = connect_wt(true, true); + assert!(client.webtransport_enabled()); + check_wt_event(&mut client, true, true); + + let (mut client, _server) = connect_wt(true, false); + assert!(!client.webtransport_enabled()); + check_wt_event(&mut client, true, false); + + let (mut client, _server) = connect_wt(false, true); + assert!(!client.webtransport_enabled()); + check_wt_event(&mut client, false, true); + + let (mut client, _server) = connect_wt(false, false); + assert!(!client.webtransport_enabled()); + check_wt_event(&mut client, false, false); +} + +#[derive(PartialEq, Eq)] +enum ClientState { + ClientEnabled, + ClientDisabled, +} + +#[derive(PartialEq, Eq)] +enum ServerState { + ServerEnabled, + ServerDisabled, +} + +fn zero_rtt( + client_state: &ClientState, + server_state: &ServerState, + client_resumed_state: &ClientState, + server_resumed_state: &ServerState, +) { + let client_org = ClientState::ClientEnabled.eq(client_state); + let server_org = ServerState::ServerEnabled.eq(server_state); + let client_resumed = ClientState::ClientEnabled.eq(client_resumed_state); + let server_resumed = ServerState::ServerEnabled.eq(server_resumed_state); + + let (mut client, mut server) = connect_wt(client_org, server_org); + assert_eq!(client.webtransport_enabled(), client_org && server_org); + + // exchange token + let out = server.process(None, now()); + // We do not have a token so we need to wait for a resumption token timer to trigger. + std::mem::drop(client.process(out.dgram(), now() + Duration::from_millis(250))); + assert_eq!(client.state(), Http3State::Connected); + let token = client + .events() + .find_map(|e| { + if let Http3ClientEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }) + .unwrap(); + + let mut client = default_http3_client(Http3Parameters::default().webtransport(client_resumed)); + let mut server = default_http3_server(Http3Parameters::default().webtransport(server_resumed)); + client + .enable_resumption(now(), &token) + .expect("Set resumption token."); + assert_eq!(client.state(), Http3State::ZeroRtt); + + exchange_packets(&mut client, &mut server); + + assert_eq!(&client.state(), &Http3State::Connected); + assert_eq!( + client.webtransport_enabled(), + client_resumed && server_resumed + ); + + let mut early_data_accepted = true; + // The only case we should not do 0-RTT is when webtransport was enabled + // originally and is disabled afterwards. + if server_org && !server_resumed { + early_data_accepted = false; + } + assert_eq!( + client.tls_info().unwrap().early_data_accepted(), + early_data_accepted + ); + + check_wt_event(&mut client, client_resumed, server_resumed); +} + +#[test] +fn zero_rtt_wt_settings() { + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); + + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientDisabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerDisabled, + ); + zero_rtt( + &ClientState::ClientDisabled, + &ServerState::ServerEnabled, + &ClientState::ClientEnabled, + &ServerState::ServerEnabled, + ); +} + +fn exchange_packets2(client: &mut Http3Client, server: &mut Connection) { + let mut out = None; + loop { + out = client.process(out, now()).dgram(); + out = server.process(out, now()).dgram(); + if out.is_none() { + break; + } + } +} + +#[test] +fn wrong_setting_value() { + const CONTROL_STREAM_TYPE: &[u8] = &[0x0]; + let mut client = default_http3_client(Http3Parameters::default()); + let mut server = default_server_h3(); + + exchange_packets2(&mut client, &mut server); + client.authenticated(AuthenticationStatus::Ok, now()); + exchange_packets2(&mut client, &mut server); + + let control = server.stream_create(StreamType::UniDi).unwrap(); + server.stream_send(control, CONTROL_STREAM_TYPE).unwrap(); + // Encode a settings frame and send it. + let mut enc = Encoder::default(); + let settings = HFrame::Settings { + settings: HSettings::new(&[HSetting::new(HSettingType::EnableWebTransport, 2)]), + }; + settings.encode(&mut enc); + assert_eq!( + server.stream_send(control, enc.as_ref()).unwrap(), + enc.as_ref().len() + ); + + exchange_packets2(&mut client, &mut server); + match client.state() { + Http3State::Closing(err) | Http3State::Closed(err) => { + assert_eq!( + err, + ConnectionError::Application(Error::HttpSettings.code()) + ); + } + _ => panic!("Wrong state {:?}", client.state()), + }; +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs new file mode 100644 index 0000000000..65572a1c2a --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/sessions.rs @@ -0,0 +1,450 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::features::extended_connect::tests::webtransport::{ + default_http3_client, default_http3_server, wt_default_parameters, WtTest, +}; +use crate::{ + features::extended_connect::SessionCloseReason, frames::WebTransportFrame, Error, Header, + Http3ClientEvent, Http3OrWebTransportStream, Http3Server, Http3ServerEvent, Http3State, + Priority, WebTransportEvent, WebTransportServerEvent, WebTransportSessionAcceptAction, +}; +use neqo_common::{event::Provider, Encoder}; +use neqo_transport::StreamType; +use std::mem; +use test_fixture::now; + +#[test] +fn wt_session() { + let mut wt = WtTest::new(); + mem::drop(wt.create_wt_session()); +} + +#[test] +fn wt_session_reject() { + let mut wt = WtTest::new(); + let headers = vec![Header::new(":status", "404")]; + let accept_res = WebTransportSessionAcceptAction::Reject(headers.clone()); + let (wt_session_id, _wt_session) = wt.negotiate_wt_session(&accept_res); + + wt.check_session_closed_event_client( + wt_session_id, + &SessionCloseReason::Status(404), + &Some(headers), + ); +} + +#[test] +fn wt_session_close_client() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt.cancel_session_client(wt_session.stream_id()); + wt.check_session_closed_event_server( + &mut wt_session, + &SessionCloseReason::Error(Error::HttpNoError.code()), + ); +} + +#[test] +fn wt_session_close_server() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt.cancel_session_server(&mut wt_session); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpNoError.code()), + &None, + ); +} + +#[test] +fn wt_session_close_server_close_send() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt_session.stream_close_send().unwrap(); + wt.exchange_packets(); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + &None, + ); +} + +#[test] +fn wt_session_close_server_stop_sending() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt_session + .stream_stop_sending(Error::HttpNoError.code()) + .unwrap(); + wt.exchange_packets(); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpNoError.code()), + &None, + ); +} + +#[test] +fn wt_session_close_server_reset() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt_session + .stream_reset_send(Error::HttpNoError.code()) + .unwrap(); + wt.exchange_packets(); + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpNoError.code()), + &None, + ); +} + +#[test] +fn wt_session_response_with_1xx() { + let mut wt = WtTest::new(); + + let wt_session_id = wt + .client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + wt.exchange_packets(); + + let mut wt_server_session = None; + while let Some(event) = wt.server.next_event() { + if let Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + session, + headers, + }) = event + { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + wt_server_session = Some(session); + } + } + + let mut wt_server_session = wt_server_session.unwrap(); + + // Send interim response. + wt_server_session + .send_headers(&[Header::new(":status", "111")]) + .unwrap(); + wt_server_session + .response(&WebTransportSessionAcceptAction::Accept) + .unwrap(); + + wt.exchange_packets(); + + let wt_session_negotiated_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::Session{ + stream_id, + status, + headers, + }) if ( + stream_id == wt_session_id && + status == 200 && + headers.contains(&Header::new(":status", "200")) + ) + ) + }; + assert!(wt.client.events().any(wt_session_negotiated_event)); + + assert_eq!(wt_session_id, wt_server_session.stream_id()); +} + +#[test] +fn wt_session_response_with_redirect() { + let headers = [Header::new(":status", "302"), Header::new("location", "/")].to_vec(); + let mut wt = WtTest::new(); + + let accept_res = WebTransportSessionAcceptAction::Reject(headers.clone()); + + let (wt_session_id, _wt_session) = wt.negotiate_wt_session(&accept_res); + + wt.check_session_closed_event_client( + wt_session_id, + &SessionCloseReason::Status(302), + &Some(headers), + ); +} + +#[test] +fn wt_session_respone_200_with_fin() { + let mut wt = WtTest::new(); + + let wt_session_id = wt + .client + .webtransport_create_session(now(), &("https", "something.com", "/"), &[]) + .unwrap(); + wt.exchange_packets(); + let mut wt_server_session = None; + while let Some(event) = wt.server.next_event() { + if let Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession { + session, + headers, + }) = event + { + assert!( + headers + .iter() + .any(|h| h.name() == ":method" && h.value() == "CONNECT") + && headers + .iter() + .any(|h| h.name() == ":protocol" && h.value() == "webtransport") + ); + wt_server_session = Some(session); + } + } + + let mut wt_server_session = wt_server_session.unwrap(); + wt_server_session + .response(&WebTransportSessionAcceptAction::Accept) + .unwrap(); + wt_server_session.stream_close_send().unwrap(); + + wt.exchange_packets(); + + let wt_session_close_event = |e| { + matches!( + e, + Http3ClientEvent::WebTransport(WebTransportEvent::SessionClosed{ + stream_id, + reason, + headers, + .. + }) if ( + stream_id == wt_session_id && + reason == SessionCloseReason::Clean{ error: 0, message: String::new()} && + headers.is_none() + ) + ) + }; + assert!(wt.client.events().any(wt_session_close_event)); + + assert_eq!(wt_session_id, wt_server_session.stream_id()); +} + +#[test] +fn wt_session_close_frame_client() { + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + wt.session_close_frame_client(wt_session.stream_id(), ERROR_NUM, ERROR_MESSAGE); + wt.exchange_packets(); + + wt.check_session_closed_event_server( + &mut wt_session, + &SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + ); +} + +#[test] +fn wt_session_close_frame_server() { + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + WtTest::session_close_frame_server(&mut wt_session, ERROR_NUM, ERROR_MESSAGE); + wt.exchange_packets(); + + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + &None, + ); +} + +#[test] +fn wt_unknown_session_frame_client() { + const UNKNOWN_FRAME_LEN: usize = 832; + const BUF: &[u8] = &[0; 10]; + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + // Send an unknown frame. + let mut enc = Encoder::with_capacity(UNKNOWN_FRAME_LEN + 4); + enc.encode_varint(1028_u64); // Arbitrary type. + enc.encode_varint(UNKNOWN_FRAME_LEN as u64); + let mut buf: Vec<_> = enc.into(); + buf.resize(UNKNOWN_FRAME_LEN + buf.len(), 0); + wt.client.send_data(wt_session.stream_id(), &buf).unwrap(); + wt.exchange_packets(); + + // The session is still active + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, false); + + // Now close the session. + wt.session_close_frame_client(wt_session.stream_id(), ERROR_NUM, ERROR_MESSAGE); + wt.exchange_packets(); + + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + )), + ); +} + +#[test] +fn wt_close_session_frame_broken_client() { + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + // Send a incorrect CloseSession frame. + let mut enc = Encoder::default(); + WebTransportFrame::CloseSession { + error: 5, + message: "Hello".to_string(), + } + .encode(&mut enc); + let mut buf: Vec<_> = enc.into(); + // Corrupt the string. + buf[9] = 0xff; + wt.client.send_data(wt_session.stream_id(), &buf).unwrap(); + wt.exchange_packets(); + + // check that the webtransport session is closed. + wt.check_session_closed_event_client( + wt_session.stream_id(), + &SessionCloseReason::Error(Error::HttpGeneralProtocolStream.code()), + &None, + ); + wt.check_session_closed_event_server( + &mut wt_session, + &SessionCloseReason::Error(Error::HttpGeneralProtocolStream.code()), + ); + + // The Http3 session is still working. + assert_eq!(wt.client.state(), Http3State::Connected); + assert_eq!(wt_session.state(), Http3State::Connected); +} + +fn receive_request(server: &mut Http3Server) -> Option<Http3OrWebTransportStream> { + while let Some(event) = server.next_event() { + if let Http3ServerEvent::Headers { stream, .. } = event { + return Some(stream); + } + } + None +} + +#[test] +// Ignoring this test as it is panicking at wt.create_wt_stream_client +// Issue # 1386 is created to track this +#[ignore] +fn wt_close_session_cannot_be_sent_at_once() { + const BUF: &[u8] = &[0; 443]; + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + + let client = default_http3_client(wt_default_parameters()); + let server = default_http3_server(wt_default_parameters()); + let mut wt = WtTest::new_with(client, server); + + let mut wt_session = wt.create_wt_session(); + + // Fill the flow control window using an unrelated http stream. + let req_id = wt + .client + .fetch( + now(), + "GET", + &("https", "something.com", "/"), + &[], + Priority::default(), + ) + .unwrap(); + assert_eq!(req_id, 4); + wt.exchange_packets(); + let mut req = receive_request(&mut wt.server).unwrap(); + req.send_headers(&[ + Header::new(":status", "200"), + Header::new("content-length", BUF.len().to_string()), + ]) + .unwrap(); + req.send_data(BUF).unwrap(); + + // Now close the session. + WtTest::session_close_frame_server(&mut wt_session, ERROR_NUM, ERROR_MESSAGE); + // server cannot create new streams. + assert_eq!( + wt_session.create_stream(StreamType::UniDi), + Err(Error::InvalidStreamId) + ); + + let out = wt.server.process(None, now()).dgram(); + let out = wt.client.process(out, now()).dgram(); + + // Client has not received the full CloseSession frame and it can create more streams. + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + let out = wt.server.process(out, now()).dgram(); + let out = wt.client.process(out, now()).dgram(); + let out = wt.server.process(out, now()).dgram(); + let out = wt.client.process(out, now()).dgram(); + let out = wt.server.process(out, now()).dgram(); + let _out = wt.client.process(out, now()).dgram(); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_client], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + )), + ); + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs new file mode 100644 index 0000000000..0eaf3ce928 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs @@ -0,0 +1,1081 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::features::extended_connect::tests::webtransport::WtTest; +use crate::{features::extended_connect::SessionCloseReason, Error}; +use neqo_transport::StreamType; +use std::mem; + +#[test] +fn wt_client_stream_uni() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + wt.receive_data_server(wt_stream, true, BUF_CLIENT, false); +} + +#[test] +fn wt_client_stream_bidi() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(wt_client_stream, BUF_CLIENT); + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, false); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_client_stream, false, BUF_SERVER, false); +} + +#[test] +fn wt_server_stream_uni() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); +} + +#[test] +fn wt_server_stream_bidi() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.send_data_client(wt_server_stream.stream_id(), BUF_CLIENT); + mem::drop(wt.receive_data_server(wt_server_stream.stream_id(), false, BUF_CLIENT, false)); +} + +#[test] +fn wt_client_stream_uni_close() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + wt.close_stream_sending_client(wt_stream); + wt.receive_data_server(wt_stream, true, BUF_CLIENT, true); +} + +#[test] +fn wt_client_stream_bidi_close() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + + wt.send_data_client(wt_client_stream, BUF_CLIENT); + wt.close_stream_sending_client(wt_client_stream); + + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, true); + + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.close_stream_sending_server(&mut wt_server_stream); + wt.receive_data_client(wt_client_stream, false, BUF_SERVER, true); +} + +#[test] +fn wt_server_stream_uni_closed() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.close_stream_sending_server(&mut wt_server_stream); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, true); +} + +#[test] +fn wt_server_stream_bidi_close() { + const BUF_CLIENT: &[u8] = &[0; 10]; + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.close_stream_sending_server(&mut wt_server_stream); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, true); + wt.send_data_client(wt_server_stream.stream_id(), BUF_CLIENT); + wt.close_stream_sending_client(wt_server_stream.stream_id()); + mem::drop(wt.receive_data_server(wt_server_stream.stream_id(), false, BUF_CLIENT, true)); +} + +#[test] +fn wt_client_stream_uni_reset() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + mem::drop(wt.receive_data_server(wt_stream, true, BUF_CLIENT, false)); + wt.reset_stream_client(wt_stream); + wt.receive_reset_server(wt_stream, Error::HttpNoError.code()); +} + +#[test] +fn wt_server_stream_uni_reset() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.reset_stream_server(&mut wt_server_stream); + wt.receive_reset_client(wt_server_stream.stream_id()); +} + +#[test] +fn wt_client_stream_bidi_reset() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + + wt.send_data_client(wt_client_stream, BUF_CLIENT); + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, false); + + wt.reset_stream_client(wt_client_stream); + wt.receive_reset_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + + wt.reset_stream_server(&mut wt_server_stream); + wt.receive_reset_client(wt_client_stream); +} + +#[test] +fn wt_server_stream_bidi_reset() { + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + + wt.reset_stream_client(wt_server_stream.stream_id()); + wt.receive_reset_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + + wt.reset_stream_server(&mut wt_server_stream); + wt.receive_reset_client(wt_server_stream.stream_id()); +} + +#[test] +fn wt_client_stream_uni_stop_sending() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(wt_stream, BUF_CLIENT); + let mut wt_server_stream = wt.receive_data_server(wt_stream, true, BUF_CLIENT, false); + wt.stream_stop_sending_server(&mut wt_server_stream); + wt.receive_stop_sending_client(wt_stream); +} + +#[test] +fn wt_server_stream_uni_stop_sending() { + const BUF_SERVER: &[u8] = &[2; 30]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.stream_stop_sending_client(wt_server_stream.stream_id()); + wt.receive_stop_sending_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); +} + +#[test] +fn wt_client_stream_bidi_stop_sending() { + const BUF_CLIENT: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let wt_client_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + + wt.send_data_client(wt_client_stream, BUF_CLIENT); + + let mut wt_server_stream = wt.receive_data_server(wt_client_stream, true, BUF_CLIENT, false); + + wt.stream_stop_sending_client(wt_client_stream); + + wt.receive_stop_sending_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + wt.stream_stop_sending_server(&mut wt_server_stream); + wt.receive_stop_sending_client(wt_server_stream.stream_id()); +} + +#[test] +fn wt_server_stream_bidi_stop_sending() { + const BUF_SERVER: &[u8] = &[1; 20]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + let mut wt_server_stream = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + + wt.send_data_server(&mut wt_server_stream, BUF_SERVER); + wt.receive_data_client(wt_server_stream.stream_id(), true, BUF_SERVER, false); + wt.stream_stop_sending_client(wt_server_stream.stream_id()); + wt.receive_stop_sending_server(wt_server_stream.stream_id(), Error::HttpNoError.code()); + wt.stream_stop_sending_server(&mut wt_server_stream); + wt.receive_stop_sending_client(wt_server_stream.stream_id()); +} + +// For the following tests the client cancels a session. The streams are in different states: +// 1) Both sides of a bidirectional client stream are opened. +// 2) A client unidirectional stream is opened. +// 3) A client unidirectional stream has been closed and both sides consumed the closing info. +// 4) A client unidirectional stream has been closed, but only the server has consumed the closing info. +// 5) A client unidirectional stream has been closed, but only the client has consum the closing info. +// 6) Both sides of a bidirectional server stream are opened. +// 7) A server unidirectional stream is opened. +// 8) A server unidirectional stream has been closed and both sides consumed the closing info. +// 9) A server unidirectional stream has been closed, but only the server has consumed the closing info. +// 10) A server unidirectional stream has been closed, but only the client has consumed the closing info. +// 11) Both sides of a bidirectional stream have been closed and consumed by both sides. +// 12) Both sides of a bidirectional stream have been closed, but not consumed by both sides. +// 13) Multiples open streams + +#[test] +fn wt_client_session_close_1() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let bidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_from_client, BUF); + std::mem::drop(wt.receive_data_server(bidi_from_client, true, BUF, false)); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_client], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_2() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + std::mem::drop(wt.receive_data_server(unidi_from_client, true, BUF, false)); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[unidi_from_client], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_from_client], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_3() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + std::mem::drop(wt.receive_data_server(unidi_from_client, true, BUF, false)); + wt.close_stream_sending_client(unidi_from_client); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_4() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + let mut unidi_from_client_s = wt.receive_data_server(unidi_from_client, true, BUF, false); + wt.stream_stop_sending_server(&mut unidi_from_client_s); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_from_client], + Some(Error::HttpNoError.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_5() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let unidi_from_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + + wt.send_data_client(unidi_from_client, BUF); + mem::drop(wt.receive_data_server(unidi_from_client, true, BUF, false)); + wt.reset_stream_client(unidi_from_client); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[unidi_from_client], + Some(Error::HttpNoError.code()), + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_6() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_from_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_from_server, BUF); + wt.receive_data_client(bidi_from_server.stream_id(), true, BUF, false); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_7() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_from_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_from_server, BUF); + wt.receive_data_client(unidi_from_server.stream_id(), true, BUF, false); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[unidi_from_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_8() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, true); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_9() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.stream_stop_sending_client(unidi_server.stream_id()); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpNoError.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_10() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_11() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.receive_data_client(bidi_server.stream_id(), true, BUF, true); + wt.stream_stop_sending_server(&mut bidi_server); + wt.receive_stop_sending_client(bidi_server.stream_id()); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client(&[], None, &[], None, false, &None); +} + +#[test] +fn wt_client_session_close_12() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.stream_stop_sending_server(&mut bidi_server); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[], + None, + &[], + None, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpNoError.code()), + false, + &None, + ); +} + +#[test] +fn wt_client_session_close_13() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let wt_session = wt.create_wt_session(); + + let bidi_client_1 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_1, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_1, true, BUF, false)); + let bidi_client_2 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_2, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_2, true, BUF, false)); + + wt.cancel_session_client(wt_session.stream_id()); + + wt.check_events_after_closing_session_server( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_client( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + false, + &None, + ); +} + +// For the following tests the server cancels a session. The streams are in different states: +// 1) Both sides of a bidirectional client stream are opened. +// 2) A client unidirectional stream is opened. +// 3) A client unidirectional stream has been closed and consumed by both sides. +// 4) A client unidirectional stream has been closed, but not consumed by the client. +// 5) Both sides of a bidirectional server stream are opened. +// 6) A server unidirectional stream is opened. +// 7) A server unidirectional stream has been closed and consumed by both sides. +// 8) A server unidirectional stream has been closed, but not consumed by the client. +// 9) Both sides of a bidirectional stream have been closed and consumed by both sides. +// 10) Both sides of a bidirectional stream have been closed, but not consumed by the client. +// 12) Multiples open streams + +#[test] +fn wt_client_session_server_close_1() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let bidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client, BUF); + std::mem::drop(wt.receive_data_server(bidi_client, true, BUF, false)); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_client_session_server_close_2() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(unidi_client, BUF); + std::mem::drop(wt.receive_data_server(unidi_client, true, BUF, false)); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_client], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[unidi_client], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + &None, + ); +} + +#[test] +fn wt_client_session_server_close_3() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(unidi_client, BUF); + let mut unidi_client_s = wt.receive_data_server(unidi_client, true, BUF, false); + wt.stream_stop_sending_server(&mut unidi_client_s); + wt.receive_stop_sending_client(unidi_client); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[], + None, + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_4() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let unidi_client = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi); + wt.send_data_client(unidi_client, BUF); + let mut unidi_client_s = wt.receive_data_server(unidi_client, true, BUF, false); + wt.stream_stop_sending_server(&mut unidi_client_s); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[], + None, + &[unidi_client], + Some(Error::HttpNoError.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_5() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.receive_data_client(bidi_server.stream_id(), true, BUF, false); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_client_session_server_close_6() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, false); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_client_session_server_close_7() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + wt.receive_data_client(unidi_server.stream_id(), true, BUF, true); + + wt.cancel_session_server(&mut wt_session); + + // Already close stream will not have a reset event. + wt.check_events_after_closing_session_client( + &[], + None, + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_8() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.close_stream_sending_server(&mut unidi_server); + + wt.cancel_session_server(&mut wt_session); + + // The stream was only closed on the server side therefore it is cancelled on the client side. + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_9() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.receive_data_client(bidi_server.stream_id(), true, BUF, true); + wt.stream_stop_sending_server(&mut bidi_server); + wt.receive_stop_sending_client(bidi_server.stream_id()); + + wt.cancel_session_server(&mut wt_session); + + // Already close stream will not have a reset event. + wt.check_events_after_closing_session_client( + &[], + None, + &[], + None, + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_10() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut bidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::BiDi); + wt.send_data_server(&mut bidi_server, BUF); + wt.close_stream_sending_server(&mut bidi_server); + wt.stream_stop_sending_server(&mut bidi_server); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[bidi_server.stream_id()], + Some(Error::HttpNoError.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server(&[], None, &[], None, &None); +} + +#[test] +fn wt_client_session_server_close_11() { + const BUF: &[u8] = &[0; 10]; + + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let bidi_client_1 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_1, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_1, true, BUF, false)); + let bidi_client_2 = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::BiDi); + wt.send_data_client(bidi_client_2, BUF); + std::mem::drop(wt.receive_data_server(bidi_client_2, true, BUF, false)); + + wt.cancel_session_server(&mut wt_session); + + wt.check_events_after_closing_session_client( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + false, + &Some(( + wt_session.stream_id(), + SessionCloseReason::Error(Error::HttpNoError.code()), + )), + ); + + wt.check_events_after_closing_session_server( + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &[bidi_client_1, bidi_client_2], + Some(Error::HttpRequestCancelled.code()), + &None, + ); +} + +#[test] +fn wt_session_close_frame_and_streams_client() { + const BUF: &[u8] = &[0; 10]; + const ERROR_NUM: u32 = 23; + const ERROR_MESSAGE: &str = "Something went wrong"; + let mut wt = WtTest::new(); + let mut wt_session = wt.create_wt_session(); + + let mut unidi_server = WtTest::create_wt_stream_server(&mut wt_session, StreamType::UniDi); + wt.send_data_server(&mut unidi_server, BUF); + wt.exchange_packets(); + + wt.session_close_frame_client(wt_session.stream_id(), ERROR_NUM, ERROR_MESSAGE); + wt.check_events_after_closing_session_client( + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &[], + None, + false, + &None, + ); + wt.exchange_packets(); + + wt.check_events_after_closing_session_server( + &[], + None, + &[unidi_server.stream_id()], + Some(Error::HttpRequestCancelled.code()), + &Some(( + wt_session.stream_id(), + SessionCloseReason::Clean { + error: ERROR_NUM, + message: ERROR_MESSAGE.to_string(), + }, + )), + ); +} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs new file mode 100644 index 0000000000..4a412dd27e --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_session.rs @@ -0,0 +1,539 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use super::{ExtendedConnectEvents, ExtendedConnectType, SessionCloseReason}; +use crate::{ + frames::{FrameReader, StreamReaderRecvStreamWrapper, WebTransportFrame}, + recv_message::{RecvMessage, RecvMessageInfo}, + send_message::SendMessage, + CloseType, Error, HFrame, Http3StreamInfo, Http3StreamType, HttpRecvStream, + HttpRecvStreamEvents, Priority, PriorityHandler, ReceiveOutput, RecvStream, RecvStreamEvents, + Res, SendStream, SendStreamEvents, Stream, +}; +use neqo_common::{qtrace, Encoder, Header, MessageType, Role}; +use neqo_qpack::{QPackDecoder, QPackEncoder}; +use neqo_transport::{Connection, DatagramTracking, StreamId}; +use std::any::Any; +use std::cell::RefCell; +use std::collections::BTreeSet; +use std::mem; +use std::rc::Rc; + +#[derive(Debug, PartialEq)] +enum SessionState { + Negotiating, + Active, + FinPending, + Done, +} + +impl SessionState { + pub fn closing_state(&self) -> bool { + matches!(self, Self::FinPending | Self::Done) + } +} + +#[derive(Debug)] +pub(crate) struct WebTransportSession { + control_stream_recv: Box<dyn RecvStream>, + control_stream_send: Box<dyn SendStream>, + stream_event_listener: Rc<RefCell<WebTransportSessionListener>>, + session_id: StreamId, + state: SessionState, + frame_reader: FrameReader, + events: Box<dyn ExtendedConnectEvents>, + send_streams: BTreeSet<StreamId>, + recv_streams: BTreeSet<StreamId>, + role: Role, +} + +impl ::std::fmt::Display for WebTransportSession { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "WebTransportSession session={}", self.session_id,) + } +} + +impl WebTransportSession { + #[must_use] + pub fn new( + session_id: StreamId, + events: Box<dyn ExtendedConnectEvents>, + role: Role, + qpack_encoder: Rc<RefCell<QPackEncoder>>, + qpack_decoder: Rc<RefCell<QPackDecoder>>, + ) -> Self { + let stream_event_listener = Rc::new(RefCell::new(WebTransportSessionListener::default())); + Self { + control_stream_recv: Box::new(RecvMessage::new( + &RecvMessageInfo { + message_type: MessageType::Response, + stream_type: Http3StreamType::ExtendedConnect, + stream_id: session_id, + header_frame_type_read: false, + }, + qpack_decoder, + Box::new(stream_event_listener.clone()), + None, + PriorityHandler::new(false, Priority::default()), + )), + control_stream_send: Box::new(SendMessage::new( + MessageType::Request, + Http3StreamType::ExtendedConnect, + session_id, + qpack_encoder, + Box::new(stream_event_listener.clone()), + )), + stream_event_listener, + session_id, + state: SessionState::Negotiating, + frame_reader: FrameReader::new(), + events, + send_streams: BTreeSet::new(), + recv_streams: BTreeSet::new(), + role, + } + } + + /// # Panics + /// This function is only called with `RecvStream` and `SendStream` that also implement + /// the http specific functions and `http_stream()` will never return `None`. + #[must_use] + pub fn new_with_http_streams( + session_id: StreamId, + events: Box<dyn ExtendedConnectEvents>, + role: Role, + mut control_stream_recv: Box<dyn RecvStream>, + mut control_stream_send: Box<dyn SendStream>, + ) -> Self { + let stream_event_listener = Rc::new(RefCell::new(WebTransportSessionListener::default())); + control_stream_recv + .http_stream() + .unwrap() + .set_new_listener(Box::new(stream_event_listener.clone())); + control_stream_send + .http_stream() + .unwrap() + .set_new_listener(Box::new(stream_event_listener.clone())); + Self { + control_stream_recv, + control_stream_send, + stream_event_listener, + session_id, + state: SessionState::Active, + frame_reader: FrameReader::new(), + events, + send_streams: BTreeSet::new(), + recv_streams: BTreeSet::new(), + role, + } + } + + /// # Errors + /// The function can only fail if supplied headers are not valid http headers. + /// # Panics + /// `control_stream_send` implements the http specific functions and `http_stream()` + /// will never return `None`. + pub fn send_request(&mut self, headers: &[Header], conn: &mut Connection) -> Res<()> { + self.control_stream_send + .http_stream() + .unwrap() + .send_headers(headers, conn) + } + + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + qtrace!([self], "receive control data"); + let (out, _) = self.control_stream_recv.receive(conn)?; + debug_assert!(out == ReceiveOutput::NoOutput); + self.maybe_check_headers(); + self.read_control_stream(conn)?; + Ok((ReceiveOutput::NoOutput, self.state == SessionState::Done)) + } + + fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + let (out, _) = self + .control_stream_recv + .http_stream() + .unwrap() + .header_unblocked(conn)?; + debug_assert!(out == ReceiveOutput::NoOutput); + self.maybe_check_headers(); + self.read_control_stream(conn)?; + Ok((ReceiveOutput::NoOutput, self.state == SessionState::Done)) + } + + fn maybe_update_priority(&mut self, priority: Priority) -> bool { + self.control_stream_recv + .http_stream() + .unwrap() + .maybe_update_priority(priority) + } + + fn priority_update_frame(&mut self) -> Option<HFrame> { + self.control_stream_recv + .http_stream() + .unwrap() + .priority_update_frame() + } + + fn priority_update_sent(&mut self) { + self.control_stream_recv + .http_stream() + .unwrap() + .priority_update_sent(); + } + + fn send(&mut self, conn: &mut Connection) -> Res<()> { + self.control_stream_send.send(conn)?; + if self.control_stream_send.done() { + self.state = SessionState::Done; + } + Ok(()) + } + + fn has_data_to_send(&self) -> bool { + self.control_stream_send.has_data_to_send() + } + + fn done(&self) -> bool { + self.state == SessionState::Done + } + + fn close(&mut self, close_type: CloseType) { + if self.state.closing_state() { + return; + } + qtrace!("ExtendedConnect close the session"); + self.state = SessionState::Done; + if !close_type.locally_initiated() { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::from(close_type), + None, + ); + } + } + + /// # Panics + /// This cannot panic because headers are checked before this function called. + pub fn maybe_check_headers(&mut self) { + if SessionState::Negotiating != self.state { + return; + } + + if let Some((headers, interim, fin)) = self.stream_event_listener.borrow_mut().get_headers() + { + qtrace!( + "ExtendedConnect response headers {:?}, fin={}", + headers, + fin + ); + + if interim { + if fin { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + Some(headers), + ); + self.state = SessionState::Done; + } + } else { + let status = headers + .iter() + .find_map(|h| { + if h.name() == ":status" { + h.value().parse::<u16>().ok() + } else { + None + } + }) + .unwrap(); + + self.state = if (200..300).contains(&status) { + if fin { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + Some(headers), + ); + SessionState::Done + } else { + self.events.session_start( + ExtendedConnectType::WebTransport, + self.session_id, + status, + headers, + ); + SessionState::Active + } + } else { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Status(status), + Some(headers), + ); + SessionState::Done + }; + } + } + } + + pub fn add_stream(&mut self, stream_id: StreamId) { + if let SessionState::Active = self.state { + if stream_id.is_bidi() { + self.send_streams.insert(stream_id); + self.recv_streams.insert(stream_id); + } else if stream_id.is_self_initiated(self.role) { + self.send_streams.insert(stream_id); + } else { + self.recv_streams.insert(stream_id); + } + + if !stream_id.is_self_initiated(self.role) { + self.events + .extended_connect_new_stream(Http3StreamInfo::new( + stream_id, + ExtendedConnectType::WebTransport.get_stream_type(self.session_id), + )); + } + } + } + + pub fn remove_recv_stream(&mut self, stream_id: StreamId) { + self.recv_streams.remove(&stream_id); + } + + pub fn remove_send_stream(&mut self, stream_id: StreamId) { + self.send_streams.remove(&stream_id); + } + + #[must_use] + pub fn is_active(&self) -> bool { + matches!(self.state, SessionState::Active) + } + + pub fn take_sub_streams(&mut self) -> (BTreeSet<StreamId>, BTreeSet<StreamId>) { + ( + mem::take(&mut self.recv_streams), + mem::take(&mut self.send_streams), + ) + } + + /// # Errors + /// It may return an error if the frame is not correctly decoded. + pub fn read_control_stream(&mut self, conn: &mut Connection) -> Res<()> { + let (f, fin) = self + .frame_reader + .receive::<WebTransportFrame>(&mut StreamReaderRecvStreamWrapper::new( + conn, + &mut self.control_stream_recv, + )) + .map_err(|_| Error::HttpGeneralProtocolStream)?; + qtrace!([self], "Received frame: {:?} fin={}", f, fin); + if let Some(WebTransportFrame::CloseSession { error, message }) = f { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { error, message }, + None, + ); + self.state = if fin { + SessionState::Done + } else { + SessionState::FinPending + }; + } else if fin { + self.events.session_end( + ExtendedConnectType::WebTransport, + self.session_id, + SessionCloseReason::Clean { + error: 0, + message: String::new(), + }, + None, + ); + self.state = SessionState::Done; + } + Ok(()) + } + + /// # Errors + /// 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(&mut self, conn: &mut Connection, error: u32, message: &str) -> Res<()> { + self.state = SessionState::Done; + let close_frame = WebTransportFrame::CloseSession { + error, + message: message.to_string(), + }; + let mut encoder = Encoder::default(); + close_frame.encode(&mut encoder); + self.control_stream_send + .send_data_atomic(conn, encoder.as_ref())?; + self.control_stream_send.close(conn)?; + self.state = if self.control_stream_send.done() { + SessionState::Done + } else { + SessionState::FinPending + }; + Ok(()) + } + + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + self.control_stream_send.send_data(conn, buf) + } + + /// # Errors + /// Returns an error if the datagram exceeds the remote datagram size limit. + pub fn send_datagram( + &self, + conn: &mut Connection, + buf: &[u8], + id: impl Into<DatagramTracking>, + ) -> Res<()> { + qtrace!([self], "send_datagram state={:?}", self.state); + if let SessionState::Active = self.state { + let mut dgram_data = Encoder::default(); + dgram_data.encode_varint(self.session_id.as_u64() / 4); + dgram_data.encode(buf); + conn.send_datagram(dgram_data.as_ref(), id)?; + } else { + debug_assert!(false); + return Err(Error::Unavailable); + } + Ok(()) + } + + pub fn datagram(&mut self, datagram: Vec<u8>) { + if let SessionState::Active = self.state { + self.events.new_datagram(self.session_id, datagram); + } + } +} + +impl Stream for Rc<RefCell<WebTransportSession>> { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::ExtendedConnect + } +} + +impl RecvStream for Rc<RefCell<WebTransportSession>> { + fn receive(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.borrow_mut().receive(conn) + } + + fn reset(&mut self, close_type: CloseType) -> Res<()> { + self.borrow_mut().close(close_type); + Ok(()) + } + + fn http_stream(&mut self) -> Option<&mut dyn HttpRecvStream> { + Some(self) + } + + fn webtransport(&self) -> Option<Rc<RefCell<WebTransportSession>>> { + Some(self.clone()) + } +} + +impl HttpRecvStream for Rc<RefCell<WebTransportSession>> { + fn header_unblocked(&mut self, conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.borrow_mut().header_unblocked(conn) + } + + fn maybe_update_priority(&mut self, priority: Priority) -> bool { + self.borrow_mut().maybe_update_priority(priority) + } + + fn priority_update_frame(&mut self) -> Option<HFrame> { + self.borrow_mut().priority_update_frame() + } + + fn priority_update_sent(&mut self) { + self.borrow_mut().priority_update_sent(); + } + + fn any(&self) -> &dyn Any { + self + } +} + +impl SendStream for Rc<RefCell<WebTransportSession>> { + fn send(&mut self, conn: &mut Connection) -> Res<()> { + self.borrow_mut().send(conn) + } + + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + self.borrow_mut().send_data(conn, buf) + } + + fn has_data_to_send(&self) -> bool { + self.borrow_mut().has_data_to_send() + } + + fn stream_writable(&self) {} + + fn done(&self) -> bool { + self.borrow_mut().done() + } + + fn close(&mut self, conn: &mut Connection) -> Res<()> { + self.borrow_mut().close_session(conn, 0, "") + } + + fn close_with_message(&mut self, conn: &mut Connection, error: u32, message: &str) -> Res<()> { + self.borrow_mut().close_session(conn, error, message) + } + + fn handle_stop_sending(&mut self, close_type: CloseType) { + self.borrow_mut().close(close_type); + } +} + +#[derive(Debug, Default)] +struct WebTransportSessionListener { + headers: Option<(Vec<Header>, bool, bool)>, +} + +impl WebTransportSessionListener { + fn set_headers(&mut self, headers: Vec<Header>, interim: bool, fin: bool) { + self.headers = Some((headers, interim, fin)); + } + + pub fn get_headers(&mut self) -> Option<(Vec<Header>, bool, bool)> { + mem::take(&mut self.headers) + } +} + +impl RecvStreamEvents for Rc<RefCell<WebTransportSessionListener>> {} + +impl HttpRecvStreamEvents for Rc<RefCell<WebTransportSessionListener>> { + fn header_ready( + &self, + _stream_info: Http3StreamInfo, + headers: Vec<Header>, + interim: bool, + fin: bool, + ) { + if !interim || fin { + self.borrow_mut().set_headers(headers, interim, fin); + } + } +} + +impl SendStreamEvents for Rc<RefCell<WebTransportSessionListener>> {} diff --git a/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs new file mode 100644 index 0000000000..f08f8047d7 --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/extended_connect/webtransport_streams.rs @@ -0,0 +1,202 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::WebTransportSession; +use crate::{ + CloseType, Http3StreamInfo, Http3StreamType, ReceiveOutput, RecvStream, RecvStreamEvents, Res, + SendStream, SendStreamEvents, Stream, +}; +use neqo_common::Encoder; +use neqo_transport::{Connection, StreamId}; +use std::cell::RefCell; +use std::rc::Rc; + +pub const WEBTRANSPORT_UNI_STREAM: u64 = 0x54; +pub const WEBTRANSPORT_STREAM: u64 = 0x41; + +#[derive(Debug)] +pub(crate) struct WebTransportRecvStream { + stream_id: StreamId, + events: Box<dyn RecvStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + session_id: StreamId, + fin: bool, +} + +impl WebTransportRecvStream { + pub fn new( + stream_id: StreamId, + session_id: StreamId, + events: Box<dyn RecvStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + ) -> Self { + Self { + stream_id, + events, + session_id, + session, + fin: false, + } + } + + fn get_info(&self) -> Http3StreamInfo { + Http3StreamInfo::new(self.stream_id, self.stream_type()) + } +} + +impl Stream for WebTransportRecvStream { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::WebTransport(self.session_id) + } +} + +impl RecvStream for WebTransportRecvStream { + fn receive(&mut self, _conn: &mut Connection) -> Res<(ReceiveOutput, bool)> { + self.events.data_readable(self.get_info()); + Ok((ReceiveOutput::NoOutput, false)) + } + + fn reset(&mut self, close_type: CloseType) -> Res<()> { + if !matches!(close_type, CloseType::ResetApp(_)) { + self.events.recv_closed(self.get_info(), close_type); + } + self.session.borrow_mut().remove_recv_stream(self.stream_id); + Ok(()) + } + + fn read_data(&mut self, conn: &mut Connection, buf: &mut [u8]) -> Res<(usize, bool)> { + let (amount, fin) = conn.stream_recv(self.stream_id, buf)?; + self.fin = fin; + if fin { + self.session.borrow_mut().remove_recv_stream(self.stream_id); + } + Ok((amount, fin)) + } +} + +#[derive(Debug, PartialEq)] +enum WebTransportSenderStreamState { + SendingInit { buf: Vec<u8>, fin: bool }, + SendingData, + Done, +} + +#[derive(Debug)] +pub(crate) struct WebTransportSendStream { + stream_id: StreamId, + state: WebTransportSenderStreamState, + events: Box<dyn SendStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + session_id: StreamId, +} + +impl WebTransportSendStream { + pub fn new( + stream_id: StreamId, + session_id: StreamId, + events: Box<dyn SendStreamEvents>, + session: Rc<RefCell<WebTransportSession>>, + local: bool, + ) -> Self { + Self { + stream_id, + state: if local { + let mut d = Encoder::default(); + if stream_id.is_uni() { + d.encode_varint(WEBTRANSPORT_UNI_STREAM); + } else { + d.encode_varint(WEBTRANSPORT_STREAM); + } + d.encode_varint(session_id.as_u64()); + WebTransportSenderStreamState::SendingInit { + buf: d.into(), + fin: false, + } + } else { + WebTransportSenderStreamState::SendingData + }, + events, + session_id, + session, + } + } + + fn set_done(&mut self, close_type: CloseType) { + self.state = WebTransportSenderStreamState::Done; + self.events.send_closed(self.get_info(), close_type); + self.session.borrow_mut().remove_send_stream(self.stream_id); + } + + fn get_info(&self) -> Http3StreamInfo { + Http3StreamInfo::new(self.stream_id, self.stream_type()) + } +} + +impl Stream for WebTransportSendStream { + fn stream_type(&self) -> Http3StreamType { + Http3StreamType::WebTransport(self.session_id) + } +} + +impl SendStream for WebTransportSendStream { + fn send(&mut self, conn: &mut Connection) -> Res<()> { + if let WebTransportSenderStreamState::SendingInit { ref mut buf, fin } = self.state { + let sent = conn.stream_send(self.stream_id, &buf[..])?; + if sent == buf.len() { + if fin { + conn.stream_close_send(self.stream_id)?; + self.set_done(CloseType::Done); + } else { + self.state = WebTransportSenderStreamState::SendingData; + } + } else { + let b = buf.split_off(sent); + *buf = b; + } + } + Ok(()) + } + + fn has_data_to_send(&self) -> bool { + matches!( + self.state, + WebTransportSenderStreamState::SendingInit { .. } + ) + } + + fn stream_writable(&self) { + self.events.data_writable(self.get_info()); + } + + fn done(&self) -> bool { + self.state == WebTransportSenderStreamState::Done + } + + fn send_data(&mut self, conn: &mut Connection, buf: &[u8]) -> Res<usize> { + self.send(conn)?; + if self.state == WebTransportSenderStreamState::SendingData { + let sent = conn.stream_send(self.stream_id, buf)?; + Ok(sent) + } else { + Ok(0) + } + } + + fn handle_stop_sending(&mut self, close_type: CloseType) { + self.set_done(close_type); + } + + fn close(&mut self, conn: &mut Connection) -> Res<()> { + if let WebTransportSenderStreamState::SendingInit { ref mut fin, .. } = self.state { + *fin = true; + } else { + self.state = WebTransportSenderStreamState::Done; + conn.stream_close_send(self.stream_id)?; + self.set_done(CloseType::Done); + } + Ok(()) + } +} diff --git a/third_party/rust/neqo-http3/src/features/mod.rs b/third_party/rust/neqo-http3/src/features/mod.rs new file mode 100644 index 0000000000..0e045ed80b --- /dev/null +++ b/third_party/rust/neqo-http3/src/features/mod.rs @@ -0,0 +1,91 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::{ + client_events::Http3ClientEvents, + settings::{HSettingType, HSettings}, +}; +use neqo_common::qtrace; +use std::fmt::Debug; +use std::mem; + +pub mod extended_connect; + +/// States: +/// - `Disable` - it is not turned on for this connection. +/// - `Negotiating` - the feature is enabled locally, but settings from the peer +/// have not been received yet. +/// - `Negotiated` - the settings have been received and both sides support the feature. +/// - `NegotiationFailed` - the settings have been received and the peer does not +/// support the feature. +#[derive(Debug)] +pub enum NegotiationState { + Disabled, + Negotiating { + feature_type: HSettingType, + listener: Option<Http3ClientEvents>, + }, + Negotiated, + NegotiationFailed, +} + +impl NegotiationState { + #[must_use] + pub fn new(enable: bool, feature_type: HSettingType) -> Self { + if enable { + Self::Negotiating { + feature_type, + listener: None, + } + } else { + Self::Disabled + } + } + + pub fn set_listener(&mut self, new_listener: Http3ClientEvents) { + if let Self::Negotiating { listener, .. } = self { + *listener = Some(new_listener); + } + } + + pub fn handle_settings(&mut self, settings: &HSettings) { + if !self.locally_enabled() { + return; + } + + if let Self::Negotiating { + feature_type, + listener, + } = self + { + qtrace!( + "set_negotiated {:?} to {}", + feature_type, + settings.get(*feature_type) + ); + let cb = mem::take(listener); + let ft = *feature_type; + *self = if settings.get(ft) == 1 { + Self::Negotiated + } else { + Self::NegotiationFailed + }; + if let Some(l) = cb { + l.negotiation_done(ft, self.enabled()); + } + } + } + + #[must_use] + pub fn enabled(&self) -> bool { + matches!(self, &Self::Negotiated) + } + + #[must_use] + pub fn locally_enabled(&self) -> bool { + !matches!(self, &Self::Disabled) + } +} |