1use chrono::{DateTime, Utc};
29use serde::{Deserialize, Serialize};
30use uuid::Uuid;
31
32use crate::error::{PwrError, Result};
33
34pub const PROTOCOL_VERSION: u8 = 0x01;
36
37pub const CHUNK_SIZE: usize = 1024 * 1024;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[repr(u8)]
50pub enum MessageType {
51 Handshake = 0x01,
53 HandshakeAck = 0x02,
54
55 ArchiveRequest = 0x10,
57 ArchiveAccept = 0x11,
58 ArchiveComplete = 0x12,
59
60 RestoreRequest = 0x20,
62 RestoreAccept = 0x21,
63 RestoreComplete = 0x22,
64
65 FileHeader = 0x30,
67 FileEnd = 0x31,
68
69 StatusRequest = 0x40,
71 StatusResponse = 0x41,
72
73 Error = 0xFF,
75}
76
77impl MessageType {
78 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#[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 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#[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 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#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Handshake {
166 pub version: u8,
168 pub client_id: String,
170 pub nonce: [u8; 32],
172 pub proof: [u8; 32],
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct HandshakeAck {
182 pub success: bool,
184 pub server_version: String,
186 pub server_nonce: [u8; 32],
188 pub server_proof: [u8; 32],
190 #[serde(default)]
192 pub reason: Option<String>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ArchiveRequest {
202 pub project_uuid: Uuid,
204 pub project_name: String,
206 pub total_size: u64,
208 pub file_count: u32,
210 pub compression: bool,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct ArchiveAccept {
217 pub session_id: Uuid,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ArchiveComplete {
224 pub success: bool,
226 pub total_size: u64,
228 pub archive_hash: String,
230 #[serde(default)]
232 pub error: Option<String>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct RestoreRequest {
242 pub project_uuid: Uuid,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct RestoreAccept {
249 pub session_id: Uuid,
251 pub total_size: u64,
253 pub file_count: u32,
255 pub archive_hash: String,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct RestoreComplete {
263 pub success: bool,
265 #[serde(default)]
267 pub error: Option<String>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct FileHeader {
280 pub rel_path: String,
283 pub size: u64,
285 pub mode: u32,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct FileEnd {
295 pub checksum: String,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct StatusRequest {
307 #[serde(default)]
309 pub project_uuid: Option<Uuid>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct StatusResponse {
315 pub projects: Vec<ProjectInfo>,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct ProjectInfo {
322 pub uuid: Uuid,
324 pub name: String,
326 pub size_bytes: u64,
328 pub file_count: u32,
330 pub created_at: DateTime<Utc>,
332 pub last_modified: DateTime<Utc>,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ErrorMessage {
344 pub code: u32,
348 pub message: String,
350}
351
352pub 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
390pub 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
429pub 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
450pub 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
460pub 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
470pub fn build_restore_request(uuid: Uuid) -> ClientMessage {
476 ClientMessage::RestoreRequest(RestoreRequest {
477 project_uuid: uuid,
478 })
479}
480
481pub 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
496pub fn build_restore_complete() -> ServerMessage {
498 ServerMessage::RestoreComplete(RestoreComplete {
499 success: true,
500 error: None,
501 })
502}
503
504pub fn build_restore_failed(error: &str) -> ServerMessage {
506 ServerMessage::RestoreComplete(RestoreComplete {
507 success: false,
508 error: Some(error.to_string()),
509 })
510}
511
512pub fn build_error(code: u32, message: &str) -> ServerMessage {
518 ServerMessage::Error(ErrorMessage {
519 code,
520 message: message.to_string(),
521 })
522}
523
524pub fn build_archive_accept(session_id: Uuid) -> ServerMessage {
526 ServerMessage::ArchiveAccept(ArchiveAccept { session_id })
527}
528
529pub 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
544pub 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
555pub fn build_status_response(projects: Vec<ProjectInfo>) -> ServerMessage {
557 ServerMessage::StatusResponse(StatusResponse { projects })
558}
559
560#[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 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}