// Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::{ mem, time::{Duration, Instant}, }; use neqo_common::{qtrace, Encoder}; use test_fixture::{self, now, split_datagram}; use super::{ super::{Connection, ConnectionParameters, IdleTimeout, Output, State}, 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, stats::FrameStats, stream_id::{StreamId, StreamType}, tparams::{self, TransportParameter}, tracking::PacketNumberSpace, }; 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.as_ref(), 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.as_ref(), 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_some()); // the server will want to ACK, we can drop that. // 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() { const FUDGE: 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 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. // Note that it is important that this not result in the RTT increasing above 0. // Otherwise, the eventual timeout will be extended (and we're not testing that). now += Duration::from_secs(10); let out = client.process(None, now); server.process_input(&out.dgram().unwrap(), now); assert_eq!(server.stream_send(stream, b"world").unwrap(), 5); let out = server.process_output(now); assert_ne!(out.as_dgram_ref(), None); mem::drop(client.process(out.as_dgram_ref(), now)); assert!(matches!(client.state(), State::Confirmed)); // Add a little less than the idle timeout and we're still connected. now += default_timeout() - FUDGE; mem::drop(client.process(None, now)); assert!(matches!(client.state(), State::Confirmed)); now += FUDGE; mem::drop(client.process(None, now)); 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.as_ref(), 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.as_ref(), 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(); _ = 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. _ = responder.stream_send(stream, DEFAULT_STREAM_DATA).unwrap(); let resp1 = responder.process_output(now).dgram(); _ = 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.as_ref(), now).dgram(); let out = server.process(out.as_ref(), now).dgram(); assert!(client.process(out.as_ref(), 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.as_ref(), now).dgram(); now += Duration::from_millis(20); let out = server.process(out.as_ref(), now).dgram(); assert!(client.process(out.as_ref(), 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(); _ = 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. _ = 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(_))); }