Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions noq-proto/proptest-regressions/tests/proptests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ cc 91184c7b6b718961d2dc03365f02098a18ac0035ca85b95654fbafa430d93664 # shrinks to
cc ec5baef3027436b012a332a97d46814ed157f259337c3f651bbb7a3233bd9c7f # shrinks to input = _RandomInteractionWithMultipathSimpleRoutingArgs { seed: [121, 74, 209, 215, 123, 149, 7, 227, 67, 200, 91, 12, 216, 81, 208, 77, 83, 181, 39, 2, 207, 186, 233, 211, 254, 178, 230, 22, 100, 197, 215, 43], interactions: [Drive { side: Client }, ClosePath { side: Client, path_idx: 0, error_code: 0 }] }
cc b1429b84bf576bb9000e8d0d6d53cff4c93efacf033c9df898aeb9856d1b03fe # shrinks to input = _RandomInteractionWithMultipathSimpleRoutingArgs { seed: [159, 14, 107, 252, 130, 4, 190, 131, 86, 208, 127, 29, 140, 30, 55, 65, 242, 192, 2, 158, 40, 51, 110, 116, 46, 139, 156, 165, 64, 109, 33, 62], interactions: [PassiveMigration { side: Server, addr_idx: 0 }, OpenPath { side: Client, status: Available, addr_idx: 0 }] }
cc 4f717acb71d562f33601dfc8c7fbcca89b13c8259676a20ffc764a92e3ea07a1 # shrinks to input = _RandomInteractionWithMultipathComplexRoutingArgs { seed: [84, 97, 201, 172, 244, 139, 252, 60, 222, 107, 135, 245, 103, 45, 188, 138, 26, 198, 1, 97, 144, 22, 42, 228, 19, 154, 45, 135, 222, 137, 231, 16], interactions: [PassiveMigration { side: Server, addr_idx: 0 }, OpenPath { side: Client, status: Available, addr_idx: 0 }, ClosePath { side: Client, path_idx: 0, error_code: 0 }, PathSetStatus { side: Server, path_idx: 0, status: Backup }], routes: RoutingTable { client_routes: [([::ffff:1.1.1.0]:44433, 0)], server_routes: [([::ffff:2.2.2.0]:4433, 0)] } }
cc 829f66ceb9a2b64e08c089d76137a622a3209cb11a97a8c834b1f41b095b2a63 # shrinks to input = _MonkeyInteractionArgs { setup: MultipathSimpleRouting(Zeroes), side: Client, interactions: [Monkey([Frame(PathAck(PathAck { path_id: PathId(1), largest: 1, delay: 0, ranges: [1..2], ecn: None }))]), Normal(OpenPath { side: Client, status: Available, addr_idx: 0 })] }
cc 2f43de3c3cd460004fac95bd65ee18ec93bac6fca6f15bb7218cc044b0c5ff26 # shrinks to input = _MonkeyInteractionArgs { setup: MultipathSimpleRouting(Zeroes), side: Client, interactions: [Normal(PathSetStatus { side: Client, path_idx: 0, status: Backup }), Normal(Drive { side: Client }), Normal(Drive { side: Client }), Normal(OpenPath { side: Client, status: Available, addr_idx: 0 }), Normal(AdvanceTime), Monkey([Bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), Bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])])] }
99 changes: 99 additions & 0 deletions noq-proto/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ pub struct Connection {
/// State for n0's (<https://n0.computer>) nat traversal protocol.
n0_nat_traversal: n0_nat_traversal::State,
qlog: QlogSink,

/// A test-only frame queue for injecting valid, but potentially malicious packets.
///
/// This is used in proptests to ensure that if another side behaves in a protocol-
/// incompliant way, we avoid panicking.
#[cfg(test)]
pending_data_to_inject: VecDeque<DataToInject>,
}

impl Connection {
Expand Down Expand Up @@ -432,6 +439,9 @@ impl Connection {

n0_nat_traversal: Default::default(),
qlog,

#[cfg(test)]
pending_data_to_inject: Default::default(),
};
if path_validated {
this.on_path_validated(PathId::ZERO);
Expand Down Expand Up @@ -1681,6 +1691,12 @@ impl Connection {
};
}

#[cfg(test)]
if !self.populate_injected_packet_data(path_id, &mut builder) {
self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder);
}

