bitfold_peer/peer/
fragmenter.rs

1use std::sync::Arc;
2
3use bitfold_core::shared::SharedBytes;
4use bitfold_protocol::command::ProtocolCommand;
5
6use super::Peer;
7
8impl Peer {
9    /// Enqueues reliable data, automatically fragmenting if necessary.
10    /// Returns the sequence number used for the packet(s).
11    pub fn enqueue_reliable_data(&mut self, channel_id: u8, data: Arc<[u8]>, ordered: bool) -> u16 {
12        let sequence = self.acknowledge_handler.local_sequence_num();
13
14        // Compute datagram cap and per-command payload budget so a single
15        // SendReliable or SendFragment fits within one UDP datagram when encoded.
16        let datagram_cap = std::cmp::min(
17            self.current_fragment_size() as usize,
18            self.config.receive_buffer_max_size,
19        );
20        // Overheads common to any datagram containing exactly one command
21        let compression_overhead = match self.config.compression {
22            bitfold_core::config::CompressionAlgorithm::Lz4 => 5, // marker + original size
23            _ => 1,                                               // marker only
24        };
25        let checksum_overhead = if self.config.use_checksums { 4 } else { 0 };
26        let per_packet_overhead = 1 /* command count */ + compression_overhead + checksum_overhead;
27
28        // Header sizes for commands (not including the 2-byte length prefix)
29        let send_reliable_header = 1 /* type */ + 1 /* channel */ + 2 /* sequence */
30            + 1 /* ordered flag */ + 2 /* payload len */; // = 7
31        let send_fragment_header = 1 /* type */ + 1 /* channel */ + 2 /* sequence */
32            + 1 /* ordered flag */ + 1 /* frag_id */ + 1 /* frag_count */ + 2 /* len */; // = 9
33
34        // Maximum payload that fits for non-fragmented reliable
35        let max_payload_reliable = datagram_cap
36            .saturating_sub(per_packet_overhead)
37            .saturating_sub(2 /* len prefix */)
38            .saturating_sub(send_reliable_header);
39
40        // Validate MTU is large enough for minimum payload
41        if max_payload_reliable < 1 {
42            tracing::error!(
43                "MTU too small: datagram_cap={}, overhead={}, can't fit minimum payload",
44                datagram_cap,
45                per_packet_overhead + 2 + send_reliable_header
46            );
47            // Send nothing but log the error
48            return sequence;
49        }
50
51        if data.len() <= max_payload_reliable {
52            // No fragmentation needed (fits as a single SendReliable)
53            self.enqueue_command(ProtocolCommand::SendReliable {
54                channel_id,
55                sequence,
56                ordered,
57                data: SharedBytes::from_arc(data),
58            });
59        } else {
60            // Fragment the data; compute fragment payload budget so each fragment packet fits
61            let fragment_payload = datagram_cap
62                .saturating_sub(per_packet_overhead)
63                .saturating_sub(2 /* len prefix */)
64                .saturating_sub(send_fragment_header);
65
66            if fragment_payload < 1 {
67                tracing::error!(
68                    "MTU too small for fragmentation: datagram_cap={}, overhead={}, can't fit minimum fragment payload",
69                    datagram_cap,
70                    per_packet_overhead + 2 + send_fragment_header
71                );
72                return sequence;
73            }
74
75            // Check for integer overflow before casting to u8
76            let total_fragments_usize = data.len().div_ceil(fragment_payload);
77            if total_fragments_usize > u8::MAX as usize {
78                tracing::warn!(
79                    "Payload {} bytes too large to fragment: would require {} fragments (max {}), dropping packet",
80                    data.len(),
81                    total_fragments_usize,
82                    u8::MAX
83                );
84                return sequence;
85            }
86            let total_fragments = total_fragments_usize as u8;
87
88            if total_fragments > self.config.max_fragments {
89                tracing::warn!(
90                    "Payload requires {} fragments but max allowed is {}, sending first fragment only",
91                    total_fragments,
92                    self.config.max_fragments
93                );
94                // Too many fragments - send the first fragment only (best-effort)
95                let base = SharedBytes::from_arc(data);
96                let fragment_data = base.slice(0, fragment_payload.min(base.len()));
97                self.enqueue_command(ProtocolCommand::SendFragment {
98                    channel_id,
99                    sequence,
100                    ordered,
101                    fragment_id: 0,
102                    fragment_count: 1,
103                    data: fragment_data,
104                });
105                return sequence;
106            }
107
108            tracing::trace!(
109                "Fragmenting {} byte payload into {} fragments ({} bytes each)",
110                data.len(),
111                total_fragments,
112                fragment_payload
113            );
114
115            for fragment_id in 0..total_fragments {
116                let start = (fragment_id as usize) * fragment_payload;
117                let end = ((fragment_id as usize + 1) * fragment_payload).min(data.len());
118                let base = SharedBytes::from_arc(data.clone());
119                let fragment_data = base.slice(start, end - start);
120                self.enqueue_command(ProtocolCommand::SendFragment {
121                    channel_id,
122                    sequence,
123                    ordered,
124                    fragment_id,
125                    fragment_count: total_fragments,
126                    data: fragment_data,
127                });
128            }
129        }
130
131        sequence
132    }
133
134    /// Enqueues unreliable data, automatically fragmenting if necessary.
135    /// Returns the sequence number used for reassembly.
136    pub fn enqueue_unreliable_data(&mut self, channel_id: u8, data: Arc<[u8]>) -> u16 {
137        // Use a sequence number for fragment reassembly (but not for reliability)
138        let sequence = self.next_unreliable_sequence;
139        self.next_unreliable_sequence = self.next_unreliable_sequence.wrapping_add(1);
140
141        // Compute datagram cap and per-command payload budget so a single
142        // SendUnreliable or SendUnreliableFragment fits within one UDP datagram when encoded.
143        let datagram_cap = std::cmp::min(
144            self.current_fragment_size() as usize,
145            self.config.receive_buffer_max_size,
146        );
147        let compression_overhead = match self.config.compression {
148            bitfold_core::config::CompressionAlgorithm::Lz4 => 5,
149            _ => 1,
150        };
151        let checksum_overhead = if self.config.use_checksums { 4 } else { 0 };
152        let per_packet_overhead = 1 /* command count */ + compression_overhead + checksum_overhead;
153
154        // Header sizes (without the 2-byte length prefix)
155        let send_unrel_header = 1 /* type */ + 1 /* channel */ + 2 /* payload len */; // = 4
156        let send_unrel_frag_header = 1 /* type */ + 1 /* channel */ + 2 /* sequence */
157            + 1 /* frag_id */ + 1 /* frag_count */ + 2 /* len */; // = 8
158
159        let max_payload_unreliable = datagram_cap
160            .saturating_sub(per_packet_overhead)
161            .saturating_sub(2 /* len prefix */)
162            .saturating_sub(send_unrel_header);
163
164        // Validate MTU is large enough for minimum payload
165        if max_payload_unreliable < 1 {
166            tracing::error!(
167                "MTU too small: datagram_cap={}, overhead={}, can't fit minimum unreliable payload",
168                datagram_cap,
169                per_packet_overhead + 2 + send_unrel_header
170            );
171            return sequence;
172        }
173
174        if data.len() <= max_payload_unreliable {
175            // No fragmentation needed
176            self.enqueue_command(ProtocolCommand::SendUnreliable {
177                channel_id,
178                data: SharedBytes::from_arc(data),
179            });
180        } else {
181            // Fragment the data so each fragment fits
182            let fragment_payload = datagram_cap
183                .saturating_sub(per_packet_overhead)
184                .saturating_sub(2 /* len prefix */)
185                .saturating_sub(send_unrel_frag_header);
186
187            if fragment_payload < 1 {
188                tracing::error!(
189                    "MTU too small for unreliable fragmentation: datagram_cap={}, overhead={}, can't fit minimum fragment payload",
190                    datagram_cap,
191                    per_packet_overhead + 2 + send_unrel_frag_header
192                );
193                return sequence;
194            }
195
196            // Check for integer overflow before casting to u8
197            let total_fragments_usize = data.len().div_ceil(fragment_payload);
198            if total_fragments_usize > u8::MAX as usize {
199                tracing::warn!(
200                    "Unreliable payload {} bytes too large to fragment: would require {} fragments (max {}), dropping packet",
201                    data.len(),
202                    total_fragments_usize,
203                    u8::MAX
204                );
205                return sequence;
206            }
207            let total_fragments = total_fragments_usize as u8;
208
209            if total_fragments > self.config.max_fragments {
210                tracing::warn!(
211                    "Unreliable payload requires {} fragments but max allowed is {}, sending first fragment only",
212                    total_fragments,
213                    self.config.max_fragments
214                );
215                // Too many fragments - send first fragment only (best-effort)
216                let base = SharedBytes::from_arc(data);
217                let fragment_data = base.slice(0, fragment_payload.min(base.len()));
218                self.enqueue_command(ProtocolCommand::SendUnreliableFragment {
219                    channel_id,
220                    sequence,
221                    fragment_id: 0,
222                    fragment_count: 1,
223                    data: fragment_data,
224                });
225                return sequence;
226            }
227
228            tracing::trace!(
229                "Fragmenting {} byte unreliable payload into {} fragments ({} bytes each)",
230                data.len(),
231                total_fragments,
232                fragment_payload
233            );
234
235            for fragment_id in 0..total_fragments {
236                let start = (fragment_id as usize) * fragment_payload;
237                let end = ((fragment_id as usize + 1) * fragment_payload).min(data.len());
238                let base = SharedBytes::from_arc(data.clone());
239                let fragment_data = base.slice(start, end - start);
240
241                self.enqueue_command(ProtocolCommand::SendUnreliableFragment {
242                    channel_id,
243                    sequence,
244                    fragment_id,
245                    fragment_count: total_fragments,
246                    data: fragment_data,
247                });
248            }
249        }
250
251        sequence
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use std::time::Instant;
258
259    use bitfold_core::config::Config;
260    use bitfold_protocol::{command::ProtocolCommand, packet::DeliveryGuarantee};
261
262    use super::*;
263
264    fn create_virtual_connection() -> Peer {
265        Peer::new(get_fake_addr(), &Config::default(), Instant::now())
266    }
267
268    fn get_fake_addr() -> std::net::SocketAddr {
269        "127.0.0.1:0".parse().unwrap()
270    }
271
272    // ===== Reliable Fragment Tests =====
273
274    #[test]
275    fn test_enqueue_reliable_data_with_fragmentation() {
276        let mut peer = create_virtual_connection();
277
278        // Create data larger than fragment size (default is 1024)
279        let large_data = vec![42u8; 3000];
280
281        // Enqueue the data - should automatically fragment
282        let _sequence = peer.enqueue_reliable_data(0, large_data.into(), true);
283
284        // Should have multiple SendFragment commands queued
285        assert!(peer.has_queued_commands());
286        let count = peer.queued_commands_count();
287        assert!(count >= 3); // 3000 bytes / 1024 = at least 3 fragments
288
289        // Verify commands are SendFragment
290        let commands: Vec<_> = peer.drain_commands().collect();
291        for cmd in &commands {
292            assert!(matches!(cmd, ProtocolCommand::SendFragment { .. }));
293        }
294    }
295
296    #[test]
297    fn test_fragment_reassembly() {
298        let mut peer = create_virtual_connection();
299        let time = Instant::now();
300
301        // Create fragmented data
302        let fragment1 = std::sync::Arc::<[u8]>::from(vec![1, 2, 3].into_boxed_slice());
303        let fragment2 = std::sync::Arc::<[u8]>::from(vec![4, 5, 6].into_boxed_slice());
304        let fragment3 = std::sync::Arc::<[u8]>::from(vec![7, 8, 9].into_boxed_slice());
305
306        // Send fragments
307        let cmd1 = ProtocolCommand::SendFragment {
308            channel_id: 0,
309            sequence: 0,
310            ordered: true,
311            fragment_id: 0,
312            fragment_count: 3,
313            data: fragment1.into(),
314        };
315
316        let cmd2 = ProtocolCommand::SendFragment {
317            channel_id: 0,
318            sequence: 0,
319            ordered: true,
320            fragment_id: 1,
321            fragment_count: 3,
322            data: fragment2.into(),
323        };
324
325        let cmd3 = ProtocolCommand::SendFragment {
326            channel_id: 0,
327            sequence: 0,
328            ordered: true,
329            fragment_id: 2,
330            fragment_count: 3,
331            data: fragment3.into(),
332        };
333
334        // Process first two fragments - should not produce a packet yet
335        let result1 = peer.process_command(&cmd1, time).unwrap();
336        assert_eq!(result1.into_iter().count(), 0);
337
338        let result2 = peer.process_command(&cmd2, time).unwrap();
339        assert_eq!(result2.into_iter().count(), 0);
340
341        // Process final fragment - should produce reassembled packet
342        let result3 = peer.process_command(&cmd3, time).unwrap();
343        let packets: Vec<_> = result3.into_iter().collect();
344        assert_eq!(packets.len(), 1);
345
346        // Verify reassembled data
347        assert_eq!(packets[0].0.payload(), &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
348
349        // Should have ACK queued
350        assert!(peer.has_queued_commands());
351    }
352
353    #[test]
354    fn test_round_trip_large_data_with_fragmentation() {
355        let mut peer1 = create_virtual_connection();
356        let mut peer2 = create_virtual_connection();
357        let time = Instant::now();
358
359        // Create large data that will be fragmented
360        let large_data = vec![99u8; 2500];
361
362        // peer1 sends large data - should automatically fragment
363        peer1.enqueue_reliable_data(0, large_data.clone().into(), true);
364        let bytes = peer1.encode_queued_commands().unwrap();
365
366        // peer2 receives and reassembles fragments
367        let result = peer2.process_command_packet(&bytes, time).unwrap();
368        let packets: Vec<_> = result.into_iter().collect();
369
370        // Should receive one reassembled packet
371        assert_eq!(packets.len(), 1);
372        assert_eq!(packets[0].0.payload(), &large_data[..]);
373
374        // peer2 should have ACK queued
375        assert!(peer2.has_queued_commands());
376    }
377
378    #[test]
379    fn test_fragment_out_of_order_delivery() {
380        let mut peer = create_virtual_connection();
381        let time = Instant::now();
382
383        // Send fragments out of order
384        let cmd2 = ProtocolCommand::SendFragment {
385            channel_id: 0,
386            sequence: 0,
387            ordered: true,
388            fragment_id: 1,
389            fragment_count: 3,
390            data: vec![20, 21, 22].into(),
391        };
392
393        let cmd0 = ProtocolCommand::SendFragment {
394            channel_id: 0,
395            sequence: 0,
396            ordered: true,
397            fragment_id: 0,
398            fragment_count: 3,
399            data: vec![10, 11, 12].into(),
400        };
401
402        let cmd1 = ProtocolCommand::SendFragment {
403            channel_id: 0,
404            sequence: 0,
405            ordered: true,
406            fragment_id: 2,
407            fragment_count: 3,
408            data: vec![30, 31, 32].into(),
409        };
410
411        // Process in order: 1, 0, 2
412        peer.process_command(&cmd2, time).unwrap();
413        peer.process_command(&cmd0, time).unwrap();
414        let result = peer.process_command(&cmd1, time).unwrap();
415
416        let packets: Vec<_> = result.into_iter().collect();
417        assert_eq!(packets.len(), 1);
418
419        // Should be reassembled in correct order
420        assert_eq!(packets[0].0.payload(), &[10, 11, 12, 20, 21, 22, 30, 31, 32]);
421    }
422
423    // ===== Unreliable Fragment Tests =====
424
425    #[test]
426    fn test_enqueue_unreliable_data_with_fragmentation() {
427        let mut peer = create_virtual_connection();
428
429        // Create data larger than fragment size (default is 1024)
430        let large_data = vec![99u8; 3000];
431
432        // Enqueue the data - should automatically fragment
433        let _sequence = peer.enqueue_unreliable_data(0, large_data.into());
434
435        // Should have multiple SendUnreliableFragment commands queued
436        assert!(peer.has_queued_commands());
437        let count = peer.queued_commands_count();
438        assert!(count >= 3); // 3000 bytes / 1024 = at least 3 fragments
439
440        // Verify commands are SendUnreliableFragment
441        let commands: Vec<_> = peer.drain_commands().collect();
442        for cmd in &commands {
443            assert!(matches!(cmd, ProtocolCommand::SendUnreliableFragment { .. }));
444        }
445    }
446
447    #[test]
448    fn test_unreliable_fragment_reassembly() {
449        let config = Config::default();
450        let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
451
452        // Simulate receiving fragments manually
453        let fragment1 = vec![1, 2, 3];
454        let fragment2 = vec![4, 5, 6];
455        let fragment3 = vec![7, 8, 9];
456
457        // Send fragments
458        let cmd1 = ProtocolCommand::SendUnreliableFragment {
459            channel_id: 0,
460            sequence: 1,
461            fragment_id: 0,
462            fragment_count: 3,
463            data: fragment1.into(),
464        };
465
466        let cmd2 = ProtocolCommand::SendUnreliableFragment {
467            channel_id: 0,
468            sequence: 1,
469            fragment_id: 1,
470            fragment_count: 3,
471            data: fragment2.into(),
472        };
473
474        let cmd3 = ProtocolCommand::SendUnreliableFragment {
475            channel_id: 0,
476            sequence: 1,
477            fragment_id: 2,
478            fragment_count: 3,
479            data: fragment3.into(),
480        };
481
482        let time = Instant::now();
483
484        // Process first two fragments - should not deliver yet
485        let result1 = peer.process_command(&cmd1, time).unwrap();
486        assert_eq!(result1.into_iter().count(), 0);
487
488        let result2 = peer.process_command(&cmd2, time).unwrap();
489        assert_eq!(result2.into_iter().count(), 0);
490
491        // Process last fragment - should deliver complete packet
492        let result3 = peer.process_command(&cmd3, time).unwrap();
493        let packets: Vec<_> = result3.into_iter().collect();
494        assert_eq!(packets.len(), 1);
495
496        let (packet, _) = &packets[0];
497        assert_eq!(packet.channel_id(), 0);
498        assert_eq!(packet.payload(), &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
499        assert_eq!(packet.delivery_guarantee(), DeliveryGuarantee::Unreliable);
500    }
501
502    #[test]
503    fn test_unreliable_fragment_out_of_order_delivery() {
504        let config = Config::default();
505        let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
506        let time = Instant::now();
507
508        // Send fragments out of order
509        let cmd2 = ProtocolCommand::SendUnreliableFragment {
510            channel_id: 0,
511            sequence: 5,
512            fragment_id: 1,
513            fragment_count: 3,
514            data: vec![20, 21, 22].into(),
515        };
516
517        let cmd0 = ProtocolCommand::SendUnreliableFragment {
518            channel_id: 0,
519            sequence: 5,
520            fragment_id: 0,
521            fragment_count: 3,
522            data: vec![10, 11, 12].into(),
523        };
524
525        let cmd1 = ProtocolCommand::SendUnreliableFragment {
526            channel_id: 0,
527            sequence: 5,
528            fragment_id: 2,
529            fragment_count: 3,
530            data: vec![30, 31, 32].into(),
531        };
532
533        // Process out of order (1, 0, 2)
534        let result1 = peer.process_command(&cmd2, time).unwrap();
535        assert_eq!(result1.into_iter().count(), 0);
536
537        let result2 = peer.process_command(&cmd0, time).unwrap();
538        assert_eq!(result2.into_iter().count(), 0);
539
540        // Last fragment completes the packet
541        let result3 = peer.process_command(&cmd1, time).unwrap();
542        let packets: Vec<_> = result3.into_iter().collect();
543        assert_eq!(packets.len(), 1);
544
545        let (packet, _) = &packets[0];
546        // Should reassemble correctly despite out-of-order arrival
547        assert_eq!(packet.payload(), &[10, 11, 12, 20, 21, 22, 30, 31, 32]);
548    }
549
550    #[test]
551    fn test_round_trip_unreliable_large_data_with_fragmentation() {
552        let config = Config::default();
553        let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
554        let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
555        let time = Instant::now();
556
557        // Create large data that will be fragmented
558        let large_data = vec![123u8; 2500];
559
560        // peer1 sends large unreliable data - should automatically fragment
561        peer1.enqueue_unreliable_data(0, large_data.clone().into());
562        let bytes = peer1.encode_queued_commands().unwrap();
563
564        // peer2 receives and reassembles fragments
565        let result = peer2.process_command_packet(&bytes, time).unwrap();
566        let packets: Vec<_> = result.into_iter().collect();
567
568        // Should get one reassembled packet
569        assert_eq!(packets.len(), 1);
570
571        let (packet, _) = &packets[0];
572        assert_eq!(packet.payload(), large_data.as_slice());
573        assert_eq!(packet.delivery_guarantee(), DeliveryGuarantee::Unreliable);
574    }
575
576    #[test]
577    fn test_unreliable_fragments_no_ack_sent() {
578        let config = Config::default();
579        let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
580        let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
581        let time = Instant::now();
582
583        // peer1 sends large unreliable data
584        peer1.enqueue_unreliable_data(0, vec![55u8; 2000].into());
585        let bytes = peer1.encode_queued_commands().unwrap();
586
587        // peer2 receives it
588        let _ = peer2.process_command_packet(&bytes, time).unwrap();
589
590        // peer2 should NOT have queued ACK commands (unreliable doesn't need ACK)
591        assert!(
592            !peer2.has_queued_commands() || {
593                // If there are commands, none should be Acknowledge
594                let commands: Vec<_> = peer2.drain_commands().collect();
595                !commands.iter().any(|cmd| matches!(cmd, ProtocolCommand::Acknowledge { .. }))
596            }
597        );
598    }
599
600    // ===== Stale Fragment Cleanup Tests =====
601
602    #[test]
603    fn test_stale_fragment_cleanup() {
604        let config = Config::default();
605        let start_time = Instant::now();
606        let mut peer = Peer::new(get_fake_addr(), &config, start_time);
607
608        // Create incomplete fragmented packet by sending only first fragment
609        let fragment1 = vec![1, 2, 3];
610        let cmd = ProtocolCommand::SendFragment {
611            channel_id: 0,
612            sequence: 100,
613            ordered: true,
614            fragment_id: 0,
615            fragment_count: 3, // Expecting 3 fragments total
616            data: fragment1.into(),
617        };
618
619        // Process the first fragment
620        let result = peer.process_command(&cmd, start_time).unwrap();
621        assert_eq!(result.into_iter().count(), 0); // No complete packet yet
622
623        // Verify fragment buffer was created
624        assert_eq!(peer.command_fragments.len(), 1);
625
626        // Cleanup immediately - should not remove anything (< 5 second timeout)
627        peer.cleanup_stale_fragments(start_time);
628        assert_eq!(peer.command_fragments.len(), 1, "Fragment buffer should still exist");
629
630        // Wait for timeout period (5 seconds)
631        let later = start_time + std::time::Duration::from_secs(6);
632        peer.cleanup_stale_fragments(later);
633
634        // Stale buffer should be cleaned up
635        assert_eq!(peer.command_fragments.len(), 0, "Stale fragment buffer should be cleaned up");
636    }
637
638    #[test]
639    fn test_complete_fragments_not_cleaned() {
640        let config = Config::default();
641        let start_time = Instant::now();
642        let mut peer = Peer::new(get_fake_addr(), &config, start_time);
643
644        // Send all 3 fragments to complete the packet
645        for frag_id in 0..3 {
646            let fragment_data = vec![frag_id, frag_id + 1, frag_id + 2];
647            let cmd = ProtocolCommand::SendFragment {
648                channel_id: 0,
649                sequence: 200,
650                ordered: false,
651                fragment_id: frag_id,
652                fragment_count: 3,
653                data: fragment_data.into(),
654            };
655            peer.process_command(&cmd, start_time).unwrap();
656        }
657
658        // Complete fragments should be removed automatically after reassembly
659        assert_eq!(peer.command_fragments.len(), 0, "Complete fragments should be removed");
660
661        // Cleanup should not affect anything
662        let later = start_time + std::time::Duration::from_secs(10);
663        peer.cleanup_stale_fragments(later);
664        assert_eq!(peer.command_fragments.len(), 0);
665    }
666
667    #[test]
668    fn test_multiple_stale_fragments_cleanup() {
669        let config = Config::default();
670        let start_time = Instant::now();
671        let mut peer = Peer::new(get_fake_addr(), &config, start_time);
672
673        // Create multiple incomplete fragment buffers
674        for seq in 0..5 {
675            let cmd = ProtocolCommand::SendFragment {
676                channel_id: 0,
677                sequence: seq,
678                ordered: true,
679                fragment_id: 0,
680                fragment_count: 2,
681                data: vec![seq as u8].into(),
682            };
683            peer.process_command(&cmd, start_time).unwrap();
684        }
685
686        // Should have 5 incomplete fragment buffers
687        assert_eq!(peer.command_fragments.len(), 5);
688
689        // Cleanup after timeout
690        let later = start_time + std::time::Duration::from_secs(6);
691        peer.cleanup_stale_fragments(later);
692
693        // All stale buffers should be cleaned up
694        assert_eq!(
695            peer.command_fragments.len(),
696            0,
697            "All stale fragment buffers should be cleaned up"
698        );
699    }
700}