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}