gosh_lan_transfer/
protocol.rs

1// SPDX-License-Identifier: MIT
2//! Protocol types for gosh-lan-transfer
3//!
4//! This module contains all types that cross the engine boundary:
5//! - Wire protocol types (sent between peers over HTTP)
6//! - Event types (emitted from engine to consumers)
7//! - Shared status enums
8//!
9//! Rule: If it crosses the engine boundary, it belongs here.
10
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13
14// =============================================================================
15// Status Enums - Shared vocabulary for transfer state
16// =============================================================================
17
18/// Direction of a transfer
19#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "lowercase")]
21pub enum TransferDirection {
22    Sent,
23    Received,
24}
25
26/// Status of a transfer
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28#[serde(rename_all = "lowercase")]
29pub enum TransferStatus {
30    Pending,
31    InProgress,
32    Completed,
33    Failed,
34    Rejected,
35}
36
37/// Transfer approval decision
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39#[serde(rename_all = "lowercase")]
40pub enum TransferDecision {
41    Pending,
42    Accepted,
43    Rejected,
44    NotFound,
45}
46
47// =============================================================================
48// Wire Protocol Types - Sent between peers over HTTP
49// =============================================================================
50
51/// A single file in a transfer
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct TransferFile {
55    /// File name (not full path for security)
56    pub name: String,
57    /// File size in bytes
58    pub size: u64,
59    /// MIME type (if detected)
60    pub mime_type: Option<String>,
61    /// Unique identifier for this file in the transfer
62    pub id: String,
63    /// Relative path within a directory transfer (e.g., "subdir/file.txt")
64    /// When present, the receiver will recreate the directory structure
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub relative_path: Option<String>,
67}
68
69/// Metadata for a transfer request (sent before actual data)
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct TransferRequest {
73    /// Unique transfer session ID
74    pub transfer_id: String,
75    /// Optional friendly name of the sender
76    pub sender_name: Option<String>,
77    /// List of files to be transferred
78    pub files: Vec<TransferFile>,
79    /// Total size of all files
80    pub total_size: u64,
81}
82
83/// Response to a transfer request
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct TransferResponse {
87    /// Whether the transfer was accepted
88    pub accepted: bool,
89    /// Optional message (e.g., rejection reason)
90    pub message: Option<String>,
91    /// Token for subsequent chunk uploads (if accepted)
92    pub token: Option<String>,
93}
94
95/// Status response for a transfer awaiting approval
96#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct TransferApprovalStatus {
99    /// Current approval status
100    pub status: TransferDecision,
101    /// Token for subsequent chunk uploads (if accepted)
102    pub token: Option<String>,
103    /// Optional message
104    pub message: Option<String>,
105}
106
107/// Peer device information
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct PeerInfo {
111    /// Device name
112    pub device_name: String,
113    /// Protocol version
114    pub version: String,
115}
116
117// =============================================================================
118// Event Payload Types - Data carried in engine events
119// =============================================================================
120
121/// Progress update for an ongoing transfer
122#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct TransferProgress {
125    /// Transfer ID
126    pub transfer_id: String,
127    /// Current file being transferred
128    pub current_file: Option<String>,
129    /// Bytes transferred so far
130    pub bytes_transferred: u64,
131    /// Total bytes to transfer
132    pub total_bytes: u64,
133    /// Transfer speed in bytes/sec
134    pub speed_bps: u64,
135}
136
137/// An incoming transfer pending user approval
138#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct PendingTransfer {
141    /// Transfer ID
142    pub id: String,
143    /// Source IP address
144    pub source_ip: String,
145    /// Optional sender name
146    pub sender_name: Option<String>,
147    /// Files to be received
148    pub files: Vec<TransferFile>,
149    /// Total size
150    pub total_size: u64,
151    /// When the request was received
152    pub received_at: DateTime<Utc>,
153}
154
155// =============================================================================
156// Engine Events - Emitted from engine to consumers
157// =============================================================================
158
159/// Events emitted by the engine
160///
161/// These events cross the engine boundary and are delivered to consumers
162/// via the `EventHandler` trait.
163#[derive(Debug, Clone)]
164pub enum EngineEvent {
165    /// New transfer request awaiting approval
166    TransferRequest(PendingTransfer),
167
168    /// Progress update for an active transfer
169    TransferProgress(TransferProgress),
170
171    /// Transfer completed successfully
172    TransferComplete { transfer_id: String },
173
174    /// Transfer failed
175    TransferFailed { transfer_id: String, error: String },
176
177    /// Retrying a failed operation
178    TransferRetry {
179        transfer_id: String,
180        /// Current attempt number (1-based)
181        attempt: u32,
182        /// Maximum attempts allowed
183        max_attempts: u32,
184        /// Error that triggered the retry
185        error: String,
186    },
187
188    /// Server started successfully
189    ServerStarted { port: u16 },
190
191    /// Server stopped
192    ServerStopped,
193
194    /// Server port changed at runtime
195    PortChanged { old_port: u16, new_port: u16 },
196}