1use pwr_core::frame::{FrameDecoder, FrameHeader};
13use pwr_core::protocol::{
14 self, ArchiveComplete, ArchiveRequest, ClientMessage,
15 Handshake, ProjectInfo, RestoreRequest, ServerMessage,
16 StatusRequest,
17};
18use pwr_core::crypto;
19use ring::rand::SecureRandom;
20use std::io::{Read, Write};
21use std::net::SocketAddr;
22use std::sync::{Arc, Mutex, RwLock};
23use std::time::Instant;
24use uuid::Uuid;
25
26use crate::auth::RateLimiter;
27use crate::storage::{ProjectStorage, StoredProject};
28
29#[derive(Debug)]
34enum ConnState {
35 AwaitingHandshake,
36 Authenticated,
37 Archiving(ArchiveSession),
38 Restoring(RestoreSession),
39 Closed,
40}
41
42#[derive(Debug)]
43#[allow(dead_code)]
44struct ArchiveSession {
45 session_id: Uuid,
46 project_uuid: Uuid,
47 project_name: String,
48 total_size: u64,
49 file_count: u32,
50 compression: bool,
51 bytes_received: u64,
52}
53
54#[derive(Debug)]
55#[allow(dead_code)]
56struct RestoreSession {
57 session_id: Uuid,
58 project_uuid: Uuid,
59 total_size: u64,
60 file_count: u32,
61 bytes_sent: u64,
62}
63
64pub struct HandlerContext {
65 pub storage: Arc<RwLock<ProjectStorage>>,
66 pub rate_limiter: Arc<Mutex<RateLimiter>>,
67 pub psk: [u8; 32],
68 pub peer_addr: SocketAddr,
69 pub connected_at: Instant,
70}
71
72pub fn handle_connection(
77 mut stream: impl Read + Write,
78 ctx: HandlerContext,
79) -> Result<(), String> {
80 let mut state = ConnState::AwaitingHandshake;
81 let mut decoder = FrameDecoder::new();
82 let mut read_buf = vec![0u8; 8192];
83
84 loop {
85 let n = stream.read(&mut read_buf)
86 .map_err(|e| format!("read error: {}", e))?;
87 if n == 0 {
88 break;
89 }
90
91 decoder.push_bytes(&read_buf[..n]);
92
93 loop {
94 match decoder.try_decode() {
95 Ok(Some((header, payload))) => {
96 if let Err(e) = dispatch(&mut stream, &mut state, header, &payload, &ctx) {
97 send_server_msg(&mut stream, &protocol::build_error(1, &e))?;
98 return Err(e);
99 }
100 }
101 Ok(None) => break,
102 Err(e) => {
103 send_server_msg(&mut stream, &protocol::build_error(2, &format!("Frame: {}", e)))?;
104 return Err(format!("Frame error: {}", e));
105 }
106 }
107 }
108 }
109 Ok(())
110}
111
112fn dispatch(
117 stream: &mut (impl Read + Write),
118 state: &mut ConnState,
119 header: FrameHeader,
120 payload: &[u8],
121 ctx: &HandlerContext,
122) -> Result<(), String> {
123 let msg = protocol::decode_client_message(header.msg_type, payload)
125 .map_err(|e| format!("Decode: {}", e))?;
126
127 match (&state, msg) {
128 (ConnState::AwaitingHandshake, ClientMessage::Handshake(hs)) => {
130 handle_handshake(stream, state, &hs, ctx)
131 }
132
133 (ConnState::Authenticated, ClientMessage::ArchiveRequest(req)) => {
135 handle_archive_start(stream, state, &req, ctx)?;
136 handle_archive_chunks(stream, state, ctx)
138 }
139 (ConnState::Archiving(_), ClientMessage::ArchiveComplete(complete)) => {
140 handle_archive_finish(stream, state, &complete, ctx)
141 }
142
143 (ConnState::Authenticated, ClientMessage::RestoreRequest(req)) => {
145 handle_restore_start(stream, state, &req, ctx)?;
146 handle_restore_chunks(stream, state, ctx)
148 }
149
150 (ConnState::Authenticated, ClientMessage::StatusRequest(req)) => {
152 handle_status(stream, &req, ctx)
153 }
154
155 (ConnState::AwaitingHandshake, _) => {
157 Err("Handshake required before any other message".into())
158 }
159 (ConnState::Closed, _) => Err("Connection is closed".into()),
160 (_, _msg) => Err(format!(
161 "Unexpected message in state {:?}",
162 std::mem::discriminant(state)
163 )),
164 }
165}
166
167fn handle_handshake(
172 stream: &mut (impl Read + Write),
173 state: &mut ConnState,
174 hs: &Handshake,
175 ctx: &HandlerContext,
176) -> Result<(), String> {
177 let peer_ip = ctx.peer_addr.ip();
179 {
180 let mut limiter = ctx.rate_limiter.lock().unwrap();
181 if !limiter.check_attempt(peer_ip) {
182 *state = ConnState::Closed;
183 send_server_msg(
184 stream,
185 &protocol::build_handshake_ack_failed("Too many authentication attempts — try again later"),
186 )?;
187 return Err("Rate limited".into());
188 }
189 }
190
191 let expected_proof = crypto::compute_client_proof(&ctx.psk, &hs.nonce);
192
193 if expected_proof != hs.proof {
194 *state = ConnState::Closed;
195 send_server_msg(
196 stream,
197 &protocol::build_handshake_ack_failed("Authentication failed: invalid proof"),
198 )?;
199 return Err("Authentication failed".into());
200 }
201
202 ctx.rate_limiter.lock().unwrap().record_success(peer_ip);
204
205 let mut server_nonce = [0u8; 32];
207 ring::rand::SystemRandom::new()
208 .fill(&mut server_nonce)
209 .map_err(|_| "CSPRNG failure".to_string())?;
210
211 let server_proof =
212 crypto::compute_server_proof(&ctx.psk, &hs.nonce, &server_nonce);
213
214 *state = ConnState::Authenticated;
215 send_server_msg(
216 stream,
217 &protocol::build_handshake_ack_success(
218 env!("CARGO_PKG_VERSION"),
219 server_nonce,
220 server_proof,
221 ),
222 )?;
223
224 log::info!("Client '{}' authenticated from {}", hs.client_id, ctx.peer_addr);
225 Ok(())
226}
227
228fn handle_archive_start(
233 stream: &mut (impl Read + Write),
234 state: &mut ConnState,
235 req: &ArchiveRequest,
236 ctx: &HandlerContext,
237) -> Result<(), String> {
238 let session_id = Uuid::new_v4();
239
240 {
242 let storage = ctx.storage.read().unwrap();
243 storage.check_size_limit(req.total_size)
244 .map_err(|e| format!("Archive rejected: {}", e))?;
245 }
246
247 let project = StoredProject {
249 uuid: req.project_uuid,
250 name: req.project_name.clone(),
251 size_bytes: req.total_size,
252 file_count: req.file_count,
253 encrypted: true,
254 created_at: chrono::Utc::now(),
255 updated_at: chrono::Utc::now(),
256 };
257
258 {
259 let mut storage = ctx.storage.write().unwrap();
260 storage.add_project(project.clone())
261 .map_err(|e| format!("Cannot add project: {}", e))?;
262 storage.write_meta(&req.project_uuid, &project)
263 .map_err(|e| format!("Cannot write meta: {}", e))?;
264 }
265
266 *state = ConnState::Archiving(ArchiveSession {
267 session_id,
268 project_uuid: req.project_uuid,
269 project_name: req.project_name.clone(),
270 total_size: req.total_size,
271 file_count: req.file_count,
272 compression: req.compression,
273 bytes_received: 0,
274 });
275
276 send_server_msg(stream, &protocol::build_archive_accept(session_id))?;
277
278 log::info!(
279 "Archive started: {} ({} bytes, {} files)",
280 req.project_name, req.total_size, req.file_count
281 );
282 Ok(())
283}
284
285fn handle_archive_finish(
286 stream: &mut (impl Read + Write),
287 state: &mut ConnState,
288 complete: &ArchiveComplete,
289 ctx: &HandlerContext,
290) -> Result<(), String> {
291 let (project_uuid, project_name, _total_size) = match state {
293 ConnState::Archiving(s) => (s.project_uuid, s.project_name.clone(), s.total_size),
294 _ => return Err("Not in archiving state".into()),
295 };
296
297 if !complete.success {
298 let _ = ctx.storage.write().unwrap().remove_project(&project_uuid);
299 *state = ConnState::Authenticated;
300 send_server_msg(stream, &protocol::build_error(0, "Archive cancelled by client"))?;
301 return Ok(());
302 }
303
304 {
306 let mut storage = ctx.storage.write().unwrap();
307 if let Some(mut project) = storage.get_project(&project_uuid).cloned() {
308 project.size_bytes = complete.total_size;
309 project.updated_at = chrono::Utc::now();
310 storage.update_project(project)
311 .map_err(|e| format!("Cannot update: {}", e))?;
312 }
313 }
314
315 *state = ConnState::Authenticated;
316 log::info!(
317 "Archive complete: {} ({} bytes, hash: {})",
318 project_name, complete.total_size,
319 &complete.archive_hash[..16.min(complete.archive_hash.len())]
320 );
321 Ok(())
322}
323
324fn handle_archive_chunks(
332 stream: &mut (impl Read + Write),
333 state: &ConnState,
334 ctx: &HandlerContext,
335) -> Result<(), String> {
336 let (project_uuid, _total_size) = match state {
337 ConnState::Archiving(s) => (s.project_uuid, s.total_size),
338 _ => return Err("Not in archiving state".into()),
339 };
340
341 let mut total_bytes = 0u64;
342 let mut header_buf = [0u8; 4];
343
344 loop {
345 stream
347 .read_exact(&mut header_buf)
348 .map_err(|e| format!("chunk header read: {}", e))?;
349
350 let chunk_len = u32::from_be_bytes(header_buf) as usize;
351
352 if chunk_len == 0 {
353 break; }
355
356 let mut chunk = vec![0u8; chunk_len];
358 stream
359 .read_exact(&mut chunk)
360 .map_err(|e| format!("chunk data read: {}", e))?;
361
362 total_bytes += chunk_len as u64;
363
364 {
366 let storage = ctx.storage.read().unwrap();
367 let archive_path = storage.archive_path(&project_uuid);
368
369 use std::io::Write;
370 let mut file = std::fs::OpenOptions::new()
371 .create(true)
372 .append(true)
373 .open(&archive_path)
374 .map_err(|e| format!("Cannot open archive: {}", e))?;
375 file.write_all(&chunk)
376 .map_err(|e| format!("Cannot write chunk: {}", e))?;
377 }
378
379 log::debug!(
380 "Received chunk: {} bytes (total: {})",
381 chunk_len,
382 total_bytes
383 );
384 }
385
386 log::info!(
387 "Archive data received: {} bytes for project {}",
388 total_bytes,
389 project_uuid
390 );
391
392 Ok(())
393}
394
395fn handle_restore_chunks(
401 stream: &mut (impl Read + Write),
402 state: &ConnState,
403 ctx: &HandlerContext,
404) -> Result<(), String> {
405 let project_uuid = match state {
406 ConnState::Restoring(s) => s.project_uuid,
407 _ => return Err("Not in restoring state".into()),
408 };
409
410 let archive_data = {
412 let storage = ctx.storage.read().unwrap();
413 let mut reader = storage
414 .read_archive(&project_uuid)
415 .map_err(|e| format!("Cannot read archive: {}", e))?;
416 let mut data = Vec::new();
417 std::io::Read::read_to_end(&mut reader, &mut data)
418 .map_err(|e| format!("Cannot read archive data: {}", e))?;
419 data
420 };
421
422 let chunk_size: usize = 1024 * 1024; let mut total_sent = 0u64;
425
426 for chunk in archive_data.chunks(chunk_size) {
427 stream
429 .write_all(&(chunk.len() as u32).to_be_bytes())
430 .map_err(|e| format!("chunk header write: {}", e))?;
431 stream
432 .write_all(chunk)
433 .map_err(|e| format!("chunk data write: {}", e))?;
434
435 total_sent += chunk.len() as u64;
436 }
437
438 stream
440 .write_all(&0u32.to_be_bytes())
441 .map_err(|e| format!("eof write: {}", e))?;
442 stream.flush().map_err(|e| format!("flush: {}", e))?;
443
444 log::info!(
445 "Restore data sent: {} bytes for project {}",
446 total_sent,
447 project_uuid
448 );
449
450 Ok(())
451}
452
453fn handle_restore_start(
458 stream: &mut (impl Read + Write),
459 state: &mut ConnState,
460 req: &RestoreRequest,
461 ctx: &HandlerContext,
462) -> Result<(), String> {
463 let storage = ctx.storage.read().unwrap();
464 let project = storage
465 .get_project(&req.project_uuid)
466 .cloned()
467 .ok_or_else(|| format!("Project not found: {}", req.project_uuid))?;
468
469 if !storage.archive_exists(&req.project_uuid) {
470 return Err(format!("Archive data missing for {}", req.project_uuid));
471 }
472 drop(storage);
473
474 let session_id = Uuid::new_v4();
475 let total_size = project.size_bytes;
476 let file_count = project.file_count;
477
478 *state = ConnState::Restoring(RestoreSession {
479 session_id,
480 project_uuid: req.project_uuid,
481 total_size,
482 file_count,
483 bytes_sent: 0,
484 });
485
486 send_server_msg(
487 stream,
488 &protocol::build_restore_accept(session_id, total_size, file_count, ""),
489 )?;
490
491 log::info!("Restore started: {} ({} bytes)", project.name, total_size);
492 Ok(())
493}
494
495fn handle_status(
500 stream: &mut (impl Read + Write),
501 req: &StatusRequest,
502 ctx: &HandlerContext,
503) -> Result<(), String> {
504 let storage = ctx.storage.read().unwrap();
505 let projects: Vec<ProjectInfo> = if let Some(uuid) = &req.project_uuid {
506 storage
507 .get_project(uuid)
508 .map(|p| vec![ProjectInfo {
509 uuid: p.uuid,
510 name: p.name.clone(),
511 size_bytes: p.size_bytes,
512 file_count: p.file_count,
513 created_at: p.created_at,
514 last_modified: p.updated_at,
515 }])
516 .unwrap_or_default()
517 } else {
518 storage
519 .list_projects()
520 .iter()
521 .map(|p| ProjectInfo {
522 uuid: p.uuid,
523 name: p.name.clone(),
524 size_bytes: p.size_bytes,
525 file_count: p.file_count,
526 created_at: p.created_at,
527 last_modified: p.updated_at,
528 })
529 .collect()
530 };
531
532 send_server_msg(stream, &protocol::build_status_response(projects))?;
533 Ok(())
534}
535
536fn send_server_msg(
541 stream: &mut (impl Write),
542 msg: &ServerMessage,
543) -> Result<(), String> {
544 let frame = pwr_core::frame::encode_frame(msg, msg.message_type())
545 .map_err(|e| format!("encode: {}", e))?;
546 stream.write_all(&frame).map_err(|e| format!("write: {}", e))?;
547 stream.flush().map_err(|e| format!("flush: {}", e))?;
548 Ok(())
549}