1use super::args::AppArgs;
3use crate::crypto::{Identity, StorageEncryption};
4use anyhow::{anyhow, Result};
5use base64::prelude::*;
6use std::net::TcpListener;
7use std::path::Path;
8use std::sync::Arc;
9use tracing_subscriber::EnvFilter;
10
11pub struct PreparedApp {
16 pub args: AppArgs,
18 pub port: u16,
20 pub web_port: u16,
22 pub identity: Arc<Identity>,
24 pub db: sled::Db,
26 pub encryption: Option<StorageEncryption>,
28}
29
30pub fn prepare(args: AppArgs) -> Result<PreparedApp> {
50 let port = args.port.unwrap_or(find_free_port()?);
51 let web_port = args.web_port.unwrap_or(find_free_port()?);
52
53 configure_logging(args.mailbox);
54 print_start_banner(&args, port, web_port);
55
56 std::fs::create_dir_all(&args.data_dir)?;
57
58 let identity_path = format!("{}/identity.json", args.data_dir);
59 let identity = Arc::new(Identity::load_or_generate(&identity_path)?);
60
61 print_identity_info(&identity);
62
63 let db_path = format!("{}/db", args.data_dir);
64 let db = sled::open(&db_path)?;
65
66 let encryption = if args.encrypt {
67 println!("🔐 Storage encryption enabled");
68
69 let password = resolve_encryption_password(&args)?;
70 let salt_path = format!("{}/encryption_salt.bin", args.data_dir);
71 let salt = load_or_create_salt(&salt_path)?;
72
73 Some(StorageEncryption::new(&password, &salt)?)
74 } else {
75 None
76 };
77
78 Ok(PreparedApp {
79 args,
80 port,
81 web_port,
82 identity,
83 db,
84 encryption,
85 })
86}
87
88fn configure_logging(mailbox_mode: bool) {
92 if mailbox_mode {
93 let _ = tracing_subscriber::fmt()
94 .with_env_filter(EnvFilter::new("info,p2p_chat=debug"))
95 .try_init();
96 }
97}
98
99fn print_start_banner(args: &AppArgs, port: u16, web_port: u16) {
101 println!("🚀 Starting P2P E2E Messenger");
102 println!(
103 "Mode: {}",
104 if args.mailbox {
105 "Mailbox Node"
106 } else {
107 "Client"
108 }
109 );
110 println!("Port: {}", port);
111 if !args.mailbox {
112 println!("Web UI: http://127.0.0.1:{}", web_port);
113 }
114 println!("Data directory: {}", args.data_dir);
115 println!();
116}
117
118fn print_identity_info(identity: &Arc<Identity>) {
120 println!("Identity loaded:");
121 println!(" Peer ID: {}", identity.peer_id);
122 println!(
123 " E2E Public Key: {}",
124 BASE64_STANDARD.encode(identity.hpke_public_key())
125 );
126 println!();
127}
128
129fn resolve_encryption_password(args: &AppArgs) -> Result<String> {
133 args
134 .encryption_password
135 .clone()
136 .or_else(|| std::env::var("P2P_MESSENGER_PASSWORD").ok())
137 .ok_or_else(|| {
138 anyhow!(
139 "Encryption password not provided. Supply --encryption-password or set P2P_MESSENGER_PASSWORD."
140 )
141 })
142}
143
144fn load_or_create_salt(path: &str) -> Result<[u8; 16]> {
146 if Path::new(path).exists() {
147 let bytes = std::fs::read(path)?;
148 if bytes.len() != 16 {
149 anyhow::bail!(
150 "Encryption salt at '{}' has unexpected length {} (expected 16)",
151 path,
152 bytes.len()
153 );
154 }
155 let mut salt = [0u8; 16];
156 salt.copy_from_slice(&bytes);
157 Ok(salt)
158 } else {
159 let generated = StorageEncryption::generate_salt();
160 std::fs::write(path, generated)?;
161 Ok(generated)
162 }
163}
164
165fn find_free_port() -> Result<u16> {
167 let listener = TcpListener::bind("127.0.0.1:0")?;
168 Ok(listener.local_addr()?.port())
169}