main.rs
1 mod gui; 2 mod key_operations; 3 mod network_operations; 4 mod key_exchange; 5 mod authentication; 6 mod encryption; 7 use gui::create_rocket; 8 use gui::MessagingApp; 9 use key_operations::key_operations_dilithium; 10 use key_operations::key_operations_eddsa; 11 use network_operations::create_client_with_proxy; 12 use network_operations::fetch_kyber_pubkey; 13 use network_operations::fetch_dilithium_pubkeys; 14 use network_operations::fetch_eddsa_pubkeys; 15 use network_operations::fetch_ciphertext; 16 use network_operations::send_kyber_pubkey; 17 use network_operations::send_dilithium_pubkey; 18 use network_operations::send_eddsa_pubkey; 19 use network_operations::send_ciphertext; 20 use network_operations::send_encrypted_message; 21 use network_operations::receive_and_fetch_messages; 22 use key_exchange::kyber_key_exchange; 23 use key_exchange::perform_ecdh_key_exchange; 24 use authentication::sign_data_with_dilithium; 25 use authentication::sign_data_with_eddsa; 26 use authentication::verify_signature_with_dilithium; 27 use authentication::verify_signature_with_eddsa; 28 use encryption::derive_salt_from_password; 29 use encryption::derive_key; 30 use encryption::combine_shared_secrets; 31 use encryption::encrypt_data; 32 use encryption::decrypt_data; 33 34 use oqs::*; 35 use oqs::sig::{Sig, PublicKey, SecretKey, Algorithm as SigAlgorithm}; 36 use rand::Rng; 37 use reqwest::blocking::get; 38 use std::fs; 39 use std::fs::File; 40 use std::path::Path; 41 use std::process::Command; 42 use std::str::FromStr; 43 use hex; 44 use std::io::{self, Write}; 45 use rpassword::read_password; 46 use std::result::Result; 47 use std::{ 48 collections::HashSet, 49 error::Error, 50 }; 51 use ipnetwork::IpNetwork; 52 use serde::{Deserialize, Serialize}; 53 use chacha20poly1305::aead::OsRng; 54 use rand::RngCore; 55 use sha3::{Sha3_512, Digest}; 56 use ed25519_dalek::VerifyingKey as Ed25519PublicKey; 57 58 // Function to get the raw bytes from PublicKey 59 fn get_raw_bytes_public_key(pk: &PublicKey) -> &[u8] { 60 pk.as_ref() // Directly return the raw bytes 61 } 62 63 // Function to get the raw bytes from SecretKey 64 fn get_raw_bytes_secret_key(sk: &SecretKey) -> &[u8] { 65 sk.as_ref() // Directly return the raw bytes 66 } 67 68 69 #[derive(Serialize, Deserialize, Debug)] // Make sure it can be serialized and deserialized 70 struct MessageData { 71 message: String, 72 room_id: String, 73 } 74 75 fn fingerprint_dilithium_public_key(public_key: &PublicKey) -> String { 76 // Access the raw bytes of the public key using as_ref() 77 let raw_bytes = public_key.as_ref(); // This should return &[u8] 78 let hashed = Sha3_512::digest(raw_bytes); 79 hex::encode(hashed) 80 } 81 82 fn fingerprint_eddsa_public_key(public_key: &Ed25519PublicKey) -> String { 83 // Hash the public key to generate a fingerprint (using SHA-512) 84 let hashed = Sha3_512::digest(public_key); 85 hex::encode(hashed) 86 } 87 88 fn request_user_confirmation(fingerprint: &str, own_fingerprint: &str) -> Result<bool, io::Error> { 89 // If the fingerprint matches your own public key, auto-confirm 90 if fingerprint == own_fingerprint { 91 return Ok(true); 92 } 93 94 println!("The fingerprint of the received public key is: {}", fingerprint); 95 print!("Do you confirm this fingerprint? (yes/no): "); 96 io::stdout().flush()?; 97 98 let mut input = String::new(); 99 io::stdin().read_line(&mut input)?; 100 let response = input.trim().to_lowercase(); 101 102 match response.as_str() { 103 "yes" => Ok(true), 104 "no" => Ok(false), 105 _ => { 106 println!("Invalid input. Please enter 'yes' or 'no'."); 107 request_user_confirmation(fingerprint, own_fingerprint) // Retry if invalid input 108 } 109 } 110 } 111 112 fn generate_random_room_id() -> String { 113 const ID_LENGTH: usize = 16; 114 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 115 116 let mut rng = OsRng; 117 let mut room_id = String::with_capacity(ID_LENGTH); 118 119 for _ in 0..ID_LENGTH { 120 let idx = (rng.next_u32() as usize) % CHARSET.len(); 121 room_id.push(CHARSET[idx] as char); 122 } 123 124 room_id 125 } 126 127 fn load_blacklist(file_path: &str) -> HashSet<IpNetwork> { 128 match fs::read_to_string(file_path) { 129 Ok(contents) => contents 130 .lines() 131 .filter_map(|line| { 132 let line = line.trim(); 133 IpNetwork::from_str(line).ok() 134 }) 135 .collect(), 136 Err(_) => HashSet::new(), 137 } 138 } 139 140 fn is_onion_site(url: &str) -> bool { 141 url.contains(".onion") 142 } 143 144 fn is_eepsite(url: &str) -> bool { 145 url.contains(".i2p") 146 } 147 148 fn resolve_dns(host: &str) -> Result<String, Box<dyn Error>> { 149 let output = Command::new("dig") 150 .args(["+short", host]) 151 .output()?; 152 153 if output.status.success() { 154 let response = String::from_utf8_lossy(&output.stdout); 155 // Find the first valid IP address in the response 156 if let Some(ip) = response 157 .lines() 158 .filter(|line| line.parse::<std::net::IpAddr>().is_ok()) 159 .next() 160 { 161 return Ok(ip.to_string()); 162 } 163 } 164 165 Err("Failed to resolve DNS to an IP address.".into()) 166 } 167 168 fn is_ip_blacklisted(ip: &str, blacklist: &HashSet<IpNetwork>) -> bool { 169 // Try to parse the IP address. If it fails, return false. 170 let ip: std::net::IpAddr = match ip.parse() { 171 Ok(ip) => ip, 172 Err(_) => return false, // Return false if parsing fails 173 }; 174 175 // Check if the IP is within any of the CIDR ranges in the blacklist 176 blacklist.iter().any(|range| range.contains(ip)) 177 } 178 179 fn pad_message(message: &str, max_length: usize) -> String { 180 let current_length = message.len(); 181 182 if current_length < max_length { 183 let padding_len = max_length - current_length; 184 185 let mut rng = OsRng; // Cryptographically secure RNG 186 let padding: String = (0..padding_len) 187 .map(|_| rng.gen_range(33..127) as u8 as char) // ASCII printable characters range 188 .collect(); 189 190 // Wrap the padding in <padding> and </padding> tags 191 return format!("{}<padding>{}</padding>", message, padding); 192 } 193 194 message.to_string() // Return the message as is if it's already at max length 195 } 196 197 // The main function 198 fn main() -> Result<(), Box<dyn Error>> { 199 use std::sync::{Arc, Mutex}; 200 use std::{io::{self, Write}, thread, time::Duration}; 201 202 let sigalg = sig::Sig::new(sig::Algorithm::Dilithium5)?; 203 204 // Get user input for the choice of interface 205 let mut input = String::new(); 206 print!("Choose interface (CLI or GUI): "); 207 io::stdout().flush()?; 208 io::stdin().read_line(&mut input)?; 209 let interface_choice = input.trim().to_string(); 210 input.clear(); 211 212 // Step 1: Ask user to either create a room ID or join one 213 println!("Would you like to create a new room or join an existing one?"); 214 println!("Type 'create' to create a new room or 'join' to join an existing one."); 215 let mut choice = String::new(); 216 io::stdin().read_line(&mut choice)?; 217 let choice = choice.trim(); 218 219 let room_id = match choice { 220 "create" => { 221 let new_room_id = generate_random_room_id(); 222 println!("Generated new room ID: {}", new_room_id); 223 new_room_id 224 } 225 "join" => { 226 println!("Enter the room ID to join:"); 227 let mut room_input = String::new(); 228 io::stdin().read_line(&mut room_input)?; 229 room_input.trim().to_string() 230 } 231 _ => { 232 println!("Invalid choice. Please restart the program and choose 'create' or 'join'."); 233 return Ok(()); 234 } 235 }; 236 237 238 // Check if cloudflare-ip-blacklist.txt exists in the current directory 239 let blacklist_file = "cloudflare-ip-blacklist.txt"; 240 if !Path::new(blacklist_file).exists() { 241 println!("File '{}' not found. Fetching from Codeberg...", blacklist_file); 242 243 // Fetch the blacklist file from the raw Codeberg URL 244 let url = "https://codeberg.org/umutcamliyurt/Amnezichat/raw/branch/main/client/cloudflare-ip-blacklist.txt"; // Replace with actual URL 245 let response = get(url)?; 246 247 if response.status().is_success() { 248 let content = response.text()?; 249 // Save the fetched content to cloudflare-ip-blacklist.txt in the current directory 250 let mut file = File::create(blacklist_file)?; 251 file.write_all(content.as_bytes())?; 252 println!("File fetched and saved as '{}'.", blacklist_file); 253 } else { 254 println!("Failed to fetch the file from URL."); 255 return Err("Failed to fetch blacklist.".into()); 256 } 257 } 258 259 // Load the blacklist from the file 260 let blacklist = load_blacklist("cloudflare-ip-blacklist.txt"); 261 262 // Get the server URL 263 let mut input = String::new(); 264 print!("Enter the server URL: "); 265 io::stdout().flush()?; 266 io::stdin().read_line(&mut input)?; 267 let url = input.trim().to_string(); 268 input.clear(); 269 270 if is_onion_site(&url) { 271 println!("This is an .onion site. Skipping IP check."); 272 } 273 else if is_eepsite(&url) 274 { 275 println!("This is an .i2p site. Skipping IP check."); 276 } 277 else { 278 // Extract the host portion of the URL 279 let host = url 280 .split('/') 281 .nth(2) 282 .unwrap_or(&url) // Extract the host portion 283 .split(':') 284 .next() 285 .unwrap_or(&url); 286 287 // Resolve IP of the host 288 match resolve_dns(host) { 289 Ok(ip) => { 290 // Check if the resolved IP is blacklisted 291 if is_ip_blacklisted(&ip, &blacklist) { 292 println!("WARNING! The IP {} is in the blacklist.", ip); 293 println!("The server you're trying to access is behind a Cloudflare reverse proxy."); 294 println!("Proceed with caution as this setup may expose you to several potential risks:"); 295 println!(); 296 println!("Deanonymization attacks (including 0-click exploits)"); 297 println!("Metadata leaks"); 298 println!("Encryption vulnerabilities"); 299 println!("AI-based traffic analysis"); 300 println!("Connectivity issues"); 301 println!("Other undetected malicious behavior"); 302 println!(); 303 println!("What you can do:"); 304 println!("1. Choose a different server"); 305 println!("2. Self-host your own server"); 306 println!("3. Proceed anyway (Dangerous!)"); 307 println!(); 308 println!("For more info: https://git.calitabby.net/mirrors/deCloudflare"); 309 println!(); 310 println!("Do you want to proceed? (yes/no)"); 311 312 let mut input = String::new(); 313 io::stdin() 314 .read_line(&mut input) 315 .expect("Failed to read input"); 316 let input = input.trim().to_lowercase(); 317 318 match input.as_str() { 319 "yes" | "y" => { 320 println!("Proceeding..."); 321 } 322 "no" | "n" => { 323 println!("Operation aborted!"); 324 return Ok(()); // Exit the main function, effectively closing the app 325 } 326 _ => { 327 println!("Invalid input. Please enter 'yes' or 'no'."); 328 } 329 } 330 } 331 } 332 Err(e) => { 333 println!("Failed to resolve IP for the server: {}", e); 334 } 335 } 336 } 337 338 // Get the username 339 print!("Enter your username: "); 340 io::stdout().flush()?; 341 io::stdin().read_line(&mut input)?; 342 let username = input.trim().to_string(); 343 input.clear(); 344 345 print!("Enter private key encryption password: "); 346 io::stdout().flush()?; 347 let private_password = read_password()?.to_string(); 348 349 println!("Is this a group chat? (yes/no): "); 350 let mut is_group_chat = String::new(); 351 io::stdin().read_line(&mut is_group_chat)?; 352 let is_group_chat = is_group_chat.trim().to_lowercase() == "yes"; 353 354 let room_password = if is_group_chat { 355 // Loop to get a valid room password for group chat 356 loop { 357 print!("Enter room password (must be longer than 8 characters): "); 358 io::stdout().flush()?; // Ensure the prompt is displayed immediately 359 let mut input = String::new(); 360 io::stdin().read_line(&mut input)?; 361 let password_input = input.trim(); 362 if password_input.len() > 8 { 363 break password_input.to_string(); // Exit the loop with valid password 364 } else { 365 println!("Error: Password must be longer than 8 characters. Please try again."); 366 } 367 } 368 } else { 369 // For one-to-one chat, skip password setup 370 String::new() 371 }; 372 373 // Derive the key from the room password if it's a group chat 374 let room_password = if is_group_chat { 375 let salt = derive_salt_from_password(&room_password); 376 let key = derive_key(&room_password, &salt); 377 hex::encode(key) 378 } else { 379 String::new() // No room password required for one-to-one chat 380 }; 381 382 // Skip key exchange and create hybrid_shared_secret if it's a group chat 383 if is_group_chat { 384 println!("Skipping key exchange. Using room password as shared secret."); 385 let hybrid_shared_secret = room_password.clone(); // Use room password directly 386 println!("Shared secret established."); 387 println!("You can now start messaging!"); 388 389 // Shared data setup for messaging 390 let shared_hybrid_secret = Arc::new(hybrid_shared_secret.clone()); 391 let shared_room_id = Arc::new(Mutex::new(room_id.clone())); 392 let shared_url = Arc::new(Mutex::new(url.clone())); 393 394 // Clone interface_choice before passing to thread 395 let interface_choice_clone = interface_choice.clone(); // Clone interface_choice 396 397 let random_data_thread = { 398 let shared_room_id = Arc::clone(&shared_room_id); 399 let shared_url = Arc::clone(&shared_url); 400 let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret); 401 402 thread::spawn(move || loop { 403 // Generate cryptographically secure random data 404 let mut random_data = vec![0u8; OsRng.next_u32() as usize % 2048 + 1]; // Random size between 1 and 2048 405 OsRng.fill_bytes(&mut random_data); 406 407 let dummy_message = format!("[DUMMY_DATA]: {:?}", random_data); 408 409 let encrypted_dummy_message = match encrypt_data(&dummy_message, &shared_hybrid_secret) { 410 Ok(data) => data, 411 Err(e) => { 412 eprintln!("Error encrypting dummy message: {}", e); 413 continue; 414 } 415 }; 416 417 let room_id_locked = shared_room_id.lock().unwrap(); 418 let url_locked = shared_url.lock().unwrap(); 419 420 // Pad the message to a fixed length (e.g., 2048 bytes) 421 let padded_message = pad_message(&encrypted_dummy_message, 2048); 422 423 if let Err(e) = send_encrypted_message(&padded_message, &room_id_locked, &url_locked) { 424 eprintln!("Error sending dummy message: {}", e); 425 } 426 427 // Sleep for a random interval (1 to 120 seconds) 428 let sleep_duration = Duration::from_secs(OsRng.next_u32() as u64 % 120 + 1); 429 thread::sleep(sleep_duration); 430 }) 431 }; 432 433 // Spawn message fetch thread 434 let fetch_thread = thread::spawn({ 435 let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret); 436 let shared_room_id = Arc::clone(&shared_room_id); 437 let shared_url = Arc::clone(&shared_url); 438 let interface_choice_clone = interface_choice_clone.clone(); // Clone here as well 439 440 move || loop { 441 // Lock the shared resources to access their values 442 let room_id_locked = shared_room_id.lock().unwrap().clone(); 443 let url_locked = shared_url.lock().unwrap().clone(); 444 445 // Fetch and process messages 446 match receive_and_fetch_messages( 447 &room_id_locked, 448 &shared_hybrid_secret, 449 &url_locked, 450 interface_choice_clone.to_lowercase() == "gui", // Pass true for GUI, false for CLI 451 ) { 452 Ok(messages) => { 453 // Print messages only if interface is CLI 454 if interface_choice_clone.to_lowercase() == "cli" { 455 for message in messages { 456 println!("{}", message); 457 } 458 } 459 } 460 Err(e) => { 461 eprintln!("Error fetching messages: {}", e); 462 } 463 } 464 465 // Sleep for 10 seconds before the next fetch 466 thread::sleep(Duration::from_secs(10)); 467 } 468 }); 469 470 471 // Handle GUI or CLI messaging 472 if interface_choice.to_lowercase() == "gui" { 473 let rt = rocket::tokio::runtime::Runtime::new().unwrap(); 474 rt.block_on(async { 475 // Wrap only for passing to run_gui 476 let shared_hybrid_secret_for_gui = shared_hybrid_secret; 477 478 // Correctly clone Arc<Mutex<String>> instead of Arc<String> 479 let shared_room_id_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_room_id); 480 let shared_url_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_url); 481 482 // Pass the arguments 483 let app = MessagingApp::new( 484 username, 485 shared_hybrid_secret_for_gui, 486 shared_room_id_for_gui, 487 shared_url_for_gui, 488 ); 489 490 // Await the async launch function 491 if let Err(e) = create_rocket(app).launch().await { 492 eprintln!("Rocket server failed: {}", e); 493 } 494 }); 495 496 } else { 497 loop { 498 let mut message = String::new(); 499 print!("Enter your message (or type 'exit' to quit): "); 500 io::stdout().flush()?; 501 io::stdin().read_line(&mut message)?; 502 503 let message = message.trim(); 504 505 if message == "exit" { 506 println!("Exiting messaging session."); 507 break; 508 } 509 510 let message = format!("<strong>{}</strong>: {}", username, message); 511 512 // Pad the message to a fixed length (e.g., 2048 bytes) 513 let padded_message = pad_message(&message, 2048); 514 515 let encrypted_message = encrypt_data(&padded_message, &hybrid_shared_secret)?; 516 send_encrypted_message(&encrypted_message, &room_id, &url)?; 517 } 518 } 519 520 // Ensure both threads terminate gracefully 521 if let Err(e) = random_data_thread.join() { 522 eprintln!("Random data thread terminated with error: {:?}", e); 523 } 524 525 if let Err(e) = fetch_thread.join() { 526 eprintln!("Fetch thread terminated with error: {:?}", e); 527 } 528 529 return Ok(()); 530 } 531 532 // Continue with the key exchange process for one-to-one chat 533 // Step 2: Load or generate Dilithium5 and EdDSA keys for the user 534 let dilithium_keys = key_operations_dilithium(&sigalg, &username, &private_password); 535 let Ok((dilithium_pk, dilithium_sk)) = dilithium_keys else { todo!() }; 536 537 let eddsa_keys = key_operations_eddsa(&username, &private_password); 538 let Ok((eddsa_sk, eddsa_pk)) = eddsa_keys else { todo!() }; 539 540 let encoded_dilithium_pk = hex::encode(&dilithium_pk); 541 send_dilithium_pubkey(&room_id, &encoded_dilithium_pk, &url); 542 543 let encoded_eddsa_pk = hex::encode(&eddsa_pk); 544 send_eddsa_pubkey(&room_id, &encoded_eddsa_pk, &url); 545 546 let fingerprint_dilithium = fingerprint_dilithium_public_key(&dilithium_pk); 547 548 println!("Own Dilithium5 fingerprint: {}", fingerprint_dilithium); 549 550 let fingerprint_eddsa = match Ed25519PublicKey::from_bytes(&eddsa_pk) { 551 Ok(public_key) => fingerprint_eddsa_public_key(&public_key), 552 Err(e) => { 553 eprintln!("Failed to convert EdDSA public key: {}", e); 554 return Err(Box::new(e)); 555 } 556 }; 557 558 println!("Own EdDSA fingerprint: {}", fingerprint_eddsa); 559 560 let mut processed_fingerprints: HashSet<String> = HashSet::new(); 561 processed_fingerprints.insert(fingerprint_dilithium.clone()); 562 processed_fingerprints.insert(fingerprint_eddsa.clone()); 563 564 let mut all_other_dilithium_keys: Vec<oqs::sig::PublicKey> = Vec::new(); 565 566 while all_other_dilithium_keys.len() < 1 { 567 println!("Waiting for Dilithium public key..."); 568 thread::sleep(Duration::from_secs(5)); 569 570 let encoded_other_dilithium_pks = fetch_dilithium_pubkeys(&room_id, &url); 571 572 for encoded_pk in encoded_other_dilithium_pks { 573 if let Ok(decoded_pk) = hex::decode(&encoded_pk) { 574 575 // Create a Sig instance for the "Dilithium5" algorithm 576 let algorithm = SigAlgorithm::Dilithium5; 577 578 // Create a Sig instance for the chosen algorithm 579 let sig = Sig::new(algorithm).map_err(|_| "Failed to initialize signature scheme")?; 580 581 // Convert the decoded public key to a PublicKey using public_key_from_bytes 582 if let Some(public_key_ref) = sig.public_key_from_bytes(&decoded_pk) { 583 // Convert PublicKeyRef<'_> to PublicKey by calling to_owned() 584 let public_key = public_key_ref.to_owned(); 585 586 let fetched_fingerprint = fingerprint_dilithium_public_key(&public_key); 587 588 if fetched_fingerprint == fingerprint_dilithium { 589 continue; 590 } 591 592 if processed_fingerprints.contains(&fetched_fingerprint) { 593 continue; 594 } 595 596 if request_user_confirmation(&fetched_fingerprint, &fingerprint_dilithium)? { 597 // Push the owned PublicKey to the list 598 all_other_dilithium_keys.push(public_key); 599 processed_fingerprints.insert(fetched_fingerprint); 600 } else { 601 eprintln!("User did not confirm the public key fingerprint."); 602 } 603 } else { 604 eprintln!("Failed to decode valid public key."); 605 } 606 } else { 607 eprintln!("Failed to convert decoded key to PublicKey."); 608 } 609 } 610 } 611 612 613 614 println!("Received Dilithium5 public key from the server."); 615 616 let mut eddsa_key: Option<Ed25519PublicKey> = None; 617 618 while eddsa_key.is_none() { 619 println!("Waiting for EdDSA public key..."); 620 thread::sleep(Duration::from_secs(5)); 621 622 let encoded_other_eddsa_pks = fetch_eddsa_pubkeys(&room_id, &url); 623 624 for encoded_pk in encoded_other_eddsa_pks { 625 if let Ok(decoded_pk) = hex::decode(&encoded_pk) { 626 if let Ok(public_key) = Ed25519PublicKey::from_bytes( 627 decoded_pk.as_slice().try_into().expect("Decoded public key must be 32 bytes long"), 628 ) { 629 let fetched_fingerprint = fingerprint_eddsa_public_key(&public_key); 630 631 if fetched_fingerprint == fingerprint_eddsa { 632 continue; 633 } 634 635 if processed_fingerprints.contains(&fetched_fingerprint) { 636 continue; 637 } 638 639 if request_user_confirmation(&fetched_fingerprint, &fingerprint_eddsa)? { 640 eddsa_key = Some(public_key); 641 processed_fingerprints.insert(fetched_fingerprint); 642 break; 643 } else { 644 eprintln!("User did not confirm the public key fingerprint."); 645 } 646 } else { 647 eprintln!("Failed to decode valid public key."); 648 } 649 } else { 650 eprintln!("Failed to convert decoded key to PublicKey."); 651 } 652 } 653 } 654 655 println!("Received EdDSA public key from the server."); 656 657 let mut all_dilithium_pks = vec![dilithium_pk]; 658 all_dilithium_pks.extend(all_other_dilithium_keys); 659 660 let kyber_shared_secret = kyber_key_exchange(&room_id, &all_dilithium_pks, &dilithium_sk, &url)?; 661 let ecdh_shared_secret = if let Some(ref eddsa_key) = eddsa_key { 662 perform_ecdh_key_exchange(&room_id, &eddsa_sk.to_bytes(), eddsa_key, &url)? 663 } else { 664 return Err("EdDSA public key is missing".into()); 665 }; 666 667 let hybrid_shared_secret = combine_shared_secrets(&kyber_shared_secret, &ecdh_shared_secret)?; 668 669 println!("Hybrid shared secret established."); 670 println!("You can now start messaging!"); 671 672 let shared_hybrid_secret = Arc::new(hybrid_shared_secret.clone()); // Keep as Arc<String> 673 let shared_room_id = Arc::new(Mutex::new(room_id.clone())); // Wrap in Mutex 674 let shared_url = Arc::new(Mutex::new(url.clone())); // Wrap in Mutex 675 676 let interface_choice_clone = interface_choice.clone(); // Clone interface_choice before moving it into the thread 677 678 let random_data_thread = { 679 let shared_room_id = Arc::clone(&shared_room_id); 680 let shared_url = Arc::clone(&shared_url); 681 let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret); 682 683 thread::spawn(move || loop { 684 // Generate cryptographically secure random data 685 let mut random_data = vec![0u8; OsRng.next_u32() as usize % 2048 + 1]; // Random size between 1 and 2048 686 OsRng.fill_bytes(&mut random_data); 687 688 let dummy_message = format!("[DUMMY_DATA]: {:?}", random_data); 689 690 // Pad the message to a fixed length (e.g., 2048 bytes) 691 let padded_message = pad_message(&dummy_message, 2048); 692 693 let encrypted_dummy_message = match encrypt_data(&padded_message, &shared_hybrid_secret) { 694 Ok(data) => data, 695 Err(e) => { 696 eprintln!("Error encrypting dummy message: {}", e); 697 continue; 698 } 699 }; 700 701 let room_id_locked = shared_room_id.lock().unwrap(); 702 let url_locked = shared_url.lock().unwrap(); 703 704 if let Err(e) = send_encrypted_message(&encrypted_dummy_message, &room_id_locked, &url_locked) { 705 eprintln!("Error sending dummy message: {}", e); 706 } 707 708 // Sleep for a random interval (1 to 120 seconds) 709 let sleep_duration = Duration::from_secs(OsRng.next_u32() as u64 % 120 + 1); 710 thread::sleep(sleep_duration); 711 }) 712 }; 713 714 // Spawn message fetch thread 715 let fetch_thread = thread::spawn({ 716 let shared_hybrid_secret = Arc::clone(&shared_hybrid_secret); 717 let shared_room_id = Arc::clone(&shared_room_id); 718 let shared_url = Arc::clone(&shared_url); 719 let interface_choice_clone = interface_choice_clone.clone(); // Clone here as well 720 721 move || loop { 722 // Lock the shared resources to access their values 723 let room_id_locked = shared_room_id.lock().unwrap().clone(); 724 let url_locked = shared_url.lock().unwrap().clone(); 725 726 // Fetch and process messages 727 match receive_and_fetch_messages( 728 &room_id_locked, 729 &shared_hybrid_secret, 730 &url_locked, 731 interface_choice_clone.to_lowercase() == "gui", // Pass true for GUI, false for CLI 732 ) { 733 Ok(messages) => { 734 // Print messages only if interface is CLI 735 if interface_choice_clone.to_lowercase() == "cli" { 736 for message in messages { 737 println!("{}", message); 738 } 739 } 740 } 741 Err(e) => { 742 eprintln!("Error fetching messages: {}", e); 743 } 744 } 745 746 // Sleep for 10 seconds before the next fetch 747 thread::sleep(Duration::from_secs(10)); 748 } 749 }); 750 751 752 if interface_choice.to_lowercase() == "gui" { 753 let rt = rocket::tokio::runtime::Runtime::new().unwrap(); 754 rt.block_on(async { 755 // Wrap only for passing to run_gui 756 let shared_hybrid_secret_for_gui = shared_hybrid_secret; 757 758 // Correctly clone Arc<Mutex<String>> instead of Arc<String> 759 let shared_room_id_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_room_id); 760 let shared_url_for_gui: Arc<Mutex<String>> = Arc::clone(&shared_url); 761 762 // Pass the arguments 763 let app = MessagingApp::new( 764 username, 765 shared_hybrid_secret_for_gui, 766 shared_room_id_for_gui, 767 shared_url_for_gui, 768 ); 769 770 // Await the async launch function 771 if let Err(e) = create_rocket(app).launch().await { 772 eprintln!("Rocket server failed: {}", e); 773 } 774 }); 775 776 } else { 777 loop { 778 let mut message = String::new(); 779 print!("Enter your message (or type 'exit' to quit): "); 780 io::stdout().flush()?; 781 io::stdin().read_line(&mut message)?; 782 783 let message = message.trim(); 784 785 if message == "exit" { 786 println!("Exiting messaging session."); 787 break; 788 } 789 790 let message = format!("<strong>{}</strong>: {}", username, message); 791 792 // Pad the message to a fixed length (e.g., 2048 bytes) 793 let padded_message = pad_message(&message, 2048); 794 795 let encrypted_message = encrypt_data(&padded_message, &hybrid_shared_secret)?; 796 send_encrypted_message(&encrypted_message, &room_id, &url)?; 797 } 798 } 799 800 // Ensure both threads terminate gracefully 801 if let Err(e) = random_data_thread.join() { 802 eprintln!("Random data thread terminated with error: {:?}", e); 803 } 804 805 if let Err(e) = fetch_thread.join() { 806 eprintln!("Fetch thread terminated with error: {:?}", e); 807 } 808 809 810 Ok(()) 811 } 812 813 // Function to clear the screen before printing new messages 814 fn clear_screen() { 815 if cfg!(target_os = "windows") { 816 // Windows 817 Command::new("cmd") 818 .args(&["/C", "cls"]) 819 .output() 820 .expect("Failed to clear screen on Windows"); 821 } else { 822 // Linux/macOS or others 823 print!("\x1b[2J\x1b[H"); 824 std::io::stdout().flush().unwrap(); // Ensure the command is executed immediately 825 } 826 }