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