summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-transport/src/connection
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/neqo-transport/src/connection
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/rust/neqo-transport/src/connection/idle.rs111
-rw-r--r--third_party/rust/neqo-transport/src/connection/mod.rs3124
-rw-r--r--third_party/rust/neqo-transport/src/connection/params.rs348
-rw-r--r--third_party/rust/neqo-transport/src/connection/saved.rs68
-rw-r--r--third_party/rust/neqo-transport/src/connection/state.rs279
-rw-r--r--third_party/rust/neqo-transport/src/connection/test_internal.rs13
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/ackrate.rs182
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/cc.rs429
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/close.rs206
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/datagram.rs534
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs43
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/handshake.rs1129
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/idle.rs737
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/keys.rs340
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/migration.rs941
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/mod.rs566
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/priority.rs401
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/recovery.rs810
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/resumption.rs246
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/stream.rs964
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/vn.rs486
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/zerortt.rs259
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());
+}