1pub mod handlers;
4pub mod router;
5pub mod session;
6
7use std::net::SocketAddr;
8use std::sync::Arc;
9use hyper_util::rt::TokioIo;
10use rustls::pki_types::pem::PemObject;
11use rustls::pki_types::{CertificateDer, PrivateKeyDer};
12use tokio::io::{AsyncRead, AsyncWrite};
13use tokio::net::TcpListener;
14use tokio::sync::mpsc;
15use tokio_util::sync::CancellationToken;
16use tokio_util::task::TaskTracker;
17
18pub use handlers::{ServerState, ServerTransferEvent};
19pub use router::{route_request, ClientConnectionInfo};
20pub use session::{
21 DownloadFile, DownloadManager, DownloadSession, FileTransferStatus, PrepareUploadDecision,
22 SessionEndReason, SessionFile, SessionManager,
23};
24
25use crate::config::AppConfig;
26use crate::crypto::cert::TlsIdentity;
27use crate::crypto::verifier::PinnedClientCertVerifier;
28use crate::discovery::registry::PeerRegistry;
29
30pub struct ServerHandle {
32 bound_port: u16,
34 cancel_token: CancellationToken,
36 join_handle: tokio::task::JoinHandle<()>,
38 download_manager: Arc<DownloadManager>,
40}
41
42impl ServerHandle {
43 pub fn bound_port(&self) -> u16 {
45 self.bound_port
46 }
47
48 pub fn download_manager(&self) -> &Arc<DownloadManager> {
50 &self.download_manager
51 }
52
53 pub fn stop(&self) {
55 self.cancel_token.cancel();
56 }
57
58 pub async fn wait(self) {
60 let _ = self.join_handle.await;
61 }
62}
63
64pub async fn start_server(
70 config: AppConfig,
71 tls_identity: Option<TlsIdentity>,
72 registry: Arc<PeerRegistry>,
73 event_tx: Option<mpsc::Sender<ServerTransferEvent>>,
74) -> anyhow::Result<ServerHandle> {
75 let cancel_token = CancellationToken::new();
76 let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
77 let listener = TcpListener::bind(addr).await?;
78 let bound_port = listener.local_addr()?.port();
79
80 let cert_fingerprint = match tls_identity.as_ref() {
81 Some(ident) => ident.fingerprint.clone(),
82 None => String::new(),
83 };
84
85 let tls_acceptor = match tls_identity.as_ref() {
86 Some(ident) => {
87 let cert_der = CertificateDer::from_pem_slice(ident.cert_pem.as_bytes())?;
88 let key_der = PrivateKeyDer::from_pem_slice(ident.key_pem.as_bytes())?;
89
90 let client_verifier = PinnedClientCertVerifier::new(&ident.cert_pem, false, None)?;
92
93 let server_config = rustls::ServerConfig::builder()
94 .with_client_cert_verifier(Arc::new(client_verifier))
95 .with_single_cert(vec![cert_der], key_der)?;
96
97 Some(tokio_rustls::TlsAcceptor::from(Arc::new(server_config)))
98 }
99 None => None,
100 };
101
102 let download_manager = Arc::new(DownloadManager::new());
103
104 let state = ServerState {
105 config: Arc::new(config),
106 cert_fingerprint,
107 session_manager: Arc::new(SessionManager::new()),
108 download_manager: download_manager.clone(),
109 registry,
110 event_tx,
111 };
112
113 let cancel = cancel_token.clone();
114 let join_handle = tokio::spawn(async move {
115 let tracker = TaskTracker::new();
116
117 loop {
118 tokio::select! {
119 _ = cancel.cancelled() => {
120 tracing::info!("Server shutdown signaled, closing listener");
121 break;
122 }
123 accept_res = listener.accept() => {
124 let (tcp_stream, remote_addr) = match accept_res {
125 Ok(res) => res,
126 Err(err) => {
127 tracing::warn!("Failed to accept incoming TCP connection: {err}");
128 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
129 continue;
130 }
131 };
132
133 let _ = tcp_stream.set_nodelay(true);
134 let tls_acceptor = tls_acceptor.clone();
135 let state = state.clone();
136 let conn_cancel = cancel.clone();
137
138 tracker.spawn(async move {
139 tokio::select! {
140 _ = conn_cancel.cancelled() => {}
141 _ = serve_connection(tcp_stream, remote_addr, tls_acceptor, state) => {}
142 }
143 });
144 }
145 }
146 }
147
148 tracker.close();
149 tracker.wait().await;
150 tracing::info!("Server terminated all active connections");
151 });
152
153 Ok(ServerHandle {
154 bound_port,
155 cancel_token,
156 join_handle,
157 download_manager,
158 })
159}
160
161async fn serve_connection(
162 tcp_stream: tokio::net::TcpStream,
163 remote_addr: SocketAddr,
164 tls_acceptor: Option<tokio_rustls::TlsAcceptor>,
165 state: ServerState,
166) {
167 if let Some(acceptor) = tls_acceptor {
168 match acceptor.accept(tcp_stream).await {
169 Ok(tls_stream) => {
170 let (_, conn) = tls_stream.get_ref();
171 let client_cert_fingerprint = conn
172 .peer_certificates()
173 .and_then(|certs| certs.first())
174 .map(|c| crate::crypto::cert::fingerprint_from_cert_der(c.as_ref()));
175
176 let client_info = ClientConnectionInfo {
177 remote_ip: remote_addr.ip(),
178 cert_fingerprint: client_cert_fingerprint,
179 };
180
181 serve_http_stream(tls_stream, client_info, state).await;
182 }
183 Err(err) => {
184 tracing::debug!("TLS handshake failed with {remote_addr}: {err}");
185 }
186 }
187 } else {
188 let client_info = ClientConnectionInfo {
189 remote_ip: remote_addr.ip(),
190 cert_fingerprint: None,
191 };
192 serve_http_stream(tcp_stream, client_info, state).await;
193 }
194}
195
196async fn serve_http_stream<IO>(stream: IO, client_info: ClientConnectionInfo, state: ServerState)
197where
198 IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
199{
200 let io = TokioIo::new(stream);
201 let service = hyper::service::service_fn(move |req| {
202 let client_info = client_info.clone();
203 let state = state.clone();
204 async move { route_request(req, client_info, &state).await }
205 });
206
207 if let Err(err) = hyper::server::conn::http1::Builder::new()
208 .max_buf_size(128 * 1024)
209 .serve_connection(io, service)
210 .await
211 {
212 tracing::debug!("HTTP/1.1 connection error: {err}");
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use std::collections::HashMap;
220 use sha2::{Digest, Sha256};
221 use lanflare_proto::discovery::{InfoResponseDtoV2, ProtocolType, RegisterDtoV2, RegisterResponseDtoV2};
222 use lanflare_proto::transfer::{FileDto, PrepareUploadRequestDtoV2, PrepareUploadResponseDtoV2};
223
224 #[tokio::test]
225 async fn test_server_plain_http_lifecycle_and_endpoints() {
226 let temp_dir = std::env::temp_dir().join(format!("lanflare_test_{}", uuid::Uuid::new_v4()));
227 tokio::fs::create_dir_all(&temp_dir).await.unwrap();
228
229 let config = AppConfig {
230 alias: "Test Node 1".to_string(),
231 port: 0, download_dir: temp_dir.clone(),
233 auto_accept: true,
234 pin: None,
235 };
236
237 let registry = PeerRegistry::new();
238 let (event_tx, mut event_rx) = mpsc::channel(16);
239
240 let server_handle = start_server(config, None, registry, Some(event_tx))
241 .await
242 .expect("Server should bind");
243
244 let port = server_handle.bound_port();
245 assert!(port > 0);
246
247 let client = reqwest::Client::new();
248 let base_url = format!("http://127.0.0.1:{port}");
249
250 let info_resp = client
252 .get(format!("{base_url}/api/localsend/v2/info"))
253 .send()
254 .await
255 .expect("Info request should succeed");
256 assert_eq!(info_resp.status(), reqwest::StatusCode::OK);
257 let info_dto: InfoResponseDtoV2 = info_resp.json().await.unwrap();
258 assert_eq!(info_dto.alias, "Test Node 1");
259
260 let reg_payload = RegisterDtoV2 {
262 alias: "Remote Peer".to_string(),
263 version: "2.0".to_string(),
264 device_model: Some("Linux".to_string()),
265 device_type: Some(lanflare_proto::discovery::DeviceType::Desktop),
266 fingerprint: "PEER_FINGERPRINT_123".to_string(),
267 port: 53317,
268 protocol: ProtocolType::Http,
269 download: false,
270 };
271
272 let reg_resp = client
273 .post(format!("{base_url}/api/localsend/v2/register"))
274 .json(®_payload)
275 .send()
276 .await
277 .expect("Register request should succeed");
278 assert_eq!(reg_resp.status(), reqwest::StatusCode::OK);
279 let reg_res_dto: RegisterResponseDtoV2 = reg_resp.json().await.unwrap();
280 assert_eq!(reg_res_dto.alias, "Test Node 1");
281
282 match event_rx.recv().await {
284 Some(ServerTransferEvent::DeviceRegistered { ip, info }) => {
285 assert_eq!(ip.to_string(), "127.0.0.1");
286 assert_eq!(info.alias, "Remote Peer");
287 }
288 other => panic!("Expected DeviceRegistered event, got: {other:?}"),
289 }
290
291 let file_content = b"Hello, LanFlare LocalSend Transfer Engine!";
293 let mut hasher = Sha256::new();
294 hasher.update(file_content);
295 let expected_sha256 = format!("{:x}", hasher.finalize());
296
297 let mut files = HashMap::new();
298 files.insert(
299 "file_1".to_string(),
300 FileDto {
301 id: "file_1".to_string(),
302 file_name: "greeting.txt".to_string(),
303 size: file_content.len() as u64,
304 file_type: "text/plain".to_string(),
305 sha256: Some(expected_sha256.clone()),
306 preview: None,
307 metadata: None,
308 },
309 );
310
311 let prep_req = PrepareUploadRequestDtoV2 {
312 info: reg_payload.clone(),
313 files,
314 };
315
316 let prep_resp = client
317 .post(format!("{base_url}/api/localsend/v2/prepare-upload"))
318 .json(&prep_req)
319 .send()
320 .await
321 .expect("Prepare upload should succeed");
322 assert_eq!(prep_resp.status(), reqwest::StatusCode::OK);
323
324 let prep_res_dto: PrepareUploadResponseDtoV2 = prep_resp.json().await.unwrap();
325 assert!(!prep_res_dto.session_id.is_empty());
326 let file_token = prep_res_dto.files.get("file_1").expect("Token for file_1");
327
328 let upload_url = format!(
330 "{base_url}/api/localsend/v2/upload?sessionId={}&fileId=file_1&token={}",
331 prep_res_dto.session_id, file_token
332 );
333
334 let upload_resp = client
335 .post(upload_url)
336 .body(file_content.to_vec())
337 .send()
338 .await
339 .expect("Upload request should succeed");
340 assert_eq!(upload_resp.status(), reqwest::StatusCode::OK);
341
342 let saved_file = temp_dir.join("greeting.txt");
344 assert!(saved_file.exists(), "File must exist on disk");
345 let saved_bytes = tokio::fs::read(&saved_file).await.unwrap();
346 assert_eq!(saved_bytes, file_content);
347
348 let started = event_rx.recv().await.unwrap();
350 assert!(matches!(started, ServerTransferEvent::FileStarted { .. }));
351
352 let completed = event_rx.recv().await.unwrap();
353 assert!(matches!(completed, ServerTransferEvent::FileCompleted { .. }));
354
355 let ended = event_rx.recv().await.unwrap();
356 assert!(matches!(ended, ServerTransferEvent::SessionEnded { .. }));
357
358 server_handle.stop();
360 server_handle.wait().await;
361
362 let _ = tokio::fs::remove_dir_all(&temp_dir).await;
363 }
364
365 #[tokio::test]
366 async fn test_server_pin_protection_and_cancellation() {
367 let temp_dir = std::env::temp_dir().join(format!("lanflare_pin_test_{}", uuid::Uuid::new_v4()));
368 tokio::fs::create_dir_all(&temp_dir).await.unwrap();
369
370 let config = AppConfig {
371 alias: "Protected Node".to_string(),
372 port: 0,
373 download_dir: temp_dir.clone(),
374 auto_accept: true,
375 pin: Some("54321".to_string()),
376 };
377
378 let registry = PeerRegistry::new();
379 let server_handle = start_server(config, None, registry, None)
380 .await
381 .expect("Server should bind");
382
383 let port = server_handle.bound_port();
384 let client = reqwest::Client::new();
385 let base_url = format!("http://127.0.0.1:{port}");
386
387 let mut files = HashMap::new();
388 files.insert(
389 "file_1".to_string(),
390 FileDto {
391 id: "file_1".to_string(),
392 file_name: "test.txt".to_string(),
393 size: 10,
394 file_type: "text/plain".to_string(),
395 sha256: None,
396 preview: None,
397 metadata: None,
398 },
399 );
400
401 let prep_req = PrepareUploadRequestDtoV2 {
402 info: RegisterDtoV2 {
403 alias: "Sender".to_string(),
404 version: "2.0".to_string(),
405 device_model: None,
406 device_type: None,
407 fingerprint: "FP".to_string(),
408 port: 53317,
409 protocol: ProtocolType::Http,
410 download: false,
411 },
412 files,
413 };
414
415 let resp_no_pin = client
417 .post(format!("{base_url}/api/localsend/v2/prepare-upload"))
418 .json(&prep_req)
419 .send()
420 .await
421 .unwrap();
422 assert_eq!(resp_no_pin.status(), reqwest::StatusCode::UNAUTHORIZED);
423
424 let resp_wrong_pin = client
426 .post(format!("{base_url}/api/localsend/v2/prepare-upload?pin=00000"))
427 .json(&prep_req)
428 .send()
429 .await
430 .unwrap();
431 assert_eq!(resp_wrong_pin.status(), reqwest::StatusCode::UNAUTHORIZED);
432
433 let resp_ok_pin = client
435 .post(format!("{base_url}/api/localsend/v2/prepare-upload?pin=54321"))
436 .json(&prep_req)
437 .send()
438 .await
439 .unwrap();
440 assert_eq!(resp_ok_pin.status(), reqwest::StatusCode::OK);
441
442 let prep_dto: PrepareUploadResponseDtoV2 = resp_ok_pin.json().await.unwrap();
443
444 let cancel_resp = client
446 .post(format!(
447 "{base_url}/api/localsend/v2/cancel?sessionId={}",
448 prep_dto.session_id
449 ))
450 .send()
451 .await
452 .unwrap();
453 assert_eq!(cancel_resp.status(), reqwest::StatusCode::OK);
454
455 let token = prep_dto.files.get("file_1").unwrap();
457 let upload_resp = client
458 .post(format!(
459 "{base_url}/api/localsend/v2/upload?sessionId={}&fileId=file_1&token={}",
460 prep_dto.session_id, token
461 ))
462 .body(vec![0u8; 10])
463 .send()
464 .await
465 .unwrap();
466 assert_eq!(upload_resp.status(), reqwest::StatusCode::FORBIDDEN);
467
468 server_handle.stop();
469 server_handle.wait().await;
470 let _ = tokio::fs::remove_dir_all(&temp_dir).await;
471 }
472
473 #[tokio::test]
474 async fn test_server_upload_checksum_mismatch_returns_422() {
475 let temp_dir = std::env::temp_dir().join(format!("lanflare_chk_test_{}", uuid::Uuid::new_v4()));
476 tokio::fs::create_dir_all(&temp_dir).await.unwrap();
477
478 let config = AppConfig {
479 alias: "Checksum Node".to_string(),
480 port: 0,
481 download_dir: temp_dir.clone(),
482 auto_accept: true,
483 pin: None,
484 };
485
486 let registry = PeerRegistry::new();
487 let server_handle = start_server(config, None, registry, None)
488 .await
489 .expect("Server should bind");
490
491 let port = server_handle.bound_port();
492 let client = reqwest::Client::new();
493 let base_url = format!("http://127.0.0.1:{port}");
494
495 let mut files = HashMap::new();
496 files.insert(
497 "file_chk".to_string(),
498 FileDto {
499 id: "file_chk".to_string(),
500 file_name: "corrupt.txt".to_string(),
501 size: 5,
502 file_type: "text/plain".to_string(),
503 sha256: Some("0000000000000000000000000000000000000000000000000000000000000000".to_string()),
505 preview: None,
506 metadata: None,
507 },
508 );
509
510 let prep_req = PrepareUploadRequestDtoV2 {
511 info: RegisterDtoV2 {
512 alias: "Sender".to_string(),
513 version: "2.0".to_string(),
514 device_model: None,
515 device_type: None,
516 fingerprint: "FP".to_string(),
517 port: 53317,
518 protocol: ProtocolType::Http,
519 download: false,
520 },
521 files,
522 };
523
524 let prep_resp = client
525 .post(format!("{base_url}/api/localsend/v2/prepare-upload"))
526 .json(&prep_req)
527 .send()
528 .await
529 .unwrap();
530 assert_eq!(prep_resp.status(), reqwest::StatusCode::OK);
531
532 let prep_dto: PrepareUploadResponseDtoV2 = prep_resp.json().await.unwrap();
533 let token = prep_dto.files.get("file_chk").unwrap();
534
535 let upload_resp = client
537 .post(format!(
538 "{base_url}/api/localsend/v2/upload?sessionId={}&fileId=file_chk&token={}",
539 prep_dto.session_id, token
540 ))
541 .body(b"hello".to_vec())
542 .send()
543 .await
544 .unwrap();
545
546 assert_eq!(upload_resp.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY);
548
549 server_handle.stop();
550 server_handle.wait().await;
551 let _ = tokio::fs::remove_dir_all(&temp_dir).await;
552 }
553
554 #[tokio::test]
555 async fn test_server_https_mtls() {
556 let temp_dir = std::env::temp_dir().join(format!("lanflare_tls_test_{}", uuid::Uuid::new_v4()));
557 tokio::fs::create_dir_all(&temp_dir).await.unwrap();
558
559 let server_ident = crate::crypto::cert::generate_self_signed().unwrap();
560 let client_ident = crate::crypto::cert::generate_self_signed().unwrap();
561
562 let config = AppConfig {
563 alias: "Secure TLS Node".to_string(),
564 port: 0,
565 download_dir: temp_dir.clone(),
566 auto_accept: true,
567 pin: None,
568 };
569
570 let registry = PeerRegistry::new();
571 let server_handle = start_server(config, Some(server_ident.clone()), registry, None)
572 .await
573 .expect("HTTPS Server should bind");
574
575 let port = server_handle.bound_port();
576
577 let client_cert_pkcs8 = reqwest::Identity::from_pem(
579 format!("{}\n{}", client_ident.cert_pem, client_ident.key_pem).as_bytes(),
580 )
581 .unwrap();
582
583 let client = reqwest::Client::builder()
584 .danger_accept_invalid_certs(true) .identity(client_cert_pkcs8)
586 .build()
587 .unwrap();
588
589 let base_url = format!("https://127.0.0.1:{port}");
590
591 let info_resp = client
592 .get(format!("{base_url}/api/localsend/v2/info"))
593 .send()
594 .await
595 .expect("TLS request should succeed");
596 assert_eq!(info_resp.status(), reqwest::StatusCode::OK);
597
598 let info_dto: InfoResponseDtoV2 = info_resp.json().await.unwrap();
599 assert_eq!(info_dto.alias, "Secure TLS Node");
600 assert_eq!(info_dto.fingerprint, server_ident.fingerprint);
601
602 server_handle.stop();
603 server_handle.wait().await;
604 let _ = tokio::fs::remove_dir_all(&temp_dir).await;
605 }
606}