diff --git a/src/client.rs b/src/client.rs index 1fd92c1..2fd258f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ use crate::{ - peer::{DefaultPeer, Peer}, - server::{PeerAddress, Server}, - types::FileSearchResult, + peer::{ConnectionType, DefaultPeer, Peer}, + server::{PeerAddress, Server, ServerOperation}, + types::{FileSearchResult, Transfer}, utils::{md5, thread_pool::ThreadPool}, }; use std::{ @@ -22,11 +22,15 @@ pub enum ClientOperation { ConnectToPeer(Peer), SearchResult(FileSearchResult), PeerDisconnected(String), + TransferRequest(Transfer), + PierceFireWall(Peer), } struct ClientContext { peers: HashMap, sender: Option>, + server_sender: Option>, search_results: Vec, + downloads: HashMap, thread_pool: ThreadPool, } impl ClientContext { @@ -34,7 +38,9 @@ impl ClientContext { Self { peers: HashMap::new(), sender: None, + server_sender: None, search_results: Vec::new(), + downloads: HashMap::new(), thread_pool: ThreadPool::new(MAX_THREADS), } } @@ -49,6 +55,7 @@ pub struct Client { impl Client { pub fn new(address: PeerAddress, username: String, password: String) -> Self { + crate::utils::logger::init(); Self { address, username, @@ -67,17 +74,20 @@ impl Client { // self.read_form_channel(message_reader); self.server = match Server::new(self.address.clone(), sender) { Ok(server) => { - println!( + info!( "Connected to server at {}:{}", server.get_address().get_host(), server.get_address().get_port() ); + // Store the server sender in the client context + self.context.lock().unwrap().server_sender = Some(server.get_sender().clone()); + Self::listen_to_client_operations(message_reader, self.context.clone()); Some(server) } Err(e) => { - eprintln!("Error connecting to server: {}", e); + error!("Error connecting to server: {}", e); None } }; @@ -85,7 +95,7 @@ impl Client { pub fn login(&self) -> Result { // Attempt to login - println!("Logging in as {}", self.username); + info!("Logging in as {}", self.username); if let Some(server) = &self.server { let result = server.login(&self.username, &self.password); if result.unwrap() { @@ -108,18 +118,18 @@ impl Client { pub fn remove_peer(&self, username: &str) { let mut context = self.context.lock().unwrap(); if let Some(peer) = context.peers.remove(username) { - drop(peer); // Explicitly drop to trigger cleanup + drop(peer); } } pub fn search(&self, query: &str, timeout: Duration) -> Vec { - println!("Searching for {}", query); + info!("Searching for {}", query); if let Some(server) = &self.server { let hash = md5::md5(query); - let token = i32::from_str_radix(&hash[0..5], 16).unwrap(); + let token = u32::from_str_radix(&hash[0..5], 16).unwrap(); server.file_search(token, query); } else { - eprintln!("Not connected to server"); + warn!("Not connected to server"); } let start = Instant::now(); @@ -131,6 +141,52 @@ impl Client { return self.context.lock().unwrap().search_results.clone(); } + pub fn download(&self, filename: String, username: String) -> crate::types::DownloadResult { + use crate::types::{DownloadResult, DownloadStatus}; + use std::time::{Duration, Instant}; + + println!("Downloading {} from {}", filename, username); + let start_time = Instant::now(); + + // Start the download request + let context = self.context.lock().unwrap(); + let download_initiated = context + .peers + .get(&username) + .map(|p| p.transfer_request(filename.clone())) + .is_some(); + + drop(context); + + let timeout = Duration::from_secs(50); + let check_interval = Duration::from_millis(100); + + if !download_initiated { + return DownloadResult { + filename, + username, + status: DownloadStatus::Failed, + elapsed_time: start_time.elapsed(), + }; + } + + // Non-blocking wait loop + while start_time.elapsed() < timeout { + // Check download status (for now just wait, actual download logic will be implemented later) + std::thread::sleep(check_interval); + + // TODO: Check actual download progress here + // For now, we'll just wait for the timeout + } + + DownloadResult { + filename, + username, + status: DownloadStatus::TimedOut, + elapsed_time: start_time.elapsed(), + } + } + fn listen_to_client_operations( reader: Receiver, client_context: Arc>, @@ -139,6 +195,14 @@ impl Client { if let Ok(operation) = reader.recv() { match operation { ClientOperation::ConnectToPeer(peer) => { + match peer.connection_type { + ConnectionType::P => (), + ConnectionType::F => { + debug!("Peer with F {:?}", peer) + } + ConnectionType::D => (), + }; + Self::connect_to_peer(peer, client_context.clone()) } ClientOperation::SearchResult(file_search) => { @@ -154,6 +218,16 @@ impl Client { drop(peer); // Explicitly drop to trigger cleanup } } + ClientOperation::TransferRequest(transfer) => { + client_context + .lock() + .unwrap() + .downloads + .insert(transfer.token, transfer); + } + ClientOperation::PierceFireWall(peer) => { + Self::pierce_firewall(peer, client_context.clone()); + } } } }); @@ -174,14 +248,42 @@ impl Client { let mut context = client_context.lock().unwrap(); context.peers.insert(peer.username, p); } - Err(_e) => { - // eprintln!("Error connecting to peer: {:?}", e) + Err(e) => { + trace!( + "Can't connect to {} {}:{} {:?} - {}", + peer.username, + peer.host, + peer.port, + peer.connection_type, + e + ); } } }); } } else { - eprintln!("No sender found"); + error!("No sender found"); } } + fn pierce_firewall(peer: Peer, client_context: Arc>) { + debug!("Piercing firewall for peer: {:?}", peer); + + let context = client_context.lock().unwrap(); + if let Some(server_sender) = &context.server_sender { + if let Some(token) = peer.token { + match server_sender.send(ServerOperation::PierceFirewall(token)) { + Ok(_) => debug!("Sent PierceFirewall message with token: {}", token), + Err(e) => error!("Failed to send PierceFirewall message: {}", e), + } + } else { + error!("No token available for PierceFirewall"); + } + } else { + error!("No server sender available for PierceFirewall"); + } + + drop(context); + // Also try to connect to the peer directly + Self::connect_to_peer(peer, client_context); + } } diff --git a/src/dispatcher.rs b/src/dispatcher.rs index 4b21cb3..665a53e 100644 --- a/src/dispatcher.rs +++ b/src/dispatcher.rs @@ -19,7 +19,7 @@ impl MessageDispatcher { message.set_pointer(8); handler.handle(message, self.sender.clone()); } else { - println!( + warn!( "No handler found for message code: {:?}", message.get_message_code() ); diff --git a/src/main.rs b/src/main.rs index 0b3ff89..1a1aca6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ +#[macro_use] +mod utils; mod client; mod dispatcher; mod message; mod peer; mod server; mod types; -mod utils; use client::Client; use server::PeerAddress; -use std::time::Duration; +use std::time::{Duration, Instant}; // PeerAddress::new(String::from("server.slsknet.org"), 2416), fn main() { @@ -25,10 +26,15 @@ fn main() { match client.login() { Ok(_) => { let results = client.search("Fantazia", Duration::from_secs(10)); - println!("Search results: {:?}", results); + println!("Search results: {} - {:?} ", results.len(), results); + if !results.is_empty() && !results[0].files.is_empty() { + let file = results[0].files[0].clone(); + let download_result = client.download(file.name, file.username); + println!("Download result: {:?}", download_result); + } } Err(e) => { - println!("Failed to login: {}", e); + error!("Failed to login: {}", e); } } } diff --git a/src/message/message.rs b/src/message/message.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/message/mod.rs b/src/message/mod.rs index ef1a084..00829e1 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -8,6 +8,25 @@ pub use message_reader::*; use std::str; +#[derive(Debug, PartialEq)] +pub enum MessageType { + Server, + Peer, + PeerInit, + Distributed, +} + +#[derive(Debug)] +pub struct Error(String); + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for Error {} + #[derive(Debug, PartialEq, Clone)] pub struct Message { data: Vec, @@ -62,7 +81,7 @@ impl Message { } } - println!(); + trace!(""); } } // @@ -122,9 +141,9 @@ impl Message { pub fn new_with_data(data: Vec) -> Self { Self { data, pointer: 0 } } - // pub fn reset_pointer(&mut self) { - // self.pointer = 0; - // } + pub fn reset_pointer(&mut self) { + self.pointer = 0; + } pub fn set_pointer(&mut self, pointer: usize) { self.pointer = pointer; } @@ -169,27 +188,16 @@ impl Message { String::from_utf8(data.to_vec()).expect("Failed to read string") } - pub fn read_raw_hex_str(&mut self, size: usize) -> String { - let hex_str = self.data[self.pointer..self.pointer + size] - .iter() - .fold(String::new(), |mut acc, byte| { - use std::fmt::Write; - write!(acc, "{:02x}", byte).expect("Failed to write to string"); - acc - }); - self.pointer += size; - hex_str - } - pub fn read_int8(&mut self) -> i8 { - let val = self.data[self.pointer] as i8; + pub fn read_int8(&mut self) -> u8 { + let val = self.data[self.pointer]; self.pointer += 1; val } #[allow(dead_code)] - pub fn read_int64(&mut self) -> i64 { - let val = i64::from_le_bytes([ + pub fn read_int64(&mut self) -> u64 { + let val = u64::from_le_bytes([ self.data[self.pointer], self.data[self.pointer + 1], self.data[self.pointer + 2], @@ -203,12 +211,12 @@ impl Message { val } - pub fn read_int32(&mut self) -> i32 { + pub fn read_int32(&mut self) -> u32 { if self.pointer + 4 > self.data.len() { return 0; } - let val = i32::from_le_bytes([ + let val = u32::from_le_bytes([ self.data[self.pointer], self.data[self.pointer + 1], self.data[self.pointer + 2], @@ -231,7 +239,12 @@ impl Message { self } - pub fn write_int32(&mut self, value: i32) -> &mut Self { + pub fn write_int32(&mut self, value: u32) -> &mut Self { + self.data.extend_from_slice(&value.to_le_bytes()); + self + } + + pub fn write_int64(&mut self, value: u64) -> &mut Self { self.data.extend_from_slice(&value.to_le_bytes()); self } @@ -240,6 +253,11 @@ impl Message { self } + pub fn write_bool(&mut self, value: bool) -> &mut Self { + self.data.push(if value { 1 } else { 0 }); + self + } + pub fn write_raw_hex_string(&mut self, val: &str) -> &mut Self { let mut b = Vec::new(); for i in (0..val.len()).step_by(2) { @@ -251,6 +269,67 @@ impl Message { self.pointer += b.len(); self } + + pub fn get_message_name(&self, msg_type: MessageType, code: u32) -> Result<&str, Error> { + match msg_type { + MessageType::Server => match code { + 1 => Ok("Login"), + 2 => Ok("SetWaitPort"), + 3 => Ok("GetPeerAddress"), + 5 => Ok("WatchUser"), + 6 => Ok("UnwatchUser"), + 7 => Ok("GetUserStatus"), + 13 => Ok("SayChatroom"), + 14 => Ok("JoinRoom"), + 15 => Ok("LeaveRoom"), + 18 => Ok("ConnectToPeer"), + 22 => Ok("MessageUser"), + 23 => Ok("MessageAcked"), + 26 => Ok("FileSearch"), + 28 => Ok("SetStatus"), + 32 => Ok("ServerPing"), + 35 => Ok("SharedFoldersFiles"), + 36 => Ok("GetUserStats"), + 41 => Ok("Relogged"), + 42 => Ok("UserSearch"), + 64 => Ok("RoomList"), + 92 => Ok("CheckPrivileges"), + 100 => Ok("AcceptChildren"), + 102 => Ok("PossibleParents"), + 1001 => Ok("CantConnectToPeer"), + _ => Err(Error(format!("Unknown server message code: {}", code))), + }, + MessageType::PeerInit => match code { + 0 => Ok("PierceFireWall"), + 1 => Ok("PeerInit"), + _ => Err(Error(format!("Unknown peer init message code: {}", code))), + }, + MessageType::Peer => match code { + 4 => Ok("GetShareFileList"), + 5 => Ok("SharedFileListResponse"), + 9 => Ok("FileSearchResponse"), + 15 => Ok("UserInfoRequest"), + 16 => Ok("UserInfoResponse"), + 36 => Ok("FolderContentsRequest"), + 37 => Ok("FolderContentsResponse"), + 40 => Ok("TransferRequest"), + 41 => Ok("TransferResponse"), + 43 => Ok("QueueUpload"), + 44 => Ok("PlaceInQueueResponse"), + 46 => Ok("UploadFailed"), + 50 => Ok("UploadDenied"), + 51 => Ok("PlaceInQueueRequest"), + _ => Err(Error(format!("Unknown peer message code: {}", code))), + }, + MessageType::Distributed => match code { + 3 => Ok("SearchRequest"), + 4 => Ok("BranchLevel"), + 5 => Ok("BranchRoot"), + 93 => Ok("EmbeddedMessage"), + _ => Err(Error(format!("Unknown distributed message code: {}", code))), + }, + } + } // pub fn decode(&self) { // println!("{:?}", self.data); // } @@ -326,4 +405,4 @@ fn test_read_string_invalid_utf8() { ]; let mut test_data = Message::new_with_data(data); test_data.read_string(); -} \ No newline at end of file +} diff --git a/src/message/peer/file_search_response.rs b/src/message/peer/file_search_response.rs index 577249d..cb24034 100644 --- a/src/message/peer/file_search_response.rs +++ b/src/message/peer/file_search_response.rs @@ -1,55 +1,8 @@ use crate::message::{Message, MessageHandler}; use crate::peer::PeerOperation; -use crate::types::{File, FileSearchResult}; -use crate::utils::zlib::deflate; -use std::collections::HashMap; +use crate::types::FileSearchResult; use std::sync::mpsc::Sender; -fn decompress_buffer(buffer: &[u8]) -> Result, String> { - deflate(buffer) -} -impl FileSearchResult { - pub fn new_from_message(message: &mut Message) -> Self { - let pointer = message.get_pointer(); - let size = message.get_size(); - let data: Vec = message.get_slice(pointer, size); - let deflated = decompress_buffer(&data).unwrap(); - let mut message = Message::new_with_data(deflated); - - let username = message.read_string(); - let token = message.read_raw_hex_str(4); - let n_files = message.read_int32(); - let mut files: Vec = Vec::new(); - for _ in 0..n_files { - message.read_int8(); - let name = message.read_string(); - let size = message.read_int32(); - message.read_int32(); - message.read_string(); - let n_attribs = message.read_int32(); - let mut attribs: HashMap = HashMap::new(); - - for _ in 0..n_attribs { - attribs.insert(message.read_int32(), message.read_int32()); - } - files.push(File { - username: username.clone(), - name, - size, - attribs, - }); - } - let slots = message.read_int8(); - let speed = message.read_int32(); - - Self { - token, - files, - slots, - speed, - } - } -} pub struct FileSearchResponse; impl MessageHandler for FileSearchResponse { fn get_code(&self) -> u8 { @@ -85,7 +38,7 @@ fn test_new_from_message() { message.set_pointer(8); let file_search = FileSearchResult::new_from_message(&mut message); - assert_eq!(file_search.token, "6d2b9434"); + assert_eq!(file_search.token, 23); assert_eq!(file_search.files.len(), 2); let file = &file_search.files[0]; assert_eq!( diff --git a/src/message/peer/get_share_file_list.rs b/src/message/peer/get_share_file_list.rs new file mode 100644 index 0000000..60f7d11 --- /dev/null +++ b/src/message/peer/get_share_file_list.rs @@ -0,0 +1,17 @@ +use crate::{ + message::{server::MessageFactory, Message, MessageHandler}, + peer::PeerOperation, +}; +use std::sync::mpsc::Sender; + +pub struct GetShareFileList; +impl MessageHandler for GetShareFileList { + fn get_code(&self) -> u8 { + 4 + } + fn handle(&self, _message: &mut Message, sender: Sender) { + let message = MessageFactory::build_shared_folders_message(100, 800); + + sender.send(PeerOperation::SendMessage(message)).unwrap(); + } +} diff --git a/src/message/peer/mod.rs b/src/message/peer/mod.rs index 4e49155..d8fc986 100644 --- a/src/message/peer/mod.rs +++ b/src/message/peer/mod.rs @@ -1,3 +1,6 @@ pub mod file_search_response; +pub mod get_share_file_list; +pub mod transfer_request; +pub mod upload_failed; pub use self::file_search_response::*; diff --git a/src/message/peer/transfer_request.rs b/src/message/peer/transfer_request.rs new file mode 100644 index 0000000..828b6b0 --- /dev/null +++ b/src/message/peer/transfer_request.rs @@ -0,0 +1,20 @@ +use crate::{ + message::{Message, MessageHandler}, + peer::PeerOperation, + types::Transfer, +}; +use std::sync::mpsc::Sender; + +pub struct TransferRequest; +impl MessageHandler for TransferRequest { + fn get_code(&self) -> u8 { + 40 + } + fn handle(&self, message: &mut Message, sender: Sender) { + let transfer = Transfer::new_from_message(message); + + sender + .send(PeerOperation::TransferRequest(transfer)) + .unwrap(); + } +} diff --git a/src/message/peer/upload_failed.rs b/src/message/peer/upload_failed.rs new file mode 100644 index 0000000..c016013 --- /dev/null +++ b/src/message/peer/upload_failed.rs @@ -0,0 +1,17 @@ +use crate::{ + message::{Message, MessageHandler}, + peer::PeerOperation, + types::UploadFailed, +}; +use std::sync::mpsc::Sender; + +pub struct UploadFailedHandler; +impl MessageHandler for UploadFailedHandler { + fn get_code(&self) -> u8 { + 46 + } + fn handle(&self, message: &mut Message, sender: Sender) { + let upload_failed = UploadFailed::new_from_message(message); + info!("Upload failed for ${}", upload_failed.filename); + } +} diff --git a/src/message/server/connect_to_peer.rs b/src/message/server/connect_to_peer.rs index be3acb4..9233ae8 100644 --- a/src/message/server/connect_to_peer.rs +++ b/src/message/server/connect_to_peer.rs @@ -1,5 +1,5 @@ use crate::message::{Message, MessageHandler}; -use crate::peer::Peer; +use crate::peer::{ConnectionType, Peer}; use crate::server::ServerOperation; use std::sync::mpsc::Sender; pub struct ConnectToPeerHandler; @@ -9,6 +9,12 @@ impl MessageHandler for ConnectToPeerHandler { } fn handle(&self, message: &mut Message, sender: Sender) { let peer = Peer::new_from_message(message); + match peer.connection_type { + ConnectionType::P => (), + ConnectionType::F => trace!("[server] ConnectToPeer {:?}", peer), + ConnectionType::D => (), + } + sender.send(ServerOperation::ConnectToPeer(peer)).unwrap(); } } diff --git a/src/message/server/excluded_search_phrases.rs b/src/message/server/excluded_search_phrases.rs index f6a14b6..fdd9f8f 100644 --- a/src/message/server/excluded_search_phrases.rs +++ b/src/message/server/excluded_search_phrases.rs @@ -20,6 +20,6 @@ impl MessageHandler for ExcludedSearchPhrasesHandler { let phrase = message.read_string(); exluded_phrases.push(phrase); } - println!("Excluded search phrases: {:?}", exluded_phrases); + debug!("Excluded search phrases: {:?}", exluded_phrases); } } diff --git a/src/message/server/file_search.rs b/src/message/server/file_search.rs index 8559eaf..895f5a5 100644 --- a/src/message/server/file_search.rs +++ b/src/message/server/file_search.rs @@ -12,11 +12,11 @@ impl MessageHandler for FileSearchHandler { 26 } fn handle(&self, message: &mut Message, _sender: Sender) { - println!("Handling file search message"); + debug!("Handling file search message"); let username = message.read_string(); let token = message.read_int32(); let query = message.read_string(); - println!( + info!( "Message search username:{}, token: {}, query: {}", username, token, query ); diff --git a/src/message/server/login.rs b/src/message/server/login.rs index 3ef6d45..acaecca 100644 --- a/src/message/server/login.rs +++ b/src/message/server/login.rs @@ -20,9 +20,9 @@ impl MessageHandler for LoginHandler { return sender.send(ServerOperation::LoginStatus(false)).unwrap(); } - println!("Login successful"); + info!("Login successful"); let greeting = message.read_string(); - println!("Server greeting: {:?}", greeting); + debug!("Server greeting: {:?}", greeting); sender.send(ServerOperation::LoginStatus(true)).unwrap(); } diff --git a/src/message/server/message_factory.rs b/src/message/server/message_factory.rs index a128674..5f78e83 100644 --- a/src/message/server/message_factory.rs +++ b/src/message/server/message_factory.rs @@ -1,4 +1,4 @@ -use crate::{message::Message, utils::md5}; +use crate::{message::Message, types::Transfer, utils::md5}; pub struct MessageFactory; impl MessageFactory { @@ -18,7 +18,7 @@ impl MessageFactory { // ] // .to_vec(), // ) - // .clone() + // .clone()fac let hash = md5([username, password].join("").as_str()); let mut message = Message::new(); @@ -34,14 +34,14 @@ impl MessageFactory { message } - pub fn build_shared_folders_message(folder_count: i32, file_count: i32) -> Message { + pub fn build_shared_folders_message(folder_count: u32, file_count: u32) -> Message { Message::new() .write_int32(35) .write_int32(folder_count) .write_int32(file_count) .clone() } - pub fn build_file_search_message(token: i32, query: &str) -> Message { + pub fn build_file_search_message(token: u32, query: &str) -> Message { // Message::new_with_data( // [ // 26, 0, 0, 0, 219, 178, 47, 28, 11, 0, 0, 0, 116, 104, 101, 32, 119, 101, 101, 107, 101, @@ -56,7 +56,7 @@ impl MessageFactory { .write_string(query) .clone() } - pub fn build_set_status_message(status_code: i32) -> Message { + pub fn build_set_status_message(status_code: u32) -> Message { Message::new() .write_int32(28) .write_int32(status_code) @@ -68,17 +68,38 @@ impl MessageFactory { pub fn build_set_wait_port_message() -> Message { Message::new().write_int32(2).write_int32(2234).clone() } - pub fn build_watch_user(token: &str) -> Message { + pub fn build_watch_user(token: u32) -> Message { Message::new() .write_raw_bytes([5, 0, 0, 0, 0].to_vec()) - .write_raw_hex_string(token) + .write_int32(token) + .clone() + } + + pub fn build_queue_upload_message(filename: &str) -> Message { + Message::new() + .write_int32(43) + .write_string(filename) + .clone() + } + + pub fn build_transfer_response_message(transfer: Transfer) -> Message { + Message::new() + .write_int32(41) + .write_int32(transfer.token) + .write_bool(true) + .clone() + } + pub fn build_pierce_firewall_message(token: u32) -> Message { + Message::new() + .write_int32(0) // PierceFirewall message code + .write_int32(token) .clone() } } #[test] fn test_build_watch_user() { - let token = "5b581500"; + let token: u32 = 223; let message = MessageFactory::build_watch_user(token); let expect: Vec = [5, 0, 0, 0, 0, 91, 88, 21, 0].to_vec(); diff --git a/src/message/server/message_user.rs b/src/message/server/message_user.rs index 4523cdf..a91af5e 100644 --- a/src/message/server/message_user.rs +++ b/src/message/server/message_user.rs @@ -25,7 +25,7 @@ impl MessageHandler for MessageUser { new_message, ); - println!("User message received:",); + info!("User message received:"); user_message.print() } } diff --git a/src/message/server/parent_min_speed.rs b/src/message/server/parent_min_speed.rs index 7f7bf00..e013ce7 100644 --- a/src/message/server/parent_min_speed.rs +++ b/src/message/server/parent_min_speed.rs @@ -15,6 +15,6 @@ impl MessageHandler for ParentMinSpeedHandler { fn handle(&self, message: &mut Message, sender: Sender) { let _ = sender; let number = message.read_int32(); - println!("Parent min speed: {}", number); + debug!("Parent min speed: {}", number); } } diff --git a/src/message/server/parent_speed_ratio.rs b/src/message/server/parent_speed_ratio.rs index 08d4277..b5e3214 100644 --- a/src/message/server/parent_speed_ratio.rs +++ b/src/message/server/parent_speed_ratio.rs @@ -15,6 +15,6 @@ impl MessageHandler for ParentSpeedRatioHandler { fn handle(&self, message: &mut Message, _sender: Sender) { let number = message.read_int32(); - println!("Parent min speed: {}", number); + debug!("Parent speed ratio: {}", number); } } diff --git a/src/message/server/privileged_users.rs b/src/message/server/privileged_users.rs index 2eac643..8e82020 100644 --- a/src/message/server/privileged_users.rs +++ b/src/message/server/privileged_users.rs @@ -14,6 +14,6 @@ impl MessageHandler for PrivilegedUsersHandler { fn handle(&self, message: &mut Message, _sender: Sender) { let number = message.read_int32(); - println!("Number of privilaged users: {}", number); + debug!("Number of privileged users: {}", number); } } diff --git a/src/message/server/wish_list_interval.rs b/src/message/server/wish_list_interval.rs index 459a779..99951d7 100644 --- a/src/message/server/wish_list_interval.rs +++ b/src/message/server/wish_list_interval.rs @@ -16,6 +16,6 @@ impl MessageHandler for WishListIntervalHandler { fn handle(&self, message: &mut Message, _sender: Sender) { let number = message.read_int32(); - println!("Wishlist search interval: {} in seconds", number); + debug!("Wishlist search interval: {} in seconds", number); } } diff --git a/src/peer/default_peer.rs b/src/peer/default_peer.rs index a8b08bd..cd5d658 100644 --- a/src/peer/default_peer.rs +++ b/src/peer/default_peer.rs @@ -1,8 +1,11 @@ use crate::dispatcher::MessageDispatcher; +use crate::message::peer::get_share_file_list::GetShareFileList; +use crate::message::peer::transfer_request::TransferRequest; +use crate::message::peer::upload_failed::UploadFailedHandler; use crate::message::peer::FileSearchResponse; use crate::message::server::MessageFactory; -use crate::message::{Handlers, Message, MessageReader}; -use crate::types::FileSearchResult; +use crate::message::{Handlers, Message, MessageReader, MessageType}; +use crate::types::{FileSearchResult, Transfer}; use std::sync::mpsc; use std::sync::mpsc::{Receiver, Sender}; @@ -20,7 +23,6 @@ pub struct DefaultPeer { peer: Peer, peer_channel: Option>, client_channel: Sender, - // Store thread handles for proper lifecycle management read_thread: Option>, write_thread: Option>, } @@ -29,6 +31,7 @@ pub struct DefaultPeer { pub enum PeerOperation { SendMessage(Message), FileSearchResult(FileSearchResult), + TransferRequest(Transfer), } impl DefaultPeer { pub fn new(peer: Peer, client_channel: Sender) -> Self { @@ -46,13 +49,14 @@ impl DefaultPeer { .next() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid address"))?; - let mut stream = TcpStream::connect_timeout(&socket_address, Duration::from_secs(10))?; + let mut stream = TcpStream::connect_timeout(&socket_address, Duration::from_secs(20))?; stream.set_read_timeout(Some(Duration::from_secs(5)))?; stream.set_write_timeout(Some(Duration::from_secs(5)))?; - if let Some(token) = self.peer.token.clone() { + + if let Some(token) = self.peer.token { stream - .write_all(&MessageFactory::build_watch_user(token.as_str()).get_data()) + .write_all(&MessageFactory::build_watch_user(token).get_data()) .unwrap(); } self.start_read_write_loops(stream)?; @@ -64,6 +68,9 @@ impl DefaultPeer { let (peer_sender, peer_reader): (Sender, Receiver) = mpsc::channel(); + // Set the peer_channel so transfer_request can send messages + self.peer_channel = Some(peer_sender.clone()); + let mut read_stream = stream.try_clone()?; let mut write_stream = stream; // Use the original stream for writing @@ -74,6 +81,9 @@ impl DefaultPeer { self.read_thread = Some(thread::spawn(move || { let mut handlers = Handlers::new(); handlers.register_handler(FileSearchResponse); + handlers.register_handler(TransferRequest); + handlers.register_handler(GetShareFileList); + handlers.register_handler(UploadFailedHandler); let dispatcher = MessageDispatcher::new(peer_sender, handlers); @@ -83,27 +93,41 @@ impl DefaultPeer { Ok(_) => {} Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, Err(ref e) if e.kind() == io::ErrorKind::TimedOut => { - println!( + debug!( "Read operation timed out in default peer {:}:{:}", peer.host, peer.port ); continue; } Err(e) => { - eprintln!("Error reading from peer: {}. Terminating read loop.", e); - let _ = client_channel_for_read.send(ClientOperation::PeerDisconnected(peer.username.clone())); + error!("Error reading from peer: {}. Terminating read loop.", e); + let _ = client_channel_for_read + .send(ClientOperation::PeerDisconnected(peer.username.clone())); break; } } match buffered_reader.extract_message() { - Ok(Some(mut message)) => dispatcher.dispatch(&mut message), + Ok(Some(mut message)) => { + trace!( + "[default_peer:{:?}] ← {:?}", + peer.username, + message + .get_message_name( + MessageType::Peer, + message.get_message_code() as u32 + ) + .map_err(|e| e.to_string()) + ); + dispatcher.dispatch(&mut message) + } Err(e) => { - println!( + warn!( "Error extracting message in default peer: {}. Terminating read loop.", e ); - let _ = client_channel_for_read.send(ClientOperation::PeerDisconnected(peer.username.clone())); + let _ = client_channel_for_read + .send(ClientOperation::PeerDisconnected(peer.username.clone())); break; } Ok(None) => continue, @@ -112,29 +136,62 @@ impl DefaultPeer { })); let client_channel = self.client_channel.clone(); + let peer_channel = self.peer_channel.clone(); let peer_username = self.peer.username.clone(); + self.write_thread = Some(thread::spawn(move || { loop { match peer_reader.recv() { - Ok(operation) => { - match operation { - PeerOperation::SendMessage(message) => { - if let Err(e) = write_stream.write_all(&message.get_buffer()) { - eprintln!("Error writing message to stream: {}. Terminating write loop.", e); - let _ = client_channel.send(ClientOperation::PeerDisconnected(peer_username.clone())); - break; - } + Ok(operation) => match operation { + PeerOperation::SendMessage(message) => { + trace!( + "[default_peer:{:?}] ➡ {:?} - {:?}", + peer_username, + message + .get_message_name( + MessageType::Peer, + u32::from_le_bytes( + message.get_slice(0, 4).try_into().unwrap() + ) + ) + .map_err(|e| e.to_string()), + message + ); + + if let Err(e) = write_stream.write_all(&message.get_buffer()) { + error!("Error writing message to stream: {} - {}. Terminating write loop.", peer_username, e); + let _ = client_channel + .send(ClientOperation::PeerDisconnected(peer_username.clone())); + break; } - PeerOperation::FileSearchResult(file_search) => { - client_channel - .send(ClientOperation::SearchResult(file_search)) + } + PeerOperation::FileSearchResult(file_search) => { + client_channel + .send(ClientOperation::SearchResult(file_search)) + .unwrap(); + } + PeerOperation::TransferRequest(transfer) => { + debug!( + "[default_peer:{}] transfer request: {:?}", + peer_username, transfer + ); + client_channel + .send(ClientOperation::TransferRequest(transfer.clone())) + .unwrap(); + + let transfer_response = + MessageFactory::build_transfer_response_message(transfer.clone()); + + if let Some(sender) = peer_channel.clone() { + sender + .send(PeerOperation::SendMessage(transfer_response)) .unwrap(); } } - } + }, Err(_) => { // The sender has been dropped, the peer is shutting down. - println!("Peer channel closed. Terminating write loop."); + debug!("Peer channel closed. Terminating write loop."); break; } } @@ -143,31 +200,33 @@ impl DefaultPeer { Ok(()) } + + pub fn transfer_request(&self, filename: String) -> Result<(), io::Error> { + let message = MessageFactory::build_queue_upload_message(&filename); + if let Some(sender) = &self.peer_channel { + sender.send(PeerOperation::SendMessage(message)).unwrap(); + } + Ok(()) + } } impl Drop for DefaultPeer { fn drop(&mut self) { - // Signal threads to shut down (e.g., by dropping the sender half of the channel) - // For now, we'll just drop the peer_channel, which will cause the writer thread - // to exit its loop. A more robust solution might involve a shared AtomicBool - // or a dedicated shutdown channel. self.peer_channel = None; - // Join the read thread if let Some(handle) = self.read_thread.take() { - println!("Joining read thread..."); + debug!("Joining read thread..."); match handle.join() { - Ok(_) => println!("Read thread joined successfully."), - Err(e) => eprintln!("Read thread panicked: {:?}", e), + Ok(_) => debug!("Read thread joined successfully."), + Err(e) => error!("Read thread panicked: {:?}", e), } } - // Join the write thread if let Some(handle) = self.write_thread.take() { - println!("Joining write thread..."); + debug!("Joining write thread..."); match handle.join() { - Ok(_) => println!("Write thread joined successfully."), - Err(e) => eprintln!("Write thread panicked: {:?}", e), + Ok(_) => debug!("Write thread joined successfully."), + Err(e) => error!("Write thread panicked: {:?}", e), } } } diff --git a/src/peer/listen.rs b/src/peer/listen.rs index f548118..044dc3e 100644 --- a/src/peer/listen.rs +++ b/src/peer/listen.rs @@ -8,7 +8,7 @@ pub struct Listen {} impl Listen { pub fn start(port: u32, _server_channel: Sender) { - println!("starting listener on port {port}"); + info!("starting listener on port {port}"); let listener = TcpListener::bind(format!("0.0.0.0:{port}")).unwrap(); for stream in listener.incoming() { let mut read_stream = stream.unwrap(); @@ -20,19 +20,19 @@ impl Listen { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, Err(ref e) if e.kind() == io::ErrorKind::TimedOut => { - println!("Read operation timed out"); + debug!("Read operation timed out"); continue; } Err(e) => { - eprintln!("Error reading from server: {}", e); + error!("Error reading from server: {}", e); break; } } match buffered_reader.extract_message() { Ok(Some(mut message)) => { - println!("Received message: {:?}", message.get_message_code()); - println!("{:?}", message.get_data()); + debug!("Received message: {:?}", message.get_message_code()); + trace!("{:?}", message.get_data()); message.print_hex(); if message.get_message_code() == 1 { @@ -45,8 +45,8 @@ impl Listen { // // message.read_string().parse().unwrap(); // // let token = message.read_int32(); // - println!("type: {:?}", typex); - println!("user: {:?}", size); + debug!("type: {:?}", typex); + debug!("user: {:?}", size); // server_channel // .send(ServerOperation::ConnectToPeer(peer)) @@ -54,13 +54,13 @@ impl Listen { } } Err(e) => { - println!("Error extracting message: {}", e) + warn!("Error extracting message: {}", e) } Ok(None) => continue, } } - println!("Connection established!"); + info!("Connection established!"); } } } diff --git a/src/peer/mod.rs b/src/peer/mod.rs index 8b43715..f92c054 100644 --- a/src/peer/mod.rs +++ b/src/peer/mod.rs @@ -46,11 +46,11 @@ pub struct Peer { pub username: String, pub connection_type: ConnectionType, pub host: String, - pub port: i32, - pub token: Option, - pub privileged: i8, - pub unknown: i8, - pub obfuscated_port: i8, + pub port: u32, + pub token: Option, + pub privileged: u8, + pub unknown: u8, + pub obfuscated_port: u8, } impl Peer { #[allow(clippy::too_many_arguments, dead_code)] @@ -58,11 +58,11 @@ impl Peer { username: String, connection_type: ConnectionType, host: String, - port: i32, - token: String, - privileged: i8, - unknown: i8, - obfuscated_port: i8, + port: u32, + token: u32, + privileged: u8, + unknown: u8, + obfuscated_port: u8, ) -> Self { Self { username, @@ -78,7 +78,7 @@ impl Peer { #[allow(dead_code)] pub fn new_from_message(message: &mut Message) -> Self { let username = message.read_string(); - let connection_type: ConnectionType = message.read_string().parse().unwrap(); + let connection_type = message.read_string().parse().unwrap(); let mut ip: Vec = vec![]; for _ in 0..4 { @@ -94,7 +94,7 @@ impl Peer { let (port, token, privileged, unknown, obfuscated_port) = ( message.read_int32(), - message.read_raw_hex_str(4), + message.read_int32(), message.read_int8(), message.read_int8(), message.read_int8(), @@ -155,4 +155,3 @@ fn test_new_from_message2() { assert_eq!(peer.unknown, 1); assert_eq!(peer.obfuscated_port, 0); } - diff --git a/src/peer/peer.rs b/src/peer/peer.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/server.rs b/src/server.rs index 1eddc4c..592eced 100644 --- a/src/server.rs +++ b/src/server.rs @@ -15,6 +15,7 @@ use crate::message::Handlers; use crate::message::Message; use crate::message::MessageReader; use crate::peer::listen::Listen; +use crate::peer::ConnectionType; use crate::peer::Peer; use std::io::{self, Write}; @@ -71,16 +72,16 @@ impl Context { } #[derive(Debug, Clone)] pub struct UserMessage { - id: i32, - timestamp: i32, + id: u32, + timestamp: u32, username: String, message: String, new_message: bool, } impl UserMessage { pub fn new( - id: i32, - timestamp: i32, + id: u32, + timestamp: u32, username: String, message: String, new_message: bool, @@ -94,7 +95,7 @@ impl UserMessage { } } pub fn print(&self) { - println!( + debug!( "Timestamp: {}. User: {}, Id: #{}, New message: {} Message: {}", self.timestamp, self.username, self.id, self.new_message, self.message ); @@ -121,19 +122,19 @@ impl Rooms { #[allow(dead_code)] pub fn print(&self) { - println!("Public rooms ({}):", self.public_rooms.len()); + info!("Public rooms ({}):", self.public_rooms.len()); for room in &self.public_rooms { room.print(); } - println!("Owned private rooms ({}):", self.owned_private_rooms.len()); + info!("Owned private rooms ({}):", self.owned_private_rooms.len()); for room in &self.owned_private_rooms { room.print(); } - println!("Private rooms ({}):", self.private_rooms.len()); + info!("Private rooms ({}):", self.private_rooms.len()); for room in &self.private_rooms { room.print(); } - println!( + info!( "Operated private rooms ({}):", self.operated_private_rooms.len() ); @@ -160,7 +161,7 @@ impl Room { self.number_of_users = number_of_users; } pub fn print(&self) { - println!( + debug!( "Room: {}, Number of users: {}", self.name, self.number_of_users ); @@ -172,6 +173,7 @@ pub enum ServerOperation { SendMessage(Message), #[allow(dead_code)] ConnectToPeer(Peer), + PierceFirewall(u32), } #[derive(Debug)] @@ -205,6 +207,10 @@ impl Server { &self.address } + pub fn get_sender(&self) -> &Sender { + &self.sender + } + /// Start reading and writing loops in separate threads fn start_read_write_loops( &mut self, @@ -216,7 +222,7 @@ impl Server { .next() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid address"))?; - println!( + info!( "Connecting to server at {}:{}", self.address.host, self.address.port ); @@ -261,24 +267,31 @@ impl Server { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, Err(ref e) if e.kind() == io::ErrorKind::TimedOut => { - println!("Read operation timed out"); + debug!("Read operation timed out"); continue; } Err(e) => { - eprintln!("Error reading from server: {}", e); + error!("Error reading from server: {}", e); break; } } match buffered_reader.extract_message() { Ok(Some(mut message)) => { - // println!("Received message: {:?}", message.get_message_code_u32()); - // message.print_hex(); + // trace!( + // "[server] ← {:?}", + // message + // .get_message_name( + // MessageType::Server, + // message.get_message_code() as u32 + // ) + // .map_err(|e| e.to_string()) + // ); dispatcher.dispatch(&mut message) } Err(e) => { - println!("Error extracting message: {}", e) + warn!("Error extracting message: {}", e) } Ok(None) => continue, } @@ -295,24 +308,56 @@ impl Server { loop { if let Ok(operation) = server_channel.recv() { match operation { - ServerOperation::ConnectToPeer(peer) => { - match client_channel.send(ClientOperation::ConnectToPeer(peer)) { - Ok(_) => {} - Err(e) => { - println!("Error sending message: {}", e); + ServerOperation::ConnectToPeer(peer) => match peer.connection_type { + ConnectionType::P => { + match client_channel.send(ClientOperation::ConnectToPeer(peer)) { + Ok(_) => {} + Err(_e) => {} } } - } + ConnectionType::F => { + match client_channel.send(ClientOperation::PierceFireWall(peer)) { + Ok(_) => { + debug!("Sent PierceFireWall operation for F-type connection"); + } + Err(_e) => { + error!("Failed to send PierceFireWall operation"); + } + } + } + ConnectionType::D => {} + }, ServerOperation::LoginStatus(message) => { context.lock().unwrap().logged_in = Some(message); } + ServerOperation::PierceFirewall(token) => { + let pierce_message = MessageFactory::build_pierce_firewall_message(token); + match write_stream.write_all(&pierce_message.get_buffer()) { + Ok(_) => { + debug!("Sent PierceFirewall message with token: {}", token); + } + Err(e) => { + error!("Error writing PierceFirewall message: {}", e); + break; + } + } + } ServerOperation::SendMessage(message) => { - // message.decode(); - // message.print_hex2(); + // trace!( + // "[server] ➡ {:?}", + // message + // .get_message_name( + // MessageType::Server, + // u32::from_le_bytes( + // message.get_slice(0, 4).try_into().unwrap() + // ) + // ) + // .map_err(|e| e.to_string()), + // ); match write_stream.write_all(&message.get_buffer()) { Ok(_) => {} Err(e) => { - eprintln!("Error writing message to stream : {}", e); + error!("Error writing message to stream : {}", e); break; } } @@ -328,7 +373,7 @@ impl Server { fn queue_message(&self, message: Message) { match self.sender.send(ServerOperation::SendMessage(message)) { Ok(_) => {} - Err(e) => println!("Failed to send: {}", e), + Err(e) => error!("Failed to send: {}", e), } } fn start_listener(&self, server_channel: Sender) { @@ -347,7 +392,7 @@ impl Server { // wait till server says your logged in or not loop { if start.elapsed() > timeout { - println!("Timeout waiting for login response"); + warn!("Timeout waiting for login response"); return Err(std::io::Error::new( std::io::ErrorKind::TimedOut, "Timeout waiting for login response", @@ -364,9 +409,9 @@ impl Server { } if logged_in.unwrap() { - println!("Logged in as {}", username); + info!("Logged in as {}", username); self.queue_message(MessageFactory::build_set_wait_port_message()); - self.queue_message(MessageFactory::build_shared_folders_message(1, 1)); + self.queue_message(MessageFactory::build_shared_folders_message(1, 499)); self.queue_message(MessageFactory::build_no_parent_message()); self.queue_message(MessageFactory::build_set_status_message(2)); } @@ -374,7 +419,14 @@ impl Server { Ok(logged_in.unwrap()) } - pub fn file_search(&self, token: i32, query: &str) { + pub fn file_search(&self, token: u32, query: &str) { self.queue_message(MessageFactory::build_file_search_message(token, query)); } + + pub fn pierce_firewall(&self, token: u32) { + match self.sender.send(ServerOperation::PierceFirewall(token)) { + Ok(_) => {} + Err(e) => error!("Failed to send PierceFirewall: {}", e), + } + } } diff --git a/src/types.rs b/src/types.rs index 888d7f8..91a1ee4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,20 +1,113 @@ use std::collections::HashMap; +use crate::{message::Message, utils::zlib::deflate}; + #[derive(Debug, Clone)] #[allow(dead_code)] pub struct File { pub username: String, pub name: String, - pub size: i32, - pub attribs: HashMap, + pub size: u32, + pub attribs: HashMap, } +pub struct UploadFailed { + pub filename: String, +} +impl UploadFailed { + pub fn new_from_message(message: &mut Message) -> Self { + let filename = message.read_string(); + Self { filename } + } +} #[derive(Debug, Clone)] #[allow(dead_code)] pub struct FileSearchResult { - pub token: String, + pub token: u32, pub files: Vec, - pub slots: i8, - pub speed: i32, + pub slots: u8, + pub speed: u32, } +impl FileSearchResult { + pub fn new_from_message(message: &mut Message) -> Self { + let pointer = message.get_pointer(); + let size = message.get_size(); + let data: Vec = message.get_slice(pointer, size); + let deflated = deflate(&data).unwrap(); + let mut message = Message::new_with_data(deflated); + + let username = message.read_string(); + let token = message.read_int32(); + let n_files = message.read_int32(); + let mut files: Vec = Vec::new(); + for _ in 0..n_files { + message.read_int8(); + let name = message.read_string(); + let size = message.read_int32(); + message.read_int32(); + message.read_string(); + let n_attribs = message.read_int32(); + let mut attribs: HashMap = HashMap::new(); + + for _ in 0..n_attribs { + attribs.insert(message.read_int32(), message.read_int32()); + } + files.push(File { + username: username.clone(), + name, + size, + attribs, + }); + } + let slots = message.read_int8(); + let speed = message.read_int32(); + + Self { + token, + files, + slots, + speed, + } + } +} + +#[derive(Debug, Clone)] +pub struct Transfer { + pub direction: u32, + pub token: u32, + pub filename: String, + pub size: u64, +} + +#[derive(Debug, Clone)] +pub struct DownloadResult { + pub filename: String, + pub username: String, + pub status: DownloadStatus, + pub elapsed_time: std::time::Duration, +} + +#[derive(Debug, Clone)] +pub enum DownloadStatus { + Pending, + InProgress, + Completed, + Failed, + TimedOut, +} +impl Transfer { + pub fn new_from_message(message: &mut Message) -> Self { + let direction = message.read_int32(); + let token = message.read_int32(); + let filename = message.read_string(); + let size = message.read_int64(); + + Self { + direction, + token, + filename, + size, + } + } +} diff --git a/src/utils/logger.rs b/src/utils/logger.rs new file mode 100644 index 0000000..d5e9576 --- /dev/null +++ b/src/utils/logger.rs @@ -0,0 +1,133 @@ +use std::env; +use std::sync::Once; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum LogLevel { + Error = 0, + Warn = 1, + Info = 2, + Debug = 3, + Trace = 4, +} + +static INIT: Once = Once::new(); +static mut LOG_LEVEL: LogLevel = LogLevel::Debug; // Default to VERBOSE (mapped to DEBUG) + +pub fn init() { + INIT.call_once(|| { + let level = env::var("LOG_LEVEL") + .or_else(|_| env::var("RUST_LOG")) + .unwrap_or_else(|_| "VERBOSE".to_string()) + .to_uppercase(); + + unsafe { + LOG_LEVEL = match level.as_str() { + "ERROR" => LogLevel::Error, + "WARN" => LogLevel::Warn, + "INFO" => LogLevel::Info, + "DEBUG" => LogLevel::Debug, + "TRACE" => LogLevel::Trace, + "VERBOSE" => LogLevel::Debug, // Map VERBOSE to DEBUG + _ => LogLevel::Debug, // Default to VERBOSE + }; + } + }); +} + +pub fn log(level: LogLevel, message: &str) { + unsafe { + if level <= LOG_LEVEL { + let level_str = match level { + LogLevel::Error => "\x1b[31mERROR\x1b[0m", // Red + LogLevel::Warn => "\x1b[33mWARN\x1b[0m", // Yellow + LogLevel::Info => "\x1b[32mINFO\x1b[0m", // Green + LogLevel::Debug => "\x1b[34mDEBUG\x1b[0m", // Blue + LogLevel::Trace => "\x1b[35mTRACE\x1b[0m", // Magenta + }; + + let now = std::time::SystemTime::now(); + let datetime = now.duration_since(std::time::UNIX_EPOCH).unwrap(); + let secs = datetime.as_secs(); + let subsec_millis = datetime.subsec_millis(); + + // Format as YYYY-MM-DD HH:MM:SS.mmm + let days_since_epoch = secs / 86400; + let days_since_1970 = days_since_epoch as i32; + + // Calculate year (approximately) + let mut year = 1970; + let mut remaining_days = days_since_1970; + + while remaining_days >= 365 { + let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + let days_in_year = if is_leap { 366 } else { 365 }; + if remaining_days >= days_in_year { + remaining_days -= days_in_year; + year += 1; + } else { + break; + } + } + + // Calculate month and day (simplified) + let month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + let mut month = 1; + let mut day = remaining_days + 1; + + for &days_in_month in &month_days { + let actual_days = if month == 2 && is_leap { 29 } else { days_in_month }; + if day > actual_days { + day -= actual_days; + month += 1; + } else { + break; + } + } + + // Calculate time of day + let seconds_in_day = secs % 86400; + let hours = seconds_in_day / 3600; + let minutes = (seconds_in_day % 3600) / 60; + let seconds = seconds_in_day % 60; + + eprintln!("[{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}] [{}] {}", + year, month, day, hours, minutes, seconds, subsec_millis, level_str, message); + } + } +} + +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => { + $crate::utils::logger::log($crate::utils::logger::LogLevel::Error, &format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => { + $crate::utils::logger::log($crate::utils::logger::LogLevel::Warn, &format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => { + $crate::utils::logger::log($crate::utils::logger::LogLevel::Info, &format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + $crate::utils::logger::log($crate::utils::logger::LogLevel::Debug, &format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! trace { + ($($arg:tt)*) => { + $crate::utils::logger::log($crate::utils::logger::LogLevel::Trace, &format!($($arg)*)) + }; +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a9f60ee..2db89bc 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,5 @@ +#[macro_use] +pub mod logger; pub mod md5; pub mod thread_pool; pub mod zlib;