alopex_chirps_wire/
file_transfer.rs

1use serde::{Deserialize, Serialize};
2use std::time::Duration;
3
4pub const DEFAULT_CHUNK_SIZE: usize = 1024 * 1024;
5
6pub type ChunkIndex = u32;
7pub type ChunkChecksum = u64;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
10pub struct TransferSessionId([u8; 16]);
11
12impl TransferSessionId {
13    pub fn new() -> Self {
14        TransferSessionId(*uuid::Uuid::new_v4().as_bytes())
15    }
16
17    pub fn from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
18        if bytes.len() != 16 {
19            return Err("TransferSessionId must be 16 bytes long");
20        }
21        let mut arr = [0u8; 16];
22        arr.copy_from_slice(bytes);
23        Ok(TransferSessionId(arr))
24    }
25
26    pub fn as_bytes(&self) -> &[u8; 16] {
27        &self.0
28    }
29}
30
31impl Default for TransferSessionId {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl From<[u8; 16]> for TransferSessionId {
38    fn from(bytes: [u8; 16]) -> Self {
39        TransferSessionId(bytes)
40    }
41}
42
43impl std::fmt::Display for TransferSessionId {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let uuid = uuid::Uuid::from_bytes(self.0);
46        write!(f, "{uuid}")
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
51pub enum CompressionAlgorithm {
52    #[default]
53    None,
54    Zstd,
55    ZstdLevel(i32),
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
59pub enum HashAlgorithm {
60    #[default]
61    Sha256,
62    Blake3,
63    XxHash64,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
67pub enum TransferMode {
68    #[default]
69    Copy,
70    Move,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74pub enum TransferState {
75    Initializing,
76    InProgress,
77    Paused,
78    Verifying,
79    Completed,
80    Failed,
81    Cancelled,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct RetryPolicy {
86    pub max_retries: u8,
87    pub initial_delay: Duration,
88    pub max_delay: Duration,
89    pub backoff_multiplier: f64,
90    pub jitter: bool,
91}
92
93impl Default for RetryPolicy {
94    fn default() -> Self {
95        RetryPolicy {
96            max_retries: 3,
97            initial_delay: Duration::from_millis(100),
98            max_delay: Duration::from_secs(10),
99            backoff_multiplier: 2.0,
100            jitter: true,
101        }
102    }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct TransferOptions {
107    pub chunk_size: usize,
108    pub concurrency: usize,
109    pub compression: CompressionAlgorithm,
110    pub bandwidth_limit: Option<u64>,
111    pub retry_policy: RetryPolicy,
112    pub verify_on_complete: bool,
113    pub hash_algorithm: HashAlgorithm,
114    pub resumable: bool,
115    pub overwrite: bool,
116    pub mode: TransferMode,
117    pub preserve_metadata: bool,
118}
119
120impl Default for TransferOptions {
121    fn default() -> Self {
122        TransferOptions {
123            chunk_size: DEFAULT_CHUNK_SIZE,
124            concurrency: 4,
125            compression: CompressionAlgorithm::None,
126            bandwidth_limit: None,
127            retry_policy: RetryPolicy::default(),
128            verify_on_complete: true,
129            hash_algorithm: HashAlgorithm::Sha256,
130            resumable: true,
131            overwrite: false,
132            mode: TransferMode::Copy,
133            preserve_metadata: true,
134        }
135    }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ChunkMeta {
140    pub index: ChunkIndex,
141    pub offset: u64,
142    pub size: u32,
143    pub checksum: ChunkChecksum,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct TransferManifest {
148    pub version: u16,
149    pub session_id: TransferSessionId,
150    pub source_path: String,
151    pub dest_path: String,
152    pub file_size: u64,
153    pub file_hash: Vec<u8>,
154    pub hash_algorithm: HashAlgorithm,
155    pub chunk_size: u32,
156    pub chunk_count: u32,
157    pub chunks: Vec<ChunkMeta>,
158    pub metadata: Option<FileMetadata>,
159    pub options: TransferOptions,
160    pub created_at: u64,
161}
162
163#[derive(Serialize, Deserialize, Debug, Clone)]
164pub struct FileTransferFrame {
165    pub session_id: TransferSessionId,
166    pub message: FileTransferMessage,
167}
168
169#[derive(Serialize, Deserialize, Debug, Clone)]
170pub enum FileTransferMessage {
171    TransferRequest(TransferRequest),
172    TransferResponse(TransferResponse),
173    Manifest(TransferManifest),
174    ManifestAck(ManifestAck),
175    ChunkAck(ChunkAck),
176    ChunkRequest(ChunkRequest),
177    Progress(ProgressUpdate),
178    Cancel(CancelRequest),
179    Complete(TransferComplete),
180    Error(TransferErrorMessage),
181    ExistsRequest(ExistsRequest),
182    ExistsResponse(ExistsResponse),
183    RemoveRequest(RemoveRequest),
184    RemoveResponse(RemoveResponse),
185    MetadataRequest(MetadataRequest),
186    MetadataResponse(MetadataResponse),
187    ListRequest(ListRequest),
188    ListResponse(ListResponse),
189}
190
191#[derive(Serialize, Deserialize, Debug, Clone)]
192pub struct TransferRequest {
193    pub source_path: String,
194    pub dest_path: String,
195    pub file_size: u64,
196    pub chunk_count: u32,
197    pub chunk_size: u32,
198    pub mode: TransferMode,
199    pub options: TransferOptions,
200    pub metadata: Option<FileMetadata>,
201}
202
203#[derive(Serialize, Deserialize, Debug, Clone)]
204pub struct TransferResponse {
205    pub accepted: bool,
206    pub rejection_reason: Option<String>,
207    pub existing_chunks: Vec<ChunkIndex>,
208}
209
210#[derive(Serialize, Deserialize, Debug, Clone)]
211pub struct ManifestAck {
212    pub accepted: bool,
213    pub skip_chunks: Vec<ChunkIndex>,
214    pub error: Option<String>,
215}
216
217#[derive(Serialize, Deserialize, Debug, Clone)]
218pub struct ChunkAck {
219    pub index: ChunkIndex,
220    pub verified: bool,
221    pub error: Option<String>,
222}
223
224#[derive(Serialize, Deserialize, Debug, Clone)]
225pub struct ChunkRequest {
226    pub indices: Vec<ChunkIndex>,
227}
228
229#[derive(Serialize, Deserialize, Debug, Clone)]
230pub struct ProgressUpdate {
231    pub chunks_completed: u32,
232    pub bytes_transferred: u64,
233    pub state: TransferState,
234}
235
236#[derive(Serialize, Deserialize, Debug, Clone)]
237pub struct CancelRequest {
238    pub reason: String,
239}
240
241#[derive(Serialize, Deserialize, Debug, Clone)]
242pub struct TransferComplete {
243    pub bytes_transferred: u64,
244    pub duration_ms: u64,
245    pub file_hash: Vec<u8>,
246    pub hash_algorithm: HashAlgorithm,
247}
248
249#[derive(Serialize, Deserialize, Debug, Clone)]
250pub struct TransferErrorMessage {
251    pub code: u32,
252    pub message: String,
253    pub recoverable: bool,
254}
255
256#[derive(Serialize, Deserialize, Debug, Clone)]
257pub struct ExistsRequest {
258    pub path: String,
259}
260
261#[derive(Serialize, Deserialize, Debug, Clone)]
262pub struct ExistsResponse {
263    pub exists: bool,
264    pub is_file: bool,
265    pub is_directory: bool,
266}
267
268#[derive(Serialize, Deserialize, Debug, Clone)]
269pub struct RemoveRequest {
270    pub path: String,
271    pub recursive: bool,
272}
273
274#[derive(Serialize, Deserialize, Debug, Clone)]
275pub struct RemoveResponse {
276    pub success: bool,
277    pub error: Option<String>,
278}
279
280#[derive(Serialize, Deserialize, Debug, Clone)]
281pub struct MetadataRequest {
282    pub path: String,
283}
284
285#[derive(Serialize, Deserialize, Debug, Clone)]
286pub struct MetadataResponse {
287    pub found: bool,
288    pub metadata: Option<FileMetadata>,
289    pub size: Option<u64>,
290    pub error: Option<String>,
291}
292
293#[derive(Serialize, Deserialize, Debug, Clone)]
294pub struct ListRequest {
295    pub path: String,
296    pub recursive: bool,
297    pub include_hidden: bool,
298}
299
300#[derive(Serialize, Deserialize, Debug, Clone)]
301pub struct ListResponse {
302    pub files: Vec<FileInfo>,
303    pub error: Option<String>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct FileMetadata {
308    pub created_at: Option<u64>,
309    pub modified_at: Option<u64>,
310    pub permissions: Option<u32>,
311    pub file_type: FileType,
312    pub size: Option<u64>,
313}
314
315#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
316pub enum FileType {
317    File,
318    Directory,
319    Symlink,
320}
321
322#[derive(Serialize, Deserialize, Debug, Clone)]
323pub struct FileInfo {
324    pub path: String,
325    pub size: u64,
326    pub modified_at: u64,
327    pub file_type: FileType,
328}