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-transport/src/connection | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.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-transport/src/connection')
22 files changed, 12216 insertions, 0 deletions
diff --git a/third_party/rust/neqo-transport/src/connection/idle.rs b/third_party/rust/neqo-transport/src/connection/idle.rs new file mode 100644 index 0000000000..5b1bd857dc --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/idle.rs @@ -0,0 +1,111 @@ +// 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::recovery::RecoveryToken; +use std::cmp::{max, min}; +use std::time::{Duration, Instant}; + +#[derive(Debug, Clone)] +/// There's a little bit of different behavior for resetting idle timeout. See +/// -transport 10.2 ("Idle Timeout"). +enum IdleTimeoutState { + Init, + PacketReceived(Instant), + AckElicitingPacketSent(Instant), +} + +#[derive(Debug, Clone)] +/// There's a little bit of different behavior for resetting idle timeout. See +/// -transport 10.2 ("Idle Timeout"). +pub struct IdleTimeout { + timeout: Duration, + state: IdleTimeoutState, + keep_alive_outstanding: bool, +} + +impl IdleTimeout { + pub fn new(timeout: Duration) -> Self { + Self { + timeout, + state: IdleTimeoutState::Init, + keep_alive_outstanding: false, + } + } +} + +impl IdleTimeout { + pub fn set_peer_timeout(&mut self, peer_timeout: Duration) { + self.timeout = min(self.timeout, peer_timeout); + } + + pub fn expiry(&self, now: Instant, pto: Duration, keep_alive: bool) -> Instant { + let start = match self.state { + IdleTimeoutState::Init => now, + IdleTimeoutState::PacketReceived(t) | IdleTimeoutState::AckElicitingPacketSent(t) => t, + }; + let delay = if keep_alive && !self.keep_alive_outstanding { + // For a keep-alive timer, wait for half the timeout interval, but be sure + // not to wait too little or we will send many unnecessary probes. + max(self.timeout / 2, pto) + } else { + max(self.timeout, pto * 3) + }; + start + delay + } + + pub fn on_packet_sent(&mut self, now: Instant) { + // Only reset idle timeout if we've received a packet since the last + // time we reset the timeout here. + match self.state { + IdleTimeoutState::AckElicitingPacketSent(_) => {} + IdleTimeoutState::Init | IdleTimeoutState::PacketReceived(_) => { + self.state = IdleTimeoutState::AckElicitingPacketSent(now); + } + } + } + + pub fn on_packet_received(&mut self, now: Instant) { + // Only update if this doesn't rewind the idle timeout. + // We sometimes process packets after caching them, which uses + // the time the packet was received. That could be in the past. + let update = match self.state { + IdleTimeoutState::Init => true, + IdleTimeoutState::AckElicitingPacketSent(t) | IdleTimeoutState::PacketReceived(t) => { + t <= now + } + }; + if update { + self.state = IdleTimeoutState::PacketReceived(now); + } + } + + pub fn expired(&self, now: Instant, pto: Duration) -> bool { + now >= self.expiry(now, pto, false) + } + + pub fn send_keep_alive( + &mut self, + now: Instant, + pto: Duration, + tokens: &mut Vec<RecoveryToken>, + ) -> bool { + if !self.keep_alive_outstanding && now >= self.expiry(now, pto, true) { + self.keep_alive_outstanding = true; + tokens.push(RecoveryToken::KeepAlive); + true + } else { + false + } + } + + pub fn lost_keep_alive(&mut self) { + self.keep_alive_outstanding = false; + } + + pub fn ack_keep_alive(&mut self) { + self.keep_alive_outstanding = false; + } +} diff --git a/third_party/rust/neqo-transport/src/connection/mod.rs b/third_party/rust/neqo-transport/src/connection/mod.rs new file mode 100644 index 0000000000..95f24f24b9 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/mod.rs @@ -0,0 +1,3124 @@ +// 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. + +// The class implementing a QUIC connection. + +use std::cell::RefCell; +use std::cmp::{max, min}; +use std::convert::TryFrom; +use std::fmt::{self, Debug}; +use std::mem; +use std::net::{IpAddr, SocketAddr}; +use std::ops::RangeInclusive; +use std::rc::{Rc, Weak}; +use std::time::{Duration, Instant}; + +use smallvec::SmallVec; + +use neqo_common::{ + event::Provider as EventProvider, hex, hex_snip_middle, hrtime, qdebug, qerror, qinfo, + qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, Role, +}; +use neqo_crypto::{ + agent::CertificateInfo, random, Agent, AntiReplay, AuthenticationStatus, Cipher, Client, + HandshakeState, PrivateKey, PublicKey, ResumptionToken, SecretAgentInfo, SecretAgentPreInfo, + Server, ZeroRttChecker, +}; + +use crate::addr_valid::{AddressValidation, NewTokenState}; +use crate::cid::{ + ConnectionId, ConnectionIdEntry, ConnectionIdGenerator, ConnectionIdManager, ConnectionIdRef, + ConnectionIdStore, LOCAL_ACTIVE_CID_LIMIT, +}; + +use crate::crypto::{Crypto, CryptoDxState, CryptoSpace}; +use crate::dump::*; +use crate::events::{ConnectionEvent, ConnectionEvents, OutgoingDatagramOutcome}; +use crate::frame::{ + CloseError, Frame, FrameType, FRAME_TYPE_CONNECTION_CLOSE_APPLICATION, + FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT, +}; +use crate::packet::{DecryptedPacket, PacketBuilder, PacketNumber, PacketType, PublicPacket}; +use crate::path::{Path, PathRef, Paths}; +use crate::quic_datagrams::{DatagramTracking, QuicDatagrams}; +use crate::recovery::{LossRecovery, RecoveryToken, SendProfile}; +use crate::rtt::GRANULARITY; +pub use crate::send_stream::{RetransmissionPriority, TransmissionPriority}; +use crate::stats::{Stats, StatsCell}; +use crate::stream_id::StreamType; +use crate::streams::Streams; +use crate::tparams::{ + self, TransportParameter, TransportParameterId, TransportParameters, TransportParametersHandler, +}; +use crate::tracking::{AckTracker, PacketNumberSpace, SentPacket}; +use crate::version::{Version, WireVersion}; +use crate::{qlog, AppError, ConnectionError, Error, Res, StreamId}; + +mod idle; +pub mod params; +mod saved; +mod state; +#[cfg(test)] +pub mod test_internal; + +use idle::IdleTimeout; +use params::PreferredAddressConfig; +pub use params::{ConnectionParameters, ACK_RATIO_SCALE}; +use saved::SavedDatagrams; +use state::StateSignaling; +pub use state::{ClosingFrame, State}; + +#[derive(Debug, Default)] +struct Packet(Vec<u8>); + +/// The number of Initial packets that the client will send in response +/// to receiving an undecryptable packet during the early part of the +/// handshake. This is a hack, but a useful one. +const EXTRA_INITIALS: usize = 4; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ZeroRttState { + Init, + Sending, + AcceptedClient, + AcceptedServer, + Rejected, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Type returned from process() and `process_output()`. Users are required to +/// call these repeatedly until `Callback` or `None` is returned. +pub enum Output { + /// Connection requires no action. + None, + /// Connection requires the datagram be sent. + Datagram(Datagram), + /// Connection requires `process_input()` be called when the `Duration` + /// elapses. + Callback(Duration), +} + +impl Output { + /// Convert into an `Option<Datagram>`. + #[must_use] + pub fn dgram(self) -> Option<Datagram> { + match self { + Self::Datagram(dg) => Some(dg), + _ => None, + } + } + + /// Get a reference to the Datagram, if any. + pub fn as_dgram_ref(&self) -> Option<&Datagram> { + match self { + Self::Datagram(dg) => Some(dg), + _ => None, + } + } + + /// Ask how long the caller should wait before calling back. + #[must_use] + pub fn callback(&self) -> Duration { + match self { + Self::Callback(t) => *t, + _ => Duration::new(0, 0), + } + } +} + +/// Used by inner functions like Connection::output. +enum SendOption { + /// Yes, please send this datagram. + Yes(Datagram), + /// Don't send. If this was blocked on the pacer (the arg is true). + No(bool), +} + +impl Default for SendOption { + fn default() -> Self { + Self::No(false) + } +} + +/// Used by `Connection::preprocess` to determine what to do +/// with an packet before attempting to remove protection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PreprocessResult { + /// End processing and return successfully. + End, + /// Stop processing this datagram and move on to the next. + Next, + /// Continue and process this packet. + Continue, +} + +/// `AddressValidationInfo` holds information relevant to either +/// responding to address validation (`NewToken`, `Retry`) or generating +/// tokens for address validation (`Server`). +enum AddressValidationInfo { + None, + // We are a client and have information from `NEW_TOKEN`. + NewToken(Vec<u8>), + // We are a client and have received a `Retry` packet. + Retry { + token: Vec<u8>, + retry_source_cid: ConnectionId, + }, + // We are a server and can generate tokens. + Server(Weak<RefCell<AddressValidation>>), +} + +impl AddressValidationInfo { + pub fn token(&self) -> &[u8] { + match self { + Self::NewToken(token) | Self::Retry { token, .. } => token, + _ => &[], + } + } + + pub fn generate_new_token( + &mut self, + peer_address: SocketAddr, + now: Instant, + ) -> Option<Vec<u8>> { + match self { + Self::Server(ref w) => { + if let Some(validation) = w.upgrade() { + validation + .borrow() + .generate_new_token(peer_address, now) + .ok() + } else { + None + } + } + Self::None => None, + _ => unreachable!("called a server function on a client"), + } + } +} + +/// A QUIC Connection +/// +/// First, create a new connection using `new_client()` or `new_server()`. +/// +/// For the life of the connection, handle activity in the following manner: +/// 1. Perform operations using the `stream_*()` methods. +/// 1. Call `process_input()` when a datagram is received or the timer +/// expires. Obtain information on connection state changes by checking +/// `events()`. +/// 1. Having completed handling current activity, repeatedly call +/// `process_output()` for packets to send, until it returns `Output::Callback` +/// or `Output::None`. +/// +/// After the connection is closed (either by calling `close()` or by the +/// remote) continue processing until `state()` returns `Closed`. +pub struct Connection { + role: Role, + version: Version, + state: State, + tps: Rc<RefCell<TransportParametersHandler>>, + /// What we are doing with 0-RTT. + zero_rtt_state: ZeroRttState, + /// All of the network paths that we are aware of. + paths: Paths, + /// This object will generate connection IDs for the connection. + cid_manager: ConnectionIdManager, + address_validation: AddressValidationInfo, + /// The connection IDs that were provided by the peer. + connection_ids: ConnectionIdStore<[u8; 16]>, + + /// The source connection ID that this endpoint uses for the handshake. + /// Since we need to communicate this to our peer in tparams, setting this + /// value is part of constructing the struct. + local_initial_source_cid: ConnectionId, + /// The source connection ID from the first packet from the other end. + /// This is checked against the peer's transport parameters. + remote_initial_source_cid: Option<ConnectionId>, + /// The destination connection ID from the first packet from the client. + /// This is checked by the client against the server's transport parameters. + original_destination_cid: Option<ConnectionId>, + + /// We sometimes save a datagram against the possibility that keys will later + /// become available. This avoids reporting packets as dropped during the handshake + /// when they are either just reordered or we haven't been able to install keys yet. + /// In particular, this occurs when asynchronous certificate validation happens. + saved_datagrams: SavedDatagrams, + /// Some packets were received, but not tracked. + received_untracked: bool, + + /// This is responsible for the QuicDatagrams' handling: + /// <https://datatracker.ietf.org/doc/html/draft-ietf-quic-datagram> + quic_datagrams: QuicDatagrams, + + pub(crate) crypto: Crypto, + pub(crate) acks: AckTracker, + idle_timeout: IdleTimeout, + streams: Streams, + state_signaling: StateSignaling, + loss_recovery: LossRecovery, + events: ConnectionEvents, + new_token: NewTokenState, + stats: StatsCell, + qlog: NeqoQlog, + /// A session ticket was received without NEW_TOKEN, + /// this is when that turns into an event without NEW_TOKEN. + release_resumption_token_timer: Option<Instant>, + conn_params: ConnectionParameters, + hrtime: hrtime::Handle, + + /// For testing purposes it is sometimes necessary to inject frames that wouldn't + /// otherwise be sent, just to see how a connection handles them. Inserting them + /// into packets proper mean that the frames follow the entire processing path. + #[cfg(test)] + pub test_frame_writer: Option<Box<dyn test_internal::FrameWriter>>, +} + +impl Debug for Connection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:?} Connection: {:?} {:?}", + self.role, + self.state, + self.paths.primary_fallible() + ) + } +} + +impl Connection { + /// A long default for timer resolution, so that we don't tax the + /// system too hard when we don't need to. + const LOOSE_TIMER_RESOLUTION: Duration = Duration::from_millis(50); + + /// Create a new QUIC connection with Client role. + pub fn new_client( + server_name: impl Into<String>, + protocols: &[impl AsRef<str>], + cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>, + local_addr: SocketAddr, + remote_addr: SocketAddr, + conn_params: ConnectionParameters, + now: Instant, + ) -> Res<Self> { + let dcid = ConnectionId::generate_initial(); + let mut c = Self::new( + Role::Client, + Agent::from(Client::new(server_name.into())?), + cid_generator, + protocols, + conn_params, + )?; + c.crypto.states.init( + c.conn_params.get_versions().compatible(), + Role::Client, + &dcid, + ); + c.original_destination_cid = Some(dcid); + let path = Path::temporary( + local_addr, + remote_addr, + c.conn_params.get_cc_algorithm(), + NeqoQlog::default(), + now, + ); + c.setup_handshake_path(&Rc::new(RefCell::new(path)), now); + Ok(c) + } + + /// Create a new QUIC connection with Server role. + pub fn new_server( + certs: &[impl AsRef<str>], + protocols: &[impl AsRef<str>], + cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>, + conn_params: ConnectionParameters, + ) -> Res<Self> { + Self::new( + Role::Server, + Agent::from(Server::new(certs)?), + cid_generator, + protocols, + conn_params, + ) + } + + fn new<P: AsRef<str>>( + role: Role, + agent: Agent, + cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>, + protocols: &[P], + conn_params: ConnectionParameters, + ) -> Res<Self> { + // Setup the local connection ID. + let local_initial_source_cid = cid_generator + .borrow_mut() + .generate_cid() + .ok_or(Error::ConnectionIdsExhausted)?; + let mut cid_manager = + ConnectionIdManager::new(cid_generator, local_initial_source_cid.clone()); + let mut tps = conn_params.create_transport_parameter(role, &mut cid_manager)?; + tps.local.set_bytes( + tparams::INITIAL_SOURCE_CONNECTION_ID, + local_initial_source_cid.to_vec(), + ); + + let tphandler = Rc::new(RefCell::new(tps)); + let crypto = Crypto::new( + conn_params.get_versions().initial(), + agent, + protocols.iter().map(P::as_ref).map(String::from).collect(), + Rc::clone(&tphandler), + )?; + + let stats = StatsCell::default(); + let events = ConnectionEvents::default(); + let quic_datagrams = QuicDatagrams::new( + conn_params.get_datagram_size(), + conn_params.get_outgoing_datagram_queue(), + conn_params.get_incoming_datagram_queue(), + events.clone(), + ); + + let c = Self { + role, + version: conn_params.get_versions().initial(), + state: State::Init, + paths: Paths::default(), + cid_manager, + tps: tphandler.clone(), + zero_rtt_state: ZeroRttState::Init, + address_validation: AddressValidationInfo::None, + local_initial_source_cid, + remote_initial_source_cid: None, + original_destination_cid: None, + saved_datagrams: SavedDatagrams::default(), + received_untracked: false, + crypto, + acks: AckTracker::default(), + idle_timeout: IdleTimeout::new(conn_params.get_idle_timeout()), + streams: Streams::new(tphandler, role, events.clone()), + connection_ids: ConnectionIdStore::default(), + state_signaling: StateSignaling::Idle, + loss_recovery: LossRecovery::new(stats.clone(), conn_params.get_fast_pto()), + events, + new_token: NewTokenState::new(role), + stats, + qlog: NeqoQlog::disabled(), + release_resumption_token_timer: None, + conn_params, + hrtime: hrtime::Time::get(Self::LOOSE_TIMER_RESOLUTION), + quic_datagrams, + #[cfg(test)] + test_frame_writer: None, + }; + c.stats.borrow_mut().init(format!("{}", c)); + Ok(c) + } + + pub fn server_enable_0rtt( + &mut self, + anti_replay: &AntiReplay, + zero_rtt_checker: impl ZeroRttChecker + 'static, + ) -> Res<()> { + self.crypto + .server_enable_0rtt(self.tps.clone(), anti_replay, zero_rtt_checker) + } + + pub fn server_enable_ech( + &mut self, + config: u8, + public_name: &str, + sk: &PrivateKey, + pk: &PublicKey, + ) -> Res<()> { + self.crypto.server_enable_ech(config, public_name, sk, pk) + } + + /// Get the active ECH configuration, which is empty if ECH is disabled. + pub fn ech_config(&self) -> &[u8] { + self.crypto.ech_config() + } + + pub fn client_enable_ech(&mut self, ech_config_list: impl AsRef<[u8]>) -> Res<()> { + self.crypto.client_enable_ech(ech_config_list) + } + + /// Set or clear the qlog for this connection. + pub fn set_qlog(&mut self, qlog: NeqoQlog) { + self.loss_recovery.set_qlog(qlog.clone()); + self.paths.set_qlog(qlog.clone()); + self.qlog = qlog; + } + + /// Get the qlog (if any) for this connection. + pub fn qlog_mut(&mut self) -> &mut NeqoQlog { + &mut self.qlog + } + + /// Get the original destination connection id for this connection. This + /// will always be present for Role::Client but not if Role::Server is in + /// State::Init. + pub fn odcid(&self) -> Option<&ConnectionId> { + self.original_destination_cid.as_ref() + } + + /// Set a local transport parameter, possibly overriding a default value. + /// This only sets transport parameters without dealing with other aspects of + /// setting the value. + /// # Panics + /// This panics if the transport parameter is known to this crate. + pub fn set_local_tparam(&self, tp: TransportParameterId, value: TransportParameter) -> Res<()> { + #[cfg(not(test))] + { + assert!(!tparams::INTERNAL_TRANSPORT_PARAMETERS.contains(&tp)); + } + if *self.state() == State::Init { + self.tps.borrow_mut().local.set(tp, value); + Ok(()) + } else { + qerror!("Current state: {:?}", self.state()); + qerror!("Cannot set local tparam when not in an initial connection state."); + Err(Error::ConnectionState) + } + } + + /// `odcid` is their original choice for our CID, which we get from the Retry token. + /// `remote_cid` is the value from the Source Connection ID field of + /// an incoming packet: what the peer wants us to use now. + /// `retry_cid` is what we asked them to use when we sent the Retry. + pub(crate) fn set_retry_cids( + &mut self, + odcid: ConnectionId, + remote_cid: ConnectionId, + retry_cid: ConnectionId, + ) { + debug_assert_eq!(self.role, Role::Server); + qtrace!( + [self], + "Retry CIDs: odcid={} remote={} retry={}", + odcid, + remote_cid, + retry_cid + ); + // We advertise "our" choices in transport parameters. + let local_tps = &mut self.tps.borrow_mut().local; + local_tps.set_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID, odcid.to_vec()); + local_tps.set_bytes(tparams::RETRY_SOURCE_CONNECTION_ID, retry_cid.to_vec()); + + // ...and save their choices for later validation. + self.remote_initial_source_cid = Some(remote_cid); + } + + fn retry_sent(&self) -> bool { + self.tps + .borrow() + .local + .get_bytes(tparams::RETRY_SOURCE_CONNECTION_ID) + .is_some() + } + + /// Set ALPN preferences. Strings that appear earlier in the list are given + /// higher preference. + pub fn set_alpn(&mut self, protocols: &[impl AsRef<str>]) -> Res<()> { + self.crypto.tls.set_alpn(protocols)?; + Ok(()) + } + + /// Enable a set of ciphers. + pub fn set_ciphers(&mut self, ciphers: &[Cipher]) -> Res<()> { + if self.state != State::Init { + qerror!([self], "Cannot enable ciphers in state {:?}", self.state); + return Err(Error::ConnectionState); + } + self.crypto.tls.set_ciphers(ciphers)?; + Ok(()) + } + + fn make_resumption_token(&mut self) -> ResumptionToken { + debug_assert_eq!(self.role, Role::Client); + debug_assert!(self.crypto.has_resumption_token()); + let rtt = self.paths.primary().borrow().rtt().estimate(); + self.crypto + .create_resumption_token( + self.new_token.take_token(), + self.tps + .borrow() + .remote + .as_ref() + .expect("should have transport parameters"), + self.version, + u64::try_from(rtt.as_millis()).unwrap_or(0), + ) + .unwrap() + } + + /// Get the simplest PTO calculation for all those cases where we need + /// a value of this approximate order. Don't use this for loss recovery, + /// only use it where a more precise value is not important. + fn pto(&self) -> Duration { + self.paths + .primary() + .borrow() + .rtt() + .pto(PacketNumberSpace::ApplicationData) + } + + fn create_resumption_token(&mut self, now: Instant) { + if self.role == Role::Server || self.state < State::Connected { + return; + } + + qtrace!( + [self], + "Maybe create resumption token: {} {}", + self.crypto.has_resumption_token(), + self.new_token.has_token() + ); + + while self.crypto.has_resumption_token() && self.new_token.has_token() { + let token = self.make_resumption_token(); + self.events.client_resumption_token(token); + } + + // If we have a resumption ticket check or set a timer. + if self.crypto.has_resumption_token() { + let arm = if let Some(expiration_time) = self.release_resumption_token_timer { + if expiration_time <= now { + let token = self.make_resumption_token(); + self.events.client_resumption_token(token); + self.release_resumption_token_timer = None; + + // This means that we release one session ticket every 3 PTOs + // if no NEW_TOKEN frame is received. + self.crypto.has_resumption_token() + } else { + false + } + } else { + true + }; + + if arm { + self.release_resumption_token_timer = Some(now + 3 * self.pto()); + } + } + } + + /// The correct way to obtain a resumption token is to wait for the + /// `ConnectionEvent::ResumptionToken` event. To emit the event we are waiting for a + /// resumption token and a `NEW_TOKEN` frame to arrive. Some servers don't send `NEW_TOKEN` + /// frames and in this case, we wait for 3xPTO before emitting an event. This is especially a + /// problem for short-lived connections, where the connection is closed before any events are + /// released. This function retrieves the token, without waiting for a `NEW_TOKEN` frame to + /// arrive. + /// # Panics + /// If this is called on a server. + pub fn take_resumption_token(&mut self, now: Instant) -> Option<ResumptionToken> { + assert_eq!(self.role, Role::Client); + + if self.crypto.has_resumption_token() { + let token = self.make_resumption_token(); + if self.crypto.has_resumption_token() { + self.release_resumption_token_timer = Some(now + 3 * self.pto()); + } + Some(token) + } else { + None + } + } + + /// Enable resumption, using a token previously provided. + /// This can only be called once and only on the client. + /// After calling the function, it should be possible to attempt 0-RTT + /// if the token supports that. + pub fn enable_resumption(&mut self, now: Instant, token: impl AsRef<[u8]>) -> Res<()> { + if self.state != State::Init { + qerror!([self], "set token in state {:?}", self.state); + return Err(Error::ConnectionState); + } + if self.role == Role::Server { + return Err(Error::ConnectionState); + } + + qinfo!( + [self], + "resumption token {}", + hex_snip_middle(token.as_ref()) + ); + let mut dec = Decoder::from(token.as_ref()); + + let version = + Version::try_from(dec.decode_uint(4).ok_or(Error::InvalidResumptionToken)? as u32)?; + qtrace!([self], " version {:?}", version); + if !self.conn_params.get_versions().all().contains(&version) { + return Err(Error::DisabledVersion); + } + + let rtt = Duration::from_millis(dec.decode_varint().ok_or(Error::InvalidResumptionToken)?); + qtrace!([self], " RTT {:?}", rtt); + + let tp_slice = dec.decode_vvec().ok_or(Error::InvalidResumptionToken)?; + qtrace!([self], " transport parameters {}", hex(tp_slice)); + let mut dec_tp = Decoder::from(tp_slice); + let tp = + TransportParameters::decode(&mut dec_tp).map_err(|_| Error::InvalidResumptionToken)?; + + let init_token = dec.decode_vvec().ok_or(Error::InvalidResumptionToken)?; + qtrace!([self], " Initial token {}", hex(init_token)); + + let tok = dec.decode_remainder(); + qtrace!([self], " TLS token {}", hex(tok)); + + match self.crypto.tls { + Agent::Client(ref mut c) => { + let res = c.enable_resumption(tok); + if let Err(e) = res { + self.absorb_error::<Error>(now, Err(Error::from(e))); + return Ok(()); + } + } + Agent::Server(_) => return Err(Error::WrongRole), + } + + self.version = version; + self.conn_params.get_versions_mut().set_initial(version); + self.tps.borrow_mut().set_version(version); + self.tps.borrow_mut().remote_0rtt = Some(tp); + if !init_token.is_empty() { + self.address_validation = AddressValidationInfo::NewToken(init_token.to_vec()); + } + self.paths.primary().borrow_mut().rtt_mut().set_initial(rtt); + self.set_initial_limits(); + // Start up TLS, which has the effect of setting up all the necessary + // state for 0-RTT. This only stages the CRYPTO frames. + let res = self.client_start(now); + self.absorb_error(now, res); + Ok(()) + } + + pub(crate) fn set_validation(&mut self, validation: Rc<RefCell<AddressValidation>>) { + qtrace!([self], "Enabling NEW_TOKEN"); + assert_eq!(self.role, Role::Server); + self.address_validation = AddressValidationInfo::Server(Rc::downgrade(&validation)); + } + + /// Send a TLS session ticket AND a NEW_TOKEN frame (if possible). + pub fn send_ticket(&mut self, now: Instant, extra: &[u8]) -> Res<()> { + if self.role == Role::Client { + return Err(Error::WrongRole); + } + + let tps = &self.tps; + if let Agent::Server(ref mut s) = self.crypto.tls { + let mut enc = Encoder::default(); + enc.encode_vvec_with(|enc_inner| { + tps.borrow().local.encode(enc_inner); + }); + enc.encode(extra); + let records = s.send_ticket(now, enc.as_ref())?; + qinfo!([self], "send session ticket {}", hex(&enc)); + self.crypto.buffer_records(records)?; + } else { + unreachable!(); + } + + // If we are able, also send a NEW_TOKEN frame. + // This should be recording all remote addresses that are valid, + // but there are just 0 or 1 in the current implementation. + if let Some(path) = self.paths.primary_fallible() { + if let Some(token) = self + .address_validation + .generate_new_token(path.borrow().remote_address(), now) + { + self.new_token.send_new_token(token); + } + Ok(()) + } else { + Err(Error::NotConnected) + } + } + + pub fn tls_info(&self) -> Option<&SecretAgentInfo> { + self.crypto.tls.info() + } + + pub fn tls_preinfo(&self) -> Res<SecretAgentPreInfo> { + Ok(self.crypto.tls.preinfo()?) + } + + /// Get the peer's certificate chain and other info. + pub fn peer_certificate(&self) -> Option<CertificateInfo> { + self.crypto.tls.peer_certificate() + } + + /// Call by application when the peer cert has been verified. + /// + /// This panics if there is no active peer. It's OK to call this + /// when authentication isn't needed, that will likely only cause + /// the connection to fail. However, if no packets have been + /// exchanged, it's not OK. + pub fn authenticated(&mut self, status: AuthenticationStatus, now: Instant) { + qinfo!([self], "Authenticated {:?}", status); + self.crypto.tls.authenticated(status); + let res = self.handshake(now, self.version, PacketNumberSpace::Handshake, None); + self.absorb_error(now, res); + self.process_saved(now); + } + + /// Get the role of the connection. + pub fn role(&self) -> Role { + self.role + } + + /// Get the state of the connection. + pub fn state(&self) -> &State { + &self.state + } + + /// The QUIC version in use. + pub fn version(&self) -> Version { + self.version + } + + /// Get the 0-RTT state of the connection. + pub fn zero_rtt_state(&self) -> ZeroRttState { + self.zero_rtt_state + } + + /// Get a snapshot of collected statistics. + pub fn stats(&self) -> Stats { + let mut v = self.stats.borrow().clone(); + if let Some(p) = self.paths.primary_fallible() { + let p = p.borrow(); + v.rtt = p.rtt().estimate(); + v.rttvar = p.rtt().rttvar(); + } + v + } + + // This function wraps a call to another function and sets the connection state + // properly if that call fails. + fn capture_error<T>( + &mut self, + path: Option<PathRef>, + now: Instant, + frame_type: FrameType, + res: Res<T>, + ) -> Res<T> { + if let Err(v) = &res { + #[cfg(debug_assertions)] + let msg = format!("{:?}", v); + #[cfg(not(debug_assertions))] + let msg = ""; + let error = ConnectionError::Transport(v.clone()); + match &self.state { + State::Closing { error: err, .. } + | State::Draining { error: err, .. } + | State::Closed(err) => { + qwarn!([self], "Closing again after error {:?}", err); + } + State::Init => { + // We have not even sent anything just close the connection without sending any error. + // This may happen when client_start fails. + self.set_state(State::Closed(error)); + } + State::WaitInitial => { + // We don't have any state yet, so don't bother with + // the closing state, just send one CONNECTION_CLOSE. + if let Some(path) = path.or_else(|| self.paths.primary_fallible()) { + self.state_signaling + .close(path, error.clone(), frame_type, msg); + } + self.set_state(State::Closed(error)); + } + _ => { + if let Some(path) = path.or_else(|| self.paths.primary_fallible()) { + self.state_signaling + .close(path, error.clone(), frame_type, msg); + if matches!(v, Error::KeysExhausted) { + self.set_state(State::Closed(error)); + } else { + self.set_state(State::Closing { + error, + timeout: self.get_closing_period_time(now), + }); + } + } else { + self.set_state(State::Closed(error)); + } + } + } + } + res + } + + /// For use with process_input(). Errors there can be ignored, but this + /// needs to ensure that the state is updated. + fn absorb_error<T>(&mut self, now: Instant, res: Res<T>) -> Option<T> { + self.capture_error(None, now, 0, res).ok() + } + + fn process_timer(&mut self, now: Instant) { + match &self.state { + // Only the client runs timers while waiting for Initial packets. + State::WaitInitial => debug_assert_eq!(self.role, Role::Client), + // If Closing or Draining, check if it is time to move to Closed. + State::Closing { error, timeout } | State::Draining { error, timeout } => { + if *timeout <= now { + let st = State::Closed(error.clone()); + self.set_state(st); + qinfo!("Closing timer expired"); + return; + } + } + State::Closed(_) => { + qdebug!("Timer fired while closed"); + return; + } + _ => (), + } + + let pto = self.pto(); + if self.idle_timeout.expired(now, pto) { + qinfo!([self], "idle timeout expired"); + self.set_state(State::Closed(ConnectionError::Transport( + Error::IdleTimeout, + ))); + return; + } + + self.streams.cleanup_closed_streams(); + + let res = self.crypto.states.check_key_update(now); + self.absorb_error(now, res); + + let lost = self.loss_recovery.timeout(&self.paths.primary(), now); + self.handle_lost_packets(&lost); + qlog::packets_lost(&mut self.qlog, &lost); + + if self.release_resumption_token_timer.is_some() { + self.create_resumption_token(now); + } + + if !self.paths.process_timeout(now, pto) { + qinfo!([self], "last available path failed"); + self.absorb_error::<Error>(now, Err(Error::NoAvailablePath)); + } + } + + /// Process new input datagrams on the connection. + pub fn process_input(&mut self, d: Datagram, now: Instant) { + self.input(d, now, now); + self.process_saved(now); + self.streams.cleanup_closed_streams(); + } + + /// Get the time that we next need to be called back, relative to `now`. + fn next_delay(&mut self, now: Instant, paced: bool) -> Duration { + qtrace!([self], "Get callback delay {:?}", now); + + // Only one timer matters when closing... + if let State::Closing { timeout, .. } | State::Draining { timeout, .. } = self.state { + self.hrtime.update(Self::LOOSE_TIMER_RESOLUTION); + return timeout.duration_since(now); + } + + let mut delays = SmallVec::<[_; 6]>::new(); + if let Some(ack_time) = self.acks.ack_time(now) { + qtrace!([self], "Delayed ACK timer {:?}", ack_time); + delays.push(ack_time); + } + + if let Some(p) = self.paths.primary_fallible() { + let path = p.borrow(); + let rtt = path.rtt(); + let pto = rtt.pto(PacketNumberSpace::ApplicationData); + + let keep_alive = self.streams.need_keep_alive(); + let idle_time = self.idle_timeout.expiry(now, pto, keep_alive); + qtrace!([self], "Idle/keepalive timer {:?}", idle_time); + delays.push(idle_time); + + if let Some(lr_time) = self.loss_recovery.next_timeout(rtt) { + qtrace!([self], "Loss recovery timer {:?}", lr_time); + delays.push(lr_time); + } + + if paced { + if let Some(pace_time) = path.sender().next_paced(rtt.estimate()) { + qtrace!([self], "Pacing timer {:?}", pace_time); + delays.push(pace_time); + } + } + + if let Some(path_time) = self.paths.next_timeout(pto) { + qtrace!([self], "Path probe timer {:?}", path_time); + delays.push(path_time); + } + } + + if let Some(key_update_time) = self.crypto.states.update_time() { + qtrace!([self], "Key update timer {:?}", key_update_time); + delays.push(key_update_time); + } + + // `release_resumption_token_timer` is not considered here, because + // it is not important enough to force the application to set a + // timeout for it It is expected that other activities will + // drive it. + + let earliest = delays.into_iter().min().unwrap(); + // TODO(agrover, mt) - need to analyze and fix #47 + // rather than just clamping to zero here. + debug_assert!(earliest > now); + let delay = earliest.saturating_duration_since(now); + qdebug!([self], "delay duration {:?}", delay); + self.hrtime.update(delay / 4); + delay + } + + /// Get output packets, as a result of receiving packets, or actions taken + /// by the application. + /// Returns datagrams to send, and how long to wait before calling again + /// even if no incoming packets. + #[must_use = "Output of the process_output function must be handled"] + pub fn process_output(&mut self, now: Instant) -> Output { + qtrace!([self], "process_output {:?} {:?}", self.state, now); + + match (&self.state, self.role) { + (State::Init, Role::Client) => { + let res = self.client_start(now); + self.absorb_error(now, res); + } + (State::Init, Role::Server) | (State::WaitInitial, Role::Server) => { + return Output::None; + } + _ => { + self.process_timer(now); + } + } + + match self.output(now) { + SendOption::Yes(dgram) => Output::Datagram(dgram), + SendOption::No(paced) => match self.state { + State::Init | State::Closed(_) => Output::None, + State::Closing { timeout, .. } | State::Draining { timeout, .. } => { + Output::Callback(timeout.duration_since(now)) + } + _ => Output::Callback(self.next_delay(now, paced)), + }, + } + } + + /// Process input and generate output. + #[must_use = "Output of the process function must be handled"] + pub fn process(&mut self, dgram: Option<Datagram>, now: Instant) -> Output { + if let Some(d) = dgram { + self.input(d, now, now); + self.process_saved(now); + } + self.process_output(now) + } + + fn handle_retry(&mut self, packet: &PublicPacket, now: Instant) { + qinfo!([self], "received Retry"); + if matches!(self.address_validation, AddressValidationInfo::Retry { .. }) { + self.stats.borrow_mut().pkt_dropped("Extra Retry"); + return; + } + if packet.token().is_empty() { + self.stats.borrow_mut().pkt_dropped("Retry without a token"); + return; + } + if !packet.is_valid_retry(self.original_destination_cid.as_ref().unwrap()) { + self.stats + .borrow_mut() + .pkt_dropped("Retry with bad integrity tag"); + return; + } + // At this point, we should only have the connection ID that we generated. + // Update to the one that the server prefers. + let path = self.paths.primary(); + path.borrow_mut().set_remote_cid(packet.scid()); + + let retry_scid = ConnectionId::from(packet.scid()); + qinfo!( + [self], + "Valid Retry received, token={} scid={}", + hex(packet.token()), + retry_scid + ); + + let lost_packets = self.loss_recovery.retry(&path, now); + self.handle_lost_packets(&lost_packets); + + self.crypto.states.init( + self.conn_params.get_versions().compatible(), + self.role, + &retry_scid, + ); + self.address_validation = AddressValidationInfo::Retry { + token: packet.token().to_vec(), + retry_source_cid: retry_scid, + }; + } + + fn discard_keys(&mut self, space: PacketNumberSpace, now: Instant) { + if self.crypto.discard(space) { + qinfo!([self], "Drop packet number space {}", space); + let primary = self.paths.primary(); + self.loss_recovery.discard(&primary, space, now); + self.acks.drop_space(space); + } + } + + fn is_stateless_reset(&self, path: &PathRef, d: &Datagram) -> bool { + // If the datagram is too small, don't try. + // If the connection is connected, then the reset token will be invalid. + if d.len() < 16 || !self.state.connected() { + return false; + } + let token = <&[u8; 16]>::try_from(&d[d.len() - 16..]).unwrap(); + path.borrow().is_stateless_reset(token) + } + + fn check_stateless_reset( + &mut self, + path: &PathRef, + d: &Datagram, + first: bool, + now: Instant, + ) -> Res<()> { + if first && self.is_stateless_reset(path, d) { + // Failing to process a packet in a datagram might + // indicate that there is a stateless reset present. + qdebug!([self], "Stateless reset: {}", hex(&d[d.len() - 16..])); + self.state_signaling.reset(); + self.set_state(State::Draining { + error: ConnectionError::Transport(Error::StatelessReset), + timeout: self.get_closing_period_time(now), + }); + Err(Error::StatelessReset) + } else { + Ok(()) + } + } + + /// Process any saved datagrams that might be available for processing. + fn process_saved(&mut self, now: Instant) { + while let Some(cspace) = self.saved_datagrams.available() { + qdebug!([self], "process saved for space {:?}", cspace); + debug_assert!(self.crypto.states.rx_hp(self.version, cspace).is_some()); + for saved in self.saved_datagrams.take_saved() { + qtrace!([self], "input saved @{:?}: {:?}", saved.t, saved.d); + self.input(saved.d, saved.t, now); + } + } + } + + /// In case a datagram arrives that we can only partially process, save any + /// part that we don't have keys for. + fn save_datagram(&mut self, cspace: CryptoSpace, d: Datagram, remaining: usize, now: Instant) { + let d = if remaining < d.len() { + Datagram::new(d.source(), d.destination(), &d[d.len() - remaining..]) + } else { + d + }; + self.saved_datagrams.save(cspace, d, now); + self.stats.borrow_mut().saved_datagrams += 1; + } + + /// Perform version negotiation. + fn version_negotiation(&mut self, supported: &[WireVersion], now: Instant) -> Res<()> { + debug_assert_eq!(self.role, Role::Client); + + if let Some(version) = self.conn_params.get_versions().preferred(supported) { + assert_ne!(self.version, version); + + qinfo!([self], "Version negotiation: trying {:?}", version); + let local_addr = self.paths.primary().borrow().local_address(); + let remote_addr = self.paths.primary().borrow().remote_address(); + let conn_params = self + .conn_params + .clone() + .versions(version, self.conn_params.get_versions().all().to_vec()); + let mut c = Self::new_client( + self.crypto.server_name().unwrap(), + self.crypto.protocols(), + self.cid_manager.generator(), + local_addr, + remote_addr, + conn_params, + now, + )?; + c.conn_params + .get_versions_mut() + .set_initial(self.conn_params.get_versions().initial()); + mem::swap(self, &mut c); + Ok(()) + } else { + qinfo!([self], "Version negotiation: failed with {:?}", supported); + // This error goes straight to closed. + self.set_state(State::Closed(ConnectionError::Transport( + Error::VersionNegotiation, + ))); + Err(Error::VersionNegotiation) + } + } + + /// Perform any processing that we might have to do on packets prior to + /// attempting to remove protection. + fn preprocess_packet( + &mut self, + packet: &PublicPacket, + path: &PathRef, + dcid: Option<&ConnectionId>, + now: Instant, + ) -> Res<PreprocessResult> { + if dcid.map_or(false, |d| d != packet.dcid()) { + self.stats + .borrow_mut() + .pkt_dropped("Coalesced packet has different DCID"); + return Ok(PreprocessResult::Next); + } + + if (packet.packet_type() == PacketType::Initial + || packet.packet_type() == PacketType::Handshake) + && self.role == Role::Client + && !path.borrow().is_primary() + { + // If we have received a packet from a different address than we have sent to + // we should ignore the packet. In such a case a path will be a newly created + // temporary path, not the primary path. + return Ok(PreprocessResult::Next); + } + + match (packet.packet_type(), &self.state, &self.role) { + (PacketType::Initial, State::Init, Role::Server) => { + let version = *packet.version().as_ref().unwrap(); + if !packet.is_valid_initial() + || !self.conn_params.get_versions().all().contains(&version) + { + self.stats.borrow_mut().pkt_dropped("Invalid Initial"); + return Ok(PreprocessResult::Next); + } + qinfo!( + [self], + "Received valid Initial packet with scid {:?} dcid {:?}", + packet.scid(), + packet.dcid() + ); + // Record the client's selected CID so that it can be accepted until + // the client starts using a real connection ID. + let dcid = ConnectionId::from(packet.dcid()); + self.crypto.states.init_server(version, &dcid); + self.original_destination_cid = Some(dcid); + self.set_state(State::WaitInitial); + + // We need to make sure that we set this transport parameter. + // This has to happen prior to processing the packet so that + // the TLS handshake has all it needs. + if !self.retry_sent() { + self.tps.borrow_mut().local.set_bytes( + tparams::ORIGINAL_DESTINATION_CONNECTION_ID, + packet.dcid().to_vec(), + ) + } + } + (PacketType::VersionNegotiation, State::WaitInitial, Role::Client) => { + match packet.supported_versions() { + Ok(versions) => { + if versions.is_empty() + || versions.contains(&self.version().wire_version()) + || versions.contains(&0) + || packet.scid() != self.odcid().unwrap() + || matches!( + self.address_validation, + AddressValidationInfo::Retry { .. } + ) + { + // Ignore VersionNegotiation packets that contain the current version. + // Or don't have the right connection ID. + // Or are received after a Retry. + self.stats.borrow_mut().pkt_dropped("Invalid VN"); + return Ok(PreprocessResult::End); + } + + self.version_negotiation(&versions, now)?; + return Ok(PreprocessResult::End); + } + Err(_) => { + self.stats.borrow_mut().pkt_dropped("VN with no versions"); + return Ok(PreprocessResult::End); + } + } + } + (PacketType::Retry, State::WaitInitial, Role::Client) => { + self.handle_retry(packet, now); + return Ok(PreprocessResult::Next); + } + (PacketType::Handshake, State::WaitInitial, Role::Client) + | (PacketType::Short, State::WaitInitial, Role::Client) => { + // This packet can't be processed now, but it could be a sign + // that Initial packets were lost. + // Resend Initial CRYPTO frames immediately a few times just + // in case. As we don't have an RTT estimate yet, this helps + // when there is a short RTT and losses. + if dcid.is_none() + && self.cid_manager.is_valid(packet.dcid()) + && self.stats.borrow().saved_datagrams <= EXTRA_INITIALS + { + self.crypto.resend_unacked(PacketNumberSpace::Initial); + } + } + (PacketType::VersionNegotiation, ..) + | (PacketType::Retry, ..) + | (PacketType::OtherVersion, ..) => { + self.stats + .borrow_mut() + .pkt_dropped(format!("{:?}", packet.packet_type())); + return Ok(PreprocessResult::Next); + } + _ => {} + } + + let res = match self.state { + State::Init => { + self.stats + .borrow_mut() + .pkt_dropped("Received while in Init state"); + PreprocessResult::Next + } + State::WaitInitial => PreprocessResult::Continue, + State::WaitVersion | State::Handshaking | State::Connected | State::Confirmed => { + if !self.cid_manager.is_valid(packet.dcid()) { + self.stats + .borrow_mut() + .pkt_dropped(format!("Invalid DCID {:?}", packet.dcid())); + PreprocessResult::Next + } else { + if self.role == Role::Server && packet.packet_type() == PacketType::Handshake { + // Server has received a Handshake packet -> discard Initial keys and states + self.discard_keys(PacketNumberSpace::Initial, now); + } + PreprocessResult::Continue + } + } + State::Closing { .. } => { + // Don't bother processing the packet. Instead ask to get a + // new close frame. + self.state_signaling.send_close(); + PreprocessResult::Next + } + State::Draining { .. } | State::Closed(..) => { + // Do nothing. + self.stats + .borrow_mut() + .pkt_dropped(format!("State {:?}", self.state)); + PreprocessResult::Next + } + }; + Ok(res) + } + + /// After a Initial, Handshake, ZeroRtt, or Short packet is successfully processed. + fn postprocess_packet( + &mut self, + path: &PathRef, + d: &Datagram, + packet: &PublicPacket, + migrate: bool, + now: Instant, + ) { + if self.state == State::WaitInitial { + self.start_handshake(path, packet, now); + } + + if self.state.connected() { + self.handle_migration(path, d, migrate, now); + } else if self.role != Role::Client + && (packet.packet_type() == PacketType::Handshake + || (packet.dcid().len() >= 8 && packet.dcid() == &self.local_initial_source_cid)) + { + // We only allow one path during setup, so apply handshake + // path validation to this path. + path.borrow_mut().set_valid(now); + } + } + + /// Take a datagram as input. This reports an error if the packet was bad. + /// This takes two times: when the datagram was received, and the current time. + fn input(&mut self, d: Datagram, received: Instant, now: Instant) { + // First determine the path. + let path = self.paths.find_path_with_rebinding( + d.destination(), + d.source(), + self.conn_params.get_cc_algorithm(), + now, + ); + path.borrow_mut().add_received(d.len()); + let res = self.input_path(&path, d, received); + self.capture_error(Some(path), now, 0, res).ok(); + } + + fn input_path(&mut self, path: &PathRef, d: Datagram, now: Instant) -> Res<()> { + let mut slc = &d[..]; + let mut dcid = None; + + qtrace!([self], "{} input {}", path.borrow(), hex(&**d)); + let pto = path.borrow().rtt().pto(PacketNumberSpace::ApplicationData); + + // Handle each packet in the datagram. + while !slc.is_empty() { + self.stats.borrow_mut().packets_rx += 1; + let (packet, remainder) = + match PublicPacket::decode(slc, self.cid_manager.decoder().as_ref()) { + Ok((packet, remainder)) => (packet, remainder), + Err(e) => { + qinfo!([self], "Garbage packet: {}", e); + qtrace!([self], "Garbage packet contents: {}", hex(slc)); + self.stats.borrow_mut().pkt_dropped("Garbage packet"); + break; + } + }; + match self.preprocess_packet(&packet, path, dcid.as_ref(), now)? { + PreprocessResult::Continue => (), + PreprocessResult::Next => break, + PreprocessResult::End => return Ok(()), + } + + qtrace!([self], "Received unverified packet {:?}", packet); + + match packet.decrypt(&mut self.crypto.states, now + pto) { + Ok(payload) => { + // OK, we have a valid packet. + self.idle_timeout.on_packet_received(now); + dump_packet( + self, + path, + "-> RX", + payload.packet_type(), + payload.pn(), + &payload[..], + ); + + qlog::packet_received(&mut self.qlog, &packet, &payload); + let space = PacketNumberSpace::from(payload.packet_type()); + if self.acks.get_mut(space).unwrap().is_duplicate(payload.pn()) { + qdebug!([self], "Duplicate packet {}-{}", space, payload.pn()); + self.stats.borrow_mut().dups_rx += 1; + } else { + match self.process_packet(path, &payload, now) { + Ok(migrate) => self.postprocess_packet(path, &d, &packet, migrate, now), + Err(e) => { + self.ensure_error_path(path, &packet, now); + return Err(e); + } + } + } + } + Err(e) => { + match e { + Error::KeysPending(cspace) => { + // This packet can't be decrypted because we don't have the keys yet. + // Don't check this packet for a stateless reset, just return. + let remaining = slc.len(); + self.save_datagram(cspace, d, remaining, now); + return Ok(()); + } + Error::KeysExhausted => { + // Exhausting read keys is fatal. + return Err(e); + } + Error::KeysDiscarded(cspace) => { + // This was a valid-appearing Initial packet: maybe probe with + // a Handshake packet to keep the handshake moving. + self.received_untracked |= + self.role == Role::Client && cspace == CryptoSpace::Initial; + } + _ => (), + } + // Decryption failure, or not having keys is not fatal. + // If the state isn't available, or we can't decrypt the packet, drop + // the rest of the datagram on the floor, but don't generate an error. + self.check_stateless_reset(path, &d, dcid.is_none(), now)?; + self.stats.borrow_mut().pkt_dropped("Decryption failure"); + qlog::packet_dropped(&mut self.qlog, &packet); + } + } + slc = remainder; + dcid = Some(ConnectionId::from(packet.dcid())); + } + self.check_stateless_reset(path, &d, dcid.is_none(), now)?; + Ok(()) + } + + /// Process a packet. Returns true if the packet might initiate migration. + fn process_packet( + &mut self, + path: &PathRef, + packet: &DecryptedPacket, + now: Instant, + ) -> Res<bool> { + // TODO(ekr@rtfm.com): Have the server blow away the initial + // crypto state if this fails? Otherwise, we will get a panic + // on the assert for doesn't exist. + // OK, we have a valid packet. + + let mut ack_eliciting = false; + let mut probing = true; + let mut d = Decoder::from(&packet[..]); + let mut consecutive_padding = 0; + while d.remaining() > 0 { + let mut f = Frame::decode(&mut d)?; + + // Skip padding + while f == Frame::Padding && d.remaining() > 0 { + consecutive_padding += 1; + f = Frame::decode(&mut d)?; + } + if consecutive_padding > 0 { + qdebug!( + [self], + "PADDING frame repeated {} times", + consecutive_padding + ); + consecutive_padding = 0; + } + + ack_eliciting |= f.ack_eliciting(); + probing &= f.path_probing(); + let t = f.get_type(); + if let Err(e) = self.input_frame(path, packet.version(), packet.packet_type(), f, now) { + self.capture_error(Some(Rc::clone(path)), now, t, Err(e))?; + } + } + + let largest_received = if let Some(space) = self + .acks + .get_mut(PacketNumberSpace::from(packet.packet_type())) + { + space.set_received(now, packet.pn(), ack_eliciting) + } else { + qdebug!( + [self], + "processed a {:?} packet without tracking it", + packet.packet_type(), + ); + // This was a valid packet that caused the same packet number to be + // discarded. This happens when the client discards the Initial packet + // number space after receiving the ServerHello. Remember this so + // that we guarantee that we send a Handshake packet. + self.received_untracked = true; + // We don't migrate during the handshake, so return false. + false + }; + + Ok(largest_received && !probing) + } + + /// During connection setup, the first path needs to be setup. + /// This uses the connection IDs that were provided during the handshake + /// to setup that path. + #[allow(clippy::or_fun_call)] // Remove when MSRV >= 1.59 + fn setup_handshake_path(&mut self, path: &PathRef, now: Instant) { + self.paths.make_permanent( + path, + Some(self.local_initial_source_cid.clone()), + // Ideally we know what the peer wants us to use for the remote CID. + // But we will use our own guess if necessary. + ConnectionIdEntry::initial_remote( + self.remote_initial_source_cid + .as_ref() + .or(self.original_destination_cid.as_ref()) + .unwrap() + .clone(), + ), + ); + path.borrow_mut().set_valid(now); + } + + /// If the path isn't permanent, assign it a connection ID to make it so. + fn ensure_permanent(&mut self, path: &PathRef) -> Res<()> { + if self.paths.is_temporary(path) { + // If there isn't a connection ID to use for this path, the packet + // will be processed, but it won't be attributed to a path. That means + // no path probes or PATH_RESPONSE. But it's not fatal. + if let Some(cid) = self.connection_ids.next() { + self.paths.make_permanent(path, None, cid); + Ok(()) + } else if self.paths.primary().borrow().remote_cid().is_empty() { + self.paths + .make_permanent(path, None, ConnectionIdEntry::empty_remote()); + Ok(()) + } else { + qtrace!([self], "Unable to make path permanent: {}", path.borrow()); + Err(Error::InvalidMigration) + } + } else { + Ok(()) + } + } + + /// After an error, a permanent path is needed to send the CONNECTION_CLOSE. + /// This attempts to ensure that this exists. As the connection is now + /// temporary, there is no reason to do anything special here. + fn ensure_error_path(&mut self, path: &PathRef, packet: &PublicPacket, now: Instant) { + path.borrow_mut().set_valid(now); + if self.paths.is_temporary(path) { + // First try to fill in handshake details. + if packet.packet_type() == PacketType::Initial { + self.remote_initial_source_cid = Some(ConnectionId::from(packet.scid())); + self.setup_handshake_path(path, now); + } else { + // Otherwise try to get a usable connection ID. + mem::drop(self.ensure_permanent(path)); + } + } + } + + fn start_handshake(&mut self, path: &PathRef, packet: &PublicPacket, now: Instant) { + qtrace!([self], "starting handshake"); + debug_assert_eq!(packet.packet_type(), PacketType::Initial); + self.remote_initial_source_cid = Some(ConnectionId::from(packet.scid())); + + let got_version = if self.role == Role::Server { + self.cid_manager + .add_odcid(self.original_destination_cid.as_ref().unwrap().clone()); + // Make a path on which to run the handshake. + self.setup_handshake_path(path, now); + + self.zero_rtt_state = match self.crypto.enable_0rtt(self.version, self.role) { + Ok(true) => { + qdebug!([self], "Accepted 0-RTT"); + ZeroRttState::AcceptedServer + } + _ => ZeroRttState::Rejected, + }; + + // The server knows the final version if it has remote transport parameters. + self.tps.borrow().remote.is_some() + } else { + qdebug!([self], "Changing to use Server CID={}", packet.scid()); + debug_assert!(path.borrow().is_primary()); + path.borrow_mut().set_remote_cid(packet.scid()); + + // The client knows the final version if it processed a CRYPTO frame. + self.stats.borrow().frame_rx.crypto > 0 + }; + if got_version { + self.set_state(State::Handshaking); + } else { + self.set_state(State::WaitVersion); + } + } + + /// Migrate to the provided path. + /// Either local or remote address (but not both) may be provided as `None` to have + /// the address from the current primary path used. + /// If `force` is true, then migration is immediate. + /// Otherwise, migration occurs after the path is probed successfully. + /// Either way, the path is probed and will be abandoned if the probe fails. + /// + /// # Errors + /// Fails if this is not a client, not confirmed, or there are not enough connection + /// IDs available to use. + pub fn migrate( + &mut self, + local: Option<SocketAddr>, + remote: Option<SocketAddr>, + force: bool, + now: Instant, + ) -> Res<()> { + if self.role != Role::Client { + return Err(Error::InvalidMigration); + } + if !matches!(self.state(), State::Confirmed) { + return Err(Error::InvalidMigration); + } + + // Fill in the blanks, using the current primary path. + if local.is_none() && remote.is_none() { + // Pointless migration is pointless. + return Err(Error::InvalidMigration); + } + let local = local.unwrap_or_else(|| self.paths.primary().borrow().local_address()); + let remote = remote.unwrap_or_else(|| self.paths.primary().borrow().remote_address()); + + if mem::discriminant(&local.ip()) != mem::discriminant(&remote.ip()) { + // Can't mix address families. + return Err(Error::InvalidMigration); + } + if local.port() == 0 || remote.ip().is_unspecified() || remote.port() == 0 { + // All but the local address need to be specified. + return Err(Error::InvalidMigration); + } + if (local.ip().is_loopback() ^ remote.ip().is_loopback()) && !local.ip().is_unspecified() { + // Block attempts to migrate to a path with loopback on only one end, unless the local + // address is unspecified. + return Err(Error::InvalidMigration); + } + + let path = self + .paths + .find_path(local, remote, self.conn_params.get_cc_algorithm(), now); + self.ensure_permanent(&path)?; + qinfo!( + [self], + "Migrate to {} probe {}", + path.borrow(), + if force { "now" } else { "after" } + ); + if self.paths.migrate(&path, force, now) { + self.loss_recovery.migrate(); + } + Ok(()) + } + + fn migrate_to_preferred_address(&mut self, now: Instant) -> Res<()> { + let spa = if matches!( + self.conn_params.get_preferred_address(), + PreferredAddressConfig::Disabled + ) { + None + } else { + self.tps.borrow_mut().remote().get_preferred_address() + }; + if let Some((addr, cid)) = spa { + // The connection ID isn't special, so just save it. + self.connection_ids.add_remote(cid)?; + + // The preferred address doesn't dictate what the local address is, so this + // has to use the existing address. So only pay attention to a preferred + // address from the same family as is currently in use. More thought will + // be needed to work out how to get addresses from a different family. + let prev = self.paths.primary().borrow().remote_address(); + let remote = match prev.ip() { + IpAddr::V4(_) => addr.ipv4(), + IpAddr::V6(_) => addr.ipv6(), + }; + + if let Some(remote) = remote { + // Ignore preferred address that move to loopback from non-loopback. + // `migrate` doesn't enforce this rule. + if !prev.ip().is_loopback() && remote.ip().is_loopback() { + qwarn!([self], "Ignoring a move to a loopback address: {}", remote); + return Ok(()); + } + + if self.migrate(None, Some(remote), false, now).is_err() { + qwarn!([self], "Ignoring bad preferred address: {}", remote); + } + } else { + qwarn!([self], "Unable to migrate to a different address family"); + } + } + Ok(()) + } + + fn handle_migration(&mut self, path: &PathRef, d: &Datagram, migrate: bool, now: Instant) { + if !migrate { + return; + } + if self.role == Role::Client { + return; + } + + if self.ensure_permanent(path).is_ok() { + self.paths.handle_migration(path, d.source(), now); + } else { + qinfo!( + [self], + "{} Peer migrated, but no connection ID available", + path.borrow() + ); + } + } + + fn output(&mut self, now: Instant) -> SendOption { + qtrace!([self], "output {:?}", now); + let res = match &self.state { + State::Init + | State::WaitInitial + | State::WaitVersion + | State::Handshaking + | State::Connected + | State::Confirmed => { + if let Some(path) = self.paths.select_path() { + let res = self.output_path(&path, now); + self.capture_error(Some(path), now, 0, res) + } else { + Ok(SendOption::default()) + } + } + State::Closing { .. } | State::Draining { .. } | State::Closed(_) => { + if let Some(details) = self.state_signaling.close_frame() { + let path = Rc::clone(details.path()); + let res = self.output_close(details); + self.capture_error(Some(path), now, 0, res) + } else { + Ok(SendOption::default()) + } + } + }; + res.unwrap_or_default() + } + + fn build_packet_header( + path: &Path, + cspace: CryptoSpace, + encoder: Encoder, + tx: &CryptoDxState, + address_validation: &AddressValidationInfo, + version: Version, + grease_quic_bit: bool, + ) -> (PacketType, PacketBuilder) { + let pt = PacketType::from(cspace); + let mut builder = if pt == PacketType::Short { + qdebug!("Building Short dcid {}", path.remote_cid()); + PacketBuilder::short(encoder, tx.key_phase(), path.remote_cid()) + } else { + qdebug!( + "Building {:?} dcid {} scid {}", + pt, + path.remote_cid(), + path.local_cid(), + ); + + PacketBuilder::long(encoder, pt, version, path.remote_cid(), path.local_cid()) + }; + if builder.remaining() > 0 { + builder.scramble(grease_quic_bit); + if pt == PacketType::Initial { + builder.initial_token(address_validation.token()); + } + } + + (pt, builder) + } + + #[must_use] + fn add_packet_number( + builder: &mut PacketBuilder, + tx: &CryptoDxState, + largest_acknowledged: Option<PacketNumber>, + ) -> PacketNumber { + // Get the packet number and work out how long it is. + let pn = tx.next_pn(); + let unacked_range = if let Some(la) = largest_acknowledged { + // Double the range from this to the last acknowledged in this space. + (pn - la) << 1 + } else { + pn + 1 + }; + // Count how many bytes in this range are non-zero. + let pn_len = mem::size_of::<PacketNumber>() + - usize::try_from(unacked_range.leading_zeros() / 8).unwrap(); + // pn_len can't be zero (unacked_range is > 0) + // TODO(mt) also use `4*path CWND/path MTU` to set a minimum length. + builder.pn(pn, pn_len); + pn + } + + fn can_grease_quic_bit(&self) -> bool { + let tph = self.tps.borrow(); + if let Some(r) = &tph.remote { + r.get_empty(tparams::GREASE_QUIC_BIT) + } else if let Some(r) = &tph.remote_0rtt { + r.get_empty(tparams::GREASE_QUIC_BIT) + } else { + false + } + } + + fn output_close(&mut self, close: ClosingFrame) -> Res<SendOption> { + let mut encoder = Encoder::with_capacity(256); + let grease_quic_bit = self.can_grease_quic_bit(); + let version = self.version(); + for space in PacketNumberSpace::iter() { + let (cspace, tx) = + if let Some(crypto) = self.crypto.states.select_tx_mut(self.version, *space) { + crypto + } else { + continue; + }; + + let path = close.path().borrow(); + let (_, mut builder) = Self::build_packet_header( + &path, + cspace, + encoder, + tx, + &AddressValidationInfo::None, + version, + grease_quic_bit, + ); + let _ = Self::add_packet_number( + &mut builder, + tx, + self.loss_recovery.largest_acknowledged_pn(*space), + ); + // The builder will set the limit to 0 if there isn't enough space for the header. + if builder.is_full() { + encoder = builder.abort(); + break; + } + builder.set_limit(min(path.amplification_limit(), path.mtu()) - tx.expansion()); + debug_assert!(builder.limit() <= 2048); + + // ConnectionError::Application is only allowed at 1RTT. + let sanitized = if *space == PacketNumberSpace::ApplicationData { + None + } else { + close.sanitize() + }; + sanitized + .as_ref() + .unwrap_or(&close) + .write_frame(&mut builder); + if builder.len() > builder.limit() { + return Err(Error::InternalError(10)); + } + encoder = builder.build(tx)?; + } + + Ok(SendOption::Yes(close.path().borrow().datagram(encoder))) + } + + /// Write the frames that are exchanged in the application data space. + /// The order of calls here determines the relative priority of frames. + fn write_appdata_frames( + &mut self, + builder: &mut PacketBuilder, + tokens: &mut Vec<RecoveryToken>, + ) -> Res<()> { + if self.role == Role::Server { + if let Some(t) = self.state_signaling.write_done(builder)? { + tokens.push(t); + self.stats.borrow_mut().frame_tx.handshake_done += 1; + } + } + + // Check if there is a Datagram to be written + self.quic_datagrams + .write_frames(builder, tokens, &mut self.stats.borrow_mut()); + + let stats = &mut self.stats.borrow_mut().frame_tx; + + self.streams + .write_frames(TransmissionPriority::Critical, builder, tokens, stats); + if builder.is_full() { + return Ok(()); + } + + self.streams + .write_frames(TransmissionPriority::Important, builder, tokens, stats); + if builder.is_full() { + return Ok(()); + } + + // NEW_CONNECTION_ID, RETIRE_CONNECTION_ID, and ACK_FREQUENCY. + self.cid_manager.write_frames(builder, tokens, stats)?; + if builder.is_full() { + return Ok(()); + } + self.paths.write_frames(builder, tokens, stats)?; + if builder.is_full() { + return Ok(()); + } + + self.streams + .write_frames(TransmissionPriority::High, builder, tokens, stats); + if builder.is_full() { + return Ok(()); + } + + self.streams + .write_frames(TransmissionPriority::Normal, builder, tokens, stats); + if builder.is_full() { + return Ok(()); + } + + // CRYPTO here only includes NewSessionTicket, plus NEW_TOKEN. + // Both of these are only used for resumption and so can be relatively low priority. + self.crypto + .write_frame(PacketNumberSpace::ApplicationData, builder, tokens, stats)?; + if builder.is_full() { + return Ok(()); + } + self.new_token.write_frames(builder, tokens, stats)?; + if builder.is_full() { + return Ok(()); + } + + self.streams + .write_frames(TransmissionPriority::Low, builder, tokens, stats); + + #[cfg(test)] + { + if let Some(w) = &mut self.test_frame_writer { + w.write_frames(builder); + } + } + + Ok(()) + } + + // Maybe send a probe. Return true if the packet was ack-eliciting. + fn maybe_probe( + &mut self, + path: &PathRef, + force_probe: bool, + builder: &mut PacketBuilder, + ack_end: usize, + tokens: &mut Vec<RecoveryToken>, + now: Instant, + ) -> bool { + let untracked = self.received_untracked && !self.state.connected(); + self.received_untracked = false; + + // Anything written after an ACK already elicits acknowledgment. + // If we need to probe and nothing has been written, send a PING. + if builder.len() > ack_end { + return true; + } + + let probe = if untracked && builder.packet_empty() || force_probe { + // If we received an untracked packet and we aren't probing already + // or the PTO timer fired: probe. + true + } else { + let pto = path.borrow().rtt().pto(PacketNumberSpace::ApplicationData); + if !builder.packet_empty() { + // The packet only contains an ACK. Check whether we want to + // force an ACK with a PING so we can stop tracking packets. + self.loss_recovery.should_probe(pto, now) + } else if self.streams.need_keep_alive() { + // We need to keep the connection alive, including sending + // a PING again. + self.idle_timeout.send_keep_alive(now, pto, tokens) + } else { + false + } + }; + if probe { + // Nothing ack-eliciting and we need to probe; send PING. + debug_assert_ne!(builder.remaining(), 0); + builder.encode_varint(crate::frame::FRAME_TYPE_PING); + let stats = &mut self.stats.borrow_mut().frame_tx; + stats.ping += 1; + stats.all += 1; + } + probe + } + + /// Write frames to the provided builder. Returns a list of tokens used for + /// tracking loss or acknowledgment, whether any frame was ACK eliciting, and + /// whether the packet was padded. + fn write_frames( + &mut self, + path: &PathRef, + space: PacketNumberSpace, + profile: &SendProfile, + builder: &mut PacketBuilder, + now: Instant, + ) -> Res<(Vec<RecoveryToken>, bool, bool)> { + let mut tokens = Vec::new(); + let primary = path.borrow().is_primary(); + let mut ack_eliciting = false; + + if primary { + let stats = &mut self.stats.borrow_mut().frame_tx; + self.acks + .write_frame(space, now, builder, &mut tokens, stats)?; + } + let ack_end = builder.len(); + + // Avoid sending probes until the handshake completes, + // but send them even when we don't have space. + let full_mtu = profile.limit() == path.borrow().mtu(); + if space == PacketNumberSpace::ApplicationData && self.state.connected() { + // Probes should only be padded if the full MTU is available. + // The probing code needs to know so it can track that. + if path.borrow_mut().write_frames( + builder, + &mut self.stats.borrow_mut().frame_tx, + full_mtu, + now, + )? { + builder.enable_padding(true); + } + } + + if profile.ack_only(space) { + // If we are CC limited we can only send acks! + return Ok((tokens, false, false)); + } + + if primary { + if space == PacketNumberSpace::ApplicationData { + self.write_appdata_frames(builder, &mut tokens)?; + } else { + let stats = &mut self.stats.borrow_mut().frame_tx; + self.crypto + .write_frame(space, builder, &mut tokens, stats)?; + } + } + + // Maybe send a probe now, either to probe for losses or to keep the connection live. + let force_probe = profile.should_probe(space); + ack_eliciting |= self.maybe_probe(path, force_probe, builder, ack_end, &mut tokens, now); + // If this is not the primary path, this should be ack-eliciting. + debug_assert!(primary || ack_eliciting); + + // Add padding. Only pad 1-RTT packets so that we don't prevent coalescing. + // And avoid padding packets that otherwise only contain ACK because adding PADDING + // causes those packets to consume congestion window, which is not tracked (yet). + // And avoid padding if we don't have a full MTU available. + let stats = &mut self.stats.borrow_mut().frame_tx; + let padded = if ack_eliciting && full_mtu && builder.pad() { + stats.padding += 1; + stats.all += 1; + true + } else { + false + }; + + stats.all += tokens.len(); + Ok((tokens, ack_eliciting, padded)) + } + + /// Build a datagram, possibly from multiple packets (for different PN + /// spaces) and each containing 1+ frames. + fn output_path(&mut self, path: &PathRef, now: Instant) -> Res<SendOption> { + let mut initial_sent = None; + let mut needs_padding = false; + let grease_quic_bit = self.can_grease_quic_bit(); + let version = self.version(); + + // Determine how we are sending packets (PTO, etc..). + let mtu = path.borrow().mtu(); + let profile = self.loss_recovery.send_profile(&path.borrow(), now); + qdebug!([self], "output_path send_profile {:?}", profile); + + // Frames for different epochs must go in different packets, but then these + // packets can go in a single datagram + let mut encoder = Encoder::with_capacity(profile.limit()); + for space in PacketNumberSpace::iter() { + // Ensure we have tx crypto state for this epoch, or skip it. + let (cspace, tx) = + if let Some(crypto) = self.crypto.states.select_tx_mut(self.version, *space) { + crypto + } else { + continue; + }; + + let header_start = encoder.len(); + let (pt, mut builder) = Self::build_packet_header( + &path.borrow(), + cspace, + encoder, + tx, + &self.address_validation, + version, + grease_quic_bit, + ); + let pn = Self::add_packet_number( + &mut builder, + tx, + self.loss_recovery.largest_acknowledged_pn(*space), + ); + // The builder will set the limit to 0 if there isn't enough space for the header. + if builder.is_full() { + encoder = builder.abort(); + break; + } + + // Configure the limits and padding for this packet. + let aead_expansion = tx.expansion(); + builder.set_limit(profile.limit() - aead_expansion); + builder.enable_padding(needs_padding); + debug_assert!(builder.limit() <= 2048); + if builder.is_full() { + encoder = builder.abort(); + break; + } + + // Add frames to the packet. + let payload_start = builder.len(); + let (tokens, ack_eliciting, padded) = + self.write_frames(path, *space, &profile, &mut builder, now)?; + if builder.packet_empty() { + // Nothing to include in this packet. + encoder = builder.abort(); + continue; + } + + dump_packet( + self, + path, + "TX ->", + pt, + pn, + &builder.as_ref()[payload_start..], + ); + qlog::packet_sent( + &mut self.qlog, + pt, + pn, + builder.len() - header_start + aead_expansion, + &builder.as_ref()[payload_start..], + ); + + self.stats.borrow_mut().packets_tx += 1; + let tx = self.crypto.states.tx_mut(self.version, cspace).unwrap(); + encoder = builder.build(tx)?; + debug_assert!(encoder.len() <= mtu); + self.crypto.states.auto_update()?; + + if ack_eliciting { + self.idle_timeout.on_packet_sent(now); + } + let sent = SentPacket::new( + pt, + pn, + now, + ack_eliciting, + tokens, + encoder.len() - header_start, + ); + if padded { + needs_padding = false; + self.loss_recovery.on_packet_sent(path, sent); + } else if pt == PacketType::Initial && (self.role == Role::Client || ack_eliciting) { + // Packets containing Initial packets might need padding, and we want to + // track that padding along with the Initial packet. So defer tracking. + initial_sent = Some(sent); + needs_padding = true; + } else { + if pt == PacketType::Handshake && self.role == Role::Client { + needs_padding = false; + } + self.loss_recovery.on_packet_sent(path, sent); + } + + if *space == PacketNumberSpace::Handshake + && self.role == Role::Server + && self.state == State::Confirmed + { + // We could discard handshake keys in set_state, + // but wait until after sending an ACK. + self.discard_keys(PacketNumberSpace::Handshake, now); + } + } + + if encoder.is_empty() { + Ok(SendOption::No(profile.paced())) + } else { + // Perform additional padding for Initial packets as necessary. + let mut packets: Vec<u8> = encoder.into(); + if let Some(mut initial) = initial_sent.take() { + if needs_padding { + qdebug!( + [self], + "pad Initial from {} to path MTU {}", + packets.len(), + mtu + ); + initial.size += mtu - packets.len(); + packets.resize(mtu, 0); + } + self.loss_recovery.on_packet_sent(path, initial); + } + path.borrow_mut().add_sent(packets.len()); + Ok(SendOption::Yes(path.borrow().datagram(packets))) + } + } + + pub fn initiate_key_update(&mut self) -> Res<()> { + if self.state == State::Confirmed { + let la = self + .loss_recovery + .largest_acknowledged_pn(PacketNumberSpace::ApplicationData); + qinfo!([self], "Initiating key update"); + self.crypto.states.initiate_key_update(la) + } else { + Err(Error::KeyUpdateBlocked) + } + } + + #[cfg(test)] + pub fn get_epochs(&self) -> (Option<usize>, Option<usize>) { + self.crypto.states.get_epochs() + } + + fn client_start(&mut self, now: Instant) -> Res<()> { + qinfo!([self], "client_start"); + debug_assert_eq!(self.role, Role::Client); + qlog::client_connection_started(&mut self.qlog, &self.paths.primary()); + + self.handshake(now, self.version, PacketNumberSpace::Initial, None)?; + self.set_state(State::WaitInitial); + self.zero_rtt_state = if self.crypto.enable_0rtt(self.version, self.role)? { + qdebug!([self], "Enabled 0-RTT"); + ZeroRttState::Sending + } else { + ZeroRttState::Init + }; + Ok(()) + } + + fn get_closing_period_time(&self, now: Instant) -> Instant { + // Spec says close time should be at least PTO times 3. + now + (self.pto() * 3) + } + + /// Close the connection. + pub fn close(&mut self, now: Instant, app_error: AppError, msg: impl AsRef<str>) { + let error = ConnectionError::Application(app_error); + let timeout = self.get_closing_period_time(now); + if let Some(path) = self.paths.primary_fallible() { + self.state_signaling.close(path, error.clone(), 0, msg); + self.set_state(State::Closing { error, timeout }); + } else { + self.set_state(State::Closed(error)); + } + } + + fn set_initial_limits(&mut self) { + self.streams.set_initial_limits(); + let peer_timeout = self + .tps + .borrow() + .remote() + .get_integer(tparams::IDLE_TIMEOUT); + if peer_timeout > 0 { + self.idle_timeout + .set_peer_timeout(Duration::from_millis(peer_timeout)); + } + + self.quic_datagrams.set_remote_datagram_size( + self.tps + .borrow() + .remote() + .get_integer(tparams::MAX_DATAGRAM_FRAME_SIZE), + ); + } + + pub fn is_stream_id_allowed(&self, stream_id: StreamId) -> bool { + self.streams.is_stream_id_allowed(stream_id) + } + + /// Process the final set of transport parameters. + fn process_tps(&mut self) -> Res<()> { + self.validate_cids()?; + self.validate_versions()?; + { + let tps = self.tps.borrow(); + let remote = tps.remote.as_ref().unwrap(); + + // If the peer provided a preferred address, then we have to be a client + // and they have to be using a non-empty connection ID. + if remote.get_preferred_address().is_some() + && (self.role == Role::Server + || self.remote_initial_source_cid.as_ref().unwrap().is_empty()) + { + return Err(Error::TransportParameterError); + } + + let reset_token = if let Some(token) = remote.get_bytes(tparams::STATELESS_RESET_TOKEN) + { + <[u8; 16]>::try_from(token).unwrap() + } else { + // The other side didn't provide a stateless reset token. + // That's OK, they can try guessing this. + <[u8; 16]>::try_from(&random(16)[..]).unwrap() + }; + self.paths + .primary() + .borrow_mut() + .set_reset_token(reset_token); + + let max_ad = Duration::from_millis(remote.get_integer(tparams::MAX_ACK_DELAY)); + let min_ad = if remote.has_value(tparams::MIN_ACK_DELAY) { + let min_ad = Duration::from_micros(remote.get_integer(tparams::MIN_ACK_DELAY)); + if min_ad > max_ad { + return Err(Error::TransportParameterError); + } + Some(min_ad) + } else { + None + }; + self.paths.primary().borrow_mut().set_ack_delay( + max_ad, + min_ad, + self.conn_params.get_ack_ratio(), + ); + + let max_active_cids = remote.get_integer(tparams::ACTIVE_CONNECTION_ID_LIMIT); + self.cid_manager.set_limit(max_active_cids); + } + self.set_initial_limits(); + qlog::connection_tparams_set(&mut self.qlog, &self.tps.borrow()); + Ok(()) + } + + fn validate_cids(&mut self) -> Res<()> { + let tph = self.tps.borrow(); + let remote_tps = tph.remote.as_ref().unwrap(); + + let tp = remote_tps.get_bytes(tparams::INITIAL_SOURCE_CONNECTION_ID); + if self + .remote_initial_source_cid + .as_ref() + .map(ConnectionId::as_cid_ref) + != tp.map(ConnectionIdRef::from) + { + qwarn!( + [self], + "ISCID test failed: self cid {:?} != tp cid {:?}", + self.remote_initial_source_cid, + tp.map(hex), + ); + return Err(Error::ProtocolViolation); + } + + if self.role == Role::Client { + let tp = remote_tps.get_bytes(tparams::ORIGINAL_DESTINATION_CONNECTION_ID); + if self + .original_destination_cid + .as_ref() + .map(ConnectionId::as_cid_ref) + != tp.map(ConnectionIdRef::from) + { + qwarn!( + [self], + "ODCID test failed: self cid {:?} != tp cid {:?}", + self.original_destination_cid, + tp.map(hex), + ); + return Err(Error::ProtocolViolation); + } + + let tp = remote_tps.get_bytes(tparams::RETRY_SOURCE_CONNECTION_ID); + let expected = if let AddressValidationInfo::Retry { + retry_source_cid, .. + } = &self.address_validation + { + Some(retry_source_cid.as_cid_ref()) + } else { + None + }; + if expected != tp.map(ConnectionIdRef::from) { + qwarn!( + [self], + "RSCID test failed. self cid {:?} != tp cid {:?}", + expected, + tp.map(hex), + ); + return Err(Error::ProtocolViolation); + } + } + + Ok(()) + } + + /// Validate the `version_negotiation` transport parameter from the peer. + fn validate_versions(&mut self) -> Res<()> { + let tph = self.tps.borrow(); + let remote_tps = tph.remote.as_ref().unwrap(); + // `current` and `other` are the value from the peer's transport parameters. + // We're checking that these match our expectations. + if let Some((current, other)) = remote_tps.get_versions() { + qtrace!( + [self], + "validate_versions: current={:x} chosen={:x} other={:x?}", + self.version.wire_version(), + current, + other, + ); + if self.role == Role::Server { + // 1. A server acts on transport parameters, with validation + // of `current` happening in the transport parameter handler. + // All we need to do is confirm that the transport parameter + // was provided. + Ok(()) + } else if self.version().wire_version() != current { + qinfo!([self], "validate_versions: current version mismatch"); + Err(Error::VersionNegotiation) + } else if self + .conn_params + .get_versions() + .initial() + .is_compatible(self.version) + { + // 2. The current version is compatible with what we attempted. + // That's a compatible upgrade and that's OK. + Ok(()) + } else { + // 3. The initial version we attempted isn't compatible. Check that + // the one we would have chosen is compatible with this one. + let mut all_versions = other.to_owned(); + all_versions.push(current); + if self + .conn_params + .get_versions() + .preferred(&all_versions) + .ok_or(Error::VersionNegotiation)? + .is_compatible(self.version) + { + Ok(()) + } else { + qinfo!([self], "validate_versions: failed"); + Err(Error::VersionNegotiation) + } + } + } else if self.version != Version::Version1 && !self.version.is_draft() { + qinfo!([self], "validate_versions: missing extension"); + Err(Error::VersionNegotiation) + } else { + Ok(()) + } + } + + fn confirm_version(&mut self, v: Version) { + if self.version != v { + qinfo!([self], "Compatible upgrade {:?} ==> {:?}", self.version, v); + } + self.crypto.confirm_version(v); + self.version = v; + } + + fn compatible_upgrade(&mut self, packet_version: Version) { + if !matches!(self.state, State::WaitInitial | State::WaitVersion) { + return; + } + + if self.role == Role::Client { + self.confirm_version(packet_version); + } else if self.tps.borrow().remote.is_some() { + let version = self.tps.borrow().version(); + let dcid = self.original_destination_cid.as_ref().unwrap(); + self.crypto.states.init_server(version, dcid); + self.confirm_version(version); + } + } + + fn handshake( + &mut self, + now: Instant, + packet_version: Version, + space: PacketNumberSpace, + data: Option<&[u8]>, + ) -> Res<()> { + qtrace!([self], "Handshake space={} data={:0x?}", space, data); + + let try_update = data.is_some(); + match self.crypto.handshake(now, space, data)? { + HandshakeState::Authenticated(_) | HandshakeState::InProgress => (), + HandshakeState::AuthenticationPending => self.events.authentication_needed(), + HandshakeState::EchFallbackAuthenticationPending(public_name) => self + .events + .ech_fallback_authentication_needed(public_name.clone()), + HandshakeState::Complete(_) => { + if !self.state.connected() { + self.set_connected(now)?; + } + } + _ => { + unreachable!("Crypto state should not be new or failed after successful handshake") + } + } + + // There is a chance that this could be called less often, but getting the + // conditions right is a little tricky, so call whenever CRYPTO data is used. + if try_update { + self.compatible_upgrade(packet_version); + // We have transport parameters, it's go time. + if self.tps.borrow().remote.is_some() { + self.set_initial_limits(); + } + if self.crypto.install_keys(self.role)? { + if self.role == Role::Client { + // We won't acknowledge Initial packets as a result of this, but the + // server can rely on implicit acknowledgment. + self.discard_keys(PacketNumberSpace::Initial, now); + } + self.saved_datagrams.make_available(CryptoSpace::Handshake); + } + } + + Ok(()) + } + + fn input_frame( + &mut self, + path: &PathRef, + packet_version: Version, + packet_type: PacketType, + frame: Frame, + now: Instant, + ) -> Res<()> { + if !frame.is_allowed(packet_type) { + qinfo!("frame not allowed: {:?} {:?}", frame, packet_type); + return Err(Error::ProtocolViolation); + } + self.stats.borrow_mut().frame_rx.all += 1; + let space = PacketNumberSpace::from(packet_type); + if frame.is_stream() { + return self + .streams + .input_frame(frame, &mut self.stats.borrow_mut().frame_rx); + } + match frame { + Frame::Padding => { + // Note: This counts contiguous padding as a single frame. + self.stats.borrow_mut().frame_rx.padding += 1; + } + Frame::Ping => { + // If we get a PING and there are outstanding CRYPTO frames, + // prepare to resend them. + self.stats.borrow_mut().frame_rx.ping += 1; + self.crypto.resend_unacked(space); + if space == PacketNumberSpace::ApplicationData { + // Send an ACK immediately if we might not otherwise do so. + self.acks.immediate_ack(now); + } + } + Frame::Ack { + largest_acknowledged, + ack_delay, + first_ack_range, + ack_ranges, + } => { + let ranges = + Frame::decode_ack_frame(largest_acknowledged, first_ack_range, &ack_ranges)?; + self.handle_ack(space, largest_acknowledged, ranges, ack_delay, now); + } + Frame::Crypto { offset, data } => { + qtrace!( + [self], + "Crypto frame on space={} offset={}, data={:0x?}", + space, + offset, + &data + ); + self.stats.borrow_mut().frame_rx.crypto += 1; + self.crypto.streams.inbound_frame(space, offset, data); + if self.crypto.streams.data_ready(space) { + let mut buf = Vec::new(); + let read = self.crypto.streams.read_to_end(space, &mut buf); + qdebug!("Read {} bytes", read); + self.handshake(now, packet_version, space, Some(&buf))?; + self.create_resumption_token(now); + } else { + // If we get a useless CRYPTO frame send outstanding CRYPTO frames again. + self.crypto.resend_unacked(space); + } + } + Frame::NewToken { token } => { + self.stats.borrow_mut().frame_rx.new_token += 1; + self.new_token.save_token(token.to_vec()); + self.create_resumption_token(now); + } + Frame::NewConnectionId { + sequence_number, + connection_id, + stateless_reset_token, + retire_prior, + } => { + self.stats.borrow_mut().frame_rx.new_connection_id += 1; + self.connection_ids.add_remote(ConnectionIdEntry::new( + sequence_number, + ConnectionId::from(connection_id), + stateless_reset_token.to_owned(), + ))?; + self.paths + .retire_cids(retire_prior, &mut self.connection_ids); + if self.connection_ids.len() >= LOCAL_ACTIVE_CID_LIMIT { + qinfo!([self], "received too many connection IDs"); + return Err(Error::ConnectionIdLimitExceeded); + } + } + Frame::RetireConnectionId { sequence_number } => { + self.stats.borrow_mut().frame_rx.retire_connection_id += 1; + self.cid_manager.retire(sequence_number); + } + Frame::PathChallenge { data } => { + self.stats.borrow_mut().frame_rx.path_challenge += 1; + // If we were challenged, try to make the path permanent. + // Report an error if we don't have enough connection IDs. + self.ensure_permanent(path)?; + path.borrow_mut().challenged(data); + } + Frame::PathResponse { data } => { + self.stats.borrow_mut().frame_rx.path_response += 1; + if self.paths.path_response(data, now) { + // This PATH_RESPONSE enabled migration; tell loss recovery. + self.loss_recovery.migrate(); + } + } + Frame::ConnectionClose { + error_code, + frame_type, + reason_phrase, + } => { + self.stats.borrow_mut().frame_rx.connection_close += 1; + let reason_phrase = String::from_utf8_lossy(&reason_phrase); + qinfo!( + [self], + "ConnectionClose received. Error code: {:?} frame type {:x} reason {}", + error_code, + frame_type, + reason_phrase + ); + let (detail, frame_type) = if let CloseError::Application(_) = error_code { + // Use a transport error here because we want to send + // NO_ERROR in this case. + ( + Error::PeerApplicationError(error_code.code()), + FRAME_TYPE_CONNECTION_CLOSE_APPLICATION, + ) + } else { + ( + Error::PeerError(error_code.code()), + FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT, + ) + }; + let error = ConnectionError::Transport(detail); + self.state_signaling + .drain(Rc::clone(path), error.clone(), frame_type, ""); + self.set_state(State::Draining { + error, + timeout: self.get_closing_period_time(now), + }); + } + Frame::HandshakeDone => { + self.stats.borrow_mut().frame_rx.handshake_done += 1; + if self.role == Role::Server || !self.state.connected() { + return Err(Error::ProtocolViolation); + } + self.set_state(State::Confirmed); + self.discard_keys(PacketNumberSpace::Handshake, now); + self.migrate_to_preferred_address(now)?; + } + Frame::AckFrequency { + seqno, + tolerance, + delay, + ignore_order, + } => { + self.stats.borrow_mut().frame_rx.ack_frequency += 1; + let delay = Duration::from_micros(delay); + if delay < GRANULARITY { + return Err(Error::ProtocolViolation); + } + self.acks + .ack_freq(seqno, tolerance - 1, delay, ignore_order); + } + Frame::Datagram { data, .. } => { + self.stats.borrow_mut().frame_rx.datagram += 1; + self.quic_datagrams + .handle_datagram(data, &mut self.stats.borrow_mut())?; + } + _ => unreachable!("All other frames are for streams"), + }; + + Ok(()) + } + + /// Given a set of `SentPacket` instances, ensure that the source of the packet + /// is told that they are lost. This gives the frame generation code a chance + /// to retransmit the frame as needed. + fn handle_lost_packets(&mut self, lost_packets: &[SentPacket]) { + for lost in lost_packets { + for token in &lost.tokens { + qdebug!([self], "Lost: {:?}", token); + match token { + RecoveryToken::Ack(_) => {} + RecoveryToken::Crypto(ct) => self.crypto.lost(ct), + RecoveryToken::HandshakeDone => self.state_signaling.handshake_done(), + RecoveryToken::NewToken(seqno) => self.new_token.lost(*seqno), + RecoveryToken::NewConnectionId(ncid) => self.cid_manager.lost(ncid), + RecoveryToken::RetireConnectionId(seqno) => self.paths.lost_retire_cid(*seqno), + RecoveryToken::AckFrequency(rate) => self.paths.lost_ack_frequency(rate), + RecoveryToken::KeepAlive => self.idle_timeout.lost_keep_alive(), + RecoveryToken::Stream(stream_token) => self.streams.lost(stream_token), + RecoveryToken::Datagram(dgram_tracker) => { + self.events + .datagram_outcome(dgram_tracker, OutgoingDatagramOutcome::Lost); + self.stats.borrow_mut().datagram_tx.lost += 1; + } + } + } + } + } + + fn decode_ack_delay(&self, v: u64) -> Duration { + // If we have remote transport parameters, use them. + // Otherwise, ack delay should be zero (because it's the handshake). + if let Some(r) = self.tps.borrow().remote.as_ref() { + let exponent = u32::try_from(r.get_integer(tparams::ACK_DELAY_EXPONENT)).unwrap(); + Duration::from_micros(v.checked_shl(exponent).unwrap_or(u64::MAX)) + } else { + Duration::new(0, 0) + } + } + + fn handle_ack<R>( + &mut self, + space: PacketNumberSpace, + largest_acknowledged: u64, + ack_ranges: R, + ack_delay: u64, + now: Instant, + ) where + R: IntoIterator<Item = RangeInclusive<u64>> + Debug, + R::IntoIter: ExactSizeIterator, + { + qinfo!([self], "Rx ACK space={}, ranges={:?}", space, ack_ranges); + + let (acked_packets, lost_packets) = self.loss_recovery.on_ack_received( + &self.paths.primary(), + space, + largest_acknowledged, + ack_ranges, + self.decode_ack_delay(ack_delay), + now, + ); + for acked in acked_packets { + for token in &acked.tokens { + match token { + RecoveryToken::Stream(stream_token) => self.streams.acked(stream_token), + RecoveryToken::Ack(at) => self.acks.acked(at), + RecoveryToken::Crypto(ct) => self.crypto.acked(ct), + RecoveryToken::NewToken(seqno) => self.new_token.acked(*seqno), + RecoveryToken::NewConnectionId(entry) => self.cid_manager.acked(entry), + RecoveryToken::RetireConnectionId(seqno) => self.paths.acked_retire_cid(*seqno), + RecoveryToken::AckFrequency(rate) => self.paths.acked_ack_frequency(rate), + RecoveryToken::KeepAlive => self.idle_timeout.ack_keep_alive(), + RecoveryToken::Datagram(dgram_tracker) => self + .events + .datagram_outcome(dgram_tracker, OutgoingDatagramOutcome::Acked), + // We only worry when these are lost + RecoveryToken::HandshakeDone => (), + } + } + } + self.handle_lost_packets(&lost_packets); + qlog::packets_lost(&mut self.qlog, &lost_packets); + let stats = &mut self.stats.borrow_mut().frame_rx; + stats.ack += 1; + stats.largest_acknowledged = max(stats.largest_acknowledged, largest_acknowledged); + } + + /// When the server rejects 0-RTT we need to drop a bunch of stuff. + fn client_0rtt_rejected(&mut self, now: Instant) { + if !matches!(self.zero_rtt_state, ZeroRttState::Sending) { + return; + } + qdebug!([self], "0-RTT rejected"); + + // Tell 0-RTT packets that they were "lost". + let dropped = self.loss_recovery.drop_0rtt(&self.paths.primary(), now); + self.handle_lost_packets(&dropped); + + self.streams.zero_rtt_rejected(); + + self.crypto.states.discard_0rtt_keys(); + self.events.client_0rtt_rejected(); + } + + fn set_connected(&mut self, now: Instant) -> Res<()> { + qinfo!([self], "TLS connection complete"); + if self.crypto.tls.info().map(SecretAgentInfo::alpn).is_none() { + qwarn!([self], "No ALPN. Closing connection."); + // 120 = no_application_protocol + return Err(Error::CryptoAlert(120)); + } + if self.role == Role::Server { + // Remove the randomized client CID from the list of acceptable CIDs. + self.cid_manager.remove_odcid(); + // Mark the path as validated, if it isn't already. + let path = self.paths.primary(); + path.borrow_mut().set_valid(now); + // Generate a qlog event that the server connection started. + qlog::server_connection_started(&mut self.qlog, &path); + } else { + self.zero_rtt_state = if self.crypto.tls.info().unwrap().early_data_accepted() { + ZeroRttState::AcceptedClient + } else { + self.client_0rtt_rejected(now); + ZeroRttState::Rejected + }; + } + + // Setting application keys has to occur after 0-RTT rejection. + let pto = self.pto(); + self.crypto + .install_application_keys(self.version, now + pto)?; + self.process_tps()?; + self.set_state(State::Connected); + self.create_resumption_token(now); + self.saved_datagrams + .make_available(CryptoSpace::ApplicationData); + self.stats.borrow_mut().resumed = self.crypto.tls.info().unwrap().resumed(); + if self.role == Role::Server { + self.state_signaling.handshake_done(); + self.set_state(State::Confirmed); + } + qinfo!([self], "Connection established"); + Ok(()) + } + + fn set_state(&mut self, state: State) { + if state > self.state { + qinfo!([self], "State change from {:?} -> {:?}", self.state, state); + self.state = state.clone(); + if self.state.closed() { + self.streams.clear_streams(); + } + self.events.connection_state_change(state); + qlog::connection_state_updated(&mut self.qlog, &self.state) + } else if mem::discriminant(&state) != mem::discriminant(&self.state) { + // Only tolerate a regression in state if the new state is closing + // and the connection is already closed. + debug_assert!(matches!( + state, + State::Closing { .. } | State::Draining { .. } + )); + debug_assert!(self.state.closed()); + } + } + + /// Create a stream. + /// Returns new stream id + /// # Errors + /// `ConnectionState` if the connecton stat does not allow to create streams. + /// `StreamLimitError` if we are limiied by server's stream concurence. + pub fn stream_create(&mut self, st: StreamType) -> Res<StreamId> { + // Can't make streams while closing, otherwise rely on the stream limits. + match self.state { + State::Closing { .. } | State::Draining { .. } | State::Closed { .. } => { + return Err(Error::ConnectionState); + } + State::WaitInitial | State::Handshaking => { + if self.role == Role::Client && self.zero_rtt_state != ZeroRttState::Sending { + return Err(Error::ConnectionState); + } + } + // In all other states, trust that the stream limits are correct. + _ => (), + } + + self.streams.stream_create(st) + } + + /// Set the priority of a stream. + /// # Errors + /// `InvalidStreamId` the stream does not exist. + pub fn stream_priority( + &mut self, + stream_id: StreamId, + transmission: TransmissionPriority, + retransmission: RetransmissionPriority, + ) -> Res<()> { + self.streams + .get_send_stream_mut(stream_id)? + .set_priority(transmission, retransmission); + Ok(()) + } + + /// Send data on a stream. + /// Returns how many bytes were successfully sent. Could be less + /// than total, based on receiver credit space available, etc. + /// # Errors + /// `InvalidStreamId` the stream does not exist, + /// `InvalidInput` if length of `data` is zero, + /// `FinalSizeError` if the stream has already been closed. + pub fn stream_send(&mut self, stream_id: StreamId, data: &[u8]) -> Res<usize> { + self.streams.get_send_stream_mut(stream_id)?.send(data) + } + + /// Send all data or nothing on a stream. May cause DATA_BLOCKED or + /// STREAM_DATA_BLOCKED frames to be sent. + /// Returns true if data was successfully sent, otherwise false. + /// # Errors + /// `InvalidStreamId` the stream does not exist, + /// `InvalidInput` if length of `data` is zero, + /// `FinalSizeError` if the stream has already been closed. + pub fn stream_send_atomic(&mut self, stream_id: StreamId, data: &[u8]) -> Res<bool> { + let val = self + .streams + .get_send_stream_mut(stream_id)? + .send_atomic(data); + if let Ok(val) = val { + debug_assert!( + val == 0 || val == data.len(), + "Unexpected value {} when trying to send {} bytes atomically", + val, + data.len() + ); + } + val.map(|v| v == data.len()) + } + + /// Bytes that stream_send() is guaranteed to accept for sending. + /// i.e. that will not be blocked by flow credits or send buffer max + /// capacity. + pub fn stream_avail_send_space(&self, stream_id: StreamId) -> Res<usize> { + Ok(self.streams.get_send_stream(stream_id)?.avail()) + } + + /// Close the stream. Enqueued data will be sent. + pub fn stream_close_send(&mut self, stream_id: StreamId) -> Res<()> { + self.streams.get_send_stream_mut(stream_id)?.close(); + Ok(()) + } + + /// Abandon transmission of in-flight and future stream data. + pub fn stream_reset_send(&mut self, stream_id: StreamId, err: AppError) -> Res<()> { + self.streams.get_send_stream_mut(stream_id)?.reset(err); + Ok(()) + } + + /// Read buffered data from stream. bool says whether read bytes includes + /// the final data on stream. + /// # Errors + /// `InvalidStreamId` if the stream does not exist. + /// `NoMoreData` if data and fin bit were previously read by the application. + pub fn stream_recv(&mut self, stream_id: StreamId, data: &mut [u8]) -> Res<(usize, bool)> { + let stream = self.streams.get_recv_stream_mut(stream_id)?; + + let rb = stream.read(data)?; + Ok(rb) + } + + /// Application is no longer interested in this stream. + pub fn stream_stop_sending(&mut self, stream_id: StreamId, err: AppError) -> Res<()> { + let stream = self.streams.get_recv_stream_mut(stream_id)?; + + stream.stop_sending(err); + Ok(()) + } + + /// Increases `max_stream_data` for a `stream_id`. + /// # Errors + /// Returns `InvalidStreamId` if a stream does not exist or the receiving + /// side is closed. + pub fn set_stream_max_data(&mut self, stream_id: StreamId, max_data: u64) -> Res<()> { + let stream = self.streams.get_recv_stream_mut(stream_id)?; + + stream.set_stream_max_data(max_data); + Ok(()) + } + + /// Mark a receive stream as being important enough to keep the connection alive + /// (if `keep` is `true`) or no longer important (if `keep` is `false`). If any + /// stream is marked this way, PING frames will be used to keep the connection + /// alive, even when there is no activity. + /// # Errors + /// Returns `InvalidStreamId` if a stream does not exist or the receiving + /// side is closed. + pub fn stream_keep_alive(&mut self, stream_id: StreamId, keep: bool) -> Res<()> { + self.streams.keep_alive(stream_id, keep) + } + + pub fn remote_datagram_size(&self) -> u64 { + self.quic_datagrams.remote_datagram_size() + } + + /// Returns the current max size of a datagram that can fit into a packet. + /// The value will change over time depending on the encoded size of the + /// packet number, ack frames, etc. + /// # Error + /// The function returns `NotAvailable` if datagrams are not enabled. + pub fn max_datagram_size(&self) -> Res<u64> { + let max_dgram_size = self.quic_datagrams.remote_datagram_size(); + if max_dgram_size == 0 { + return Err(Error::NotAvailable); + } + let version = self.version(); + let (cspace, tx) = if let Some(crypto) = self + .crypto + .states + .select_tx(self.version, PacketNumberSpace::ApplicationData) + { + crypto + } else { + return Err(Error::NotAvailable); + }; + let path = self.paths.primary_fallible().ok_or(Error::NotAvailable)?; + let mtu = path.borrow().mtu(); + let encoder = Encoder::with_capacity(mtu); + + let (_, mut builder) = Self::build_packet_header( + &path.borrow(), + cspace, + encoder, + tx, + &self.address_validation, + version, + false, + ); + let _ = Self::add_packet_number( + &mut builder, + tx, + self.loss_recovery + .largest_acknowledged_pn(PacketNumberSpace::ApplicationData), + ); + + let data_len_possible = + u64::try_from(mtu.saturating_sub(tx.expansion() + builder.len() + 1)).unwrap(); + Ok(min(data_len_possible, max_dgram_size)) + } + + /// Queue a datagram for sending. + /// # Error + /// The function returns `TooMuchData` if the supply buffer is bigger than + /// the allowed remote datagram size. The funcion does not check if the + /// datagram can fit into a packet (i.e. MTU limit). This is checked during + /// creation of an actual packet and the datagram will be dropped if it does + /// not fit into the packet. The app is encourage to use `max_datagram_size` + /// to check the estimated max datagram size and to use smaller datagrams. + /// `max_datagram_size` is just a current estimate and will change over + /// time depending on the encoded size of the packet number, ack frames, etc. + + pub fn send_datagram(&mut self, buf: &[u8], id: impl Into<DatagramTracking>) -> Res<()> { + self.quic_datagrams + .add_datagram(buf, id.into(), &mut self.stats.borrow_mut()) + } +} + +impl EventProvider for Connection { + type Event = ConnectionEvent; + + /// Return true if there are outstanding events. + fn has_events(&self) -> bool { + self.events.has_events() + } + + /// Get events that indicate state changes on the connection. This method + /// correctly handles cases where handling one event can obsolete + /// previously-queued events, or cause new events to be generated. + fn next_event(&mut self) -> Option<Self::Event> { + self.events.next_event() + } +} + +impl ::std::fmt::Display for Connection { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{:?} ", self.role)?; + if let Some(cid) = self.odcid() { + std::fmt::Display::fmt(&cid, f) + } else { + write!(f, "...") + } + } +} + +#[cfg(test)] +mod tests; diff --git a/third_party/rust/neqo-transport/src/connection/params.rs b/third_party/rust/neqo-transport/src/connection/params.rs new file mode 100644 index 0000000000..bb4979fcf1 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/params.rs @@ -0,0 +1,348 @@ +// 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::connection::{ConnectionIdManager, Role, LOCAL_ACTIVE_CID_LIMIT}; +pub use crate::recovery::FAST_PTO_SCALE; +use crate::recv_stream::RECV_BUFFER_SIZE; +use crate::rtt::GRANULARITY; +use crate::stream_id::StreamType; +use crate::tparams::{self, PreferredAddress, TransportParameter, TransportParametersHandler}; +use crate::tracking::DEFAULT_ACK_DELAY; +use crate::version::{Version, VersionConfig}; +use crate::{CongestionControlAlgorithm, Res}; +use std::cmp::max; +use std::convert::TryFrom; +use std::time::Duration; + +const LOCAL_MAX_DATA: u64 = 0x3FFF_FFFF_FFFF_FFFF; // 2^62-1 +const LOCAL_STREAM_LIMIT_BIDI: u64 = 16; +const LOCAL_STREAM_LIMIT_UNI: u64 = 16; +/// See `ConnectionParameters.ack_ratio` for a discussion of this value. +pub const ACK_RATIO_SCALE: u8 = 10; +/// By default, aim to have the peer acknowledge 4 times per round trip time. +/// See `ConnectionParameters.ack_ratio` for more. +const DEFAULT_ACK_RATIO: u8 = 4 * ACK_RATIO_SCALE; +/// The local value for the idle timeout period. +const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(30); +const MAX_QUEUED_DATAGRAMS_DEFAULT: usize = 10; + +/// What to do with preferred addresses. +#[derive(Debug, Clone, Copy)] +pub enum PreferredAddressConfig { + /// Disabled, whether for client or server. + Disabled, + /// Enabled at a client, disabled at a server. + Default, + /// Enabled at both client and server. + Address(PreferredAddress), +} + +/// ConnectionParameters use for setting intitial value for QUIC parameters. +/// This collects configuration like initial limits, protocol version, and +/// congestion control algorithm. +#[derive(Debug, Clone)] +pub struct ConnectionParameters { + versions: VersionConfig, + cc_algorithm: CongestionControlAlgorithm, + /// Initial connection-level flow control limit. + max_data: u64, + /// Initial flow control limit for receiving data on bidirectional streams that the peer creates. + max_stream_data_bidi_remote: u64, + /// Initial flow control limit for receiving data on bidirectional streams that this endpoint creates. + max_stream_data_bidi_local: u64, + /// Initial flow control limit for receiving data on unidirectional streams that the peer creates. + max_stream_data_uni: u64, + /// Initial limit on bidirectional streams that the peer creates. + max_streams_bidi: u64, + /// Initial limit on unidirectional streams that this endpoint creates. + max_streams_uni: u64, + /// The ACK ratio determines how many acknowledgements we will request as a + /// fraction of both the current congestion window (expressed in packets) and + /// as a fraction of the current round trip time. This value is scaled by + /// `ACK_RATIO_SCALE`; that is, if the goal is to have at least five + /// acknowledgments every round trip, set the value to `5 * ACK_RATIO_SCALE`. + /// Values less than `ACK_RATIO_SCALE` are clamped to `ACK_RATIO_SCALE`. + ack_ratio: u8, + /// The duration of the idle timeout for the connection. + idle_timeout: Duration, + preferred_address: PreferredAddressConfig, + datagram_size: u64, + outgoing_datagram_queue: usize, + incoming_datagram_queue: usize, + fast_pto: u8, +} + +impl Default for ConnectionParameters { + fn default() -> Self { + Self { + versions: VersionConfig::default(), + cc_algorithm: CongestionControlAlgorithm::NewReno, + max_data: LOCAL_MAX_DATA, + max_stream_data_bidi_remote: u64::try_from(RECV_BUFFER_SIZE).unwrap(), + max_stream_data_bidi_local: u64::try_from(RECV_BUFFER_SIZE).unwrap(), + max_stream_data_uni: u64::try_from(RECV_BUFFER_SIZE).unwrap(), + max_streams_bidi: LOCAL_STREAM_LIMIT_BIDI, + max_streams_uni: LOCAL_STREAM_LIMIT_UNI, + ack_ratio: DEFAULT_ACK_RATIO, + idle_timeout: DEFAULT_IDLE_TIMEOUT, + preferred_address: PreferredAddressConfig::Default, + datagram_size: 0, + outgoing_datagram_queue: MAX_QUEUED_DATAGRAMS_DEFAULT, + incoming_datagram_queue: MAX_QUEUED_DATAGRAMS_DEFAULT, + fast_pto: FAST_PTO_SCALE, + } + } +} + +impl ConnectionParameters { + pub fn get_versions(&self) -> &VersionConfig { + &self.versions + } + + pub(crate) fn get_versions_mut(&mut self) -> &mut VersionConfig { + &mut self.versions + } + + /// Describe the initial version that should be attempted and all the + /// versions that should be enabled. This list should contain the initial + /// version and be in order of preference, with more preferred versions + /// before less preferred. + pub fn versions(mut self, initial: Version, all: Vec<Version>) -> Self { + self.versions = VersionConfig::new(initial, all); + self + } + + pub fn get_cc_algorithm(&self) -> CongestionControlAlgorithm { + self.cc_algorithm + } + + pub fn cc_algorithm(mut self, v: CongestionControlAlgorithm) -> Self { + self.cc_algorithm = v; + self + } + + pub fn get_max_data(&self) -> u64 { + self.max_data + } + + pub fn max_data(mut self, v: u64) -> Self { + self.max_data = v; + self + } + + pub fn get_max_streams(&self, stream_type: StreamType) -> u64 { + match stream_type { + StreamType::BiDi => self.max_streams_bidi, + StreamType::UniDi => self.max_streams_uni, + } + } + + /// # Panics + /// If v > 2^60 (the maximum allowed by the protocol). + pub fn max_streams(mut self, stream_type: StreamType, v: u64) -> Self { + assert!(v <= (1 << 60), "max_streams is too large"); + match stream_type { + StreamType::BiDi => { + self.max_streams_bidi = v; + } + StreamType::UniDi => { + self.max_streams_uni = v; + } + } + self + } + + /// Get the maximum stream data that we will accept on different types of streams. + /// # Panics + /// If `StreamType::UniDi` and `false` are passed as that is not a valid combination. + pub fn get_max_stream_data(&self, stream_type: StreamType, remote: bool) -> u64 { + match (stream_type, remote) { + (StreamType::BiDi, false) => self.max_stream_data_bidi_local, + (StreamType::BiDi, true) => self.max_stream_data_bidi_remote, + (StreamType::UniDi, false) => { + panic!("Can't get receive limit on a stream that can only be sent.") + } + (StreamType::UniDi, true) => self.max_stream_data_uni, + } + } + + /// Set the maximum stream data that we will accept on different types of streams. + /// # Panics + /// If `StreamType::UniDi` and `false` are passed as that is not a valid combination + /// or if v >= 62 (the maximum allowed by the protocol). + pub fn max_stream_data(mut self, stream_type: StreamType, remote: bool, v: u64) -> Self { + assert!(v < (1 << 62), "max stream data is too large"); + match (stream_type, remote) { + (StreamType::BiDi, false) => { + self.max_stream_data_bidi_local = v; + } + (StreamType::BiDi, true) => { + self.max_stream_data_bidi_remote = v; + } + (StreamType::UniDi, false) => { + panic!("Can't set receive limit on a stream that can only be sent.") + } + (StreamType::UniDi, true) => { + self.max_stream_data_uni = v; + } + } + self + } + + /// Set a preferred address (which only has an effect for a server). + pub fn preferred_address(mut self, preferred: PreferredAddress) -> Self { + self.preferred_address = PreferredAddressConfig::Address(preferred); + self + } + + /// Disable the use of preferred addresses. + pub fn disable_preferred_address(mut self) -> Self { + self.preferred_address = PreferredAddressConfig::Disabled; + self + } + + pub fn get_preferred_address(&self) -> &PreferredAddressConfig { + &self.preferred_address + } + + pub fn ack_ratio(mut self, ack_ratio: u8) -> Self { + self.ack_ratio = ack_ratio; + self + } + + pub fn get_ack_ratio(&self) -> u8 { + self.ack_ratio + } + + /// # Panics + /// If `timeout` is 2^62 milliseconds or more. + pub fn idle_timeout(mut self, timeout: Duration) -> Self { + assert!(timeout.as_millis() < (1 << 62), "idle timeout is too long"); + self.idle_timeout = timeout; + self + } + + pub fn get_idle_timeout(&self) -> Duration { + self.idle_timeout + } + + pub fn get_datagram_size(&self) -> u64 { + self.datagram_size + } + + pub fn datagram_size(mut self, v: u64) -> Self { + self.datagram_size = v; + self + } + + pub fn get_outgoing_datagram_queue(&self) -> usize { + self.outgoing_datagram_queue + } + + pub fn outgoing_datagram_queue(mut self, v: usize) -> Self { + // The max queue length must be at least 1. + self.outgoing_datagram_queue = max(v, 1); + self + } + + pub fn get_incoming_datagram_queue(&self) -> usize { + self.incoming_datagram_queue + } + + pub fn incoming_datagram_queue(mut self, v: usize) -> Self { + // The max queue length must be at least 1. + self.incoming_datagram_queue = max(v, 1); + self + } + + pub fn get_fast_pto(&self) -> u8 { + self.fast_pto + } + + /// Scale the PTO timer. A value of `FAST_PTO_SCALE` follows the spec, a smaller + /// value does not, but produces more probes with the intent of ensuring lower + /// latency in the event of tail loss. A value of `FAST_PTO_SCALE/4` is quite + /// aggressive. Smaller values (other than zero) are not rejected, but could be + /// very wasteful. Values greater than `FAST_PTO_SCALE` delay probes and could + /// reduce performance. It should not be possible to increase the PTO timer by + /// too much based on the range of valid values, but a maximum value of 255 will + /// result in very poor performance. + /// Scaling PTO this way does not affect when persistent congestion is declared, + /// but may change how many retransmissions are sent before declaring persistent + /// congestion. + /// + /// # Panics + /// A value of 0 is invalid and will cause a panic. + pub fn fast_pto(mut self, scale: u8) -> Self { + assert_ne!(scale, 0); + self.fast_pto = scale; + self + } + + pub fn create_transport_parameter( + &self, + role: Role, + cid_manager: &mut ConnectionIdManager, + ) -> Res<TransportParametersHandler> { + let mut tps = TransportParametersHandler::new(role, self.versions.clone()); + // default parameters + tps.local.set_integer( + tparams::ACTIVE_CONNECTION_ID_LIMIT, + u64::try_from(LOCAL_ACTIVE_CID_LIMIT).unwrap(), + ); + tps.local.set_empty(tparams::DISABLE_MIGRATION); + tps.local.set_empty(tparams::GREASE_QUIC_BIT); + tps.local.set_integer( + tparams::MAX_ACK_DELAY, + u64::try_from(DEFAULT_ACK_DELAY.as_millis()).unwrap(), + ); + tps.local.set_integer( + tparams::MIN_ACK_DELAY, + u64::try_from(GRANULARITY.as_micros()).unwrap(), + ); + + // set configurable parameters + tps.local + .set_integer(tparams::INITIAL_MAX_DATA, self.max_data); + tps.local.set_integer( + tparams::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + self.max_stream_data_bidi_local, + ); + tps.local.set_integer( + tparams::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + self.max_stream_data_bidi_remote, + ); + tps.local.set_integer( + tparams::INITIAL_MAX_STREAM_DATA_UNI, + self.max_stream_data_uni, + ); + tps.local + .set_integer(tparams::INITIAL_MAX_STREAMS_BIDI, self.max_streams_bidi); + tps.local + .set_integer(tparams::INITIAL_MAX_STREAMS_UNI, self.max_streams_uni); + tps.local.set_integer( + tparams::IDLE_TIMEOUT, + u64::try_from(self.idle_timeout.as_millis()).unwrap_or(0), + ); + if let PreferredAddressConfig::Address(preferred) = &self.preferred_address { + if role == Role::Server { + let (cid, srt) = cid_manager.preferred_address_cid()?; + tps.local.set( + tparams::PREFERRED_ADDRESS, + TransportParameter::PreferredAddress { + v4: preferred.ipv4(), + v6: preferred.ipv6(), + cid, + srt, + }, + ); + } + } + tps.local + .set_integer(tparams::MAX_DATAGRAM_FRAME_SIZE, self.datagram_size); + Ok(tps) + } +} diff --git a/third_party/rust/neqo-transport/src/connection/saved.rs b/third_party/rust/neqo-transport/src/connection/saved.rs new file mode 100644 index 0000000000..368a859f5d --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/saved.rs @@ -0,0 +1,68 @@ +// 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 std::mem; +use std::time::Instant; + +use crate::crypto::CryptoSpace; +use neqo_common::{qdebug, qinfo, Datagram}; + +/// The number of datagrams that are saved during the handshake when +/// keys to decrypt them are not yet available. +const MAX_SAVED_DATAGRAMS: usize = 4; + +pub struct SavedDatagram { + /// The datagram. + pub d: Datagram, + /// The time that the datagram was received. + pub t: Instant, +} + +#[derive(Default)] +pub struct SavedDatagrams { + handshake: Vec<SavedDatagram>, + application_data: Vec<SavedDatagram>, + available: Option<CryptoSpace>, +} + +impl SavedDatagrams { + fn store(&mut self, cspace: CryptoSpace) -> &mut Vec<SavedDatagram> { + match cspace { + CryptoSpace::Handshake => &mut self.handshake, + CryptoSpace::ApplicationData => &mut self.application_data, + _ => panic!("unexpected space"), + } + } + + pub fn save(&mut self, cspace: CryptoSpace, d: Datagram, t: Instant) { + let store = self.store(cspace); + + if store.len() < MAX_SAVED_DATAGRAMS { + qdebug!("saving datagram of {} bytes", d.len()); + store.push(SavedDatagram { d, t }); + } else { + qinfo!("not saving datagram of {} bytes", d.len()); + } + } + + pub fn make_available(&mut self, cspace: CryptoSpace) { + debug_assert_ne!(cspace, CryptoSpace::ZeroRtt); + debug_assert_ne!(cspace, CryptoSpace::Initial); + if !self.store(cspace).is_empty() { + self.available = Some(cspace); + } + } + + pub fn available(&self) -> Option<CryptoSpace> { + self.available + } + + pub fn take_saved(&mut self) -> Vec<SavedDatagram> { + self.available + .take() + .map_or_else(Vec::new, |cspace| mem::take(self.store(cspace))) + } +} diff --git a/third_party/rust/neqo-transport/src/connection/state.rs b/third_party/rust/neqo-transport/src/connection/state.rs new file mode 100644 index 0000000000..a34c91865e --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/state.rs @@ -0,0 +1,279 @@ +// 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 neqo_common::Encoder; +use std::cmp::{min, Ordering}; +use std::mem; +use std::rc::Rc; +use std::time::Instant; + +use crate::frame::{ + FrameType, FRAME_TYPE_CONNECTION_CLOSE_APPLICATION, FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT, + FRAME_TYPE_HANDSHAKE_DONE, +}; +use crate::packet::PacketBuilder; +use crate::path::PathRef; +use crate::recovery::RecoveryToken; +use crate::{ConnectionError, Error, Res}; + +#[derive(Clone, Debug, PartialEq, Eq)] +/// The state of the Connection. +pub enum State { + /// A newly created connection. + Init, + /// Waiting for the first Initial packet. + WaitInitial, + /// Waiting to confirm which version was selected. + /// For a client, this is confirmed when a CRYPTO frame is received; + /// the version of the packet determines the version. + /// For a server, this is confirmed when transport parameters are + /// received and processed. + WaitVersion, + /// Exchanging Handshake packets. + Handshaking, + Connected, + Confirmed, + Closing { + error: ConnectionError, + timeout: Instant, + }, + Draining { + error: ConnectionError, + timeout: Instant, + }, + Closed(ConnectionError), +} + +impl State { + #[must_use] + pub fn connected(&self) -> bool { + matches!(self, Self::Connected | Self::Confirmed) + } + + #[must_use] + pub fn closed(&self) -> bool { + matches!( + self, + Self::Closing { .. } | Self::Draining { .. } | Self::Closed(_) + ) + } + + pub fn error(&self) -> Option<&ConnectionError> { + if let Self::Closing { error, .. } | Self::Draining { error, .. } | Self::Closed(error) = + self + { + Some(error) + } else { + None + } + } +} + +// Implement `PartialOrd` so that we can enforce monotonic state progression. +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + if mem::discriminant(self) == mem::discriminant(other) { + return Ordering::Equal; + } + #[allow(clippy::match_same_arms)] // Lint bug: rust-lang/rust-clippy#860 + match (self, other) { + (Self::Init, _) => Ordering::Less, + (_, Self::Init) => Ordering::Greater, + (Self::WaitInitial, _) => Ordering::Less, + (_, Self::WaitInitial) => Ordering::Greater, + (Self::WaitVersion, _) => Ordering::Less, + (_, Self::WaitVersion) => Ordering::Greater, + (Self::Handshaking, _) => Ordering::Less, + (_, Self::Handshaking) => Ordering::Greater, + (Self::Connected, _) => Ordering::Less, + (_, Self::Connected) => Ordering::Greater, + (Self::Confirmed, _) => Ordering::Less, + (_, Self::Confirmed) => Ordering::Greater, + (Self::Closing { .. }, _) => Ordering::Less, + (_, Self::Closing { .. }) => Ordering::Greater, + (Self::Draining { .. }, _) => Ordering::Less, + (_, Self::Draining { .. }) => Ordering::Greater, + (Self::Closed(_), _) => unreachable!(), + } + } +} + +#[derive(Debug, Clone)] +pub struct ClosingFrame { + path: PathRef, + error: ConnectionError, + frame_type: FrameType, + reason_phrase: Vec<u8>, +} + +impl ClosingFrame { + fn new( + path: PathRef, + error: ConnectionError, + frame_type: FrameType, + message: impl AsRef<str>, + ) -> Self { + let reason_phrase = message.as_ref().as_bytes().to_vec(); + Self { + path, + error, + frame_type, + reason_phrase, + } + } + + pub fn path(&self) -> &PathRef { + &self.path + } + + pub fn sanitize(&self) -> Option<Self> { + if let ConnectionError::Application(_) = self.error { + // The default CONNECTION_CLOSE frame that is sent when an application + // error code needs to be sent in an Initial or Handshake packet. + Some(Self { + path: Rc::clone(&self.path), + error: ConnectionError::Transport(Error::ApplicationError), + frame_type: 0, + reason_phrase: Vec::new(), + }) + } else { + None + } + } + + pub fn write_frame(&self, builder: &mut PacketBuilder) { + // Allow 8 bytes for the reason phrase to ensure that if it needs to be + // truncated there is still at least a few bytes of the value. + if builder.remaining() < 1 + 8 + 8 + 2 + 8 { + return; + } + match &self.error { + ConnectionError::Transport(e) => { + builder.encode_varint(FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT); + builder.encode_varint(e.code()); + builder.encode_varint(self.frame_type); + } + ConnectionError::Application(code) => { + builder.encode_varint(FRAME_TYPE_CONNECTION_CLOSE_APPLICATION); + builder.encode_varint(*code); + } + } + // Truncate the reason phrase if it doesn't fit. As we send this frame in + // multiple packet number spaces, limit the overall size to 256. + let available = min(256, builder.remaining()); + let reason = if available < Encoder::vvec_len(self.reason_phrase.len()) { + &self.reason_phrase[..available - 2] + } else { + &self.reason_phrase + }; + builder.encode_vvec(reason); + } +} + +/// `StateSignaling` manages whether we need to send HANDSHAKE_DONE and CONNECTION_CLOSE. +/// Valid state transitions are: +/// * Idle -> HandshakeDone: at the server when the handshake completes +/// * HandshakeDone -> Idle: when a HANDSHAKE_DONE frame is sent +/// * Idle/HandshakeDone -> Closing/Draining: when closing or draining +/// * Closing/Draining -> CloseSent: after sending CONNECTION_CLOSE +/// * CloseSent -> Closing: any time a new CONNECTION_CLOSE is needed +/// * -> Reset: from any state in case of a stateless reset +#[derive(Debug, Clone)] +pub enum StateSignaling { + Idle, + HandshakeDone, + /// These states save the frame that needs to be sent. + Closing(ClosingFrame), + Draining(ClosingFrame), + /// This state saves the frame that might need to be sent again. + /// If it is `None`, then we are draining and don't send. + CloseSent(Option<ClosingFrame>), + Reset, +} + +impl StateSignaling { + pub fn handshake_done(&mut self) { + if !matches!(self, Self::Idle) { + debug_assert!(false, "StateSignaling must be in Idle state."); + return; + } + *self = Self::HandshakeDone + } + + pub fn write_done(&mut self, builder: &mut PacketBuilder) -> Res<Option<RecoveryToken>> { + if matches!(self, Self::HandshakeDone) && builder.remaining() >= 1 { + *self = Self::Idle; + builder.encode_varint(FRAME_TYPE_HANDSHAKE_DONE); + if builder.len() > builder.limit() { + return Err(Error::InternalError(14)); + } + Ok(Some(RecoveryToken::HandshakeDone)) + } else { + Ok(None) + } + } + + pub fn close( + &mut self, + path: PathRef, + error: ConnectionError, + frame_type: FrameType, + message: impl AsRef<str>, + ) { + if !matches!(self, Self::Reset) { + *self = Self::Closing(ClosingFrame::new(path, error, frame_type, message)); + } + } + + pub fn drain( + &mut self, + path: PathRef, + error: ConnectionError, + frame_type: FrameType, + message: impl AsRef<str>, + ) { + if !matches!(self, Self::Reset) { + *self = Self::Draining(ClosingFrame::new(path, error, frame_type, message)); + } + } + + /// If a close is pending, take a frame. + pub fn close_frame(&mut self) -> Option<ClosingFrame> { + match self { + Self::Closing(frame) => { + // When we are closing, we might need to send the close frame again. + let res = Some(frame.clone()); + *self = Self::CloseSent(Some(frame.clone())); + res + } + Self::Draining(frame) => { + // When we are draining, just send once. + let res = Some(frame.clone()); + *self = Self::CloseSent(None); + res + } + _ => None, + } + } + + /// If a close can be sent again, prepare to send it again. + pub fn send_close(&mut self) { + if let Self::CloseSent(Some(frame)) = self { + *self = Self::Closing(frame.clone()); + } + } + + /// We just got a stateless reset. Terminate. + pub fn reset(&mut self) { + *self = Self::Reset; + } +} diff --git a/third_party/rust/neqo-transport/src/connection/test_internal.rs b/third_party/rust/neqo-transport/src/connection/test_internal.rs new file mode 100644 index 0000000000..353c38e526 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/test_internal.rs @@ -0,0 +1,13 @@ +// 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. + +// Some access to internal connection stuff for testing purposes. + +use crate::packet::PacketBuilder; + +pub trait FrameWriter { + fn write_frames(&mut self, builder: &mut PacketBuilder); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/ackrate.rs b/third_party/rust/neqo-transport/src/connection/tests/ackrate.rs new file mode 100644 index 0000000000..fb9a95529d --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/ackrate.rs @@ -0,0 +1,182 @@ +// 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::super::{ConnectionParameters, ACK_RATIO_SCALE}; +use super::{ + ack_bytes, connect_rtt_idle, default_client, default_server, fill_cwnd, increase_cwnd, + induce_persistent_congestion, new_client, new_server, send_something, DEFAULT_RTT, +}; +use crate::stream_id::StreamType; + +use std::mem; +use std::time::Duration; +use test_fixture::{addr_v4, assertions}; + +/// With the default RTT here (100ms) and default ratio (4), endpoints won't send +/// `ACK_FREQUENCY` as the ACK delay isn't different enough from the default. +#[test] +fn ack_rate_default() { + let mut client = default_client(); + let mut server = default_server(); + let _ = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + assert_eq!(client.stats().frame_tx.ack_frequency, 0); + assert_eq!(server.stats().frame_tx.ack_frequency, 0); +} + +/// When the congestion window increases, the rate doesn't change. +#[test] +fn ack_rate_slow_start() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Increase the congestion window a few times. + let stream = client.stream_create(StreamType::UniDi).unwrap(); + let now = increase_cwnd(&mut client, &mut server, stream, now); + let now = increase_cwnd(&mut client, &mut server, stream, now); + let _ = increase_cwnd(&mut client, &mut server, stream, now); + + // The client should not have sent an ACK_FREQUENCY frame, even + // though the value would have updated. + assert_eq!(client.stats().frame_tx.ack_frequency, 0); + assert_eq!(server.stats().frame_rx.ack_frequency, 0); +} + +/// When the congestion window decreases, a frame is sent. +#[test] +fn ack_rate_exit_slow_start() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Increase the congestion window a few times, enough that after a loss, + // there are enough packets in the window to increase the packet + // count in ACK_FREQUENCY frames. + let stream = client.stream_create(StreamType::UniDi).unwrap(); + let now = increase_cwnd(&mut client, &mut server, stream, now); + let now = increase_cwnd(&mut client, &mut server, stream, now); + + // Now fill the congestion window and drop the first packet. + let (mut pkts, mut now) = fill_cwnd(&mut client, stream, now); + pkts.remove(0); + + // After acknowledging the other packets the client will notice the loss. + now += DEFAULT_RTT / 2; + let ack = ack_bytes(&mut server, stream, pkts, now); + + // Receiving the ACK will cause the client to reduce its congestion window + // and to send ACK_FREQUENCY. + now += DEFAULT_RTT / 2; + assert_eq!(client.stats().frame_tx.ack_frequency, 0); + let af = client.process(Some(ack), now).dgram(); + assert!(af.is_some()); + assert_eq!(client.stats().frame_tx.ack_frequency, 1); +} + +/// When the congestion window collapses, `ACK_FREQUENCY` is updated. +#[test] +fn ack_rate_persistent_congestion() { + // Use a configuration that results in the value being set after exiting + // the handshake. + const RTT: Duration = Duration::from_millis(3); + let mut client = new_client(ConnectionParameters::default().ack_ratio(ACK_RATIO_SCALE)); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, RTT); + + // The client should have sent a frame. + assert_eq!(client.stats().frame_tx.ack_frequency, 1); + + // Now crash the congestion window. + let stream = client.stream_create(StreamType::UniDi).unwrap(); + let (dgrams, mut now) = fill_cwnd(&mut client, stream, now); + now += RTT / 2; + mem::drop(ack_bytes(&mut server, stream, dgrams, now)); + + let now = induce_persistent_congestion(&mut client, &mut server, stream, now); + + // The client sends a second ACK_FREQUENCY frame with an increased rate. + let af = client.process_output(now).dgram(); + assert!(af.is_some()); + assert_eq!(client.stats().frame_tx.ack_frequency, 2); +} + +/// Validate that the configuration works for the client. +#[test] +fn ack_rate_client_one_rtt() { + // This has to be chosen so that the resulting ACK delay is between 1ms and 50ms. + // We also have to avoid values between 20..30ms (approximately). The default + // maximum ACK delay is 25ms and an ACK_FREQUENCY frame won't be sent when the + // change to the maximum ACK delay is too small. + const RTT: Duration = Duration::from_millis(3); + let mut client = new_client(ConnectionParameters::default().ack_ratio(ACK_RATIO_SCALE)); + let mut server = default_server(); + let mut now = connect_rtt_idle(&mut client, &mut server, RTT); + + // A single packet from the client will cause the server to engage its delayed + // acknowledgment timer, which should now be equal to RTT. + let d = send_something(&mut client, now); + now += RTT / 2; + let delay = server.process(Some(d), now).callback(); + assert_eq!(delay, RTT); + + assert_eq!(client.stats().frame_tx.ack_frequency, 1); +} + +/// Validate that the configuration works for the server. +#[test] +fn ack_rate_server_half_rtt() { + const RTT: Duration = Duration::from_millis(10); + let mut client = default_client(); + let mut server = new_server(ConnectionParameters::default().ack_ratio(ACK_RATIO_SCALE * 2)); + let mut now = connect_rtt_idle(&mut client, &mut server, RTT); + + let d = send_something(&mut server, now); + now += RTT / 2; + let delay = client.process(Some(d), now).callback(); + assert_eq!(delay, RTT / 2); + + assert_eq!(server.stats().frame_tx.ack_frequency, 1); +} + +/// ACK delay calculations are path-specific, +/// so check that they can be sent on new paths. +#[test] +fn migrate_ack_delay() { + // Have the client send ACK_FREQUENCY frames at a normal-ish rate. + let mut client = new_client(ConnectionParameters::default().ack_ratio(ACK_RATIO_SCALE)); + let mut server = default_server(); + let mut now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), true, now) + .unwrap(); + + let client1 = send_something(&mut client, now); + assertions::assert_v4_path(&client1, true); // Contains PATH_CHALLENGE. + let client2 = send_something(&mut client, now); + assertions::assert_v4_path(&client2, false); // Doesn't. Is dropped. + now += DEFAULT_RTT / 2; + server.process_input(client1, now); + + let stream = client.stream_create(StreamType::UniDi).unwrap(); + let now = increase_cwnd(&mut client, &mut server, stream, now); + let now = increase_cwnd(&mut client, &mut server, stream, now); + let now = increase_cwnd(&mut client, &mut server, stream, now); + + // Now lose a packet and force the client to update + let (mut pkts, mut now) = fill_cwnd(&mut client, stream, now); + pkts.remove(0); + now += DEFAULT_RTT / 2; + let ack = ack_bytes(&mut server, stream, pkts, now); + + // After noticing this new loss, the client sends ACK_FREQUENCY. + // It has sent a few before (as we dropped `client2`), so ignore those. + let ad_before = client.stats().frame_tx.ack_frequency; + let af = client.process(Some(ack), now).dgram(); + assert!(af.is_some()); + assert_eq!(client.stats().frame_tx.ack_frequency, ad_before + 1); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/cc.rs b/third_party/rust/neqo-transport/src/connection/tests/cc.rs new file mode 100644 index 0000000000..26e4dbd014 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/cc.rs @@ -0,0 +1,429 @@ +// 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::super::Output; +use super::{ + ack_bytes, assert_full_cwnd, connect_rtt_idle, cwnd, cwnd_avail, cwnd_packets, default_client, + default_server, fill_cwnd, induce_persistent_congestion, send_something, DEFAULT_RTT, + FORCE_IDLE_CLIENT_1RTT_PACKETS, POST_HANDSHAKE_CWND, +}; +use crate::cc::MAX_DATAGRAM_SIZE; +use crate::packet::PacketNumber; +use crate::recovery::{ACK_ONLY_SIZE_LIMIT, PACKET_THRESHOLD}; +use crate::sender::PACING_BURST_SIZE; +use crate::stream_id::StreamType; +use crate::tracking::DEFAULT_ACK_PACKET_TOLERANCE; + +use neqo_common::{qdebug, qinfo, Datagram}; +use std::convert::TryFrom; +use std::mem; +use std::time::Duration; + +#[test] +/// Verify initial CWND is honored. +fn cc_slow_start() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Try to send a lot of data + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + let (c_tx_dgrams, _) = fill_cwnd(&mut client, stream_id, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + assert!(cwnd_avail(&client) < ACK_ONLY_SIZE_LIMIT); +} + +#[test] +/// Verify that CC moves to cong avoidance when a packet is marked lost. +fn cc_slow_start_to_cong_avoidance_recovery_period() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Create stream 0 + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(stream_id, 0); + + // Buffer up lot of data and generate packets + let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, stream_id, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + // Predict the packet number of the last packet sent. + // We have already sent packets in `connect_rtt_idle`, + // so include a fudge factor. + let flight1_largest = + PacketNumber::try_from(c_tx_dgrams.len() + FORCE_IDLE_CLIENT_1RTT_PACKETS).unwrap(); + + // Server: Receive and generate ack + now += DEFAULT_RTT / 2; + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams, now); + assert_eq!( + server.stats().frame_tx.largest_acknowledged, + flight1_largest + ); + + // Client: Process ack + now += DEFAULT_RTT / 2; + client.process_input(s_ack, now); + assert_eq!( + client.stats().frame_rx.largest_acknowledged, + flight1_largest + ); + + // Client: send more + let (mut c_tx_dgrams, mut now) = fill_cwnd(&mut client, stream_id, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND * 2); + let flight2_largest = flight1_largest + u64::try_from(c_tx_dgrams.len()).unwrap(); + + // Server: Receive and generate ack again, but drop first packet + now += DEFAULT_RTT / 2; + c_tx_dgrams.remove(0); + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams, now); + assert_eq!( + server.stats().frame_tx.largest_acknowledged, + flight2_largest + ); + + // Client: Process ack + now += DEFAULT_RTT / 2; + client.process_input(s_ack, now); + assert_eq!( + client.stats().frame_rx.largest_acknowledged, + flight2_largest + ); +} + +#[test] +/// Verify that CC stays in recovery period when packet sent before start of +/// recovery period is acked. +fn cc_cong_avoidance_recovery_period_unchanged() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Create stream 0 + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(stream_id, 0); + + // Buffer up lot of data and generate packets + let (mut c_tx_dgrams, now) = fill_cwnd(&mut client, stream_id, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + + // Drop 0th packet. When acked, this should put client into CARP. + c_tx_dgrams.remove(0); + + let c_tx_dgrams2 = c_tx_dgrams.split_off(5); + + // Server: Receive and generate ack + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams, now); + client.process_input(s_ack, now); + + let cwnd1 = cwnd(&client); + + // Generate ACK for more received packets + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams2, now); + + // ACK more packets but they were sent before end of recovery period + client.process_input(s_ack, now); + + // cwnd should not have changed since ACKed packets were sent before + // recovery period expired + let cwnd2 = cwnd(&client); + assert_eq!(cwnd1, cwnd2); +} + +#[test] +/// Ensure that a single packet is sent after entering recovery, even +/// when that exceeds the available congestion window. +fn single_packet_on_recovery() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Drop a few packets, up to the reordering threshold. + for _ in 0..PACKET_THRESHOLD { + let _dropped = send_something(&mut client, now); + } + let delivered = send_something(&mut client, now); + + // Now fill the congestion window. + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(stream_id, 0); + let (_, now) = fill_cwnd(&mut client, stream_id, now); + assert!(cwnd_avail(&client) < ACK_ONLY_SIZE_LIMIT); + + // Acknowledge just one packet and cause one packet to be declared lost. + // The length is the amount of credit the client should have. + let ack = server.process(Some(delivered), now).dgram(); + assert!(ack.is_some()); + + // The client should see the loss and enter recovery. + // As there are many outstanding packets, there should be no available cwnd. + client.process_input(ack.unwrap(), now); + assert_eq!(cwnd_avail(&client), 0); + + // The client should send one packet, ignoring the cwnd. + let dgram = client.process_output(now).dgram(); + assert!(dgram.is_some()); +} + +#[test] +/// Verify that CC moves out of recovery period when packet sent after start +/// of recovery period is acked. +fn cc_cong_avoidance_recovery_period_to_cong_avoidance() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Create stream 0 + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(stream_id, 0); + + // Buffer up lot of data and generate packets + let (mut c_tx_dgrams, mut now) = fill_cwnd(&mut client, stream_id, now); + + // Drop 0th packet. When acked, this should put client into CARP. + c_tx_dgrams.remove(0); + + // Server: Receive and generate ack + now += DEFAULT_RTT / 2; + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams, now); + + // Client: Process ack + now += DEFAULT_RTT / 2; + client.process_input(s_ack, now); + + // Should be in CARP now. + now += DEFAULT_RTT / 2; + qinfo!("moving to congestion avoidance {}", cwnd(&client)); + + // Now make sure that we increase congestion window according to the + // accurate byte counting version of congestion avoidance. + // Check over several increases to be sure. + let mut expected_cwnd = cwnd(&client); + // Fill cwnd. + let (mut c_tx_dgrams, next_now) = fill_cwnd(&mut client, stream_id, now); + now = next_now; + for i in 0..5 { + qinfo!("iteration {}", i); + + let c_tx_size: usize = c_tx_dgrams.iter().map(|d| d.len()).sum(); + qinfo!( + "client sending {} bytes into cwnd of {}", + c_tx_size, + cwnd(&client) + ); + assert_eq!(c_tx_size, expected_cwnd); + + // As acks arrive we will continue filling cwnd and save all packets + // from this cycle will be stored in next_c_tx_dgrams. + let mut next_c_tx_dgrams: Vec<Datagram> = Vec::new(); + + // Until we process all the packets, the congestion window remains the same. + // Note that we need the client to process ACK frames in stages, so split the + // datagrams into two, ensuring that we allow for an ACK for each batch. + let most = c_tx_dgrams.len() - usize::try_from(DEFAULT_ACK_PACKET_TOLERANCE).unwrap() - 1; + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams.drain(..most), now); + assert_eq!(cwnd(&client), expected_cwnd); + client.process_input(s_ack, now); + // make sure to fill cwnd again. + let (mut new_pkts, next_now) = fill_cwnd(&mut client, stream_id, now); + now = next_now; + next_c_tx_dgrams.append(&mut new_pkts); + + let s_ack = ack_bytes(&mut server, stream_id, c_tx_dgrams, now); + assert_eq!(cwnd(&client), expected_cwnd); + client.process_input(s_ack, now); + // make sure to fill cwnd again. + let (mut new_pkts, next_now) = fill_cwnd(&mut client, stream_id, now); + now = next_now; + next_c_tx_dgrams.append(&mut new_pkts); + + expected_cwnd += MAX_DATAGRAM_SIZE; + assert_eq!(cwnd(&client), expected_cwnd); + c_tx_dgrams = next_c_tx_dgrams; + } +} + +#[test] +/// Verify transition to persistent congestion state if conditions are met. +fn cc_slow_start_to_persistent_congestion_no_acks() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + let stream = client.stream_create(StreamType::BiDi).unwrap(); + + // Buffer up lot of data and generate packets + let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, stream, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + + // Server: Receive and generate ack + now += DEFAULT_RTT / 2; + mem::drop(ack_bytes(&mut server, stream, c_tx_dgrams, now)); + + // ACK lost. + induce_persistent_congestion(&mut client, &mut server, stream, now); +} + +#[test] +/// Verify transition to persistent congestion state if conditions are met. +fn cc_slow_start_to_persistent_congestion_some_acks() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Create stream 0 + let stream = client.stream_create(StreamType::BiDi).unwrap(); + + // Buffer up lot of data and generate packets + let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, stream, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + + // Server: Receive and generate ack + now += Duration::from_millis(100); + let s_ack = ack_bytes(&mut server, stream, c_tx_dgrams, now); + + now += Duration::from_millis(100); + client.process_input(s_ack, now); + + // send bytes that will be lost + let (_, next_now) = fill_cwnd(&mut client, stream, now); + now = next_now + Duration::from_millis(100); + + induce_persistent_congestion(&mut client, &mut server, stream, now); +} + +#[test] +/// Verify persistent congestion moves to slow start after recovery period +/// ends. +fn cc_persistent_congestion_to_slow_start() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Create stream 0 + let stream = client.stream_create(StreamType::BiDi).unwrap(); + + // Buffer up lot of data and generate packets + let (c_tx_dgrams, mut now) = fill_cwnd(&mut client, stream, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + + // Server: Receive and generate ack + now += Duration::from_millis(10); + mem::drop(ack_bytes(&mut server, stream, c_tx_dgrams, now)); + + // ACK lost. + + now = induce_persistent_congestion(&mut client, &mut server, stream, now); + + // New part of test starts here + + now += Duration::from_millis(10); + + // Send packets from after start of CARP + let (c_tx_dgrams, next_now) = fill_cwnd(&mut client, stream, now); + assert_eq!(c_tx_dgrams.len(), 2); + + // Server: Receive and generate ack + now = next_now + Duration::from_millis(100); + let s_ack = ack_bytes(&mut server, stream, c_tx_dgrams, now); + + // No longer in CARP. (pkts acked from after start of CARP) + // Should be in slow start now. + client.process_input(s_ack, now); + + // ACKing 2 packets should let client send 4. + let (c_tx_dgrams, _) = fill_cwnd(&mut client, stream, now); + assert_eq!(c_tx_dgrams.len(), 4); +} + +#[test] +fn ack_are_not_cc() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Create a stream + let stream = client.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(stream, 0); + + // Buffer up lot of data and generate packets, so that cc window is filled. + let (c_tx_dgrams, now) = fill_cwnd(&mut client, stream, now); + assert_full_cwnd(&c_tx_dgrams, POST_HANDSHAKE_CWND); + + // The server hasn't received any of these packets yet, the server + // won't ACK, but if it sends an ack-eliciting packet instead. + qdebug!([server], "Sending ack-eliciting"); + let other_stream = server.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(other_stream, 1); + server.stream_send(other_stream, b"dropped").unwrap(); + let dropped_packet = server.process(None, now).dgram(); + assert!(dropped_packet.is_some()); // Now drop this one. + + // Now the server sends a packet that will force an ACK, + // because the client will detect a gap. + server.stream_send(other_stream, b"sent").unwrap(); + let ack_eliciting_packet = server.process(None, now).dgram(); + assert!(ack_eliciting_packet.is_some()); + + // The client can ack the server packet even if cc windows is full. + qdebug!([client], "Process ack-eliciting"); + let ack_pkt = client.process(ack_eliciting_packet, now).dgram(); + assert!(ack_pkt.is_some()); + qdebug!([server], "Handle ACK"); + let prev_ack_count = server.stats().frame_rx.ack; + server.process_input(ack_pkt.unwrap(), now); + assert_eq!(server.stats().frame_rx.ack, prev_ack_count + 1); +} + +#[test] +fn pace() { + const DATA: &[u8] = &[0xcc; 4_096]; + let mut client = default_client(); + let mut server = default_server(); + let mut now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Now fill up the pipe and watch it trickle out. + let stream = client.stream_create(StreamType::BiDi).unwrap(); + loop { + let written = client.stream_send(stream, DATA).unwrap(); + if written < DATA.len() { + break; + } + } + let mut count = 0; + // We should get a burst at first. + // The first packet is not subject to pacing as there are no bytes in flight. + // After that we allow the burst to continue up to a number of packets (2). + for _ in 0..=PACING_BURST_SIZE { + let dgram = client.process_output(now).dgram(); + assert!(dgram.is_some()); + count += 1; + } + let gap = client.process_output(now).callback(); + assert_ne!(gap, Duration::new(0, 0)); + for _ in (1 + PACING_BURST_SIZE)..cwnd_packets(POST_HANDSHAKE_CWND) { + match client.process_output(now) { + Output::Callback(t) => assert_eq!(t, gap), + Output::Datagram(_) => { + // The last packet might not be paced. + count += 1; + break; + } + Output::None => panic!(), + } + now += gap; + let dgram = client.process_output(now).dgram(); + assert!(dgram.is_some()); + count += 1; + } + let dgram = client.process_output(now).dgram(); + assert!(dgram.is_none()); + assert_eq!(count, cwnd_packets(POST_HANDSHAKE_CWND)); + let fin = client.process_output(now).callback(); + assert_ne!(fin, Duration::new(0, 0)); + assert_ne!(fin, gap); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/close.rs b/third_party/rust/neqo-transport/src/connection/tests/close.rs new file mode 100644 index 0000000000..a9f1fafa25 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/close.rs @@ -0,0 +1,206 @@ +// 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::super::{Connection, Output, State}; +use super::{connect, connect_force_idle, default_client, default_server, send_something}; +use crate::tparams::{self, TransportParameter}; +use crate::{AppError, ConnectionError, Error, ERROR_APPLICATION_CLOSE}; + +use neqo_common::Datagram; +use std::time::Duration; +use test_fixture::{self, addr, now}; + +fn assert_draining(c: &Connection, expected: &Error) { + assert!(c.state().closed()); + if let State::Draining { + error: ConnectionError::Transport(error), + .. + } = c.state() + { + assert_eq!(error, expected); + } else { + panic!(); + } +} + +#[test] +fn connection_close() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let now = now(); + + client.close(now, 42, ""); + + let out = client.process(None, now); + + server.process_input(out.dgram().unwrap(), now); + assert_draining(&server, &Error::PeerApplicationError(42)); +} + +#[test] +fn connection_close_with_long_reason_string() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let now = now(); + // Create a long string and use it as the close reason. + let long_reason = String::from_utf8([0x61; 2048].to_vec()).unwrap(); + client.close(now, 42, long_reason); + + let out = client.process(None, now); + + server.process_input(out.dgram().unwrap(), now); + assert_draining(&server, &Error::PeerApplicationError(42)); +} + +// During the handshake, an application close should be sanitized. +#[test] +fn early_application_close() { + let mut client = default_client(); + let mut server = default_server(); + + // One flight each. + let dgram = client.process(None, now()).dgram(); + assert!(dgram.is_some()); + let dgram = server.process(dgram, now()).dgram(); + assert!(dgram.is_some()); + + server.close(now(), 77, String::new()); + assert!(server.state().closed()); + let dgram = server.process(None, now()).dgram(); + assert!(dgram.is_some()); + + client.process_input(dgram.unwrap(), now()); + assert_draining(&client, &Error::PeerError(ERROR_APPLICATION_CLOSE)); +} + +#[test] +fn bad_tls_version() { + let mut client = default_client(); + // Do a bad, bad thing. + client + .crypto + .tls + .set_option(neqo_crypto::Opt::Tls13CompatMode, true) + .unwrap(); + let mut server = default_server(); + + let dgram = client.process(None, now()).dgram(); + assert!(dgram.is_some()); + let dgram = server.process(dgram, now()).dgram(); + assert_eq!( + *server.state(), + State::Closed(ConnectionError::Transport(Error::ProtocolViolation)) + ); + assert!(dgram.is_some()); + client.process_input(dgram.unwrap(), now()); + assert_draining(&client, &Error::PeerError(Error::ProtocolViolation.code())); +} + +/// Test the interaction between the loss recovery timer +/// and the closing timer. +#[test] +fn closing_timers_interation() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let mut now = now(); + + // We're going to induce time-based loss recovery so that timer is set. + let _p1 = send_something(&mut client, now); + let p2 = send_something(&mut client, now); + let ack = server.process(Some(p2), now).dgram(); + assert!(ack.is_some()); // This is an ACK. + + // After processing the ACK, we should be on the loss recovery timer. + let cb = client.process(ack, now).callback(); + assert_ne!(cb, Duration::from_secs(0)); + now += cb; + + // Rather than let the timer pop, close the connection. + client.close(now, 0, ""); + let client_close = client.process(None, now).dgram(); + assert!(client_close.is_some()); + // This should now report the end of the closing period, not a + // zero-duration wait driven by the (now defunct) loss recovery timer. + let client_close_timer = client.process(None, now).callback(); + assert_ne!(client_close_timer, Duration::from_secs(0)); +} + +#[test] +fn closing_and_draining() { + const APP_ERROR: AppError = 7; + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // Save a packet from the client for later. + let p1 = send_something(&mut client, now()); + + // Close the connection. + client.close(now(), APP_ERROR, ""); + let client_close = client.process(None, now()).dgram(); + assert!(client_close.is_some()); + let client_close_timer = client.process(None, now()).callback(); + assert_ne!(client_close_timer, Duration::from_secs(0)); + + // The client will spit out the same packet in response to anything it receives. + let p3 = send_something(&mut server, now()); + let client_close2 = client.process(Some(p3), now()).dgram(); + assert_eq!( + client_close.as_ref().unwrap().len(), + client_close2.as_ref().unwrap().len() + ); + + // After this time, the client should transition to closed. + let end = client.process(None, now() + client_close_timer); + assert_eq!(end, Output::None); + assert_eq!( + *client.state(), + State::Closed(ConnectionError::Application(APP_ERROR)) + ); + + // When the server receives the close, it too should generate CONNECTION_CLOSE. + let server_close = server.process(client_close, now()).dgram(); + assert!(server.state().closed()); + assert!(server_close.is_some()); + // .. but it ignores any further close packets. + let server_close_timer = server.process(client_close2, now()).callback(); + assert_ne!(server_close_timer, Duration::from_secs(0)); + // Even a legitimate packet without a close in it. + let server_close_timer2 = server.process(Some(p1), now()).callback(); + assert_eq!(server_close_timer, server_close_timer2); + + let end = server.process(None, now() + server_close_timer); + assert_eq!(end, Output::None); + assert_eq!( + *server.state(), + State::Closed(ConnectionError::Transport(Error::PeerApplicationError( + APP_ERROR + ))) + ); +} + +/// Test that a client can handle a stateless reset correctly. +#[test] +fn stateless_reset_client() { + let mut client = default_client(); + let mut server = default_server(); + server + .set_local_tparam( + tparams::STATELESS_RESET_TOKEN, + TransportParameter::Bytes(vec![77; 16]), + ) + .unwrap(); + connect_force_idle(&mut client, &mut server); + + client.process_input(Datagram::new(addr(), addr(), vec![77; 21]), now()); + assert_draining(&client, &Error::StatelessReset); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/datagram.rs b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs new file mode 100644 index 0000000000..f81f52ee98 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs @@ -0,0 +1,534 @@ +// 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::{ + assert_error, connect_force_idle, default_client, default_server, new_client, new_server, + AT_LEAST_PTO, +}; +use crate::events::{ConnectionEvent, OutgoingDatagramOutcome}; +use crate::frame::FRAME_TYPE_DATAGRAM; +use crate::packet::PacketBuilder; +use crate::quic_datagrams::MAX_QUIC_DATAGRAM; +use crate::{Connection, ConnectionError, ConnectionParameters, Error}; +use neqo_common::event::Provider; +use std::cell::RefCell; +use std::convert::TryFrom; +use std::rc::Rc; +use test_fixture::now; + +const DATAGRAM_LEN_MTU: u64 = 1310; +const DATA_MTU: &[u8] = &[1; 1310]; +const DATA_BIGGER_THAN_MTU: &[u8] = &[0; 2620]; +const DATAGRAM_LEN_SMALLER_THAN_MTU: u64 = 1200; +const DATA_SMALLER_THAN_MTU: &[u8] = &[0; 1200]; +const DATA_SMALLER_THAN_MTU_2: &[u8] = &[0; 600]; +const OUTGOING_QUEUE: usize = 2; + +struct InsertDatagram<'a> { + data: &'a [u8], +} + +impl crate::connection::test_internal::FrameWriter for InsertDatagram<'_> { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + builder.encode_varint(FRAME_TYPE_DATAGRAM); + builder.encode(self.data); + } +} + +#[test] +fn datagram_disabled_both() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + assert_eq!(client.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!(server.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU, None), + Err(Error::TooMuchData) + ); + assert_eq!(server.stats().frame_tx.datagram, 0); + assert_eq!( + server.send_datagram(DATA_SMALLER_THAN_MTU, None), + Err(Error::TooMuchData) + ); + assert_eq!(server.stats().frame_tx.datagram, 0); +} + +#[test] +fn datagram_enabled_on_client() { + let mut client = + new_client(ConnectionParameters::default().datagram_size(DATAGRAM_LEN_SMALLER_THAN_MTU)); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + assert_eq!(client.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!( + server.max_datagram_size(), + Ok(DATAGRAM_LEN_SMALLER_THAN_MTU) + ); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), + Err(Error::TooMuchData) + ); + let dgram_sent = server.stats().frame_tx.datagram; + assert_eq!(server.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let out = server.process_output(now()).dgram().unwrap(); + assert_eq!(server.stats().frame_tx.datagram, dgram_sent + 1); + + client.process_input(out, now()); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU + )); +} + +#[test] +fn datagram_enabled_on_server() { + let mut client = default_client(); + let mut server = + new_server(ConnectionParameters::default().datagram_size(DATAGRAM_LEN_SMALLER_THAN_MTU)); + connect_force_idle(&mut client, &mut server); + + assert_eq!( + client.max_datagram_size(), + Ok(DATAGRAM_LEN_SMALLER_THAN_MTU) + ); + assert_eq!(server.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!( + server.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), + Err(Error::TooMuchData) + ); + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let out = client.process_output(now()).dgram().unwrap(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + server.process_input(out, now()); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU + )); +} + +fn connect_datagram() -> (Connection, Connection) { + let mut client = new_client( + ConnectionParameters::default() + .datagram_size(MAX_QUIC_DATAGRAM) + .outgoing_datagram_queue(OUTGOING_QUEUE), + ); + let mut server = new_server(ConnectionParameters::default().datagram_size(MAX_QUIC_DATAGRAM)); + connect_force_idle(&mut client, &mut server); + (client, server) +} + +#[test] +fn mtu_limit() { + let (client, server) = connect_datagram(); + + assert_eq!(client.max_datagram_size(), Ok(DATAGRAM_LEN_MTU)); + assert_eq!(server.max_datagram_size(), Ok(DATAGRAM_LEN_MTU)); +} + +#[test] +fn limit_data_size() { + let (mut client, mut server) = connect_datagram(); + + assert!(u64::try_from(DATA_BIGGER_THAN_MTU.len()).unwrap() > DATAGRAM_LEN_MTU); + // Datagram can be queued because they are smaller than allowed by the peer, + // but they cannot be sent. + assert_eq!(server.send_datagram(DATA_BIGGER_THAN_MTU, Some(1)), Ok(())); + + let dgram_dropped_s = server.stats().datagram_tx.dropped_too_big; + let dgram_sent_s = server.stats().frame_tx.datagram; + assert!(server.process_output(now()).dgram().is_none()); + assert_eq!( + server.stats().datagram_tx.dropped_too_big, + dgram_dropped_s + 1 + ); + assert_eq!(server.stats().frame_tx.datagram, dgram_sent_s); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedTooBig + )); + + // The same test for the client side. + assert_eq!(client.send_datagram(DATA_BIGGER_THAN_MTU, Some(1)), Ok(())); + let dgram_sent_c = client.stats().frame_tx.datagram; + assert!(client.process_output(now()).dgram().is_none()); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent_c); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedTooBig + )); +} + +#[test] +fn after_dgram_dropped_continue_writing_frames() { + let (mut client, _) = connect_datagram(); + + assert!(u64::try_from(DATA_BIGGER_THAN_MTU.len()).unwrap() > DATAGRAM_LEN_MTU); + // Datagram can be queued because they are smaller than allowed by the peer, + // but they cannot be sent. + assert_eq!(client.send_datagram(DATA_BIGGER_THAN_MTU, Some(1)), Ok(())); + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(2)), Ok(())); + + let datagram_dropped = |e| { + matches!( + e, + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedTooBig) + }; + + let dgram_dropped_c = client.stats().datagram_tx.dropped_too_big; + let dgram_sent_c = client.stats().frame_tx.datagram; + + assert!(client.process_output(now()).dgram().is_some()); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent_c + 1); + assert_eq!( + client.stats().datagram_tx.dropped_too_big, + dgram_dropped_c + 1 + ); + assert!(client.events().any(datagram_dropped)); +} + +#[test] +fn datagram_acked() { + let (mut client, mut server) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + let dgram_received = server.stats().frame_rx.datagram; + server.process_input(out.unwrap(), now()); + assert_eq!(server.stats().frame_rx.datagram, dgram_received + 1); + let now = now() + AT_LEAST_PTO; + // Ack should be sent + let ack_sent = server.stats().frame_tx.ack; + let out = server.process_output(now).dgram(); + assert_eq!(server.stats().frame_tx.ack, ack_sent + 1); + + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU + )); + + client.process_input(out.unwrap(), now); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::Acked + )); +} + +#[test] +fn datagram_lost() { + let (mut client, _) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let _out = client.process_output(now()).dgram(); // This packet will be lost. + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + // Wait for PTO + let now = now() + AT_LEAST_PTO; + let dgram_sent2 = client.stats().frame_tx.datagram; + let pings_sent = client.stats().frame_tx.ping; + let dgram_lost = client.stats().datagram_tx.lost; + let out = client.process_output(now).dgram(); + assert!(out.is_some()); //PING probing + // Datagram is not sent again. + assert_eq!(client.stats().frame_tx.ping, pings_sent + 1); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent2); + assert_eq!(client.stats().datagram_tx.lost, dgram_lost + 1); + + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::Lost + )); +} + +#[test] +fn datagram_sent_once() { + let (mut client, _) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let _out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + // Call process_output again should not send any new Datagram. + assert!(client.process_output(now()).dgram().is_none()); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); +} + +#[test] +fn dgram_no_allowed() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + server.test_frame_writer = Some(Box::new(InsertDatagram { data: DATA_MTU })); + let out = server.process_output(now()).dgram().unwrap(); + server.test_frame_writer = None; + + client.process_input(out, now()); + + assert_error( + &client, + &ConnectionError::Transport(Error::ProtocolViolation), + ); +} + +#[test] +#[allow(clippy::assertions_on_constants)] // this is a static assert, thanks +fn dgram_too_big() { + let mut client = + new_client(ConnectionParameters::default().datagram_size(DATAGRAM_LEN_SMALLER_THAN_MTU)); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + assert!(DATAGRAM_LEN_MTU > DATAGRAM_LEN_SMALLER_THAN_MTU); + server.test_frame_writer = Some(Box::new(InsertDatagram { data: DATA_MTU })); + let out = server.process_output(now()).dgram().unwrap(); + server.test_frame_writer = None; + + client.process_input(out, now()); + + assert_error( + &client, + &ConnectionError::Transport(Error::ProtocolViolation), + ); +} + +#[test] +fn outgoing_datagram_queue_full() { + let (mut client, mut server) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU_2, Some(2)), + Ok(()) + ); + + // The outgoing datagram queue limit is 2, therefore the datagram with id 1 + // will be dropped after adding one more datagram. + let dgram_dropped = client.stats().datagram_tx.dropped_queue_full; + assert_eq!(client.send_datagram(DATA_MTU, Some(3)), Ok(())); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedQueueFull + )); + assert_eq!( + client.stats().datagram_tx.dropped_queue_full, + dgram_dropped + 1 + ); + + // Send DATA_SMALLER_THAN_MTU_2 datagram + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + server.process_input(out.unwrap(), now()); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU_2 + )); + + // Send DATA_SMALLER_THAN_MTU_2 datagram + let dgram_sent2 = client.stats().frame_tx.datagram; + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent2 + 1); + server.process_input(out.unwrap(), now()); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_MTU + )); +} + +fn send_datagram(sender: &mut Connection, receiver: &mut Connection, data: &[u8]) { + let dgram_sent = sender.stats().frame_tx.datagram; + assert_eq!(sender.send_datagram(data, Some(1)), Ok(())); + let out = sender.process_output(now()).dgram().unwrap(); + assert_eq!(sender.stats().frame_tx.datagram, dgram_sent + 1); + + let dgram_received = receiver.stats().frame_rx.datagram; + receiver.process_input(out, now()); + assert_eq!(receiver.stats().frame_rx.datagram, dgram_received + 1); +} + +#[test] +fn multiple_datagram_events() { + const DATA_SIZE: usize = 1200; + const MAX_QUEUE: usize = 3; + const FIRST_DATAGRAM: &[u8] = &[0; DATA_SIZE]; + const SECOND_DATAGRAM: &[u8] = &[1; DATA_SIZE]; + const THIRD_DATAGRAM: &[u8] = &[2; DATA_SIZE]; + const FOURTH_DATAGRAM: &[u8] = &[3; DATA_SIZE]; + + let mut client = new_client( + ConnectionParameters::default() + .datagram_size(u64::try_from(DATA_SIZE).unwrap()) + .incoming_datagram_queue(MAX_QUEUE), + ); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + send_datagram(&mut server, &mut client, FIRST_DATAGRAM); + send_datagram(&mut server, &mut client, SECOND_DATAGRAM); + send_datagram(&mut server, &mut client, THIRD_DATAGRAM); + + let mut datagrams = client.events().filter_map(|evt| { + if let ConnectionEvent::Datagram(d) = evt { + Some(d) + } else { + None + } + }); + assert_eq!(datagrams.next().unwrap(), FIRST_DATAGRAM); + assert_eq!(datagrams.next().unwrap(), SECOND_DATAGRAM); + assert_eq!(datagrams.next().unwrap(), THIRD_DATAGRAM); + assert!(datagrams.next().is_none()); + + // New events can be queued. + send_datagram(&mut server, &mut client, FOURTH_DATAGRAM); + let mut datagrams = client.events().filter_map(|evt| { + if let ConnectionEvent::Datagram(d) = evt { + Some(d) + } else { + None + } + }); + assert_eq!(datagrams.next().unwrap(), FOURTH_DATAGRAM); + assert!(datagrams.next().is_none()); +} + +#[test] +fn too_many_datagram_events() { + const DATA_SIZE: usize = 1200; + const MAX_QUEUE: usize = 2; + const FIRST_DATAGRAM: &[u8] = &[0; DATA_SIZE]; + const SECOND_DATAGRAM: &[u8] = &[1; DATA_SIZE]; + const THIRD_DATAGRAM: &[u8] = &[2; DATA_SIZE]; + const FOURTH_DATAGRAM: &[u8] = &[3; DATA_SIZE]; + + let mut client = new_client( + ConnectionParameters::default() + .datagram_size(u64::try_from(DATA_SIZE).unwrap()) + .incoming_datagram_queue(MAX_QUEUE), + ); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + send_datagram(&mut server, &mut client, FIRST_DATAGRAM); + send_datagram(&mut server, &mut client, SECOND_DATAGRAM); + send_datagram(&mut server, &mut client, THIRD_DATAGRAM); + + // Datagram with FIRST_DATAGRAM data will be dropped. + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == SECOND_DATAGRAM + )); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::IncomingDatagramDropped + )); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == THIRD_DATAGRAM + )); + assert!(client.next_event().is_none()); + assert_eq!(client.stats().incoming_datagram_dropped, 1); + + // New events can be queued. + send_datagram(&mut server, &mut client, FOURTH_DATAGRAM); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == FOURTH_DATAGRAM + )); + assert!(client.next_event().is_none()); + assert_eq!(client.stats().incoming_datagram_dropped, 1); +} + +#[test] +fn multiple_quic_datagrams_in_one_packet() { + let (mut client, mut server) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + // Enqueue 2 datagrams that can fit in a single packet. + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU_2, Some(1)), + Ok(()) + ); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU_2, Some(2)), + Ok(()) + ); + + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 2); + server.process_input(out.unwrap(), now()); + let datagram = |e: &_| matches!(e, ConnectionEvent::Datagram(..)); + assert_eq!(server.events().filter(datagram).count(), 2); +} + +/// Datagrams that are close to the capacity of the packet need special +/// handling. They need to use the packet-filling frame type and +/// they cannot allow other frames to follow. +#[test] +fn datagram_fill() { + struct PanickingFrameWriter {} + impl crate::connection::test_internal::FrameWriter for PanickingFrameWriter { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + panic!( + "builder invoked with {} bytes remaining", + builder.remaining() + ); + } + } + struct TrackingFrameWriter { + called: Rc<RefCell<bool>>, + } + impl crate::connection::test_internal::FrameWriter for TrackingFrameWriter { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + assert_eq!(builder.remaining(), 2); + *self.called.borrow_mut() = true; + } + } + + let (mut client, mut server) = connect_datagram(); + + // Work out how much space we have for a datagram. + let space = { + let p = client.paths.primary(); + let path = p.borrow(); + // Minimum overhead is connection ID length, 1 byte short header, 1 byte packet number, + // 1 byte for the DATAGRAM frame type, and 16 bytes for the AEAD. + path.mtu() - path.remote_cid().len() - 19 + }; + assert!(space >= 64); // Unlikely, but this test depends on the datagram being this large. + + // This should not be called. + client.test_frame_writer = Some(Box::new(PanickingFrameWriter {})); + + let buf = vec![9; space]; + // This will completely fill available space. + send_datagram(&mut client, &mut server, &buf); + // This will leave 1 byte free, but more frames won't be added in this space. + send_datagram(&mut client, &mut server, &buf[..buf.len() - 1]); + // This will leave 2 bytes free, which is enough space for a length field, + // but not enough space for another frame after that. + send_datagram(&mut client, &mut server, &buf[..buf.len() - 2]); + // Three bytes free will be space enough for a length frame, but not enough + // space left over for another frame (we need 2 bytes). + send_datagram(&mut client, &mut server, &buf[..buf.len() - 3]); + + // Four bytes free is enough space for another frame. + let called = Rc::new(RefCell::new(false)); + client.test_frame_writer = Some(Box::new(TrackingFrameWriter { + called: Rc::clone(&called), + })); + send_datagram(&mut client, &mut server, &buf[..buf.len() - 4]); + assert!(*called.borrow()); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs b/third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs new file mode 100644 index 0000000000..24201eff26 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs @@ -0,0 +1,43 @@ +// 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. + +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(clippy::pedantic)] +#![cfg(feature = "fuzzing")] + +use super::{connect_force_idle, default_client, default_server}; +use crate::StreamType; +use neqo_crypto::FIXED_TAG_FUZZING; +use test_fixture::now; + +#[test] +fn no_encryption() { + const DATA_CLIENT: &[u8] = &[2; 40]; + const DATA_SERVER: &[u8] = &[3; 50]; + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + + client.stream_send(stream_id, DATA_CLIENT).unwrap(); + let client_pkt = client.process_output(now()).dgram().unwrap(); + assert!(client_pkt[..client_pkt.len() - FIXED_TAG_FUZZING.len()].ends_with(DATA_CLIENT)); + + server.process_input(client_pkt, now()); + let mut buf = vec![0; 100]; + let (len, _) = server.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(len, DATA_CLIENT.len()); + assert_eq!(&buf[..len], DATA_CLIENT); + server.stream_send(stream_id, DATA_SERVER).unwrap(); + let server_pkt = server.process_output(now()).dgram().unwrap(); + assert!(server_pkt[..server_pkt.len() - FIXED_TAG_FUZZING.len()].ends_with(DATA_SERVER)); + + client.process_input(server_pkt, now()); + let (len, _) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(len, DATA_SERVER.len()); + assert_eq!(&buf[..len], DATA_SERVER); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs new file mode 100644 index 0000000000..436468757d --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs @@ -0,0 +1,1129 @@ +// 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::super::{Connection, Output, State}; +use super::{ + assert_error, connect, connect_force_idle, connect_with_rtt, default_client, default_server, + get_tokens, handshake, maybe_authenticate, resumed_server, send_something, + CountingConnectionIdGenerator, AT_LEAST_PTO, DEFAULT_RTT, DEFAULT_STREAM_DATA, +}; +use crate::connection::AddressValidation; +use crate::events::ConnectionEvent; +use crate::path::PATH_MTU_V6; +use crate::server::ValidateAddress; +use crate::tparams::{TransportParameter, MIN_ACK_DELAY}; +use crate::tracking::DEFAULT_ACK_DELAY; +use crate::{ + ConnectionError, ConnectionParameters, EmptyConnectionIdGenerator, Error, StreamType, Version, +}; + +use neqo_common::{event::Provider, qdebug, Datagram}; +use neqo_crypto::{ + constants::TLS_CHACHA20_POLY1305_SHA256, generate_ech_keys, AuthenticationStatus, +}; +use std::cell::RefCell; +use std::convert::TryFrom; +use std::mem; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::rc::Rc; +use std::time::Duration; +use test_fixture::{self, addr, assertions, fixture_init, now, split_datagram}; + +const ECH_CONFIG_ID: u8 = 7; +const ECH_PUBLIC_NAME: &str = "public.example"; + +#[test] +fn full_handshake() { + qdebug!("---- client: generate CH"); + let mut client = default_client(); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + assert_eq!(out.as_dgram_ref().unwrap().len(), PATH_MTU_V6); + + qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN"); + let mut server = default_server(); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + assert_eq!(out.as_dgram_ref().unwrap().len(), PATH_MTU_V6); + + qdebug!("---- client: cert verification"); + let out = client.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_none()); + + assert!(maybe_authenticate(&mut client)); + + qdebug!("---- client: SH..FIN -> FIN"); + let out = client.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + assert_eq!(*client.state(), State::Connected); + + qdebug!("---- server: FIN -> ACKS"); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + assert_eq!(*server.state(), State::Confirmed); + + qdebug!("---- client: ACKS -> 0"); + let out = client.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_none()); + assert_eq!(*client.state(), State::Confirmed); +} + +#[test] +fn handshake_failed_authentication() { + qdebug!("---- client: generate CH"); + let mut client = default_client(); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + + qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN"); + let mut server = default_server(); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + + qdebug!("---- client: cert verification"); + let out = client.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_none()); + + let authentication_needed = |e| matches!(e, ConnectionEvent::AuthenticationNeeded); + assert!(client.events().any(authentication_needed)); + qdebug!("---- client: Alert(certificate_revoked)"); + client.authenticated(AuthenticationStatus::CertRevoked, now()); + + qdebug!("---- client: -> Alert(certificate_revoked)"); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + + qdebug!("---- server: Alert(certificate_revoked)"); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + assert_error(&client, &ConnectionError::Transport(Error::CryptoAlert(44))); + assert_error(&server, &ConnectionError::Transport(Error::PeerError(300))); +} + +#[test] +fn no_alpn() { + fixture_init(); + let mut client = Connection::new_client( + "example.com", + &["bad-alpn"], + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + ConnectionParameters::default(), + now(), + ) + .unwrap(); + let mut server = default_server(); + + handshake(&mut client, &mut server, now(), Duration::new(0, 0)); + // TODO (mt): errors are immediate, which means that we never send CONNECTION_CLOSE + // and the client never sees the server's rejection of its handshake. + //assert_error(&client, ConnectionError::Transport(Error::CryptoAlert(120))); + assert_error( + &server, + &ConnectionError::Transport(Error::CryptoAlert(120)), + ); +} + +#[test] +fn dup_server_flight1() { + qdebug!("---- client: generate CH"); + let mut client = default_client(); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + assert_eq!(out.as_dgram_ref().unwrap().len(), PATH_MTU_V6); + qdebug!("Output={:0x?}", out.as_dgram_ref()); + + qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN"); + let mut server = default_server(); + let out_to_rep = server.process(out.dgram(), now()); + assert!(out_to_rep.as_dgram_ref().is_some()); + qdebug!("Output={:0x?}", out_to_rep.as_dgram_ref()); + + qdebug!("---- client: cert verification"); + let out = client.process(Some(out_to_rep.as_dgram_ref().unwrap().clone()), now()); + assert!(out.as_dgram_ref().is_some()); + qdebug!("Output={:0x?}", out.as_dgram_ref()); + + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_none()); + + assert!(maybe_authenticate(&mut client)); + + qdebug!("---- client: SH..FIN -> FIN"); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + qdebug!("Output={:0x?}", out.as_dgram_ref()); + + assert_eq!(3, client.stats().packets_rx); + assert_eq!(0, client.stats().dups_rx); + assert_eq!(1, client.stats().dropped_rx); + + qdebug!("---- Dup, ignored"); + let out = client.process(out_to_rep.dgram(), now()); + assert!(out.as_dgram_ref().is_none()); + qdebug!("Output={:0x?}", out.as_dgram_ref()); + + // Four packets total received, 1 of them is a dup and one has been dropped because Initial keys + // are dropped. Add 2 counts of the padding that the server adds to Initial packets. + assert_eq!(6, client.stats().packets_rx); + assert_eq!(1, client.stats().dups_rx); + assert_eq!(3, client.stats().dropped_rx); +} + +// Test that we split crypto data if they cannot fit into one packet. +// To test this we will use a long server certificate. +#[test] +fn crypto_frame_split() { + let mut client = default_client(); + + let mut server = Connection::new_server( + test_fixture::LONG_CERT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + ConnectionParameters::default(), + ) + .expect("create a server"); + + let client1 = client.process(None, now()); + assert!(client1.as_dgram_ref().is_some()); + + // The entire server flight doesn't fit in a single packet because the + // certificate is large, therefore the server will produce 2 packets. + let server1 = server.process(client1.dgram(), now()); + assert!(server1.as_dgram_ref().is_some()); + let server2 = server.process(None, now()); + assert!(server2.as_dgram_ref().is_some()); + + let client2 = client.process(server1.dgram(), now()); + // This is an ack. + assert!(client2.as_dgram_ref().is_some()); + // The client might have the certificate now, so we can't guarantee that + // this will work. + let auth1 = maybe_authenticate(&mut client); + assert_eq!(*client.state(), State::Handshaking); + + // let server process the ack for the first packet. + let server3 = server.process(client2.dgram(), now()); + assert!(server3.as_dgram_ref().is_none()); + + // Consume the second packet from the server. + let client3 = client.process(server2.dgram(), now()); + + // Check authentication. + let auth2 = maybe_authenticate(&mut client); + assert!(auth1 ^ auth2); + // Now client has all data to finish handshake. + assert_eq!(*client.state(), State::Connected); + + let client4 = client.process(server3.dgram(), now()); + // One of these will contain data depending on whether Authentication was completed + // after the first or second server packet. + assert!(client3.as_dgram_ref().is_some() ^ client4.as_dgram_ref().is_some()); + + mem::drop(server.process(client3.dgram(), now())); + mem::drop(server.process(client4.dgram(), now())); + + assert_eq!(*client.state(), State::Connected); + assert_eq!(*server.state(), State::Confirmed); +} + +/// Run a single ChaCha20-Poly1305 test and get a PTO. +#[test] +fn chacha20poly1305() { + let mut server = default_server(); + let mut client = Connection::new_client( + test_fixture::DEFAULT_SERVER_NAME, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())), + addr(), + addr(), + ConnectionParameters::default(), + now(), + ) + .expect("create a default client"); + client.set_ciphers(&[TLS_CHACHA20_POLY1305_SHA256]).unwrap(); + connect_force_idle(&mut client, &mut server); +} + +/// Test that a server can send 0.5 RTT application data. +#[test] +fn send_05rtt() { + let mut client = default_client(); + let mut server = default_server(); + + let c1 = client.process(None, now()).dgram(); + assert!(c1.is_some()); + let s1 = server.process(c1, now()).dgram().unwrap(); + assert_eq!(s1.len(), PATH_MTU_V6); + + // The server should accept writes at this point. + let s2 = send_something(&mut server, now()); + + // Complete the handshake at the client. + client.process_input(s1, now()); + maybe_authenticate(&mut client); + assert_eq!(*client.state(), State::Connected); + + // The client should receive the 0.5-RTT data now. + client.process_input(s2, now()); + let mut buf = vec![0; DEFAULT_STREAM_DATA.len() + 1]; + let stream_id = client + .events() + .find_map(|e| { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + Some(stream_id) + } else { + None + } + }) + .unwrap(); + let (l, ended) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(&buf[..l], DEFAULT_STREAM_DATA); + assert!(ended); +} + +/// Test that a client buffers 0.5-RTT data when it arrives early. +#[test] +fn reorder_05rtt() { + let mut client = default_client(); + let mut server = default_server(); + + let c1 = client.process(None, now()).dgram(); + assert!(c1.is_some()); + let s1 = server.process(c1, now()).dgram().unwrap(); + + // The server should accept writes at this point. + let s2 = send_something(&mut server, now()); + + // We can't use the standard facility to complete the handshake, so + // drive it as aggressively as possible. + client.process_input(s2, now()); + assert_eq!(client.stats().saved_datagrams, 1); + + // After processing the first packet, the client should go back and + // process the 0.5-RTT packet data, which should make data available. + client.process_input(s1, now()); + // We can't use `maybe_authenticate` here as that consumes events. + client.authenticated(AuthenticationStatus::Ok, now()); + assert_eq!(*client.state(), State::Connected); + + let mut buf = vec![0; DEFAULT_STREAM_DATA.len() + 1]; + let stream_id = client + .events() + .find_map(|e| { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + Some(stream_id) + } else { + None + } + }) + .unwrap(); + let (l, ended) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(&buf[..l], DEFAULT_STREAM_DATA); + assert!(ended); +} + +#[test] +fn reorder_05rtt_with_0rtt() { + const RTT: Duration = Duration::from_millis(100); + + let mut client = default_client(); + let mut server = default_server(); + let validation = AddressValidation::new(now(), ValidateAddress::NoToken).unwrap(); + let validation = Rc::new(RefCell::new(validation)); + server.set_validation(Rc::clone(&validation)); + let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT); + + // Include RTT in sending the ticket or the ticket age reported by the + // client is wrong, which causes the server to reject 0-RTT. + now += RTT / 2; + server.send_ticket(now, &[]).unwrap(); + let ticket = server.process_output(now).dgram().unwrap(); + now += RTT / 2; + client.process_input(ticket, now); + + let token = get_tokens(&mut client).pop().unwrap(); + let mut client = default_client(); + client.enable_resumption(now, token).unwrap(); + let mut server = resumed_server(&client); + + // Send ClientHello and some 0-RTT. + let c1 = send_something(&mut client, now); + assertions::assert_coalesced_0rtt(&c1[..]); + // Drop the 0-RTT from the coalesced datagram, so that the server + // acknowledges the next 0-RTT packet. + let (c1, _) = split_datagram(&c1); + let c2 = send_something(&mut client, now); + + // Handle the first packet and send 0.5-RTT in response. Drop the response. + now += RTT / 2; + mem::drop(server.process(Some(c1), now).dgram().unwrap()); + // The gap in 0-RTT will result in this 0.5 RTT containing an ACK. + server.process_input(c2, now); + let s2 = send_something(&mut server, now); + + // Save the 0.5 RTT. + now += RTT / 2; + client.process_input(s2, now); + assert_eq!(client.stats().saved_datagrams, 1); + + // Now PTO at the client and cause the server to re-send handshake packets. + now += AT_LEAST_PTO; + let c3 = client.process(None, now).dgram(); + + now += RTT / 2; + let s3 = server.process(c3, now).dgram().unwrap(); + assertions::assert_no_1rtt(&s3[..]); + + // The client should be able to process the 0.5 RTT now. + // This should contain an ACK, so we are processing an ACK from the past. + now += RTT / 2; + client.process_input(s3, now); + maybe_authenticate(&mut client); + let c4 = client.process(None, now).dgram(); + assert_eq!(*client.state(), State::Connected); + assert_eq!(client.paths.rtt(), RTT); + + now += RTT / 2; + server.process_input(c4.unwrap(), now); + assert_eq!(*server.state(), State::Confirmed); + // Don't check server RTT as it will be massively inflated by a + // poor initial estimate received when the server dropped the + // Initial packet number space. +} + +/// Test that a server that coalesces 0.5 RTT with handshake packets +/// doesn't cause the client to drop application data. +#[test] +fn coalesce_05rtt() { + const RTT: Duration = Duration::from_millis(100); + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + + // The first exchange doesn't offer a chance for the server to send. + // So drop the server flight and wait for the PTO. + let c1 = client.process(None, now).dgram(); + assert!(c1.is_some()); + now += RTT / 2; + let s1 = server.process(c1, now).dgram(); + assert!(s1.is_some()); + + // Drop the server flight. Then send some data. + let stream_id = server.stream_create(StreamType::UniDi).unwrap(); + assert!(server.stream_send(stream_id, DEFAULT_STREAM_DATA).is_ok()); + assert!(server.stream_close_send(stream_id).is_ok()); + + // Now after a PTO the client can send another packet. + // The server should then send its entire flight again, + // including the application data, which it sends in a 1-RTT packet. + now += AT_LEAST_PTO; + let c2 = client.process(None, now).dgram(); + assert!(c2.is_some()); + now += RTT / 2; + let s2 = server.process(c2, now).dgram(); + // Even though there is a 1-RTT packet at the end of the datagram, the + // flight should be padded to full size. + assert_eq!(s2.as_ref().unwrap().len(), PATH_MTU_V6); + + // The client should process the datagram. It can't process the 1-RTT + // packet until authentication completes though. So it saves it. + now += RTT / 2; + assert_eq!(client.stats().dropped_rx, 0); + mem::drop(client.process(s2, now).dgram()); + // This packet will contain an ACK, but we can ignore it. + assert_eq!(client.stats().dropped_rx, 0); + assert_eq!(client.stats().packets_rx, 3); + assert_eq!(client.stats().saved_datagrams, 1); + + // After (successful) authentication, the packet is processed. + maybe_authenticate(&mut client); + let c3 = client.process(None, now).dgram(); + assert!(c3.is_some()); + assert_eq!(client.stats().dropped_rx, 0); // No Initial padding. + assert_eq!(client.stats().packets_rx, 4); + assert_eq!(client.stats().saved_datagrams, 1); + assert_eq!(client.stats().frame_rx.padding, 1); // Padding uses frames. + + // Allow the handshake to complete. + now += RTT / 2; + let s3 = server.process(c3, now).dgram(); + assert!(s3.is_some()); + assert_eq!(*server.state(), State::Confirmed); + now += RTT / 2; + mem::drop(client.process(s3, now).dgram()); + assert_eq!(*client.state(), State::Confirmed); + + assert_eq!(client.stats().dropped_rx, 0); // No dropped packets. +} + +#[test] +fn reorder_handshake() { + const RTT: Duration = Duration::from_millis(100); + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + + let c1 = client.process(None, now).dgram(); + assert!(c1.is_some()); + + now += RTT / 2; + let s1 = server.process(c1, now).dgram(); + assert!(s1.is_some()); + + // Drop the Initial packet from this. + let (_, s_hs) = split_datagram(&s1.unwrap()); + assert!(s_hs.is_some()); + + // Pass just the handshake packet in and the client can't handle it yet. + // It can only send another Initial packet. + now += RTT / 2; + let dgram = client.process(s_hs, now).dgram(); + assertions::assert_initial(dgram.as_ref().unwrap(), false); + assert_eq!(client.stats().saved_datagrams, 1); + assert_eq!(client.stats().packets_rx, 1); + + // Get the server to try again. + // Though we currently allow the server to arm its PTO timer, use + // a second client Initial packet to cause it to send again. + now += AT_LEAST_PTO; + let c2 = client.process(None, now).dgram(); + now += RTT / 2; + let s2 = server.process(c2, now).dgram(); + assert!(s2.is_some()); + + let (s_init, s_hs) = split_datagram(&s2.unwrap()); + assert!(s_hs.is_some()); + + // Processing the Handshake packet first should save it. + now += RTT / 2; + client.process_input(s_hs.unwrap(), now); + assert_eq!(client.stats().saved_datagrams, 2); + assert_eq!(client.stats().packets_rx, 2); + + client.process_input(s_init, now); + // Each saved packet should now be "received" again. + assert_eq!(client.stats().packets_rx, 7); + maybe_authenticate(&mut client); + let c3 = client.process(None, now).dgram(); + assert!(c3.is_some()); + + // Note that though packets were saved and processed very late, + // they don't cause the RTT to change. + now += RTT / 2; + let s3 = server.process(c3, now).dgram(); + assert_eq!(*server.state(), State::Confirmed); + // Don't check server RTT estimate as it will be inflated due to + // it making a guess based on retransmissions when it dropped + // the Initial packet number space. + + now += RTT / 2; + client.process_input(s3.unwrap(), now); + assert_eq!(*client.state(), State::Confirmed); + assert_eq!(client.paths.rtt(), RTT); +} + +#[test] +fn reorder_1rtt() { + const RTT: Duration = Duration::from_millis(100); + const PACKETS: usize = 4; // Many, but not enough to overflow cwnd. + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + + let c1 = client.process(None, now).dgram(); + assert!(c1.is_some()); + + now += RTT / 2; + let s1 = server.process(c1, now).dgram(); + assert!(s1.is_some()); + + now += RTT / 2; + client.process_input(s1.unwrap(), now); + maybe_authenticate(&mut client); + let c2 = client.process(None, now).dgram(); + assert!(c2.is_some()); + + // Now get a bunch of packets from the client. + // Give them to the server before giving it `c2`. + for _ in 0..PACKETS { + let d = send_something(&mut client, now); + server.process_input(d, now + RTT / 2); + } + // The server has now received those packets, and saved them. + // The two extra received are Initial + the junk we use for padding. + assert_eq!(server.stats().packets_rx, PACKETS + 2); + assert_eq!(server.stats().saved_datagrams, PACKETS); + assert_eq!(server.stats().dropped_rx, 1); + + now += RTT / 2; + let s2 = server.process(c2, now).dgram(); + // The server has now received those packets, and saved them. + // The two additional are a Handshake and a 1-RTT (w/ NEW_CONNECTION_ID). + assert_eq!(server.stats().packets_rx, PACKETS * 2 + 4); + assert_eq!(server.stats().saved_datagrams, PACKETS); + assert_eq!(server.stats().dropped_rx, 1); + assert_eq!(*server.state(), State::Confirmed); + assert_eq!(server.paths.rtt(), RTT); + + now += RTT / 2; + client.process_input(s2.unwrap(), now); + assert_eq!(client.paths.rtt(), RTT); + + // All the stream data that was sent should now be available. + let streams = server + .events() + .filter_map(|e| { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + Some(stream_id) + } else { + None + } + }) + .collect::<Vec<_>>(); + assert_eq!(streams.len(), PACKETS); + for stream_id in streams { + let mut buf = vec![0; DEFAULT_STREAM_DATA.len() + 1]; + let (recvd, fin) = server.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(recvd, DEFAULT_STREAM_DATA.len()); + assert!(fin); + } +} + +#[cfg(not(feature = "fuzzing"))] +#[test] +fn corrupted_initial() { + let mut client = default_client(); + let mut server = default_server(); + let d = client.process(None, now()).dgram().unwrap(); + let mut corrupted = Vec::from(&d[..]); + // Find the last non-zero value and corrupt that. + let (idx, _) = corrupted + .iter() + .enumerate() + .rev() + .find(|(_, &v)| v != 0) + .unwrap(); + corrupted[idx] ^= 0x76; + let dgram = Datagram::new(d.source(), d.destination(), corrupted); + server.process_input(dgram, now()); + // The server should have received two packets, + // the first should be dropped, the second saved. + assert_eq!(server.stats().packets_rx, 2); + assert_eq!(server.stats().dropped_rx, 2); + assert_eq!(server.stats().saved_datagrams, 0); +} + +#[test] +// Absent path PTU discovery, max v6 packet size should be PATH_MTU_V6. +fn verify_pkt_honors_mtu() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let now = now(); + + let res = client.process(None, now); + let idle_timeout = ConnectionParameters::default().get_idle_timeout(); + assert_eq!(res, Output::Callback(idle_timeout)); + + // Try to send a large stream and verify first packet is correctly sized + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.stream_send(stream_id, &[0xbb; 2000]).unwrap(), 2000); + let pkt0 = client.process(None, now); + assert!(matches!(pkt0, Output::Datagram(_))); + assert_eq!(pkt0.as_dgram_ref().unwrap().len(), PATH_MTU_V6); +} + +#[test] +fn extra_initial_hs() { + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + + let c_init = client.process(None, now).dgram(); + assert!(c_init.is_some()); + now += DEFAULT_RTT / 2; + let s_init = server.process(c_init, now).dgram(); + assert!(s_init.is_some()); + now += DEFAULT_RTT / 2; + + // Drop the Initial packet, keep only the Handshake. + let (_, undecryptable) = split_datagram(&s_init.unwrap()); + assert!(undecryptable.is_some()); + + // Feed the same undecryptable packet into the client a few times. + // Do that EXTRA_INITIALS times and each time the client will emit + // another Initial packet. + for _ in 0..=super::super::EXTRA_INITIALS { + let c_init = client.process(undecryptable.clone(), now).dgram(); + assertions::assert_initial(c_init.as_ref().unwrap(), false); + now += DEFAULT_RTT / 10; + } + + // After EXTRA_INITIALS, the client stops sending Initial packets. + let nothing = client.process(undecryptable, now).dgram(); + assert!(nothing.is_none()); + + // Until PTO, where another Initial can be used to complete the handshake. + now += AT_LEAST_PTO; + let c_init = client.process(None, now).dgram(); + assertions::assert_initial(c_init.as_ref().unwrap(), false); + now += DEFAULT_RTT / 2; + let s_init = server.process(c_init, now).dgram(); + now += DEFAULT_RTT / 2; + client.process_input(s_init.unwrap(), now); + maybe_authenticate(&mut client); + let c_fin = client.process_output(now).dgram(); + assert_eq!(*client.state(), State::Connected); + now += DEFAULT_RTT / 2; + server.process_input(c_fin.unwrap(), now); + assert_eq!(*server.state(), State::Confirmed); +} + +#[test] +fn extra_initial_invalid_cid() { + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + + let c_init = client.process(None, now).dgram(); + assert!(c_init.is_some()); + now += DEFAULT_RTT / 2; + let s_init = server.process(c_init, now).dgram(); + assert!(s_init.is_some()); + now += DEFAULT_RTT / 2; + + // If the client receives a packet that contains the wrong connection + // ID, it won't send another Initial. + let (_, hs) = split_datagram(&s_init.unwrap()); + let hs = hs.unwrap(); + let mut copy = hs.to_vec(); + assert_ne!(copy[5], 0); // The DCID should be non-zero length. + copy[6] ^= 0xc4; + let dgram_copy = Datagram::new(hs.destination(), hs.source(), copy); + let nothing = client.process(Some(dgram_copy), now).dgram(); + assert!(nothing.is_none()); +} + +#[test] +fn connect_one_version() { + fn connect_v(version: Version) { + fixture_init(); + let mut client = Connection::new_client( + test_fixture::DEFAULT_SERVER_NAME, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + ConnectionParameters::default().versions(version, vec![version]), + now(), + ) + .unwrap(); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + ConnectionParameters::default().versions(version, vec![version]), + ) + .unwrap(); + connect_force_idle(&mut client, &mut server); + assert_eq!(client.version(), version); + assert_eq!(server.version(), version); + } + + for v in Version::all() { + println!("Connecting with {:?}", v); + connect_v(v); + } +} + +#[test] +fn anti_amplification() { + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + + // With a gigantic transport parameter, the server is unable to complete + // the handshake within the amplification limit. + let very_big = TransportParameter::Bytes(vec![0; PATH_MTU_V6 * 3]); + server.set_local_tparam(0xce16, very_big).unwrap(); + + let c_init = client.process_output(now).dgram(); + now += DEFAULT_RTT / 2; + let s_init1 = server.process(c_init, now).dgram().unwrap(); + assert_eq!(s_init1.len(), PATH_MTU_V6); + let s_init2 = server.process_output(now).dgram().unwrap(); + assert_eq!(s_init2.len(), PATH_MTU_V6); + + // Skip the gap for pacing here. + let s_pacing = server.process_output(now).callback(); + assert_ne!(s_pacing, Duration::new(0, 0)); + now += s_pacing; + + let s_init3 = server.process_output(now).dgram().unwrap(); + assert_eq!(s_init3.len(), PATH_MTU_V6); + let cb = server.process_output(now).callback(); + assert_ne!(cb, Duration::new(0, 0)); + + now += DEFAULT_RTT / 2; + client.process_input(s_init1, now); + client.process_input(s_init2, now); + let ack_count = client.stats().frame_tx.ack; + let frame_count = client.stats().frame_tx.all; + let ack = client.process(Some(s_init3), now).dgram().unwrap(); + assert!(!maybe_authenticate(&mut client)); // No need yet. + + // The client sends a padded datagram, with just ACK for Handshake. + assert_eq!(client.stats().frame_tx.ack, ack_count + 1); + assert_eq!(client.stats().frame_tx.all, frame_count + 1); + assert_ne!(ack.len(), PATH_MTU_V6); // Not padded (it includes Handshake). + + now += DEFAULT_RTT / 2; + let remainder = server.process(Some(ack), now).dgram(); + + now += DEFAULT_RTT / 2; + client.process_input(remainder.unwrap(), now); + assert!(maybe_authenticate(&mut client)); // OK, we have all of it. + let fin = client.process_output(now).dgram(); + assert_eq!(*client.state(), State::Connected); + + now += DEFAULT_RTT / 2; + server.process_input(fin.unwrap(), now); + assert_eq!(*server.state(), State::Confirmed); +} + +#[cfg(not(feature = "fuzzing"))] +#[test] +fn garbage_initial() { + let mut client = default_client(); + let mut server = default_server(); + + let dgram = client.process_output(now()).dgram().unwrap(); + let (initial, rest) = split_datagram(&dgram); + let mut corrupted = Vec::from(&initial[..initial.len() - 1]); + corrupted.push(initial[initial.len() - 1] ^ 0xb7); + corrupted.extend_from_slice(rest.as_ref().map_or(&[], |r| &r[..])); + let garbage = Datagram::new(addr(), addr(), corrupted); + assert_eq!(Output::None, server.process(Some(garbage), now())); +} + +#[test] +fn drop_initial_packet_from_wrong_address() { + let mut client = default_client(); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + + let mut server = default_server(); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + + let p = out.dgram().unwrap(); + let dgram = Datagram::new( + SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2)), 443), + p.destination(), + &p[..], + ); + + let out = client.process(Some(dgram), now()); + assert!(out.as_dgram_ref().is_none()); +} + +#[test] +fn drop_handshake_packet_from_wrong_address() { + let mut client = default_client(); + let out = client.process(None, now()); + assert!(out.as_dgram_ref().is_some()); + + let mut server = default_server(); + let out = server.process(out.dgram(), now()); + assert!(out.as_dgram_ref().is_some()); + + let (s_in, s_hs) = split_datagram(&out.dgram().unwrap()); + + // Pass the initial packet. + mem::drop(client.process(Some(s_in), now()).dgram()); + + let p = s_hs.unwrap(); + let dgram = Datagram::new( + SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2)), 443), + p.destination(), + &p[..], + ); + + let out = client.process(Some(dgram), now()); + assert!(out.as_dgram_ref().is_none()); +} + +#[test] +fn ech() { + let mut server = default_server(); + let (sk, pk) = generate_ech_keys().unwrap(); + server + .server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk) + .unwrap(); + + let mut client = default_client(); + client.client_enable_ech(server.ech_config()).unwrap(); + + connect(&mut client, &mut server); + + assert!(client.tls_info().unwrap().ech_accepted()); + assert!(server.tls_info().unwrap().ech_accepted()); + assert!(client.tls_preinfo().unwrap().ech_accepted().unwrap()); + assert!(server.tls_preinfo().unwrap().ech_accepted().unwrap()); +} + +fn damaged_ech_config(config: &[u8]) -> Vec<u8> { + let mut cfg = Vec::from(config); + // Ensure that the version and config_id is correct. + assert_eq!(cfg[2], 0xfe); + assert_eq!(cfg[3], 0x0d); + assert_eq!(cfg[6], ECH_CONFIG_ID); + // Change the config_id so that the server doesn't recognize it. + cfg[6] ^= 0x94; + cfg +} + +#[test] +fn ech_retry() { + fixture_init(); + let mut server = default_server(); + let (sk, pk) = generate_ech_keys().unwrap(); + server + .server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk) + .unwrap(); + + let mut client = default_client(); + client + .client_enable_ech(&damaged_ech_config(server.ech_config())) + .unwrap(); + + let dgram = client.process_output(now()).dgram(); + let dgram = server.process(dgram, now()).dgram(); + client.process_input(dgram.unwrap(), now()); + let auth_event = ConnectionEvent::EchFallbackAuthenticationNeeded { + public_name: String::from(ECH_PUBLIC_NAME), + }; + assert!(client.events().any(|e| e == auth_event)); + client.authenticated(AuthenticationStatus::Ok, now()); + assert!(client.state().error().is_some()); + + // Tell the server about the error. + let dgram = client.process_output(now()).dgram(); + server.process_input(dgram.unwrap(), now()); + assert_eq!( + server.state().error(), + Some(&ConnectionError::Transport(Error::PeerError(0x100 + 121))) + ); + + let updated_config = + if let Some(ConnectionError::Transport(Error::EchRetry(c))) = client.state().error() { + c + } else { + panic!( + "Client state should be failed with EchRetry, is {:?}", + client.state() + ); + }; + + let mut server = default_server(); + server + .server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk) + .unwrap(); + let mut client = default_client(); + client.client_enable_ech(updated_config).unwrap(); + + connect(&mut client, &mut server); + + assert!(client.tls_info().unwrap().ech_accepted()); + assert!(server.tls_info().unwrap().ech_accepted()); + assert!(client.tls_preinfo().unwrap().ech_accepted().unwrap()); + assert!(server.tls_preinfo().unwrap().ech_accepted().unwrap()); +} + +#[test] +fn ech_retry_fallback_rejected() { + fixture_init(); + let mut server = default_server(); + let (sk, pk) = generate_ech_keys().unwrap(); + server + .server_enable_ech(ECH_CONFIG_ID, ECH_PUBLIC_NAME, &sk, &pk) + .unwrap(); + + let mut client = default_client(); + client + .client_enable_ech(&damaged_ech_config(server.ech_config())) + .unwrap(); + + let dgram = client.process_output(now()).dgram(); + let dgram = server.process(dgram, now()).dgram(); + client.process_input(dgram.unwrap(), now()); + let auth_event = ConnectionEvent::EchFallbackAuthenticationNeeded { + public_name: String::from(ECH_PUBLIC_NAME), + }; + assert!(client.events().any(|e| e == auth_event)); + client.authenticated(AuthenticationStatus::PolicyRejection, now()); + assert!(client.state().error().is_some()); + + if let Some(ConnectionError::Transport(Error::EchRetry(_))) = client.state().error() { + panic!("Client should not get EchRetry error"); + } + + // Pass the error on. + let dgram = client.process_output(now()).dgram(); + server.process_input(dgram.unwrap(), now()); + assert_eq!( + server.state().error(), + Some(&ConnectionError::Transport(Error::PeerError(298))) + ); // A bad_certificate alert. +} + +#[test] +fn bad_min_ack_delay() { + const EXPECTED_ERROR: ConnectionError = + ConnectionError::Transport(Error::TransportParameterError); + let mut server = default_server(); + let max_ad = u64::try_from(DEFAULT_ACK_DELAY.as_micros()).unwrap(); + server + .set_local_tparam(MIN_ACK_DELAY, TransportParameter::Integer(max_ad + 1)) + .unwrap(); + let mut client = default_client(); + + let dgram = client.process_output(now()).dgram(); + let dgram = server.process(dgram, now()).dgram(); + client.process_input(dgram.unwrap(), now()); + client.authenticated(AuthenticationStatus::Ok, now()); + assert_eq!(client.state().error(), Some(&EXPECTED_ERROR)); + let dgram = client.process_output(now()).dgram(); + + server.process_input(dgram.unwrap(), now()); + assert_eq!( + server.state().error(), + Some(&ConnectionError::Transport(Error::PeerError( + Error::TransportParameterError.code() + ))) + ); +} + +/// Ensure that the client probes correctly if it only receives Initial packets +/// from the server. +#[test] +fn only_server_initial() { + let mut server = default_server(); + let mut client = default_client(); + let mut now = now(); + + let client_dgram = client.process_output(now).dgram(); + + // Now fetch two flights of messages from the server. + let server_dgram1 = server.process(client_dgram, now).dgram(); + let server_dgram2 = server.process_output(now + AT_LEAST_PTO).dgram(); + + // Only pass on the Initial from the first. We should get a Handshake in return. + let (initial, handshake) = split_datagram(&server_dgram1.unwrap()); + assert!(handshake.is_some()); + + // The client will not acknowledge the Initial as it discards keys. + // It sends a Handshake probe instead, containing just a PING frame. + assert_eq!(client.stats().frame_tx.ping, 0); + let probe = client.process(Some(initial), now).dgram(); + assertions::assert_handshake(&probe.unwrap()); + assert_eq!(client.stats().dropped_rx, 0); + assert_eq!(client.stats().frame_tx.ping, 1); + + let (initial, handshake) = split_datagram(&server_dgram2.unwrap()); + assert!(handshake.is_some()); + + // The same happens after a PTO, even though the client will discard the Initial packet. + now += AT_LEAST_PTO; + assert_eq!(client.stats().frame_tx.ping, 1); + let discarded = client.stats().dropped_rx; + let probe = client.process(Some(initial), now).dgram(); + assertions::assert_handshake(&probe.unwrap()); + assert_eq!(client.stats().frame_tx.ping, 2); + assert_eq!(client.stats().dropped_rx, discarded + 1); + + // Pass the Handshake packet and complete the handshake. + client.process_input(handshake.unwrap(), now); + maybe_authenticate(&mut client); + let dgram = client.process_output(now).dgram(); + let dgram = server.process(dgram, now).dgram(); + client.process_input(dgram.unwrap(), now); + + assert_eq!(*client.state(), State::Confirmed); + assert_eq!(*server.state(), State::Confirmed); +} + +// Collect a few spare Initial packets as the handshake is exchanged. +// Later, replay those packets to see if they result in additional probes; they should not. +#[test] +fn no_extra_probes_after_confirmed() { + let mut server = default_server(); + let mut client = default_client(); + let mut now = now(); + + // First, collect a client Initial. + let spare_initial = client.process_output(now).dgram(); + assert!(spare_initial.is_some()); + + // Collect ANOTHER client Initial. + now += AT_LEAST_PTO; + let dgram = client.process_output(now).dgram(); + let (replay_initial, _) = split_datagram(dgram.as_ref().unwrap()); + + // Finally, run the handshake. + now += AT_LEAST_PTO * 2; + let dgram = client.process_output(now).dgram(); + let dgram = server.process(dgram, now).dgram(); + + // The server should have dropped the Initial keys now, so passing in the Initial + // should elicit a retransmit rather than having it completely ignored. + let spare_handshake = server.process(Some(replay_initial), now).dgram(); + assert!(spare_handshake.is_some()); + + client.process_input(dgram.unwrap(), now); + maybe_authenticate(&mut client); + let dgram = client.process_output(now).dgram(); + let dgram = server.process(dgram, now).dgram(); + client.process_input(dgram.unwrap(), now); + + assert_eq!(*client.state(), State::Confirmed); + assert_eq!(*server.state(), State::Confirmed); + + let probe = server.process(spare_initial, now).dgram(); + assert!(probe.is_none()); + let probe = client.process(spare_handshake, now).dgram(); + assert!(probe.is_none()); +} + +#[test] +fn implicit_rtt_server() { + const RTT: Duration = Duration::from_secs(2); + let mut server = default_server(); + let mut client = default_client(); + let mut now = now(); + + let dgram = client.process_output(now).dgram(); + now += RTT / 2; + let dgram = server.process(dgram, now).dgram(); + now += RTT / 2; + let dgram = client.process(dgram, now).dgram(); + assertions::assert_handshake(dgram.as_ref().unwrap()); + now += RTT / 2; + server.process_input(dgram.unwrap(), now); + + // The server doesn't receive any acknowledgments, but it can infer + // an RTT estimate from having discarded the Initial packet number space. + assert_eq!(server.stats().rtt, RTT); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/idle.rs b/third_party/rust/neqo-transport/src/connection/tests/idle.rs new file mode 100644 index 0000000000..f8f394e030 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/idle.rs @@ -0,0 +1,737 @@ +// 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::super::{Connection, ConnectionParameters, IdleTimeout, Output, State}; +use super::{ + connect, connect_force_idle, connect_rtt_idle, connect_with_rtt, default_client, + default_server, maybe_authenticate, new_client, new_server, send_and_receive, send_something, + AT_LEAST_PTO, DEFAULT_STREAM_DATA, +}; +use crate::packet::PacketBuilder; +use crate::stats::FrameStats; +use crate::stream_id::{StreamId, StreamType}; +use crate::tparams::{self, TransportParameter}; +use crate::tracking::PacketNumberSpace; + +use neqo_common::{qtrace, Encoder}; +use std::mem; +use std::time::{Duration, Instant}; +use test_fixture::{self, now, split_datagram}; + +fn default_timeout() -> Duration { + ConnectionParameters::default().get_idle_timeout() +} + +fn test_idle_timeout(client: &mut Connection, server: &mut Connection, timeout: Duration) { + assert!(timeout > Duration::from_secs(1)); + connect_force_idle(client, server); + + let now = now(); + + let res = client.process(None, now); + assert_eq!(res, Output::Callback(timeout)); + + // Still connected after timeout-1 seconds. Idle timer not reset + mem::drop(client.process( + None, + now + timeout.checked_sub(Duration::from_secs(1)).unwrap(), + )); + assert!(matches!(client.state(), State::Confirmed)); + + mem::drop(client.process(None, now + timeout)); + + // Not connected after timeout. + assert!(matches!(client.state(), State::Closed(_))); +} + +#[test] +fn idle_timeout() { + let mut client = default_client(); + let mut server = default_server(); + test_idle_timeout(&mut client, &mut server, default_timeout()); +} + +#[test] +fn idle_timeout_custom_client() { + const IDLE_TIMEOUT: Duration = Duration::from_secs(5); + let mut client = new_client(ConnectionParameters::default().idle_timeout(IDLE_TIMEOUT)); + let mut server = default_server(); + test_idle_timeout(&mut client, &mut server, IDLE_TIMEOUT); +} + +#[test] +fn idle_timeout_custom_server() { + const IDLE_TIMEOUT: Duration = Duration::from_secs(5); + let mut client = default_client(); + let mut server = new_server(ConnectionParameters::default().idle_timeout(IDLE_TIMEOUT)); + test_idle_timeout(&mut client, &mut server, IDLE_TIMEOUT); +} + +#[test] +fn idle_timeout_custom_both() { + const LOWER_TIMEOUT: Duration = Duration::from_secs(5); + const HIGHER_TIMEOUT: Duration = Duration::from_secs(10); + let mut client = new_client(ConnectionParameters::default().idle_timeout(HIGHER_TIMEOUT)); + let mut server = new_server(ConnectionParameters::default().idle_timeout(LOWER_TIMEOUT)); + test_idle_timeout(&mut client, &mut server, LOWER_TIMEOUT); +} + +#[test] +fn asymmetric_idle_timeout() { + const LOWER_TIMEOUT_MS: u64 = 1000; + const LOWER_TIMEOUT: Duration = Duration::from_millis(LOWER_TIMEOUT_MS); + // Sanity check the constant. + assert!(LOWER_TIMEOUT < default_timeout()); + + let mut client = default_client(); + let mut server = default_server(); + + // Overwrite the default at the server. + server + .tps + .borrow_mut() + .local + .set_integer(tparams::IDLE_TIMEOUT, LOWER_TIMEOUT_MS); + server.idle_timeout = IdleTimeout::new(LOWER_TIMEOUT); + + // Now connect and force idleness manually. + // We do that by following what `force_idle` does and have each endpoint + // send two packets, which are delivered out of order. See `force_idle`. + connect(&mut client, &mut server); + let c1 = send_something(&mut client, now()); + let c2 = send_something(&mut client, now()); + server.process_input(c2, now()); + server.process_input(c1, now()); + let s1 = send_something(&mut server, now()); + let s2 = send_something(&mut server, now()); + client.process_input(s2, now()); + let ack = client.process(Some(s1), now()).dgram(); + assert!(ack.is_some()); + // Now both should have received ACK frames so should be idle. + assert_eq!(server.process(ack, now()), Output::Callback(LOWER_TIMEOUT)); + assert_eq!(client.process(None, now()), Output::Callback(LOWER_TIMEOUT)); +} + +#[test] +fn tiny_idle_timeout() { + const RTT: Duration = Duration::from_millis(500); + const LOWER_TIMEOUT_MS: u64 = 100; + const LOWER_TIMEOUT: Duration = Duration::from_millis(LOWER_TIMEOUT_MS); + // We won't respect a value that is lower than 3*PTO, sanity check. + assert!(LOWER_TIMEOUT < 3 * RTT); + + let mut client = default_client(); + let mut server = default_server(); + + // Overwrite the default at the server. + server + .set_local_tparam( + tparams::IDLE_TIMEOUT, + TransportParameter::Integer(LOWER_TIMEOUT_MS), + ) + .unwrap(); + server.idle_timeout = IdleTimeout::new(LOWER_TIMEOUT); + + // Now connect with an RTT and force idleness manually. + let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT); + let c1 = send_something(&mut client, now); + let c2 = send_something(&mut client, now); + now += RTT / 2; + server.process_input(c2, now); + server.process_input(c1, now); + let s1 = send_something(&mut server, now); + let s2 = send_something(&mut server, now); + now += RTT / 2; + client.process_input(s2, now); + let ack = client.process(Some(s1), now).dgram(); + assert!(ack.is_some()); + + // The client should be idle now, but with a different timer. + if let Output::Callback(t) = client.process(None, now) { + assert!(t > LOWER_TIMEOUT); + } else { + panic!("Client not idle"); + } + + // The server should go idle after the ACK, but again with a larger timeout. + now += RTT / 2; + if let Output::Callback(t) = client.process(ack, now) { + assert!(t > LOWER_TIMEOUT); + } else { + panic!("Client not idle"); + } +} + +#[test] +fn idle_send_packet1() { + const DELTA: Duration = Duration::from_millis(10); + + let mut client = default_client(); + let mut server = default_server(); + let mut now = now(); + connect_force_idle(&mut client, &mut server); + + let timeout = client.process(None, now).callback(); + assert_eq!(timeout, default_timeout()); + + now += Duration::from_secs(10); + let dgram = send_and_receive(&mut client, &mut server, now); + assert!(dgram.is_none()); + + // Still connected after 39 seconds because idle timer reset by the + // outgoing packet. + now += default_timeout() - DELTA; + let dgram = client.process(None, now).dgram(); + assert!(dgram.is_some()); // PTO + assert!(client.state().connected()); + + // Not connected after 40 seconds. + now += DELTA; + let out = client.process(None, now); + assert!(matches!(out, Output::None)); + assert!(client.state().closed()); +} + +#[test] +fn idle_send_packet2() { + const GAP: Duration = Duration::from_secs(10); + const DELTA: Duration = Duration::from_millis(10); + + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let mut now = now(); + + let timeout = client.process(None, now).callback(); + assert_eq!(timeout, default_timeout()); + + // First transmission at t=GAP. + now += GAP; + mem::drop(send_something(&mut client, now)); + + // Second transmission at t=2*GAP. + mem::drop(send_something(&mut client, now + GAP)); + assert!((GAP * 2 + DELTA) < default_timeout()); + + // Still connected just before GAP + default_timeout(). + now += default_timeout() - DELTA; + let dgram = client.process(None, now).dgram(); + assert!(dgram.is_some()); // PTO + assert!(matches!(client.state(), State::Confirmed)); + + // Not connected after 40 seconds because timer not reset by second + // outgoing packet + now += DELTA; + let out = client.process(None, now); + assert!(matches!(out, Output::None)); + assert!(matches!(client.state(), State::Closed(_))); +} + +#[test] +fn idle_recv_packet() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let now = now(); + + let res = client.process(None, now); + assert_eq!(res, Output::Callback(default_timeout())); + + let stream = client.stream_create(StreamType::BiDi).unwrap(); + assert_eq!(stream, 0); + assert_eq!(client.stream_send(stream, b"hello").unwrap(), 5); + + // Respond with another packet + let out = client.process(None, now + Duration::from_secs(10)); + server.process_input(out.dgram().unwrap(), now + Duration::from_secs(10)); + assert_eq!(server.stream_send(stream, b"world").unwrap(), 5); + let out = server.process_output(now + Duration::from_secs(10)); + assert_ne!(out.as_dgram_ref(), None); + + mem::drop(client.process(out.dgram(), now + Duration::from_secs(20))); + assert!(matches!(client.state(), State::Confirmed)); + + // Still connected after 49 seconds because idle timer reset by received + // packet + mem::drop(client.process(None, now + default_timeout() + Duration::from_secs(19))); + assert!(matches!(client.state(), State::Confirmed)); + + // Not connected after 50 seconds. + mem::drop(client.process(None, now + default_timeout() + Duration::from_secs(20))); + + assert!(matches!(client.state(), State::Closed(_))); +} + +/// Caching packets should not cause the connection to become idle. +/// This requires a few tricks to keep the connection from going +/// idle while preventing any progress on the handshake. +#[test] +fn idle_caching() { + let mut client = default_client(); + let mut server = default_server(); + let start = now(); + let mut builder = PacketBuilder::short(Encoder::new(), false, []); + + // Perform the first round trip, but drop the Initial from the server. + // The client then caches the Handshake packet. + let dgram = client.process_output(start).dgram(); + let dgram = server.process(dgram, start).dgram(); + let (_, handshake) = split_datagram(&dgram.unwrap()); + client.process_input(handshake.unwrap(), start); + + // Perform an exchange and keep the connection alive. + // Only allow a packet containing a PING to pass. + let middle = start + AT_LEAST_PTO; + mem::drop(client.process_output(middle)); + let dgram = client.process_output(middle).dgram(); + + // Get the server to send its first probe and throw that away. + mem::drop(server.process_output(middle).dgram()); + // Now let the server process the client PING. This causes the server + // to send CRYPTO frames again, so manually extract and discard those. + let ping_before_s = server.stats().frame_rx.ping; + server.process_input(dgram.unwrap(), middle); + assert_eq!(server.stats().frame_rx.ping, ping_before_s + 1); + let mut tokens = Vec::new(); + server + .crypto + .streams + .write_frame( + PacketNumberSpace::Initial, + &mut builder, + &mut tokens, + &mut FrameStats::default(), + ) + .unwrap(); + assert_eq!(tokens.len(), 1); + tokens.clear(); + server + .crypto + .streams + .write_frame( + PacketNumberSpace::Initial, + &mut builder, + &mut tokens, + &mut FrameStats::default(), + ) + .unwrap(); + assert!(tokens.is_empty()); + let dgram = server.process_output(middle).dgram(); + + // Now only allow the Initial packet from the server through; + // it shouldn't contain a CRYPTO frame. + let (initial, _) = split_datagram(&dgram.unwrap()); + let ping_before_c = client.stats().frame_rx.ping; + let ack_before = client.stats().frame_rx.ack; + client.process_input(initial, middle); + assert_eq!(client.stats().frame_rx.ping, ping_before_c + 1); + assert_eq!(client.stats().frame_rx.ack, ack_before + 1); + + let end = start + default_timeout() + (AT_LEAST_PTO / 2); + // Now let the server Initial through, with the CRYPTO frame. + let dgram = server.process_output(end).dgram(); + let (initial, _) = split_datagram(&dgram.unwrap()); + neqo_common::qwarn!("client ingests initial, finally"); + mem::drop(client.process(Some(initial), end)); + maybe_authenticate(&mut client); + let dgram = client.process_output(end).dgram(); + let dgram = server.process(dgram, end).dgram(); + client.process_input(dgram.unwrap(), end); + assert_eq!(*client.state(), State::Confirmed); + assert_eq!(*server.state(), State::Confirmed); +} + +/// This function opens a bidirectional stream and leaves both endpoints +/// idle, with the stream left open. +/// The stream ID of that stream is returned (along with the new time). +fn create_stream_idle_rtt( + initiator: &mut Connection, + responder: &mut Connection, + mut now: Instant, + rtt: Duration, +) -> (Instant, StreamId) { + let check_idle = |endpoint: &mut Connection, now: Instant| { + let delay = endpoint.process_output(now).callback(); + qtrace!([endpoint], "idle timeout {:?}", delay); + if rtt < default_timeout() / 4 { + assert_eq!(default_timeout(), delay); + } else { + assert!(delay > default_timeout()); + } + }; + + // Exchange a message each way on a stream. + let stream = initiator.stream_create(StreamType::BiDi).unwrap(); + let _ = initiator.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(); + let req = initiator.process_output(now).dgram(); + now += rtt / 2; + responder.process_input(req.unwrap(), now); + + // Reordering two packets from the responder forces the initiator to be idle. + let _ = responder.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(); + let resp1 = responder.process_output(now).dgram(); + let _ = responder.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(); + let resp2 = responder.process_output(now).dgram(); + + now += rtt / 2; + initiator.process_input(resp2.unwrap(), now); + initiator.process_input(resp1.unwrap(), now); + let ack = initiator.process_output(now).dgram(); + assert!(ack.is_some()); + check_idle(initiator, now); + + // Receiving the ACK should return the responder to idle too. + now += rtt / 2; + responder.process_input(ack.unwrap(), now); + check_idle(responder, now); + + (now, stream) +} + +fn create_stream_idle(initiator: &mut Connection, responder: &mut Connection) -> StreamId { + let (_, stream) = create_stream_idle_rtt(initiator, responder, now(), Duration::new(0, 0)); + stream +} + +fn assert_idle(endpoint: &mut Connection, now: Instant, expected: Duration) { + assert_eq!(endpoint.process_output(now).callback(), expected); +} + +/// The creator of a stream marks it as important enough to use a keep-alive. +#[test] +fn keep_alive_initiator() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut server, &mut client); + let mut now = now(); + + // Marking the stream for keep-alive changes the idle timeout. + server.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut server, now, default_timeout() / 2); + + // Wait that long and the server should send a PING frame. + now += default_timeout() / 2; + let pings_before = server.stats().frame_tx.ping; + let ping = server.process_output(now).dgram(); + assert!(ping.is_some()); + assert_eq!(server.stats().frame_tx.ping, pings_before + 1); + + // Exchange ack for the PING. + let out = client.process(ping, now).dgram(); + let out = server.process(out, now).dgram(); + assert!(client.process(out, now).dgram().is_none()); + + // Check that there will be next keep-alive ping after default_timeout() / 2. + assert_idle(&mut server, now, default_timeout() / 2); + now += default_timeout() / 2; + let pings_before2 = server.stats().frame_tx.ping; + let ping = server.process_output(now).dgram(); + assert!(ping.is_some()); + assert_eq!(server.stats().frame_tx.ping, pings_before2 + 1); +} + +/// Test a keep-alive ping is retransmitted if lost. +#[test] +fn keep_alive_lost() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut server, &mut client); + let mut now = now(); + + // Marking the stream for keep-alive changes the idle timeout. + server.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut server, now, default_timeout() / 2); + + // Wait that long and the server should send a PING frame. + now += default_timeout() / 2; + let pings_before = server.stats().frame_tx.ping; + let ping = server.process_output(now).dgram(); + assert!(ping.is_some()); + assert_eq!(server.stats().frame_tx.ping, pings_before + 1); + + // Wait for ping to be marked lost. + assert!(server.process_output(now).callback() < AT_LEAST_PTO); + now += AT_LEAST_PTO; + let pings_before2 = server.stats().frame_tx.ping; + let ping = server.process_output(now).dgram(); + assert!(ping.is_some()); + assert_eq!(server.stats().frame_tx.ping, pings_before2 + 1); + + // Exchange ack for the PING. + let out = client.process(ping, now).dgram(); + + now += Duration::from_millis(20); + let out = server.process(out, now).dgram(); + + assert!(client.process(out, now).dgram().is_none()); + + // TODO: if we run server.process with current value of now, the server will + // return some small timeout for the recovry although it does not have + // any outstanding data. Therefore we call it after AT_LEAST_PTO. + now += AT_LEAST_PTO; + assert_idle(&mut server, now, default_timeout() / 2 - AT_LEAST_PTO); +} + +/// The other peer can also keep it alive. +#[test] +fn keep_alive_responder() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut server, &mut client); + let mut now = now(); + + // Marking the stream for keep-alive changes the idle timeout. + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now, default_timeout() / 2); + + // Wait that long and the client should send a PING frame. + now += default_timeout() / 2; + let pings_before = client.stats().frame_tx.ping; + let ping = client.process_output(now).dgram(); + assert!(ping.is_some()); + assert_eq!(client.stats().frame_tx.ping, pings_before + 1); +} + +/// Unmark a stream as being keep-alive. +#[test] +fn keep_alive_unmark() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut client, &mut server); + + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + client.stream_keep_alive(stream, false).unwrap(); + assert_idle(&mut client, now(), default_timeout()); +} + +/// The sender has something to send. Make it send it +/// and cause the receiver to become idle by sending something +/// else, reordering the packets, and consuming the ACK. +/// Note that the sender might not be idle if the thing that it +/// sends results in something in addition to an ACK. +fn transfer_force_idle(sender: &mut Connection, receiver: &mut Connection) { + let dgram = sender.process_output(now()).dgram(); + let chaff = send_something(sender, now()); + receiver.process_input(chaff, now()); + receiver.process_input(dgram.unwrap(), now()); + let ack = receiver.process_output(now()).dgram(); + sender.process_input(ack.unwrap(), now()); +} + +/// Receiving the end of the stream stops keep-alives for that stream. +/// Even if that data hasn't been read. +#[test] +fn keep_alive_close() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut client, &mut server); + + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + client.stream_close_send(stream).unwrap(); + transfer_force_idle(&mut client, &mut server); + assert_idle(&mut client, now(), default_timeout() / 2); + + server.stream_close_send(stream).unwrap(); + transfer_force_idle(&mut server, &mut client); + assert_idle(&mut client, now(), default_timeout()); +} + +/// Receiving `RESET_STREAM` stops keep-alives for that stream, but only once +/// the sending side is also closed. +#[test] +fn keep_alive_reset() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut client, &mut server); + + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + client.stream_close_send(stream).unwrap(); + transfer_force_idle(&mut client, &mut server); + assert_idle(&mut client, now(), default_timeout() / 2); + + server.stream_reset_send(stream, 0).unwrap(); + transfer_force_idle(&mut server, &mut client); + assert_idle(&mut client, now(), default_timeout()); + + // The client will fade away from here. + let t = now() + (default_timeout() / 2); + assert_eq!(client.process_output(t).callback(), default_timeout() / 2); + let t = now() + default_timeout(); + assert_eq!(client.process_output(t), Output::None); +} + +/// Stopping sending also cancels the keep-alive. +#[test] +fn keep_alive_stop_sending() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut client, &mut server); + + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + client.stream_close_send(stream).unwrap(); + client.stream_stop_sending(stream, 0).unwrap(); + transfer_force_idle(&mut client, &mut server); + // The server will have sent RESET_STREAM, which the client will + // want to acknowledge, so force that out. + let junk = send_something(&mut server, now()); + let ack = client.process(Some(junk), now()).dgram(); + assert!(ack.is_some()); + + // Now the client should be idle. + assert_idle(&mut client, now(), default_timeout()); +} + +/// Multiple active streams are tracked properly. +#[test] +fn keep_alive_multiple_stop() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let stream = create_stream_idle(&mut client, &mut server); + + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + let other = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_keep_alive(other, true).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + client.stream_keep_alive(stream, false).unwrap(); + assert_idle(&mut client, now(), default_timeout() / 2); + + client.stream_keep_alive(other, false).unwrap(); + assert_idle(&mut client, now(), default_timeout()); +} + +/// If the RTT is too long relative to the idle timeout, the keep-alive is large too. +#[test] +fn keep_alive_large_rtt() { + let mut client = default_client(); + let mut server = default_server(); + // Use an RTT that is large enough to cause the PTO timer to exceed half + // the idle timeout. + let rtt = default_timeout() * 3 / 4; + let now = connect_with_rtt(&mut client, &mut server, now(), rtt); + let (now, stream) = create_stream_idle_rtt(&mut server, &mut client, now, rtt); + + // Calculating PTO here is tricky as RTTvar has eroded after multiple round trips. + // Just check that the delay is larger than the baseline and the RTT. + for endpoint in &mut [client, server] { + endpoint.stream_keep_alive(stream, true).unwrap(); + let delay = endpoint.process_output(now).callback(); + qtrace!([endpoint], "new delay {:?}", delay); + assert!(delay > default_timeout() / 2); + assert!(delay > rtt); + } +} + +/// Only the recipient of a unidirectional stream can keep it alive. +#[test] +fn keep_alive_uni() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let stream = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_keep_alive(stream, true).unwrap_err(); + let _ = client.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(); + let dgram = client.process_output(now()).dgram(); + + server.process_input(dgram.unwrap(), now()); + server.stream_keep_alive(stream, true).unwrap(); +} + +/// Test a keep-alive ping is send if there are outstading ack-eliciting packets and that +/// the connection is closed after the idle timeout passes. +#[test] +fn keep_alive_with_ack_eliciting_packet_lost() { + const RTT: Duration = Duration::from_millis(500); // PTO will be ~1.1125s + + // The idle time out will be set to ~ 5 * PTO. (IDLE_TIMEOUT/2 > pto and IDLE_TIMEOUT/2 < pto + 2pto) + // After handshake all packets will be lost. The following steps will happen after the handshake: + // - data will be sent on a stream that is marked for keep-alive, (at start time) + // - PTO timer will trigger first, and the data will be retransmited toghether with a PING, (at the start time + pto) + // - keep-alive timer will trigger and a keep-alive PING will be sent, (at the start time + IDLE_TIMEOUT / 2) + // - PTO timer will trigger again. (at the start time + pto + 2*pto) + // - Idle time out will trigger (at the timeout + IDLE_TIMEOUT) + const IDLE_TIMEOUT: Duration = Duration::from_millis(6000); + + let mut client = new_client(ConnectionParameters::default().idle_timeout(IDLE_TIMEOUT)); + let mut server = default_server(); + let mut now = connect_rtt_idle(&mut client, &mut server, RTT); + // connect_rtt_idle increase now by RTT / 2; + now -= RTT / 2; + assert_idle(&mut client, now, IDLE_TIMEOUT); + + // Create a stream. + let stream = client.stream_create(StreamType::BiDi).unwrap(); + // Marking the stream for keep-alive changes the idle timeout. + client.stream_keep_alive(stream, true).unwrap(); + assert_idle(&mut client, now, IDLE_TIMEOUT / 2); + + // Send data on the stream that will be lost. + let _ = client.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(); + let _lost_packet = client.process_output(now).dgram(); + + let pto = client.process_output(now).callback(); + // Wait for packet to be marked lost. + assert!(pto < IDLE_TIMEOUT / 2); + now += pto; + let retransmit = client.process_output(now).dgram(); + assert!(retransmit.is_some()); + let retransmit = client.process_output(now).dgram(); + assert!(retransmit.is_some()); + + // The next callback should be for an idle PING. + assert_eq!( + client.process_output(now).callback(), + IDLE_TIMEOUT / 2 - pto + ); + + // Wait that long and the client should send a PING frame. + now += IDLE_TIMEOUT / 2 - pto; + let pings_before = client.stats().frame_tx.ping; + let ping = client.process_output(now).dgram(); + assert!(ping.is_some()); + assert_eq!(client.stats().frame_tx.ping, pings_before + 1); + + // The next callback is for a PTO, the PTO timer is 2 * pto now. + assert_eq!(client.process_output(now).callback(), pto * 2); + now += pto * 2; + // Now we will retransmit stream data. + let retransmit = client.process_output(now).dgram(); + assert!(retransmit.is_some()); + let retransmit = client.process_output(now).dgram(); + assert!(retransmit.is_some()); + + // The next callback will be an idle timeout. + assert_eq!( + client.process_output(now).callback(), + IDLE_TIMEOUT / 2 - 2 * pto + ); + + now += IDLE_TIMEOUT / 2 - 2 * pto; + let out = client.process_output(now); + assert!(matches!(out, Output::None)); + assert!(matches!(client.state(), State::Closed(_))); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/keys.rs b/third_party/rust/neqo-transport/src/connection/tests/keys.rs new file mode 100644 index 0000000000..26a3768b7b --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/keys.rs @@ -0,0 +1,340 @@ +// 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::super::super::{ConnectionError, ERROR_AEAD_LIMIT_REACHED}; +use super::super::{Connection, ConnectionParameters, Error, Output, State, StreamType}; +use super::{ + connect, connect_force_idle, default_client, default_server, maybe_authenticate, + send_and_receive, send_something, AT_LEAST_PTO, +}; +use crate::crypto::{OVERWRITE_INVOCATIONS, UPDATE_WRITE_KEYS_AT}; +use crate::packet::PacketNumber; +use crate::path::PATH_MTU_V6; + +use neqo_common::{qdebug, Datagram}; +use std::mem; +use test_fixture::{self, now}; + +fn check_discarded( + peer: &mut Connection, + pkt: Datagram, + response: bool, + dropped: usize, + dups: usize, +) { + // Make sure to flush any saved datagrams before doing this. + mem::drop(peer.process_output(now())); + + let before = peer.stats(); + let out = peer.process(Some(pkt), now()); + assert_eq!(out.as_dgram_ref().is_some(), response); + let after = peer.stats(); + assert_eq!(dropped, after.dropped_rx - before.dropped_rx); + assert_eq!(dups, after.dups_rx - before.dups_rx); +} + +fn assert_update_blocked(c: &mut Connection) { + assert_eq!( + c.initiate_key_update().unwrap_err(), + Error::KeyUpdateBlocked + ); +} + +fn overwrite_invocations(n: PacketNumber) { + OVERWRITE_INVOCATIONS.with(|v| { + *v.borrow_mut() = Some(n); + }); +} + +#[test] +fn discarded_initial_keys() { + qdebug!("---- client: generate CH"); + let mut client = default_client(); + let init_pkt_c = client.process(None, now()).dgram(); + assert!(init_pkt_c.is_some()); + assert_eq!(init_pkt_c.as_ref().unwrap().len(), PATH_MTU_V6); + + qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN"); + let mut server = default_server(); + let init_pkt_s = server.process(init_pkt_c.clone(), now()).dgram(); + assert!(init_pkt_s.is_some()); + + qdebug!("---- client: cert verification"); + let out = client.process(init_pkt_s.clone(), now()).dgram(); + assert!(out.is_some()); + + // The client has received a handshake packet. It will remove the Initial keys. + // We will check this by processing init_pkt_s a second time. + // The initial packet should be dropped. The packet contains a Handshake packet as well, which + // will be marked as dup. And it will contain padding, which will be "dropped". + // The client will generate a Handshake packet here to avoid stalling. + check_discarded(&mut client, init_pkt_s.unwrap(), true, 2, 1); + + assert!(maybe_authenticate(&mut client)); + + // The server has not removed the Initial keys yet, because it has not yet received a Handshake + // packet from the client. + // We will check this by processing init_pkt_c a second time. + // The dropped packet is padding. The Initial packet has been mark dup. + check_discarded(&mut server, init_pkt_c.clone().unwrap(), false, 1, 1); + + qdebug!("---- client: SH..FIN -> FIN"); + let out = client.process(None, now()).dgram(); + assert!(out.is_some()); + + // The server will process the first Handshake packet. + // After this the Initial keys will be dropped. + let out = server.process(out, now()).dgram(); + assert!(out.is_some()); + + // Check that the Initial keys are dropped at the server + // We will check this by processing init_pkt_c a third time. + // The Initial packet has been dropped and padding that follows it. + // There is no dups, everything has been dropped. + check_discarded(&mut server, init_pkt_c.unwrap(), false, 1, 0); +} + +#[test] +fn key_update_client() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let mut now = now(); + + assert_eq!(client.get_epochs(), (Some(3), Some(3))); // (write, read) + assert_eq!(server.get_epochs(), (Some(3), Some(3))); + + assert!(client.initiate_key_update().is_ok()); + assert_update_blocked(&mut client); + + // Initiating an update should only increase the write epoch. + let idle_timeout = ConnectionParameters::default().get_idle_timeout(); + assert_eq!(Output::Callback(idle_timeout), client.process(None, now)); + assert_eq!(client.get_epochs(), (Some(4), Some(3))); + + // Send something to propagate the update. + assert!(send_and_receive(&mut client, &mut server, now).is_none()); + + // The server should now be waiting to discharge read keys. + assert_eq!(server.get_epochs(), (Some(4), Some(3))); + let res = server.process(None, now); + if let Output::Callback(t) = res { + assert!(t < idle_timeout); + } else { + panic!("server should now be waiting to clear keys"); + } + + // Without having had time to purge old keys, more updates are blocked. + // The spec would permits it at this point, but we are more conservative. + assert_update_blocked(&mut client); + // The server can't update until it receives an ACK for a packet. + assert_update_blocked(&mut server); + + // Waiting now for at least a PTO should cause the server to drop old keys. + // But at this point the client hasn't received a key update from the server. + // It will be stuck with old keys. + now += AT_LEAST_PTO; + let dgram = client.process(None, now).dgram(); + assert!(dgram.is_some()); // Drop this packet. + assert_eq!(client.get_epochs(), (Some(4), Some(3))); + mem::drop(server.process(None, now)); + assert_eq!(server.get_epochs(), (Some(4), Some(4))); + + // Even though the server has updated, it hasn't received an ACK yet. + assert_update_blocked(&mut server); + + // Now get an ACK from the server. + // The previous PTO packet (see above) was dropped, so we should get an ACK here. + let dgram = send_and_receive(&mut client, &mut server, now); + assert!(dgram.is_some()); + let res = client.process(dgram, now); + // This is the first packet that the client has received from the server + // with new keys, so its read timer just started. + if let Output::Callback(t) = res { + assert!(t < ConnectionParameters::default().get_idle_timeout()); + } else { + panic!("client should now be waiting to clear keys"); + } + + assert_update_blocked(&mut client); + assert_eq!(client.get_epochs(), (Some(4), Some(3))); + // The server can't update until it gets something from the client. + assert_update_blocked(&mut server); + + now += AT_LEAST_PTO; + mem::drop(client.process(None, now)); + assert_eq!(client.get_epochs(), (Some(4), Some(4))); +} + +#[test] +fn key_update_consecutive() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let now = now(); + + assert!(server.initiate_key_update().is_ok()); + assert_eq!(server.get_epochs(), (Some(4), Some(3))); + + // Server sends something. + // Send twice and drop the first to induce an ACK from the client. + mem::drop(send_something(&mut server, now)); // Drop this. + + // Another packet from the server will cause the client to ACK and update keys. + let dgram = send_and_receive(&mut server, &mut client, now); + assert!(dgram.is_some()); + assert_eq!(client.get_epochs(), (Some(4), Some(3))); + + // Have the server process the ACK. + if let Output::Callback(_) = server.process(dgram, now) { + assert_eq!(server.get_epochs(), (Some(4), Some(3))); + // Now move the server temporarily into the future so that it + // rotates the keys. The client stays in the present. + mem::drop(server.process(None, now + AT_LEAST_PTO)); + assert_eq!(server.get_epochs(), (Some(4), Some(4))); + } else { + panic!("server should have a timer set"); + } + + // Now update keys on the server again. + assert!(server.initiate_key_update().is_ok()); + assert_eq!(server.get_epochs(), (Some(5), Some(4))); + + let dgram = send_something(&mut server, now + AT_LEAST_PTO); + + // However, as the server didn't wait long enough to update again, the + // client hasn't rotated its keys, so the packet gets dropped. + check_discarded(&mut client, dgram, false, 1, 0); +} + +// Key updates can't be initiated too early. +#[test] +fn key_update_before_confirmed() { + let mut client = default_client(); + assert_update_blocked(&mut client); + let mut server = default_server(); + assert_update_blocked(&mut server); + + // Client Initial + let dgram = client.process(None, now()).dgram(); + assert!(dgram.is_some()); + assert_update_blocked(&mut client); + + // Server Initial + Handshake + let dgram = server.process(dgram, now()).dgram(); + assert!(dgram.is_some()); + assert_update_blocked(&mut server); + + // Client Handshake + client.process_input(dgram.unwrap(), now()); + assert_update_blocked(&mut client); + + assert!(maybe_authenticate(&mut client)); + assert_update_blocked(&mut client); + + let dgram = client.process(None, now()).dgram(); + assert!(dgram.is_some()); + assert_update_blocked(&mut client); + + // Server HANDSHAKE_DONE + let dgram = server.process(dgram, now()).dgram(); + assert!(dgram.is_some()); + assert!(server.initiate_key_update().is_ok()); + + // Client receives HANDSHAKE_DONE + let dgram = client.process(dgram, now()).dgram(); + assert!(dgram.is_none()); + assert!(client.initiate_key_update().is_ok()); +} + +#[test] +fn exhaust_write_keys() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + overwrite_invocations(0); + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert!(client.stream_send(stream_id, b"explode!").is_ok()); + let dgram = client.process_output(now()).dgram(); + assert!(dgram.is_none()); + assert!(matches!( + client.state(), + State::Closed(ConnectionError::Transport(Error::KeysExhausted)) + )); +} + +#[test] +fn exhaust_read_keys() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let dgram = send_something(&mut client, now()); + + overwrite_invocations(0); + let dgram = server.process(Some(dgram), now()).dgram(); + assert!(matches!( + server.state(), + State::Closed(ConnectionError::Transport(Error::KeysExhausted)) + )); + + client.process_input(dgram.unwrap(), now()); + assert!(matches!( + client.state(), + State::Draining { + error: ConnectionError::Transport(Error::PeerError(ERROR_AEAD_LIMIT_REACHED)), + .. + } + )); +} + +#[test] +fn automatic_update_write_keys() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + overwrite_invocations(UPDATE_WRITE_KEYS_AT); + mem::drop(send_something(&mut client, now())); + assert_eq!(client.get_epochs(), (Some(4), Some(3))); +} + +#[test] +fn automatic_update_write_keys_later() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + overwrite_invocations(UPDATE_WRITE_KEYS_AT + 2); + // No update after the first. + mem::drop(send_something(&mut client, now())); + assert_eq!(client.get_epochs(), (Some(3), Some(3))); + // The second will update though. + mem::drop(send_something(&mut client, now())); + assert_eq!(client.get_epochs(), (Some(4), Some(3))); +} + +#[test] +fn automatic_update_write_keys_blocked() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + // An outstanding key update will block the automatic update. + client.initiate_key_update().unwrap(); + + overwrite_invocations(UPDATE_WRITE_KEYS_AT); + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert!(client.stream_send(stream_id, b"explode!").is_ok()); + let dgram = client.process_output(now()).dgram(); + // Not being able to update is fatal. + assert!(dgram.is_none()); + assert!(matches!( + client.state(), + State::Closed(ConnectionError::Transport(Error::KeysExhausted)) + )); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/migration.rs b/third_party/rust/neqo-transport/src/connection/tests/migration.rs new file mode 100644 index 0000000000..9e6a2ba90b --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/migration.rs @@ -0,0 +1,941 @@ +// 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::super::{Connection, Output, State, StreamType}; +use super::{ + connect_fail, connect_force_idle, connect_rtt_idle, default_client, default_server, + maybe_authenticate, new_client, new_server, send_something, CountingConnectionIdGenerator, +}; +use crate::cid::LOCAL_ACTIVE_CID_LIMIT; +use crate::frame::FRAME_TYPE_NEW_CONNECTION_ID; +use crate::packet::PacketBuilder; +use crate::path::{PATH_MTU_V4, PATH_MTU_V6}; +use crate::tparams::{self, PreferredAddress, TransportParameter}; +use crate::{ + ConnectionError, ConnectionId, ConnectionIdDecoder, ConnectionIdGenerator, ConnectionIdRef, + ConnectionParameters, EmptyConnectionIdGenerator, Error, +}; + +use neqo_common::{Datagram, Decoder}; +use std::cell::RefCell; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; +use std::rc::Rc; +use std::time::{Duration, Instant}; +use test_fixture::{ + self, addr, addr_v4, + assertions::{assert_v4_path, assert_v6_path}, + fixture_init, now, +}; + +/// This should be a valid-seeming transport parameter. +/// And it should have different values to `addr` and `addr_v4`. +const SAMPLE_PREFERRED_ADDRESS: &[u8] = &[ + 0xc0, 0x00, 0x02, 0x02, 0x01, 0xbb, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0xbb, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, +]; + +// These tests generally use two paths: +// The connection is established on a path with the same IPv6 address on both ends. +// Migrations move to a path with the same IPv4 address on both ends. +// This simplifies validation as the same assertions can be used for client and server. +// The risk is that there is a place where source/destination local/remote is inverted. + +fn loopback() -> SocketAddr { + SocketAddr::new(IpAddr::V6(Ipv6Addr::from(1)), 443) +} + +fn change_path(d: &Datagram, a: SocketAddr) -> Datagram { + Datagram::new(a, a, &d[..]) +} + +fn new_port(a: SocketAddr) -> SocketAddr { + let (port, _) = a.port().overflowing_add(410); + SocketAddr::new(a.ip(), port) +} + +fn change_source_port(d: &Datagram) -> Datagram { + Datagram::new(new_port(d.source()), d.destination(), &d[..]) +} + +/// As these tests use a new path, that path often has a non-zero RTT. +/// Pacing can be a problem when testing that path. This skips time forward. +fn skip_pacing(c: &mut Connection, now: Instant) -> Instant { + let pacing = c.process_output(now).callback(); + assert_ne!(pacing, Duration::new(0, 0)); + now + pacing +} + +#[test] +fn rebinding_port() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let dgram = send_something(&mut client, now()); + let dgram = change_source_port(&dgram); + + server.process_input(dgram, now()); + // Have the server send something so that it generates a packet. + let stream_id = server.stream_create(StreamType::UniDi).unwrap(); + server.stream_close_send(stream_id).unwrap(); + let dgram = server.process_output(now()).dgram(); + let dgram = dgram.unwrap(); + assert_eq!(dgram.source(), addr()); + assert_eq!(dgram.destination(), new_port(addr())); +} + +/// This simulates an attack where a valid packet is forwarded on +/// a different path. This shows how both paths are probed and the +/// server eventually returns to the original path. +#[test] +fn path_forwarding_attack() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let mut now = now(); + + let dgram = send_something(&mut client, now); + let dgram = change_path(&dgram, addr_v4()); + server.process_input(dgram, now); + + // The server now probes the new (primary) path. + let new_probe = server.process_output(now).dgram().unwrap(); + assert_eq!(server.stats().frame_tx.path_challenge, 1); + assert_v4_path(&new_probe, false); // Can't be padded. + + // The server also probes the old path. + let old_probe = server.process_output(now).dgram().unwrap(); + assert_eq!(server.stats().frame_tx.path_challenge, 2); + assert_v6_path(&old_probe, true); + + // New data from the server is sent on the new path, but that is + // now constrained by the amplification limit. + let stream_id = server.stream_create(StreamType::UniDi).unwrap(); + server.stream_close_send(stream_id).unwrap(); + assert!(server.process_output(now).dgram().is_none()); + + // The client should respond to the challenge on the new path. + // The server couldn't pad, so the client is also amplification limited. + let new_resp = client.process(Some(new_probe), now).dgram().unwrap(); + assert_eq!(client.stats().frame_rx.path_challenge, 1); + assert_eq!(client.stats().frame_tx.path_challenge, 1); + assert_eq!(client.stats().frame_tx.path_response, 1); + assert_v4_path(&new_resp, false); + + // The client also responds to probes on the old path. + let old_resp = client.process(Some(old_probe), now).dgram().unwrap(); + assert_eq!(client.stats().frame_rx.path_challenge, 2); + assert_eq!(client.stats().frame_tx.path_challenge, 1); + assert_eq!(client.stats().frame_tx.path_response, 2); + assert_v6_path(&old_resp, true); + + // But the client still sends data on the old path. + let client_data1 = send_something(&mut client, now); + assert_v6_path(&client_data1, false); // Just data. + + // Receiving the PATH_RESPONSE from the client opens the amplification + // limit enough for the server to respond. + // This is padded because it includes PATH_CHALLENGE. + let server_data1 = server.process(Some(new_resp), now).dgram().unwrap(); + assert_v4_path(&server_data1, true); + assert_eq!(server.stats().frame_tx.path_challenge, 3); + + // The client responds to this probe on the new path. + client.process_input(server_data1, now); + let stream_before = client.stats().frame_tx.stream; + let padded_resp = send_something(&mut client, now); + assert_eq!(stream_before, client.stats().frame_tx.stream); + assert_v4_path(&padded_resp, true); // This is padded! + + // But new data from the client stays on the old path. + let client_data2 = client.process_output(now).dgram().unwrap(); + assert_v6_path(&client_data2, false); + + // The server keeps sending on the new path. + now = skip_pacing(&mut server, now); + let server_data2 = send_something(&mut server, now); + assert_v4_path(&server_data2, false); + + // Until new data is received from the client on the old path. + server.process_input(client_data2, now); + // The server sends a probe on the "old" path. + let server_data3 = send_something(&mut server, now); + assert_v4_path(&server_data3, true); + // But switches data transmission to the "new" path. + let server_data4 = server.process_output(now).dgram().unwrap(); + assert_v6_path(&server_data4, false); +} + +#[test] +fn migrate_immediate() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let mut now = now(); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), true, now) + .unwrap(); + + let client1 = send_something(&mut client, now); + assert_v4_path(&client1, true); // Contains PATH_CHALLENGE. + let client2 = send_something(&mut client, now); + assert_v4_path(&client2, false); // Doesn't. + + let server_delayed = send_something(&mut server, now); + + // The server accepts the first packet and migrates (but probes). + let server1 = server.process(Some(client1), now).dgram().unwrap(); + assert_v4_path(&server1, true); + let server2 = server.process_output(now).dgram().unwrap(); + assert_v6_path(&server2, true); + + // The second packet has no real effect, it just elicits an ACK. + let all_before = server.stats().frame_tx.all; + let ack_before = server.stats().frame_tx.ack; + let server3 = server.process(Some(client2), now).dgram(); + assert!(server3.is_some()); + assert_eq!(server.stats().frame_tx.all, all_before + 1); + assert_eq!(server.stats().frame_tx.ack, ack_before + 1); + + // Receiving a packet sent by the server before migration doesn't change path. + client.process_input(server_delayed, now); + now = skip_pacing(&mut client, now); + let client3 = send_something(&mut client, now); + assert_v4_path(&client3, false); +} + +/// RTT estimates for paths should be preserved across migrations. +#[test] +fn migrate_rtt() { + const RTT: Duration = Duration::from_millis(20); + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, RTT); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), true, now) + .unwrap(); + // The RTT might be increased for the new path, so allow a little flexibility. + let rtt = client.paths.rtt(); + assert!(rtt > RTT); + assert!(rtt < RTT * 2); +} + +#[test] +fn migrate_immediate_fail() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let mut now = now(); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), true, now) + .unwrap(); + + let probe = client.process_output(now).dgram().unwrap(); + assert_v4_path(&probe, true); // Contains PATH_CHALLENGE. + + for _ in 0..2 { + let cb = client.process_output(now).callback(); + assert_ne!(cb, Duration::new(0, 0)); + now += cb; + + let before = client.stats().frame_tx; + let probe = client.process_output(now).dgram().unwrap(); + assert_v4_path(&probe, true); // Contains PATH_CHALLENGE. + let after = client.stats().frame_tx; + assert_eq!(after.path_challenge, before.path_challenge + 1); + assert_eq!(after.padding, before.padding + 1); + assert_eq!(after.all, before.all + 2); + + // This might be a PTO, which will result in sending a probe. + if let Some(probe) = client.process_output(now).dgram() { + assert_v4_path(&probe, false); // Contains PATH_CHALLENGE. + let after = client.stats().frame_tx; + assert_eq!(after.ping, before.ping + 1); + assert_eq!(after.all, before.all + 3); + } + } + + let pto = client.process_output(now).callback(); + assert_ne!(pto, Duration::new(0, 0)); + now += pto; + + // The client should fall back to the original path and retire the connection ID. + let fallback = client.process_output(now).dgram(); + assert_v6_path(&fallback.unwrap(), false); + assert_eq!(client.stats().frame_tx.retire_connection_id, 1); +} + +/// Migrating to the same path shouldn't do anything special, +/// except that the path is probed. +#[test] +fn migrate_same() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let now = now(); + + client + .migrate(Some(addr()), Some(addr()), true, now) + .unwrap(); + + let probe = client.process_output(now).dgram().unwrap(); + assert_v6_path(&probe, true); // Contains PATH_CHALLENGE. + assert_eq!(client.stats().frame_tx.path_challenge, 1); + + let resp = server.process(Some(probe), now).dgram().unwrap(); + assert_v6_path(&resp, true); + assert_eq!(server.stats().frame_tx.path_response, 1); + assert_eq!(server.stats().frame_tx.path_challenge, 0); + + // Everything continues happily. + client.process_input(resp, now); + let contd = send_something(&mut client, now); + assert_v6_path(&contd, false); +} + +/// Migrating to the same path, if it fails, causes the connection to fail. +#[test] +fn migrate_same_fail() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let mut now = now(); + + client + .migrate(Some(addr()), Some(addr()), true, now) + .unwrap(); + + let probe = client.process_output(now).dgram().unwrap(); + assert_v6_path(&probe, true); // Contains PATH_CHALLENGE. + + for _ in 0..2 { + let cb = client.process_output(now).callback(); + assert_ne!(cb, Duration::new(0, 0)); + now += cb; + + let before = client.stats().frame_tx; + let probe = client.process_output(now).dgram().unwrap(); + assert_v6_path(&probe, true); // Contains PATH_CHALLENGE. + let after = client.stats().frame_tx; + assert_eq!(after.path_challenge, before.path_challenge + 1); + assert_eq!(after.padding, before.padding + 1); + assert_eq!(after.all, before.all + 2); + + // This might be a PTO, which will result in sending a probe. + if let Some(probe) = client.process_output(now).dgram() { + assert_v6_path(&probe, false); // Contains PATH_CHALLENGE. + let after = client.stats().frame_tx; + assert_eq!(after.ping, before.ping + 1); + assert_eq!(after.all, before.all + 3); + } + } + + let pto = client.process_output(now).callback(); + assert_ne!(pto, Duration::new(0, 0)); + now += pto; + + // The client should mark this path as failed and close immediately. + let res = client.process_output(now); + assert!(matches!(res, Output::None)); + assert!(matches!( + client.state(), + State::Closed(ConnectionError::Transport(Error::NoAvailablePath)) + )); +} + +/// This gets the connection ID from a datagram using the default +/// connection ID generator/decoder. +fn get_cid(d: &Datagram) -> ConnectionIdRef { + let gen = CountingConnectionIdGenerator::default(); + assert_eq!(d[0] & 0x80, 0); // Only support short packets for now. + gen.decode_cid(&mut Decoder::from(&d[1..])).unwrap() +} + +fn migration(mut client: Connection) { + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let now = now(); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), false, now) + .unwrap(); + + let probe = client.process_output(now).dgram().unwrap(); + assert_v4_path(&probe, true); // Contains PATH_CHALLENGE. + assert_eq!(client.stats().frame_tx.path_challenge, 1); + let probe_cid = ConnectionId::from(&get_cid(&probe)); + + let resp = server.process(Some(probe), now).dgram().unwrap(); + assert_v4_path(&resp, true); + assert_eq!(server.stats().frame_tx.path_response, 1); + assert_eq!(server.stats().frame_tx.path_challenge, 1); + + // Data continues to be exchanged on the new path. + let client_data = send_something(&mut client, now); + assert_ne!(get_cid(&client_data), probe_cid); + assert_v6_path(&client_data, false); + server.process_input(client_data, now); + let server_data = send_something(&mut server, now); + assert_v6_path(&server_data, false); + + // Once the client receives the probe response, it migrates to the new path. + client.process_input(resp, now); + assert_eq!(client.stats().frame_rx.path_challenge, 1); + let migrate_client = send_something(&mut client, now); + assert_v4_path(&migrate_client, true); // Responds to server probe. + + // The server now sees the migration and will switch over. + // However, it will probe the old path again, even though it has just + // received a response to its last probe, because it needs to verify + // that the migration is genuine. + server.process_input(migrate_client, now); + let stream_before = server.stats().frame_tx.stream; + let probe_old_server = send_something(&mut server, now); + // This is just the double-check probe; no STREAM frames. + assert_v6_path(&probe_old_server, true); + assert_eq!(server.stats().frame_tx.path_challenge, 2); + assert_eq!(server.stats().frame_tx.stream, stream_before); + + // The server then sends data on the new path. + let migrate_server = server.process_output(now).dgram().unwrap(); + assert_v4_path(&migrate_server, false); + assert_eq!(server.stats().frame_tx.path_challenge, 2); + assert_eq!(server.stats().frame_tx.stream, stream_before + 1); + + // The client receives these checks and responds to the probe, but uses the new path. + client.process_input(migrate_server, now); + client.process_input(probe_old_server, now); + let old_probe_resp = send_something(&mut client, now); + assert_v6_path(&old_probe_resp, true); + let client_confirmation = client.process_output(now).dgram().unwrap(); + assert_v4_path(&client_confirmation, false); + + // The server has now sent 2 packets, so it is blocked on the pacer. Wait. + let server_pacing = server.process_output(now).callback(); + assert_ne!(server_pacing, Duration::new(0, 0)); + // ... then confirm that the server sends on the new path still. + let server_confirmation = send_something(&mut server, now + server_pacing); + assert_v4_path(&server_confirmation, false); +} + +#[test] +fn migration_graceful() { + migration(default_client()); +} + +/// A client should be able to migrate when it has a zero-length connection ID. +#[test] +fn migration_client_empty_cid() { + fixture_init(); + let client = Connection::new_client( + test_fixture::DEFAULT_SERVER_NAME, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())), + addr(), + addr(), + ConnectionParameters::default(), + now(), + ) + .unwrap(); + migration(client); +} + +/// Drive the handshake in the most expeditious fashion. +/// Returns the packet containing `HANDSHAKE_DONE` from the server. +fn fast_handshake(client: &mut Connection, server: &mut Connection) -> Option<Datagram> { + let dgram = client.process_output(now()).dgram(); + let dgram = server.process(dgram, now()).dgram(); + client.process_input(dgram.unwrap(), now()); + assert!(maybe_authenticate(client)); + let dgram = client.process_output(now()).dgram(); + server.process(dgram, now()).dgram() +} + +fn preferred_address(hs_client: SocketAddr, hs_server: SocketAddr, preferred: SocketAddr) { + let mtu = match hs_client.ip() { + IpAddr::V4(_) => PATH_MTU_V4, + IpAddr::V6(_) => PATH_MTU_V6, + }; + let assert_orig_path = |d: &Datagram, full_mtu: bool| { + assert_eq!( + d.destination(), + if d.source() == hs_client { + hs_server + } else if d.source() == hs_server { + hs_client + } else { + panic!(); + } + ); + if full_mtu { + assert_eq!(d.len(), mtu); + } + }; + let assert_toward_spa = |d: &Datagram, full_mtu: bool| { + assert_eq!(d.destination(), preferred); + assert_eq!(d.source(), hs_client); + if full_mtu { + assert_eq!(d.len(), mtu); + } + }; + let assert_from_spa = |d: &Datagram, full_mtu: bool| { + assert_eq!(d.destination(), hs_client); + assert_eq!(d.source(), preferred); + if full_mtu { + assert_eq!(d.len(), mtu); + } + }; + + fixture_init(); + let mut client = Connection::new_client( + test_fixture::DEFAULT_SERVER_NAME, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())), + hs_client, + hs_server, + ConnectionParameters::default(), + now(), + ) + .unwrap(); + let spa = if preferred.ip().is_ipv6() { + PreferredAddress::new(None, Some(preferred)) + } else { + PreferredAddress::new(Some(preferred), None) + }; + let mut server = new_server(ConnectionParameters::default().preferred_address(spa)); + + let dgram = fast_handshake(&mut client, &mut server); + + // The client is about to process HANDSHAKE_DONE. + // It should start probing toward the server's preferred address. + let probe = client.process(dgram, now()).dgram().unwrap(); + assert_toward_spa(&probe, true); + assert_eq!(client.stats().frame_tx.path_challenge, 1); + assert_ne!(client.process_output(now()).callback(), Duration::new(0, 0)); + + // Data continues on the main path for the client. + let data = send_something(&mut client, now()); + assert_orig_path(&data, false); + + // The server responds to the probe. + let resp = server.process(Some(probe), now()).dgram().unwrap(); + assert_from_spa(&resp, true); + assert_eq!(server.stats().frame_tx.path_challenge, 1); + assert_eq!(server.stats().frame_tx.path_response, 1); + + // Data continues on the main path for the server. + server.process_input(data, now()); + let data = send_something(&mut server, now()); + assert_orig_path(&data, false); + + // Client gets the probe response back and it migrates. + client.process_input(resp, now()); + client.process_input(data, now()); + let data = send_something(&mut client, now()); + assert_toward_spa(&data, true); + assert_eq!(client.stats().frame_tx.stream, 2); + assert_eq!(client.stats().frame_tx.path_response, 1); + + // The server sees the migration and probes the old path. + let probe = server.process(Some(data), now()).dgram().unwrap(); + assert_orig_path(&probe, true); + assert_eq!(server.stats().frame_tx.path_challenge, 2); + + // But data now goes on the new path. + let data = send_something(&mut server, now()); + assert_from_spa(&data, false); +} + +/// Migration works for a new port number. +#[test] +fn preferred_address_new_port() { + let a = addr(); + preferred_address(a, a, new_port(a)); +} + +/// Migration works for a new address too. +#[test] +fn preferred_address_new_address() { + let mut preferred = addr(); + preferred.set_ip(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2))); + preferred_address(addr(), addr(), preferred); +} + +/// Migration works for IPv4 addresses. +#[test] +fn preferred_address_new_port_v4() { + let a = addr_v4(); + preferred_address(a, a, new_port(a)); +} + +/// Migrating to a loopback address is OK if we started there. +#[test] +fn preferred_address_loopback() { + let a = loopback(); + preferred_address(a, a, new_port(a)); +} + +fn expect_no_migration(client: &mut Connection, server: &mut Connection) { + let dgram = fast_handshake(client, server); + + // The client won't probe now, though it could; it remains idle. + let out = client.process(dgram, now()); + assert_ne!(out.callback(), Duration::new(0, 0)); + + // Data continues on the main path for the client. + let data = send_something(client, now()); + assert_v6_path(&data, false); + assert_eq!(client.stats().frame_tx.path_challenge, 0); +} + +fn preferred_address_ignored(spa: PreferredAddress) { + let mut client = default_client(); + let mut server = new_server(ConnectionParameters::default().preferred_address(spa)); + + expect_no_migration(&mut client, &mut server); +} + +/// Using a loopback address in the preferred address is ignored. +#[test] +fn preferred_address_ignore_loopback() { + preferred_address_ignored(PreferredAddress::new(None, Some(loopback()))); +} + +/// A preferred address in the wrong address family is ignored. +#[test] +fn preferred_address_ignore_different_family() { + preferred_address_ignored(PreferredAddress::new(Some(addr_v4()), None)); +} + +/// Disabling preferred addresses at the client means that it ignores a perfectly +/// good preferred address. +#[test] +fn preferred_address_disabled_client() { + let mut client = new_client(ConnectionParameters::default().disable_preferred_address()); + let mut preferred = addr(); + preferred.set_ip(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2))); + let spa = PreferredAddress::new(None, Some(preferred)); + let mut server = new_server(ConnectionParameters::default().preferred_address(spa)); + + expect_no_migration(&mut client, &mut server); +} + +#[test] +fn preferred_address_empty_cid() { + fixture_init(); + + let spa = PreferredAddress::new(None, Some(new_port(addr()))); + let res = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())), + ConnectionParameters::default().preferred_address(spa), + ); + assert_eq!(res.unwrap_err(), Error::ConnectionIdsExhausted); +} + +/// A server cannot include a preferred address if it chooses an empty connection ID. +#[test] +fn preferred_address_server_empty_cid() { + let mut client = default_client(); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(EmptyConnectionIdGenerator::default())), + ConnectionParameters::default(), + ) + .unwrap(); + + server + .set_local_tparam( + tparams::PREFERRED_ADDRESS, + TransportParameter::Bytes(SAMPLE_PREFERRED_ADDRESS.to_vec()), + ) + .unwrap(); + + connect_fail( + &mut client, + &mut server, + Error::TransportParameterError, + Error::PeerError(Error::TransportParameterError.code()), + ); +} + +/// A client shouldn't send a preferred address transport parameter. +#[test] +fn preferred_address_client() { + let mut client = default_client(); + let mut server = default_server(); + + client + .set_local_tparam( + tparams::PREFERRED_ADDRESS, + TransportParameter::Bytes(SAMPLE_PREFERRED_ADDRESS.to_vec()), + ) + .unwrap(); + + connect_fail( + &mut client, + &mut server, + Error::PeerError(Error::TransportParameterError.code()), + Error::TransportParameterError, + ); +} + +/// Test that migration isn't permitted if the connection isn't in the right state. +#[test] +fn migration_invalid_state() { + let mut client = default_client(); + assert!(client + .migrate(Some(addr()), Some(addr()), false, now()) + .is_err()); + + let mut server = default_server(); + assert!(server + .migrate(Some(addr()), Some(addr()), false, now()) + .is_err()); + connect_force_idle(&mut client, &mut server); + + assert!(server + .migrate(Some(addr()), Some(addr()), false, now()) + .is_err()); + + client.close(now(), 0, "closing"); + assert!(client + .migrate(Some(addr()), Some(addr()), false, now()) + .is_err()); + let close = client.process(None, now()).dgram(); + + let dgram = server.process(close, now()).dgram(); + assert!(server + .migrate(Some(addr()), Some(addr()), false, now()) + .is_err()); + + client.process_input(dgram.unwrap(), now()); + assert!(client + .migrate(Some(addr()), Some(addr()), false, now()) + .is_err()); +} + +#[test] +fn migration_invalid_address() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let mut cant_migrate = |local, remote| { + assert_eq!( + client.migrate(local, remote, true, now()).unwrap_err(), + Error::InvalidMigration + ); + }; + + // Providing neither address is pointless and therefore an error. + cant_migrate(None, None); + + // Providing a zero port number isn't valid. + let mut zero_port = addr(); + zero_port.set_port(0); + cant_migrate(None, Some(zero_port)); + cant_migrate(Some(zero_port), None); + + // An unspecified remote address is bad. + let mut remote_unspecified = addr(); + remote_unspecified.set_ip(IpAddr::V6(Ipv6Addr::from(0))); + cant_migrate(None, Some(remote_unspecified)); + + // Mixed address families is bad. + cant_migrate(Some(addr()), Some(addr_v4())); + cant_migrate(Some(addr_v4()), Some(addr())); + + // Loopback to non-loopback is bad. + cant_migrate(Some(addr()), Some(loopback())); + cant_migrate(Some(loopback()), Some(addr())); + assert_eq!( + client + .migrate(Some(addr()), Some(loopback()), true, now()) + .unwrap_err(), + Error::InvalidMigration + ); + assert_eq!( + client + .migrate(Some(loopback()), Some(addr()), true, now()) + .unwrap_err(), + Error::InvalidMigration + ); +} + +/// This inserts a frame into packets that provides a single new +/// connection ID and retires all others. +struct RetireAll { + cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>>, +} + +impl crate::connection::test_internal::FrameWriter for RetireAll { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + // Use a sequence number that is large enough that all existing values + // will be lower (so they get retired). As the code doesn't care about + // gaps in sequence numbers, this is safe, even though the gap might + // hint that there are more outstanding connection IDs that are allowed. + const SEQNO: u64 = 100; + let cid = self.cid_gen.borrow_mut().generate_cid().unwrap(); + builder + .encode_varint(FRAME_TYPE_NEW_CONNECTION_ID) + .encode_varint(SEQNO) + .encode_varint(SEQNO) // Retire Prior To + .encode_vec(1, &cid) + .encode(&[0x7f; 16]); + } +} + +/// Test that forcing retirement of connection IDs forces retirement of all active +/// connection IDs and the use of of newer one. +#[test] +fn retire_all() { + let mut client = default_client(); + let cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>> = + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::clone(&cid_gen), + ConnectionParameters::default(), + ) + .unwrap(); + connect_force_idle(&mut client, &mut server); + + let original_cid = ConnectionId::from(&get_cid(&send_something(&mut client, now()))); + + server.test_frame_writer = Some(Box::new(RetireAll { cid_gen })); + let ncid = send_something(&mut server, now()); + server.test_frame_writer = None; + + let new_cid_before = client.stats().frame_rx.new_connection_id; + let retire_cid_before = client.stats().frame_tx.retire_connection_id; + client.process_input(ncid, now()); + let retire = send_something(&mut client, now()); + assert_eq!( + client.stats().frame_rx.new_connection_id, + new_cid_before + 1 + ); + assert_eq!( + client.stats().frame_tx.retire_connection_id, + retire_cid_before + LOCAL_ACTIVE_CID_LIMIT + ); + + assert_ne!(get_cid(&retire), original_cid); +} + +/// During a graceful migration, if the probed path can't get a new connection ID due +/// to being forced to retire the one it is using, the migration will fail. +#[test] +fn retire_prior_to_migration_failure() { + let mut client = default_client(); + let cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>> = + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::clone(&cid_gen), + ConnectionParameters::default(), + ) + .unwrap(); + connect_force_idle(&mut client, &mut server); + + let original_cid = ConnectionId::from(&get_cid(&send_something(&mut client, now()))); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), false, now()) + .unwrap(); + + // The client now probes the new path. + let probe = client.process_output(now()).dgram().unwrap(); + assert_v4_path(&probe, true); + assert_eq!(client.stats().frame_tx.path_challenge, 1); + let probe_cid = ConnectionId::from(&get_cid(&probe)); + assert_ne!(original_cid, probe_cid); + + // Have the server receive the probe, but separately have it decide to + // retire all of the available connection IDs. + server.test_frame_writer = Some(Box::new(RetireAll { cid_gen })); + let retire_all = send_something(&mut server, now()); + server.test_frame_writer = None; + + let resp = server.process(Some(probe), now()).dgram().unwrap(); + assert_v4_path(&resp, true); + assert_eq!(server.stats().frame_tx.path_response, 1); + assert_eq!(server.stats().frame_tx.path_challenge, 1); + + // Have the client receive the NEW_CONNECTION_ID with Retire Prior To. + client.process_input(retire_all, now()); + // This packet contains the probe response, which should be fine, but it + // also includes PATH_CHALLENGE for the new path, and the client can't + // respond without a connection ID. We treat this as a connection error. + client.process_input(resp, now()); + assert!(matches!( + client.state(), + State::Closing { + error: ConnectionError::Transport(Error::InvalidMigration), + .. + } + )); +} + +/// The timing of when frames arrive can mean that the migration path can +/// get the last available connection ID. +#[test] +fn retire_prior_to_migration_success() { + let mut client = default_client(); + let cid_gen: Rc<RefCell<dyn ConnectionIdGenerator>> = + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::clone(&cid_gen), + ConnectionParameters::default(), + ) + .unwrap(); + connect_force_idle(&mut client, &mut server); + + let original_cid = ConnectionId::from(&get_cid(&send_something(&mut client, now()))); + + client + .migrate(Some(addr_v4()), Some(addr_v4()), false, now()) + .unwrap(); + + // The client now probes the new path. + let probe = client.process_output(now()).dgram().unwrap(); + assert_v4_path(&probe, true); + assert_eq!(client.stats().frame_tx.path_challenge, 1); + let probe_cid = ConnectionId::from(&get_cid(&probe)); + assert_ne!(original_cid, probe_cid); + + // Have the server receive the probe, but separately have it decide to + // retire all of the available connection IDs. + server.test_frame_writer = Some(Box::new(RetireAll { cid_gen })); + let retire_all = send_something(&mut server, now()); + server.test_frame_writer = None; + + let resp = server.process(Some(probe), now()).dgram().unwrap(); + assert_v4_path(&resp, true); + assert_eq!(server.stats().frame_tx.path_response, 1); + assert_eq!(server.stats().frame_tx.path_challenge, 1); + + // Have the client receive the NEW_CONNECTION_ID with Retire Prior To second. + // As this occurs in a very specific order, migration succeeds. + client.process_input(resp, now()); + client.process_input(retire_all, now()); + + // Migration succeeds and the new path gets the last connection ID. + let dgram = send_something(&mut client, now()); + assert_v4_path(&dgram, false); + assert_ne!(get_cid(&dgram), original_cid); + assert_ne!(get_cid(&dgram), probe_cid); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/mod.rs b/third_party/rust/neqo-transport/src/connection/tests/mod.rs new file mode 100644 index 0000000000..3703246944 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/mod.rs @@ -0,0 +1,566 @@ +// 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. + +#![deny(clippy::pedantic)] + +use super::{Connection, ConnectionError, ConnectionId, Output, State}; +use crate::addr_valid::{AddressValidation, ValidateAddress}; +use crate::cc::{CWND_INITIAL_PKTS, CWND_MIN}; +use crate::cid::ConnectionIdRef; +use crate::events::ConnectionEvent; +use crate::path::PATH_MTU_V6; +use crate::recovery::ACK_ONLY_SIZE_LIMIT; +use crate::stats::{FrameStats, Stats, MAX_PTO_COUNTS}; +use crate::{ + ConnectionIdDecoder, ConnectionIdGenerator, ConnectionParameters, Error, StreamId, StreamType, + Version, +}; + +use std::cell::RefCell; +use std::cmp::min; +use std::convert::TryFrom; +use std::mem; +use std::rc::Rc; +use std::time::{Duration, Instant}; + +use neqo_common::{event::Provider, qdebug, qtrace, Datagram, Decoder, Role}; +use neqo_crypto::{random, AllowZeroRtt, AuthenticationStatus, ResumptionToken}; +use test_fixture::{self, addr, fixture_init, now}; + +// All the tests. +mod ackrate; +mod cc; +mod close; +mod datagram; +mod fuzzing; +mod handshake; +mod idle; +mod keys; +mod migration; +mod priority; +mod recovery; +mod resumption; +mod stream; +mod vn; +mod zerortt; + +const DEFAULT_RTT: Duration = Duration::from_millis(100); +const AT_LEAST_PTO: Duration = Duration::from_secs(1); +const DEFAULT_STREAM_DATA: &[u8] = b"message"; +/// The number of 1-RTT packets sent in `force_idle` by a client. +const FORCE_IDLE_CLIENT_1RTT_PACKETS: usize = 3; + +/// WARNING! In this module, this version of the generator needs to be used. +/// This copies the implementation from +/// `test_fixture::CountingConnectionIdGenerator`, but it uses the different +/// types that are exposed to this module. See also `default_client`. +/// +/// This version doesn't randomize the length; as the congestion control tests +/// count the amount of data sent precisely. +#[derive(Debug, Default)] +pub struct CountingConnectionIdGenerator { + counter: u32, +} + +impl ConnectionIdDecoder for CountingConnectionIdGenerator { + fn decode_cid<'a>(&self, dec: &mut Decoder<'a>) -> Option<ConnectionIdRef<'a>> { + let len = usize::from(dec.peek_byte().unwrap()); + dec.decode(len).map(ConnectionIdRef::from) + } +} + +impl ConnectionIdGenerator for CountingConnectionIdGenerator { + fn generate_cid(&mut self) -> Option<ConnectionId> { + let mut r = random(20); + r[0] = 8; + r[1] = u8::try_from(self.counter >> 24).unwrap(); + r[2] = u8::try_from((self.counter >> 16) & 0xff).unwrap(); + r[3] = u8::try_from((self.counter >> 8) & 0xff).unwrap(); + r[4] = u8::try_from(self.counter & 0xff).unwrap(); + self.counter += 1; + Some(ConnectionId::from(&r[..8])) + } + + fn as_decoder(&self) -> &dyn ConnectionIdDecoder { + self + } +} + +// This is fabulous: because test_fixture uses the public API for Connection, +// it gets a different type to the ones that are referenced via super::super::*. +// Thus, this code can't use default_client() and default_server() from +// test_fixture because they produce different - and incompatible - types. +// +// These are a direct copy of those functions. +pub fn new_client(params: ConnectionParameters) -> Connection { + fixture_init(); + Connection::new_client( + test_fixture::DEFAULT_SERVER_NAME, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + addr(), + addr(), + params, + now(), + ) + .expect("create a default client") +} +pub fn default_client() -> Connection { + new_client(ConnectionParameters::default()) +} + +pub fn new_server(params: ConnectionParameters) -> Connection { + fixture_init(); + + let mut c = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + params, + ) + .expect("create a default server"); + c.server_enable_0rtt(&test_fixture::anti_replay(), AllowZeroRtt {}) + .expect("enable 0-RTT"); + c +} +pub fn default_server() -> Connection { + new_server(ConnectionParameters::default()) +} +pub fn resumed_server(client: &Connection) -> Connection { + new_server(ConnectionParameters::default().versions(client.version(), Version::all())) +} + +/// If state is `AuthenticationNeeded` call `authenticated()`. This function will +/// consume all outstanding events on the connection. +pub fn maybe_authenticate(conn: &mut Connection) -> bool { + let authentication_needed = |e| matches!(e, ConnectionEvent::AuthenticationNeeded); + if conn.events().any(authentication_needed) { + conn.authenticated(AuthenticationStatus::Ok, now()); + return true; + } + false +} + +/// Drive the handshake between the client and server. +fn handshake( + client: &mut Connection, + server: &mut Connection, + now: Instant, + rtt: Duration, +) -> Instant { + let mut a = client; + let mut b = server; + let mut now = now; + + let mut input = None; + let is_done = |c: &mut Connection| { + matches!( + c.state(), + State::Confirmed | State::Closing { .. } | State::Closed(..) + ) + }; + + while !is_done(a) { + let _ = maybe_authenticate(a); + let had_input = input.is_some(); + let output = a.process(input, now).dgram(); + assert!(had_input || output.is_some()); + input = output; + qtrace!("handshake: t += {:?}", rtt / 2); + now += rtt / 2; + mem::swap(&mut a, &mut b); + } + if let Some(d) = input { + a.process_input(d, now); + } + now +} + +fn connect_fail( + client: &mut Connection, + server: &mut Connection, + client_error: Error, + server_error: Error, +) { + handshake(client, server, now(), Duration::new(0, 0)); + assert_error(client, &ConnectionError::Transport(client_error)); + assert_error(server, &ConnectionError::Transport(server_error)); +} + +fn connect_with_rtt( + client: &mut Connection, + server: &mut Connection, + now: Instant, + rtt: Duration, +) -> Instant { + fn check_rtt(stats: &Stats, rtt: Duration) { + assert_eq!(stats.rtt, rtt); + // Confirmation takes 2 round trips, + // so rttvar is reduced by 1/4 (from rtt/2). + assert_eq!(stats.rttvar, rtt * 3 / 8); + } + let now = handshake(client, server, now, rtt); + assert_eq!(*client.state(), State::Confirmed); + assert_eq!(*server.state(), State::Confirmed); + + check_rtt(&client.stats(), rtt); + check_rtt(&server.stats(), rtt); + now +} + +fn connect(client: &mut Connection, server: &mut Connection) { + connect_with_rtt(client, server, now(), Duration::new(0, 0)); +} + +fn assert_error(c: &Connection, expected: &ConnectionError) { + match c.state() { + State::Closing { error, .. } | State::Draining { error, .. } | State::Closed(error) => { + assert_eq!(*error, *expected, "{} error mismatch", c); + } + _ => panic!("bad state {:?}", c.state()), + } +} + +fn exchange_ticket( + client: &mut Connection, + server: &mut Connection, + now: Instant, +) -> ResumptionToken { + let validation = AddressValidation::new(now, ValidateAddress::NoToken).unwrap(); + let validation = Rc::new(RefCell::new(validation)); + server.set_validation(Rc::clone(&validation)); + server.send_ticket(now, &[]).expect("can send ticket"); + let ticket = server.process_output(now).dgram(); + assert!(ticket.is_some()); + client.process_input(ticket.unwrap(), now); + assert_eq!(*client.state(), State::Confirmed); + get_tokens(client).pop().expect("should have token") +} + +/// Getting the client and server to reach an idle state is surprisingly hard. +/// The server sends `HANDSHAKE_DONE` at the end of the handshake, and the client +/// doesn't immediately acknowledge it. Reordering packets does the trick. +fn force_idle( + client: &mut Connection, + server: &mut Connection, + rtt: Duration, + mut now: Instant, +) -> Instant { + // The client has sent NEW_CONNECTION_ID, so ensure that the server generates + // an acknowledgment by sending some reordered packets. + qtrace!("force_idle: send reordered client packets"); + let c1 = send_something(client, now); + let c2 = send_something(client, now); + now += rtt / 2; + server.process_input(c2, now); + server.process_input(c1, now); + + // Now do the same for the server. (The ACK is in the first one.) + qtrace!("force_idle: send reordered server packets"); + let s1 = send_something(server, now); + let s2 = send_something(server, now); + now += rtt / 2; + // Delivering s2 first at the client causes it to want to ACK. + client.process_input(s2, now); + // Delivering s1 should not have the client change its mind about the ACK. + let ack = client.process(Some(s1), now).dgram(); + assert!(ack.is_some()); + let idle_timeout = min( + client.conn_params.get_idle_timeout(), + server.conn_params.get_idle_timeout(), + ); + assert_eq!(client.process_output(now), Output::Callback(idle_timeout)); + now += rtt / 2; + assert_eq!(server.process(ack, now), Output::Callback(idle_timeout)); + now +} + +/// Connect with an RTT and then force both peers to be idle. +fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Duration) -> Instant { + let now = connect_with_rtt(client, server, now(), rtt); + let now = force_idle(client, server, rtt, now); + // Drain events from both as well. + let _ = client.events().count(); + let _ = server.events().count(); + qtrace!("----- connected and idle with RTT {:?}", rtt); + now +} + +fn connect_force_idle(client: &mut Connection, server: &mut Connection) { + connect_rtt_idle(client, server, Duration::new(0, 0)); +} + +fn fill_stream(c: &mut Connection, stream: StreamId) { + const BLOCK_SIZE: usize = 4_096; + loop { + let bytes_sent = c.stream_send(stream, &[0x42; BLOCK_SIZE]).unwrap(); + qtrace!("fill_cwnd wrote {} bytes", bytes_sent); + if bytes_sent < BLOCK_SIZE { + break; + } + } +} + +/// This fills the congestion window from a single source. +/// As the pacer will interfere with this, this moves time forward +/// as `Output::Callback` is received. Because it is hard to tell +/// from the return value whether a timeout is an ACK delay, PTO, or +/// pacing, this looks at the congestion window to tell when to stop. +/// Returns a list of datagrams and the new time. +fn fill_cwnd(c: &mut Connection, stream: StreamId, mut now: Instant) -> (Vec<Datagram>, Instant) { + // Train wreck function to get the remaining congestion window on the primary path. + fn cwnd(c: &Connection) -> usize { + c.paths.primary().borrow().sender().cwnd_avail() + } + + qtrace!("fill_cwnd starting cwnd: {}", cwnd(c)); + fill_stream(c, stream); + + let mut total_dgrams = Vec::new(); + loop { + let pkt = c.process_output(now); + qtrace!("fill_cwnd cwnd remaining={}, output: {:?}", cwnd(c), pkt); + match pkt { + Output::Datagram(dgram) => { + total_dgrams.push(dgram); + } + Output::Callback(t) => { + if cwnd(c) < ACK_ONLY_SIZE_LIMIT { + break; + } + now += t; + } + Output::None => panic!(), + } + } + + qtrace!( + "fill_cwnd sent {} bytes", + total_dgrams.iter().map(|d| d.len()).sum::<usize>() + ); + (total_dgrams, now) +} + +/// This function is like the combination of `fill_cwnd` and `ack_bytes`. +/// However, it acknowledges everything inline and preserves an RTT of `DEFAULT_RTT`. +fn increase_cwnd( + sender: &mut Connection, + receiver: &mut Connection, + stream: StreamId, + mut now: Instant, +) -> Instant { + fill_stream(sender, stream); + loop { + let pkt = sender.process_output(now); + match pkt { + Output::Datagram(dgram) => { + receiver.process_input(dgram, now + DEFAULT_RTT / 2); + } + Output::Callback(t) => { + if t < DEFAULT_RTT { + now += t; + } else { + break; // We're on PTO now. + } + } + Output::None => panic!(), + } + } + + // Now acknowledge all those packets at once. + now += DEFAULT_RTT / 2; + let ack = receiver.process_output(now).dgram(); + now += DEFAULT_RTT / 2; + sender.process_input(ack.unwrap(), now); + now +} + +/// Receive multiple packets and generate an ack-only packet. +/// # Panics +/// The caller is responsible for ensuring that `dest` has received +/// enough data that it wants to generate an ACK. This panics if +/// no ACK frame is generated. +fn ack_bytes<D>(dest: &mut Connection, stream: StreamId, in_dgrams: D, now: Instant) -> Datagram +where + D: IntoIterator<Item = Datagram>, + D::IntoIter: ExactSizeIterator, +{ + let mut srv_buf = [0; 4_096]; + + let in_dgrams = in_dgrams.into_iter(); + qdebug!([dest], "ack_bytes {} datagrams", in_dgrams.len()); + for dgram in in_dgrams { + dest.process_input(dgram, now); + } + + loop { + let (bytes_read, _fin) = dest.stream_recv(stream, &mut srv_buf).unwrap(); + qtrace!([dest], "ack_bytes read {} bytes", bytes_read); + if bytes_read == 0 { + break; + } + } + + dest.process_output(now).dgram().unwrap() +} + +// Get the current congestion window for the connection. +fn cwnd(c: &Connection) -> usize { + c.paths.primary().borrow().sender().cwnd() +} +fn cwnd_avail(c: &Connection) -> usize { + c.paths.primary().borrow().sender().cwnd_avail() +} + +fn induce_persistent_congestion( + client: &mut Connection, + server: &mut Connection, + stream: StreamId, + mut now: Instant, +) -> Instant { + // Note: wait some arbitrary time that should be longer than pto + // timer. This is rather brittle. + qtrace!([client], "induce_persistent_congestion"); + now += AT_LEAST_PTO; + + let mut pto_counts = [0; MAX_PTO_COUNTS]; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + qtrace!([client], "first PTO"); + let (c_tx_dgrams, next_now) = fill_cwnd(client, stream, now); + now = next_now; + assert_eq!(c_tx_dgrams.len(), 2); // Two PTO packets + + pto_counts[0] = 1; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + qtrace!([client], "second PTO"); + now += AT_LEAST_PTO * 2; + let (c_tx_dgrams, next_now) = fill_cwnd(client, stream, now); + now = next_now; + assert_eq!(c_tx_dgrams.len(), 2); // Two PTO packets + + pto_counts[0] = 0; + pto_counts[1] = 1; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + qtrace!([client], "third PTO"); + now += AT_LEAST_PTO * 4; + let (c_tx_dgrams, next_now) = fill_cwnd(client, stream, now); + now = next_now; + assert_eq!(c_tx_dgrams.len(), 2); // Two PTO packets + + pto_counts[1] = 0; + pto_counts[2] = 1; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + // An ACK for the third PTO causes persistent congestion. + let s_ack = ack_bytes(server, stream, c_tx_dgrams, now); + client.process_input(s_ack, now); + assert_eq!(cwnd(client), CWND_MIN); + now +} + +/// This magic number is the size of the client's CWND after the handshake completes. +/// This is the same as the initial congestion window, because during the handshake +/// the cc is app limited and cwnd is not increased. +/// +/// As we change how we build packets, or even as NSS changes, +/// this number might be different. The tests that depend on this +/// value could fail as a result of variations, so it's OK to just +/// change this value, but it is good to first understand where the +/// change came from. +const POST_HANDSHAKE_CWND: usize = PATH_MTU_V6 * CWND_INITIAL_PKTS; + +/// Determine the number of packets required to fill the CWND. +const fn cwnd_packets(data: usize) -> usize { + // Add one if the last chunk is >= ACK_ONLY_SIZE_LIMIT. + (data + PATH_MTU_V6 - ACK_ONLY_SIZE_LIMIT) / PATH_MTU_V6 +} + +/// Determine the size of the last packet. +/// The minimal size of a packet is `ACK_ONLY_SIZE_LIMIT`. +fn last_packet(cwnd: usize) -> usize { + if (cwnd % PATH_MTU_V6) > ACK_ONLY_SIZE_LIMIT { + cwnd % PATH_MTU_V6 + } else { + PATH_MTU_V6 + } +} + +/// Assert that the set of packets fill the CWND. +fn assert_full_cwnd(packets: &[Datagram], cwnd: usize) { + assert_eq!(packets.len(), cwnd_packets(cwnd)); + let (last, rest) = packets.split_last().unwrap(); + assert!(rest.iter().all(|d| d.len() == PATH_MTU_V6)); + assert_eq!(last.len(), last_packet(cwnd)); +} + +/// Send something on a stream from `sender` to `receiver`. +/// Return the resulting datagram. +#[must_use] +fn send_something(sender: &mut Connection, now: Instant) -> Datagram { + let stream_id = sender.stream_create(StreamType::UniDi).unwrap(); + assert!(sender.stream_send(stream_id, DEFAULT_STREAM_DATA).is_ok()); + assert!(sender.stream_close_send(stream_id).is_ok()); + qdebug!([sender], "send_something on {}", stream_id); + let dgram = sender.process(None, now).dgram(); + dgram.expect("should have something to send") +} + +/// Send something on a stream from `sender` to `receiver`. +/// Return any ACK that might result. +fn send_and_receive( + sender: &mut Connection, + receiver: &mut Connection, + now: Instant, +) -> Option<Datagram> { + let dgram = send_something(sender, now); + receiver.process(Some(dgram), now).dgram() +} + +fn get_tokens(client: &mut Connection) -> Vec<ResumptionToken> { + client + .events() + .filter_map(|e| { + if let ConnectionEvent::ResumptionToken(token) = e { + Some(token) + } else { + None + } + }) + .collect() +} + +fn assert_default_stats(stats: &Stats) { + assert_eq!(stats.packets_rx, 0); + assert_eq!(stats.packets_tx, 0); + let dflt_frames = FrameStats::default(); + assert_eq!(stats.frame_rx, dflt_frames); + assert_eq!(stats.frame_tx, dflt_frames); +} + +#[test] +fn create_client() { + let client = default_client(); + assert_eq!(client.role(), Role::Client); + assert!(matches!(client.state(), State::Init)); + let stats = client.stats(); + assert_default_stats(&stats); + assert_eq!(stats.rtt, crate::rtt::INITIAL_RTT); + assert_eq!(stats.rttvar, crate::rtt::INITIAL_RTT / 2); +} + +#[test] +fn create_server() { + let server = default_server(); + assert_eq!(server.role(), Role::Server); + assert!(matches!(server.state(), State::Init)); + let stats = server.stats(); + assert_default_stats(&stats); + // Server won't have a default path, so no RTT. + assert_eq!(stats.rtt, Duration::from_secs(0)); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/priority.rs b/third_party/rust/neqo-transport/src/connection/tests/priority.rs new file mode 100644 index 0000000000..d3bb2ed6da --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/priority.rs @@ -0,0 +1,401 @@ +// 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::super::{Connection, Error, Output}; +use super::{connect, default_client, default_server, fill_cwnd, maybe_authenticate}; +use crate::addr_valid::{AddressValidation, ValidateAddress}; +use crate::send_stream::{RetransmissionPriority, TransmissionPriority}; +use crate::{ConnectionEvent, StreamId, StreamType}; + +use neqo_common::event::Provider; +use std::cell::RefCell; +use std::mem; +use std::rc::Rc; +use test_fixture::{self, now}; + +const BLOCK_SIZE: usize = 4_096; + +fn fill_stream(c: &mut Connection, id: StreamId) { + loop { + if c.stream_send(id, &[0x42; BLOCK_SIZE]).unwrap() < BLOCK_SIZE { + return; + } + } +} + +/// A receive stream cannot be prioritized (yet). +#[test] +fn receive_stream() { + const MESSAGE: &[u8] = b"hello"; + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(MESSAGE.len(), client.stream_send(id, MESSAGE).unwrap()); + let dgram = client.process_output(now()).dgram(); + + server.process_input(dgram.unwrap(), now()); + assert_eq!( + server + .stream_priority( + id, + TransmissionPriority::default(), + RetransmissionPriority::default() + ) + .unwrap_err(), + Error::InvalidStreamId, + "Priority doesn't apply to inbound unidirectional streams" + ); + + // But the stream does exist and can be read. + let mut buf = [0; 10]; + let (len, end) = server.stream_recv(id, &mut buf).unwrap(); + assert_eq!(MESSAGE, &buf[..len]); + assert!(!end); +} + +/// Higher priority streams get sent ahead of lower ones, even when +/// the higher priority stream is written to later. +#[test] +fn relative() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // id_normal is created first, but it is lower priority. + let id_normal = client.stream_create(StreamType::UniDi).unwrap(); + fill_stream(&mut client, id_normal); + let high = client.stream_create(StreamType::UniDi).unwrap(); + fill_stream(&mut client, high); + client + .stream_priority( + high, + TransmissionPriority::High, + RetransmissionPriority::default(), + ) + .unwrap(); + + let dgram = client.process_output(now()).dgram(); + server.process_input(dgram.unwrap(), now()); + + // The "id_normal" stream will get a `NewStream` event, but no data. + for e in server.events() { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + assert_ne!(stream_id, id_normal); + } + } +} + +/// Check that changing priority has effect on the next packet that is sent. +#[test] +fn reprioritize() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // id_normal is created first, but it is lower priority. + let id_normal = client.stream_create(StreamType::UniDi).unwrap(); + fill_stream(&mut client, id_normal); + let id_high = client.stream_create(StreamType::UniDi).unwrap(); + fill_stream(&mut client, id_high); + client + .stream_priority( + id_high, + TransmissionPriority::High, + RetransmissionPriority::default(), + ) + .unwrap(); + + let dgram = client.process_output(now()).dgram(); + server.process_input(dgram.unwrap(), now()); + + // The "id_normal" stream will get a `NewStream` event, but no data. + for e in server.events() { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + assert_ne!(stream_id, id_normal); + } + } + + // When the high priority stream drops in priority, the streams are equal + // priority and so their stream ID determines what is sent. + client + .stream_priority( + id_high, + TransmissionPriority::Normal, + RetransmissionPriority::default(), + ) + .unwrap(); + let dgram = client.process_output(now()).dgram(); + server.process_input(dgram.unwrap(), now()); + + for e in server.events() { + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + assert_ne!(stream_id, id_high); + } + } +} + +/// Retransmission can be prioritized differently (usually higher). +#[test] +fn repairing_loss() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let mut now = now(); + + // Send a few packets at low priority, lose one. + let id_low = client.stream_create(StreamType::UniDi).unwrap(); + fill_stream(&mut client, id_low); + client + .stream_priority( + id_low, + TransmissionPriority::Low, + RetransmissionPriority::Higher, + ) + .unwrap(); + + let _lost = client.process_output(now).dgram(); + for _ in 0..5 { + match client.process_output(now) { + Output::Datagram(d) => server.process_input(d, now), + Output::Callback(delay) => now += delay, + Output::None => unreachable!(), + } + } + + // Generate an ACK. The first packet is now considered lost. + let ack = server.process_output(now).dgram(); + let _ = server.events().count(); // Drain events. + + let id_normal = client.stream_create(StreamType::UniDi).unwrap(); + fill_stream(&mut client, id_normal); + + let dgram = client.process(ack, now).dgram(); + assert_eq!(client.stats().lost, 1); // Client should have noticed the loss. + server.process_input(dgram.unwrap(), now); + + // Only the low priority stream has data as the retransmission of the data from + // the lost packet is now more important than new data from the high priority stream. + for e in server.events() { + println!("Event: {:?}", e); + if let ConnectionEvent::RecvStreamReadable { stream_id } = e { + assert_eq!(stream_id, id_low); + } + } + + // However, only the retransmission is prioritized. + // Though this might contain some retransmitted data, as other frames might push + // the retransmitted data into a second packet, it will also contain data from the + // normal priority stream. + let dgram = client.process_output(now).dgram(); + server.process_input(dgram.unwrap(), now); + assert!(server.events().any( + |e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id } if stream_id == id_normal), + )); +} + +#[test] +fn critical() { + let mut client = default_client(); + let mut server = default_server(); + let now = now(); + + // Rather than connect, send stream data in 0.5-RTT. + // That allows this to test that critical streams pre-empt most frame types. + let dgram = client.process_output(now).dgram(); + let dgram = server.process(dgram, now).dgram(); + client.process_input(dgram.unwrap(), now); + maybe_authenticate(&mut client); + + let id = server.stream_create(StreamType::UniDi).unwrap(); + server + .stream_priority( + id, + TransmissionPriority::Critical, + RetransmissionPriority::default(), + ) + .unwrap(); + + // Can't use fill_cwnd here because the server is blocked on the amplification + // limit, so it can't fill the congestion window. + while server.stream_create(StreamType::UniDi).is_ok() {} + + fill_stream(&mut server, id); + let stats_before = server.stats().frame_tx; + let dgram = server.process_output(now).dgram(); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto); + assert_eq!(stats_after.streams_blocked, 0); + assert_eq!(stats_after.new_connection_id, 0); + assert_eq!(stats_after.new_token, 0); + assert_eq!(stats_after.handshake_done, 0); + + // Complete the handshake. + let dgram = client.process(dgram, now).dgram(); + server.process_input(dgram.unwrap(), now); + + // Critical beats everything but HANDSHAKE_DONE. + let stats_before = server.stats().frame_tx; + mem::drop(fill_cwnd(&mut server, id, now)); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto); + assert_eq!(stats_after.streams_blocked, 0); + assert_eq!(stats_after.new_connection_id, 0); + assert_eq!(stats_after.new_token, 0); + assert_eq!(stats_after.handshake_done, 1); +} + +#[test] +fn important() { + let mut client = default_client(); + let mut server = default_server(); + let now = now(); + + // Rather than connect, send stream data in 0.5-RTT. + // That allows this to test that important streams pre-empt most frame types. + let dgram = client.process_output(now).dgram(); + let dgram = server.process(dgram, now).dgram(); + client.process_input(dgram.unwrap(), now); + maybe_authenticate(&mut client); + + let id = server.stream_create(StreamType::UniDi).unwrap(); + server + .stream_priority( + id, + TransmissionPriority::Important, + RetransmissionPriority::default(), + ) + .unwrap(); + fill_stream(&mut server, id); + + // Important beats everything but flow control. + // Make enough streams to get a STREAMS_BLOCKED frame out. + while server.stream_create(StreamType::UniDi).is_ok() {} + + let stats_before = server.stats().frame_tx; + let dgram = server.process_output(now).dgram(); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto); + assert_eq!(stats_after.streams_blocked, 1); + assert_eq!(stats_after.new_connection_id, 0); + assert_eq!(stats_after.new_token, 0); + assert_eq!(stats_after.handshake_done, 0); + assert_eq!(stats_after.stream, stats_before.stream + 1); + + // Complete the handshake. + let dgram = client.process(dgram, now).dgram(); + server.process_input(dgram.unwrap(), now); + + // Important beats everything but flow control. + let stats_before = server.stats().frame_tx; + mem::drop(fill_cwnd(&mut server, id, now)); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto); + assert_eq!(stats_after.streams_blocked, 1); + assert_eq!(stats_after.new_connection_id, 0); + assert_eq!(stats_after.new_token, 0); + assert_eq!(stats_after.handshake_done, 1); + assert!(stats_after.stream > stats_before.stream); +} + +#[test] +fn high_normal() { + let mut client = default_client(); + let mut server = default_server(); + let now = now(); + + // Rather than connect, send stream data in 0.5-RTT. + // That allows this to test that important streams pre-empt most frame types. + let dgram = client.process_output(now).dgram(); + let dgram = server.process(dgram, now).dgram(); + client.process_input(dgram.unwrap(), now); + maybe_authenticate(&mut client); + + let id = server.stream_create(StreamType::UniDi).unwrap(); + server + .stream_priority( + id, + TransmissionPriority::High, + RetransmissionPriority::default(), + ) + .unwrap(); + fill_stream(&mut server, id); + + // Important beats everything but flow control. + // Make enough streams to get a STREAMS_BLOCKED frame out. + while server.stream_create(StreamType::UniDi).is_ok() {} + + let stats_before = server.stats().frame_tx; + let dgram = server.process_output(now).dgram(); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto); + assert_eq!(stats_after.streams_blocked, 1); + assert_eq!(stats_after.new_connection_id, 0); + assert_eq!(stats_after.new_token, 0); + assert_eq!(stats_after.handshake_done, 0); + assert_eq!(stats_after.stream, stats_before.stream + 1); + + // Complete the handshake. + let dgram = client.process(dgram, now).dgram(); + server.process_input(dgram.unwrap(), now); + + // High or Normal doesn't beat NEW_CONNECTION_ID, + // but they beat CRYPTO/NEW_TOKEN. + let stats_before = server.stats().frame_tx; + server.send_ticket(now, &[]).unwrap(); + mem::drop(fill_cwnd(&mut server, id, now)); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto); + assert_eq!(stats_after.streams_blocked, 1); + assert_ne!(stats_after.new_connection_id, 0); // Note: > 0 + assert_eq!(stats_after.new_token, 0); + assert_eq!(stats_after.handshake_done, 1); + assert!(stats_after.stream > stats_before.stream); +} + +#[test] +fn low() { + let mut client = default_client(); + let mut server = default_server(); + let now = now(); + // Use address validation; note that we need to hold a strong reference + // as the server will only hold a weak reference. + let validation = Rc::new(RefCell::new( + AddressValidation::new(now, ValidateAddress::Never).unwrap(), + )); + server.set_validation(Rc::clone(&validation)); + connect(&mut client, &mut server); + + let id = server.stream_create(StreamType::UniDi).unwrap(); + server + .stream_priority( + id, + TransmissionPriority::Low, + RetransmissionPriority::default(), + ) + .unwrap(); + fill_stream(&mut server, id); + + // Send a session ticket and make it big enough to require a whole packet. + // The resulting CRYPTO frame beats out the stream data. + let stats_before = server.stats().frame_tx; + server.send_ticket(now, &[0; 2048]).unwrap(); + mem::drop(server.process_output(now)); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto + 1); + assert_eq!(stats_after.stream, stats_before.stream); + + // The above can't test if NEW_TOKEN wins because once that fits in a packet, + // it is very hard to ensure that the STREAM frame won't also fit. + // However, we can ensure that the next packet doesn't consist of just STREAM. + let stats_before = server.stats().frame_tx; + mem::drop(server.process_output(now)); + let stats_after = server.stats().frame_tx; + assert_eq!(stats_after.crypto, stats_before.crypto + 1); + assert_eq!(stats_after.new_token, 1); + assert_eq!(stats_after.stream, stats_before.stream + 1); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/recovery.rs b/third_party/rust/neqo-transport/src/connection/tests/recovery.rs new file mode 100644 index 0000000000..0e3ee412f5 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/recovery.rs @@ -0,0 +1,810 @@ +// 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::super::{Connection, ConnectionParameters, Output, State}; +use super::{ + assert_full_cwnd, connect, connect_force_idle, connect_rtt_idle, connect_with_rtt, cwnd, + default_client, default_server, fill_cwnd, maybe_authenticate, new_client, send_and_receive, + send_something, AT_LEAST_PTO, DEFAULT_RTT, DEFAULT_STREAM_DATA, POST_HANDSHAKE_CWND, +}; +use crate::cc::CWND_MIN; +use crate::path::PATH_MTU_V6; +use crate::recovery::{ + FAST_PTO_SCALE, MAX_OUTSTANDING_UNACK, MIN_OUTSTANDING_UNACK, PTO_PACKET_COUNT, +}; +use crate::rtt::GRANULARITY; +use crate::stats::MAX_PTO_COUNTS; +use crate::tparams::TransportParameter; +use crate::tracking::DEFAULT_ACK_DELAY; +use crate::StreamType; + +use neqo_common::qdebug; +use neqo_crypto::AuthenticationStatus; +use std::mem; +use std::time::{Duration, Instant}; +use test_fixture::{self, now, split_datagram}; + +#[test] +fn pto_works_basic() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let mut now = now(); + + let res = client.process(None, now); + let idle_timeout = ConnectionParameters::default().get_idle_timeout(); + assert_eq!(res, Output::Callback(idle_timeout)); + + // Send data on two streams + let stream1 = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.stream_send(stream1, b"hello").unwrap(), 5); + assert_eq!(client.stream_send(stream1, b" world!").unwrap(), 7); + + let stream2 = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.stream_send(stream2, b"there!").unwrap(), 6); + + // Send a packet after some time. + now += Duration::from_secs(10); + let out = client.process(None, now); + assert!(out.dgram().is_some()); + + // Nothing to do, should return callback + let out = client.process(None, now); + assert!(matches!(out, Output::Callback(_))); + + // One second later, it should want to send PTO packet + now += AT_LEAST_PTO; + let out = client.process(None, now); + + let stream_before = server.stats().frame_rx.stream; + server.process_input(out.dgram().unwrap(), now); + assert_eq!(server.stats().frame_rx.stream, stream_before + 2); +} + +#[test] +fn pto_works_full_cwnd() { + let mut client = default_client(); + let mut server = default_server(); + let now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + // Send lots of data. + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + let (dgrams, now) = fill_cwnd(&mut client, stream_id, now); + assert_full_cwnd(&dgrams, POST_HANDSHAKE_CWND); + + // Fill the CWND after waiting for a PTO. + let (dgrams, now) = fill_cwnd(&mut client, stream_id, now + AT_LEAST_PTO); + // Two packets in the PTO. + // The first should be full sized; the second might be small. + assert_eq!(dgrams.len(), 2); + assert_eq!(dgrams[0].len(), PATH_MTU_V6); + + // Both datagrams contain one or more STREAM frames. + for d in dgrams { + let stream_before = server.stats().frame_rx.stream; + server.process_input(d, now); + assert!(server.stats().frame_rx.stream > stream_before); + } +} + +#[test] +fn pto_works_ping() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + let mut now = now(); + + let res = client.process(None, now); + assert_eq!( + res, + Output::Callback(ConnectionParameters::default().get_idle_timeout()) + ); + + now += Duration::from_secs(10); + + // Send a few packets from the client. + let pkt0 = send_something(&mut client, now); + let pkt1 = send_something(&mut client, now); + let pkt2 = send_something(&mut client, now); + let pkt3 = send_something(&mut client, now); + + // Nothing to do, should return callback + let cb = client.process(None, now).callback(); + // The PTO timer is calculated with: + // RTT + max(rttvar * 4, GRANULARITY) + max_ack_delay + // With zero RTT and rttvar, max_ack_delay is minimum too (GRANULARITY) + assert_eq!(cb, GRANULARITY * 2); + + // Process these by server, skipping pkt0 + let srv0 = server.process(Some(pkt1), now).dgram(); + assert!(srv0.is_some()); // ooo, ack client pkt1 + + now += Duration::from_millis(20); + + // process pkt2 (no ack yet) + let srv1 = server.process(Some(pkt2), now).dgram(); + assert!(srv1.is_none()); + + // process pkt3 (acked) + let srv2 = server.process(Some(pkt3), now).dgram(); + // ack client pkt 2 & 3 + assert!(srv2.is_some()); + + now += Duration::from_millis(20); + // client processes ack + let pkt4 = client.process(srv2, now).dgram(); + // client resends data from pkt0 + assert!(pkt4.is_some()); + + // server sees ooo pkt0 and generates ack + let srv3 = server.process(Some(pkt0), now).dgram(); + assert!(srv3.is_some()); + + // Accept the acknowledgment. + let pkt5 = client.process(srv3, now).dgram(); + assert!(pkt5.is_none()); + + now += Duration::from_millis(70); + // PTO expires. No unacked data. Only send PING. + let client_pings = client.stats().frame_tx.ping; + let pkt6 = client.process(None, now).dgram(); + assert_eq!(client.stats().frame_tx.ping, client_pings + 1); + + let server_pings = server.stats().frame_rx.ping; + server.process_input(pkt6.unwrap(), now); + assert_eq!(server.stats().frame_rx.ping, server_pings + 1); +} + +#[test] +fn pto_initial() { + const INITIAL_PTO: Duration = Duration::from_millis(300); + let mut now = now(); + + qdebug!("---- client: generate CH"); + let mut client = default_client(); + let pkt1 = client.process(None, now).dgram(); + assert!(pkt1.is_some()); + assert_eq!(pkt1.clone().unwrap().len(), PATH_MTU_V6); + + let delay = client.process(None, now).callback(); + assert_eq!(delay, INITIAL_PTO); + + // Resend initial after PTO. + now += delay; + let pkt2 = client.process(None, now).dgram(); + assert!(pkt2.is_some()); + assert_eq!(pkt2.unwrap().len(), PATH_MTU_V6); + + let pkt3 = client.process(None, now).dgram(); + assert!(pkt3.is_some()); + assert_eq!(pkt3.unwrap().len(), PATH_MTU_V6); + + let delay = client.process(None, now).callback(); + // PTO has doubled. + assert_eq!(delay, INITIAL_PTO * 2); + + // Server process the first initial pkt. + let mut server = default_server(); + let out = server.process(pkt1, now).dgram(); + assert!(out.is_some()); + + // Client receives ack for the first initial packet as well a Handshake packet. + // After the handshake packet the initial keys and the crypto stream for the initial + // packet number space will be discarded. + // Here only an ack for the Handshake packet will be sent. + let out = client.process(out, now).dgram(); + assert!(out.is_some()); + + // We do not have PTO for the resent initial packet any more, but + // the Handshake PTO timer should be armed. As the RTT is apparently + // the same as the initial PTO value, and there is only one sample, + // the PTO will be 3x the INITIAL PTO. + let delay = client.process(None, now).callback(); + assert_eq!(delay, INITIAL_PTO * 3); +} + +/// A complete handshake that involves a PTO in the Handshake space. +#[test] +fn pto_handshake_complete() { + const HALF_RTT: Duration = Duration::from_millis(10); + + let mut now = now(); + // start handshake + let mut client = default_client(); + let mut server = default_server(); + + let pkt = client.process(None, now).dgram(); + let cb = client.process(None, now).callback(); + assert_eq!(cb, Duration::from_millis(300)); + + now += HALF_RTT; + let pkt = server.process(pkt, now).dgram(); + + now += HALF_RTT; + let pkt = client.process(pkt, now).dgram(); + + let cb = client.process(None, now).callback(); + // The client now has a single RTT estimate (20ms), so + // the handshake PTO is set based on that. + assert_eq!(cb, HALF_RTT * 6); + + now += HALF_RTT; + let pkt = server.process(pkt, now).dgram(); + assert!(pkt.is_none()); + + now += HALF_RTT; + client.authenticated(AuthenticationStatus::Ok, now); + + qdebug!("---- client: SH..FIN -> FIN"); + let pkt1 = client.process(None, now).dgram(); + assert!(pkt1.is_some()); + assert_eq!(*client.state(), State::Connected); + + let cb = client.process(None, now).callback(); + assert_eq!(cb, HALF_RTT * 6); + + let mut pto_counts = [0; MAX_PTO_COUNTS]; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + // Wait for PTO to expire and resend a handshake packet. + // Wait long enough that the 1-RTT PTO also fires. + qdebug!("---- client: PTO"); + now += HALF_RTT * 6; + let pkt2 = client.process(None, now).dgram(); + + pto_counts[0] = 1; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + // Get a second PTO packet. + // Add some application data to this datagram, then split the 1-RTT off. + // We'll use that packet to force the server to acknowledge 1-RTT. + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_close_send(stream_id).unwrap(); + let pkt3 = client.process(None, now).dgram(); + let (pkt3_hs, pkt3_1rtt) = split_datagram(&pkt3.unwrap()); + + // PTO has been doubled. + let cb = client.process(None, now).callback(); + assert_eq!(cb, HALF_RTT * 12); + + // We still have only a single PTO + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + qdebug!("---- server: receive FIN and send ACK"); + now += HALF_RTT; + // Now let the server have pkt1 and expect an immediate Handshake ACK. + // The output will be a Handshake packet with ACK and 1-RTT packet with + // HANDSHAKE_DONE and (because of pkt3_1rtt) an ACK. + // This should remove the 1-RTT PTO from messing this test up. + let server_acks = server.stats().frame_tx.ack; + let server_done = server.stats().frame_tx.handshake_done; + server.process_input(pkt3_1rtt.unwrap(), now); + let ack = server.process(pkt1, now).dgram(); + assert!(ack.is_some()); + assert_eq!(server.stats().frame_tx.ack, server_acks + 2); + assert_eq!(server.stats().frame_tx.handshake_done, server_done + 1); + + // Check that the other packets (pkt2, pkt3) are Handshake packets. + // The server discarded the Handshake keys already, therefore they are dropped. + // Note that these don't include 1-RTT packets, because 1-RTT isn't send on PTO. + let dropped_before1 = server.stats().dropped_rx; + let server_frames = server.stats().frame_rx.all; + server.process_input(pkt2.unwrap(), now); + assert_eq!(1, server.stats().dropped_rx - dropped_before1); + assert_eq!(server.stats().frame_rx.all, server_frames); + + let dropped_before2 = server.stats().dropped_rx; + server.process_input(pkt3_hs, now); + assert_eq!(1, server.stats().dropped_rx - dropped_before2); + assert_eq!(server.stats().frame_rx.all, server_frames); + + now += HALF_RTT; + + // Let the client receive the ACK. + // It should now be wait to acknowledge the HANDSHAKE_DONE. + let cb = client.process(ack, now).callback(); + // The default ack delay is the RTT divided by the default ACK ratio of 4. + let expected_ack_delay = HALF_RTT * 2 / 4; + assert_eq!(cb, expected_ack_delay); + + // Let the ACK delay timer expire. + now += cb; + let out = client.process(None, now).dgram(); + assert!(out.is_some()); + let cb = client.process(None, now).callback(); + // The handshake keys are discarded, but now we're back to the idle timeout. + // We don't send another PING because the handshake space is done and there + // is nothing to probe for. + + let idle_timeout = ConnectionParameters::default().get_idle_timeout(); + assert_eq!(cb, idle_timeout - expected_ack_delay); +} + +/// Test that PTO in the Handshake space contains the right frames. +#[test] +fn pto_handshake_frames() { + let mut now = now(); + qdebug!("---- client: generate CH"); + let mut client = default_client(); + let pkt = client.process(None, now); + + now += Duration::from_millis(10); + qdebug!("---- server: CH -> SH, EE, CERT, CV, FIN"); + let mut server = default_server(); + let pkt = server.process(pkt.dgram(), now); + + now += Duration::from_millis(10); + qdebug!("---- client: cert verification"); + let pkt = client.process(pkt.dgram(), now); + + now += Duration::from_millis(10); + mem::drop(server.process(pkt.dgram(), now)); + + now += Duration::from_millis(10); + client.authenticated(AuthenticationStatus::Ok, now); + + let stream = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(stream, 2); + assert_eq!(client.stream_send(stream, b"zero").unwrap(), 4); + qdebug!("---- client: SH..FIN -> FIN and 1RTT packet"); + let pkt1 = client.process(None, now).dgram(); + assert!(pkt1.is_some()); + + // Get PTO timer. + let out = client.process(None, now); + assert_eq!(out, Output::Callback(Duration::from_millis(60))); + + // Wait for PTO to expire and resend a handshake packet. + now += Duration::from_millis(60); + let pkt2 = client.process(None, now).dgram(); + assert!(pkt2.is_some()); + + now += Duration::from_millis(10); + let crypto_before = server.stats().frame_rx.crypto; + server.process_input(pkt2.unwrap(), now); + assert_eq!(server.stats().frame_rx.crypto, crypto_before + 1); +} + +/// In the case that the Handshake takes too many packets, the server might +/// be stalled on the anti-amplification limit. If a Handshake ACK from the +/// client is lost, the client has to keep the PTO timer armed or the server +/// might be unable to send anything, causing a deadlock. +#[test] +fn handshake_ack_pto() { + const RTT: Duration = Duration::from_millis(10); + let mut now = now(); + let mut client = default_client(); + let mut server = default_server(); + // This is a greasing transport parameter, and large enough that the + // server needs to send two Handshake packets. + let big = TransportParameter::Bytes(vec![0; PATH_MTU_V6]); + server.set_local_tparam(0xce16, big).unwrap(); + + let c1 = client.process(None, now).dgram(); + + now += RTT / 2; + let s1 = server.process(c1, now).dgram(); + assert!(s1.is_some()); + let s2 = server.process(None, now).dgram(); + assert!(s1.is_some()); + + // Now let the client have the Initial, but drop the first coalesced Handshake packet. + now += RTT / 2; + let (initial, _) = split_datagram(&s1.unwrap()); + client.process_input(initial, now); + let c2 = client.process(s2, now).dgram(); + assert!(c2.is_some()); // This is an ACK. Drop it. + let delay = client.process(None, now).callback(); + assert_eq!(delay, RTT * 3); + + let mut pto_counts = [0; MAX_PTO_COUNTS]; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + // Wait for the PTO and ensure that the client generates a packet. + now += delay; + let c3 = client.process(None, now).dgram(); + assert!(c3.is_some()); + + now += RTT / 2; + let ping_before = server.stats().frame_rx.ping; + server.process_input(c3.unwrap(), now); + assert_eq!(server.stats().frame_rx.ping, ping_before + 1); + + pto_counts[0] = 1; + assert_eq!(client.stats.borrow().pto_counts, pto_counts); + + // Now complete the handshake as cheaply as possible. + let dgram = server.process(None, now).dgram(); + client.process_input(dgram.unwrap(), now); + maybe_authenticate(&mut client); + let dgram = client.process(None, now).dgram(); + assert_eq!(*client.state(), State::Connected); + let dgram = server.process(dgram, now).dgram(); + assert_eq!(*server.state(), State::Confirmed); + client.process_input(dgram.unwrap(), now); + assert_eq!(*client.state(), State::Confirmed); + + assert_eq!(client.stats.borrow().pto_counts, pto_counts); +} + +#[test] +fn loss_recovery_crash() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let now = now(); + + // The server sends something, but we will drop this. + mem::drop(send_something(&mut server, now)); + + // Then send something again, but let it through. + let ack = send_and_receive(&mut server, &mut client, now); + assert!(ack.is_some()); + + // Have the server process the ACK. + let cb = server.process(ack, now).callback(); + assert!(cb > Duration::from_secs(0)); + + // Now we leap into the future. The server should regard the first + // packet as lost based on time alone. + let dgram = server.process(None, now + AT_LEAST_PTO).dgram(); + assert!(dgram.is_some()); + + // This crashes. + mem::drop(send_something(&mut server, now + AT_LEAST_PTO)); +} + +// If we receive packets after the PTO timer has fired, we won't clear +// the PTO state, but we might need to acknowledge those packets. +// This shouldn't happen, but we found that some implementations do this. +#[test] +fn ack_after_pto() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + let mut now = now(); + + // The client sends and is forced into a PTO. + mem::drop(send_something(&mut client, now)); + + // Jump forward to the PTO and drain the PTO packets. + now += AT_LEAST_PTO; + for _ in 0..PTO_PACKET_COUNT { + let dgram = client.process(None, now).dgram(); + assert!(dgram.is_some()); + } + assert!(client.process(None, now).dgram().is_none()); + + // The server now needs to send something that will cause the + // client to want to acknowledge it. A little out of order + // delivery is just the thing. + // Note: The server can't ACK anything here, but none of what + // the client has sent so far has been transferred. + mem::drop(send_something(&mut server, now)); + let dgram = send_something(&mut server, now); + + // The client is now after a PTO, but if it receives something + // that demands acknowledgment, it will send just the ACK. + let ack = client.process(Some(dgram), now).dgram(); + assert!(ack.is_some()); + + // Make sure that the packet only contained an ACK frame. + let all_frames_before = server.stats().frame_rx.all; + let ack_before = server.stats().frame_rx.ack; + server.process_input(ack.unwrap(), now); + assert_eq!(server.stats().frame_rx.all, all_frames_before + 1); + assert_eq!(server.stats().frame_rx.ack, ack_before + 1); +} + +/// When we declare a packet as lost, we keep it around for a while for another loss period. +/// Those packets should not affect how we report the loss recovery timer. +/// As the loss recovery timer based on RTT we use that to drive the state. +#[test] +fn lost_but_kept_and_lr_timer() { + const RTT: Duration = Duration::from_secs(1); + let mut client = default_client(); + let mut server = default_server(); + let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT); + + // Two packets (p1, p2) are sent at around t=0. The first is lost. + let _p1 = send_something(&mut client, now); + let p2 = send_something(&mut client, now); + + // At t=RTT/2 the server receives the packet and ACKs it. + now += RTT / 2; + let ack = server.process(Some(p2), now).dgram(); + assert!(ack.is_some()); + // The client also sends another two packets (p3, p4), again losing the first. + let _p3 = send_something(&mut client, now); + let p4 = send_something(&mut client, now); + + // At t=RTT the client receives the ACK and goes into timed loss recovery. + // The client doesn't call p1 lost at this stage, but it will soon. + now += RTT / 2; + let res = client.process(ack, now); + // The client should be on a loss recovery timer as p1 is missing. + let lr_timer = res.callback(); + // Loss recovery timer should be RTT/8, but only check for 0 or >=RTT/2. + assert_ne!(lr_timer, Duration::from_secs(0)); + assert!(lr_timer < (RTT / 2)); + // The server also receives and acknowledges p4, again sending an ACK. + let ack = server.process(Some(p4), now).dgram(); + assert!(ack.is_some()); + + // At t=RTT*3/2 the client should declare p1 to be lost. + now += RTT / 2; + // So the client will send the data from p1 again. + let res = client.process(None, now); + assert!(res.dgram().is_some()); + // When the client processes the ACK, it should engage the + // loss recovery timer for p3, not p1 (even though it still tracks p1). + let res = client.process(ack, now); + let lr_timer2 = res.callback(); + assert_eq!(lr_timer, lr_timer2); +} + +/// We should not be setting the loss recovery timer based on packets +/// that are sent prior to the largest acknowledged. +/// Testing this requires that we construct a case where one packet +/// number space causes the loss recovery timer to be engaged. At the same time, +/// there is a packet in another space that hasn't been acknowledged AND +/// that packet number space has not received acknowledgments for later packets. +#[test] +fn loss_time_past_largest_acked() { + const RTT: Duration = Duration::from_secs(10); + const INCR: Duration = Duration::from_millis(1); + let mut client = default_client(); + let mut server = default_server(); + + let mut now = now(); + + // Start the handshake. + let c_in = client.process(None, now).dgram(); + now += RTT / 2; + let s_hs1 = server.process(c_in, now).dgram(); + + // Get some spare server handshake packets for the client to ACK. + // This involves a time machine, so be a little cautious. + // This test uses an RTT of 10s, but our server starts + // with a much lower RTT estimate, so the PTO at this point should + // be much smaller than an RTT and so the server shouldn't see + // time go backwards. + let s_pto = server.process(None, now).callback(); + assert_ne!(s_pto, Duration::from_secs(0)); + assert!(s_pto < RTT); + let s_hs2 = server.process(None, now + s_pto).dgram(); + assert!(s_hs2.is_some()); + let s_hs3 = server.process(None, now + s_pto).dgram(); + assert!(s_hs3.is_some()); + + // Get some Handshake packets from the client. + // We need one to be left unacknowledged before one that is acknowledged. + // So that the client engages the loss recovery timer. + // This is complicated by the fact that it is hard to cause the client + // to generate an ack-eliciting packet. For that, we use the Finished message. + // Reordering delivery ensures that the later packet is also acknowledged. + now += RTT / 2; + let c_hs1 = client.process(s_hs1, now).dgram(); + assert!(c_hs1.is_some()); // This comes first, so it's useless. + maybe_authenticate(&mut client); + let c_hs2 = client.process(None, now).dgram(); + assert!(c_hs2.is_some()); // This one will elicit an ACK. + + // The we need the outstanding packet to be sent after the + // application data packet, so space these out a tiny bit. + let _p1 = send_something(&mut client, now + INCR); + let c_hs3 = client.process(s_hs2, now + (INCR * 2)).dgram(); + assert!(c_hs3.is_some()); // This will be left outstanding. + let c_hs4 = client.process(s_hs3, now + (INCR * 3)).dgram(); + assert!(c_hs4.is_some()); // This will be acknowledged. + + // Process c_hs2 and c_hs4, but skip c_hs3. + // Then get an ACK for the client. + now += RTT / 2; + // Deliver c_hs4 first, but don't generate a packet. + server.process_input(c_hs4.unwrap(), now); + let s_ack = server.process(c_hs2, now).dgram(); + assert!(s_ack.is_some()); + // This includes an ACK, but it also includes HANDSHAKE_DONE, + // which we need to remove because that will cause the Handshake loss + // recovery state to be dropped. + let (s_hs_ack, _s_ap_ack) = split_datagram(&s_ack.unwrap()); + + // Now the client should start its loss recovery timer based on the ACK. + now += RTT / 2; + let c_ack = client.process(Some(s_hs_ack), now).dgram(); + assert!(c_ack.is_none()); + // The client should now have the loss recovery timer active. + let lr_time = client.process(None, now).callback(); + assert_ne!(lr_time, Duration::from_secs(0)); + assert!(lr_time < (RTT / 2)); + + // Skipping forward by the loss recovery timer should cause the client to + // mark packets as lost and retransmit, after which we should be on the PTO + // timer. + now += lr_time; + let delay = client.process(None, now).callback(); + assert_ne!(delay, Duration::from_secs(0)); + assert!(delay > lr_time); +} + +/// `sender` sends a little, `receiver` acknowledges it. +/// Repeat until `count` acknowledgements are sent. +/// Returns the last packet containing acknowledgements, if any. +fn trickle(sender: &mut Connection, receiver: &mut Connection, mut count: usize, now: Instant) { + let id = sender.stream_create(StreamType::UniDi).unwrap(); + let mut maybe_ack = None; + while count > 0 { + qdebug!("trickle: remaining={}", count); + assert_eq!(sender.stream_send(id, &[9]).unwrap(), 1); + let dgram = sender.process(maybe_ack, now).dgram(); + + maybe_ack = receiver.process(dgram, now).dgram(); + count -= usize::from(maybe_ack.is_some()); + } + sender.process_input(maybe_ack.unwrap(), now); +} + +/// Ensure that a PING frame is sent with ACK sometimes. +/// `fast` allows testing of when `MAX_OUTSTANDING_UNACK` packets are +/// outstanding (`fast` is `true`) within 1 PTO and when only +/// `MIN_OUTSTANDING_UNACK` packets arrive after 2 PTOs (`fast` is `false`). +fn ping_with_ack(fast: bool) { + let mut sender = default_client(); + let mut receiver = default_server(); + let mut now = now(); + connect_force_idle(&mut sender, &mut receiver); + let sender_acks_before = sender.stats().frame_tx.ack; + let receiver_acks_before = receiver.stats().frame_tx.ack; + let count = if fast { + MAX_OUTSTANDING_UNACK + } else { + MIN_OUTSTANDING_UNACK + }; + trickle(&mut sender, &mut receiver, count, now); + assert_eq!(sender.stats().frame_tx.ack, sender_acks_before); + assert_eq!(receiver.stats().frame_tx.ack, receiver_acks_before + count); + assert_eq!(receiver.stats().frame_tx.ping, 0); + + if !fast { + // Wait at least one PTO, from the reciever's perspective. + // A receiver that hasn't received MAX_OUTSTANDING_UNACK won't send PING. + now += receiver.pto() + Duration::from_micros(1); + trickle(&mut sender, &mut receiver, 1, now); + assert_eq!(receiver.stats().frame_tx.ping, 0); + } + + // After a second PTO (or the first if fast), new acknowledgements come + // with a PING frame and cause an ACK to be sent by the sender. + now += receiver.pto() + Duration::from_micros(1); + trickle(&mut sender, &mut receiver, 1, now); + assert_eq!(receiver.stats().frame_tx.ping, 1); + if let Output::Callback(t) = sender.process_output(now) { + assert_eq!(t, DEFAULT_ACK_DELAY); + assert!(sender.process_output(now + t).dgram().is_some()); + } + assert_eq!(sender.stats().frame_tx.ack, sender_acks_before + 1); +} + +#[test] +fn ping_with_ack_fast() { + ping_with_ack(true); +} + +#[test] +fn ping_with_ack_slow() { + ping_with_ack(false); +} + +#[test] +fn ping_with_ack_min() { + const COUNT: usize = MIN_OUTSTANDING_UNACK - 2; + let mut sender = default_client(); + let mut receiver = default_server(); + let mut now = now(); + connect_force_idle(&mut sender, &mut receiver); + let sender_acks_before = sender.stats().frame_tx.ack; + let receiver_acks_before = receiver.stats().frame_tx.ack; + trickle(&mut sender, &mut receiver, COUNT, now); + assert_eq!(sender.stats().frame_tx.ack, sender_acks_before); + assert_eq!(receiver.stats().frame_tx.ack, receiver_acks_before + COUNT); + assert_eq!(receiver.stats().frame_tx.ping, 0); + + // After 3 PTO, no PING because there are too few outstanding packets. + now += receiver.pto() * 3 + Duration::from_micros(1); + trickle(&mut sender, &mut receiver, 1, now); + assert_eq!(receiver.stats().frame_tx.ping, 0); +} + +/// This calculates the PTO timer immediately after connection establishment. +/// It depends on there only being 2 RTT samples in the handshake. +fn expected_pto(rtt: Duration) -> Duration { + // PTO calculation is rtt + 4rttvar + ack delay. + // rttvar should be (rtt + 4 * (rtt / 2) * (3/4)^n + 25ms)/2 + // where n is the number of round trips + // This uses a 25ms ack delay as the ACK delay extension + // is negotiated and no ACK_DELAY frame has been received. + rtt + rtt * 9 / 8 + Duration::from_millis(25) +} + +#[test] +fn fast_pto() { + let mut client = new_client(ConnectionParameters::default().fast_pto(FAST_PTO_SCALE / 2)); + let mut server = default_server(); + let mut now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + let res = client.process(None, now); + let idle_timeout = ConnectionParameters::default().get_idle_timeout() - (DEFAULT_RTT / 2); + assert_eq!(res, Output::Callback(idle_timeout)); + + // Send data on two streams + let stream = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(), + DEFAULT_STREAM_DATA.len() + ); + + // Send a packet after some time. + now += idle_timeout / 2; + let dgram = client.process_output(now).dgram(); + assert!(dgram.is_some()); + + // Nothing to do, should return a callback. + let cb = client.process_output(now).callback(); + assert_eq!(expected_pto(DEFAULT_RTT) / 2, cb); + + // Once the PTO timer expires, a PTO packet should be sent should want to send PTO packet. + now += cb; + let dgram = client.process(None, now).dgram(); + + let stream_before = server.stats().frame_rx.stream; + server.process_input(dgram.unwrap(), now); + assert_eq!(server.stats().frame_rx.stream, stream_before + 1); +} + +/// Even if the PTO timer is slowed right down, persistent congestion is declared +/// based on the "true" value of the timer. +#[test] +fn fast_pto_persistent_congestion() { + let mut client = new_client(ConnectionParameters::default().fast_pto(FAST_PTO_SCALE * 2)); + let mut server = default_server(); + let mut now = connect_rtt_idle(&mut client, &mut server, DEFAULT_RTT); + + let res = client.process(None, now); + let idle_timeout = ConnectionParameters::default().get_idle_timeout() - (DEFAULT_RTT / 2); + assert_eq!(res, Output::Callback(idle_timeout)); + + // Send packets spaced by the PTO timer. And lose them. + // Note: This timing is a tiny bit higher than the client will use + // to determine persistent congestion. The ACK below adds another RTT + // estimate, which will reduce rttvar by 3/4, so persistent congestion + // will occur at `rtt + rtt*27/32 + 25ms`. + // That is OK as we're still showing that this interval is less than + // six times the PTO, which is what would be used if the scaling + // applied to the PTO used to determine persistent congestion. + let pc_interval = expected_pto(DEFAULT_RTT) * 3; + println!("pc_interval {:?}", pc_interval); + let _drop1 = send_something(&mut client, now); + + // Check that the PTO matches expectations. + let cb = client.process_output(now).callback(); + assert_eq!(expected_pto(DEFAULT_RTT) * 2, cb); + + now += pc_interval; + let _drop2 = send_something(&mut client, now); + let _drop3 = send_something(&mut client, now); + let _drop4 = send_something(&mut client, now); + let dgram = send_something(&mut client, now); + + // Now acknowledge the tail packet and enter persistent congestion. + now += DEFAULT_RTT / 2; + let ack = server.process(Some(dgram), now).dgram(); + now += DEFAULT_RTT / 2; + client.process_input(ack.unwrap(), now); + assert_eq!(cwnd(&client), CWND_MIN); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/resumption.rs b/third_party/rust/neqo-transport/src/connection/tests/resumption.rs new file mode 100644 index 0000000000..0c34f3448d --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/resumption.rs @@ -0,0 +1,246 @@ +// 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, connect_with_rtt, default_client, default_server, exchange_ticket, get_tokens, + new_client, resumed_server, send_something, AT_LEAST_PTO, +}; +use crate::addr_valid::{AddressValidation, ValidateAddress}; +use crate::{ConnectionParameters, Error, Version}; + +use std::cell::RefCell; +use std::mem; +use std::rc::Rc; +use std::time::Duration; +use test_fixture::{self, assertions, now}; + +#[test] +fn resume() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let token = exchange_ticket(&mut client, &mut server, now()); + let mut client = default_client(); + client + .enable_resumption(now(), token) + .expect("should set token"); + let mut server = resumed_server(&client); + connect(&mut client, &mut server); + assert!(client.tls_info().unwrap().resumed()); + assert!(server.tls_info().unwrap().resumed()); +} + +#[test] +fn remember_smoothed_rtt() { + const RTT1: Duration = Duration::from_millis(130); + const RTT2: Duration = Duration::from_millis(70); + + let mut client = default_client(); + let mut server = default_server(); + + let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT1); + assert_eq!(client.paths.rtt(), RTT1); + + // We can't use exchange_ticket here because it doesn't respect RTT. + // Also, connect_with_rtt() ends with the server receiving a packet it + // wants to acknowledge; so the ticket will include an ACK frame too. + let validation = AddressValidation::new(now, ValidateAddress::NoToken).unwrap(); + let validation = Rc::new(RefCell::new(validation)); + server.set_validation(Rc::clone(&validation)); + server.send_ticket(now, &[]).expect("can send ticket"); + let ticket = server.process_output(now).dgram(); + assert!(ticket.is_some()); + now += RTT1 / 2; + client.process_input(ticket.unwrap(), now); + let token = get_tokens(&mut client).pop().unwrap(); + + let mut client = default_client(); + client.enable_resumption(now, token).unwrap(); + assert_eq!( + client.paths.rtt(), + RTT1, + "client should remember previous RTT" + ); + let mut server = resumed_server(&client); + + connect_with_rtt(&mut client, &mut server, now, RTT2); + assert_eq!( + client.paths.rtt(), + RTT2, + "previous RTT should be completely erased" + ); +} + +/// Check that a resumed connection uses a token on Initial packets. +#[test] +fn address_validation_token_resume() { + const RTT: Duration = Duration::from_millis(10); + + let mut client = default_client(); + let mut server = default_server(); + let validation = AddressValidation::new(now(), ValidateAddress::Always).unwrap(); + let validation = Rc::new(RefCell::new(validation)); + server.set_validation(Rc::clone(&validation)); + let mut now = connect_with_rtt(&mut client, &mut server, now(), RTT); + + let token = exchange_ticket(&mut client, &mut server, now); + let mut client = default_client(); + client.enable_resumption(now, token).unwrap(); + let mut server = resumed_server(&client); + + // Grab an Initial packet from the client. + let dgram = client.process(None, now).dgram(); + assertions::assert_initial(dgram.as_ref().unwrap(), true); + + // Now try to complete the handshake after giving time for a client PTO. + now += AT_LEAST_PTO; + connect_with_rtt(&mut client, &mut server, now, RTT); + assert!(client.crypto.tls.info().unwrap().resumed()); + assert!(server.crypto.tls.info().unwrap().resumed()); +} + +fn can_resume(token: impl AsRef<[u8]>, initial_has_token: bool) { + let mut client = default_client(); + client.enable_resumption(now(), token).unwrap(); + let initial = client.process_output(now()).dgram(); + assertions::assert_initial(initial.as_ref().unwrap(), initial_has_token); +} + +#[test] +fn two_tickets_on_timer() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // Send two tickets and then bundle those into a packet. + server.send_ticket(now(), &[]).expect("send ticket1"); + server.send_ticket(now(), &[]).expect("send ticket2"); + let pkt = send_something(&mut server, now()); + + // process() will return an ack first + assert!(client.process(Some(pkt), now()).dgram().is_some()); + // We do not have a ResumptionToken event yet, because NEW_TOKEN was not sent. + assert_eq!(get_tokens(&mut client).len(), 0); + + // We need to wait for release_resumption_token_timer to expire. The timer will be + // set to 3 * PTO + let mut now = now() + 3 * client.pto(); + mem::drop(client.process(None, now)); + let mut recv_tokens = get_tokens(&mut client); + assert_eq!(recv_tokens.len(), 1); + let token1 = recv_tokens.pop().unwrap(); + // Wai for anottheer 3 * PTO to get the nex okeen. + now += 3 * client.pto(); + mem::drop(client.process(None, now)); + let mut recv_tokens = get_tokens(&mut client); + assert_eq!(recv_tokens.len(), 1); + let token2 = recv_tokens.pop().unwrap(); + // Wait for 3 * PTO, but now there are no more tokens. + now += 3 * client.pto(); + mem::drop(client.process(None, now)); + assert_eq!(get_tokens(&mut client).len(), 0); + assert_ne!(token1.as_ref(), token2.as_ref()); + + can_resume(token1, false); + can_resume(token2, false); +} + +#[test] +fn two_tickets_with_new_token() { + let mut client = default_client(); + let mut server = default_server(); + let validation = AddressValidation::new(now(), ValidateAddress::Always).unwrap(); + let validation = Rc::new(RefCell::new(validation)); + server.set_validation(Rc::clone(&validation)); + connect(&mut client, &mut server); + + // Send two tickets with tokens and then bundle those into a packet. + server.send_ticket(now(), &[]).expect("send ticket1"); + server.send_ticket(now(), &[]).expect("send ticket2"); + let pkt = send_something(&mut server, now()); + + client.process_input(pkt, now()); + let mut all_tokens = get_tokens(&mut client); + assert_eq!(all_tokens.len(), 2); + let token1 = all_tokens.pop().unwrap(); + let token2 = all_tokens.pop().unwrap(); + assert_ne!(token1.as_ref(), token2.as_ref()); + + can_resume(token1, true); + can_resume(token2, true); +} + +/// By disabling address validation, the server won't send `NEW_TOKEN`, but +/// we can take the session ticket still. +#[test] +fn take_token() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + server.send_ticket(now(), &[]).unwrap(); + let dgram = server.process(None, now()).dgram(); + client.process_input(dgram.unwrap(), now()); + + // There should be no ResumptionToken event here. + let tokens = get_tokens(&mut client); + assert_eq!(tokens.len(), 0); + + // But we should be able to get the token directly, and use it. + let token = client.take_resumption_token(now()).unwrap(); + can_resume(token, false); +} + +/// If a version is selected and subsequently disabled, resumption fails. +#[test] +fn resume_disabled_version() { + let mut client = new_client( + ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]), + ); + let mut server = default_server(); + connect(&mut client, &mut server); + let token = exchange_ticket(&mut client, &mut server, now()); + + let mut client = new_client( + ConnectionParameters::default().versions(Version::Version2, vec![Version::Version2]), + ); + assert_eq!( + client.enable_resumption(now(), token).unwrap_err(), + Error::DisabledVersion + ); +} + +/// It's not possible to resume once a packet has been sent. +#[test] +fn resume_after_packet() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let token = exchange_ticket(&mut client, &mut server, now()); + + let mut client = default_client(); + mem::drop(client.process_output(now()).dgram().unwrap()); + assert_eq!( + client.enable_resumption(now(), token).unwrap_err(), + Error::ConnectionState + ); +} + +/// It's not possible to resume at the server. +#[test] +fn resume_server() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + let token = exchange_ticket(&mut client, &mut server, now()); + + let mut server = default_server(); + assert_eq!( + server.enable_resumption(now(), token).unwrap_err(), + Error::ConnectionState + ); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/stream.rs b/third_party/rust/neqo-transport/src/connection/tests/stream.rs new file mode 100644 index 0000000000..2968d96d4a --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/stream.rs @@ -0,0 +1,964 @@ +// 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::super::State; +use super::{ + assert_error, connect, connect_force_idle, default_client, default_server, maybe_authenticate, + new_client, new_server, send_something, DEFAULT_STREAM_DATA, +}; +use crate::events::ConnectionEvent; +use crate::recv_stream::RECV_BUFFER_SIZE; +use crate::send_stream::{SendStreamState, SEND_BUFFER_SIZE}; +use crate::tparams::{self, TransportParameter}; +use crate::tracking::DEFAULT_ACK_PACKET_TOLERANCE; +use crate::{Connection, ConnectionError, ConnectionParameters}; +use crate::{Error, StreamType}; + +use neqo_common::{event::Provider, qdebug}; +use std::cmp::max; +use std::convert::TryFrom; +use std::mem; +use test_fixture::now; + +#[test] +fn stream_create() { + let mut client = default_client(); + + let out = client.process(None, now()); + let mut server = default_server(); + let out = server.process(out.dgram(), now()); + + let out = client.process(out.dgram(), now()); + mem::drop(server.process(out.dgram(), now())); + assert!(maybe_authenticate(&mut client)); + let out = client.process(None, now()); + + // client now in State::Connected + assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 2); + assert_eq!(client.stream_create(StreamType::UniDi).unwrap(), 6); + assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 0); + assert_eq!(client.stream_create(StreamType::BiDi).unwrap(), 4); + + mem::drop(server.process(out.dgram(), now())); + // server now in State::Connected + assert_eq!(server.stream_create(StreamType::UniDi).unwrap(), 3); + assert_eq!(server.stream_create(StreamType::UniDi).unwrap(), 7); + assert_eq!(server.stream_create(StreamType::BiDi).unwrap(), 1); + assert_eq!(server.stream_create(StreamType::BiDi).unwrap(), 5); +} + +#[test] +// tests stream send/recv after connection is established. +fn transfer() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + qdebug!("---- client sends"); + // Send + let client_stream_id = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_send(client_stream_id, &[6; 100]).unwrap(); + client.stream_send(client_stream_id, &[7; 40]).unwrap(); + client.stream_send(client_stream_id, &[8; 4000]).unwrap(); + + // Send to another stream but some data after fin has been set + let client_stream_id2 = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_send(client_stream_id2, &[6; 60]).unwrap(); + client.stream_close_send(client_stream_id2).unwrap(); + client.stream_send(client_stream_id2, &[7; 50]).unwrap_err(); + // Sending this much takes a few datagrams. + let mut datagrams = vec![]; + let mut out = client.process_output(now()); + while let Some(d) = out.dgram() { + datagrams.push(d); + out = client.process_output(now()); + } + assert_eq!(datagrams.len(), 4); + assert_eq!(*client.state(), State::Confirmed); + + qdebug!("---- server receives"); + for (d_num, d) in datagrams.into_iter().enumerate() { + let out = server.process(Some(d), now()); + assert_eq!( + out.as_dgram_ref().is_some(), + (d_num + 1) % usize::try_from(DEFAULT_ACK_PACKET_TOLERANCE + 1).unwrap() == 0 + ); + qdebug!("Output={:0x?}", out.as_dgram_ref()); + } + assert_eq!(*server.state(), State::Confirmed); + + let mut buf = vec![0; 4000]; + + let mut stream_ids = server.events().filter_map(|evt| match evt { + ConnectionEvent::NewStream { stream_id, .. } => Some(stream_id), + _ => None, + }); + let first_stream = stream_ids.next().expect("should have a new stream event"); + let second_stream = stream_ids + .next() + .expect("should have a second new stream event"); + assert!(stream_ids.next().is_none()); + let (received1, fin1) = server.stream_recv(first_stream, &mut buf).unwrap(); + assert_eq!(received1, 4000); + assert!(!fin1); + let (received2, fin2) = server.stream_recv(first_stream, &mut buf).unwrap(); + assert_eq!(received2, 140); + assert!(!fin2); + + let (received3, fin3) = server.stream_recv(second_stream, &mut buf).unwrap(); + assert_eq!(received3, 60); + assert!(fin3); +} + +#[test] +// Send fin even if a peer closes a reomte bidi send stream before sending any data. +fn report_fin_when_stream_closed_wo_data() { + // Note that the two servers in this test will get different anti-replay filters. + // That's OK because we aren't testing anti-replay. + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // create a stream + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_send(stream_id, &[0x00]).unwrap(); + let out = client.process(None, now()); + mem::drop(server.process(out.dgram(), now())); + + server.stream_close_send(stream_id).unwrap(); + let out = server.process(None, now()); + mem::drop(client.process(out.dgram(), now())); + let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. }); + assert!(client.events().any(stream_readable)); +} + +fn exchange_data(client: &mut Connection, server: &mut Connection) { + let mut input = None; + loop { + let out = client.process(input, now()).dgram(); + let c_done = out.is_none(); + let out = server.process(out, now()).dgram(); + if out.is_none() && c_done { + break; + } + input = out; + } +} + +#[test] +fn sending_max_data() { + const SMALL_MAX_DATA: usize = 2048; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().max_data(u64::try_from(SMALL_MAX_DATA).unwrap()), + ); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.events().count(), 2); // SendStreamWritable, StateChange(connected) + assert_eq!(stream_id, 2); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA + 1]) + .unwrap(), + SMALL_MAX_DATA + ); + + exchange_data(&mut client, &mut server); + + let mut buf = vec![0; 40000]; + let (received, fin) = server.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(received, SMALL_MAX_DATA); + assert!(!fin); + + let out = server.process(None, now()).dgram(); + client.process_input(out.unwrap(), now()); + + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA + 1]) + .unwrap(), + SMALL_MAX_DATA + ); +} + +#[test] +fn max_data() { + const SMALL_MAX_DATA: usize = 16383; + + let mut client = default_client(); + let mut server = default_server(); + + server + .set_local_tparam( + tparams::INITIAL_MAX_DATA, + TransportParameter::Integer(u64::try_from(SMALL_MAX_DATA).unwrap()), + ) + .unwrap(); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.events().count(), 2); // SendStreamWritable, StateChange(connected) + assert_eq!(stream_id, 2); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA + 1]) + .unwrap(), + SMALL_MAX_DATA + ); + assert_eq!(client.events().count(), 0); + + assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 0); + client + .streams + .get_send_stream_mut(stream_id) + .unwrap() + .mark_as_sent(0, 4096, false); + assert_eq!(client.events().count(), 0); + client + .streams + .get_send_stream_mut(stream_id) + .unwrap() + .mark_as_acked(0, 4096, false); + assert_eq!(client.events().count(), 0); + + assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 0); + // no event because still limited by conn max data + assert_eq!(client.events().count(), 0); + + // Increase max data. Avail space now limited by stream credit + client.streams.handle_max_data(100_000_000); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SEND_BUFFER_SIZE - SMALL_MAX_DATA + ); + + // Increase max stream data. Avail space now limited by tx buffer + client + .streams + .get_send_stream_mut(stream_id) + .unwrap() + .set_max_stream_data(100_000_000); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SEND_BUFFER_SIZE - SMALL_MAX_DATA + 4096 + ); + + let evts = client.events().collect::<Vec<_>>(); + assert_eq!(evts.len(), 1); + assert!(matches!( + evts[0], + ConnectionEvent::SendStreamWritable { .. } + )); +} + +#[test] +fn exceed_max_data() { + const SMALL_MAX_DATA: usize = 1024; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().max_data(u64::try_from(SMALL_MAX_DATA).unwrap()), + ); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.events().count(), 2); // SendStreamWritable, StateChange(connected) + assert_eq!(stream_id, 2); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA + 1]) + .unwrap(), + SMALL_MAX_DATA + ); + + assert_eq!(client.stream_send(stream_id, b"hello").unwrap(), 0); + + // Artificially trick the client to think that it has more flow control credit. + client.streams.handle_max_data(100_000_000); + assert_eq!(client.stream_send(stream_id, b"h").unwrap(), 1); + + exchange_data(&mut client, &mut server); + + assert_error( + &client, + &ConnectionError::Transport(Error::PeerError(Error::FlowControlError.code())), + ); + assert_error( + &server, + &ConnectionError::Transport(Error::FlowControlError), + ); +} + +#[test] +// If we send a stop_sending to the peer, we should not accept more data from the peer. +fn do_not_accept_data_after_stop_sending() { + // Note that the two servers in this test will get different anti-replay filters. + // That's OK because we aren't testing anti-replay. + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // create a stream + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_send(stream_id, &[0x00]).unwrap(); + let out = client.process(None, now()); + mem::drop(server.process(out.dgram(), now())); + + let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. }); + assert!(server.events().any(stream_readable)); + + // Send one more packet from client. The packet should arrive after the server + // has already requested stop_sending. + client.stream_send(stream_id, &[0x00]).unwrap(); + let out_second_data_frame = client.process(None, now()); + // Call stop sending. + assert_eq!( + Ok(()), + server.stream_stop_sending(stream_id, Error::NoError.code()) + ); + + // Receive the second data frame. The frame should be ignored and + // DataReadable events shouldn't be posted. + let out = server.process(out_second_data_frame.dgram(), now()); + assert!(!server.events().any(stream_readable)); + + mem::drop(client.process(out.dgram(), now())); + assert_eq!( + Err(Error::FinalSizeError), + client.stream_send(stream_id, &[0x00]) + ); +} + +#[test] +// Server sends stop_sending, the client simultaneous sends reset. +fn simultaneous_stop_sending_and_reset() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // create a stream + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_send(stream_id, &[0x00]).unwrap(); + let out = client.process(None, now()); + let ack = server.process(out.dgram(), now()).dgram(); + + let stream_readable = + |e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id: id } if id == stream_id); + assert!(server.events().any(stream_readable)); + + // The client resets the stream. The packet with reset should arrive after the server + // has already requested stop_sending. + client.stream_reset_send(stream_id, 0).unwrap(); + let out_reset_frame = client.process(ack, now()).dgram(); + + // Send something out of order to force the server to generate an + // acknowledgment at the next opportunity. + let force_ack = send_something(&mut client, now()); + server.process_input(force_ack, now()); + + // Call stop sending. + server.stream_stop_sending(stream_id, 0).unwrap(); + // Receive the second data frame. The frame should be ignored and + // DataReadable events shouldn't be posted. + let ack = server.process(out_reset_frame, now()).dgram(); + assert!(ack.is_some()); + assert!(!server.events().any(stream_readable)); + + // The client gets the STOP_SENDING frame. + client.process_input(ack.unwrap(), now()); + assert_eq!( + Err(Error::InvalidStreamId), + client.stream_send(stream_id, &[0x00]) + ); +} + +#[test] +fn client_fin_reorder() { + let mut client = default_client(); + let mut server = default_server(); + + // Send ClientHello. + let client_hs = client.process(None, now()); + assert!(client_hs.as_dgram_ref().is_some()); + + let server_hs = server.process(client_hs.dgram(), now()); + assert!(server_hs.as_dgram_ref().is_some()); // ServerHello, etc... + + let client_ack = client.process(server_hs.dgram(), now()); + assert!(client_ack.as_dgram_ref().is_some()); + + let server_out = server.process(client_ack.dgram(), now()); + assert!(server_out.as_dgram_ref().is_none()); + + assert!(maybe_authenticate(&mut client)); + assert_eq!(*client.state(), State::Connected); + + let client_fin = client.process(None, now()); + assert!(client_fin.as_dgram_ref().is_some()); + + let client_stream_id = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_send(client_stream_id, &[1, 2, 3]).unwrap(); + let client_stream_data = client.process(None, now()); + assert!(client_stream_data.as_dgram_ref().is_some()); + + // Now stream data gets before client_fin + let server_out = server.process(client_stream_data.dgram(), now()); + assert!(server_out.as_dgram_ref().is_none()); // the packet will be discarded + + assert_eq!(*server.state(), State::Handshaking); + let server_out = server.process(client_fin.dgram(), now()); + assert!(server_out.as_dgram_ref().is_some()); +} + +#[test] +fn after_fin_is_read_conn_events_for_stream_should_be_removed() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let id = server.stream_create(StreamType::BiDi).unwrap(); + server.stream_send(id, &[6; 10]).unwrap(); + server.stream_close_send(id).unwrap(); + let out = server.process(None, now()).dgram(); + assert!(out.is_some()); + + mem::drop(client.process(out, now())); + + // read from the stream before checking connection events. + let mut buf = vec![0; 4000]; + let (_, fin) = client.stream_recv(id, &mut buf).unwrap(); + assert!(fin); + + // Make sure we do not have RecvStreamReadable events for the stream when fin has been read. + let readable_stream_evt = + |e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id } if stream_id == id); + assert!(!client.events().any(readable_stream_evt)); +} + +#[test] +fn after_stream_stop_sending_is_called_conn_events_for_stream_should_be_removed() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let id = server.stream_create(StreamType::BiDi).unwrap(); + server.stream_send(id, &[6; 10]).unwrap(); + server.stream_close_send(id).unwrap(); + let out = server.process(None, now()).dgram(); + assert!(out.is_some()); + + mem::drop(client.process(out, now())); + + // send stop seending. + client + .stream_stop_sending(id, Error::NoError.code()) + .unwrap(); + + // Make sure we do not have RecvStreamReadable events for the stream after stream_stop_sending + // has been called. + let readable_stream_evt = + |e| matches!(e, ConnectionEvent::RecvStreamReadable { stream_id } if stream_id == id); + assert!(!client.events().any(readable_stream_evt)); +} + +#[test] +fn stream_data_blocked_generates_max_stream_data() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let now = now(); + + // Send some data and consume some flow control. + let stream_id = server.stream_create(StreamType::UniDi).unwrap(); + let _ = server.stream_send(stream_id, DEFAULT_STREAM_DATA).unwrap(); + let dgram = server.process(None, now).dgram(); + assert!(dgram.is_some()); + + // Consume the data. + client.process_input(dgram.unwrap(), now); + let mut buf = [0; 10]; + let (count, end) = client.stream_recv(stream_id, &mut buf[..]).unwrap(); + assert_eq!(count, DEFAULT_STREAM_DATA.len()); + assert!(!end); + + // Now send `STREAM_DATA_BLOCKED`. + let internal_stream = server.streams.get_send_stream_mut(stream_id).unwrap(); + if let SendStreamState::Send { fc, .. } = internal_stream.state() { + fc.blocked(); + } else { + panic!("unexpected stream state"); + } + let dgram = server.process_output(now).dgram(); + assert!(dgram.is_some()); + + let sdb_before = client.stats().frame_rx.stream_data_blocked; + let dgram = client.process(dgram, now).dgram(); + assert_eq!(client.stats().frame_rx.stream_data_blocked, sdb_before + 1); + assert!(dgram.is_some()); + + // Client should have sent a MAX_STREAM_DATA frame with just a small increase + // on the default window size. + let msd_before = server.stats().frame_rx.max_stream_data; + server.process_input(dgram.unwrap(), now); + assert_eq!(server.stats().frame_rx.max_stream_data, msd_before + 1); + + // Test that the entirety of the receive buffer is available now. + let mut written = 0; + loop { + const LARGE_BUFFER: &[u8] = &[0; 1024]; + let amount = server.stream_send(stream_id, LARGE_BUFFER).unwrap(); + if amount == 0 { + break; + } + written += amount; + } + assert_eq!(written, RECV_BUFFER_SIZE); +} + +/// See <https://github.com/mozilla/neqo/issues/871> +#[test] +fn max_streams_after_bidi_closed() { + const REQUEST: &[u8] = b"ping"; + const RESPONSE: &[u8] = b"pong"; + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + while client.stream_create(StreamType::BiDi).is_ok() { + // Exhaust the stream limit. + } + // Write on the one stream and send that out. + let _ = client.stream_send(stream_id, REQUEST).unwrap(); + client.stream_close_send(stream_id).unwrap(); + let dgram = client.process(None, now()).dgram(); + + // Now handle the stream and send an incomplete response. + server.process_input(dgram.unwrap(), now()); + server.stream_send(stream_id, RESPONSE).unwrap(); + let dgram = server.process_output(now()).dgram(); + + // The server shouldn't have released more stream credit. + client.process_input(dgram.unwrap(), now()); + let e = client.stream_create(StreamType::BiDi).unwrap_err(); + assert!(matches!(e, Error::StreamLimitError)); + + // Closing the stream isn't enough. + server.stream_close_send(stream_id).unwrap(); + let dgram = server.process_output(now()).dgram(); + client.process_input(dgram.unwrap(), now()); + assert!(client.stream_create(StreamType::BiDi).is_err()); + + // The server needs to see an acknowledgment from the client for its + // response AND the server has to read all of the request. + // and the server needs to read all the data. Read first. + let mut buf = [0; REQUEST.len()]; + let (count, fin) = server.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(&buf[..count], REQUEST); + assert!(fin); + + // We need an ACK from the client now, but that isn't guaranteed, + // so give the client one more packet just in case. + let dgram = send_something(&mut server, now()); + client.process_input(dgram, now()); + + // Now get the client to send the ACK and have the server handle that. + let dgram = send_something(&mut client, now()); + let dgram = server.process(Some(dgram), now()).dgram(); + client.process_input(dgram.unwrap(), now()); + assert!(client.stream_create(StreamType::BiDi).is_ok()); + assert!(client.stream_create(StreamType::BiDi).is_err()); +} + +#[test] +fn no_dupdata_readable_events() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // create a stream + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_send(stream_id, &[0x00]).unwrap(); + let out = client.process(None, now()); + mem::drop(server.process(out.dgram(), now())); + + // We have a data_readable event. + let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. }); + assert!(server.events().any(stream_readable)); + + // Send one more data frame from client. The previous stream data has not been read yet, + // therefore there should not be a new DataReadable event. + client.stream_send(stream_id, &[0x00]).unwrap(); + let out_second_data_frame = client.process(None, now()); + mem::drop(server.process(out_second_data_frame.dgram(), now())); + assert!(!server.events().any(stream_readable)); + + // One more frame with a fin will not produce a new DataReadable event, because the + // previous stream data has not been read yet. + client.stream_send(stream_id, &[0x00]).unwrap(); + client.stream_close_send(stream_id).unwrap(); + let out_third_data_frame = client.process(None, now()); + mem::drop(server.process(out_third_data_frame.dgram(), now())); + assert!(!server.events().any(stream_readable)); +} + +#[test] +fn no_dupdata_readable_events_empty_last_frame() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + // create a stream + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_send(stream_id, &[0x00]).unwrap(); + let out = client.process(None, now()); + mem::drop(server.process(out.dgram(), now())); + + // We have a data_readable event. + let stream_readable = |e| matches!(e, ConnectionEvent::RecvStreamReadable { .. }); + assert!(server.events().any(stream_readable)); + + // An empty frame with a fin will not produce a new DataReadable event, because + // the previous stream data has not been read yet. + client.stream_close_send(stream_id).unwrap(); + let out_second_data_frame = client.process(None, now()); + mem::drop(server.process(out_second_data_frame.dgram(), now())); + assert!(!server.events().any(stream_readable)); +} + +fn change_flow_control(stream_type: StreamType, new_fc: u64) { + const RECV_BUFFER_START: u64 = 300; + + let mut client = new_client( + ConnectionParameters::default() + .max_stream_data(StreamType::BiDi, true, RECV_BUFFER_START) + .max_stream_data(StreamType::UniDi, true, RECV_BUFFER_START), + ); + let mut server = default_server(); + connect(&mut client, &mut server); + + // create a stream + let stream_id = server.stream_create(stream_type).unwrap(); + let written1 = server.stream_send(stream_id, &[0x0; 10000]).unwrap(); + assert_eq!(u64::try_from(written1).unwrap(), RECV_BUFFER_START); + + // Send the stream to the client. + let out = server.process(None, now()); + mem::drop(client.process(out.dgram(), now())); + + // change max_stream_data for stream_id. + client.set_stream_max_data(stream_id, new_fc).unwrap(); + + // server should receive a MAX_SREAM_DATA frame if the flow control window is updated. + let out2 = client.process(None, now()); + let out3 = server.process(out2.dgram(), now()); + let expected = usize::from(RECV_BUFFER_START < new_fc); + assert_eq!(server.stats().frame_rx.max_stream_data, expected); + + // If the flow control window has been increased, server can write more data. + let written2 = server.stream_send(stream_id, &[0x0; 10000]).unwrap(); + if RECV_BUFFER_START < new_fc { + assert_eq!(u64::try_from(written2).unwrap(), new_fc - RECV_BUFFER_START); + } else { + assert_eq!(written2, 0); + } + + // Exchange packets so that client gets all data. + let out4 = client.process(out3.dgram(), now()); + let out5 = server.process(out4.dgram(), now()); + mem::drop(client.process(out5.dgram(), now())); + + // read all data by client + let mut buf = [0x0; 10000]; + let (read, _) = client.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(u64::try_from(read).unwrap(), max(RECV_BUFFER_START, new_fc)); + + let out4 = client.process(None, now()); + mem::drop(server.process(out4.dgram(), now())); + + let written3 = server.stream_send(stream_id, &[0x0; 10000]).unwrap(); + assert_eq!(u64::try_from(written3).unwrap(), new_fc); +} + +#[test] +fn increase_decrease_flow_control() { + const RECV_BUFFER_NEW_BIGGER: u64 = 400; + const RECV_BUFFER_NEW_SMALLER: u64 = 200; + + change_flow_control(StreamType::UniDi, RECV_BUFFER_NEW_BIGGER); + change_flow_control(StreamType::BiDi, RECV_BUFFER_NEW_BIGGER); + + change_flow_control(StreamType::UniDi, RECV_BUFFER_NEW_SMALLER); + change_flow_control(StreamType::BiDi, RECV_BUFFER_NEW_SMALLER); +} + +#[test] +fn session_flow_control_stop_sending_state_recv() { + const SMALL_MAX_DATA: usize = 1024; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().max_data(u64::try_from(SMALL_MAX_DATA).unwrap()), + ); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + + // send 1 byte so that the server learns about the stream. + assert_eq!(client.stream_send(stream_id, b"a").unwrap(), 1); + + exchange_data(&mut client, &mut server); + + server + .stream_stop_sending(stream_id, Error::NoError.code()) + .unwrap(); + + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA]) + .unwrap(), + SMALL_MAX_DATA - 1 + ); + + // In this case the final size is only known after RESET frame is received. + // The server sends STOP_SENDING -> the client sends RESET -> the server + // sends MAX_DATA. + let out = server.process(None, now()).dgram(); + let out = client.process(out, now()).dgram(); + // the client is still limited. + let stream_id2 = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(client.stream_avail_send_space(stream_id2).unwrap(), 0); + let out = server.process(out, now()).dgram(); + client.process_input(out.unwrap(), now()); + assert_eq!( + client.stream_avail_send_space(stream_id2).unwrap(), + SMALL_MAX_DATA + ); +} + +#[test] +fn session_flow_control_stop_sending_state_size_known() { + const SMALL_MAX_DATA: usize = 1024; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().max_data(u64::try_from(SMALL_MAX_DATA).unwrap()), + ); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + + // send 1 byte so that the server learns about the stream. + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA + 1]) + .unwrap(), + SMALL_MAX_DATA + ); + + let out1 = client.process(None, now()).dgram(); + // Delay this packet and let the server receive fin first (it will enter SizeKnown state). + client.stream_close_send(stream_id).unwrap(); + let out2 = client.process(None, now()).dgram(); + + server.process_input(out2.unwrap(), now()); + + server + .stream_stop_sending(stream_id, Error::NoError.code()) + .unwrap(); + + // In this case the final size is known when stream_stop_sending is called + // and the server releases flow control immediately and sends STOP_SENDING and + // MAX_DATA in the same packet. + let out = server.process(out1, now()).dgram(); + client.process_input(out.unwrap(), now()); + + // The flow control should have been updated and the client can again send + // SMALL_MAX_DATA. + let stream_id2 = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id2).unwrap(), + SMALL_MAX_DATA + ); +} + +#[test] +fn session_flow_control_stop_sending_state_data_recvd() { + const SMALL_MAX_DATA: usize = 1024; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().max_data(u64::try_from(SMALL_MAX_DATA).unwrap()), + ); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + + // send 1 byte so that the server learns about the stream. + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA + 1]) + .unwrap(), + SMALL_MAX_DATA + ); + + client.stream_close_send(stream_id).unwrap(); + + exchange_data(&mut client, &mut server); + + // The stream is DataRecvd state + server + .stream_stop_sending(stream_id, Error::NoError.code()) + .unwrap(); + + exchange_data(&mut client, &mut server); + + // The flow control should have been updated and the client can again send + // SMALL_MAX_DATA. + let stream_id2 = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id2).unwrap(), + SMALL_MAX_DATA + ); +} + +#[test] +fn session_flow_control_affects_all_streams() { + const SMALL_MAX_DATA: usize = 1024; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().max_data(u64::try_from(SMALL_MAX_DATA).unwrap()), + ); + + connect(&mut client, &mut server); + + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + + let stream_id2 = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!( + client.stream_avail_send_space(stream_id2).unwrap(), + SMALL_MAX_DATA + ); + + assert_eq!( + client + .stream_send(stream_id, &[b'a'; SMALL_MAX_DATA / 2 + 1]) + .unwrap(), + SMALL_MAX_DATA / 2 + 1 + ); + + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA / 2 - 1 + ); + assert_eq!( + client.stream_avail_send_space(stream_id2).unwrap(), + SMALL_MAX_DATA / 2 - 1 + ); + + exchange_data(&mut client, &mut server); + + let mut buf = [0x0; SMALL_MAX_DATA]; + let (read, _) = server.stream_recv(stream_id, &mut buf).unwrap(); + assert_eq!(read, SMALL_MAX_DATA / 2 + 1); + + exchange_data(&mut client, &mut server); + + assert_eq!( + client.stream_avail_send_space(stream_id).unwrap(), + SMALL_MAX_DATA + ); + + assert_eq!( + client.stream_avail_send_space(stream_id2).unwrap(), + SMALL_MAX_DATA + ); +} + +fn connect_w_different_limit(bidi_limit: u64, unidi_limit: u64) { + let mut client = default_client(); + let out = client.process(None, now()); + let mut server = new_server( + ConnectionParameters::default() + .max_streams(StreamType::BiDi, bidi_limit) + .max_streams(StreamType::UniDi, unidi_limit), + ); + let out = server.process(out.dgram(), now()); + + let out = client.process(out.dgram(), now()); + mem::drop(server.process(out.dgram(), now())); + + assert!(maybe_authenticate(&mut client)); + + let mut bidi_events = 0; + let mut unidi_events = 0; + let mut connected_events = 0; + for e in client.events() { + match e { + ConnectionEvent::SendStreamCreatable { stream_type } => { + if stream_type == StreamType::BiDi { + bidi_events += 1; + } else { + unidi_events += 1; + } + } + ConnectionEvent::StateChange(state) if state == State::Connected => { + connected_events += 1; + } + _ => {} + } + } + assert_eq!(bidi_events, usize::from(bidi_limit > 0)); + assert_eq!(unidi_events, usize::from(unidi_limit > 0)); + assert_eq!(connected_events, 1); +} + +#[test] +fn client_stream_creatable_event() { + connect_w_different_limit(0, 0); + connect_w_different_limit(0, 1); + connect_w_different_limit(1, 0); + connect_w_different_limit(1, 1); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/vn.rs b/third_party/rust/neqo-transport/src/connection/tests/vn.rs new file mode 100644 index 0000000000..c243dc6e8a --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/vn.rs @@ -0,0 +1,486 @@ +// 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::super::{ConnectionError, ConnectionEvent, Output, State, ZeroRttState}; +use super::{ + connect, connect_fail, default_client, default_server, exchange_ticket, new_client, new_server, + send_something, +}; +use crate::packet::PACKET_BIT_LONG; +use crate::tparams::{self, TransportParameter}; +use crate::{ConnectionParameters, Error, Version}; + +use neqo_common::{event::Provider, Datagram, Decoder, Encoder}; +use std::mem; +use std::time::Duration; +use test_fixture::{self, addr, assertions, now}; + +// The expected PTO duration after the first Initial is sent. +const INITIAL_PTO: Duration = Duration::from_millis(300); + +#[test] +fn unknown_version() { + let mut client = default_client(); + // Start the handshake. + mem::drop(client.process(None, now()).dgram()); + + let mut unknown_version_packet = vec![0x80, 0x1a, 0x1a, 0x1a, 0x1a]; + unknown_version_packet.resize(1200, 0x0); + mem::drop(client.process( + Some(Datagram::new(addr(), addr(), unknown_version_packet)), + now(), + )); + assert_eq!(1, client.stats().dropped_rx); +} + +#[test] +fn server_receive_unknown_first_packet() { + let mut server = default_server(); + + let mut unknown_version_packet = vec![0x80, 0x1a, 0x1a, 0x1a, 0x1a]; + unknown_version_packet.resize(1200, 0x0); + + assert_eq!( + server.process( + Some(Datagram::new(addr(), addr(), unknown_version_packet,)), + now(), + ), + Output::None + ); + + assert_eq!(1, server.stats().dropped_rx); +} + +fn create_vn(initial_pkt: &[u8], versions: &[u32]) -> Vec<u8> { + let mut dec = Decoder::from(&initial_pkt[5..]); // Skip past version. + let dst_cid = dec.decode_vec(1).expect("client DCID"); + let src_cid = dec.decode_vec(1).expect("client SCID"); + + let mut encoder = Encoder::default(); + encoder.encode_byte(PACKET_BIT_LONG); + encoder.encode(&[0; 4]); // Zero version == VN. + encoder.encode_vec(1, src_cid); + encoder.encode_vec(1, dst_cid); + + for v in versions { + encoder.encode_uint(4, *v); + } + encoder.into() +} + +#[test] +fn version_negotiation_current_version() { + let mut client = default_client(); + // Start the handshake. + let initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + let vn = create_vn( + &initial_pkt, + &[0x1a1a_1a1a, Version::default().wire_version()], + ); + + let dgram = Datagram::new(addr(), addr(), vn); + let delay = client.process(Some(dgram), now()).callback(); + assert_eq!(delay, INITIAL_PTO); + assert_eq!(*client.state(), State::WaitInitial); + assert_eq!(1, client.stats().dropped_rx); +} + +#[test] +fn version_negotiation_version0() { + let mut client = default_client(); + // Start the handshake. + let initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + let vn = create_vn(&initial_pkt, &[0, 0x1a1a_1a1a]); + + let dgram = Datagram::new(addr(), addr(), vn); + let delay = client.process(Some(dgram), now()).callback(); + assert_eq!(delay, INITIAL_PTO); + assert_eq!(*client.state(), State::WaitInitial); + assert_eq!(1, client.stats().dropped_rx); +} + +#[test] +fn version_negotiation_only_reserved() { + let mut client = default_client(); + // Start the handshake. + let initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a]); + + let dgram = Datagram::new(addr(), addr(), vn); + assert_eq!(client.process(Some(dgram), now()), Output::None); + match client.state() { + State::Closed(err) => { + assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation)); + } + _ => panic!("Invalid client state"), + } +} + +#[test] +fn version_negotiation_corrupted() { + let mut client = default_client(); + // Start the handshake. + let initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a]); + + let dgram = Datagram::new(addr(), addr(), &vn[..vn.len() - 1]); + let delay = client.process(Some(dgram), now()).callback(); + assert_eq!(delay, INITIAL_PTO); + assert_eq!(*client.state(), State::WaitInitial); + assert_eq!(1, client.stats().dropped_rx); +} + +#[test] +fn version_negotiation_empty() { + let mut client = default_client(); + // Start the handshake. + let initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + let vn = create_vn(&initial_pkt, &[]); + + let dgram = Datagram::new(addr(), addr(), vn); + let delay = client.process(Some(dgram), now()).callback(); + assert_eq!(delay, INITIAL_PTO); + assert_eq!(*client.state(), State::WaitInitial); + assert_eq!(1, client.stats().dropped_rx); +} + +#[test] +fn version_negotiation_not_supported() { + let mut client = default_client(); + // Start the handshake. + let initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]); + let dgram = Datagram::new(addr(), addr(), vn); + assert_eq!(client.process(Some(dgram), now()), Output::None); + match client.state() { + State::Closed(err) => { + assert_eq!(*err, ConnectionError::Transport(Error::VersionNegotiation)); + } + _ => panic!("Invalid client state"), + } +} + +#[test] +fn version_negotiation_bad_cid() { + let mut client = default_client(); + // Start the handshake. + let mut initial_pkt = client + .process(None, now()) + .dgram() + .expect("a datagram") + .to_vec(); + + initial_pkt[6] ^= 0xc4; + let vn = create_vn(&initial_pkt, &[0x1a1a_1a1a, 0x2a2a_2a2a, 0xff00_0001]); + + let dgram = Datagram::new(addr(), addr(), vn); + let delay = client.process(Some(dgram), now()).callback(); + assert_eq!(delay, INITIAL_PTO); + assert_eq!(*client.state(), State::WaitInitial); + assert_eq!(1, client.stats().dropped_rx); +} + +#[test] +fn compatible_upgrade() { + let mut client = default_client(); + let mut server = default_server(); + + connect(&mut client, &mut server); + assert_eq!(client.version(), Version::Version2); + assert_eq!(server.version(), Version::Version2); +} + +/// When the first packet from the client is gigantic, the server might generate acknowledgment packets in +/// version 1. Both client and server need to handle that gracefully. +#[test] +fn compatible_upgrade_large_initial() { + let params = ConnectionParameters::default().versions( + Version::Version1, + vec![Version::Version2, Version::Version1], + ); + let mut client = new_client(params.clone()); + client + .set_local_tparam( + 0x0845_de37_00ac_a5f9, + TransportParameter::Bytes(vec![0; 2048]), + ) + .unwrap(); + let mut server = new_server(params); + + // Client Initial should take 2 packets. + // Each should elicit a Version 1 ACK from the server. + let dgram = client.process_output(now()).dgram(); + assert!(dgram.is_some()); + let dgram = server.process(dgram, now()).dgram(); + assert!(dgram.is_some()); + // The following uses the Version from *outside* this crate. + assertions::assert_version(dgram.as_ref().unwrap(), Version::Version1.wire_version()); + client.process_input(dgram.unwrap(), now()); + + connect(&mut client, &mut server); + assert_eq!(client.version(), Version::Version2); + assert_eq!(server.version(), Version::Version2); + // Only handshake padding is "dropped". + assert_eq!(client.stats().dropped_rx, 1); + assert_eq!(server.stats().dropped_rx, 1); +} + +/// A server that supports versions 1 and 2 might prefer version 1 and that's OK. +/// This one starts with version 1 and stays there. +#[test] +fn compatible_no_upgrade() { + let mut client = new_client(ConnectionParameters::default().versions( + Version::Version1, + vec![Version::Version2, Version::Version1], + )); + let mut server = new_server(ConnectionParameters::default().versions( + Version::Version1, + vec![Version::Version1, Version::Version2], + )); + + connect(&mut client, &mut server); + assert_eq!(client.version(), Version::Version1); + assert_eq!(server.version(), Version::Version1); +} + +/// A server that supports versions 1 and 2 might prefer version 1 and that's OK. +/// This one starts with version 2 and downgrades to version 1. +#[test] +fn compatible_downgrade() { + let mut client = new_client(ConnectionParameters::default().versions( + Version::Version2, + vec![Version::Version2, Version::Version1], + )); + let mut server = new_server(ConnectionParameters::default().versions( + Version::Version2, + vec![Version::Version1, Version::Version2], + )); + + connect(&mut client, &mut server); + assert_eq!(client.version(), Version::Version1); + assert_eq!(server.version(), Version::Version1); +} + +/// Inject a Version Negotiation packet, which the client detects when it validates the +/// server `version_negotiation` transport parameter. +#[test] +fn version_negotiation_downgrade() { + const DOWNGRADE: Version = Version::Draft29; + + let mut client = default_client(); + // The server sets the current version in the transport parameter and + // protects Initial packets with the version in its configuration. + // When a server `Connection` is created by a `Server`, the configuration is set + // to match the version of the packet it first receives. This replicates that. + let mut server = + new_server(ConnectionParameters::default().versions(DOWNGRADE, Version::all())); + + // Start the handshake and spoof a VN packet. + let initial = client.process_output(now()).dgram().unwrap(); + let vn = create_vn(&initial, &[DOWNGRADE.wire_version()]); + let dgram = Datagram::new(addr(), addr(), vn); + client.process_input(dgram, now()); + + connect_fail( + &mut client, + &mut server, + Error::VersionNegotiation, + Error::PeerError(Error::VersionNegotiation.code()), + ); +} + +/// A server connection needs to be configured with the version that the client attempts. +/// Otherwise, it will object to the client transport parameters and not do anything. +#[test] +fn invalid_server_version() { + let mut client = + new_client(ConnectionParameters::default().versions(Version::Version1, Version::all())); + let mut server = + new_server(ConnectionParameters::default().versions(Version::Version2, Version::all())); + + let dgram = client.process_output(now()).dgram(); + server.process_input(dgram.unwrap(), now()); + + // One packet received. + assert_eq!(server.stats().packets_rx, 1); + // None dropped; the server will have decrypted it successfully. + assert_eq!(server.stats().dropped_rx, 0); + assert_eq!(server.stats().saved_datagrams, 0); + // The server effectively hasn't reacted here. + match server.state() { + State::Closed(err) => { + assert_eq!(*err, ConnectionError::Transport(Error::CryptoAlert(47))); + } + _ => panic!("invalid server state"), + } +} + +#[test] +fn invalid_current_version_client() { + const OTHER_VERSION: Version = Version::Draft29; + + let mut client = default_client(); + let mut server = default_server(); + + assert_ne!(OTHER_VERSION, client.version()); + client + .set_local_tparam( + tparams::VERSION_NEGOTIATION, + TransportParameter::Versions { + current: OTHER_VERSION.wire_version(), + other: Version::all() + .iter() + .copied() + .map(Version::wire_version) + .collect(), + }, + ) + .unwrap(); + + connect_fail( + &mut client, + &mut server, + Error::PeerError(Error::CryptoAlert(47).code()), + Error::CryptoAlert(47), + ); +} + +/// To test this, we need to disable compatible upgrade so that the server doesn't update +/// its transport parameters. Then, we can overwrite its transport parameters without +/// them being overwritten. Otherwise, it would be hard to find a window during which +/// the transport parameter can be modified. +#[test] +fn invalid_current_version_server() { + const OTHER_VERSION: Version = Version::Draft29; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default().versions(Version::default(), vec![Version::default()]), + ); + + assert!(!Version::default().is_compatible(OTHER_VERSION)); + server + .set_local_tparam( + tparams::VERSION_NEGOTIATION, + TransportParameter::Versions { + current: OTHER_VERSION.wire_version(), + other: vec![OTHER_VERSION.wire_version()], + }, + ) + .unwrap(); + + connect_fail( + &mut client, + &mut server, + Error::CryptoAlert(47), + Error::PeerError(Error::CryptoAlert(47).code()), + ); +} + +#[test] +fn no_compatible_version() { + const OTHER_VERSION: Version = Version::Draft29; + + let mut client = default_client(); + let mut server = default_server(); + + assert_ne!(OTHER_VERSION, client.version()); + client + .set_local_tparam( + tparams::VERSION_NEGOTIATION, + TransportParameter::Versions { + current: Version::default().wire_version(), + other: vec![OTHER_VERSION.wire_version()], + }, + ) + .unwrap(); + + connect_fail( + &mut client, + &mut server, + Error::PeerError(Error::CryptoAlert(47).code()), + Error::CryptoAlert(47), + ); +} + +/// When a compatible upgrade chooses a different version, 0-RTT is rejected. +#[test] +fn compatible_upgrade_0rtt_rejected() { + // This is the baseline configuration where v1 is attempted and v2 preferred. + let prefer_v2 = ConnectionParameters::default().versions( + Version::Version1, + vec![Version::Version2, Version::Version1], + ); + let mut client = new_client(prefer_v2.clone()); + // The server will start with this so that the client resumes with v1. + let just_v1 = + ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]); + let mut server = new_server(just_v1); + + connect(&mut client, &mut server); + assert_eq!(client.version(), Version::Version1); + let token = exchange_ticket(&mut client, &mut server, now()); + + // Now upgrade the server to the preferred configuration. + let mut client = new_client(prefer_v2.clone()); + let mut server = new_server(prefer_v2); + client.enable_resumption(now(), token).unwrap(); + + // Create a packet with 0-RTT from the client. + let initial = send_something(&mut client, now()); + assertions::assert_version(&initial, Version::Version1.wire_version()); + assertions::assert_coalesced_0rtt(&initial); + server.process_input(initial, now()); + assert!(!server + .events() + .any(|e| matches!(e, ConnectionEvent::NewStream { .. }))); + + // Finalize the connection. Don't use connect() because it uses + // maybe_authenticate() too liberally and that eats the events we want to check. + let dgram = server.process_output(now()).dgram(); // ServerHello flight + let dgram = client.process(dgram, now()).dgram(); // Client Finished (note: no authentication) + let dgram = server.process(dgram, now()).dgram(); // HANDSHAKE_DONE + client.process_input(dgram.unwrap(), now()); + + assert!(matches!(client.state(), State::Confirmed)); + assert!(matches!(server.state(), State::Confirmed)); + + assert!(client.events().any(|e| { + println!(" client event: {:?}", e); + matches!(e, ConnectionEvent::ZeroRttRejected) + })); + assert_eq!(client.zero_rtt_state(), ZeroRttState::Rejected); +} diff --git a/third_party/rust/neqo-transport/src/connection/tests/zerortt.rs b/third_party/rust/neqo-transport/src/connection/tests/zerortt.rs new file mode 100644 index 0000000000..8c8a980c0c --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/zerortt.rs @@ -0,0 +1,259 @@ +// 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::super::Connection; +use super::{ + connect, default_client, default_server, exchange_ticket, new_server, resumed_server, + CountingConnectionIdGenerator, +}; +use crate::events::ConnectionEvent; +use crate::{ConnectionParameters, Error, StreamType, Version}; + +use neqo_common::event::Provider; +use neqo_crypto::{AllowZeroRtt, AntiReplay}; +use std::cell::RefCell; +use std::rc::Rc; +use test_fixture::{self, assertions, now}; + +#[test] +fn zero_rtt_negotiate() { + // Note that the two servers in this test will get different anti-replay filters. + // That's OK because we aren't testing anti-replay. + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let token = exchange_ticket(&mut client, &mut server, now()); + let mut client = default_client(); + client + .enable_resumption(now(), token) + .expect("should set token"); + let mut server = resumed_server(&client); + connect(&mut client, &mut server); + assert!(client.tls_info().unwrap().early_data_accepted()); + assert!(server.tls_info().unwrap().early_data_accepted()); +} + +#[test] +fn zero_rtt_send_recv() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let token = exchange_ticket(&mut client, &mut server, now()); + let mut client = default_client(); + client + .enable_resumption(now(), token) + .expect("should set token"); + let mut server = resumed_server(&client); + + // Send ClientHello. + let client_hs = client.process(None, now()); + assert!(client_hs.as_dgram_ref().is_some()); + + // Now send a 0-RTT packet. + let client_stream_id = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_send(client_stream_id, &[1, 2, 3]).unwrap(); + let client_0rtt = client.process(None, now()); + assert!(client_0rtt.as_dgram_ref().is_some()); + // 0-RTT packets on their own shouldn't be padded to 1200. + assert!(client_0rtt.as_dgram_ref().unwrap().len() < 1200); + + let server_hs = server.process(client_hs.dgram(), now()); + assert!(server_hs.as_dgram_ref().is_some()); // ServerHello, etc... + + let all_frames = server.stats().frame_tx.all; + let ack_frames = server.stats().frame_tx.ack; + let server_process_0rtt = server.process(client_0rtt.dgram(), now()); + assert!(server_process_0rtt.as_dgram_ref().is_some()); + assert_eq!(server.stats().frame_tx.all, all_frames + 1); + assert_eq!(server.stats().frame_tx.ack, ack_frames + 1); + + let server_stream_id = server + .events() + .find_map(|evt| match evt { + ConnectionEvent::NewStream { stream_id, .. } => Some(stream_id), + _ => None, + }) + .expect("should have received a new stream event"); + assert_eq!(client_stream_id, server_stream_id.as_u64()); +} + +#[test] +fn zero_rtt_send_coalesce() { + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let token = exchange_ticket(&mut client, &mut server, now()); + let mut client = default_client(); + client + .enable_resumption(now(), token) + .expect("should set token"); + let mut server = resumed_server(&client); + + // Write 0-RTT before generating any packets. + // This should result in a datagram that coalesces Initial and 0-RTT. + let client_stream_id = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_send(client_stream_id, &[1, 2, 3]).unwrap(); + let client_0rtt = client.process(None, now()); + assert!(client_0rtt.as_dgram_ref().is_some()); + + assertions::assert_coalesced_0rtt(&client_0rtt.as_dgram_ref().unwrap()[..]); + + let server_hs = server.process(client_0rtt.dgram(), now()); + assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc... + + let server_stream_id = server + .events() + .find_map(|evt| match evt { + ConnectionEvent::NewStream { stream_id } => Some(stream_id), + _ => None, + }) + .expect("should have received a new stream event"); + assert_eq!(client_stream_id, server_stream_id.as_u64()); +} + +#[test] +fn zero_rtt_before_resumption_token() { + let mut client = default_client(); + assert!(client.stream_create(StreamType::BiDi).is_err()); +} + +#[test] +fn zero_rtt_send_reject() { + const MESSAGE: &[u8] = &[1, 2, 3]; + + let mut client = default_client(); + let mut server = default_server(); + connect(&mut client, &mut server); + + let token = exchange_ticket(&mut client, &mut server, now()); + let mut client = default_client(); + client + .enable_resumption(now(), token) + .expect("should set token"); + let mut server = Connection::new_server( + test_fixture::DEFAULT_KEYS, + test_fixture::DEFAULT_ALPN, + Rc::new(RefCell::new(CountingConnectionIdGenerator::default())), + ConnectionParameters::default().versions(client.version(), Version::all()), + ) + .unwrap(); + // Using a freshly initialized anti-replay context + // should result in the server rejecting 0-RTT. + let ar = + AntiReplay::new(now(), test_fixture::ANTI_REPLAY_WINDOW, 1, 3).expect("setup anti-replay"); + server + .server_enable_0rtt(&ar, AllowZeroRtt {}) + .expect("enable 0-RTT"); + + // Send ClientHello. + let client_hs = client.process(None, now()); + assert!(client_hs.as_dgram_ref().is_some()); + + // Write some data on the client. + let stream_id = client.stream_create(StreamType::UniDi).unwrap(); + client.stream_send(stream_id, MESSAGE).unwrap(); + let client_0rtt = client.process(None, now()); + assert!(client_0rtt.as_dgram_ref().is_some()); + + let server_hs = server.process(client_hs.dgram(), now()); + assert!(server_hs.as_dgram_ref().is_some()); // Should produce ServerHello etc... + let server_ignored = server.process(client_0rtt.dgram(), now()); + assert!(server_ignored.as_dgram_ref().is_none()); + + // The server shouldn't receive that 0-RTT data. + let recvd_stream_evt = |e| matches!(e, ConnectionEvent::NewStream { .. }); + assert!(!server.events().any(recvd_stream_evt)); + + // Client should get a rejection. + let client_fin = client.process(server_hs.dgram(), now()); + let recvd_0rtt_reject = |e| e == ConnectionEvent::ZeroRttRejected; + assert!(client.events().any(recvd_0rtt_reject)); + + // Server consume client_fin + let server_ack = server.process(client_fin.dgram(), now()); + assert!(server_ack.as_dgram_ref().is_some()); + let client_out = client.process(server_ack.dgram(), now()); + assert!(client_out.as_dgram_ref().is_none()); + + // ...and the client stream should be gone. + let res = client.stream_send(stream_id, MESSAGE); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), Error::InvalidStreamId); + + // Open a new stream and send data. StreamId should start with 0. + let stream_id_after_reject = client.stream_create(StreamType::UniDi).unwrap(); + assert_eq!(stream_id, stream_id_after_reject); + client.stream_send(stream_id_after_reject, MESSAGE).unwrap(); + let client_after_reject = client.process(None, now()).dgram(); + assert!(client_after_reject.is_some()); + + // The server should receive new stream + server.process_input(client_after_reject.unwrap(), now()); + assert!(server.events().any(recvd_stream_evt)); +} + +#[test] +fn zero_rtt_update_flow_control() { + const LOW: u64 = 3; + const HIGH: u64 = 10; + #[allow(clippy::cast_possible_truncation)] + const MESSAGE: &[u8] = &[0; HIGH as usize]; + + let mut client = default_client(); + let mut server = new_server( + ConnectionParameters::default() + .max_stream_data(StreamType::UniDi, true, LOW) + .max_stream_data(StreamType::BiDi, true, LOW), + ); + connect(&mut client, &mut server); + + let token = exchange_ticket(&mut client, &mut server, now()); + let mut client = default_client(); + client + .enable_resumption(now(), token) + .expect("should set token"); + let mut server = new_server( + ConnectionParameters::default() + .max_stream_data(StreamType::UniDi, true, HIGH) + .max_stream_data(StreamType::BiDi, true, HIGH) + .versions(client.version, Version::all()), + ); + + // Stream limits should be low for 0-RTT. + let client_hs = client.process(None, now()).dgram(); + let uni_stream = client.stream_create(StreamType::UniDi).unwrap(); + assert!(!client.stream_send_atomic(uni_stream, MESSAGE).unwrap()); + let bidi_stream = client.stream_create(StreamType::BiDi).unwrap(); + assert!(!client.stream_send_atomic(bidi_stream, MESSAGE).unwrap()); + + // Now get the server transport parameters. + let server_hs = server.process(client_hs, now()).dgram(); + client.process_input(server_hs.unwrap(), now()); + + // The streams should report a writeable event. + let mut uni_stream_event = false; + let mut bidi_stream_event = false; + for e in client.events() { + if let ConnectionEvent::SendStreamWritable { stream_id } = e { + if stream_id.is_uni() { + uni_stream_event = true; + } else { + bidi_stream_event = true; + } + } + } + assert!(uni_stream_event); + assert!(bidi_stream_event); + // But no MAX_STREAM_DATA frame was received. + assert_eq!(client.stats().frame_rx.max_stream_data, 0); + + // And the new limit applies. + assert!(client.stream_send_atomic(uni_stream, MESSAGE).unwrap()); + assert!(client.stream_send_atomic(bidi_stream, MESSAGE).unwrap()); +} |