Skip to main content

pwr_core/
protocol.rs

1//! Wire protocol message types shared between client and server.
2//!
3//! Control messages are serialized as JSON in framed envelopes (see `frame.rs`).
4//! File data is streamed as raw bytes outside the framing layer.
5//!
6//! ## Message flow
7//!
8//! ```text
9//! Client                                 Server
10//!   |                                      |
11//!   |--- Handshake ----------------------->|
12//!   |<-- HandshakeAck ---------------------|
13//!   |                                      |
14//!   |--- ArchiveRequest ------------------>|
15//!   |<-- ArchiveAccept --------------------|
16//!   |--- [FileHeader + raw chunks]* ------>|
17//!   |--- ArchiveComplete ----------------->|
18//!   |                                      |
19//!   |--- RestoreRequest ------------------>|
20//!   |<-- RestoreAccept --------------------|
21//!   |<-- [FileHeader + raw chunks]* -------|
22//!   |<-- RestoreComplete ------------------|
23//!   |                                      |
24//!   |--- StatusRequest ------------------->|
25//!   |<-- StatusResponse -------------------|
26//! ```
27
28use chrono::{DateTime, Utc};
29use serde::{Deserialize, Serialize};
30use uuid::Uuid;
31
32use crate::error::{PwrError, Result};
33
34/// Protocol version constant. Both client and server must agree on this.
35pub const PROTOCOL_VERSION: u8 = 0x01;
36
37/// Default chunk size for file streaming: 1 MiB.
38pub const CHUNK_SIZE: usize = 1024 * 1024;
39
40// =========================================================================
41// Message type identifiers
42// =========================================================================
43
44/// Identifies the type of a protocol message in the frame header.
45///
46/// Each variant corresponds to a payload struct below. The discriminants
47/// are assigned explicitly so they remain stable across code changes.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[repr(u8)]
50pub enum MessageType {
51    // Handshake
52    Handshake = 0x01,
53    HandshakeAck = 0x02,
54
55    // Archive flow (client → server)
56    ArchiveRequest = 0x10,
57    ArchiveAccept = 0x11,
58    ArchiveComplete = 0x12,
59
60    // Restore flow (client → server)
61    RestoreRequest = 0x20,
62    RestoreAccept = 0x21,
63    RestoreComplete = 0x22,
64
65    // File streaming
66    FileHeader = 0x30,
67    FileEnd = 0x31,
68
69    // Query
70    StatusRequest = 0x40,
71    StatusResponse = 0x41,
72
73    // Generic
74    Error = 0xFF,
75}
76
77impl MessageType {
78    /// Convert from the byte stored in the frame header.
79    pub fn from_byte(b: u8) -> Option<Self> {
80        match b {
81            0x01 => Some(Self::Handshake),
82            0x02 => Some(Self::HandshakeAck),
83            0x10 => Some(Self::ArchiveRequest),
84            0x11 => Some(Self::ArchiveAccept),
85            0x12 => Some(Self::ArchiveComplete),
86            0x20 => Some(Self::RestoreRequest),
87            0x21 => Some(Self::RestoreAccept),
88            0x22 => Some(Self::RestoreComplete),
89            0x30 => Some(Self::FileHeader),
90            0x31 => Some(Self::FileEnd),
91            0x40 => Some(Self::StatusRequest),
92            0x41 => Some(Self::StatusResponse),
93            0xFF => Some(Self::Error),
94            _ => None,
95        }
96    }
97}
98
99// =========================================================================
100// Unified message enums — each variant wraps the corresponding payload struct
101// =========================================================================
102
103/// All messages a client can send to the server.
104///
105/// Serialization is delegated to the inner struct; this enum exists
106/// for type-safe dispatch in the handler state machine.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(tag = "type", rename_all = "snake_case")]
109pub enum ClientMessage {
110    Handshake(Handshake),
111    ArchiveRequest(ArchiveRequest),
112    ArchiveComplete(ArchiveComplete),
113    RestoreRequest(RestoreRequest),
114    StatusRequest(StatusRequest),
115}
116
117impl ClientMessage {
118    /// Return the MessageType discriminant for this variant.
119    pub fn message_type(&self) -> MessageType {
120        match self {
121            Self::Handshake(_) => MessageType::Handshake,
122            Self::ArchiveRequest(_) => MessageType::ArchiveRequest,
123            Self::ArchiveComplete(_) => MessageType::ArchiveComplete,
124            Self::RestoreRequest(_) => MessageType::RestoreRequest,
125            Self::StatusRequest(_) => MessageType::StatusRequest,
126        }
127    }
128}
129
130/// All messages a server can send to the client.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(tag = "type", rename_all = "snake_case")]
133pub enum ServerMessage {
134    HandshakeAck(HandshakeAck),
135    ArchiveAccept(ArchiveAccept),
136    RestoreAccept(RestoreAccept),
137    RestoreComplete(RestoreComplete),
138    StatusResponse(StatusResponse),
139    Error(ErrorMessage),
140}
141
142impl ServerMessage {
143    /// Return the MessageType discriminant for this variant.
144    pub fn message_type(&self) -> MessageType {
145        match self {
146            Self::HandshakeAck(_) => MessageType::HandshakeAck,
147            Self::ArchiveAccept(_) => MessageType::ArchiveAccept,
148            Self::RestoreAccept(_) => MessageType::RestoreAccept,
149            Self::RestoreComplete(_) => MessageType::RestoreComplete,
150            Self::StatusResponse(_) => MessageType::StatusResponse,
151            Self::Error(_) => MessageType::Error,
152        }
153    }
154}
155
156// =========================================================================
157// Handshake messages
158// =========================================================================
159
160/// Sent by the client immediately after the TLS handshake completes.
161///
162/// Contains a random nonce and an HMAC-SHA256 proof computed over the
163/// nonce and the pre-shared key, authenticating the client to the server.
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Handshake {
166    /// Protocol version the client speaks.
167    pub version: u8,
168    /// Human-readable client identifier (hostname or user-supplied name).
169    pub client_id: String,
170    /// 32-byte random nonce generated fresh for this connection.
171    pub nonce: [u8; 32],
172    /// HMAC-SHA256(nonce || "pwr-auth-v1", PSK).
173    pub proof: [u8; 32],
174}
175
176/// Server response to a Handshake message.
177///
178/// Contains its own nonce and proof so the client can mutually
179/// authenticate the server (prevents impersonation).
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct HandshakeAck {
182    /// Whether authentication succeeded.
183    pub success: bool,
184    /// Server version string (informational).
185    pub server_version: String,
186    /// 32-byte server nonce for mutual authentication.
187    pub server_nonce: [u8; 32],
188    /// HMAC-SHA256(client_nonce || server_nonce || "pwr-auth-v1", PSK).
189    pub server_proof: [u8; 32],
190    /// Human-readable reason if success is false.
191    #[serde(default)]
192    pub reason: Option<String>,
193}
194
195// =========================================================================
196// Archive flow messages (client → server direction, server acks)
197// =========================================================================
198
199/// Client requests permission to upload a project archive.
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ArchiveRequest {
202    /// UUID of the project being archived.
203    pub project_uuid: Uuid,
204    /// Human-readable project name.
205    pub project_name: String,
206    /// Total size of the encrypted archive in bytes.
207    pub total_size: u64,
208    /// Number of files in the project (for progress granularity).
209    pub file_count: u32,
210    /// Whether the archive is compressed.
211    pub compression: bool,
212}
213
214/// Server accepts the archive request and assigns a session.
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct ArchiveAccept {
217    /// Session identifier for correlating subsequent file chunks.
218    pub session_id: Uuid,
219}
220
221/// Client reports the final outcome of the archive transfer.
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ArchiveComplete {
224    /// Whether the client considers the transfer successful.
225    pub success: bool,
226    /// Total bytes received by the server.
227    pub total_size: u64,
228    /// SHA-256 hash of the encrypted archive (hex-encoded).
229    pub archive_hash: String,
230    /// Error description if success is false.
231    #[serde(default)]
232    pub error: Option<String>,
233}
234
235// =========================================================================
236// Restore flow messages (client requests, server streams back)
237// =========================================================================
238
239/// Client requests a project archive be sent back for restore.
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct RestoreRequest {
242    /// UUID of the project to restore.
243    pub project_uuid: Uuid,
244}
245
246/// Server accepts the restore request and provides metadata.
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct RestoreAccept {
249    /// Session identifier for correlating subsequent file chunks.
250    pub session_id: Uuid,
251    /// Total size of the encrypted archive in bytes.
252    pub total_size: u64,
253    /// Number of files in the archive.
254    pub file_count: u32,
255    /// SHA-256 hash of the archive (hex-encoded) for client-side
256    /// integrity verification after download.
257    pub archive_hash: String,
258}
259
260/// Server reports the restore transfer is complete.
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct RestoreComplete {
263    /// Whether the server considers the transfer successful.
264    pub success: bool,
265    /// Error description if success is false.
266    #[serde(default)]
267    pub error: Option<String>,
268}
269
270// =========================================================================
271// File streaming messages (used by both archive and restore)
272// =========================================================================
273
274/// Metadata for a single file within a project archive.
275///
276/// Sent before the file's raw content bytes. The receiver uses this
277/// to create the correct directory structure and allocate space.
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct FileHeader {
280    /// Relative path within the project (e.g., "src/main.rs").
281    /// Uses forward slashes regardless of platform.
282    pub rel_path: String,
283    /// File size in bytes (0 for empty files).
284    pub size: u64,
285    /// Unix file mode bits (e.g., 0o644 for regular files).
286    pub mode: u32,
287}
288
289/// Sent after a file's content has been fully streamed.
290///
291/// Carries the SHA-256 hash of the file content so the receiver
292/// can verify integrity before acknowledging.
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct FileEnd {
295    /// SHA-256 hash of the file content (hex-encoded).
296    pub checksum: String,
297}
298
299// =========================================================================
300// Query messages
301// =========================================================================
302
303/// Request project information from the server.
304/// An empty request returns all projects; provide a UUID to query one.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct StatusRequest {
307    /// If set, only return information for this project.
308    #[serde(default)]
309    pub project_uuid: Option<Uuid>,
310}
311
312/// Server response with matching project information.
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct StatusResponse {
315    /// List of projects matching the request.
316    pub projects: Vec<ProjectInfo>,
317}
318
319/// Summary information about a stored project.
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct ProjectInfo {
322    /// Project UUID.
323    pub uuid: Uuid,
324    /// Human-readable name.
325    pub name: String,
326    /// Total size in bytes of the stored archive.
327    pub size_bytes: u64,
328    /// Number of files in the project.
329    pub file_count: u32,
330    /// When the project was first archived.
331    pub created_at: DateTime<Utc>,
332    /// When the project was last modified (archived or restored).
333    pub last_modified: DateTime<Utc>,
334}
335
336// =========================================================================
337// Error message
338// =========================================================================
339
340/// Generic error response sent by either party on protocol violations,
341/// authentication failures, or storage errors.
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ErrorMessage {
344    /// Numeric error code for programmatic handling:
345    /// 1 = authentication failed, 2 = framing error, 3 = not found,
346    /// 4 = storage full, 5 = protocol violation.
347    pub code: u32,
348    /// Human-readable error description.
349    pub message: String,
350}
351
352// =========================================================================
353// Deserialization helpers
354// =========================================================================
355
356/// Deserialize a client message from raw frame payload bytes.
357pub fn decode_client_message(msg_type: MessageType, payload: &[u8]) -> Result<ClientMessage> {
358    match msg_type {
359        MessageType::Handshake => {
360            let m: Handshake = serde_json::from_slice(payload)
361                .map_err(|e| PwrError::Framing(format!("bad Handshake: {}", e)))?;
362            Ok(ClientMessage::Handshake(m))
363        }
364        MessageType::ArchiveRequest => {
365            let m: ArchiveRequest = serde_json::from_slice(payload)
366                .map_err(|e| PwrError::Framing(format!("bad ArchiveRequest: {}", e)))?;
367            Ok(ClientMessage::ArchiveRequest(m))
368        }
369        MessageType::ArchiveComplete => {
370            let m: ArchiveComplete = serde_json::from_slice(payload)
371                .map_err(|e| PwrError::Framing(format!("bad ArchiveComplete: {}", e)))?;
372            Ok(ClientMessage::ArchiveComplete(m))
373        }
374        MessageType::RestoreRequest => {
375            let m: RestoreRequest = serde_json::from_slice(payload)
376                .map_err(|e| PwrError::Framing(format!("bad RestoreRequest: {}", e)))?;
377            Ok(ClientMessage::RestoreRequest(m))
378        }
379        MessageType::StatusRequest => {
380            let m: StatusRequest = serde_json::from_slice(payload)
381                .map_err(|e| PwrError::Framing(format!("bad StatusRequest: {}", e)))?;
382            Ok(ClientMessage::StatusRequest(m))
383        }
384        other => Err(PwrError::Protocol(format!(
385            "Expected client message, got server message type {:?}", other
386        ))),
387    }
388}
389
390/// Deserialize a server message from raw frame payload bytes.
391pub fn decode_server_message(msg_type: MessageType, payload: &[u8]) -> Result<ServerMessage> {
392    match msg_type {
393        MessageType::HandshakeAck => {
394            let m: HandshakeAck = serde_json::from_slice(payload)
395                .map_err(|e| PwrError::Framing(format!("bad HandshakeAck: {}", e)))?;
396            Ok(ServerMessage::HandshakeAck(m))
397        }
398        MessageType::ArchiveAccept => {
399            let m: ArchiveAccept = serde_json::from_slice(payload)
400                .map_err(|e| PwrError::Framing(format!("bad ArchiveAccept: {}", e)))?;
401            Ok(ServerMessage::ArchiveAccept(m))
402        }
403        MessageType::RestoreAccept => {
404            let m: RestoreAccept = serde_json::from_slice(payload)
405                .map_err(|e| PwrError::Framing(format!("bad RestoreAccept: {}", e)))?;
406            Ok(ServerMessage::RestoreAccept(m))
407        }
408        MessageType::RestoreComplete => {
409            let m: RestoreComplete = serde_json::from_slice(payload)
410                .map_err(|e| PwrError::Framing(format!("bad RestoreComplete: {}", e)))?;
411            Ok(ServerMessage::RestoreComplete(m))
412        }
413        MessageType::StatusResponse => {
414            let m: StatusResponse = serde_json::from_slice(payload)
415                .map_err(|e| PwrError::Framing(format!("bad StatusResponse: {}", e)))?;
416            Ok(ServerMessage::StatusResponse(m))
417        }
418        MessageType::Error => {
419            let m: ErrorMessage = serde_json::from_slice(payload)
420                .map_err(|e| PwrError::Framing(format!("bad ErrorMessage: {}", e)))?;
421            Ok(ServerMessage::Error(m))
422        }
423        other => Err(PwrError::Protocol(format!(
424            "Expected server message, got client message type {:?}", other
425        ))),
426    }
427}
428
429// =========================================================================
430// Archive flow helpers
431// =========================================================================
432
433/// Build an ArchiveRequest message for the given project metadata.
434pub fn build_archive_request(
435    uuid: Uuid,
436    name: &str,
437    total_size: u64,
438    file_count: u32,
439    compression: bool,
440) -> ClientMessage {
441    ClientMessage::ArchiveRequest(ArchiveRequest {
442        project_uuid: uuid,
443        project_name: name.to_string(),
444        total_size,
445        file_count,
446        compression,
447    })
448}
449
450/// Build a successful ArchiveComplete message.
451pub fn build_archive_complete(total_size: u64, archive_hash: &str) -> ClientMessage {
452    ClientMessage::ArchiveComplete(ArchiveComplete {
453        success: true,
454        total_size,
455        archive_hash: archive_hash.to_string(),
456        error: None,
457    })
458}
459
460/// Build a failed ArchiveComplete message.
461pub fn build_archive_failed(error: &str) -> ClientMessage {
462    ClientMessage::ArchiveComplete(ArchiveComplete {
463        success: false,
464        total_size: 0,
465        archive_hash: String::new(),
466        error: Some(error.to_string()),
467    })
468}
469
470// =========================================================================
471// Restore flow helpers
472// =========================================================================
473
474/// Build a RestoreRequest for a project UUID.
475pub fn build_restore_request(uuid: Uuid) -> ClientMessage {
476    ClientMessage::RestoreRequest(RestoreRequest {
477        project_uuid: uuid,
478    })
479}
480
481/// Build a RestoreAccept server response with session and metadata.
482pub fn build_restore_accept(
483    session_id: Uuid,
484    total_size: u64,
485    file_count: u32,
486    archive_hash: &str,
487) -> ServerMessage {
488    ServerMessage::RestoreAccept(RestoreAccept {
489        session_id,
490        total_size,
491        file_count,
492        archive_hash: archive_hash.to_string(),
493    })
494}
495
496/// Build a successful RestoreComplete message.
497pub fn build_restore_complete() -> ServerMessage {
498    ServerMessage::RestoreComplete(RestoreComplete {
499        success: true,
500        error: None,
501    })
502}
503
504/// Build a failed RestoreComplete message.
505pub fn build_restore_failed(error: &str) -> ServerMessage {
506    ServerMessage::RestoreComplete(RestoreComplete {
507        success: false,
508        error: Some(error.to_string()),
509    })
510}
511
512// =========================================================================
513// Error helpers
514// =========================================================================
515
516/// Build an Error server message.
517pub fn build_error(code: u32, message: &str) -> ServerMessage {
518    ServerMessage::Error(ErrorMessage {
519        code,
520        message: message.to_string(),
521    })
522}
523
524/// Build an ArchiveAccept server message.
525pub fn build_archive_accept(session_id: Uuid) -> ServerMessage {
526    ServerMessage::ArchiveAccept(ArchiveAccept { session_id })
527}
528
529/// Build a HandshakeAck for successful authentication.
530pub fn build_handshake_ack_success(
531    server_version: &str,
532    server_nonce: [u8; 32],
533    server_proof: [u8; 32],
534) -> ServerMessage {
535    ServerMessage::HandshakeAck(HandshakeAck {
536        success: true,
537        server_version: server_version.to_string(),
538        server_nonce,
539        server_proof,
540        reason: None,
541    })
542}
543
544/// Build a HandshakeAck for failed authentication.
545pub fn build_handshake_ack_failed(reason: &str) -> ServerMessage {
546    ServerMessage::HandshakeAck(HandshakeAck {
547        success: false,
548        server_version: String::new(),
549        server_nonce: [0u8; 32],
550        server_proof: [0u8; 32],
551        reason: Some(reason.to_string()),
552    })
553}
554
555/// Build a StatusResponse with project listings.
556pub fn build_status_response(projects: Vec<ProjectInfo>) -> ServerMessage {
557    ServerMessage::StatusResponse(StatusResponse { projects })
558}
559
560// =========================================================================
561// Tests
562// =========================================================================
563
564#[cfg(test)]
565mod tests {
566    use super::*;
567
568    #[test]
569    fn test_message_type_round_trip() {
570        let variants = [
571            MessageType::Handshake,
572            MessageType::HandshakeAck,
573            MessageType::ArchiveRequest,
574            MessageType::ArchiveAccept,
575            MessageType::ArchiveComplete,
576            MessageType::RestoreRequest,
577            MessageType::RestoreAccept,
578            MessageType::RestoreComplete,
579            MessageType::FileHeader,
580            MessageType::FileEnd,
581            MessageType::StatusRequest,
582            MessageType::StatusResponse,
583            MessageType::Error,
584        ];
585
586        for v in &variants {
587            let byte = *v as u8;
588            let decoded = MessageType::from_byte(byte);
589            assert_eq!(decoded, Some(*v));
590        }
591    }
592
593    #[test]
594    fn test_invalid_message_type() {
595        assert_eq!(MessageType::from_byte(0x00), None);
596        assert_eq!(MessageType::from_byte(0x99), None);
597    }
598
599    #[test]
600    fn test_archive_request_serialization() {
601        let req = ArchiveRequest {
602            project_uuid: Uuid::new_v4(),
603            project_name: "testproj".into(),
604            total_size: 1_048_576,
605            file_count: 42,
606            compression: true,
607        };
608
609        let encoded = serde_json::to_vec(&req).unwrap();
610        let decoded: ArchiveRequest = serde_json::from_slice(&encoded).unwrap();
611
612        assert_eq!(decoded.project_name, "testproj");
613        assert_eq!(decoded.file_count, 42);
614        assert!(decoded.compression);
615    }
616
617    #[test]
618    fn test_client_message_round_trip() {
619        let msg = ClientMessage::ArchiveRequest(ArchiveRequest {
620            project_uuid: Uuid::new_v4(),
621            project_name: "roundtrip".into(),
622            total_size: 5000,
623            file_count: 10,
624            compression: false,
625        });
626
627        let encoded = serde_json::to_vec(&msg).unwrap();
628        let decoded: ClientMessage = serde_json::from_slice(&encoded).unwrap();
629
630        match decoded {
631            ClientMessage::ArchiveRequest(req) => {
632                assert_eq!(req.project_name, "roundtrip");
633            }
634            _ => panic!("Wrong variant"),
635        }
636    }
637
638    #[test]
639    fn test_server_message_round_trip() {
640        let msg = ServerMessage::Error(ErrorMessage {
641            code: 3,
642            message: "project not found".into(),
643        });
644
645        let encoded = serde_json::to_vec(&msg).unwrap();
646        let decoded: ServerMessage = serde_json::from_slice(&encoded).unwrap();
647
648        match decoded {
649            ServerMessage::Error(e) => {
650                assert_eq!(e.code, 3);
651            }
652            _ => panic!("Wrong variant"),
653        }
654    }
655
656    #[test]
657    fn test_decode_client_message_wrong_type() {
658        // Passing a server message type should error
659        let payload = serde_json::to_vec(&ErrorMessage { code: 1, message: "err".into() }).unwrap();
660        let result = decode_client_message(MessageType::Error, &payload);
661        assert!(result.is_err());
662    }
663
664    #[test]
665    fn test_decode_server_message_wrong_type() {
666        let payload = serde_json::to_vec(&Handshake {
667            version: 1,
668            client_id: "x".into(),
669            nonce: [0; 32],
670            proof: [0; 32],
671        }).unwrap();
672        let result = decode_server_message(MessageType::Handshake, &payload);
673        assert!(result.is_err());
674    }
675
676    #[test]
677    fn test_project_info_timestamps() {
678        let info = ProjectInfo {
679            uuid: Uuid::new_v4(),
680            name: "test".into(),
681            size_bytes: 1000,
682            file_count: 10,
683            created_at: Utc::now(),
684            last_modified: Utc::now(),
685        };
686
687        let encoded = serde_json::to_vec(&info).unwrap();
688        let decoded: ProjectInfo = serde_json::from_slice(&encoded).unwrap();
689
690        assert_eq!(decoded.name, "test");
691        assert_eq!(decoded.size_bytes, 1000);
692    }
693
694    #[test]
695    fn test_client_message_type_mapping() {
696        let msg = ClientMessage::Handshake(Handshake {
697            version: 1,
698            client_id: "test".into(),
699            nonce: [0; 32],
700            proof: [0; 32],
701        });
702        assert_eq!(msg.message_type(), MessageType::Handshake);
703
704        let msg = ClientMessage::StatusRequest(StatusRequest { project_uuid: None });
705        assert_eq!(msg.message_type(), MessageType::StatusRequest);
706    }
707
708    #[test]
709    fn test_server_message_type_mapping() {
710        let msg = ServerMessage::HandshakeAck(HandshakeAck {
711            success: true,
712            server_version: "0.1.0".into(),
713            server_nonce: [0; 32],
714            server_proof: [0; 32],
715            reason: None,
716        });
717        assert_eq!(msg.message_type(), MessageType::HandshakeAck);
718
719        let msg = ServerMessage::Error(ErrorMessage { code: 1, message: "oops".into() });
720        assert_eq!(msg.message_type(), MessageType::Error);
721    }
722}