From 972dad0a4a1aaf18ce247968dec7e024777086cf Mon Sep 17 00:00:00 2001 From: Sterling Deng Date: Sun, 8 Sep 2024 00:21:41 -0700 Subject: [PATCH 1/4] Add transport parameters for recv timestamps Adds the 3 transport parameters specified in the receiver timestamp implementation. --- quinn-proto/src/frame.rs | 2 ++ quinn-proto/src/transport_parameters.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/quinn-proto/src/frame.rs b/quinn-proto/src/frame.rs index d58cb36e5..d85b6bb2a 100644 --- a/quinn-proto/src/frame.rs +++ b/quinn-proto/src/frame.rs @@ -133,6 +133,8 @@ frame_types! { ACK_FREQUENCY = 0xaf, IMMEDIATE_ACK = 0x1f, // DATAGRAM + // Custom frame for https://www.ietf.org/archive/id/draft-smith-quic-receive-ts-00.html + ACK_RECEIVE_TIMESTAMPS = 0x40, } const STREAM_TYS: RangeInclusive = RangeInclusive::new(0x08, 0x0f); diff --git a/quinn-proto/src/transport_parameters.rs b/quinn-proto/src/transport_parameters.rs index c09c41f97..5d5752994 100644 --- a/quinn-proto/src/transport_parameters.rs +++ b/quinn-proto/src/transport_parameters.rs @@ -59,6 +59,15 @@ macro_rules! apply_params { max_ack_delay(0x000b) = 25, /// Maximum number of connection IDs from the peer that an endpoint is willing to store active_connection_id_limit(0x000e) = 2, + /// The below 2 fields are for the implementation of + /// https://www.ietf.org/archive/id/draft-smith-quic-receive-ts-00.html#name-extension-negotiation + /// + /// The transport parameter values selected are placeholders, using the first 2 reserved values specified + /// in https://www.rfc-editor.org/rfc/rfc9000#section-22.3 + max_recv_timestamps_per_ack(0x00f0) = 0, + recv_timestamps_exponent(0x00f1) = 0, + receive_timestamp_basis(0x00f2) = 0, + } }; } @@ -490,6 +499,9 @@ mod test { initial_max_streams_bidi: 16u32.into(), initial_max_streams_uni: 16u32.into(), ack_delay_exponent: 2u32.into(), + recv_timestamps_exponent: 3u32.into(), + max_recv_timestamps_per_ack: 5u32.into(), + receive_timestamp_basis: 3u32.into(), max_udp_payload_size: 1200u32.into(), preferred_address: Some(PreferredAddress { address_v4: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 42)), From 016b122a3e279e5dc14d518b903879796d84ecb1 Mon Sep 17 00:00:00 2001 From: Sterling Deng Date: Sun, 8 Sep 2024 00:36:58 -0700 Subject: [PATCH 2/4] ReceivedTimestamp impl Impl of ReceivedTimestamps struct that holds a monotonically increasing vector of packets and their received time. --- quinn-proto/src/connection/spaces.rs | 55 +++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/quinn-proto/src/connection/spaces.rs b/quinn-proto/src/connection/spaces.rs index 3f999c886..798adb5e4 100644 --- a/quinn-proto/src/connection/spaces.rs +++ b/quinn-proto/src/connection/spaces.rs @@ -2,8 +2,8 @@ use std::{ cmp, collections::{BTreeMap, VecDeque}, mem, - ops::{Bound, Index, IndexMut}, - time::{Duration, Instant}, + ops::{Bound, Index, IndexMut, Range}, + time::{Duration, Instant, SystemTime}, }; use rand::Rng; @@ -553,6 +553,50 @@ impl SendableFrames { } } +pub struct ReceiverTimestamps(VecDeque<(u64, Instant)>); + +impl std::fmt::Debug for ReceiverTimestamps { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // todo + Ok(()) + } +} + +impl ReceiverTimestamps { + pub fn new() -> Self { + ReceiverTimestamps(VecDeque::new()) + } + + pub fn add(&mut self, packet_number: u64, t: Instant) -> Result<(), &str> { + if let Some(v) = self.0.back() { + if packet_number <= v.0 { + return Err("out of order packets are unsupported"); + } + } + self.0.push_back((packet_number, t)); + Ok(()) + } + + fn clear(&mut self) { + self.0.clear() + } + + pub fn encode_iter(&mut self) {} + + // why do we need '_ + pub fn iter(&self) -> impl DoubleEndedIterator + '_ { + self.0.iter().cloned() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn inner(&self) -> &VecDeque<(u64, Instant)> { + &self.0 + } +} + #[derive(Debug)] pub(super) struct PendingAcks { /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet @@ -587,6 +631,8 @@ pub(super) struct PendingAcks { largest_ack_eliciting_packet: Option, /// The largest acknowledged packet number sent in an ACK frame largest_acked: Option, + + received_timestamps: ReceiverTimestamps, } impl PendingAcks { @@ -602,6 +648,7 @@ impl PendingAcks { largest_packet: None, largest_ack_eliciting_packet: None, largest_acked: None, + received_timestamps: ReceiverTimestamps::new(), } } @@ -755,6 +802,10 @@ impl PendingAcks { &self.ranges } + pub(super) fn received_timestamps(&self) -> &ReceiverTimestamps { + &self.received_timestamps + } + /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been /// acknowledged /// From f814f4cece547f8c82bf0199bd0a14196465a0ab Mon Sep 17 00:00:00 2001 From: Sterling Deng Date: Sun, 8 Sep 2024 00:41:31 -0700 Subject: [PATCH 3/4] Encode and Decode Initial implementation of encoding and decoding from wire format. --- quinn-proto/src/connection/mod.rs | 29 +- .../src/connection/receiver_timestamps.rs | 17 + quinn-proto/src/frame.rs | 384 +++++++++++++++++- 3 files changed, 421 insertions(+), 9 deletions(-) create mode 100644 quinn-proto/src/connection/receiver_timestamps.rs diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index 2c0f92b55..3fc76406e 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -65,12 +65,15 @@ use paths::{PathData, PathResponses}; mod send_buffer; -mod spaces; -#[cfg(fuzzing)] -pub use spaces::Retransmits; +mod receiver_timestamps; +use receiver_timestamps::ReceiverTimestampConfig; + +pub mod spaces; #[cfg(not(fuzzing))] use spaces::Retransmits; use spaces::{PacketNumberFilter, PacketSpace, SendableFrames, SentPacket, ThinRetransmits}; +#[cfg(fuzzing)] +pub use spaces::{ReceivedTimestamps, Retransmits}; mod stats; pub use stats::{ConnectionStats, FrameStats, PathStats, UdpStats}; @@ -227,6 +230,11 @@ pub struct Connection { /// no outgoing application data. app_limited: bool, + // + // Ack Receive Timestamps + // + receiver_timestamp_cfg: Option, + streams: StreamsState, /// Surplus remote CIDs for future use on new paths rem_cids: CidQueue, @@ -275,6 +283,7 @@ impl Connection { }); let mut rng = StdRng::from_seed(rng_seed); let mut this = Self { + receiver_timestamp_cfg: None, endpoint_config, server_config, crypto, @@ -817,6 +826,7 @@ impl Connection { &mut self.spaces[space_id], buf, &mut self.stats, + None, ); } @@ -3047,6 +3057,7 @@ impl Connection { space, buf, &mut self.stats, + self.receiver_timestamp_cfg.as_ref(), ); } @@ -3231,6 +3242,7 @@ impl Connection { space: &mut PacketSpace, buf: &mut Vec, stats: &mut ConnectionStats, + timestamp_config: Option<&ReceiverTimestampConfig>, ) { debug_assert!(!space.pending_acks.ranges().is_empty()); @@ -3255,7 +3267,16 @@ impl Connection { delay_micros ); - frame::Ack::encode(delay as _, space.pending_acks.ranges(), ecn, buf); + frame::Ack::encode( + delay as _, + space.pending_acks.ranges(), + ecn, + Some(space.pending_acks.received_timestamps()), + timestamp_config.as_ref().map(|cfg| cfg.basis), + timestamp_config.as_ref().map(|cfg| cfg.exponent), + timestamp_config.as_ref().map(|cfg| cfg.instant_basis), + buf, + ); stats.frame_tx.acks += 1; } diff --git a/quinn-proto/src/connection/receiver_timestamps.rs b/quinn-proto/src/connection/receiver_timestamps.rs new file mode 100644 index 000000000..08e902d99 --- /dev/null +++ b/quinn-proto/src/connection/receiver_timestamps.rs @@ -0,0 +1,17 @@ +use std::time::Instant; + +pub(crate) struct ReceiverTimestampConfig { + pub exponent: u64, + pub basis: u64, + pub instant_basis: Instant, +} + +impl ReceiverTimestampConfig { + fn new(basis: u64, exponent: u64, instant_basis: Instant) -> Self { + ReceiverTimestampConfig { + exponent, + basis, + instant_basis, + } + } +} diff --git a/quinn-proto/src/frame.rs b/quinn-proto/src/frame.rs index d85b6bb2a..bfa37da02 100644 --- a/quinn-proto/src/frame.rs +++ b/quinn-proto/src/frame.rs @@ -1,7 +1,9 @@ +use core::time::Duration; use std::{ fmt::{self, Write}, io, mem, - ops::{Range, RangeInclusive}, + ops::{Range, RangeInclusive, Shr}, + time::Instant, }; use bytes::{Buf, BufMut, Bytes}; @@ -9,6 +11,7 @@ use tinyvec::TinyVec; use crate::{ coding::{self, BufExt, BufMutExt, UnexpectedEnd}, + connection, range_set::ArrayRangeSet, shared::{ConnectionId, EcnCodepoint}, Dir, ResetToken, StreamId, TransportError, TransportErrorCode, VarInt, MAX_CID_SIZE, @@ -345,6 +348,8 @@ pub struct Ack { pub delay: u64, pub additional: Bytes, pub ecn: Option, + // hide behind FF + pub timestamps: Option, } impl fmt::Debug for Ack { @@ -383,8 +388,12 @@ impl Ack { delay: u64, ranges: &ArrayRangeSet, ecn: Option<&EcnCounts>, + timestamps: Option<&connection::spaces::ReceiverTimestamps>, + timestamp_basis: Option, + timestamp_exponent: Option, + timestamp_instant_basis: Option, buf: &mut W, - ) { + ) -> Result<(), TransportError> { let mut rest = ranges.iter().rev(); let first = rest.next().unwrap(); let largest = first.end - 1; @@ -406,13 +415,186 @@ impl Ack { prev = block.start; } if let Some(x) = ecn { - x.encode(buf) + x.encode(buf); + } + + if let (Some(ts), Some(basis), Some(exponent), Some(instant_basis)) = ( + timestamps, + timestamp_basis, + timestamp_exponent, + timestamp_instant_basis, + ) { + Self::encode_timestamps(&ts, largest, buf, basis, exponent, instant_basis); + } + // validate that ecn && timestamp_ranges are not possible + // validate timestamp_ranges.len() == timestamps.len(); + // if let Some(x) = timestamp_ranges {} + Ok(()) + + // error if both ecn && timestamp_ranges are Some + } + + // https://www.ietf.org/archive/id/draft-smith-quic-receive-ts-00.html#ts-ranges + fn encode_timestamps( + timestamps: &connection::spaces::ReceiverTimestamps, + mut largest: u64, + buf: &mut W, + receive_timestamp_basis: u64, + timestamp_exponent: u64, + mut timestamp_instant_basis: Instant, + ) { + // iterates from largest number to smallest + let mut prev: Option = None; + + // segment_idx tracks the positions in `timestamps` in which a gap occurs. + let mut segment_idxs = Vec::::new(); + for (i, (pn, _)) in timestamps.iter().rev().enumerate() { + if let Some(prev) = prev { + if pn + 1 != prev { + segment_idxs.push(timestamps.len() - i); + } + } + prev = Some(pn); + } + segment_idxs.push(0); + // Timestamp Range Count + buf.write_var(segment_idxs.len() as u64); + + { + let mut right = timestamps.len(); + let mut first = true; + + for segment_idx in segment_idxs { + // *Gap + // For the first Timestamp Range: Gap is the difference between (a) the Largest Acknowledged packet number + // in the frame and (b) the largest packet in the current (first) Timestamp Range. + let gap = if first { + largest - timestamps.inner().get(right - 1).unwrap().0 + } else { + largest - 2 - timestamps.inner().get(right - 1).unwrap().0 + }; + buf.write_var(gap); + // *Timestamp Delta Count + buf.write_var((right - segment_idx) as u64); + // *Timestamp Deltas + for (pn, recv_time) in timestamps.inner().range(segment_idx..right).rev() { + let delta: u64 = if first { + first = false; + // For the first Timestamp Delta of the first Timestamp Range in the frame: the value + // is the difference between (a) the receive timestamp of the largest packet in the + // Timestamp Range (indicated by Gap) and (b) the session receive_timestamp_basis + receive_timestamp_basis + + recv_time + .duration_since(timestamp_instant_basis) + .as_micros() as u64 + } else { + // For all other Timestamp Deltas: the value is the difference between + // (a) the receive timestamp specified by the previous Timestamp Delta and + // (b) the receive timestamp of the current packet in the Timestamp Range, decoded as described below. + timestamp_instant_basis + .duration_since(*recv_time) + .as_micros() as u64 + }; + buf.write_var(delta.shr(timestamp_exponent).try_into().unwrap()); + timestamp_instant_basis = *recv_time; + largest = *pn; + } + + right = segment_idx; + } } } pub fn iter(&self) -> AckIter<'_> { self.into_iter() } + + pub fn decode_timestamp( + &self, + basis: (u64, Instant), + exponent: u64, + ) -> Option { + if let Some(ref v) = self.timestamps { + Some(AckTimestampDecoder::new( + self.largest, + basis.0, + basis.1, + exponent, + &v[..], + )) + } else { + None + } + } +} + +pub struct AckTimestampDecoder<'a> { + timestamp_basis: u64, + timestamp_exponent: u64, + timestamp_instant_basis: Instant, + data: &'a [u8], + + deltas_remaining: usize, + first: bool, + next_pn: u64, +} + +impl<'a> AckTimestampDecoder<'a> { + fn new( + largest: u64, + basis: u64, + basis_instant: Instant, + exponent: u64, + mut data: &'a [u8], + ) -> Self { + // We read and throw away the Timestamp Range Count value because + // it was already used to properly slice the data. + let _ = data.get_var().unwrap(); + AckTimestampDecoder { + timestamp_basis: basis, + timestamp_exponent: exponent, + timestamp_instant_basis: basis_instant, + data, + deltas_remaining: 0, + first: true, + next_pn: largest, + } + } +} + +impl<'a> Iterator for AckTimestampDecoder<'a> { + type Item = (u64, Instant); + fn next(&mut self) -> Option { + if !self.data.has_remaining() { + return None; + } + if self.deltas_remaining == 0 { + let gap = self.data.get_var().unwrap(); + self.deltas_remaining = self.data.get_var().unwrap() as usize; + if self.first { + self.next_pn -= gap; + } else { + self.next_pn -= gap + 2; + } + } else { + self.next_pn -= 1; + } + + let delta = self.data.get_var().unwrap(); + self.deltas_remaining -= 1; + + if self.first { + self.timestamp_basis += delta << self.timestamp_exponent; + self.first = false; + } else { + self.timestamp_basis -= delta << self.timestamp_exponent; + } + + Some(( + self.next_pn, + self.timestamp_instant_basis + Duration::from_micros(self.timestamp_basis), + )) + } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -621,7 +803,7 @@ impl Iter { Type::RETIRE_CONNECTION_ID => Frame::RetireConnectionId { sequence: self.bytes.get_var()?, }, - Type::ACK | Type::ACK_ECN => { + Type::ACK | Type::ACK_ECN | Type::ACK_RECEIVE_TIMESTAMPS => { let largest = self.bytes.get_var()?; let delay = self.bytes.get_var()?; let extra_blocks = self.bytes.get_var()? as usize; @@ -641,6 +823,15 @@ impl Iter { ce: self.bytes.get_var()?, }) }, + timestamps: if ty != Type::ACK_RECEIVE_TIMESTAMPS { + None + } else { + let ts_start = end; + let ts_range_count = self.bytes.get_var()? as usize; + scan_ack_timestamp_blocks(&mut self.bytes, largest, ts_range_count)?; + let ts_end = self.bytes.position() as usize; + Some(self.bytes.get_ref().slice(ts_start..ts_end)) + }, }) } Type::PATH_CHALLENGE => Frame::PathChallenge(self.bytes.get()?), @@ -769,6 +960,28 @@ fn scan_ack_blocks(buf: &mut io::Cursor, largest: u64, n: usize) -> Resul Ok(()) } +fn scan_ack_timestamp_blocks( + buf: &mut io::Cursor, + largest: u64, + n: usize, +) -> Result<(), IterErr> { + // timestamp range count + let first_block = buf.get_var()?; + let mut smallest = largest.checked_sub(first_block).ok_or(IterErr::Malformed)?; + for _ in 0..n { + let gap = buf.get_var()?; + smallest = smallest.checked_sub(gap + 2).ok_or(IterErr::Malformed)?; + let timestamp_delta_count = buf.get_var()?; + smallest = smallest + .checked_sub(timestamp_delta_count) + .ok_or(IterErr::Malformed)?; + for _ in 0..timestamp_delta_count { + buf.get_var()?; + } + } + Ok(()) +} + enum IterErr { UnexpectedEnd, InvalidFrameId, @@ -957,7 +1170,7 @@ mod test { ect1: 24, ce: 12, }; - Ack::encode(42, &ranges, Some(&ECN), &mut buf); + Ack::encode(42, &ranges, Some(&ECN), None, None, None, None, &mut buf).unwrap(); let frames = frames(buf); assert_eq!(frames.len(), 1); match frames[0] { @@ -997,4 +1210,165 @@ mod test { assert_eq!(frames.len(), 1); assert_matches!(&frames[0], Frame::ImmediateAck); } + + mod ack_timestamp_tests { + use super::*; + + #[test] + fn timestamp_iter() { + let mut timestamps = connection::spaces::ReceiverTimestamps::new(); + let second = Duration::from_secs(1); + let t0 = Instant::now(); + timestamps.add(1, t0 + second).unwrap(); + timestamps.add(2, t0 + second * 2).unwrap(); + timestamps.add(3, t0 + second * 3).unwrap(); + let mut buf = bytes::BytesMut::new(); + + Ack::encode_timestamps(×tamps, 12, &mut buf, 0, 0, t0); + + // Manually decode and assert the values in the buffer. + assert_eq!(1, buf.get_var().unwrap()); // timestamp_range_count + assert_eq!(9, buf.get_var().unwrap()); // gap: 12-3 + assert_eq!(3, buf.get_var().unwrap()); // timestamp delta count + assert_eq!(3_000_000, buf.get_var().unwrap()); // timestamp delta: 3_000_000 μs = 3 seconds = diff between largest timestamp and basis + assert_eq!(1_000_000, buf.get_var().unwrap()); // timestamp delta: 1 second diff + assert_eq!(1_000_000, buf.get_var().unwrap()); // timestamp delta: 1 second diff + assert!(buf.get_var().is_err()); + } + + #[test] + fn timestamp_iter_with_gaps() { + let mut timestamps = connection::spaces::ReceiverTimestamps::new(); + let one_second = Duration::from_secs(1); + let t0 = Instant::now(); + vec![(1..=3), (5..=5), (10..=12)] + .into_iter() + .flatten() + .for_each(|i| timestamps.add(i, t0 + one_second * i as u32).unwrap()); + + let mut buf = bytes::BytesMut::new(); + + Ack::encode_timestamps(×tamps, 12, &mut buf, 0, 0, t0); + // Manually decode and assert the values in the buffer. + assert_eq!(3, buf.get_var().unwrap()); // timestamp_range_count + // + assert_eq!(0, buf.get_var().unwrap()); // gap: 12 - 12 = 0 + assert_eq!(3, buf.get_var().unwrap()); // timestamp_delta_count + assert_eq!(12_000_000, buf.get_var().unwrap()); // delta: 3_000_000 μs = 3 seconds = diff between largest timestamp and basis + assert_eq!(1_000_000, buf.get_var().unwrap()); // delta: 1 second diff + assert_eq!(1_000_000, buf.get_var().unwrap()); // delta: 1 second diff + // + assert_eq!(3, buf.get_var().unwrap()); // gap: 10 - 2 - 5 = 3 + assert_eq!(1, buf.get_var().unwrap()); // timestamp_delta_count + assert_eq!(5_000_000, buf.get_var().unwrap()); // delta: 1 second diff + + assert_eq!(0, buf.get_var().unwrap()); // gap + assert_eq!(3, buf.get_var().unwrap()); // timestamp_delta_count + assert_eq!(2_000_000, buf.get_var().unwrap()); // delta: 2 second diff + assert_eq!(1_000_000, buf.get_var().unwrap()); // delta: 1 second diff + assert_eq!(1_000_000, buf.get_var().unwrap()); // delta: 1 second diff + + // end + assert!(buf.get_var().is_err()); + } + + #[test] + fn timestamp_iter_with_basis() { + let mut timestamps = connection::spaces::ReceiverTimestamps::new(); + let one_second = Duration::from_secs(1); + let t0 = Instant::now(); + timestamps.add(1, t0 + one_second).unwrap(); + timestamps.add(2, t0 + one_second * 2).unwrap(); + let mut buf = bytes::BytesMut::new(); + + let basis: u64 = 32; + + Ack::encode_timestamps(×tamps, 12, &mut buf, basis, 0, t0); + + // values below are tested in another unit test + buf.get_var().unwrap(); // timestamp_range_count + buf.get_var().unwrap(); // gap + buf.get_var().unwrap(); // timestamp_delta_count + assert_eq!(basis + 2_000_000, buf.get_var().unwrap()); // 1 second diff + assert_eq!(1_000_000, buf.get_var().unwrap()); // 1 second diff + assert!(buf.get_var().is_err()); + } + + #[test] + fn timestamp_iter_with_exponent() { + let mut timestamps = connection::spaces::ReceiverTimestamps::new(); + let millisecond = Duration::from_millis(1); + let t0 = Instant::now(); + timestamps.add(1, t0 + millisecond * 200).unwrap(); + timestamps.add(2, t0 + millisecond * 300).unwrap(); + let mut buf = bytes::BytesMut::new(); + + let exponent = 2; + Ack::encode_timestamps(×tamps, 12, &mut buf, 0, exponent, t0); + + // values below are tested in another unit test + buf.get_var().unwrap(); // timestamp_range_count + buf.get_var().unwrap(); // gap + buf.get_var().unwrap(); // timestamp_delta_count + assert_eq!(300_000 >> exponent, buf.get_var().unwrap()); // 300ms diff + assert_eq!(100_000 >> exponent, buf.get_var().unwrap()); // 100ms diff + assert!(buf.get_var().is_err()); + } + + #[test] + fn timestamp_encode_decode() { + let mut timestamps = connection::spaces::ReceiverTimestamps::new(); + let one_second = Duration::from_secs(1); + let t0 = Instant::now(); + timestamps.add(1, t0 + one_second).unwrap(); + timestamps.add(2, t0 + one_second * 2).unwrap(); + timestamps.add(3, t0 + one_second * 3).unwrap(); + + let mut buf = bytes::BytesMut::new(); + + Ack::encode_timestamps(×tamps, 12, &mut buf, 0, 0, t0); + + let decoder = AckTimestampDecoder::new(12, 0, t0, 0, &buf); + + let got: Vec<_> = decoder.collect(); + // [(3, _), (2, _), (1, _)] + assert_eq!(3, got.len()); + assert_eq!(3, got[0].0); + assert_eq!(t0 + (3 * one_second), got[0].1,); + + assert_eq!(2, got[1].0); + assert_eq!(t0 + (2 * one_second), got[1].1,); + + assert_eq!(1, got[2].0); + assert_eq!(t0 + (1 * one_second), got[2].1,); + } + + #[test] + fn timestamp_encode_decode_with_gaps() { + let mut timestamps = connection::spaces::ReceiverTimestamps::new(); + let one_second = Duration::from_secs(1); + let t0 = Instant::now(); + let expect: Vec<_> = vec![(1..=3), (5..=5), (10..=12)] + .into_iter() + .flatten() + .collect::>() + .into_iter() + .map(|i| { + let t = t0 + one_second * i as u32; + timestamps.add(i, t).unwrap(); + (i, t) + }) + .collect(); + + let mut buf = bytes::BytesMut::new(); + + Ack::encode_timestamps(×tamps, 12, &mut buf, 0, 0, t0); + + let decoder = AckTimestampDecoder::new(12, 0, t0, 0, &buf); + let got: Vec<_> = decoder.collect(); + + assert_eq!(7, got.len()); + assert_eq!(expect, got.into_iter().rev().collect::>()); + } + } } From 937f17f045bb6596c977468d275ee817ff90a3ee Mon Sep 17 00:00:00 2001 From: Sterling Deng Date: Sun, 8 Sep 2024 08:35:48 -0700 Subject: [PATCH 4/4] impl Debug for ReceiveTimestamps --- quinn-proto/src/connection/spaces.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/quinn-proto/src/connection/spaces.rs b/quinn-proto/src/connection/spaces.rs index 798adb5e4..cbeb3ec86 100644 --- a/quinn-proto/src/connection/spaces.rs +++ b/quinn-proto/src/connection/spaces.rs @@ -557,8 +557,11 @@ pub struct ReceiverTimestamps(VecDeque<(u64, Instant)>); impl std::fmt::Debug for ReceiverTimestamps { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // todo - Ok(()) + let mut l = f.debug_list(); + self.iter().for_each(|v| { + l.entry(&v); + }); + l.finish() } }