Files
fileshare-app/src/main.rs
T
2026-10-22 20:45:13 +03:00

181 lines
6.6 KiB
Rust

mod discovery;
mod models;
mod p2p;
mod tls;
mod ui;
use axum::{
extract::DefaultBodyLimit,
routing::{get, post},
Router,
};
use axum_server::tls_rustls::RustlsConfig;
use local_ip_address::local_ip;
use models::{AppState, MAX_PAYLOAD_SIZE, MAX_RAM_TEXTS, MEDIA_DIR};
use std::{
collections::{HashMap, VecDeque},
net::SocketAddr,
sync::{Arc, RwLock},
};
#[tokio::main]
async fn main() {
// 0. Регистрация глобального CryptoProvider (ring) для rustls 0.23+
let _ = rustls::crypto::ring::default_provider().install_default();
let args: Vec<String> = std::env::args().collect();
let mut device_name = "My-Device".to_string();
let mut p2p_port: u16 = 8443;
let mut ui_port: u16 = 8080;
let mut custom_ip: Option<String> = None;
let mut i = 1;
let mut positional_idx = 0;
while i < args.len() {
match args[i].as_str() {
"--ip" => {
if i + 1 < args.len() {
custom_ip = Some(args[i + 1].clone());
i += 1;
}
}
"--name" => {
if i + 1 < args.len() {
device_name = args[i + 1].clone();
i += 1;
}
}
"--p2p-port" => {
if i + 1 < args.len() {
if let Ok(p) = args[i + 1].parse() {
p2p_port = p;
}
i += 1;
}
}
"--ui-port" => {
if i + 1 < args.len() {
if let Ok(p) = args[i + 1].parse() {
ui_port = p;
}
i += 1;
}
}
arg if !arg.starts_with("--") => {
match positional_idx {
0 => device_name = arg.to_string(),
1 => {
if let Ok(p) = arg.parse() {
p2p_port = p;
}
}
2 => {
if let Ok(p) = arg.parse() {
ui_port = p;
}
}
3 => custom_ip = Some(arg.to_string()),
_ => {}
}
positional_idx += 1;
}
_ => {}
}
i += 1;
}
let (bind_ip, my_ip) = match custom_ip {
Some(ip) => {
if ip == "0.0.0.0" {
let detected = local_ip()
.map(|addr| addr.to_string())
.unwrap_or_else(|_| "127.0.0.1".to_string());
("0.0.0.0".to_string(), detected)
} else {
(ip.clone(), ip)
}
}
None => {
let detected = local_ip().expect("Не удалось определить локальный IP").to_string();
("0.0.0.0".to_string(), detected)
}
};
tokio::fs::create_dir_all(MEDIA_DIR)
.await
.expect("Не удалось создать папку received_media на диске");
// 1. Генерация сертификата в ОЗУ под выбранный IP и настройка HTTPS-клиентов
let (cert_der, key_der, my_fingerprint) = tls::generate_in_memory_cert(&my_ip);
let trusted_fingerprints = Arc::new(RwLock::new(HashMap::new()));
let http_client = tls::build_pinned_http_client(trusted_fingerprints.clone());
let probe_client = tls::build_probe_http_client();
let state = Arc::new(AppState {
device_name: device_name.clone(),
my_ip: my_ip.clone(),
p2p_port,
my_fingerprint: my_fingerprint.clone(),
ram_texts: RwLock::new(VecDeque::with_capacity(MAX_RAM_TEXTS)),
disk_media: RwLock::new(VecDeque::new()),
peers: RwLock::new(HashMap::new()),
trusted_fingerprints,
http_client,
probe_client,
});
// 2. Запуск широковещательного UDP-маяка на порт 5354
discovery::start_discovery(state.clone());
// 3. Запуск внешнего P2P HTTPS-сервера
let tls_config = RustlsConfig::from_der(vec![cert_der], key_der)
.await
.expect("Ошибка конфигурации TLS");
let p2p_router = Router::new()
.route("/p2p/info", get(p2p::p2p_info))
.route("/p2p/text", post(p2p::p2p_receive_text))
.route("/p2p/media", post(p2p::p2p_receive_media))
.layer(DefaultBodyLimit::max(MAX_PAYLOAD_SIZE))
.with_state(state.clone());
let p2p_addr: SocketAddr = format!("{}:{}", bind_ip, p2p_port)
.parse()
.unwrap_or_else(|_| panic!("Некорректный сокет: {}:{}", bind_ip, p2p_port));
tokio::spawn(async move {
axum_server::bind_rustls(p2p_addr, tls_config)
.serve(p2p_router.into_make_service())
.await
.expect("Ошибка P2P HTTPS-сервера");
});
// 4. Запуск локального UI-сервера (127.0.0.1)
let ui_router = Router::new()
.route("/", get(ui::ui_index))
.route("/ui/state", get(ui::ui_get_state))
.route("/ui/manual-peer", post(ui::ui_add_manual_peer))
.route("/ui/trust", post(ui::ui_trust_peer))
.route("/ui/clear-ram", post(ui::ui_clear_ram))
.route("/ui/send-text", post(ui::ui_send_text))
.route("/ui/send-media", post(ui::ui_send_media))
.route("/ui/files/:filename", get(ui::ui_serve_saved_file))
.layer(DefaultBodyLimit::max(MAX_PAYLOAD_SIZE))
.with_state(state);
let ui_addr: SocketAddr = format!("127.0.0.1:{}", ui_port).parse().unwrap();
println!("==============================================================");
println!(" Устройство: {}", device_name);
println!(" P2P сокет (HTTPS): https://{}:{}", bind_ip, p2p_port);
println!(" Маяк обнаружения (UDP): 255.255.255.255:5354");
println!(" КОД СВЕРКИ СЕРТИФИКАТА (PIN): [ {} ]", &my_fingerprint[..8].to_uppercase());
println!(" Хранилище медиа: ./{}/", MEDIA_DIR);
println!("--------------------------------------------------------------");
println!(" ОТКРОЙТЕ ИНТЕРФЕЙС В БРАУЗЕРЕ: http://127.0.0.1:{}", ui_port);
println!("==============================================================");
let listener = tokio::net::TcpListener::bind(ui_addr).await.unwrap();
axum::serve(listener, ui_router).await.unwrap();
}