Skip to main content

bitfold_peer/peer/
mod.rs

1use std::{
2    collections::HashMap,
3    fmt,
4    net::SocketAddr,
5    time::{Duration, Instant},
6};
7
8use bitfold_core::{config::Config, packet_pool::PacketAllocator};
9use bitfold_protocol::{command::ProtocolCommand, AcknowledgmentHandler, SentPacket};
10
11use super::{
12    bandwidth_throttle::BandwidthThrottle,
13    channel_state::ChannelState,
14    command_queue::CommandQueue,
15    flow_control::FlowControl,
16    fragment_buffer::{cleanup_stale_fragments, CommandFragmentBuffer},
17    peer_state::PeerState,
18    pmtu_discovery::PmtuDiscovery,
19    statistics::PeerStatistics,
20    unsequenced::UnsequencedState,
21};
22
23mod command_processor;
24mod encoder;
25mod fragmenter;
26
27/// Represents a remote peer in the network.
28/// Tracks network quality, processes packets, and manages connection state.
29pub struct Peer {
30    /// Last time we received a packet from this client
31    pub last_heard: Instant,
32    /// Last time we sent a packet to this client
33    pub last_sent: Instant,
34    /// The address of the remote endpoint
35    pub remote_address: SocketAddr,
36
37    /// Current connection state
38    state: PeerState,
39
40    // Connection handshake tracking
41    /// Assigned peer ID (from server)
42    peer_id: u16,
43    /// Incoming session ID (from remote peer's perspective)
44    incoming_session_id: u16,
45    /// Outgoing session ID (our perspective)
46    outgoing_session_id: u16,
47    /// Connect ID for replay protection
48    connect_id: u32,
49
50    /// Outgoing reliable packet sequence number
51    #[allow(dead_code)]
52    outgoing_reliable_sequence: u16,
53    /// Outgoing unreliable packet sequence number
54    #[allow(dead_code)]
55    outgoing_unreliable_sequence: u16,
56    /// Incoming reliable packet sequence number
57    #[allow(dead_code)]
58    incoming_reliable_sequence: u16,
59    /// Incoming unreliable packet sequence number
60    #[allow(dead_code)]
61    incoming_unreliable_sequence: u16,
62    /// Sequence number for unreliable fragment reassembly (increments for each fragmented unreliable packet)
63    next_unreliable_sequence: u16,
64
65    /// Unsequenced packet duplicate detection state
66    unsequenced_state: UnsequencedState,
67
68    /// Handler for reliable delivery acknowledgments and congestion control
69    acknowledge_handler: AcknowledgmentHandler,
70
71    /// Configuration parameters for this peer
72    config: Config,
73
74    /// Command queue for batching protocol commands
75    command_queue: CommandQueue,
76    /// Total bytes of packet data waiting in the command queue
77    total_waiting_data: usize,
78    /// Fragment reassembly buffer for command-based fragments (indexed by sequence number)
79    command_fragments: HashMap<u16, CommandFragmentBuffer>,
80    /// Per-channel ordering/sequencing state
81    channel_states: HashMap<u8, ChannelState>,
82
83    // Window-based flow control
84    /// Flow control state for reliable data transmission
85    flow_control: FlowControl,
86
87    // Bandwidth throttling
88    /// Bandwidth tracking and limiting
89    bandwidth_throttle: BandwidthThrottle,
90
91    // Statistics tracking
92    /// Comprehensive statistics for this peer
93    statistics: PeerStatistics,
94
95    /// Scratch buffer pool for encoding/compression to reduce heap allocations
96    tx_pool: PacketAllocator,
97    /// Compression output buffer pool for reducing compression allocations
98    compression_pool: bitfold_core::packet_pool::CompressionBufferPool,
99
100    /// Path MTU discovery manager
101    pmtu: PmtuDiscovery,
102}
103
104impl Peer {
105    /// Creates and returns a new peer for the provided socket address.
106    pub fn new(addr: SocketAddr, config: &Config, time: Instant) -> Peer {
107        use rand::Rng;
108        let mut rng = rand::rng();
109
110        Peer {
111            last_heard: time,
112            last_sent: time,
113            remote_address: addr,
114            state: PeerState::Idle,
115            peer_id: 0, // Will be assigned during handshake
116            incoming_session_id: 0,
117            outgoing_session_id: rng.random(), // Random session ID for security
118            connect_id: rng.random(),          // Random connect ID for replay protection
119            outgoing_reliable_sequence: 0,
120            outgoing_unreliable_sequence: 0,
121            incoming_reliable_sequence: 0,
122            incoming_unreliable_sequence: 0,
123            next_unreliable_sequence: 0,
124            unsequenced_state: UnsequencedState::new(),
125            acknowledge_handler: {
126                let mut handler = AcknowledgmentHandler::new();
127                // Configure advanced throttling if enabled
128                if config.use_advanced_throttling {
129                    handler.congestion_mut().enable_advanced_throttling(
130                        config.throttle_scale,
131                        config.throttle_acceleration,
132                        config.throttle_deceleration,
133                        config.throttle_interval,
134                    );
135                }
136                handler
137            },
138            config: config.to_owned(),
139            command_queue: CommandQueue::default(),
140            total_waiting_data: 0,
141            command_fragments: HashMap::new(),
142            channel_states: HashMap::new(),
143            flow_control: FlowControl::new(config),
144            bandwidth_throttle: BandwidthThrottle::new(
145                config.outgoing_bandwidth_limit,
146                config.incoming_bandwidth_limit,
147                time,
148            ),
149            statistics: PeerStatistics::default(),
150            tx_pool: PacketAllocator::new(config.max_packet_size, 256),
151            compression_pool: bitfold_core::packet_pool::CompressionBufferPool::default(),
152            pmtu: PmtuDiscovery::new(config, time),
153        }
154    }
155
156    /// Records that this connection has sent a packet. Returns whether the connection has
157    /// become acknowledged because of this send.
158    pub fn record_send(&mut self) -> bool {
159        let was_est = self.is_established();
160
161        // Update state based on send
162        match self.state {
163            PeerState::Idle => {
164                if self.config.use_connection_handshake {
165                    // Initiate formal 3-way handshake
166                    self.initiate_connect();
167                } else {
168                    // Simple implicit connection (backward compatible)
169                    self.state = PeerState::Connecting;
170                }
171            }
172            PeerState::ConnectionSucceeded => {
173                // Client completing handshake with implicit ACK
174                self.state = PeerState::Connected;
175            }
176            _ => {}
177        }
178
179        !was_est && self.is_established()
180    }
181
182    /// Records that this connection has received a packet. Returns whether the connection has
183    /// become acknowledged because of this receive.
184    pub fn record_recv(&mut self) -> bool {
185        let was_est = self.is_established();
186
187        // Update state based on receive
188        match self.state {
189            PeerState::Idle => {
190                if !self.config.use_connection_handshake {
191                    // Simple implicit connection (backward compatible)
192                    // Server receiving packet from unknown client
193                    self.state = PeerState::Connected;
194                }
195                // Otherwise, formal handshake via Connect command will handle state
196            }
197            PeerState::Connecting => {
198                if !self.config.use_connection_handshake {
199                    // Simple implicit connection (backward compatible)
200                    self.state = PeerState::Connected;
201                }
202                // Otherwise, formal handshake via VerifyConnect command will handle state
203            }
204            PeerState::AcknowledgingConnect => {
205                // Server received ACK (any data packet) from client
206                self.state = PeerState::Connected;
207            }
208            _ => {}
209        }
210
211        !was_est && self.is_established()
212    }
213
214    /// Returns if the connection has been established
215    pub fn is_established(&self) -> bool {
216        self.state.is_established()
217    }
218
219    /// Returns the current peer state
220    pub fn state(&self) -> PeerState {
221        self.state
222    }
223
224    /// Initiates graceful disconnect
225    pub fn disconnect(&mut self) {
226        if !self.state.is_disconnecting() {
227            self.state = PeerState::Disconnecting;
228            self.command_queue.enqueue(ProtocolCommand::Disconnect { reason: 0 });
229        }
230    }
231
232    // ===== Connection Handshake (3-way) =====
233
234    /// Initiates a connection handshake by sending CONNECT command (step 1 of 3).
235    /// Should be called when transitioning from Idle to Connecting.
236    pub fn initiate_connect(&mut self) {
237        if self.state == PeerState::Idle {
238            self.state = PeerState::Connecting;
239            let connect_command = ProtocolCommand::Connect {
240                channels: self.config.channel_count,
241                mtu: 1400,           // Default MTU
242                protocol_version: 1, // Protocol version
243                outgoing_session_id: self.outgoing_session_id,
244                connect_id: self.connect_id,
245            };
246            self.command_queue.enqueue(connect_command);
247        }
248    }
249
250    /// Returns the current number of not yet acknowledged packets
251    pub fn packets_in_flight(&self) -> u16 {
252        self.acknowledge_handler.packets_in_flight()
253    }
254
255    /// Returns a [Duration] representing the interval since we last heard from the client
256    pub fn last_heard(&self, time: Instant) -> Duration {
257        time.duration_since(self.last_heard)
258    }
259
260    /// Returns a [Duration] representing the interval since we last sent to the client
261    pub fn last_sent(&self, time: Instant) -> Duration {
262        time.duration_since(self.last_sent)
263    }
264
265    /// Returns the current round-trip time for this connection.
266    pub fn rtt(&self) -> Duration {
267        self.acknowledge_handler.rtt()
268    }
269
270    /// Returns the retransmission timeout for this connection.
271    pub fn rto(&self) -> Duration {
272        self.acknowledge_handler.rto()
273    }
274
275    /// Returns the current packet loss rate (0.0 to 1.0).
276    pub fn loss_rate(&self) -> f32 {
277        self.acknowledge_handler.loss_rate()
278    }
279
280    /// Returns the current congestion throttle value (0.0 to 1.0).
281    pub fn throttle(&self) -> f32 {
282        self.acknowledge_handler.throttle()
283    }
284
285    /// Updates the congestion throttle based on current network conditions.
286    pub fn update_throttle(&mut self, time: Instant) -> bool {
287        self.acknowledge_handler.update_throttle(time)
288    }
289
290    /// Returns the configuration for this peer.
291    pub fn config(&self) -> &Config {
292        &self.config
293    }
294
295    /// Gathers dropped packets from the acknowledgment handler.
296    pub fn gather_dropped_packets(&mut self) -> Vec<SentPacket> {
297        let dropped = self.acknowledge_handler.dropped_packets();
298
299        // Track packet loss
300        for _ in &dropped {
301            self.record_packet_lost();
302        }
303
304        dropped
305    }
306
307    // ===== Statistics =====
308
309    /// Returns a reference to the peer's statistics.
310    pub fn statistics(&self) -> &PeerStatistics {
311        &self.statistics
312    }
313
314    /// Returns a mutable reference to the peer's statistics.
315    pub fn statistics_mut(&mut self) -> &mut PeerStatistics {
316        &mut self.statistics
317    }
318
319    /// Records a packet being sent.
320    fn record_packet_sent(&mut self) {
321        self.statistics.packets_sent += 1;
322    }
323
324    /// Records a packet being received.
325    fn record_packet_received(&mut self) {
326        self.statistics.packets_received += 1;
327    }
328
329    /// Records a packet being lost.
330    fn record_packet_lost(&mut self) {
331        self.statistics.packets_lost += 1;
332    }
333
334    /// Records data bytes being sent (payload only, not protocol overhead).
335    fn record_data_sent(&mut self, bytes: usize) {
336        self.statistics.bytes_sent += bytes as u64;
337    }
338
339    /// Records data bytes being received (payload only, not protocol overhead).
340    fn record_data_received(&mut self, bytes: usize) {
341        self.statistics.bytes_received += bytes as u64;
342    }
343
344    /// Returns current per-peer fragment size in bytes.
345    pub fn current_fragment_size(&self) -> u16 {
346        self.pmtu.current_fragment_size()
347    }
348
349    /// Handles PMTU probing state machine (enqueue probes, process timeouts).
350    pub fn handle_pmtu(&mut self, time: Instant) {
351        let rto = self.rto();
352        if let Some(probe_cmd) = self.pmtu.handle_pmtu(time, rto) {
353            self.enqueue_command(probe_cmd);
354        }
355    }
356
357    // ===== Window-based Flow Control =====
358
359    /// Returns the current window size (in packets).
360    pub fn window_size(&self) -> u32 {
361        self.flow_control.window_size()
362    }
363
364    /// Returns the amount of reliable data currently in transit (in bytes).
365    pub fn reliable_data_in_transit(&self) -> u32 {
366        self.flow_control.reliable_data_in_transit()
367    }
368
369    /// Sets the window size (for negotiation during handshake).
370    pub fn set_window_size(&mut self, window_size: u32) {
371        self.flow_control.set_window_size(&self.config, window_size);
372    }
373
374    /// Records reliable data being sent (adds to in-transit counter).
375    pub fn record_reliable_data_sent(&mut self, data_size: u32) {
376        self.flow_control.record_reliable_data_sent(data_size);
377    }
378
379    /// Records reliable data being acknowledged (removes from in-transit counter).
380    pub fn record_reliable_data_acked(&mut self, data_size: u32) {
381        self.flow_control.record_reliable_data_acked(data_size);
382    }
383
384    /// Checks if we can send more data based on window-based flow control.
385    /// Returns true if we have room in the window.
386    pub fn can_send_reliable(&self) -> bool {
387        self.flow_control.can_send_reliable(&self.config, self.packets_in_flight())
388    }
389
390    /// Dynamically adjusts the window size based on network conditions.
391    /// Called periodically to adapt to changing network conditions.
392    pub fn adjust_window_size(&mut self) {
393        let loss_rate = self.loss_rate();
394        let rtt_ms = self.rtt().as_millis() as u32;
395        self.flow_control.adjust_window_size(&self.config, loss_rate, rtt_ms);
396    }
397
398    // ===== Bandwidth Throttling =====
399
400    /// Updates bandwidth tracking window, resetting counters if window expired.
401    /// Returns true if the window was reset.
402    pub fn update_bandwidth_window(&mut self, time: Instant) -> bool {
403        self.bandwidth_throttle.update_bandwidth_window(time)
404    }
405
406    /// Records bytes sent for bandwidth tracking.
407    pub fn record_bytes_sent(&mut self, bytes: u32) {
408        self.bandwidth_throttle.record_bytes_sent(bytes);
409    }
410
411    /// Records bytes received for bandwidth tracking.
412    pub fn record_bytes_received(&mut self, bytes: u32) {
413        self.bandwidth_throttle.record_bytes_received(bytes);
414    }
415
416    /// Checks if we can send based on outgoing bandwidth limit.
417    /// Returns true if we're under the limit or if throttling is disabled (limit == 0).
418    pub fn can_send_within_bandwidth(&self) -> bool {
419        self.bandwidth_throttle.can_send_within_bandwidth()
420    }
421
422    /// Returns current bandwidth utilization (0.0 to 1.0+).
423    /// Returns 0.0 if bandwidth limiting is disabled.
424    pub fn bandwidth_utilization(&self) -> f32 {
425        self.bandwidth_throttle.bandwidth_utilization()
426    }
427
428    /// Checks if we can receive based on incoming bandwidth limit.
429    /// Returns true if we're under the limit or if throttling is disabled (limit == 0).
430    pub fn can_receive_within_bandwidth(&self) -> bool {
431        self.bandwidth_throttle.can_receive_within_bandwidth()
432    }
433
434    /// Returns current incoming bandwidth utilization (0.0 to 1.0+).
435    /// Returns 0.0 if bandwidth limiting is disabled.
436    pub fn incoming_bandwidth_utilization(&self) -> f32 {
437        self.bandwidth_throttle.incoming_bandwidth_utilization()
438    }
439
440    // ===== Unsequenced Packet Handling =====
441
442    /// Gets the next outgoing unsequenced group ID and increments the counter.
443    pub fn next_unsequenced_group(&mut self) -> u16 {
444        self.unsequenced_state.next_outgoing_group()
445    }
446
447    /// Checks if an incoming unsequenced group is a duplicate or within the window.
448    /// Returns true if this is a duplicate (already received), false if new.
449    pub fn is_unsequenced_duplicate(&self, group: u16) -> bool {
450        self.unsequenced_state.is_duplicate(group)
451    }
452
453    /// Marks an incoming unsequenced group as received in the window.
454    /// Advances the window base if necessary.
455    pub fn mark_unsequenced_received(&mut self, group: u16) {
456        self.unsequenced_state.mark_received(group);
457    }
458
459    /// Cleans up stale fragment buffers that haven't been completed within the timeout period.
460    /// This prevents memory leaks from incomplete fragments (e.g., due to packet loss or malicious behavior).
461    ///
462    /// Call this periodically (e.g., once per second) to prevent accumulation of stale buffers.
463    /// Default timeout is 5 seconds after the first fragment is received.
464    pub fn cleanup_stale_fragments(&mut self, time: Instant) {
465        cleanup_stale_fragments(&mut self.command_fragments, time);
466    }
467
468    // ===== Command-based API =====
469
470    /// Returns the size of data carried by a protocol command.
471    /// Only counts actual packet payload data, not protocol overhead.
472    fn command_data_size(command: &ProtocolCommand) -> usize {
473        match command {
474            ProtocolCommand::SendReliable { data, .. }
475            | ProtocolCommand::SendUnreliable { data, .. }
476            | ProtocolCommand::SendUnreliableSequenced { data, .. }
477            | ProtocolCommand::SendUnsequenced { data, .. }
478            | ProtocolCommand::SendFragment { data, .. }
479            | ProtocolCommand::SendUnreliableFragment { data, .. } => data.len(),
480            _ => 0, // Control commands have no data
481        }
482    }
483
484    /// Enqueues a protocol command for batching.
485    /// Returns true if the queue should be flushed (reached max size).
486    /// Returns false and drops the command if it would exceed max_waiting_data limit.
487    pub fn enqueue_command(&mut self, command: ProtocolCommand) -> bool {
488        let data_size = Self::command_data_size(&command);
489
490        // Check if adding this command would exceed the limit (0 = unlimited)
491        if self.config.max_waiting_data > 0 {
492            let new_total = self.total_waiting_data + data_size;
493            if new_total > self.config.max_waiting_data {
494                // Drop the command to prevent memory exhaustion
495                tracing::warn!(
496                    "Dropping command: would exceed max_waiting_data limit ({} + {} > {})",
497                    self.total_waiting_data,
498                    data_size,
499                    self.config.max_waiting_data
500                );
501                return false;
502            }
503        }
504
505        self.total_waiting_data += data_size;
506        self.command_queue.enqueue(command)
507    }
508
509    /// Generates and enqueues an Acknowledge command based on current state.
510    /// This should be called after receiving reliable packets to send ACKs back.
511    pub fn enqueue_ack_command(&mut self, sent_time: Option<u32>) {
512        let ack_command = ProtocolCommand::Acknowledge {
513            sequence: self.acknowledge_handler.remote_sequence_num(),
514            received_mask: self.acknowledge_handler.ack_bitfield(),
515            sent_time,
516        };
517        self.enqueue_command(ack_command);
518    }
519
520    /// Generates and enqueues a Ping command with the current timestamp.
521    pub fn enqueue_ping_command(&mut self, timestamp: u32) {
522        self.enqueue_command(ProtocolCommand::Ping { timestamp });
523    }
524
525    /// Generates and enqueues a Pong command in response to a Ping.
526    pub fn enqueue_pong_command(&mut self, timestamp: u32) {
527        self.enqueue_command(ProtocolCommand::Pong { timestamp });
528    }
529
530    /// Drains all pending commands from the queue.
531    /// Resets the total_waiting_data counter since commands are being sent.
532    pub fn drain_commands(&mut self) -> impl Iterator<Item = ProtocolCommand> + '_ {
533        self.total_waiting_data = 0; // Reset since we're draining all commands
534        self.command_queue.drain()
535    }
536
537    /// Returns the number of queued commands.
538    pub fn queued_commands_count(&self) -> usize {
539        self.command_queue.len()
540    }
541
542    /// Returns true if the command queue is empty.
543    pub fn has_queued_commands(&self) -> bool {
544        !self.command_queue.is_empty()
545    }
546}
547
548impl fmt::Debug for Peer {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        write!(f, "{}:{}", self.remote_address.ip(), self.remote_address.port())
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use std::time::Instant;
557
558    use bitfold_core::config::Config;
559
560    use super::Peer;
561    use crate::peer_state::PeerState;
562
563    fn get_fake_addr() -> std::net::SocketAddr {
564        "127.0.0.1:0".parse().unwrap()
565    }
566
567    // Unit tests that access private fields (acknowledge_handler, pmtu, state)
568    // Integration tests that only use public API are in tests/integration.rs
569
570    #[test]
571    fn test_window_adjustment_decreases_on_poor_conditions() {
572        let mut config = Config::default();
573        config.use_window_flow_control = true;
574        config.initial_window_size = 200;
575        config.min_window_size = 64;
576        let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
577
578        let initial_window = peer.window_size();
579
580        // Simulate poor conditions (high packet loss)
581        // Need to record packets and losses
582        for _ in 0..100 {
583            peer.acknowledge_handler.congestion_mut().record_sent();
584        }
585        for _ in 0..10 {
586            peer.acknowledge_handler.congestion_mut().record_loss();
587        } // 10% loss
588
589        peer.adjust_window_size();
590
591        // Window should decrease
592        assert!(peer.window_size() < initial_window);
593    }
594
595    #[test]
596    fn test_window_flow_control_respects_min_max() {
597        let mut config = Config::default();
598        config.use_window_flow_control = true;
599        config.initial_window_size = 100;
600        config.min_window_size = 64;
601        config.max_window_size = 128;
602        let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
603
604        // Repeatedly adjust upward
605        for _ in 0..50 {
606            peer.adjust_window_size();
607        }
608
609        // Should not exceed max
610        assert!(peer.window_size() <= 128);
611
612        // Simulate sustained poor conditions
613        for _round in 0..50 {
614            for _ in 0..100 {
615                peer.acknowledge_handler.congestion_mut().record_sent();
616            }
617            for _ in 0..10 {
618                peer.acknowledge_handler.congestion_mut().record_loss();
619            }
620            peer.adjust_window_size();
621        }
622
623        // Should not go below min
624        assert!(peer.window_size() >= 64);
625    }
626
627    #[test]
628    fn test_pmtu_discovery_probe_reply() {
629        let mut config = Config::default();
630        config.use_pmtu_discovery = true;
631        config.pmtu_min = 576;
632        config.pmtu_max = 1400;
633        config.pmtu_interval_ms = 100; // Short interval for testing
634
635        let start_time = Instant::now();
636        let mut peer = Peer::new(get_fake_addr(), &config, start_time);
637
638        // Initially, peer should use config fragment_size
639        assert_eq!(peer.current_fragment_size(), config.fragment_size);
640
641        // Advance time past the probe interval to trigger first probe
642        let time = start_time + std::time::Duration::from_millis(150);
643        peer.handle_pmtu(time);
644
645        // Should have queued a probe command
646        assert!(peer.has_queued_commands());
647
648        // Encode and check for PMTUProbe command
649        let encoded = peer.encode_queued_commands().unwrap();
650        assert!(!encoded.is_empty());
651
652        // Check that peer is waiting for reply
653        assert!(peer.pmtu.has_outstanding_probe());
654
655        // Simulate successful reply (this would update low bound)
656        if let Some((size, token, _)) = peer.pmtu.outstanding_probe() {
657            let reply = bitfold_protocol::command::ProtocolCommand::PMTUReply { size, token };
658            let _ = peer.process_command(&reply, time).unwrap();
659
660            // After successful reply, low bound should be updated
661            assert_eq!(peer.current_fragment_size(), size);
662        }
663    }
664
665    #[test]
666    fn test_pmtu_discovery_timeout() {
667        let mut config = Config::default();
668        config.use_pmtu_discovery = true;
669        config.pmtu_min = 576;
670        config.pmtu_max = 1400;
671        config.pmtu_interval_ms = 100;
672
673        let start_time = Instant::now();
674        let mut peer = Peer::new(get_fake_addr(), &config, start_time);
675
676        // Advance time to trigger initial probe
677        let mut time = start_time + std::time::Duration::from_millis(150);
678        peer.handle_pmtu(time);
679        assert!(peer.has_queued_commands());
680        let _ = peer.encode_queued_commands().unwrap();
681
682        let high_before = peer.pmtu.high_bound();
683
684        // Advance time beyond RTO to trigger timeout (RTO is typically 200ms+)
685        time = time + std::time::Duration::from_secs(2);
686
687        // Handle PMTU again - should timeout the outstanding probe
688        peer.handle_pmtu(time);
689
690        // After timeout, outstanding should be cleared and high bound reduced
691        assert!(!peer.pmtu.has_outstanding_probe());
692        assert!(peer.pmtu.high_bound() < high_before);
693    }
694
695    #[test]
696    fn test_pmtu_discovery_enabled_by_default() {
697        let config = Config::default();
698        assert!(config.use_pmtu_discovery);
699
700        let creation_time = Instant::now();
701        let mut peer = Peer::new(get_fake_addr(), &config, creation_time);
702        // Move peer to connected state via state transitions
703        peer.state = PeerState::Connected;
704
705        // Wait for the PMTU interval to elapse before checking for probes
706        let probe_time =
707            creation_time + std::time::Duration::from_millis(config.pmtu_interval_ms as u64 + 100);
708
709        // Should generate probes when enabled and connected after interval
710        peer.handle_pmtu(probe_time);
711        assert!(peer.has_queued_commands()); // Should have queued a PMTUProbe command
712    }
713}