use std::sync::Arc;
use bitfold_core::shared::SharedBytes;
use bitfold_protocol::command::ProtocolCommand;
use super::Peer;
impl Peer {
pub fn enqueue_reliable_data(&mut self, channel_id: u8, data: Arc<[u8]>, ordered: bool) -> u16 {
let sequence = self.acknowledge_handler.local_sequence_num();
let datagram_cap = std::cmp::min(
self.current_fragment_size() as usize,
self.config.receive_buffer_max_size,
);
let compression_overhead = match self.config.compression {
bitfold_core::config::CompressionAlgorithm::Lz4 => 5, _ => 1, };
let checksum_overhead = if self.config.use_checksums { 4 } else { 0 };
let per_packet_overhead = 1 + compression_overhead + checksum_overhead;
let send_reliable_header = 1 + 1 + 2
+ 1 + 2 ; let send_fragment_header = 1 + 1 + 2
+ 1 + 1 + 1 + 2 ;
let max_payload_reliable = datagram_cap
.saturating_sub(per_packet_overhead)
.saturating_sub(2 )
.saturating_sub(send_reliable_header);
if max_payload_reliable < 1 {
tracing::error!(
"MTU too small: datagram_cap={}, overhead={}, can't fit minimum payload",
datagram_cap,
per_packet_overhead + 2 + send_reliable_header
);
return sequence;
}
if data.len() <= max_payload_reliable {
self.enqueue_command(ProtocolCommand::SendReliable {
channel_id,
sequence,
ordered,
data: SharedBytes::from_arc(data),
});
} else {
let fragment_payload = datagram_cap
.saturating_sub(per_packet_overhead)
.saturating_sub(2 )
.saturating_sub(send_fragment_header);
if fragment_payload < 1 {
tracing::error!(
"MTU too small for fragmentation: datagram_cap={}, overhead={}, can't fit minimum fragment payload",
datagram_cap,
per_packet_overhead + 2 + send_fragment_header
);
return sequence;
}
let total_fragments_usize = data.len().div_ceil(fragment_payload);
if total_fragments_usize > u8::MAX as usize {
tracing::warn!(
"Payload {} bytes too large to fragment: would require {} fragments (max {}), dropping packet",
data.len(),
total_fragments_usize,
u8::MAX
);
return sequence;
}
let total_fragments = total_fragments_usize as u8;
if total_fragments > self.config.max_fragments {
tracing::warn!(
"Payload requires {} fragments but max allowed is {}, sending first fragment only",
total_fragments,
self.config.max_fragments
);
let base = SharedBytes::from_arc(data);
let fragment_data = base.slice(0, fragment_payload.min(base.len()));
self.enqueue_command(ProtocolCommand::SendFragment {
channel_id,
sequence,
ordered,
fragment_id: 0,
fragment_count: 1,
data: fragment_data,
});
return sequence;
}
tracing::trace!(
"Fragmenting {} byte payload into {} fragments ({} bytes each)",
data.len(),
total_fragments,
fragment_payload
);
for fragment_id in 0..total_fragments {
let start = (fragment_id as usize) * fragment_payload;
let end = ((fragment_id as usize + 1) * fragment_payload).min(data.len());
let base = SharedBytes::from_arc(data.clone());
let fragment_data = base.slice(start, end - start);
self.enqueue_command(ProtocolCommand::SendFragment {
channel_id,
sequence,
ordered,
fragment_id,
fragment_count: total_fragments,
data: fragment_data,
});
}
}
sequence
}
pub fn enqueue_unreliable_data(&mut self, channel_id: u8, data: Arc<[u8]>) -> u16 {
let sequence = self.next_unreliable_sequence;
self.next_unreliable_sequence = self.next_unreliable_sequence.wrapping_add(1);
let datagram_cap = std::cmp::min(
self.current_fragment_size() as usize,
self.config.receive_buffer_max_size,
);
let compression_overhead = match self.config.compression {
bitfold_core::config::CompressionAlgorithm::Lz4 => 5,
_ => 1,
};
let checksum_overhead = if self.config.use_checksums { 4 } else { 0 };
let per_packet_overhead = 1 + compression_overhead + checksum_overhead;
let send_unrel_header = 1 + 1 + 2 ; let send_unrel_frag_header = 1 + 1 + 2
+ 1 + 1 + 2 ;
let max_payload_unreliable = datagram_cap
.saturating_sub(per_packet_overhead)
.saturating_sub(2 )
.saturating_sub(send_unrel_header);
if max_payload_unreliable < 1 {
tracing::error!(
"MTU too small: datagram_cap={}, overhead={}, can't fit minimum unreliable payload",
datagram_cap,
per_packet_overhead + 2 + send_unrel_header
);
return sequence;
}
if data.len() <= max_payload_unreliable {
self.enqueue_command(ProtocolCommand::SendUnreliable {
channel_id,
data: SharedBytes::from_arc(data),
});
} else {
let fragment_payload = datagram_cap
.saturating_sub(per_packet_overhead)
.saturating_sub(2 )
.saturating_sub(send_unrel_frag_header);
if fragment_payload < 1 {
tracing::error!(
"MTU too small for unreliable fragmentation: datagram_cap={}, overhead={}, can't fit minimum fragment payload",
datagram_cap,
per_packet_overhead + 2 + send_unrel_frag_header
);
return sequence;
}
let total_fragments_usize = data.len().div_ceil(fragment_payload);
if total_fragments_usize > u8::MAX as usize {
tracing::warn!(
"Unreliable payload {} bytes too large to fragment: would require {} fragments (max {}), dropping packet",
data.len(),
total_fragments_usize,
u8::MAX
);
return sequence;
}
let total_fragments = total_fragments_usize as u8;
if total_fragments > self.config.max_fragments {
tracing::warn!(
"Unreliable payload requires {} fragments but max allowed is {}, sending first fragment only",
total_fragments,
self.config.max_fragments
);
let base = SharedBytes::from_arc(data);
let fragment_data = base.slice(0, fragment_payload.min(base.len()));
self.enqueue_command(ProtocolCommand::SendUnreliableFragment {
channel_id,
sequence,
fragment_id: 0,
fragment_count: 1,
data: fragment_data,
});
return sequence;
}
tracing::trace!(
"Fragmenting {} byte unreliable payload into {} fragments ({} bytes each)",
data.len(),
total_fragments,
fragment_payload
);
for fragment_id in 0..total_fragments {
let start = (fragment_id as usize) * fragment_payload;
let end = ((fragment_id as usize + 1) * fragment_payload).min(data.len());
let base = SharedBytes::from_arc(data.clone());
let fragment_data = base.slice(start, end - start);
self.enqueue_command(ProtocolCommand::SendUnreliableFragment {
channel_id,
sequence,
fragment_id,
fragment_count: total_fragments,
data: fragment_data,
});
}
}
sequence
}
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use bitfold_core::config::Config;
use bitfold_protocol::{command::ProtocolCommand, packet::DeliveryGuarantee};
use super::*;
fn create_virtual_connection() -> Peer {
Peer::new(get_fake_addr(), &Config::default(), Instant::now())
}
fn get_fake_addr() -> std::net::SocketAddr {
"127.0.0.1:0".parse().unwrap()
}
#[test]
fn test_enqueue_reliable_data_with_fragmentation() {
let mut peer = create_virtual_connection();
let large_data = vec![42u8; 3000];
let _sequence = peer.enqueue_reliable_data(0, large_data.into(), true);
assert!(peer.has_queued_commands());
let count = peer.queued_commands_count();
assert!(count >= 3);
let commands: Vec<_> = peer.drain_commands().collect();
for cmd in &commands {
assert!(matches!(cmd, ProtocolCommand::SendFragment { .. }));
}
}
#[test]
fn test_fragment_reassembly() {
let mut peer = create_virtual_connection();
let time = Instant::now();
let fragment1 = std::sync::Arc::<[u8]>::from(vec![1, 2, 3].into_boxed_slice());
let fragment2 = std::sync::Arc::<[u8]>::from(vec![4, 5, 6].into_boxed_slice());
let fragment3 = std::sync::Arc::<[u8]>::from(vec![7, 8, 9].into_boxed_slice());
let cmd1 = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 0,
ordered: true,
fragment_id: 0,
fragment_count: 3,
data: fragment1.into(),
};
let cmd2 = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 0,
ordered: true,
fragment_id: 1,
fragment_count: 3,
data: fragment2.into(),
};
let cmd3 = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 0,
ordered: true,
fragment_id: 2,
fragment_count: 3,
data: fragment3.into(),
};
let result1 = peer.process_command(&cmd1, time).unwrap();
assert_eq!(result1.into_iter().count(), 0);
let result2 = peer.process_command(&cmd2, time).unwrap();
assert_eq!(result2.into_iter().count(), 0);
let result3 = peer.process_command(&cmd3, time).unwrap();
let packets: Vec<_> = result3.into_iter().collect();
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].0.payload(), &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert!(peer.has_queued_commands());
}
#[test]
fn test_round_trip_large_data_with_fragmentation() {
let mut peer1 = create_virtual_connection();
let mut peer2 = create_virtual_connection();
let time = Instant::now();
let large_data = vec![99u8; 2500];
peer1.enqueue_reliable_data(0, large_data.clone().into(), true);
let bytes = peer1.encode_queued_commands().unwrap();
let result = peer2.process_command_packet(&bytes, time).unwrap();
let packets: Vec<_> = result.into_iter().collect();
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].0.payload(), &large_data[..]);
assert!(peer2.has_queued_commands());
}
#[test]
fn test_fragment_out_of_order_delivery() {
let mut peer = create_virtual_connection();
let time = Instant::now();
let cmd2 = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 0,
ordered: true,
fragment_id: 1,
fragment_count: 3,
data: vec![20, 21, 22].into(),
};
let cmd0 = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 0,
ordered: true,
fragment_id: 0,
fragment_count: 3,
data: vec![10, 11, 12].into(),
};
let cmd1 = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 0,
ordered: true,
fragment_id: 2,
fragment_count: 3,
data: vec![30, 31, 32].into(),
};
peer.process_command(&cmd2, time).unwrap();
peer.process_command(&cmd0, time).unwrap();
let result = peer.process_command(&cmd1, time).unwrap();
let packets: Vec<_> = result.into_iter().collect();
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].0.payload(), &[10, 11, 12, 20, 21, 22, 30, 31, 32]);
}
#[test]
fn test_enqueue_unreliable_data_with_fragmentation() {
let mut peer = create_virtual_connection();
let large_data = vec![99u8; 3000];
let _sequence = peer.enqueue_unreliable_data(0, large_data.into());
assert!(peer.has_queued_commands());
let count = peer.queued_commands_count();
assert!(count >= 3);
let commands: Vec<_> = peer.drain_commands().collect();
for cmd in &commands {
assert!(matches!(cmd, ProtocolCommand::SendUnreliableFragment { .. }));
}
}
#[test]
fn test_unreliable_fragment_reassembly() {
let config = Config::default();
let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
let fragment1 = vec![1, 2, 3];
let fragment2 = vec![4, 5, 6];
let fragment3 = vec![7, 8, 9];
let cmd1 = ProtocolCommand::SendUnreliableFragment {
channel_id: 0,
sequence: 1,
fragment_id: 0,
fragment_count: 3,
data: fragment1.into(),
};
let cmd2 = ProtocolCommand::SendUnreliableFragment {
channel_id: 0,
sequence: 1,
fragment_id: 1,
fragment_count: 3,
data: fragment2.into(),
};
let cmd3 = ProtocolCommand::SendUnreliableFragment {
channel_id: 0,
sequence: 1,
fragment_id: 2,
fragment_count: 3,
data: fragment3.into(),
};
let time = Instant::now();
let result1 = peer.process_command(&cmd1, time).unwrap();
assert_eq!(result1.into_iter().count(), 0);
let result2 = peer.process_command(&cmd2, time).unwrap();
assert_eq!(result2.into_iter().count(), 0);
let result3 = peer.process_command(&cmd3, time).unwrap();
let packets: Vec<_> = result3.into_iter().collect();
assert_eq!(packets.len(), 1);
let (packet, _) = &packets[0];
assert_eq!(packet.channel_id(), 0);
assert_eq!(packet.payload(), &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(packet.delivery_guarantee(), DeliveryGuarantee::Unreliable);
}
#[test]
fn test_unreliable_fragment_out_of_order_delivery() {
let config = Config::default();
let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
let time = Instant::now();
let cmd2 = ProtocolCommand::SendUnreliableFragment {
channel_id: 0,
sequence: 5,
fragment_id: 1,
fragment_count: 3,
data: vec![20, 21, 22].into(),
};
let cmd0 = ProtocolCommand::SendUnreliableFragment {
channel_id: 0,
sequence: 5,
fragment_id: 0,
fragment_count: 3,
data: vec![10, 11, 12].into(),
};
let cmd1 = ProtocolCommand::SendUnreliableFragment {
channel_id: 0,
sequence: 5,
fragment_id: 2,
fragment_count: 3,
data: vec![30, 31, 32].into(),
};
let result1 = peer.process_command(&cmd2, time).unwrap();
assert_eq!(result1.into_iter().count(), 0);
let result2 = peer.process_command(&cmd0, time).unwrap();
assert_eq!(result2.into_iter().count(), 0);
let result3 = peer.process_command(&cmd1, time).unwrap();
let packets: Vec<_> = result3.into_iter().collect();
assert_eq!(packets.len(), 1);
let (packet, _) = &packets[0];
assert_eq!(packet.payload(), &[10, 11, 12, 20, 21, 22, 30, 31, 32]);
}
#[test]
fn test_round_trip_unreliable_large_data_with_fragmentation() {
let config = Config::default();
let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
let time = Instant::now();
let large_data = vec![123u8; 2500];
peer1.enqueue_unreliable_data(0, large_data.clone().into());
let bytes = peer1.encode_queued_commands().unwrap();
let result = peer2.process_command_packet(&bytes, time).unwrap();
let packets: Vec<_> = result.into_iter().collect();
assert_eq!(packets.len(), 1);
let (packet, _) = &packets[0];
assert_eq!(packet.payload(), large_data.as_slice());
assert_eq!(packet.delivery_guarantee(), DeliveryGuarantee::Unreliable);
}
#[test]
fn test_unreliable_fragments_no_ack_sent() {
let config = Config::default();
let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
let time = Instant::now();
peer1.enqueue_unreliable_data(0, vec![55u8; 2000].into());
let bytes = peer1.encode_queued_commands().unwrap();
let _ = peer2.process_command_packet(&bytes, time).unwrap();
assert!(
!peer2.has_queued_commands() || {
let commands: Vec<_> = peer2.drain_commands().collect();
!commands.iter().any(|cmd| matches!(cmd, ProtocolCommand::Acknowledge { .. }))
}
);
}
#[test]
fn test_stale_fragment_cleanup() {
let config = Config::default();
let start_time = Instant::now();
let mut peer = Peer::new(get_fake_addr(), &config, start_time);
let fragment1 = vec![1, 2, 3];
let cmd = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 100,
ordered: true,
fragment_id: 0,
fragment_count: 3, data: fragment1.into(),
};
let result = peer.process_command(&cmd, start_time).unwrap();
assert_eq!(result.into_iter().count(), 0);
assert_eq!(peer.command_fragments.len(), 1);
peer.cleanup_stale_fragments(start_time);
assert_eq!(peer.command_fragments.len(), 1, "Fragment buffer should still exist");
let later = start_time + std::time::Duration::from_secs(6);
peer.cleanup_stale_fragments(later);
assert_eq!(peer.command_fragments.len(), 0, "Stale fragment buffer should be cleaned up");
}
#[test]
fn test_complete_fragments_not_cleaned() {
let config = Config::default();
let start_time = Instant::now();
let mut peer = Peer::new(get_fake_addr(), &config, start_time);
for frag_id in 0..3 {
let fragment_data = vec![frag_id, frag_id + 1, frag_id + 2];
let cmd = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: 200,
ordered: false,
fragment_id: frag_id,
fragment_count: 3,
data: fragment_data.into(),
};
peer.process_command(&cmd, start_time).unwrap();
}
assert_eq!(peer.command_fragments.len(), 0, "Complete fragments should be removed");
let later = start_time + std::time::Duration::from_secs(10);
peer.cleanup_stale_fragments(later);
assert_eq!(peer.command_fragments.len(), 0);
}
#[test]
fn test_multiple_stale_fragments_cleanup() {
let config = Config::default();
let start_time = Instant::now();
let mut peer = Peer::new(get_fake_addr(), &config, start_time);
for seq in 0..5 {
let cmd = ProtocolCommand::SendFragment {
channel_id: 0,
sequence: seq,
ordered: true,
fragment_id: 0,
fragment_count: 2,
data: vec![seq as u8].into(),
};
peer.process_command(&cmd, start_time).unwrap();
}
assert_eq!(peer.command_fragments.len(), 5);
let later = start_time + std::time::Duration::from_secs(6);
peer.cleanup_stale_fragments(later);
assert_eq!(
peer.command_fragments.len(),
0,
"All stale fragment buffers should be cleaned up"
);
}
}