bitfold_protocol/command.rs
1//! Protocol command types for command-based architecture.
2//!
3//! Everything is a command: sending data, acknowledging packets,
4//! pinging, disconnecting, etc. Commands are aggregated into larger packets
5//! to improve bandwidth utilization.
6
7use std::time::Instant;
8
9use bitfold_core::shared::SharedBytes;
10
11/// Protocol commands that can be sent between peers.
12///
13/// All protocol operations are represented as discrete commands that can be aggregated.
14#[derive(Debug, Clone, PartialEq)]
15pub enum ProtocolCommand {
16 /// Send reliable data on a channel
17 SendReliable {
18 /// Channel identifier (0-255)
19 channel_id: u8,
20 /// Sequence number for ordering
21 sequence: u16,
22 /// Whether to deliver in order on receive (true) or unordered (false)
23 ordered: bool,
24 /// Payload data (shared, sliceable)
25 data: SharedBytes,
26 },
27
28 /// Send unreliable data on a channel
29 SendUnreliable {
30 /// Channel identifier (0-255)
31 channel_id: u8,
32 /// Payload data (shared slice)
33 data: SharedBytes,
34 },
35
36 /// Send sequenced unreliable data (drops old packets)
37 SendUnreliableSequenced {
38 /// Channel identifier (0-255)
39 channel_id: u8,
40 /// Sequence number for dropping old packets
41 sequence: u16,
42 /// Payload data (shared slice)
43 data: SharedBytes,
44 },
45
46 /// Send unsequenced unreliable data (prevents duplicates without ordering)
47 SendUnsequenced {
48 /// Channel identifier (0-255)
49 channel_id: u8,
50 /// Unsequenced group identifier for duplicate detection
51 unsequenced_group: u16,
52 /// Payload data (shared slice)
53 data: SharedBytes,
54 },
55
56 /// Fragment of a larger reliable packet
57 SendFragment {
58 /// Channel identifier (0-255)
59 channel_id: u8,
60 /// Sequence number of the original packet
61 sequence: u16,
62 /// Whether to deliver in order on receive (true) or unordered (false)
63 ordered: bool,
64 /// Fragment index (0-based)
65 fragment_id: u8,
66 /// Total number of fragments
67 fragment_count: u8,
68 /// Fragment data (shared slice)
69 data: SharedBytes,
70 },
71
72 /// Fragment of a larger unreliable packet
73 SendUnreliableFragment {
74 /// Channel identifier (0-255)
75 channel_id: u8,
76 /// Sequence number of the original packet (for reassembly)
77 sequence: u16,
78 /// Fragment index (0-based)
79 fragment_id: u8,
80 /// Total number of fragments
81 fragment_count: u8,
82 /// Fragment data (shared slice)
83 data: SharedBytes,
84 },
85
86 /// Acknowledge received reliable packets
87 Acknowledge {
88 /// Sequence number being acknowledged
89 sequence: u16,
90 /// Bitfield of additional acknowledged packets (32 packets before sequence)
91 received_mask: u32,
92 /// Timestamp for RTT calculation (optional)
93 sent_time: Option<u32>,
94 },
95
96 /// Ping to measure RTT and keep connection alive
97 Ping {
98 /// Timestamp (milliseconds since epoch or relative)
99 timestamp: u32,
100 },
101
102 /// Pong response to ping
103 Pong {
104 /// Original timestamp from ping
105 timestamp: u32,
106 },
107
108 /// Request to establish connection (3-way handshake step 1)
109 Connect {
110 /// Number of channels to allocate
111 channels: u8,
112 /// Maximum transmission unit
113 mtu: u16,
114 /// Protocol version
115 protocol_version: u16,
116 /// Outgoing session ID from client
117 outgoing_session_id: u16,
118 /// Connect ID for replay protection
119 connect_id: u32,
120 },
121
122 /// Verify connection (3-way handshake step 2) - replaces old ConnectAck
123 VerifyConnect {
124 /// Assigned peer ID
125 peer_id: u16,
126 /// Channels allocated
127 channels: u8,
128 /// Maximum transmission unit
129 mtu: u16,
130 /// Incoming session ID (from server's perspective)
131 incoming_session_id: u16,
132 /// Outgoing session ID (from server's perspective)
133 outgoing_session_id: u16,
134 /// Window size for flow control
135 window_size: u32,
136 },
137
138 /// Request to disconnect
139 Disconnect {
140 /// Reason code (application-defined)
141 reason: u32,
142 },
143
144 /// Bandwidth limit notification
145 BandwidthLimit {
146 /// Incoming bandwidth limit (bytes/sec, 0 = unlimited)
147 incoming: u32,
148 /// Outgoing bandwidth limit (bytes/sec, 0 = unlimited)
149 outgoing: u32,
150 },
151
152 /// Throttle configuration for congestion control
153 ThrottleConfigure {
154 /// Throttle interval in milliseconds
155 interval: u32,
156 /// Throttle acceleration rate
157 acceleration: u32,
158 /// Throttle deceleration rate
159 deceleration: u32,
160 },
161
162 /// Path MTU probe: request to test a payload of given size
163 PMTUProbe {
164 /// Probe payload size in bytes
165 size: u16,
166 /// Correlation token
167 token: u32,
168 /// Probe payload (shared slice) sized to `size`
169 payload: SharedBytes,
170 },
171
172 /// Path MTU reply: response to a PMTU probe
173 PMTUReply {
174 /// Echoed probe size
175 size: u16,
176 /// Echoed token
177 token: u32,
178 },
179}
180
181impl ProtocolCommand {
182 /// Returns the command type identifier for serialization
183 pub fn command_type(&self) -> u8 {
184 match self {
185 ProtocolCommand::SendReliable { .. } => 1,
186 ProtocolCommand::SendUnreliable { .. } => 2,
187 ProtocolCommand::SendUnreliableSequenced { .. } => 3,
188 ProtocolCommand::SendUnsequenced { .. } => 4,
189 ProtocolCommand::SendFragment { .. } => 5,
190 ProtocolCommand::SendUnreliableFragment { .. } => 6,
191 ProtocolCommand::Acknowledge { .. } => 7,
192 ProtocolCommand::Ping { .. } => 8,
193 ProtocolCommand::Pong { .. } => 9,
194 ProtocolCommand::Connect { .. } => 10,
195 ProtocolCommand::VerifyConnect { .. } => 11,
196 ProtocolCommand::Disconnect { .. } => 12,
197 ProtocolCommand::BandwidthLimit { .. } => 13,
198 ProtocolCommand::ThrottleConfigure { .. } => 14,
199 ProtocolCommand::PMTUProbe { .. } => 15,
200 ProtocolCommand::PMTUReply { .. } => 16,
201 }
202 }
203
204 /// Returns true if this command requires reliable delivery
205 pub fn is_reliable(&self) -> bool {
206 matches!(
207 self,
208 ProtocolCommand::SendReliable { .. }
209 | ProtocolCommand::SendFragment { .. }
210 | ProtocolCommand::Connect { .. }
211 | ProtocolCommand::VerifyConnect { .. }
212 | ProtocolCommand::Disconnect { .. }
213 )
214 }
215
216 /// Returns the channel ID if this is a data command
217 pub fn channel_id(&self) -> Option<u8> {
218 match self {
219 ProtocolCommand::SendReliable { channel_id, .. }
220 | ProtocolCommand::SendUnreliable { channel_id, .. }
221 | ProtocolCommand::SendUnreliableSequenced { channel_id, .. }
222 | ProtocolCommand::SendUnsequenced { channel_id, .. }
223 | ProtocolCommand::SendFragment { channel_id, .. }
224 | ProtocolCommand::SendUnreliableFragment { channel_id, .. } => Some(*channel_id),
225 _ => None,
226 }
227 }
228}
229
230/// Aggregated packet containing multiple protocol commands.
231///
232/// Aggregates commands into larger packets to reduce overhead
233/// and improve bandwidth utilization.
234#[derive(Debug, Clone)]
235pub struct CommandPacket {
236 /// Protocol commands in this packet
237 pub commands: Vec<ProtocolCommand>,
238 /// Timestamp when packet was created
239 pub timestamp: Instant,
240}
241
242impl CommandPacket {
243 /// Creates a new empty command packet
244 pub fn new() -> Self {
245 Self { commands: Vec::new(), timestamp: Instant::now() }
246 }
247
248 /// Creates a command packet with a single command
249 pub fn single(command: ProtocolCommand) -> Self {
250 Self { commands: vec![command], timestamp: Instant::now() }
251 }
252
253 /// Adds a command to this packet
254 pub fn add_command(&mut self, command: ProtocolCommand) {
255 self.commands.push(command);
256 }
257
258 /// Returns true if the packet has no commands
259 pub fn is_empty(&self) -> bool {
260 self.commands.is_empty()
261 }
262
263 /// Returns the number of commands in this packet
264 pub fn len(&self) -> usize {
265 self.commands.len()
266 }
267}
268
269impl Default for CommandPacket {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_command_types() {
281 use bitfold_core::shared::SharedBytes;
282 let cmd = ProtocolCommand::SendReliable {
283 channel_id: 0,
284 sequence: 1,
285 ordered: true,
286 data: SharedBytes::from_vec(vec![1, 2, 3]),
287 };
288 assert_eq!(cmd.command_type(), 1);
289 assert!(cmd.is_reliable());
290 assert_eq!(cmd.channel_id(), Some(0));
291 }
292
293 #[test]
294 fn test_command_packet_aggregation() {
295 let mut packet = CommandPacket::new();
296 assert!(packet.is_empty());
297
298 packet.add_command(ProtocolCommand::Ping { timestamp: 100 });
299 packet.add_command(ProtocolCommand::SendUnreliable {
300 channel_id: 0,
301 data: SharedBytes::from_vec(vec![1, 2, 3]),
302 });
303
304 assert_eq!(packet.len(), 2);
305 assert!(!packet.is_empty());
306 }
307}