Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
larseggert committed Jan 27, 2025
1 parent 85abfeb commit c38e4b1
Show file tree
Hide file tree
Showing 25 changed files with 566 additions and 243 deletions.
54 changes: 37 additions & 17 deletions neqo-http3/src/connection_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1624,15 +1624,21 @@ mod tests {

fn handshake_only(client: &mut Http3Client, server: &mut TestServer) -> Output {
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);

assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
_ = server.conn.process(out1.dgram(), now());
let out = server.conn.process(out2.dgram(), now());
assert_eq!(*server.conn.state(), State::Handshaking);

let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());

let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());

assert!(out.as_dgram_ref().is_none());

let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
Expand Down Expand Up @@ -4156,12 +4162,13 @@ mod tests {
#[test]
fn zero_rtt_negotiated() {
let (mut client, mut server) = start_with_0rtt();

let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());

assert_eq!(client.state(), Http3State::ZeroRtt);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
_ = server.conn.process(out1.dgram(), now());
let out = server.conn.process(out2.dgram(), now());

// Check that control and qpack streams are received and a
// SETTINGS frame has been received.
Expand All @@ -4175,6 +4182,9 @@ mod tests {

assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());

assert_eq!(client.state(), Http3State::Connected);

drop(server.conn.process(out.dgram(), now()));
Expand All @@ -4192,11 +4202,13 @@ mod tests {
make_request(&mut client, true, &[Header::new("myheaders", "myvalue")]);
assert_eq!(request_stream_id, 0);

let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());

assert_eq!(client.state(), Http3State::ZeroRtt);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
_ = server.conn.process(out1.dgram(), now());
let out = server.conn.process(out2.dgram(), now());

// Check that control and qpack streams are received and a
// SETTINGS frame has been received.
Expand All @@ -4210,6 +4222,8 @@ mod tests {

assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
assert_eq!(client.state(), Http3State::Connected);
let out = server.conn.process(out.dgram(), now());
assert!(server.conn.state().connected());
Expand Down Expand Up @@ -4269,28 +4283,30 @@ mod tests {
let zerortt_event = |e| matches!(e, Http3ClientEvent::StateChange(Http3State::ZeroRtt));
assert!(client.events().any(zerortt_event));

// Send ClientHello.
let client_hs = client.process_output(now());
assert!(client_hs.as_dgram_ref().is_some());

// Create a request
let request_stream_id = make_request(&mut client, false, &[]);
assert_eq!(request_stream_id, 0);

// Send ClientHello.
let client_hs = client.process_output(now());
assert!(client_hs.as_dgram_ref().is_some());

let client_0rtt = client.process_output(now());
assert!(client_0rtt.as_dgram_ref().is_some());

let server_hs = server.process(client_hs.dgram(), now());
_ = server.process(client_hs.dgram(), now());
let server_hs = server.process(client_0rtt.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());

let dgram = client.process(server_hs.dgram(), now()).dgram();
let dgram = server.process(dgram, now());

// 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_out = client.process(server_hs.dgram(), now());
let client_out = client.process(dgram.dgram(), now());
assert!(client_out.as_dgram_ref().is_some());
let recvd_0rtt_reject = |e| e == Http3ClientEvent::ZeroRttRejected;
assert!(client.events().any(recvd_0rtt_reject));
Expand Down Expand Up @@ -4328,11 +4344,13 @@ mod tests {
.enable_resumption(now(), &token)
.expect("Set resumption token");
assert_eq!(client.state(), Http3State::ZeroRtt);
let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());

assert_eq!(client.state(), Http3State::ZeroRtt);
assert_eq!(*server.conn.state(), State::Init);
let out = server.conn.process(out.dgram(), now());
_ = server.conn.process(out1.dgram(), now());
let out = server.conn.process(out2.dgram(), now());

// Check that control and qpack streams and a SETTINGS frame are received.
// Also qpack encoder stream will send "change capacity" instruction because it has
Expand All @@ -4345,6 +4363,8 @@ mod tests {

assert_eq!(*server.conn.state(), State::Handshaking);
let out = client.process(out.dgram(), now());
let out = server.conn.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
assert_eq!(client.state(), Http3State::Connected);

drop(server.conn.process(out.dgram(), now()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
// Perform only QUIC transport handshake.
fn connect_with(client: &mut Http3Client, server: &mut Http3Server) {
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);

_ = server.process(out1.dgram(), now());
let out = server.process(out2.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
Expand Down
6 changes: 5 additions & 1 deletion neqo-http3/src/frames/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ pub fn enc_dec<T: FrameDecoder<T>>(d: &Encoder, st: &str, remaining: usize) -> T

let mut conn_c = default_client();
let mut conn_s = default_server();
let out = conn_c.process_output(now());
let out1 = conn_c.process_output(now());
let out2 = conn_c.process_output(now());
_ = conn_s.process(out1.dgram(), now());
let out = conn_s.process(out2.dgram(), now());
let out = conn_c.process(out.dgram(), now());
let out = conn_s.process(out.dgram(), now());
let out = conn_c.process(out.dgram(), now());
drop(conn_s.process(out.dgram(), now()));
Expand Down
9 changes: 6 additions & 3 deletions neqo-http3/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,12 @@ mod tests {

fn connect_transport(server: &mut Http3Server, client: &mut Connection, resume: bool) {
let c1 = client.process_output(now());
let s1 = server.process(c1.dgram(), now());
let c11 = client.process_output(now());
_ = server.process(c1.dgram(), now());
let s1 = server.process(c11.dgram(), now());
let c2 = client.process(s1.dgram(), now());
let s2 = server.process(c2.dgram(), now());
let c2 = client.process(s2.dgram(), now());
let needs_auth = client
.events()
.any(|e| e == ConnectionEvent::AuthenticationNeeded);
Expand All @@ -430,8 +434,7 @@ mod tests {
assert!(client.state().connected());
let s2 = server.process(c2.dgram(), now());
assert_connected(server);
let c3 = client.process(s2.dgram(), now());
assert!(c3.dgram().is_none());
_ = client.process(s2.dgram(), now());
}

// Start a client/server and check setting frame.
Expand Down
27 changes: 21 additions & 6 deletions neqo-http3/tests/httpconn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,13 @@ fn process_client_events(conn: &mut Http3Client) {

fn connect_peers(hconn_c: &mut Http3Client, hconn_s: &mut Http3Server) -> Option<Datagram> {
assert_eq!(hconn_c.state(), Http3State::Initializing);
let out = hconn_c.process_output(now()); // Initial
let out = hconn_s.process(out.dgram(), now()); // Initial + Handshake
let out = hconn_c.process(out.dgram(), now()); // ACK
let out1 = hconn_c.process_output(now()); // Initial
let out2 = hconn_c.process_output(now()); // Initial
_ = hconn_s.process(out1.dgram(), now()); // ACK
let out = hconn_s.process(out2.dgram(), now()); // Initial + Handshake
let out = hconn_c.process(out.dgram(), now());
let out = hconn_s.process(out.dgram(), now());
let out = hconn_c.process(out.dgram(), now());
drop(hconn_s.process(out.dgram(), now())); // consume ACK
let authentication_needed = |e| matches!(e, Http3ClientEvent::AuthenticationNeeded);
assert!(hconn_c.events().any(authentication_needed));
Expand All @@ -118,9 +122,15 @@ fn connect_peers_with_network_propagation_delay(
let net_delay = Duration::from_millis(net_delay);
assert_eq!(hconn_c.state(), Http3State::Initializing);
let mut now = now();
let out = hconn_c.process_output(now); // Initial
let out1 = hconn_c.process_output(now); // Initial
let out2 = hconn_c.process_output(now); // Initial
now += net_delay;
_ = hconn_s.process(out1.dgram(), now); // ACK
let out = hconn_s.process(out2.dgram(), now);
now += net_delay;
let out = hconn_s.process(out.dgram(), now); // Initial + Handshake
let out = hconn_c.process(out.dgram(), now);
now += net_delay;
let out = hconn_s.process(out.dgram(), now);
now += net_delay;
let out = hconn_c.process(out.dgram(), now); // ACK
now += net_delay;
Expand Down Expand Up @@ -445,7 +455,12 @@ fn zerortt() {
.unwrap();
hconn_c.stream_close_send(req).unwrap();

let out = hconn_c.process(dgram, now());
let out1 = hconn_c.process(dgram, now());
let out2 = hconn_c.process_output(now());
_ = hconn_s.process(out1.dgram(), now());
let out = hconn_s.process(out2.dgram(), now());

let out = hconn_c.process(out.dgram(), now());
let out = hconn_s.process(out.dgram(), now());

let mut request_stream = None;
Expand Down
6 changes: 5 additions & 1 deletion neqo-http3/tests/priority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ fn exchange_packets(client: &mut Http3Client, server: &mut Http3Server) {
// Perform only QUIC transport handshake.
fn connect_with(client: &mut Http3Client, server: &mut Http3Server) {
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);

_ = server.process(out1.dgram(), now());
let out = server.process(out2.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
Expand Down
6 changes: 5 additions & 1 deletion neqo-http3/tests/webtransport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ fn connect() -> (Http3Client, Http3Server) {
)
.expect("create a server");
assert_eq!(client.state(), Http3State::Initializing);
let out = client.process_output(now());
let out1 = client.process_output(now());
let out2 = client.process_output(now());
assert_eq!(client.state(), Http3State::Initializing);

_ = server.process(out1.dgram(), now());
let out = server.process(out2.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
let out = client.process(out.dgram(), now());
let out = server.process(out.dgram(), now());
Expand Down
61 changes: 41 additions & 20 deletions neqo-transport/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ impl Connection {
let tphandler = Rc::new(RefCell::new(tps));
let crypto = Crypto::new(
conn_params.get_versions().initial(),
&conn_params,
agent,
protocols.iter().map(P::as_ref).map(String::from).collect(),
Rc::clone(&tphandler),
Expand Down Expand Up @@ -915,7 +916,7 @@ impl Connection {
// error. This may happen when client_start fails.
self.set_state(State::Closed(error), now);
}
State::WaitInitial => {
State::WaitInitial | State::WaitVersion => {
// 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()) {
Expand Down Expand Up @@ -1448,6 +1449,7 @@ impl Connection {
{
self.crypto.resend_unacked(PacketNumberSpace::Initial);
self.resend_0rtt(now);
qdebug!("XXX resend done");
}
}
(PacketType::VersionNegotiation | PacketType::Retry | PacketType::OtherVersion, ..) => {
Expand Down Expand Up @@ -1520,6 +1522,26 @@ impl Connection {
self.start_handshake(path, packet, now);
}

if matches!(self.state, State::WaitInitial | State::WaitVersion) {
self.set_state(
if self.has_version() {
State::Handshaking
} else {
State::WaitVersion
},
now,
);
if self.role == Role::Server {
self.zero_rtt_state =
if self.crypto.enable_0rtt(self.version, self.role) == Ok(true) {
qdebug!("[{self}] Accepted 0-RTT");
ZeroRttState::AcceptedServer
} else {
ZeroRttState::Rejected
};
}
}

if self.state.connected() {
self.handle_migration(path, d, migrate, now);
} else if self.role != Role::Client
Expand Down Expand Up @@ -1817,34 +1839,25 @@ impl Connection {
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 {
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 = if self.crypto.enable_0rtt(self.version, self.role) == Ok(true) {
qdebug!("[{self}] Accepted 0-RTT");
ZeroRttState::AcceptedServer
} else {
qtrace!("[{self}] Rejected 0-RTT");
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());
};
}

fn has_version(&self) -> bool {
if self.role == Role::Server {
// The server knows the final version if it has remote transport parameters.
self.tps.borrow().remote.is_some()
} else {
// 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, now);
} else {
self.set_state(State::WaitVersion, now);
}
}

Expand Down Expand Up @@ -2483,7 +2496,7 @@ impl Connection {
if padded {
needs_padding = false;
self.loss_recovery.on_packet_sent(path, sent, now);
} else if pt == PacketType::Initial && (self.role == Role::Client || ack_eliciting) {
} else if pt == PacketType::Initial {
// 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);
Expand All @@ -2503,6 +2516,14 @@ impl Connection {
// but wait until after sending an ACK.
self.discard_keys(PacketNumberSpace::Handshake, now);
}

// If the client has more Initial CRYPTO data queued up, do not coalesce.
if self.role == Role::Client
&& *space == PacketNumberSpace::Initial
&& !self.crypto.streams.is_empty(*space)
{
break;
}
}

if encoder.is_empty() {
Expand All @@ -2512,7 +2533,7 @@ impl Connection {
// 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 {
if needs_padding && packets.len() < profile.limit() {
qdebug!(
"[{self}] pad Initial from {} to PLPMTU {}",
packets.len(),
Expand Down
Loading

0 comments on commit c38e4b1

Please sign in to comment.