1use bitfold_protocol::{
2 command::CommandPacket,
3 command_codec::{self, CommandEncoder},
4};
5
6use super::Peer;
7
8impl Peer {
9 pub fn encode_queued_commands(&mut self) -> std::io::Result<Vec<u8>> {
13 let mut packet = CommandPacket::new();
14 for command in self.drain_commands() {
15 packet.add_command(command);
16 }
17
18 let mut scratch = self.tx_pool.allocate();
20 scratch.clear();
21 CommandEncoder::encode_packet_into(&mut scratch, &packet)?;
22
23 let compression_buffer = self.compression_pool.acquire();
25 let mut final_data = command_codec::compress_with_buffer(
26 &scratch,
27 self.config.compression,
28 self.config.compression_threshold,
29 compression_buffer,
30 )?;
31
32 self.tx_pool.deallocate(scratch);
34
35 self.record_packet_sent();
37
38 if self.config.use_checksums {
40 command_codec::append_checksum_in_place(&mut final_data);
41 }
42
43 self.record_data_sent(final_data.len());
45
46 Ok(final_data)
47 }
48
49 pub fn encode_queued_commands_bounded(
57 &mut self,
58 max_size: usize,
59 ) -> std::io::Result<Option<Vec<u8>>> {
60 if !self.has_queued_commands() {
61 return Ok(None);
62 }
63
64 let first_is_pmtu_probe = matches!(
67 self.command_queue.iter().next(),
68 Some(bitfold_protocol::command::ProtocolCommand::PMTUProbe { .. })
69 );
70 let max_size =
71 if first_is_pmtu_probe { self.config.receive_buffer_max_size } else { max_size };
72
73 let compression_overhead = match self.config.compression {
79 bitfold_core::config::CompressionAlgorithm::Lz4 => 5, _ => 1, };
82 let checksum_overhead = if self.config.use_checksums { 4 } else { 0 };
83 let static_overhead = 1 + compression_overhead + checksum_overhead;
84
85 let mut selected_count = 0usize;
87 let mut aggregated_len = 0; let mut per_command_sizes: Vec<usize> = Vec::new();
91 for cmd in self.command_queue.iter() {
92 let encoded = bitfold_protocol::command_codec::CommandEncoder::encode_command(cmd)?;
93 let cmd_total = 2 + encoded.len();
94
95 if static_overhead + aggregated_len + cmd_total > max_size {
97 break;
98 }
99
100 aggregated_len += cmd_total;
101 per_command_sizes.push(cmd_total);
102 selected_count += 1;
103 }
104
105 if selected_count == 0 {
106 if let Some(first_cmd) = self.command_queue.iter().next() {
109 if !first_is_pmtu_probe {
111 let encoded =
112 bitfold_protocol::command_codec::CommandEncoder::encode_command(first_cmd)?;
113 let cmd_size = 2 + encoded.len();
114 let total_with_overhead = static_overhead + cmd_size;
115
116 tracing::warn!(
117 "Command too large for MTU: command type {:?}, encoded size {} bytes, total with overhead {} bytes, max allowed {} bytes. Command will remain queued.",
118 first_cmd.command_type(),
119 cmd_size,
120 total_with_overhead,
121 max_size
122 );
123 }
124 }
125 return Ok(None);
127 }
128
129 let drained: Vec<_> = self.drain_commands().collect();
131 let mut packet = CommandPacket::new();
132 for cmd in drained.iter().take(selected_count) {
133 packet.add_command(cmd.clone());
134 }
135 for cmd in drained.into_iter().skip(selected_count) {
136 self.enqueue_command(cmd);
137 }
138
139 let mut scratch = self.tx_pool.allocate();
141 scratch.clear();
142 bitfold_protocol::command_codec::CommandEncoder::encode_packet_into(&mut scratch, &packet)?;
143
144 let compression_buffer = self.compression_pool.acquire();
146 let mut final_data = command_codec::compress_with_buffer(
147 &scratch,
148 self.config.compression,
149 self.config.compression_threshold,
150 compression_buffer,
151 )?;
152 self.tx_pool.deallocate(scratch);
153
154 self.record_packet_sent();
156
157 if self.config.use_checksums {
158 command_codec::append_checksum_in_place(&mut final_data);
159 }
160
161 self.record_data_sent(final_data.len());
163
164 if final_data.len() > max_size {
166 tracing::error!(
167 "Encoded packet exceeded max_size after compression: {} bytes > {} bytes max. Selected {} commands, pre-compression size was {} bytes (command bytes) + {} bytes (overhead). Commands will remain queued.",
168 final_data.len(),
169 max_size,
170 selected_count,
171 aggregated_len,
172 static_overhead
173 );
174 return Ok(None);
176 }
177
178 Ok(Some(final_data))
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use std::time::Instant;
185
186 use bitfold_core::config::{CompressionAlgorithm, Config};
187 use bitfold_protocol::command::ProtocolCommand;
188
189 use super::*;
190
191 fn create_virtual_connection() -> Peer {
192 Peer::new(get_fake_addr(), &Config::default(), Instant::now())
193 }
194
195 fn get_fake_addr() -> std::net::SocketAddr {
196 "127.0.0.1:0".parse().unwrap()
197 }
198
199 #[test]
200 fn test_encode_decode_command_packet() {
201 let mut peer = create_virtual_connection();
202
203 peer.enqueue_command(ProtocolCommand::Ping { timestamp: 100 });
205 peer.enqueue_command(ProtocolCommand::SendUnreliable {
206 channel_id: 0,
207 data: vec![1, 2, 3].into(),
208 });
209 peer.enqueue_ack_command(Some(5000));
210
211 assert_eq!(peer.queued_commands_count(), 3);
212
213 let bytes = peer.encode_queued_commands().unwrap();
215 assert!(!bytes.is_empty());
216 assert!(!peer.has_queued_commands()); let mut peer2 = create_virtual_connection();
220 let result = peer2.process_command_packet(&bytes, Instant::now()).unwrap();
221
222 let packets: Vec<_> = result.into_iter().collect();
224 assert_eq!(packets.len(), 1);
225
226 assert!(peer2.has_queued_commands());
228 }
229
230 #[test]
231 fn test_checksum_enabled_end_to_end() {
232 let mut config = Config::default();
233 config.use_checksums = true;
234
235 let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
236 let time = Instant::now();
237
238 peer.enqueue_command(ProtocolCommand::Ping { timestamp: 1234 });
240
241 let encoded = peer.encode_queued_commands().unwrap();
243
244 assert!(encoded.len() > 10); let result = peer.process_command_packet(&encoded, time);
249 assert!(result.is_ok()); }
251
252 #[test]
253 fn test_checksum_detects_corruption_in_peer() {
254 let mut config = Config::default();
255 config.use_checksums = true;
256
257 let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
258 let time = Instant::now();
259
260 peer.enqueue_command(ProtocolCommand::SendUnreliable {
262 channel_id: 0,
263 data: vec![1, 2, 3, 4, 5].into(),
264 });
265
266 let mut encoded = peer.encode_queued_commands().unwrap();
268
269 if encoded.len() > 5 {
271 encoded[0] = 99;
272 }
273
274 let result = peer.process_command_packet(&encoded, time);
276 assert!(result.is_err());
277 }
278
279 #[test]
280 fn test_checksum_disabled_backward_compatibility() {
281 let mut config = Config::default();
282 config.use_checksums = false; let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
285 let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
286 let time = Instant::now();
287
288 peer1.enqueue_command(ProtocolCommand::Ping { timestamp: 5678 });
290
291 let encoded = peer1.encode_queued_commands().unwrap();
293
294 let result = peer2.process_command_packet(&encoded, time);
296 assert!(result.is_ok()); }
298
299 #[test]
300 fn test_compression_zlib_end_to_end() {
301 let mut config = Config::default();
302 config.compression = CompressionAlgorithm::Zlib;
303 config.compression_threshold = 10;
304
305 let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
306 let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
307 let time = Instant::now();
308
309 peer1.enqueue_command(ProtocolCommand::SendUnreliable {
311 channel_id: 0,
312 data: vec![42; 300].into(), });
314
315 let encoded = peer1.encode_queued_commands().unwrap();
317
318 assert!(encoded.len() < 100); let result = peer2.process_command_packet(&encoded, time);
324 assert!(result.is_ok());
325 }
326
327 #[test]
328 fn test_compression_lz4_end_to_end() {
329 let mut config = Config::default();
330 config.compression = CompressionAlgorithm::Lz4;
331 config.compression_threshold = 10;
332
333 let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
334 let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
335 let time = Instant::now();
336
337 peer1.enqueue_command(ProtocolCommand::SendUnreliable {
339 channel_id: 0,
340 data: vec![99; 300].into(), });
342
343 let encoded = peer1.encode_queued_commands().unwrap();
345
346 assert!(encoded.len() < 100);
348
349 let result = peer2.process_command_packet(&encoded, time);
351 assert!(result.is_ok());
352 }
353
354 #[test]
355 fn test_compression_disabled_by_default() {
356 let config = Config::default();
357
358 let mut peer1 = Peer::new(get_fake_addr(), &config, Instant::now());
359 let mut peer2 = Peer::new(get_fake_addr(), &config, Instant::now());
360 let time = Instant::now();
361
362 peer1.enqueue_command(ProtocolCommand::Ping { timestamp: 1234 });
364
365 let encoded = peer1.encode_queued_commands().unwrap();
367
368 assert_eq!(encoded[0], 0);
370
371 let result = peer2.process_command_packet(&encoded, time);
373 assert!(result.is_ok());
374 }
375
376 #[test]
377 fn test_statistics_with_compression() {
378 let mut config = Config::default();
379 config.compression = CompressionAlgorithm::Lz4;
380 config.compression_threshold = 10;
381 let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
382
383 peer.enqueue_command(ProtocolCommand::SendReliable {
385 channel_id: 0,
386 sequence: 0,
387 ordered: true,
388 data: vec![1; 100].into(), });
390
391 let encoded = peer.encode_queued_commands().unwrap();
392
393 assert_eq!(peer.statistics().bytes_sent, encoded.len() as u64);
395 assert!(peer.statistics().bytes_sent < 100); }
397
398 #[test]
399 fn test_statistics_with_checksums() {
400 let mut config = Config::default();
401 config.use_checksums = true;
402 let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
403
404 peer.enqueue_command(ProtocolCommand::SendReliable {
405 channel_id: 0,
406 sequence: 0,
407 ordered: true,
408 data: vec![1, 2, 3].into(),
409 });
410
411 let encoded = peer.encode_queued_commands().unwrap();
412
413 assert_eq!(peer.statistics().bytes_sent, encoded.len() as u64);
415 assert!(encoded.len() >= 4); }
417
418 #[test]
419 fn test_mtu_boundary_calculation() {
420 let mut config = Config::default();
421 config.use_checksums = false;
422 config.compression = bitfold_core::config::CompressionAlgorithm::None;
423 let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
424
425 let max_payload_for_mtu = 1429;
434
435 peer.enqueue_command(ProtocolCommand::SendReliable {
437 channel_id: 0,
438 sequence: 0,
439 ordered: true,
440 data: vec![42u8; max_payload_for_mtu].into(),
441 });
442
443 let result = peer.encode_queued_commands_bounded(1440);
445 assert!(result.is_ok());
446 let encoded = result.unwrap();
447 assert!(encoded.is_some(), "Command should fit within MTU");
448
449 let bytes = encoded.unwrap();
450 assert_eq!(bytes.len(), 1440, "Encoded size should be exactly 1440 bytes");
451
452 assert!(!peer.has_queued_commands());
454 }
455
456 #[test]
457 fn test_mtu_boundary_too_large() {
458 let mut config = Config::default();
459 config.use_checksums = false;
460 config.compression = bitfold_core::config::CompressionAlgorithm::None;
461 let mut peer = Peer::new(get_fake_addr(), &config, Instant::now());
462
463 let too_large_payload = 1430; peer.enqueue_command(ProtocolCommand::SendReliable {
467 channel_id: 0,
468 sequence: 0,
469 ordered: true,
470 data: vec![42u8; too_large_payload].into(),
471 });
472
473 let result = peer.encode_queued_commands_bounded(1440);
475 assert!(result.is_ok());
476 let encoded = result.unwrap();
477 assert!(encoded.is_none(), "Command should NOT fit within MTU");
478
479 assert!(peer.has_queued_commands());
481 }
482}