#[cfg(not(test))]
self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder);

// ACK-only packets should only be sent when explicitly allowed. If we write them due to
Expand Down Expand Up @@ -2166,9 +2182,29 @@ impl Connection {

can_send.close = connection_close_pending && space_has_crypto;

#[cfg(test)]
{
can_send |= self.can_send_data_to_inject(path_id);
}

can_send
}

#[cfg(test)]
fn can_send_data_to_inject(&self, _path_id: PathId) -> SendableFrames {
SendableFrames {
acks: self.pending_data_to_inject.iter().any(|frame| {
matches!(
frame,
DataToInject::Frame(Frame::Ack(_)) | DataToInject::Frame(Frame::PathAck(_))
)
}),
other: !self.pending_data_to_inject.is_empty(),
close: false,
space_specific: false,
}
}

/// Process `ConnectionEvent`s generated by the associated `Endpoint`
///
/// Will execute protocol logic upon receipt of a connection event, in turn preparing signals
Expand Down Expand Up @@ -6359,6 +6395,53 @@ impl Connection {
}
}

#[cfg(test)]
fn populate_injected_packet_data<'a, 'b>(
&mut self,
path_id: PathId,
builder: &mut PacketBuilder<'a, 'b>,
) -> bool {
let stats = &mut self.path_stats.for_path(path_id).frame_tx;

let mut injected_test_packet = false;

while let Some(inject) = self.pending_data_to_inject.pop_front() {
use crate::coding::Encodable;

match inject {
DataToInject::Frame(frame) => {
let Some(enc_frame) = frame.to_encodable_frame() else {
continue;
};
let mut encode_for_size = Vec::new();
enc_frame.encode(&mut encode_for_size);
let size = encode_for_size.len();

if builder.frame_space_remaining() < size {
self.pending_data_to_inject
.push_front(DataToInject::Frame(frame));
break;
} else {
injected_test_packet = true;
builder.inject_test_frame(enc_frame, stats);
}
}
DataToInject::Bytes(bytes) => {
if builder.frame_space_remaining() < bytes.len() {
self.pending_data_to_inject
.push_front(DataToInject::Bytes(bytes));
break;
} else {
injected_test_packet = true;
builder.inject_test_bytes(bytes);
}
}
}
}

injected_test_packet
}

/// Write pending ACKs into a buffer
fn populate_acks<'a, 'b>(
now: Instant,
Expand Down Expand Up @@ -6745,6 +6828,14 @@ impl Connection {
}
}

/// Injects arbitrary data into the next packet for test purposes.
///
/// This data can be structured frames or arbitrary bytes.
#[cfg(test)]
pub(crate) fn test_inject_data(&mut self, data: impl IntoIterator<Item = DataToInject>) {
self.pending_data_to_inject.extend(data);
}

/// Whether we have on-path 1-RTT data to send.
///
/// This checks for frames that can only be sent in the data space (1-RTT):
Expand Down Expand Up @@ -7657,6 +7748,14 @@ fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Du
}
}

#[cfg(test)]
#[derive(derive_more::Debug, test_strategy::Arbitrary)]
pub(crate) enum DataToInject {
Frame(Frame),
#[debug("Bytes({_0:02X?})")]
Bytes(#[strategy(proptest::collection::vec(proptest::prelude::any::<u8>(), 1..1500))] Vec<u8>),
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
28 changes: 28 additions & 0 deletions noq-proto/src/connection/packet_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,34 @@ impl<'a, 'b> PacketBuilder<'a, 'b> {
self.sent_frames.record_sent_frame(frame);
}

/// Writes/injects a test frame into this packet.
///
/// Exactly like [`Self::write_frame`], but
/// - logs with "(injected frame)" as the log message
/// - doesn't record the frame in `sent_frames` to avoid triggering retransmission and
/// unwanted processing of ACKs that were intentionally invalid.
#[cfg(test)]
pub(super) fn inject_test_frame<'c>(
&mut self,
frame: EncodableFrame<'c>,
stats: &mut FrameStats,
) {
frame.encode(&mut self.frame_space_mut());
stats.record(frame.get_type());
self.qlog.record(&frame);
trace!(%frame, "(test-injected frame)");
}

/// Inserts arbitrary bytes into this packet for testing purposes.
#[cfg(test)]
pub(super) fn inject_test_bytes(&mut self, bytes: Vec<u8>) {
use std::io::Write;

let len = bytes.len();
let res = self.frame_space_mut().writer().write_all(&bytes);
trace!(len, ?res, "(test-injected data)");
}

/// Returns a writable buffer limited to the remaining frame space
///
/// The [`BufMut::remaining_mut`] call on the returned buffer indicates the amount of
Expand Down
101 changes: 92 additions & 9 deletions noq-proto/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,70 @@ impl Frame {
})
)
}

