use std::{str::FromStr as _, time::Duration}; use url::Url; use utils::{binary::checksum, serialize::Serialize as _}; use crate::{API_DEFAULT_PORT, message::Message}; #[derive(Debug)] pub enum UrlError { ParseError(url::ParseError), DomainBasePortError, } pub fn fix_url(url: &str) -> Result { let mut url: String = url.to_string(); if !url.starts_with("http://") && !url.starts_with("https://") { url = format!("http://{}", url); } let mut url = Url::from_str(&url).map_err(|x| UrlError::ParseError(x))?; if url.port().is_none() { url.set_port(Some(API_DEFAULT_PORT)).map_err(|_| UrlError::DomainBasePortError)?; } let mut url = url.to_string(); if !url.ends_with('/') { url.push('/'); } Ok(url) } #[derive(Debug)] pub enum ConnectionError { UrlError(UrlError), ReqwestError(reqwest::Error), StatusCodeError(u16), } pub fn tx_message(host: &str, password: &str, timeout_sec: u64, mut message: Message) -> Result<(), ConnectionError> { message.sanitize(); let url = fix_url(host) .map_err(|x| ConnectionError::UrlError(x))?; let mut bytes = message.serialize_checked(); utils::aes::encrypt_cbc(&mut bytes, &password); bytes.push(checksum(&bytes)); let str = utils::binary::bin2hex(bytes); let resp = reqwest::blocking::Client::new() .post(url) .body(str) .timeout(Duration::from_secs(timeout_sec)) .send() .map_err(|e| ConnectionError::ReqwestError(e))?; if resp.status().is_success() { Ok(()) } else { Err(ConnectionError::StatusCodeError(resp.status().as_u16())) } } pub fn rx_messages(host: &str, password: &str, timeout_sec: u64) -> Result, ConnectionError> { let url = fix_url(host) .map_err(|x| ConnectionError::UrlError(x))?; let resp = reqwest::blocking::Client::new() .get(url) .timeout(Duration::from_secs(timeout_sec)) .send() .map_err(|x| ConnectionError::ReqwestError(x))?; if resp.status() != reqwest::StatusCode::OK { return Err(ConnectionError::StatusCodeError(resp.status().as_u16())); } let bytes = resp.bytes() .map_err(|e| ConnectionError::ReqwestError(e))?; let contents = String::from_utf8_lossy(&bytes); let mut messages = vec![]; for message_ser_hex in contents.split(",") { let Some(mut message_ser_bin) = utils::binary::hex2bin(message_ser_hex) else { continue; }; if message_ser_bin.len() < 2 { continue; } // Checksum before decryption if checksum(&message_ser_bin[..message_ser_bin.len()-1]) != message_ser_bin[message_ser_bin.len()-1] { continue; } // Remove checksum message_ser_bin.pop(); if utils::aes::decrypt_cbc(&mut message_ser_bin, &password).is_err() { continue; } let Ok(mut message) = Message::deserialize_checked(message_ser_bin) else { continue; }; if message.is_valid() { message.sanitize(); messages.push(message); } } Ok(messages) } #[test] fn test_tx_rx() { let host = "http://localhost:13337/"; let password = "null"; let message = Message::new("Alice", "Hello", "root"); tx_message(host, password, 5, message.clone()).expect("Failed to send message"); let messages = rx_messages(host, password, 5).expect("Failed to receive messages"); assert_eq!(messages.len(), 1); assert_eq!(messages[0].sender, message.sender); assert_eq!(messages[0].content, message.content); }