diff --git a/quinn-proto/src/connection.rs b/quinn-proto/src/connection.rs index 47d40190d..434f60139 100644 --- a/quinn-proto/src/connection.rs +++ b/quinn-proto/src/connection.rs @@ -71,6 +71,8 @@ pub struct Connection { events: VecDeque, /// Number of local connection IDs that have been issued in NEW_CONNECTION_ID frames. cids_issued: u64, + /// Outgoing spin bit state + spin: bool, // // Loss Detection @@ -233,6 +235,7 @@ impl Connection { io: IoQueue::new(), events: VecDeque::new(), cids_issued: 0, + spin: false, crypto_count: 0, pto_count: 0, @@ -733,6 +736,7 @@ impl Connection { now: u64, ecn: Option, packet: Option, + spin: bool, ) { self.reset_idle_timeout(now); self.receiving_ecn |= ecn.is_some(); @@ -753,6 +757,8 @@ impl Connection { if packet > self.rx_packet { self.rx_packet = packet; self.rx_packet_time = now; + // Update outgoing spin bit, inverting iff we're the client + self.spin = self.side.is_client() ^ spin; } } @@ -850,7 +856,7 @@ impl Connection { &mut io::Cursor::new(self.tls.get_quic_transport_parameters().unwrap()), )?; self.set_params(params)?; - self.on_packet_authenticated(now, ecn, Some(packet_number)); + self.on_packet_authenticated(now, ecn, Some(packet_number), false); self.write_tls(); Ok(()) } @@ -960,7 +966,12 @@ impl Connection { } } else { if !was_closed { - self.on_packet_authenticated(now, ecn, number); + let spin = if let Header::Short { spin, .. } = packet.header { + spin + } else { + false + }; + self.on_packet_authenticated(now, ecn, number, spin); } self.handle_connected_inner(now, number, packet) } @@ -1638,6 +1649,7 @@ impl Connection { let header = Header::Short { dst_cid: self.rem_cid, number: PacketNumber::new(number, self.largest_acked_packet), + spin: self.spin, key_phase: self.key_phase, }; //} @@ -1940,6 +1952,7 @@ impl Connection { let header = Header::Short { dst_cid: self.rem_cid, number: PacketNumber::new(number, self.largest_acked_packet), + spin: self.spin, key_phase: self.key_phase, }; let partial_encode = header.encode(&mut buf); @@ -1980,6 +1993,7 @@ impl Connection { CryptoLevel::OneRtt => Header::Short { dst_cid: self.rem_cid, number, + spin: self.spin, key_phase: self.key_phase, }, CryptoLevel::Initial => Header::Long { diff --git a/quinn-proto/src/packet.rs b/quinn-proto/src/packet.rs index 9ae5ba041..b999cde26 100644 --- a/quinn-proto/src/packet.rs +++ b/quinn-proto/src/packet.rs @@ -93,12 +93,14 @@ impl PartialDecode { let sample_offset = 1 + dst_cid.len() + 4; let number = Self::decrypt_header(&mut buf, header_crypto, sample_offset)?; + let spin = buf.get_ref()[0] & SPIN_BIT != 0; let key_phase = buf.get_ref()[0] & KEY_PHASE_BIT != 0; ( buf.remaining(), Header::Short { dst_cid, number, + spin, key_phase, }, false, @@ -279,6 +281,7 @@ pub enum Header { Short { dst_cid: ConnectionId, number: PacketNumber, + spin: bool, key_phase: bool, }, VersionNegotiate { @@ -350,9 +353,15 @@ impl Header { Short { ref dst_cid, number, + spin, key_phase, } => { - w.write(FIXED_BIT | if key_phase { KEY_PHASE_BIT } else { 0 } | number.tag()); + w.write( + FIXED_BIT + | if key_phase { KEY_PHASE_BIT } else { 0 } + | if spin { SPIN_BIT } else { 0 } + | number.tag(), + ); w.put_slice(dst_cid); number.encode(w); PartialEncode { @@ -814,10 +823,11 @@ pub fn set_payload_length(packet: &mut [u8], header_len: usize, pn_len: usize) { pub const AEAD_TAG_SIZE: usize = 16; pub const LONG_HEADER_FORM: u8 = 0x80; -const KEY_PHASE_BIT: u8 = 0x04; const FIXED_BIT: u8 = 0x40; +pub const SPIN_BIT: u8 = 0x20; pub const SHORT_RESERVED_BITS: u8 = 0x18; pub const LONG_RESERVED_BITS: u8 = 0x0c; +const KEY_PHASE_BIT: u8 = 0x04; /// Explicit congestion notification codepoint #[repr(u8)] diff --git a/quinn-proto/src/tests.rs b/quinn-proto/src/tests.rs index e8417efa9..313a33176 100644 --- a/quinn-proto/src/tests.rs +++ b/quinn-proto/src/tests.rs @@ -66,6 +66,9 @@ struct Pair { time: u64, // One-way latency: u64, + /// Number of spin bit flips + spins: u64, + last_spin: bool, } impl Default for Pair { @@ -148,6 +151,8 @@ impl Pair { client: TestEndpoint::new(Side::Client, client, client_addr), time: 0, latency: 0, + spins: 0, + last_spin: false, } } @@ -191,6 +196,11 @@ impl Pair { trace!(self.log, "client running"); self.client.drive(&self.log, self.time, self.server.addr); for (ecn, packet) in self.client.outbound.drain(..) { + if packet[0] & packet::LONG_HEADER_FORM == 0 { + let spin = packet[0] & packet::SPIN_BIT != 0; + self.spins += (spin == self.last_spin) as u64; + self.last_spin = spin; + } if let Some(ref socket) = self.client.socket { socket.send_to(&packet, self.server.addr).unwrap(); } @@ -441,6 +451,7 @@ fn lifecycle() { info!(pair.log, "closing"); pair.client.close(pair.time, client_conn, 42, REASON.into()); pair.drive(); + assert!(pair.spins > 0); assert_matches!(pair.server.poll(), Some((_, Event::ConnectionLost { reason: ConnectionError::ApplicationClosed { reason: ApplicationClose { error_code: 42, ref reason }