#[cfg(test)]
pub(crate) fn to_encodable_frame(&self) -> Option<EncodableFrame<'_>> {
match self {
Frame::Padding => None,
Frame::Ping => Some(EncodableFrame::Ping(Ping)),
Frame::Ack(ack) => Some(EncodableFrame::Ack(ack.as_encoder())),
Frame::PathAck(path_ack) => Some(EncodableFrame::PathAck(path_ack.as_encoder())),
Frame::ResetStream(reset_stream) => Some(EncodableFrame::ResetStream(*reset_stream)),
Frame::StopSending(stop_sending) => Some(EncodableFrame::StopSending(*stop_sending)),
Frame::Crypto(crypto) => Some(EncodableFrame::Crypto(crypto.clone())),
Frame::NewToken(new_token) => Some(EncodableFrame::NewToken(new_token.clone())),
Frame::Stream(_stream) => None,
Frame::MaxData(max_data) => Some(EncodableFrame::MaxData(*max_data)),
Frame::MaxStreamData(max_stream_data) => {
Some(EncodableFrame::MaxStreamData(*max_stream_data))
}
Frame::MaxStreams(max_streams) => Some(EncodableFrame::MaxStreams(*max_streams)),
Frame::DataBlocked(_data_blocked) => None,
Frame::StreamDataBlocked(_stream_data_blocked) => None,
Frame::StreamsBlocked(_streams_blocked) => None,
Frame::NewConnectionId(new_connection_id) => {
Some(EncodableFrame::NewConnectionId(*new_connection_id))
}
Frame::RetireConnectionId(retire_connection_id) => {
Some(EncodableFrame::RetireConnectionId(*retire_connection_id))
}
Frame::PathChallenge(path_challenge) => {
Some(EncodableFrame::PathChallenge(*path_challenge))
}
Frame::PathResponse(path_response) => {
Some(EncodableFrame::PathResponse(*path_response))
}
Frame::Close(close) => Some(EncodableFrame::Close(close.encoder(1000))),
Frame::Datagram(datagram) => Some(EncodableFrame::Datagram(datagram.clone())),
Frame::AckFrequency(ack_frequency) => {
Some(EncodableFrame::AckFrequency(*ack_frequency))
}
Frame::ImmediateAck => Some(EncodableFrame::ImmediateAck(ImmediateAck)),
Frame::HandshakeDone => Some(EncodableFrame::HandshakeDone(HandshakeDone)),
Frame::ObservedAddr(observed_addr) => {
Some(EncodableFrame::ObservedAddr(*observed_addr))
}
Frame::PathAbandon(path_abandon) => Some(EncodableFrame::PathAbandon(*path_abandon)),
Frame::PathStatusAvailable(path_status_available) => {
Some(EncodableFrame::PathStatusAvailable(*path_status_available))
}
Frame::PathStatusBackup(path_status_backup) => {
Some(EncodableFrame::PathStatusBackup(*path_status_backup))
}
Frame::MaxPathId(max_path_id) => Some(EncodableFrame::MaxPathId(*max_path_id)),
Frame::PathsBlocked(paths_blocked) => {
Some(EncodableFrame::PathsBlocked(*paths_blocked))
}
Frame::PathCidsBlocked(path_cids_blocked) => {
Some(EncodableFrame::PathCidsBlocked(*path_cids_blocked))
}
Frame::AddAddress(add_address) => Some(EncodableFrame::AddAddress(*add_address)),
Frame::ReachOut(reach_out) => Some(EncodableFrame::ReachOut(*reach_out)),
Frame::RemoveAddress(remove_address) => {
Some(EncodableFrame::RemoveAddress(*remove_address))
}
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
Expand Down Expand Up @@ -751,7 +815,7 @@ impl Encodable for MaxStreams {
}
}

