Skip to main content

corevpn_protocol/
session.rs

1//! Protocol Session Management
2
3use std::time::{Duration, Instant};
4
5use bytes::Bytes;
6
7use corevpn_crypto::{CipherSuite, KeyMaterial};
8
9use crate::{
10    KeyId, OpCode, Packet, DataPacket, DataChannel,
11    ReliableTransport, ReliableConfig, TlsRecordReassembler,
12    ProtocolError, Result,
13};
14use crate::packet::ControlPacketData;
15
16/// Protocol session state
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ProtocolState {
19    /// Initial state, waiting for client hello
20    Initial,
21    /// TLS handshake in progress
22    TlsHandshake,
23    /// Key exchange in progress
24    KeyExchange,
25    /// Authentication in progress
26    Authenticating,
27    /// Session fully established
28    Established,
29    /// Rekeying in progress
30    Rekeying,
31    /// Session terminated
32    Terminated,
33}
34
35/// Session ID type (8 bytes)
36pub type SessionIdBytes = [u8; 8];
37
38/// Replay window for tls-auth packet IDs
39/// Uses a 64-bit bitmap to track the last 64 packet IDs
40struct ReplayWindow {
41    /// Highest seen packet ID
42    highest: u32,
43    /// Bitmap of recently seen packets (relative to highest)
44    /// Bit 0 = highest, bit N = highest - N
45    bitmap: u64,
46}
47
48impl ReplayWindow {
49    /// Window size in packets (64 bits = 64 packet tracking)
50    const WINDOW_SIZE: u32 = 64;
51
52    fn new() -> Self {
53        Self {
54            highest: 0,
55            bitmap: 0,
56        }
57    }
58
59    /// Check if packet ID is valid (not replayed) and update window
60    ///
61    /// Returns true if the packet should be processed, false if it's a replay
62    /// or too old.
63    fn check_and_update(&mut self, packet_id: u32) -> bool {
64        // Packet ID 0 is invalid (counter starts at 1)
65        if packet_id == 0 {
66            return false;
67        }
68
69        if packet_id > self.highest {
70            // New highest packet - advance window
71            let shift = packet_id - self.highest;
72
73            if shift >= Self::WINDOW_SIZE {
74                // Packet is way ahead, clear entire window
75                self.bitmap = 1; // Only mark current packet
76            } else {
77                // Shift window and mark current packet
78                self.bitmap = (self.bitmap << shift) | 1;
79            }
80            self.highest = packet_id;
81            true
82        } else {
83            // Packet is at or before highest
84            let diff = self.highest - packet_id;
85
86            // Check if packet is within window
87            if diff >= Self::WINDOW_SIZE {
88                return false; // Too old
89            }
90
91            // Check if already seen using bit test
92            let mask = 1u64 << diff;
93            if self.bitmap & mask != 0 {
94                return false; // Replay detected
95            }
96
97            // Mark as seen
98            self.bitmap |= mask;
99            true
100        }
101    }
102
103    /// Reset the replay window (e.g., for key renegotiation)
104    fn reset(&mut self) {
105        self.highest = 0;
106        self.bitmap = 0;
107    }
108}
109
110/// Protocol session
111pub struct ProtocolSession {
112    /// Local session ID
113    local_session_id: SessionIdBytes,
114    /// Remote session ID
115    remote_session_id: Option<SessionIdBytes>,
116    /// Current protocol state
117    state: ProtocolState,
118    /// Current key ID
119    current_key_id: KeyId,
120    /// Reliable transport for control channel
121    reliable: ReliableTransport,
122    /// TLS record reassembler
123    tls_reassembler: TlsRecordReassembler,
124    /// Data channels (one per key ID)
125    data_channels: [Option<DataChannel>; 8],
126    /// Peer ID (for P_DATA_V2)
127    peer_id: Option<u32>,
128    /// Use tls-auth
129    use_tls_auth: bool,
130    /// tls-auth key
131    tls_auth_key: Option<corevpn_crypto::HmacAuth>,
132    /// Replay window for tls-auth packet IDs
133    replay_window: ReplayWindow,
134    /// Outgoing tls-auth packet ID counter (for replay protection)
135    tls_auth_packet_id: u32,
136    /// Session creation time
137    created_at: Instant,
138    /// Last activity time
139    last_activity: Instant,
140    /// Cipher suite to use
141    cipher_suite: CipherSuite,
142}
143
144impl ProtocolSession {
145    /// Create a new server-side session
146    pub fn new_server(cipher_suite: CipherSuite) -> Self {
147        Self {
148            local_session_id: corevpn_crypto::generate_session_id(),
149            remote_session_id: None,
150            state: ProtocolState::Initial,
151            current_key_id: KeyId::default(),
152            reliable: ReliableTransport::new(ReliableConfig::default()),
153            tls_reassembler: TlsRecordReassembler::new(65536),
154            data_channels: Default::default(),
155            peer_id: None,
156            use_tls_auth: false,
157            tls_auth_key: None,
158            replay_window: ReplayWindow::new(),
159            tls_auth_packet_id: 1, // OpenVPN packet IDs start at 1 (0 is invalid)
160            created_at: Instant::now(),
161            last_activity: Instant::now(),
162            cipher_suite,
163        }
164    }
165
166    /// Create a new client-side session
167    pub fn new_client(cipher_suite: CipherSuite) -> Self {
168        let mut session = Self::new_server(cipher_suite);
169        session.state = ProtocolState::Initial;
170        session
171    }
172
173    /// Get local session ID
174    pub fn local_session_id(&self) -> &SessionIdBytes {
175        &self.local_session_id
176    }
177
178    /// Get remote session ID
179    pub fn remote_session_id(&self) -> Option<&SessionIdBytes> {
180        self.remote_session_id.as_ref()
181    }
182
183    /// Get current state
184    pub fn state(&self) -> ProtocolState {
185        self.state
186    }
187
188    /// Set state
189    pub fn set_state(&mut self, state: ProtocolState) {
190        self.state = state;
191        self.last_activity = Instant::now();
192    }
193
194    /// Set remote session ID
195    pub fn set_remote_session_id(&mut self, id: SessionIdBytes) {
196        self.remote_session_id = Some(id);
197    }
198
199    /// Enable tls-auth
200    pub fn set_tls_auth(&mut self, key: corevpn_crypto::HmacAuth) {
201        self.use_tls_auth = true;
202        self.tls_auth_key = Some(key);
203    }
204
205    /// Update the cipher suite (e.g., after NCP cipher negotiation)
206    pub fn set_cipher_suite(&mut self, cipher_suite: CipherSuite) {
207        self.cipher_suite = cipher_suite;
208    }
209
210    /// Process incoming packet
211    pub fn process_packet(&mut self, data: &[u8]) -> Result<ProcessedPacket> {
212        self.last_activity = Instant::now();
213
214        // Verify HMAC if tls-auth enabled for control packets
215        if self.use_tls_auth {
216            if let Some(key) = &self.tls_auth_key {
217                if !data.is_empty() && OpCode::from_byte(data[0])?.is_control() {
218                    // OpenVPN tls-auth wire format:
219                    // [opcode(1)] [session_id(8)] [HMAC(32)] [pid(4)] [time(4)] [rest...]
220                    //
221                    // HMAC covers (via swap_hmac rearrangement):
222                    // [pid(4)] [time(4)] [opcode(1)] [session_id(8)] [rest...]
223                    // = data[41..49] ++ data[0..9] ++ data[49..]
224                    if data.len() < 49 {
225                        return Err(ProtocolError::PacketTooShort {
226                            expected: 49,
227                            got: data.len(),
228                        });
229                    }
230
231                    let mut hmac = [0u8; 32];
232                    hmac.copy_from_slice(&data[9..41]); // HMAC at offset 9
233
234                    // Build HMAC input: pid + time + opcode + session_id + rest
235                    let mut hmac_input = Vec::with_capacity(8 + 9 + data.len() - 49);
236                    hmac_input.extend_from_slice(&data[41..49]); // pid(4) + time(4)
237                    hmac_input.extend_from_slice(&data[0..9]);   // opcode(1) + session_id(8)
238                    hmac_input.extend_from_slice(&data[49..]);   // rest
239
240                    key.verify(&hmac_input, &hmac)?;
241                }
242            }
243        }
244
245        let packet = Packet::parse(data, self.use_tls_auth)?;
246
247        match packet {
248            Packet::Control(ctrl) => self.process_control_packet(ctrl),
249            Packet::Data(data_pkt) => self.process_data_packet(data_pkt),
250        }
251    }
252
253    fn process_control_packet(&mut self, ctrl: ControlPacketData) -> Result<ProcessedPacket> {
254        // Check replay protection for tls-auth packets
255        if self.use_tls_auth {
256            if let Some(packet_id) = ctrl.header.packet_id {
257                if !self.replay_window.check_and_update(packet_id) {
258                    return Err(ProtocolError::ReplayDetected);
259                }
260            }
261        }
262
263        // Process ACKs
264        if !ctrl.acks.is_empty() {
265            self.reliable.process_acks(&ctrl.acks);
266        }
267
268        // Handle different opcodes
269        match ctrl.header.opcode {
270            OpCode::HardResetClientV2 | OpCode::HardResetClientV3 => {
271                // Client initiating connection
272                // Security: Validate session ID - generate new one instead of accepting client's
273                // This prevents session fixation attacks
274                if let Some(remote_sid) = ctrl.header.session_id {
275                    // Validate session ID is not all zeros or obviously malicious
276                    if remote_sid == [0; 8] {
277                        return Err(ProtocolError::InvalidSessionId);
278                    }
279                    // Accept the session ID but we'll use our own for the response
280                    self.remote_session_id = Some(remote_sid);
281                }
282
283                // Queue ACK for the client's hard reset packet via the reliable
284                // transport so it will be included in our response
285                if let Some(packet_id) = ctrl.message_packet_id {
286                    let _ = self.reliable.receive(packet_id, Bytes::new())?;
287                }
288
289                self.state = ProtocolState::TlsHandshake;
290
291                Ok(ProcessedPacket::HardReset {
292                    session_id: self.local_session_id,
293                })
294            }
295            OpCode::HardResetServerV2 => {
296                // Server response to hard reset
297                if let Some(remote_sid) = ctrl.header.session_id {
298                    self.remote_session_id = Some(remote_sid);
299                }
300                Ok(ProcessedPacket::HardResetAck)
301            }
302            OpCode::ControlV1 => {
303                // TLS data
304                if let Some(packet_id) = ctrl.message_packet_id {
305                    if let Some(data) = self.reliable.receive(packet_id, ctrl.payload.clone())? {
306                        self.tls_reassembler.add(&data)?;
307                        let records = self.tls_reassembler.extract_records();
308                        if !records.is_empty() {
309                            return Ok(ProcessedPacket::TlsData(records));
310                        }
311                    }
312                }
313                Ok(ProcessedPacket::None)
314            }
315            OpCode::AckV1 => {
316                // Pure ACK, already processed above
317                Ok(ProcessedPacket::None)
318            }
319            OpCode::SoftResetV1 => {
320                // Key renegotiation
321                self.state = ProtocolState::Rekeying;
322                Ok(ProcessedPacket::SoftReset)
323            }
324            _ => Err(ProtocolError::UnknownOpcode(ctrl.header.opcode as u8)),
325        }
326    }
327
328    fn process_data_packet(&mut self, data_pkt: crate::packet::DataPacketData) -> Result<ProcessedPacket> {
329        let packet = DataPacket {
330            key_id: data_pkt.header.key_id,
331            peer_id: data_pkt.peer_id,
332            payload: data_pkt.payload,
333        };
334
335        let key_id = packet.key_id.0 as usize;
336        if let Some(channel) = &mut self.data_channels[key_id] {
337            let decrypted = channel.decrypt(&packet)?;
338            Ok(ProcessedPacket::Data(decrypted))
339        } else {
340            Err(ProtocolError::KeyNotAvailable(packet.key_id.0))
341        }
342    }
343
344    /// Create a hard reset response packet
345    pub fn create_hard_reset_response(&mut self) -> Result<Bytes> {
346        // Register with reliable transport to get a message_packet_id.
347        // OpenVPN requires all control packets (including hard resets) to
348        // carry a message_packet_id for the reliable transport layer.
349        let (packet_id, _) = self.reliable.send(Bytes::new())?;
350
351        let packet = crate::packet::ControlPacketData {
352            header: crate::PacketHeader {
353                opcode: OpCode::HardResetServerV2,
354                key_id: KeyId::default(),
355                session_id: Some(self.local_session_id),
356                hmac: None,
357                packet_id: None,
358                timestamp: None,
359            },
360            remote_session_id: self.remote_session_id,
361            acks: self.reliable.get_acks(),
362            message_packet_id: Some(packet_id),
363            payload: Bytes::new(),
364        };
365
366        let serialized = Packet::Control(packet).serialize();
367        Ok(self.maybe_wrap_tls_auth(serialized.freeze()))
368    }
369
370    /// Maximum payload size for a single control channel packet.
371    /// OpenVPN splits TLS data across multiple control packets to stay within
372    /// the link MTU. We use 1100 bytes to leave room for:
373    /// - 1 byte opcode + 8 bytes session_id + 1 byte ack_count
374    /// - up to ~12 bytes ACKs + remote_session_id
375    /// - 4 bytes message_packet_id
376    /// - 20 bytes HMAC (if tls-auth)
377    /// - 8 bytes packet_id + timestamp (if tls-auth)
378    /// - 20 bytes IP header + 8 bytes UDP header
379    /// Total overhead ~82 bytes, so 1100 + 82 = 1182 < 1500 MTU
380    const MAX_CONTROL_PAYLOAD: usize = 1100;
381
382    /// Create control packets with TLS data, splitting across multiple
383    /// packets if the data exceeds the maximum control channel payload size.
384    /// This prevents oversized UDP datagrams that would require IP fragmentation.
385    pub fn create_control_packets(&mut self, tls_data: Bytes) -> Result<Vec<Bytes>> {
386        let mut packets = Vec::new();
387
388        if tls_data.len() <= Self::MAX_CONTROL_PAYLOAD {
389            // Data fits in a single packet
390            packets.push(self.create_single_control_packet(tls_data)?);
391        } else {
392            // Split data across multiple packets
393            let mut offset = 0;
394            while offset < tls_data.len() {
395                let end = std::cmp::min(offset + Self::MAX_CONTROL_PAYLOAD, tls_data.len());
396                let chunk = tls_data.slice(offset..end);
397                packets.push(self.create_single_control_packet(chunk)?);
398                offset = end;
399            }
400        }
401
402        Ok(packets)
403    }
404
405    /// Create a single control packet with TLS data (internal helper)
406    fn create_single_control_packet(&mut self, tls_data: Bytes) -> Result<Bytes> {
407        let (packet_id, _) = self.reliable.send(tls_data.clone())?;
408
409        let packet = crate::packet::ControlPacketData {
410            header: crate::PacketHeader {
411                opcode: OpCode::ControlV1,
412                key_id: self.current_key_id,
413                session_id: Some(self.local_session_id),
414                hmac: None,
415                packet_id: None,
416                timestamp: None,
417            },
418            remote_session_id: self.remote_session_id,
419            acks: self.reliable.get_acks(),
420            message_packet_id: Some(packet_id),
421            payload: tls_data,
422        };
423
424        let serialized = Packet::Control(packet).serialize();
425        Ok(self.maybe_wrap_tls_auth(serialized.freeze()))
426    }
427
428    /// Create a control packet with TLS data (convenience for single packet)
429    pub fn create_control_packet(&mut self, tls_data: Bytes) -> Result<Bytes> {
430        self.create_single_control_packet(tls_data)
431    }
432
433    /// Create an ACK packet
434    pub fn create_ack_packet(&mut self) -> Option<Bytes> {
435        let acks = self.reliable.get_acks();
436        if acks.is_empty() {
437            return None;
438        }
439
440        let packet = crate::packet::ControlPacketData {
441            header: crate::PacketHeader {
442                opcode: OpCode::AckV1,
443                key_id: self.current_key_id,
444                session_id: Some(self.local_session_id),
445                hmac: None,
446                packet_id: None,
447                timestamp: None,
448            },
449            remote_session_id: self.remote_session_id,
450            acks,
451            message_packet_id: None,
452            payload: Bytes::new(),
453        };
454
455        self.reliable.ack_sent();
456        let serialized = Packet::Control(packet).serialize();
457        Some(self.maybe_wrap_tls_auth(serialized.freeze()))
458    }
459
460    /// Install data channel keys
461    pub fn install_keys(&mut self, key_material: &KeyMaterial, is_server: bool) {
462        let key_id = self.current_key_id;
463        let idx = key_id.0 as usize;
464
465        let (encrypt_key, decrypt_key) = if is_server {
466            (
467                key_material.server_data_key(self.cipher_suite),
468                key_material.client_data_key(self.cipher_suite),
469            )
470        } else {
471            (
472                key_material.client_data_key(self.cipher_suite),
473                key_material.server_data_key(self.cipher_suite),
474            )
475        };
476
477        // Use V2 data packets only if we have a negotiated peer_id.
478        // When peer_id is None, both sides use P_DATA_V1 where the
479        // opcode is NOT included in the AEAD AAD.
480        let use_v2 = self.peer_id.is_some();
481        self.data_channels[idx] = Some(DataChannel::new(
482            key_id,
483            encrypt_key,
484            decrypt_key,
485            use_v2,
486            self.peer_id,
487        ));
488    }
489
490    /// Encrypt data for transmission
491    pub fn encrypt_data(&mut self, data: &[u8]) -> Result<Bytes> {
492        let idx = self.current_key_id.0 as usize;
493        if let Some(channel) = &mut self.data_channels[idx] {
494            let packet = channel.encrypt(data)?;
495            Ok(packet.serialize().freeze())
496        } else {
497            Err(ProtocolError::KeyNotAvailable(self.current_key_id.0))
498        }
499    }
500
501    /// Get packets needing retransmission
502    pub fn get_retransmits(&mut self) -> Vec<Bytes> {
503        self.reliable
504            .get_retransmits()
505            .into_iter()
506            .map(|(id, data)| {
507                // Rebuild packet with same ID
508                let packet = crate::packet::ControlPacketData {
509                    header: crate::PacketHeader {
510                        opcode: OpCode::ControlV1,
511                        key_id: self.current_key_id,
512                        session_id: Some(self.local_session_id),
513                        hmac: None,
514                        packet_id: None,
515                        timestamp: None,
516                    },
517                    remote_session_id: self.remote_session_id,
518                    acks: vec![],
519                    message_packet_id: Some(id),
520                    payload: data,
521                };
522                let serialized = Packet::Control(packet).serialize();
523                self.maybe_wrap_tls_auth(serialized.freeze())
524            })
525            .collect()
526    }
527
528    /// Check if we should send an ACK
529    pub fn should_send_ack(&self) -> bool {
530        self.reliable.should_send_ack()
531    }
532
533    /// Get next timeout
534    pub fn next_timeout(&self) -> Option<Duration> {
535        self.reliable.next_timeout()
536    }
537
538    /// Check if session is established
539    pub fn is_established(&self) -> bool {
540        self.state == ProtocolState::Established
541    }
542
543    /// Get session duration
544    pub fn duration(&self) -> Duration {
545        self.created_at.elapsed()
546    }
547
548    /// Get idle time
549    pub fn idle_time(&self) -> Duration {
550        self.last_activity.elapsed()
551    }
552
553    fn maybe_wrap_tls_auth(&mut self, data: Bytes) -> Bytes {
554        if self.use_tls_auth {
555            if let Some(key) = &self.tls_auth_key {
556                // OpenVPN tls-auth wire format:
557                // [opcode(1)] [session_id(8)] [HMAC(32)] [pid(4)] [time(4)] [rest...]
558                //
559                // Input `data` is a serialized packet: [opcode(1)] [session_id(8)] [rest...]
560                //
561                // HMAC is computed over (internal/swap format):
562                //   [pid(4)] [time(4)] [opcode(1)] [session_id(8)] [rest...]
563                // Which equals: pid_bytes + time_bytes + entire_input_data
564                //
565                // Wire output: data[0..9] + HMAC + pid_bytes + time_bytes + data[9..]
566
567                if data.len() < 9 {
568                    return data;
569                }
570
571                // Allocate outgoing packet ID
572                let packet_id = self.tls_auth_packet_id;
573                self.tls_auth_packet_id += 1;
574
575                // Current timestamp
576                let timestamp = std::time::SystemTime::now()
577                    .duration_since(std::time::UNIX_EPOCH)
578                    .unwrap_or_default()
579                    .as_secs() as u32;
580
581                let pid_bytes = packet_id.to_be_bytes();
582                let time_bytes = timestamp.to_be_bytes();
583
584                // Build HMAC input: pid + time + entire original packet
585                // (This matches OpenVPN's swap_hmac internal format)
586                let mut hmac_input = Vec::with_capacity(8 + data.len());
587                hmac_input.extend_from_slice(&pid_bytes);
588                hmac_input.extend_from_slice(&time_bytes);
589                hmac_input.extend_from_slice(&data); // opcode + session_id + rest
590
591                let hmac = key.authenticate(&hmac_input);
592
593                // Build wire format: [opcode+session_id] [HMAC] [pid] [time] [rest]
594                let mut output = Vec::with_capacity(data.len() + 32 + 8);
595                output.extend_from_slice(&data[0..9]);       // opcode(1) + session_id(8)
596                output.extend_from_slice(&hmac);              // HMAC(32)
597                output.extend_from_slice(&pid_bytes);         // pid(4)
598                output.extend_from_slice(&time_bytes);        // time(4)
599                output.extend_from_slice(&data[9..]);         // rest (ack_stuff, msg_pid, payload)
600
601                return Bytes::from(output);
602            }
603        }
604        data
605    }
606
607    /// Rotate to next key ID (for rekeying)
608    pub fn rotate_key(&mut self) {
609        self.current_key_id = self.current_key_id.next();
610        // Reset replay window on key rotation
611        self.replay_window.reset();
612    }
613}
614
615/// Result of processing a packet
616#[derive(Debug)]
617pub enum ProcessedPacket {
618    /// No action needed
619    None,
620    /// Hard reset from client
621    HardReset {
622        /// Session ID for the new connection
623        session_id: SessionIdBytes,
624    },
625    /// Hard reset acknowledged
626    HardResetAck,
627    /// TLS records to process
628    TlsData(Vec<Bytes>),
629    /// Decrypted data packet
630    Data(Bytes),
631    /// Soft reset (rekey)
632    SoftReset,
633}
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638
639    #[test]
640    fn test_session_creation() {
641        let session = ProtocolSession::new_server(CipherSuite::ChaCha20Poly1305);
642        assert_eq!(session.state(), ProtocolState::Initial);
643        assert!(session.remote_session_id().is_none());
644    }
645
646    #[test]
647    fn test_hard_reset() {
648        let mut session = ProtocolSession::new_server(CipherSuite::ChaCha20Poly1305);
649
650        // Simulate receiving hard reset from client
651        let hard_reset = [
652            0x38, // opcode=7 (HardResetClientV2), key_id=0
653            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // session_id
654            0x00, // ack_count = 0
655            0x00, 0x00, 0x00, 0x00, // message_packet_id = 0
656        ];
657
658        let result = session.process_packet(&hard_reset).unwrap();
659        matches!(result, ProcessedPacket::HardReset { .. });
660        assert_eq!(session.state(), ProtocolState::TlsHandshake);
661    }
662
663    #[test]
664    fn test_hard_reset_response_has_packet_id() {
665        let mut session = ProtocolSession::new_server(CipherSuite::ChaCha20Poly1305);
666
667        // Process client hard reset first
668        let hard_reset = [
669            0x38, // opcode=7 (HardResetClientV2), key_id=0
670            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // session_id
671            0x00, // ack_count = 0
672            0x00, 0x00, 0x00, 0x00, // message_packet_id = 0
673        ];
674        session.process_packet(&hard_reset).unwrap();
675
676        // Create response and verify it contains message_packet_id
677        let response = session.create_hard_reset_response().unwrap();
678
679        // Response format (no tls-auth):
680        // [0]    opcode + key_id (HardResetServerV2 = 0x40)
681        // [1-8]  session_id (8 bytes)
682        // [9]    ack_count (should be 1 - ACK of client's packet 0)
683        // [10-13] ack_id[0] (4 bytes, value 0)
684        // [14-21] remote_session_id (8 bytes)
685        // [22-25] message_packet_id (4 bytes, value 0)
686        assert!(response.len() >= 26, "Response too short: {} bytes", response.len());
687
688        // Verify opcode is HardResetServerV2 (opcode=8, key_id=0 → 0x40)
689        assert_eq!(response[0], 0x40);
690
691        // Verify ack_count = 1 (ACK of client's hard reset)
692        assert_eq!(response[9], 1);
693
694        // Verify ACK'd packet_id = 0
695        let ack_id = u32::from_be_bytes(response[10..14].try_into().unwrap());
696        assert_eq!(ack_id, 0);
697
698        // Verify message_packet_id = 0 (first outgoing packet)
699        let msg_pid = u32::from_be_bytes(response[22..26].try_into().unwrap());
700        assert_eq!(msg_pid, 0);
701    }
702}