#[derive(Debug, PartialEq, Eq, derive_more::Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary))]
#[display("{} {} seq: {sequence}", self.get_type(), DisplayOption::new("path_id", path_id.as_ref()))]
pub(crate) struct RetireConnectionId {
Expand Down Expand Up @@ -1052,6 +1116,16 @@ impl PathAck {
ecn,
}
}

#[cfg(test)]
fn as_encoder(&self) -> PathAckEncoder<'_> {
PathAckEncoder {
path_id: self.path_id,
delay: self.delay,
ranges: &self.ranges,
ecn: self.ecn.as_ref(),
}
}
}

#[derive(derive_more::Display)]
Expand Down Expand Up @@ -1155,6 +1229,15 @@ impl Ack {
AckEncoder { delay, ranges, ecn }
}

#[cfg(test)]
fn as_encoder(&self) -> AckEncoder<'_> {
AckEncoder {
delay: self.delay,
ranges: &self.ranges,
ecn: self.ecn.as_ref(),
}
}

pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = Range<u64>> + '_ {
self.ranges.iter()
}
Expand Down Expand Up @@ -1402,7 +1485,7 @@ impl NewToken {
}
}

#[derive(Debug, Clone, derive_more::Display)]
#[derive(Debug, Clone, Copy, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))]
#[display("MAX_PATH_ID path_id: {_0}")]
pub(crate) struct MaxPathId(pub(crate) PathId);
Expand All @@ -1429,7 +1512,7 @@ impl Encodable for MaxPathId {
}
}

#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary))]
#[display("PATHS_BLOCKED remote_max_path_id: {_0}")]
pub(crate) struct PathsBlocked(pub(crate) PathId);
Expand Down Expand Up @@ -1457,7 +1540,7 @@ impl Decodable for PathsBlocked {
}
}

#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary))]
#[display("PATH_CIDS_BLOCKED path_id: {path_id} next_seq: {next_seq}")]
pub(crate) struct PathCidsBlocked {
Expand Down Expand Up @@ -2099,7 +2182,7 @@ impl Encodable for AckFrequency {

/// Conjunction of the information contained in the address discovery frames
/// ([`FrameType::ObservedIpv4Addr`], [`FrameType::ObservedIpv6Addr`]).
#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, derive_more::Display)]
#[display("{} seq_no: {seq_no} addr: {}", self.get_type(), self.socket_addr())]
#[cfg_attr(test, derive(Arbitrary))]
pub(crate) struct ObservedAddr {
Expand Down Expand Up @@ -2177,7 +2260,7 @@ impl Encodable for ObservedAddr {

/* Multipath <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/> */

#[derive(Debug, PartialEq, Eq, derive_more::Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary))]
#[display("PATH_ABANDON path_id: {path_id}")]
pub(crate) struct PathAbandon {
Expand Down Expand Up @@ -2210,7 +2293,7 @@ impl Decodable for PathAbandon {
}
}

#[derive(Debug, PartialEq, Eq, derive_more::Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary))]
#[display("PATH_STATUS_AVAILABLE path_id: {path_id} seq_no: {status_seq_no}")]
pub(crate) struct PathStatusAvailable {
Expand Down Expand Up @@ -2244,7 +2327,7 @@ impl Decodable for PathStatusAvailable {
}
}

#[derive(Debug, PartialEq, Eq, derive_more::Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary))]
#[display("PATH_STATUS_BACKUP path_id: {path_id} seq_no: {status_seq_no}")]
pub(crate) struct PathStatusBackup {
Expand Down Expand Up @@ -2370,7 +2453,7 @@ impl Encodable for AddAddress {

/// Conjunction of the information contained in the reach out frames
/// ([`FrameType::ReachOutAtIpv4`], [`FrameType::ReachOutAtIpv6`])
#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, derive_more::Display)]
#[display("REACH_OUT round: {round} local_addr: {}", self.socket_addr())]
#[cfg_attr(test, derive(Arbitrary))]
pub(crate) struct ReachOut {
Expand Down
Loading
Loading