diff options
Diffstat (limited to 'crates/utils')
43 files changed, 0 insertions, 3691 deletions
diff --git a/crates/utils/cfg_file/Cargo.toml b/crates/utils/cfg_file/Cargo.toml deleted file mode 100644 index 0685329..0000000 --- a/crates/utils/cfg_file/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "cfg_file" -edition = "2024" -version.workspace = true - -[features] -default = ["derive"] -derive = [] - -[dependencies] -cfg_file_derive = { path = "cfg_file_derive" } - -# Async -tokio = { version = "1.48.0", features = ["full"] } -async-trait = "0.1.89" - -# Serialization -serde = { version = "1.0.228", features = ["derive"] } -serde_yaml = "0.9.34" -serde_json = "1.0.145" -ron = "0.11.0" -toml = "0.9.8" -bincode2 = "2.0.1" diff --git a/crates/utils/cfg_file/cfg_file_derive/Cargo.toml b/crates/utils/cfg_file/cfg_file_derive/Cargo.toml deleted file mode 100644 index ce5e77f..0000000 --- a/crates/utils/cfg_file/cfg_file_derive/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "cfg_file_derive" -edition = "2024" -version.workspace = true - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "2.0", features = ["full", "extra-traits"] } -quote = "1.0" diff --git a/crates/utils/cfg_file/cfg_file_derive/src/lib.rs b/crates/utils/cfg_file/cfg_file_derive/src/lib.rs deleted file mode 100644 index e916311..0000000 --- a/crates/utils/cfg_file/cfg_file_derive/src/lib.rs +++ /dev/null @@ -1,130 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote; -use syn::parse::ParseStream; -use syn::{Attribute, DeriveInput, Expr, parse_macro_input}; -/// # Macro - ConfigFile -/// -/// ## Usage -/// -/// Use `#[derive(ConfigFile)]` to derive the ConfigFile trait for a struct -/// -/// Specify the default storage path via `#[cfg_file(path = "...")]` -/// -/// ## About the `cfg_file` attribute macro -/// -/// Use `#[cfg_file(path = "string")]` to specify the configuration file path -/// -/// Or use `#[cfg_file(path = constant_expression)]` to specify the configuration file path -/// -/// ## Path Rules -/// -/// Paths starting with `"./"`: relative to the current working directory -/// -/// Other paths: treated as absolute paths -/// -/// When no path is specified: use the struct name + ".json" as the default filename (e.g., `my_struct.json`) -/// -/// ## Example -/// ```ignore -/// #[derive(ConfigFile)] -/// #[cfg_file(path = "./config.json")] -/// struct AppConfig; -/// ``` -#[proc_macro_derive(ConfigFile, attributes(cfg_file))] -pub fn derive_config_file(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = &input.ident; - - // Process 'cfg_file' - let path_expr = match find_cfg_file_path(&input.attrs) { - Some(PathExpr::StringLiteral(path)) => { - if let Some(path_str) = path.strip_prefix("./") { - quote! { - std::env::current_dir()?.join(#path_str) - } - } else { - // Using Absolute Path - quote! { - std::path::PathBuf::from(#path) - } - } - } - Some(PathExpr::PathExpression(path_expr)) => { - // For path expressions (constants), generate code that references the constant - quote! { - std::path::PathBuf::from(#path_expr) - } - } - None => { - let default_file = to_snake_case(&name.to_string()) + ".json"; - quote! { - std::env::current_dir()?.join(#default_file) - } - } - }; - - let expanded = quote! { - impl cfg_file::config::ConfigFile for #name { - type DataType = #name; - - fn default_path() -> Result<std::path::PathBuf, std::io::Error> { - Ok(#path_expr) - } - } - }; - - TokenStream::from(expanded) -} - -enum PathExpr { - StringLiteral(String), - PathExpression(syn::Expr), -} - -fn find_cfg_file_path(attrs: &[Attribute]) -> Option<PathExpr> { - for attr in attrs { - if attr.path().is_ident("cfg_file") { - let parser = |meta: ParseStream| { - let path_meta: syn::MetaNameValue = meta.parse()?; - if path_meta.path.is_ident("path") { - match &path_meta.value { - // String literal case: path = "./vault.toml" - Expr::Lit(expr_lit) if matches!(expr_lit.lit, syn::Lit::Str(_)) => { - if let syn::Lit::Str(lit_str) = &expr_lit.lit { - return Ok(PathExpr::StringLiteral(lit_str.value())); - } - } - // Path expression case: path = SERVER_FILE_VAULT or crate::constants::SERVER_FILE_VAULT - expr @ (Expr::Path(_) | Expr::Macro(_)) => { - return Ok(PathExpr::PathExpression(expr.clone())); - } - _ => {} - } - } - Err(meta.error("expected `path = \"...\"` or `path = CONSTANT`")) - }; - - if let Ok(path_expr) = attr.parse_args_with(parser) { - return Some(path_expr); - } - } - } - None -} - -fn to_snake_case(s: &str) -> String { - let mut snake = String::new(); - for (i, c) in s.chars().enumerate() { - if c.is_uppercase() { - if i != 0 { - snake.push('_'); - } - snake.push(c.to_ascii_lowercase()); - } else { - snake.push(c); - } - } - snake -} diff --git a/crates/utils/cfg_file/cfg_file_test/Cargo.toml b/crates/utils/cfg_file/cfg_file_test/Cargo.toml deleted file mode 100644 index 5db1010..0000000 --- a/crates/utils/cfg_file/cfg_file_test/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "cfg_file_test" -version = "0.1.0" -edition = "2024" - -[dependencies] -cfg_file = { path = "../../cfg_file", features = ["default"] } -tokio = { version = "1.48.0", features = ["full"] } -serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/utils/cfg_file/cfg_file_test/src/lib.rs b/crates/utils/cfg_file/cfg_file_test/src/lib.rs deleted file mode 100644 index f70d00d..0000000 --- a/crates/utils/cfg_file/cfg_file_test/src/lib.rs +++ /dev/null @@ -1,95 +0,0 @@ -#[cfg(test)] -mod test_cfg_file { - use cfg_file::ConfigFile; - use cfg_file::config::ConfigFile; - use serde::{Deserialize, Serialize}; - use std::collections::HashMap; - - #[derive(ConfigFile, Deserialize, Serialize, Default)] - #[cfg_file(path = "./.temp/example_cfg.toml")] - struct ExampleConfig { - name: String, - age: i32, - hobby: Vec<String>, - secret: HashMap<String, String>, - } - - #[derive(ConfigFile, Deserialize, Serialize, Default)] - #[cfg_file(path = "./.temp/example_bincode.bcfg")] - struct ExampleBincodeConfig { - name: String, - age: i32, - hobby: Vec<String>, - secret: HashMap<String, String>, - } - - #[tokio::test] - async fn test_config_file_serialization() { - let mut example = ExampleConfig { - name: "Weicao".to_string(), - age: 22, - hobby: ["Programming", "Painting"] - .iter() - .map(|m| m.to_string()) - .collect(), - secret: HashMap::new(), - }; - let secret_no_comments = - "Actually, I'm really too lazy to write comments, documentation, and unit tests."; - example - .secret - .entry("No comments".to_string()) - .insert_entry(secret_no_comments.to_string()); - - let secret_peek = "Of course, it's peeking at you who's reading the source code."; - example - .secret - .entry("Peek".to_string()) - .insert_entry(secret_peek.to_string()); - - ExampleConfig::write(&example).await.unwrap(); // Write to default path. - - // Read from default path. - let read_cfg = ExampleConfig::read().await.unwrap(); - assert_eq!(read_cfg.name, "Weicao"); - assert_eq!(read_cfg.age, 22); - assert_eq!(read_cfg.hobby, vec!["Programming", "Painting"]); - assert_eq!(read_cfg.secret["No comments"], secret_no_comments); - assert_eq!(read_cfg.secret["Peek"], secret_peek); - } - - #[tokio::test] - async fn test_bincode_config_file_serialization() { - let mut example = ExampleBincodeConfig { - name: "Weicao".to_string(), - age: 22, - hobby: ["Programming", "Painting"] - .iter() - .map(|m| m.to_string()) - .collect(), - secret: HashMap::new(), - }; - let secret_no_comments = - "Actually, I'm really too lazy to write comments, documentation, and unit tests."; - example - .secret - .entry("No comments".to_string()) - .insert_entry(secret_no_comments.to_string()); - - let secret_peek = "Of course, it's peeking at you who's reading the source code."; - example - .secret - .entry("Peek".to_string()) - .insert_entry(secret_peek.to_string()); - - ExampleBincodeConfig::write(&example).await.unwrap(); // Write to default path. - - // Read from default path. - let read_cfg = ExampleBincodeConfig::read().await.unwrap(); - assert_eq!(read_cfg.name, "Weicao"); - assert_eq!(read_cfg.age, 22); - assert_eq!(read_cfg.hobby, vec!["Programming", "Painting"]); - assert_eq!(read_cfg.secret["No comments"], secret_no_comments); - assert_eq!(read_cfg.secret["Peek"], secret_peek); - } -} diff --git a/crates/utils/cfg_file/src/config.rs b/crates/utils/cfg_file/src/config.rs deleted file mode 100644 index d3f5477..0000000 --- a/crates/utils/cfg_file/src/config.rs +++ /dev/null @@ -1,263 +0,0 @@ -use async_trait::async_trait; -use bincode2; -use ron; -use serde::{Deserialize, Serialize}; -use std::{ - borrow::Cow, - env::current_dir, - io::Error, - path::{Path, PathBuf}, -}; -use tokio::{fs, io::AsyncReadExt}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ConfigFormat { - Yaml, - Toml, - Ron, - Json, - Bincode, -} - -impl ConfigFormat { - fn from_filename(filename: &str) -> Option<Self> { - if filename.ends_with(".yaml") || filename.ends_with(".yml") { - Some(Self::Yaml) - } else if filename.ends_with(".toml") || filename.ends_with(".tom") { - Some(Self::Toml) - } else if filename.ends_with(".ron") { - Some(Self::Ron) - } else if filename.ends_with(".json") { - Some(Self::Json) - } else if filename.ends_with(".bcfg") { - Some(Self::Bincode) - } else { - None - } - } -} - -/// # Trait - ConfigFile -/// -/// Used to implement more convenient persistent storage functionality for structs -/// -/// This trait requires the struct to implement Default and serde's Serialize and Deserialize traits -/// -/// ## Implementation -/// -/// ```ignore -/// // Your struct -/// #[derive(Default, Serialize, Deserialize)] -/// struct YourData; -/// -/// impl ConfigFile for YourData { -/// type DataType = YourData; -/// -/// // Specify default path -/// fn default_path() -> Result<PathBuf, Error> { -/// Ok(current_dir()?.join("data.json")) -/// } -/// } -/// ``` -/// -/// > **Using derive macro** -/// > -/// > We provide the derive macro `#[derive(ConfigFile)]` -/// > -/// > You can implement this trait more quickly, please check the module cfg_file::cfg_file_derive -/// -#[async_trait] -pub trait ConfigFile: Serialize + for<'a> Deserialize<'a> + Default { - type DataType: Serialize + for<'a> Deserialize<'a> + Default + Send + Sync; - - fn default_path() -> Result<PathBuf, Error>; - - /// # Read from default path - /// - /// Read data from the path specified by default_path() - /// - /// ```ignore - /// fn main() -> Result<(), std::io::Error> { - /// let data = YourData::read().await?; - /// } - /// ``` - async fn read() -> Result<Self::DataType, std::io::Error> - where - Self: Sized + Send + Sync, - { - let path = Self::default_path()?; - Self::read_from(path).await - } - - /// # Read from the given path - /// - /// Read data from the path specified by the path parameter - /// - /// ```ignore - /// fn main() -> Result<(), std::io::Error> { - /// let data_path = current_dir()?.join("data.json"); - /// let data = YourData::read_from(data_path).await?; - /// } - /// ``` - async fn read_from(path: impl AsRef<Path> + Send) -> Result<Self::DataType, std::io::Error> - where - Self: Sized + Send + Sync, - { - let path = path.as_ref(); - let cwd = current_dir()?; - let file_path = cwd.join(path); - - // Check if file exists - if fs::metadata(&file_path).await.is_err() { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Config file not found", - )); - } - - // Determine file format first - let format = file_path - .file_name() - .and_then(|name| name.to_str()) - .and_then(ConfigFormat::from_filename) - .unwrap_or(ConfigFormat::Bincode); // Default to Bincode - - // Deserialize based on format - let result = match format { - ConfigFormat::Yaml => { - let mut file = fs::File::open(&file_path).await?; - let mut contents = String::new(); - file.read_to_string(&mut contents).await?; - serde_yaml::from_str(&contents) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? - } - ConfigFormat::Toml => { - let mut file = fs::File::open(&file_path).await?; - let mut contents = String::new(); - file.read_to_string(&mut contents).await?; - toml::from_str(&contents) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? - } - ConfigFormat::Ron => { - let mut file = fs::File::open(&file_path).await?; - let mut contents = String::new(); - file.read_to_string(&mut contents).await?; - ron::from_str(&contents) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? - } - ConfigFormat::Json => { - let mut file = fs::File::open(&file_path).await?; - let mut contents = String::new(); - file.read_to_string(&mut contents).await?; - serde_json::from_str(&contents) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? - } - ConfigFormat::Bincode => { - // For Bincode, we need to read the file as bytes directly - let bytes = fs::read(&file_path).await?; - bincode2::deserialize(&bytes) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? - } - }; - - Ok(result) - } - - /// # Write to default path - /// - /// Write data to the path specified by default_path() - /// - /// ```ignore - /// fn main() -> Result<(), std::io::Error> { - /// let data = YourData::default(); - /// YourData::write(&data).await?; - /// } - /// ``` - async fn write(val: &Self::DataType) -> Result<(), std::io::Error> - where - Self: Sized + Send + Sync, - { - let path = Self::default_path()?; - Self::write_to(val, path).await - } - /// # Write to the given path - /// - /// Write data to the path specified by the path parameter - /// - /// ```ignore - /// fn main() -> Result<(), std::io::Error> { - /// let data = YourData::default(); - /// let data_path = current_dir()?.join("data.json"); - /// YourData::write_to(&data, data_path).await?; - /// } - /// ``` - async fn write_to( - val: &Self::DataType, - path: impl AsRef<Path> + Send, - ) -> Result<(), std::io::Error> - where - Self: Sized + Send + Sync, - { - let path = path.as_ref(); - - if let Some(parent) = path.parent() - && !parent.exists() - { - tokio::fs::create_dir_all(parent).await?; - } - - let cwd = current_dir()?; - let file_path = cwd.join(path); - - // Determine file format - let format = file_path - .file_name() - .and_then(|name| name.to_str()) - .and_then(ConfigFormat::from_filename) - .unwrap_or(ConfigFormat::Bincode); // Default to Bincode - - match format { - ConfigFormat::Yaml => { - let contents = serde_yaml::to_string(val) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - fs::write(&file_path, contents).await? - } - ConfigFormat::Toml => { - let contents = toml::to_string(val) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - fs::write(&file_path, contents).await? - } - ConfigFormat::Ron => { - let mut pretty_config = ron::ser::PrettyConfig::new(); - pretty_config.new_line = Cow::from("\n"); - pretty_config.indentor = Cow::from(" "); - - let contents = ron::ser::to_string_pretty(val, pretty_config) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - fs::write(&file_path, contents).await? - } - ConfigFormat::Json => { - let contents = serde_json::to_string(val) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - fs::write(&file_path, contents).await? - } - ConfigFormat::Bincode => { - let bytes = bincode2::serialize(val) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - fs::write(&file_path, bytes).await? - } - } - Ok(()) - } - - /// Check if the file returned by `default_path` exists - fn exist() -> bool - where - Self: Sized + Send + Sync, - { - let Ok(path) = Self::default_path() else { - return false; - }; - path.exists() - } -} diff --git a/crates/utils/cfg_file/src/lib.rs b/crates/utils/cfg_file/src/lib.rs deleted file mode 100644 index 72246e7..0000000 --- a/crates/utils/cfg_file/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[cfg(feature = "derive")] -extern crate cfg_file_derive; - -#[cfg(feature = "derive")] -pub use cfg_file_derive::*; - -pub mod config; diff --git a/crates/utils/data_struct/Cargo.toml b/crates/utils/data_struct/Cargo.toml deleted file mode 100644 index e8caa6e..0000000 --- a/crates/utils/data_struct/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "data_struct" -edition = "2024" -version.workspace = true - -[features] - -[dependencies] -serde = { version = "1.0.228", features = ["derive"] } -ahash = "0.8.12" diff --git a/crates/utils/data_struct/src/bi_map.rs b/crates/utils/data_struct/src/bi_map.rs deleted file mode 100644 index c21a9c8..0000000 --- a/crates/utils/data_struct/src/bi_map.rs +++ /dev/null @@ -1,239 +0,0 @@ -use ahash::AHasher; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::hash::{BuildHasherDefault, Hash}; - -type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<AHasher>>; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BiMap<A, B> -where - A: Eq + Hash + Clone, - B: Eq + Hash + Clone, -{ - #[serde(flatten)] - a_to_b: FastHashMap<A, B>, - #[serde(skip)] - b_to_a: FastHashMap<B, A>, -} - -pub struct Entry<'a, A, B> -where - A: Eq + Hash + Clone, - B: Eq + Hash + Clone, -{ - bimap: &'a mut BiMap<A, B>, - key: A, - value: Option<B>, -} - -impl<A, B> BiMap<A, B> -where - A: Eq + Hash + Clone, - B: Eq + Hash + Clone, -{ - pub fn new() -> Self { - Self { - a_to_b: FastHashMap::default(), - b_to_a: FastHashMap::default(), - } - } - - pub fn entry(&mut self, a: A) -> Entry<'_, A, B> { - let value = self.a_to_b.get(&a).cloned(); - Entry { - bimap: self, - key: a, - value, - } - } - - #[inline(always)] - pub fn insert(&mut self, a: A, b: B) { - if let Some(old_b) = self.a_to_b.insert(a.clone(), b.clone()) { - self.b_to_a.remove(&old_b); - } - if let Some(old_a) = self.b_to_a.insert(b.clone(), a.clone()) { - self.a_to_b.remove(&old_a); - } - } - - #[inline(always)] - pub fn get_by_a(&self, key: &A) -> Option<&B> { - self.a_to_b.get(key) - } - - #[inline(always)] - pub fn get_by_b(&self, key: &B) -> Option<&A> { - self.b_to_a.get(key) - } - - pub fn remove_by_a(&mut self, key: &A) -> Option<(A, B)> { - if let Some(b) = self.get_by_a(key).cloned() { - let a = self.get_by_b(&b).cloned().unwrap(); - self.a_to_b.remove(key); - self.b_to_a.remove(&b); - Some((a, b)) - } else { - None - } - } - - pub fn remove_by_b(&mut self, key: &B) -> Option<(A, B)> { - if let Some(a) = self.get_by_b(key).cloned() { - let b = self.get_by_a(&a).cloned().unwrap(); - self.b_to_a.remove(key); - self.a_to_b.remove(&a); - Some((a, b)) - } else { - None - } - } - - pub fn reserve(&mut self, additional: usize) { - self.a_to_b.reserve(additional); - self.b_to_a.reserve(additional); - } - - pub fn len(&self) -> usize { - self.a_to_b.len() - } - - pub fn is_empty(&self) -> bool { - self.a_to_b.is_empty() - } - - pub fn clear(&mut self) { - self.a_to_b.clear(); - self.b_to_a.clear(); - } - - pub fn contains_a(&self, key: &A) -> bool { - self.a_to_b.contains_key(key) - } - - pub fn contains_b(&self, key: &B) -> bool { - self.b_to_a.contains_key(key) - } - - pub fn keys_a(&self) -> impl Iterator<Item = &A> { - self.a_to_b.keys() - } - - pub fn keys_b(&self) -> impl Iterator<Item = &B> { - self.b_to_a.keys() - } - - pub fn iter_a_to_b(&self) -> impl Iterator<Item = (&A, &B)> { - self.a_to_b.iter() - } - - pub fn iter_b_to_a(&self) -> impl Iterator<Item = (&B, &A)> { - self.b_to_a.iter() - } -} - -impl<'a, A, B> Entry<'a, A, B> -where - A: Eq + Hash + Clone, - B: Eq + Hash + Clone, -{ - pub fn and_modify<F>(mut self, f: F) -> Self - where - F: FnOnce(&mut B), - { - if let Some(ref mut value) = self.value { - f(value); - } - self - } - - pub fn or_insert(self, default: B) -> Result<&'a mut B, &'static str> { - self.or_insert_with(|| default) - } - - pub fn or_insert_with<F>(mut self, default: F) -> Result<&'a mut B, &'static str> - where - F: FnOnce() -> B, - { - if self.value.is_none() { - self.value = Some(default()); - } - - let value = self.value.as_ref().ok_or("Value is None")?.clone(); - self.bimap.insert(self.key.clone(), value); - - self.bimap - .a_to_b - .get_mut(&self.key) - .ok_or("Key not found in a_to_b map") - } -} - -impl<A, B> Default for BiMap<A, B> -where - A: Eq + Hash + Clone, - B: Eq + Hash + Clone, -{ - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bimap_basic_operations() { - let mut bimap = BiMap::new(); - bimap.insert("key1", "value1"); - - assert_eq!(bimap.get_by_a(&"key1"), Some(&"value1")); - assert_eq!(bimap.get_by_b(&"value1"), Some(&"key1")); - assert!(bimap.contains_a(&"key1")); - assert!(bimap.contains_b(&"value1")); - } - - #[test] - fn test_bimap_remove() { - let mut bimap = BiMap::new(); - bimap.insert(1, "one"); - - assert_eq!(bimap.remove_by_a(&1), Some((1, "one"))); - assert!(bimap.is_empty()); - } - - #[test] - fn test_bimap_entry() { - let mut bimap = BiMap::new(); - bimap.entry("key1").or_insert("value1").unwrap(); - - assert_eq!(bimap.get_by_a(&"key1"), Some(&"value1")); - } - - #[test] - fn test_bimap_iterators() { - let mut bimap = BiMap::new(); - bimap.insert(1, "one"); - bimap.insert(2, "two"); - - let a_keys: Vec<_> = bimap.keys_a().collect(); - assert!(a_keys.contains(&&1) && a_keys.contains(&&2)); - - let b_keys: Vec<_> = bimap.keys_b().collect(); - assert!(b_keys.contains(&&"one") && b_keys.contains(&&"two")); - } - - #[test] - fn test_bimap_duplicate_insert() { - let mut bimap = BiMap::new(); - bimap.insert(1, "one"); - bimap.insert(1, "new_one"); - bimap.insert(2, "one"); - - assert_eq!(bimap.get_by_a(&1), Some(&"new_one")); - assert_eq!(bimap.get_by_b(&"one"), Some(&2)); - assert_eq!(bimap.get_by_a(&2), Some(&"one")); - } -} diff --git a/crates/utils/data_struct/src/data_sort.rs b/crates/utils/data_struct/src/data_sort.rs deleted file mode 100644 index 2c7a452..0000000 --- a/crates/utils/data_struct/src/data_sort.rs +++ /dev/null @@ -1,232 +0,0 @@ -/// Quick sort a slice with a custom comparison function -/// -/// # Arguments -/// * `arr` - The mutable slice to be sorted -/// * `inverse` - Sort direction: true for descending, false for ascending -/// * `compare` - Comparison function that returns -1, 0, or 1 indicating the relative order of two elements -pub fn quick_sort_with_cmp<T, F>(arr: &mut [T], inverse: bool, compare: F) -where - F: Fn(&T, &T) -> i32, -{ - quick_sort_with_cmp_helper(arr, inverse, &compare); -} - -/// Quick sort for types that implement the PartialOrd trait -/// -/// # Arguments -/// * `arr` - The mutable slice to be sorted -/// * `inverse` - Sort direction: true for descending, false for ascending -pub fn quick_sort<T: PartialOrd>(arr: &mut [T], inverse: bool) { - quick_sort_with_cmp(arr, inverse, |a, b| { - if a < b { - -1 - } else if a > b { - 1 - } else { - 0 - } - }); -} - -fn quick_sort_with_cmp_helper<T, F>(arr: &mut [T], inverse: bool, compare: &F) -where - F: Fn(&T, &T) -> i32, -{ - if arr.len() <= 1 { - return; - } - - let pivot_index = partition_with_cmp(arr, inverse, compare); - let (left, right) = arr.split_at_mut(pivot_index); - - quick_sort_with_cmp_helper(left, inverse, compare); - quick_sort_with_cmp_helper(&mut right[1..], inverse, compare); -} - -fn partition_with_cmp<T, F>(arr: &mut [T], inverse: bool, compare: &F) -> usize -where - F: Fn(&T, &T) -> i32, -{ - let len = arr.len(); - let pivot_index = len / 2; - - arr.swap(pivot_index, len - 1); - - let mut i = 0; - for j in 0..len - 1 { - let cmp_result = compare(&arr[j], &arr[len - 1]); - let should_swap = if inverse { - cmp_result > 0 - } else { - cmp_result < 0 - }; - - if should_swap { - arr.swap(i, j); - i += 1; - } - } - - arr.swap(i, len - 1); - i -} - -#[cfg(test)] -pub mod sort_test { - use crate::data_sort::{quick_sort, quick_sort_with_cmp}; - - #[test] - fn test_quick_sort_ascending() { - let mut arr = [3, 1, 4, 1, 5, 9, 2, 6]; - quick_sort(&mut arr, false); - assert_eq!(arr, [1, 1, 2, 3, 4, 5, 6, 9]); - } - - #[test] - fn test_quick_sort_descending() { - let mut arr = [3, 1, 4, 1, 5, 9, 2, 6]; - quick_sort(&mut arr, true); - assert_eq!(arr, [9, 6, 5, 4, 3, 2, 1, 1]); - } - - #[test] - fn test_quick_sort_single() { - let mut arr = [42]; - quick_sort(&mut arr, false); - assert_eq!(arr, [42]); - } - - #[test] - fn test_quick_sort_already_sorted() { - let mut arr = [1, 2, 3, 4, 5]; - quick_sort(&mut arr, false); - assert_eq!(arr, [1, 2, 3, 4, 5]); - } - - #[test] - fn test_quick_sort_with_cmp_by_count() { - #[derive(Debug, PartialEq)] - struct WordCount { - word: String, - count: usize, - } - - let mut words = vec![ - WordCount { - word: "apple".to_string(), - count: 3, - }, - WordCount { - word: "banana".to_string(), - count: 1, - }, - WordCount { - word: "cherry".to_string(), - count: 5, - }, - WordCount { - word: "date".to_string(), - count: 2, - }, - ]; - - quick_sort_with_cmp(&mut words, false, |a, b| { - if a.count < b.count { - -1 - } else if a.count > b.count { - 1 - } else { - 0 - } - }); - - assert_eq!( - words, - vec![ - WordCount { - word: "banana".to_string(), - count: 1 - }, - WordCount { - word: "date".to_string(), - count: 2 - }, - WordCount { - word: "apple".to_string(), - count: 3 - }, - WordCount { - word: "cherry".to_string(), - count: 5 - }, - ] - ); - - quick_sort_with_cmp(&mut words, true, |a, b| { - if a.count < b.count { - -1 - } else if a.count > b.count { - 1 - } else { - 0 - } - }); - - assert_eq!( - words, - vec![ - WordCount { - word: "cherry".to_string(), - count: 5 - }, - WordCount { - word: "apple".to_string(), - count: 3 - }, - WordCount { - word: "date".to_string(), - count: 2 - }, - WordCount { - word: "banana".to_string(), - count: 1 - }, - ] - ); - } - - #[test] - fn test_quick_sort_with_cmp_by_first_letter() { - let mut words = vec!["zebra", "apple", "banana", "cherry", "date"]; - - quick_sort_with_cmp(&mut words, false, |a, b| { - let a_first = a.chars().next().unwrap(); - let b_first = b.chars().next().unwrap(); - - if a_first < b_first { - -1 - } else if a_first > b_first { - 1 - } else { - 0 - } - }); - - assert_eq!(words, vec!["apple", "banana", "cherry", "date", "zebra"]); - - quick_sort_with_cmp(&mut words, true, |a, b| { - let a_first = a.chars().next().unwrap(); - let b_first = b.chars().next().unwrap(); - - if a_first < b_first { - -1 - } else if a_first > b_first { - 1 - } else { - 0 - } - }); - - assert_eq!(words, vec!["zebra", "date", "cherry", "banana", "apple"]); - } -} diff --git a/crates/utils/data_struct/src/lib.rs b/crates/utils/data_struct/src/lib.rs deleted file mode 100644 index 47cc03c..0000000 --- a/crates/utils/data_struct/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bi_map; -pub mod data_sort; diff --git a/crates/utils/sha1_hash/Cargo.toml b/crates/utils/sha1_hash/Cargo.toml deleted file mode 100644 index e206efd..0000000 --- a/crates/utils/sha1_hash/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "sha1_hash" -edition = "2024" -version.workspace = true - -[dependencies] -tokio = { version = "1.48", features = ["full"] } -sha1 = "0.10" -futures = "0.3" diff --git a/crates/utils/sha1_hash/res/story.txt b/crates/utils/sha1_hash/res/story.txt deleted file mode 100644 index a91f467..0000000 --- a/crates/utils/sha1_hash/res/story.txt +++ /dev/null @@ -1,48 +0,0 @@ -魏曹者,程序员也,发稀甚于代码。 -忽接神秘电话曰: -"贺君中彩,得长生之赐。" -魏曹冷笑曰:"吾命尚不及下版之期。" - -翌日果得U盘。 -接入电脑,弹窗示曰: -"点此确认,即获永生。" -魏曹径点"永拒"。 - -三月后,U盘自格其盘。 -进度条滞于九九。 -客服电话已成空号。 -魏曹乃知身可不死,然体内癌细胞亦得不灭。 - -遂谒主请辞。 -主曰:"巧甚,公司正欲优化。" -魏曹曰:"吾不死。" -主目骤亮:"则可007至司闭。" - -魏曹始试诸死法。 -坠楼,卧医三月,账单令其愿死。 -饮鸩,肝肾永损,然终不得死。 -终决卧轨。 - -择高铁最速者。 -司机探头曰:"兄台,吾亦不死身也。" -"此车已碾如君者二十人矣。" - -二人遂坐轨畔对饮。 -司机曰:"知最讽者何?" -"吾等永存,而所爱者皆逝矣。" - -魏曹忽得系统提示: -"侦得用户消极求生,将启工模。" -自是无日不毕KPI,否则遍尝绝症之苦。 - -是日对镜整寿衣。 -忽见顶生一丝乌发。 -泫然泣下,此兆示其将复活一轮回。 - ---- 忽忆DeepSeek尝作Footer曰: -"文成而Hash1验,若星河之固。" -遂取哈希值校之, -字符流转如天河倒泻, -终得"e3b0c44298fc1c14"之数。 -然文末数字竟阙如残月, -方知此篇亦遭永劫轮回。 diff --git a/crates/utils/sha1_hash/res/story_crlf.sha1 b/crates/utils/sha1_hash/res/story_crlf.sha1 deleted file mode 100644 index bc8ad25..0000000 --- a/crates/utils/sha1_hash/res/story_crlf.sha1 +++ /dev/null @@ -1 +0,0 @@ -40c1d848d8d6a14b9403ee022f2b28dabb3b3a71 diff --git a/crates/utils/sha1_hash/res/story_lf.sha1 b/crates/utils/sha1_hash/res/story_lf.sha1 deleted file mode 100644 index c2e3213..0000000 --- a/crates/utils/sha1_hash/res/story_lf.sha1 +++ /dev/null @@ -1 +0,0 @@ -6838aca280112635a2cbf93440f4c04212f58ee8 diff --git a/crates/utils/sha1_hash/src/lib.rs b/crates/utils/sha1_hash/src/lib.rs deleted file mode 100644 index 96a7897..0000000 --- a/crates/utils/sha1_hash/src/lib.rs +++ /dev/null @@ -1,257 +0,0 @@ -use sha1::{Digest, Sha1}; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use tokio::fs::File; -use tokio::io::{AsyncReadExt, BufReader}; -use tokio::task; - -/// # Struct - Sha1Result -/// -/// Records SHA1 calculation results, including the file path and hash value -#[derive(Debug, Clone)] -pub struct Sha1Result { - pub file_path: PathBuf, - pub hash: String, -} - -/// Calc SHA1 hash of a string -pub fn calc_sha1_string<S: AsRef<str>>(input: S) -> String { - let mut hasher = Sha1::new(); - hasher.update(input.as_ref().as_bytes()); - let hash_result = hasher.finalize(); - - hash_result - .iter() - .map(|b| format!("{:02x}", b)) - .collect::<String>() -} - -/// Calc SHA1 hash of a single file -pub async fn calc_sha1<P: AsRef<Path>>( - path: P, - buffer_size: usize, -) -> Result<Sha1Result, Box<dyn std::error::Error + Send + Sync>> { - let file_path = path.as_ref().to_string_lossy().to_string(); - - // Open file asynchronously - let file = File::open(&path).await?; - let mut reader = BufReader::with_capacity(buffer_size, file); - let mut hasher = Sha1::new(); - let mut buffer = vec![0u8; buffer_size]; - - // Read file in chunks and update hash asynchronously - loop { - let n = reader.read(&mut buffer).await?; - if n == 0 { - break; - } - hasher.update(&buffer[..n]); - } - - let hash_result = hasher.finalize(); - - // Convert to hex string - let hash_hex = hash_result - .iter() - .map(|b| format!("{:02x}", b)) - .collect::<String>(); - - Ok(Sha1Result { - file_path: file_path.into(), - hash: hash_hex, - }) -} - -/// Calc SHA1 hashes for multiple files using multi-threading -pub async fn calc_sha1_multi<P, I>( - paths: I, - buffer_size: usize, -) -> Result<Vec<Sha1Result>, Box<dyn std::error::Error + Send + Sync>> -where - P: AsRef<Path> + Send + Sync + 'static, - I: IntoIterator<Item = P>, -{ - let buffer_size = Arc::new(buffer_size); - - // Collect all file paths - let file_paths: Vec<P> = paths.into_iter().collect(); - - if file_paths.is_empty() { - return Ok(Vec::new()); - } - - // Create tasks for each file - let tasks: Vec<_> = file_paths - .into_iter() - .map(|path| { - let buffer_size = Arc::clone(&buffer_size); - task::spawn(async move { calc_sha1(path, *buffer_size).await }) - }) - .collect(); - - // Execute tasks with concurrency limit using join_all - let results: Vec<Result<Sha1Result, Box<dyn std::error::Error + Send + Sync>>> = - futures::future::join_all(tasks) - .await - .into_iter() - .map(|task_result| match task_result { - Ok(Ok(calc_result)) => Ok(calc_result), - Ok(Err(e)) => Err(e), - Err(e) => Err(Box::new(e) as Box<dyn std::error::Error + Send + Sync>), - }) - .collect(); - - // Check for any errors and collect successful results - let mut successful_results = Vec::new(); - for result in results { - match result { - Ok(success) => successful_results.push(success), - Err(e) => return Err(e), - } - } - - Ok(successful_results) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - #[test] - fn test_sha1_string() { - let test_string = "Hello, SHA1!"; - let hash = calc_sha1_string(test_string); - - let expected_hash = "de1c3daadc6f0f1626f4cf56c03e05a1e5d7b187"; - - assert_eq!( - hash, expected_hash, - "SHA1 hash should be consistent for same input" - ); - } - - #[test] - fn test_sha1_string_empty() { - let hash = calc_sha1_string(""); - - // SHA1 of empty string is "da39a3ee5e6b4b0d3255bfef95601890afd80709" - let expected_empty_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; - assert_eq!( - hash, expected_empty_hash, - "SHA1 hash mismatch for empty string" - ); - } - - #[tokio::test] - async fn test_sha1_accuracy() { - // Test file path relative to the crate root - let test_file_path = "res/story.txt"; - // Choose expected hash file based on platform - let expected_hash_path = if cfg!(windows) { - "res/story_crlf.sha1" - } else { - "res/story_lf.sha1" - }; - - // Calculate SHA1 hash - let result = calc_sha1(test_file_path, 8192) - .await - .expect("Failed to calculate SHA1"); - - // Read expected hash from file - let expected_hash = fs::read_to_string(expected_hash_path) - .expect("Failed to read expected hash file") - .trim() - .to_string(); - - // Verify the calculated hash matches expected hash - assert_eq!( - result.hash, expected_hash, - "SHA1 hash mismatch for test file" - ); - - println!("Test file: {}", result.file_path.display()); - println!("Calculated hash: {}", result.hash); - println!("Expected hash: {}", expected_hash); - println!( - "Platform: {}", - if cfg!(windows) { - "Windows" - } else { - "Unix/Linux" - } - ); - } - - #[tokio::test] - async fn test_sha1_empty_file() { - // Create a temporary empty file for testing - let temp_file = "test_empty.txt"; - fs::write(temp_file, "").expect("Failed to create empty test file"); - - let result = calc_sha1(temp_file, 4096) - .await - .expect("Failed to calculate SHA1 for empty file"); - - // SHA1 of empty string is "da39a3ee5e6b4b0d3255bfef95601890afd80709" - let expected_empty_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; - assert_eq!( - result.hash, expected_empty_hash, - "SHA1 hash mismatch for empty file" - ); - - // Clean up - fs::remove_file(temp_file).expect("Failed to remove temporary test file"); - } - - #[tokio::test] - async fn test_sha1_simple_text() { - // Create a temporary file with simple text - let temp_file = "test_simple.txt"; - let test_content = "Hello, SHA1!"; - fs::write(temp_file, test_content).expect("Failed to create simple test file"); - - let result = calc_sha1(temp_file, 4096) - .await - .expect("Failed to calculate SHA1 for simple text"); - - // Note: This test just verifies that the function works without errors - // The actual hash value is not critical for this test - - println!("Simple text test - Calculated hash: {}", result.hash); - - // Clean up - fs::remove_file(temp_file).expect("Failed to remove temporary test file"); - } - - #[tokio::test] - async fn test_sha1_multi_files() { - // Test multiple files calculation - let test_files = vec!["res/story.txt"]; - - let results = calc_sha1_multi(test_files, 8192) - .await - .expect("Failed to calculate SHA1 for multiple files"); - - assert_eq!(results.len(), 1, "Should have calculated hash for 1 file"); - - // Choose expected hash file based on platform - let expected_hash_path = if cfg!(windows) { - "res/story_crlf.sha1" - } else { - "res/story_lf.sha1" - }; - - // Read expected hash from file - let expected_hash = fs::read_to_string(expected_hash_path) - .expect("Failed to read expected hash file") - .trim() - .to_string(); - - assert_eq!( - results[0].hash, expected_hash, - "SHA1 hash mismatch in multi-file test" - ); - } -} diff --git a/crates/utils/string_proc/Cargo.toml b/crates/utils/string_proc/Cargo.toml deleted file mode 100644 index 5292339..0000000 --- a/crates/utils/string_proc/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "string_proc" -version = "0.1.0" -edition = "2024" - -[dependencies] -strip-ansi-escapes = "0.2.1" diff --git a/crates/utils/string_proc/src/format_path.rs b/crates/utils/string_proc/src/format_path.rs deleted file mode 100644 index 35689b8..0000000 --- a/crates/utils/string_proc/src/format_path.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::path::{Path, PathBuf}; - -/// Format path str -pub fn format_path_str(path: impl Into<String>) -> Result<String, std::io::Error> { - let path_str = path.into(); - let ends_with_slash = path_str.ends_with('/'); - - // ANSI Strip - let cleaned = strip_ansi_escapes::strip(&path_str); - let path_without_ansi = String::from_utf8(cleaned) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - - let path_with_forward_slash = path_without_ansi.replace('\\', "/"); - let mut result = String::new(); - let mut prev_char = '\0'; - - for c in path_with_forward_slash.chars() { - if c == '/' && prev_char == '/' { - continue; - } - result.push(c); - prev_char = c; - } - - let unfriendly_chars = ['*', '?', '"', '<', '>', '|']; - result = result - .chars() - .filter(|c| !unfriendly_chars.contains(c)) - .collect(); - - // Handle ".." path components - let path_buf = PathBuf::from(&result); - let normalized_path = normalize_path(&path_buf); - result = normalized_path.to_string_lossy().replace('\\', "/"); - - // Restore trailing slash if original path had one - if ends_with_slash && !result.ends_with('/') { - result.push('/'); - } - - // Special case: when result is only "./", return "" - if result == "./" { - return Ok(String::new()); - } - - Ok(result) -} - -/// Normalize path by resolving ".." components without requiring file system access -fn normalize_path(path: &Path) -> PathBuf { - let mut components = Vec::new(); - - for component in path.components() { - match component { - std::path::Component::ParentDir => { - if !components.is_empty() { - components.pop(); - } - } - std::path::Component::CurDir => { - // Skip current directory components - } - _ => { - components.push(component); - } - } - } - - if components.is_empty() { - PathBuf::from(".") - } else { - components.iter().collect() - } -} - -pub fn format_path(path: impl Into<PathBuf>) -> Result<PathBuf, std::io::Error> { - let path_str = format_path_str(path.into().display().to_string())?; - Ok(PathBuf::from(path_str)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_path() -> Result<(), std::io::Error> { - assert_eq!(format_path_str("C:\\Users\\\\test")?, "C:/Users/test"); - - assert_eq!( - format_path_str("/path/with/*unfriendly?chars")?, - "/path/with/unfriendlychars" - ); - - assert_eq!(format_path_str("\x1b[31m/path\x1b[0m")?, "/path"); - assert_eq!(format_path_str("/home/user/dir/")?, "/home/user/dir/"); - assert_eq!( - format_path_str("/home/user/file.txt")?, - "/home/user/file.txt" - ); - assert_eq!( - format_path_str("/home/my_user/DOCS/JVCS_TEST/Workspace/../Vault/")?, - "/home/my_user/DOCS/JVCS_TEST/Vault/" - ); - - assert_eq!(format_path_str("./home/file.txt")?, "home/file.txt"); - assert_eq!(format_path_str("./home/path/")?, "home/path/"); - assert_eq!(format_path_str("./")?, ""); - - Ok(()) - } -} diff --git a/crates/utils/string_proc/src/format_processer.rs b/crates/utils/string_proc/src/format_processer.rs deleted file mode 100644 index 8d0a770..0000000 --- a/crates/utils/string_proc/src/format_processer.rs +++ /dev/null @@ -1,132 +0,0 @@ -pub struct FormatProcesser { - content: Vec<String>, -} - -impl From<String> for FormatProcesser { - fn from(value: String) -> Self { - Self { - content: Self::process_string(value), - } - } -} - -impl From<&str> for FormatProcesser { - fn from(value: &str) -> Self { - Self { - content: Self::process_string(value.to_string()), - } - } -} - -impl FormatProcesser { - /// Process the string into an intermediate format - fn process_string(input: String) -> Vec<String> { - let mut result = String::new(); - let mut prev_space = false; - - for c in input.chars() { - match c { - 'a'..='z' | 'A'..='Z' | '0'..='9' => { - result.push(c); - prev_space = false; - } - '_' | ',' | '.' | '-' | ' ' => { - if !prev_space { - result.push(' '); - prev_space = true; - } - } - _ => {} - } - } - - let mut processed = String::new(); - let mut chars = result.chars().peekable(); - - while let Some(c) = chars.next() { - processed.push(c); - if let Some(&next) = chars.peek() - && c.is_lowercase() - && next.is_uppercase() - { - processed.push(' '); - } - } - - processed - .to_lowercase() - .split_whitespace() - .map(|s| s.to_string()) - .collect() - } - - /// Convert to camelCase format (brewCoffee) - pub fn to_camel_case(&self) -> String { - let mut result = String::new(); - for (i, word) in self.content.iter().enumerate() { - if i == 0 { - result.push_str(&word.to_lowercase()); - } else { - let mut chars = word.chars(); - if let Some(first) = chars.next() { - result.push_str(&first.to_uppercase().collect::<String>()); - result.push_str(&chars.collect::<String>().to_lowercase()); - } - } - } - result - } - - /// Convert to PascalCase format (BrewCoffee) - pub fn to_pascal_case(&self) -> String { - let mut result = String::new(); - for word in &self.content { - let mut chars = word.chars(); - if let Some(first) = chars.next() { - result.push_str(&first.to_uppercase().collect::<String>()); - result.push_str(&chars.collect::<String>().to_lowercase()); - } - } - result - } - - /// Convert to kebab-case format (brew-coffee) - pub fn to_kebab_case(&self) -> String { - self.content.join("-").to_lowercase() - } - - /// Convert to snake_case format (brew_coffee) - pub fn to_snake_case(&self) -> String { - self.content.join("_").to_lowercase() - } - - /// Convert to dot.case format (brew.coffee) - pub fn to_dot_case(&self) -> String { - self.content.join(".").to_lowercase() - } - - /// Convert to Title Case format (Brew Coffee) - pub fn to_title_case(&self) -> String { - let mut result = String::new(); - for word in &self.content { - let mut chars = word.chars(); - if let Some(first) = chars.next() { - result.push_str(&first.to_uppercase().collect::<String>()); - result.push_str(&chars.collect::<String>().to_lowercase()); - } - result.push(' '); - } - result.pop(); - result - } - - /// Convert to lower case format (brew coffee) - pub fn to_lower_case(&self) -> String { - self.content.join(" ").to_lowercase() - } - - /// Convert to UPPER CASE format (BREW COFFEE) - pub fn to_upper_case(&self) -> String { - self.content.join(" ").to_uppercase() - } -} diff --git a/crates/utils/string_proc/src/lib.rs b/crates/utils/string_proc/src/lib.rs deleted file mode 100644 index 76588c1..0000000 --- a/crates/utils/string_proc/src/lib.rs +++ /dev/null @@ -1,50 +0,0 @@ -pub mod format_path; -pub mod format_processer; -pub mod macros; -pub mod simple_processer; - -#[cfg(test)] -mod tests { - use crate::format_processer::FormatProcesser; - - #[test] - fn test_processer() { - let test_cases = vec![ - ("brew_coffee", "brewCoffee"), - ("brew, coffee", "brewCoffee"), - ("brew-coffee", "brewCoffee"), - ("Brew.Coffee", "brewCoffee"), - ("bRewCofFee", "bRewCofFee"), - ("brewCoffee", "brewCoffee"), - ("b&rewCoffee", "brewCoffee"), - ("BrewCoffee", "brewCoffee"), - ("brew.coffee", "brewCoffee"), - ("Brew_Coffee", "brewCoffee"), - ("BREW COFFEE", "brewCoffee"), - ]; - - for (input, expected) in test_cases { - let processor = FormatProcesser::from(input); - assert_eq!( - processor.to_camel_case(), - expected, - "Failed for input: '{}'", - input - ); - } - } - - #[test] - fn test_conversions() { - let processor = FormatProcesser::from("brewCoffee"); - - assert_eq!(processor.to_upper_case(), "BREW COFFEE"); - assert_eq!(processor.to_lower_case(), "brew coffee"); - assert_eq!(processor.to_title_case(), "Brew Coffee"); - assert_eq!(processor.to_dot_case(), "brew.coffee"); - assert_eq!(processor.to_snake_case(), "brew_coffee"); - assert_eq!(processor.to_kebab_case(), "brew-coffee"); - assert_eq!(processor.to_pascal_case(), "BrewCoffee"); - assert_eq!(processor.to_camel_case(), "brewCoffee"); - } -} diff --git a/crates/utils/string_proc/src/macros.rs b/crates/utils/string_proc/src/macros.rs deleted file mode 100644 index 135268e..0000000 --- a/crates/utils/string_proc/src/macros.rs +++ /dev/null @@ -1,63 +0,0 @@ -#[macro_export] -macro_rules! camel_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_camel_case() - }}; -} - -#[macro_export] -macro_rules! upper_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_upper_case() - }}; -} - -#[macro_export] -macro_rules! lower_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_lower_case() - }}; -} - -#[macro_export] -macro_rules! title_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_title_case() - }}; -} - -#[macro_export] -macro_rules! dot_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_dot_case() - }}; -} - -#[macro_export] -macro_rules! snake_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_snake_case() - }}; -} - -#[macro_export] -macro_rules! kebab_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_kebab_case() - }}; -} - -#[macro_export] -macro_rules! pascal_case { - ($input:expr) => {{ - use string_proc::format_processer::FormatProcesser; - FormatProcesser::from($input).to_pascal_case() - }}; -} diff --git a/crates/utils/string_proc/src/simple_processer.rs b/crates/utils/string_proc/src/simple_processer.rs deleted file mode 100644 index 2de5dfc..0000000 --- a/crates/utils/string_proc/src/simple_processer.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// Sanitizes a file path by replacing special characters with underscores. -/// -/// This function takes a file path as input and returns a sanitized version -/// where characters that are not allowed in file paths (such as path separators -/// and other reserved characters) are replaced with underscores. -pub fn sanitize_file_path<P: AsRef<str>>(path: P) -> String { - let path_str = path.as_ref(); - path_str - .chars() - .map(|c| match c { - '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_', - _ => c, - }) - .collect() -} diff --git a/crates/utils/tcp_connection/Cargo.toml b/crates/utils/tcp_connection/Cargo.toml deleted file mode 100644 index da258be..0000000 --- a/crates/utils/tcp_connection/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tcp_connection" -edition = "2024" -version.workspace = true - -[dependencies] -tokio = { version = "1.48.0", features = ["full"] } - -# Serialization -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.145" -rmp-serde = "1.3.0" - -# Error handling -thiserror = "2.0.17" - -# Uuid & Random -uuid = "1.18.1" - -# Crypto -rsa = { version = "0.9", features = ["pkcs5", "sha2"] } -ed25519-dalek = "3.0.0-pre.1" -ring = "0.17.14" -rand = "0.10.0-rc.0" -base64 = "0.22.1" -pem = "3.0.6" -crc = "3.3.0" -blake3 = "1.8.2" diff --git a/crates/utils/tcp_connection/src/error.rs b/crates/utils/tcp_connection/src/error.rs deleted file mode 100644 index 32d06cc..0000000 --- a/crates/utils/tcp_connection/src/error.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::io; -use thiserror::Error; - -#[derive(Error, Debug, Clone)] -pub enum TcpTargetError { - #[error("Authentication failed: {0}")] - Authentication(String), - - #[error("Reference sheet not allowed: {0}")] - ReferenceSheetNotAllowed(String), - - #[error("Cryptographic error: {0}")] - Crypto(String), - - #[error("File operation error: {0}")] - File(String), - - #[error("I/O error: {0}")] - Io(String), - - #[error("Invalid configuration: {0}")] - Config(String), - - #[error("Locked: {0}")] - Locked(String), - - #[error("Network error: {0}")] - Network(String), - - #[error("No result: {0}")] - NoResult(String), - - #[error("Not found: {0}")] - NotFound(String), - - #[error("Not local machine: {0}")] - NotLocal(String), - - #[error("Not remote machine: {0}")] - NotRemote(String), - - #[error("Pool already exists: {0}")] - PoolAlreadyExists(String), - - #[error("Protocol error: {0}")] - Protocol(String), - - #[error("Serialization error: {0}")] - Serialization(String), - - #[error("Timeout: {0}")] - Timeout(String), - - #[error("Unsupported operation: {0}")] - Unsupported(String), -} - -impl From<io::Error> for TcpTargetError { - fn from(error: io::Error) -> Self { - TcpTargetError::Io(error.to_string()) - } -} - -impl From<serde_json::Error> for TcpTargetError { - fn from(error: serde_json::Error) -> Self { - TcpTargetError::Serialization(error.to_string()) - } -} - -impl From<&str> for TcpTargetError { - fn from(value: &str) -> Self { - TcpTargetError::Protocol(value.to_string()) - } -} - -impl From<String> for TcpTargetError { - fn from(value: String) -> Self { - TcpTargetError::Protocol(value) - } -} - -impl From<rsa::errors::Error> for TcpTargetError { - fn from(error: rsa::errors::Error) -> Self { - TcpTargetError::Crypto(error.to_string()) - } -} - -impl From<ed25519_dalek::SignatureError> for TcpTargetError { - fn from(error: ed25519_dalek::SignatureError) -> Self { - TcpTargetError::Crypto(error.to_string()) - } -} - -impl From<ring::error::Unspecified> for TcpTargetError { - fn from(error: ring::error::Unspecified) -> Self { - TcpTargetError::Crypto(error.to_string()) - } -} - -impl From<base64::DecodeError> for TcpTargetError { - fn from(error: base64::DecodeError) -> Self { - TcpTargetError::Serialization(error.to_string()) - } -} - -impl From<pem::PemError> for TcpTargetError { - fn from(error: pem::PemError) -> Self { - TcpTargetError::Crypto(error.to_string()) - } -} - -impl From<rmp_serde::encode::Error> for TcpTargetError { - fn from(error: rmp_serde::encode::Error) -> Self { - TcpTargetError::Serialization(error.to_string()) - } -} - -impl From<rmp_serde::decode::Error> for TcpTargetError { - fn from(error: rmp_serde::decode::Error) -> Self { - TcpTargetError::Serialization(error.to_string()) - } -} diff --git a/crates/utils/tcp_connection/src/instance.rs b/crates/utils/tcp_connection/src/instance.rs deleted file mode 100644 index 8e6886c..0000000 --- a/crates/utils/tcp_connection/src/instance.rs +++ /dev/null @@ -1,542 +0,0 @@ -use std::{path::Path, time::Duration}; - -use serde::Serialize; -use tokio::{ - fs::{File, OpenOptions}, - io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, - net::TcpStream, -}; - -use ring::signature::{self}; - -use crate::error::TcpTargetError; - -const DEFAULT_CHUNK_SIZE: usize = 4096; -const DEFAULT_TIMEOUT_SECS: u64 = 10; - -const ECDSA_P256_SHA256_ASN1_SIGNING: &signature::EcdsaSigningAlgorithm = - &signature::ECDSA_P256_SHA256_ASN1_SIGNING; -const ECDSA_P384_SHA384_ASN1_SIGNING: &signature::EcdsaSigningAlgorithm = - &signature::ECDSA_P384_SHA384_ASN1_SIGNING; - -#[derive(Debug, Clone)] -pub struct ConnectionConfig { - pub chunk_size: usize, - pub timeout_secs: u64, - pub enable_crc_validation: bool, -} - -impl Default for ConnectionConfig { - fn default() -> Self { - Self { - chunk_size: DEFAULT_CHUNK_SIZE, - timeout_secs: DEFAULT_TIMEOUT_SECS, - enable_crc_validation: false, - } - } -} - -pub struct ConnectionInstance { - pub(crate) stream: TcpStream, - config: ConnectionConfig, -} - -impl From<TcpStream> for ConnectionInstance { - fn from(stream: TcpStream) -> Self { - Self { - stream, - config: ConnectionConfig::default(), - } - } -} - -impl ConnectionInstance { - /// Create a new ConnectionInstance with custom configuration - pub fn with_config(stream: TcpStream, config: ConnectionConfig) -> Self { - Self { stream, config } - } - - /// Get a reference to the current configuration - pub fn config(&self) -> &ConnectionConfig { - &self.config - } - - /// Get a mutable reference to the current configuration - pub fn config_mut(&mut self) -> &mut ConnectionConfig { - &mut self.config - } - /// Serialize data and write to the target machine - pub async fn write<Data>(&mut self, data: Data) -> Result<(), TcpTargetError> - where - Data: Default + Serialize, - { - let Ok(json_text) = serde_json::to_string(&data) else { - return Err(TcpTargetError::Serialization( - "Serialize failed.".to_string(), - )); - }; - Self::write_text(self, json_text).await?; - Ok(()) - } - - /// Serialize data to MessagePack and write to the target machine - pub async fn write_msgpack<Data>(&mut self, data: Data) -> Result<(), TcpTargetError> - where - Data: Serialize, - { - let msgpack_data = rmp_serde::to_vec(&data)?; - let len = msgpack_data.len() as u32; - - self.stream.write_all(&len.to_be_bytes()).await?; - self.stream.write_all(&msgpack_data).await?; - Ok(()) - } - - /// Read data from target machine and deserialize from MessagePack - pub async fn read_msgpack<Data>(&mut self) -> Result<Data, TcpTargetError> - where - Data: serde::de::DeserializeOwned, - { - let mut len_buf = [0u8; 4]; - self.stream.read_exact(&mut len_buf).await?; - let len = u32::from_be_bytes(len_buf) as usize; - - let mut buffer = vec![0; len]; - self.stream.read_exact(&mut buffer).await?; - - let data = rmp_serde::from_slice(&buffer)?; - Ok(data) - } - - /// Read data from target machine and deserialize - pub async fn read<Data>(&mut self) -> Result<Data, TcpTargetError> - where - Data: Default + serde::de::DeserializeOwned, - { - let Ok(json_text) = Self::read_text(self).await else { - return Err(TcpTargetError::Io("Read failed.".to_string())); - }; - let Ok(deser_obj) = serde_json::from_str::<Data>(&json_text) else { - return Err(TcpTargetError::Serialization( - "Deserialize failed.".to_string(), - )); - }; - Ok(deser_obj) - } - - /// Serialize data and write to the target machine - pub async fn write_large<Data>(&mut self, data: Data) -> Result<(), TcpTargetError> - where - Data: Default + Serialize, - { - let Ok(json_text) = serde_json::to_string(&data) else { - return Err(TcpTargetError::Serialization( - "Serialize failed.".to_string(), - )); - }; - Self::write_large_text(self, json_text).await?; - Ok(()) - } - - /// Read data from target machine and deserialize - pub async fn read_large<Data>( - &mut self, - buffer_size: impl Into<u32>, - ) -> Result<Data, TcpTargetError> - where - Data: Default + serde::de::DeserializeOwned, - { - let Ok(json_text) = Self::read_large_text(self, buffer_size).await else { - return Err(TcpTargetError::Io("Read failed.".to_string())); - }; - let Ok(deser_obj) = serde_json::from_str::<Data>(&json_text) else { - return Err(TcpTargetError::Serialization( - "Deserialize failed.".to_string(), - )); - }; - Ok(deser_obj) - } - - /// Write text to the target machine - pub async fn write_text(&mut self, text: impl Into<String>) -> Result<(), TcpTargetError> { - let text = text.into(); - let bytes = text.as_bytes(); - let len = bytes.len() as u32; - - self.stream.write_all(&len.to_be_bytes()).await?; - match self.stream.write_all(bytes).await { - Ok(_) => Ok(()), - Err(err) => Err(TcpTargetError::Io(err.to_string())), - } - } - - /// Read text from the target machine - pub async fn read_text(&mut self) -> Result<String, TcpTargetError> { - let mut len_buf = [0u8; 4]; - self.stream.read_exact(&mut len_buf).await?; - let len = u32::from_be_bytes(len_buf) as usize; - - let mut buffer = vec![0; len]; - self.stream.read_exact(&mut buffer).await?; - - match String::from_utf8(buffer) { - Ok(text) => Ok(text), - Err(err) => Err(TcpTargetError::Serialization(format!( - "Invalid UTF-8 sequence: {}", - err - ))), - } - } - - /// Write large text to the target machine (chunked) - pub async fn write_large_text( - &mut self, - text: impl Into<String>, - ) -> Result<(), TcpTargetError> { - let text = text.into(); - let bytes = text.as_bytes(); - let mut offset = 0; - - while offset < bytes.len() { - let chunk = &bytes[offset..]; - let written = match self.stream.write(chunk).await { - Ok(n) => n, - Err(err) => return Err(TcpTargetError::Io(err.to_string())), - }; - offset += written; - } - - Ok(()) - } - - /// Read large text from the target machine (chunked) - pub async fn read_large_text( - &mut self, - chunk_size: impl Into<u32>, - ) -> Result<String, TcpTargetError> { - let chunk_size = chunk_size.into() as usize; - let mut buffer = Vec::new(); - let mut chunk_buf = vec![0; chunk_size]; - - loop { - match self.stream.read(&mut chunk_buf).await { - Ok(0) => break, // EOF - Ok(n) => { - buffer.extend_from_slice(&chunk_buf[..n]); - } - Err(err) => return Err(TcpTargetError::Io(err.to_string())), - } - } - - Ok(String::from_utf8_lossy(&buffer).to_string()) - } - - /// Write large MessagePack data to the target machine (chunked) - pub async fn write_large_msgpack<Data>( - &mut self, - data: Data, - chunk_size: impl Into<u32>, - ) -> Result<(), TcpTargetError> - where - Data: Serialize, - { - let msgpack_data = rmp_serde::to_vec(&data)?; - let chunk_size = chunk_size.into() as usize; - let len = msgpack_data.len() as u32; - - // Write total length first - self.stream.write_all(&len.to_be_bytes()).await?; - - // Write data in chunks - let mut offset = 0; - while offset < msgpack_data.len() { - let end = std::cmp::min(offset + chunk_size, msgpack_data.len()); - let chunk = &msgpack_data[offset..end]; - match self.stream.write(chunk).await { - Ok(n) => offset += n, - Err(err) => return Err(TcpTargetError::Io(err.to_string())), - } - } - - Ok(()) - } - - /// Read large MessagePack data from the target machine (chunked) - pub async fn read_large_msgpack<Data>( - &mut self, - chunk_size: impl Into<u32>, - ) -> Result<Data, TcpTargetError> - where - Data: serde::de::DeserializeOwned, - { - let chunk_size = chunk_size.into() as usize; - - // Read total length first - let mut len_buf = [0u8; 4]; - self.stream.read_exact(&mut len_buf).await?; - let total_len = u32::from_be_bytes(len_buf) as usize; - - // Read data in chunks - let mut buffer = Vec::with_capacity(total_len); - let mut remaining = total_len; - let mut chunk_buf = vec![0; chunk_size]; - - while remaining > 0 { - let read_size = std::cmp::min(chunk_size, remaining); - let chunk = &mut chunk_buf[..read_size]; - - match self.stream.read_exact(chunk).await { - Ok(_) => { - buffer.extend_from_slice(chunk); - remaining -= read_size; - } - Err(err) => return Err(TcpTargetError::Io(err.to_string())), - } - } - - let data = rmp_serde::from_slice(&buffer)?; - Ok(data) - } - - /// Write file to target machine. - pub async fn write_file(&mut self, file_path: impl AsRef<Path>) -> Result<(), TcpTargetError> { - let path = file_path.as_ref(); - - // Validate file - if !path.exists() { - return Err(TcpTargetError::File(format!( - "File not found: {}", - path.display() - ))); - } - if path.is_dir() { - return Err(TcpTargetError::File(format!( - "Path is directory: {}", - path.display() - ))); - } - - // Open file and get metadata - let mut file = File::open(path).await?; - let file_size = file.metadata().await?.len(); - - // Send file header (version + size + crc) - self.stream.write_all(&1u64.to_be_bytes()).await?; - self.stream.write_all(&file_size.to_be_bytes()).await?; - - // Calculate and send CRC32 if enabled - let file_crc = if self.config.enable_crc_validation { - let crc32 = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC); - let mut crc_calculator = crc32.digest(); - - let mut temp_reader = - BufReader::with_capacity(self.config.chunk_size, File::open(path).await?); - let mut temp_buffer = vec![0u8; self.config.chunk_size]; - let mut temp_bytes_read = 0; - - while temp_bytes_read < file_size { - let bytes_to_read = - (file_size - temp_bytes_read).min(self.config.chunk_size as u64) as usize; - temp_reader - .read_exact(&mut temp_buffer[..bytes_to_read]) - .await?; - crc_calculator.update(&temp_buffer[..bytes_to_read]); - temp_bytes_read += bytes_to_read as u64; - } - - crc_calculator.finalize() - } else { - 0 - }; - - self.stream.write_all(&file_crc.to_be_bytes()).await?; - - // If file size is 0, skip content transfer - if file_size == 0 { - self.stream.flush().await?; - - // Wait for receiver confirmation - let mut ack = [0u8; 1]; - tokio::time::timeout( - Duration::from_secs(self.config.timeout_secs), - self.stream.read_exact(&mut ack), - ) - .await - .map_err(|_| TcpTargetError::Timeout("Ack timeout".to_string()))??; - - if ack[0] != 1 { - return Err(TcpTargetError::Protocol( - "Receiver verification failed".to_string(), - )); - } - - return Ok(()); - } - - // Transfer file content - let mut reader = BufReader::with_capacity(self.config.chunk_size, &mut file); - let mut bytes_sent = 0; - - while bytes_sent < file_size { - let buffer = reader.fill_buf().await?; - if buffer.is_empty() { - break; - } - - let chunk_size = buffer.len().min((file_size - bytes_sent) as usize); - self.stream.write_all(&buffer[..chunk_size]).await?; - reader.consume(chunk_size); - - bytes_sent += chunk_size as u64; - } - - // Verify transfer completion - if bytes_sent != file_size { - return Err(TcpTargetError::File(format!( - "Transfer incomplete: expected {} bytes, sent {} bytes", - file_size, bytes_sent - ))); - } - - self.stream.flush().await?; - - // Wait for receiver confirmation - let mut ack = [0u8; 1]; - tokio::time::timeout( - Duration::from_secs(self.config.timeout_secs), - self.stream.read_exact(&mut ack), - ) - .await - .map_err(|_| TcpTargetError::Timeout("Ack timeout".to_string()))??; - - if ack[0] != 1 { - return Err(TcpTargetError::Protocol( - "Receiver verification failed".to_string(), - )); - } - - Ok(()) - } - - /// Read file from target machine - pub async fn read_file(&mut self, save_path: impl AsRef<Path>) -> Result<(), TcpTargetError> { - let path = save_path.as_ref(); - // Create CRC instance at function scope to ensure proper lifetime - let crc_instance = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC); - - // Make sure parent directory exists - if let Some(parent) = path.parent() - && !parent.exists() - { - tokio::fs::create_dir_all(parent).await?; - } - - // Read file header (version + size + crc) - let mut version_buf = [0u8; 8]; - self.stream.read_exact(&mut version_buf).await?; - let version = u64::from_be_bytes(version_buf); - if version != 1 { - return Err(TcpTargetError::Protocol( - "Unsupported transfer version".to_string(), - )); - } - - let mut size_buf = [0u8; 8]; - self.stream.read_exact(&mut size_buf).await?; - let file_size = u64::from_be_bytes(size_buf); - - let mut expected_crc_buf = [0u8; 4]; - self.stream.read_exact(&mut expected_crc_buf).await?; - let expected_crc = u32::from_be_bytes(expected_crc_buf); - if file_size == 0 { - // Create empty file and return early - let _file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(path) - .await?; - // Send confirmation - self.stream.write_all(&[1u8]).await?; - self.stream.flush().await?; - return Ok(()); - } - - // Prepare output file - let file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(path) - .await?; - let mut writer = BufWriter::with_capacity(self.config.chunk_size, file); - - // Receive file content with CRC calculation if enabled - let mut bytes_received = 0; - let mut buffer = vec![0u8; self.config.chunk_size]; - let mut crc_calculator = if self.config.enable_crc_validation { - Some(crc_instance.digest()) - } else { - None - }; - - while bytes_received < file_size { - let bytes_to_read = - (file_size - bytes_received).min(self.config.chunk_size as u64) as usize; - let chunk = &mut buffer[..bytes_to_read]; - - self.stream.read_exact(chunk).await?; - - writer.write_all(chunk).await?; - - // Update CRC if validation is enabled - if let Some(ref mut crc) = crc_calculator { - crc.update(chunk); - } - - bytes_received += bytes_to_read as u64; - } - - // Verify transfer completion - if bytes_received != file_size { - return Err(TcpTargetError::File(format!( - "Transfer incomplete: expected {} bytes, received {} bytes", - file_size, bytes_received - ))); - } - - writer.flush().await?; - - // Validate CRC if enabled - if self.config.enable_crc_validation - && let Some(crc_calculator) = crc_calculator - { - let actual_crc = crc_calculator.finalize(); - if actual_crc != expected_crc && expected_crc != 0 { - return Err(TcpTargetError::File(format!( - "CRC validation failed: expected {:08x}, got {:08x}", - expected_crc, actual_crc - ))); - } - } - - // Final flush and sync - writer.flush().await?; - writer.into_inner().sync_all().await?; - - // Verify completion - if bytes_received != file_size { - let _ = tokio::fs::remove_file(path).await; - return Err(TcpTargetError::File(format!( - "Transfer incomplete: expected {} bytes, received {} bytes", - file_size, bytes_received - ))); - } - - // Send confirmation - self.stream.write_all(&[1u8]).await?; - self.stream.flush().await?; - - Ok(()) - } -} diff --git a/crates/utils/tcp_connection/src/instance_challenge.rs b/crates/utils/tcp_connection/src/instance_challenge.rs deleted file mode 100644 index 3a7f6a3..0000000 --- a/crates/utils/tcp_connection/src/instance_challenge.rs +++ /dev/null @@ -1,311 +0,0 @@ -use std::path::Path; - -use rand::TryRngCore; -use rsa::{ - RsaPrivateKey, RsaPublicKey, - pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey}, - sha2, -}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; -use ring::rand::SystemRandom; -use ring::signature::{ - self, ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, EcdsaKeyPair, RSA_PKCS1_2048_8192_SHA256, - UnparsedPublicKey, -}; - -use crate::{error::TcpTargetError, instance::ConnectionInstance}; - -const ECDSA_P256_SHA256_ASN1_SIGNING: &signature::EcdsaSigningAlgorithm = - &signature::ECDSA_P256_SHA256_ASN1_SIGNING; -const ECDSA_P384_SHA384_ASN1_SIGNING: &signature::EcdsaSigningAlgorithm = - &signature::ECDSA_P384_SHA384_ASN1_SIGNING; - -impl ConnectionInstance { - /// Initiates a challenge to the target machine to verify connection security - /// - /// This method performs a cryptographic challenge-response authentication: - /// 1. Generates a random 32-byte challenge - /// 2. Sends the challenge to the target machine - /// 3. Receives a digital signature of the challenge - /// 4. Verifies the signature using the appropriate public key - /// - /// # Arguments - /// * `public_key_dir` - Directory containing public key files for verification - /// - /// # Returns - /// * `Ok((true, "KeyId"))` - Challenge verification successful - /// * `Ok((false, "KeyId"))` - Challenge verification failed - /// * `Err(TcpTargetError)` - Error during challenge process - pub async fn challenge( - &mut self, - public_key_dir: impl AsRef<Path>, - ) -> Result<(bool, String), TcpTargetError> { - // Generate random challenge - let mut challenge = [0u8; 32]; - rand::rngs::OsRng - .try_fill_bytes(&mut challenge) - .map_err(|e| { - TcpTargetError::Crypto(format!("Failed to generate random challenge: {}", e)) - })?; - - // Send challenge to target - self.stream.write_all(&challenge).await?; - self.stream.flush().await?; - - // Read signature from target - let mut signature = Vec::new(); - let mut signature_len_buf = [0u8; 4]; - self.stream.read_exact(&mut signature_len_buf).await?; - - let signature_len = u32::from_be_bytes(signature_len_buf) as usize; - signature.resize(signature_len, 0); - self.stream.read_exact(&mut signature).await?; - - // Read key identifier from target to identify which public key to use - let mut key_id_len_buf = [0u8; 4]; - self.stream.read_exact(&mut key_id_len_buf).await?; - let key_id_len = u32::from_be_bytes(key_id_len_buf) as usize; - - let mut key_id_buf = vec![0u8; key_id_len]; - self.stream.read_exact(&mut key_id_buf).await?; - let key_id = String::from_utf8(key_id_buf) - .map_err(|e| TcpTargetError::Crypto(format!("Invalid key identifier: {}", e)))?; - - // Load appropriate public key - let public_key_path = public_key_dir.as_ref().join(format!("{}.pem", key_id)); - if !public_key_path.exists() { - return Ok((false, key_id)); - } - - let public_key_pem = tokio::fs::read_to_string(&public_key_path).await?; - - // Try to verify with different key types - let verified = if let Ok(rsa_key) = RsaPublicKey::from_pkcs1_pem(&public_key_pem) { - let padding = rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>(); - rsa_key.verify(padding, &challenge, &signature).is_ok() - } else if let Ok(ed25519_key) = - VerifyingKey::from_bytes(&parse_ed25519_public_key(&public_key_pem)) - { - if signature.len() == 64 { - let sig_bytes: [u8; 64] = signature.as_slice().try_into().map_err(|_| { - TcpTargetError::Crypto("Invalid signature length for Ed25519".to_string()) - })?; - let sig = Signature::from_bytes(&sig_bytes); - ed25519_key.verify(&challenge, &sig).is_ok() - } else { - false - } - } else if let Ok(dsa_key_info) = parse_dsa_public_key(&public_key_pem) { - verify_dsa_signature(&dsa_key_info, &challenge, &signature) - } else { - false - }; - - Ok((verified, key_id)) - } - - /// Accepts a challenge from the target machine to verify connection security - /// - /// This method performs a cryptographic challenge-response authentication: - /// 1. Receives a random 32-byte challenge from the target machine - /// 2. Signs the challenge using the appropriate private key - /// 3. Sends the digital signature back to the target machine - /// 4. Sends the key identifier for public key verification - /// - /// # Arguments - /// * `private_key_file` - Path to the private key file for signing - /// * `verify_public_key` - Key identifier for public key verification - /// - /// # Returns - /// * `Ok(true)` - Challenge response sent successfully - /// * `Ok(false)` - Private key format not supported - /// * `Err(TcpTargetError)` - Error during challenge response process - pub async fn accept_challenge( - &mut self, - private_key_file: impl AsRef<Path>, - verify_public_key: &str, - ) -> Result<bool, TcpTargetError> { - // Read challenge from initiator - let mut challenge = [0u8; 32]; - self.stream.read_exact(&mut challenge).await?; - - // Load private key - let private_key_pem = tokio::fs::read_to_string(&private_key_file) - .await - .map_err(|e| { - TcpTargetError::NotFound(format!( - "Read private key \"{}\" failed: \"{}\"", - private_key_file - .as_ref() - .display() - .to_string() - .split("/") - .last() - .unwrap_or("UNKNOWN"), - e - )) - })?; - - // Sign the challenge with supported key types - let signature = if let Ok(rsa_key) = RsaPrivateKey::from_pkcs1_pem(&private_key_pem) { - let padding = rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>(); - rsa_key.sign(padding, &challenge)? - } else if let Ok(ed25519_key) = parse_ed25519_private_key(&private_key_pem) { - ed25519_key.sign(&challenge).to_bytes().to_vec() - } else if let Ok(dsa_key_info) = parse_dsa_private_key(&private_key_pem) { - sign_with_dsa(&dsa_key_info, &challenge)? - } else { - return Ok(false); - }; - - // Send signature length and signature - let signature_len = signature.len() as u32; - self.stream.write_all(&signature_len.to_be_bytes()).await?; - self.stream.flush().await?; - self.stream.write_all(&signature).await?; - self.stream.flush().await?; - - // Send key identifier for public key identification - let key_id_bytes = verify_public_key.as_bytes(); - let key_id_len = key_id_bytes.len() as u32; - self.stream.write_all(&key_id_len.to_be_bytes()).await?; - self.stream.flush().await?; - self.stream.write_all(key_id_bytes).await?; - self.stream.flush().await?; - - Ok(true) - } -} - -/// Parse Ed25519 public key from PEM format -fn parse_ed25519_public_key(pem: &str) -> [u8; 32] { - // Robust parsing for Ed25519 public key using pem crate - let mut key_bytes = [0u8; 32]; - - if let Ok(pem_data) = pem::parse(pem) - && pem_data.tag() == "PUBLIC KEY" - && pem_data.contents().len() >= 32 - { - let contents = pem_data.contents(); - key_bytes.copy_from_slice(&contents[contents.len() - 32..]); - } - key_bytes -} - -/// Parse Ed25519 private key from PEM format -fn parse_ed25519_private_key(pem: &str) -> Result<SigningKey, TcpTargetError> { - if let Ok(pem_data) = pem::parse(pem) - && pem_data.tag() == "PRIVATE KEY" - && pem_data.contents().len() >= 32 - { - let contents = pem_data.contents(); - let mut seed = [0u8; 32]; - seed.copy_from_slice(&contents[contents.len() - 32..]); - return Ok(SigningKey::from_bytes(&seed)); - } - Err(TcpTargetError::Crypto( - "Invalid Ed25519 private key format".to_string(), - )) -} - -/// Parse DSA public key information from PEM -fn parse_dsa_public_key( - pem: &str, -) -> Result<(&'static dyn signature::VerificationAlgorithm, Vec<u8>), TcpTargetError> { - if let Ok(pem_data) = pem::parse(pem) { - let contents = pem_data.contents().to_vec(); - - // Try different DSA algorithms based on PEM tag - match pem_data.tag() { - "EC PUBLIC KEY" | "PUBLIC KEY" if pem.contains("ECDSA") || pem.contains("ecdsa") => { - if pem.contains("P-256") { - return Ok((&ECDSA_P256_SHA256_ASN1, contents)); - } else if pem.contains("P-384") { - return Ok((&ECDSA_P384_SHA384_ASN1, contents)); - } - } - "RSA PUBLIC KEY" | "PUBLIC KEY" => { - return Ok((&RSA_PKCS1_2048_8192_SHA256, contents)); - } - _ => {} - } - - // Default to RSA for unknown types - return Ok((&RSA_PKCS1_2048_8192_SHA256, contents)); - } - Err(TcpTargetError::Crypto( - "Invalid DSA public key format".to_string(), - )) -} - -/// Parse DSA private key information from PEM -fn parse_dsa_private_key( - pem: &str, -) -> Result<(&'static dyn signature::VerificationAlgorithm, Vec<u8>), TcpTargetError> { - // For DSA, private key verification uses the same algorithm as public key - parse_dsa_public_key(pem) -} - -/// Verify DSA signature -fn verify_dsa_signature( - algorithm_and_key: &(&'static dyn signature::VerificationAlgorithm, Vec<u8>), - message: &[u8], - signature: &[u8], -) -> bool { - let (algorithm, key_bytes) = algorithm_and_key; - let public_key = UnparsedPublicKey::new(*algorithm, key_bytes); - public_key.verify(message, signature).is_ok() -} - -/// Sign with DSA -fn sign_with_dsa( - algorithm_and_key: &(&'static dyn signature::VerificationAlgorithm, Vec<u8>), - message: &[u8], -) -> Result<Vec<u8>, TcpTargetError> { - let (algorithm, key_bytes) = algorithm_and_key; - - // Handle different DSA/ECDSA algorithms by comparing algorithm identifiers - // Since we can't directly compare trait objects, we use pointer comparison - let algorithm_ptr = algorithm as *const _ as *const (); - let ecdsa_p256_ptr = &ECDSA_P256_SHA256_ASN1 as *const _ as *const (); - let ecdsa_p384_ptr = &ECDSA_P384_SHA384_ASN1 as *const _ as *const (); - - if algorithm_ptr == ecdsa_p256_ptr { - let key_pair = EcdsaKeyPair::from_pkcs8( - ECDSA_P256_SHA256_ASN1_SIGNING, - key_bytes, - &SystemRandom::new(), - ) - .map_err(|e| { - TcpTargetError::Crypto(format!("Failed to create ECDSA P-256 key pair: {}", e)) - })?; - - let signature = key_pair - .sign(&SystemRandom::new(), message) - .map_err(|e| TcpTargetError::Crypto(format!("ECDSA P-256 signing failed: {}", e)))?; - - Ok(signature.as_ref().to_vec()) - } else if algorithm_ptr == ecdsa_p384_ptr { - let key_pair = EcdsaKeyPair::from_pkcs8( - ECDSA_P384_SHA384_ASN1_SIGNING, - key_bytes, - &SystemRandom::new(), - ) - .map_err(|e| { - TcpTargetError::Crypto(format!("Failed to create ECDSA P-384 key pair: {}", e)) - })?; - - let signature = key_pair - .sign(&SystemRandom::new(), message) - .map_err(|e| TcpTargetError::Crypto(format!("ECDSA P-384 signing failed: {}", e)))?; - - Ok(signature.as_ref().to_vec()) - } else { - // RSA or unsupported algorithm - Err(TcpTargetError::Unsupported( - "DSA/ECDSA signing not supported for this algorithm type".to_string(), - )) - } -} diff --git a/crates/utils/tcp_connection/src/lib.rs b/crates/utils/tcp_connection/src/lib.rs deleted file mode 100644 index 6a2e599..0000000 --- a/crates/utils/tcp_connection/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[allow(dead_code)] -pub mod instance; - -pub mod instance_challenge; - -pub mod error; diff --git a/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml b/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml deleted file mode 100644 index 19a6e9b..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "tcp_connection_test" -edition = "2024" -version.workspace = true - -[dependencies] -tcp_connection = { path = "../../tcp_connection" } -tokio = { version = "1.48.0", features = ["full"] } -serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/utils/tcp_connection/tcp_connection_test/res/image/test_transfer.png b/crates/utils/tcp_connection/tcp_connection_test/res/image/test_transfer.png Binary files differdeleted file mode 100644 index 5fa94f0..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/res/image/test_transfer.png +++ /dev/null diff --git a/crates/utils/tcp_connection/tcp_connection_test/res/key/test_key.pem b/crates/utils/tcp_connection/tcp_connection_test/res/key/test_key.pem deleted file mode 100644 index e155876..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/res/key/test_key.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN RSA PUBLIC KEY----- -MIICCgKCAgEAl5vyIwGYiQ1zZpW2tg+LwOUV547T2SjlzKQjcms5je/epP4CnUfT -5cmHCe8ZaSbnofcntCzi8FzMpQmzhNzFk5tCAe4tSrghfr2kYDO7aUL0G09KbNZ5 -iuMTkMaHx6LMjZ+Ljy8fC47yC2dFMUgLjGS7xS6rnIo4YtFuvMdwbLjs7mSn+vVc -kcEV8RLlQg8wDbzpl66Jd1kiUgPfVLBRTLE/iL8kUCz1l8c+DvOzr3ATwJysM9CG -LFahGLlTd3CZaj0QsEzf/AQsn79Su+rnCXhXqcvynhAcil0UW9RWp5Zsvp3Me3W8 -pJg6vZuAA6lQ062hkRLiJ91F2rpyqtkax5i/simLjelpsRzLKo6Xsz1bZht2+5d5 -ArgTBtZBxS044t8caZWLXetnPEcxEGz8KYUVKf7X9S7R53gy36y88Fbu9giqUr3m -b3Da+SYzBT//hacGn55nhzLRdsJGaFFWcKCbpue6JHLsFhizhdEAjaec0hfphw29 -veY0adPdIFLQDmMKaNk4ulrz8Lbgpqn9gxx6fRssj9jqNJmW64a0eV+Rw7BCJazH -xp3zz4A3rwdI8BjxLUb3YiCUcavA9WzJ1DUfdX1FSvbcFw4CEiGJjfpWGrm1jtc6 -DMOsoX/C6yFOyRpipsgqIToBClchLSNgrO6A7SIoSdIqNDEgIanFcjECAwEAAQ== ------END RSA PUBLIC KEY----- diff --git a/crates/utils/tcp_connection/tcp_connection_test/res/key/test_key_private.pem b/crates/utils/tcp_connection/tcp_connection_test/res/key/test_key_private.pem deleted file mode 100644 index 183d2d9..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/res/key/test_key_private.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -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 ------END RSA PRIVATE KEY----- diff --git a/crates/utils/tcp_connection/tcp_connection_test/res/key/wrong_key_private.pem b/crates/utils/tcp_connection/tcp_connection_test/res/key/wrong_key_private.pem deleted file mode 100644 index 4b77eea..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/res/key/wrong_key_private.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -f9qBWGCwdZjeDjaVVUgwi+Waf+M0tQ== ------END PRIVATE KEY----- diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs b/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs deleted file mode 100644 index c9372d4..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[cfg(test)] -pub mod test_tcp_target_build; - -#[cfg(test)] -pub mod test_connection; - -#[cfg(test)] -pub mod test_challenge; - -#[cfg(test)] -pub mod test_file_transfer; - -#[cfg(test)] -pub mod test_msgpack; - -pub mod test_utils; -pub use test_utils::*; diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_challenge.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_challenge.rs deleted file mode 100644 index 9327b3e..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_challenge.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{env::current_dir, time::Duration}; - -use tcp_connection::instance::ConnectionInstance; -use tokio::{ - join, - time::{sleep, timeout}, -}; - -use crate::test_utils::{ - handle::{ClientHandle, ServerHandle}, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; - -pub(crate) struct ExampleChallengeClientHandle; - -impl ClientHandle<ExampleChallengeServerHandle> for ExampleChallengeClientHandle { - async fn process(mut instance: ConnectionInstance) { - // Accept challenge with correct key - let key = current_dir() - .unwrap() - .join("res") - .join("key") - .join("test_key_private.pem"); - let result = instance.accept_challenge(key, "test_key").await.unwrap(); - - // Sent success - assert!(result); - let response = instance.read_text().await.unwrap(); - - // Verify success - assert_eq!("OK", response); - - // Accept challenge with wrong key - let key = current_dir() - .unwrap() - .join("res") - .join("key") - .join("wrong_key_private.pem"); - let result = instance.accept_challenge(key, "test_key").await.unwrap(); - - // Sent success - assert!(result); - let response = instance.read_text().await.unwrap(); - - // Verify fail - assert_eq!("ERROR", response); - - // Accept challenge with wrong name - let key = current_dir() - .unwrap() - .join("res") - .join("key") - .join("test_key_private.pem"); - let result = instance.accept_challenge(key, "test_key__").await.unwrap(); - - // Sent success - assert!(result); - let response = instance.read_text().await.unwrap(); - - // Verify fail - assert_eq!("ERROR", response); - } -} - -pub(crate) struct ExampleChallengeServerHandle; - -impl ServerHandle<ExampleChallengeClientHandle> for ExampleChallengeServerHandle { - async fn process(mut instance: ConnectionInstance) { - // Challenge with correct key - let key_dir = current_dir().unwrap().join("res").join("key"); - let (result, key_id) = instance.challenge(key_dir).await.unwrap(); - assert!(result); - assert_eq!(key_id, "test_key"); - - // Send response - instance - .write_text(if result { "OK" } else { "ERROR" }) - .await - .unwrap(); - - // Challenge again - let key_dir = current_dir().unwrap().join("res").join("key"); - let (result, key_id) = instance.challenge(key_dir).await.unwrap(); - assert!(!result); - assert_eq!(key_id, "test_key"); - - // Send response - instance - .write_text(if result { "OK" } else { "ERROR" }) - .await - .unwrap(); - - // Challenge again - let key_dir = current_dir().unwrap().join("res").join("key"); - let (result, key_id) = instance.challenge(key_dir).await.unwrap(); - assert!(!result); - assert_eq!(key_id, "test_key__"); - - // Send response - instance - .write_text(if result { "OK" } else { "ERROR" }) - .await - .unwrap(); - } -} - -#[tokio::test] -async fn test_connection_with_challenge_handle() -> Result<(), std::io::Error> { - let host = "localhost:5011"; - - // Server setup - let Ok(server_target) = TcpServerTarget::< - ExampleChallengeClientHandle, - ExampleChallengeServerHandle, - >::from_domain(host) - .await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - // Client setup - let Ok(client_target) = TcpServerTarget::< - ExampleChallengeClientHandle, - ExampleChallengeServerHandle, - >::from_domain(host) - .await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - let future_server = async move { - // Only process once - let configured_server = server_target.server_cfg(ServerTargetConfig::default().once()); - - // Listen here - let _ = configured_server.listen().await; - }; - - let future_client = async move { - // Wait for server start - let _ = sleep(Duration::from_secs_f32(1.5)).await; - - // Connect here - let _ = client_target.connect().await; - }; - - let test_timeout = Duration::from_secs(10); - - timeout(test_timeout, async { join!(future_client, future_server) }) - .await - .map_err(|_| { - std::io::Error::new( - std::io::ErrorKind::TimedOut, - format!("Test timed out after {:?}", test_timeout), - ) - })?; - - Ok(()) -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_connection.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_connection.rs deleted file mode 100644 index 8c3ab01..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_connection.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::time::Duration; - -use tcp_connection::instance::ConnectionInstance; -use tokio::{join, time::sleep}; - -use crate::test_utils::{ - handle::{ClientHandle, ServerHandle}, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; - -pub(crate) struct ExampleClientHandle; - -impl ClientHandle<ExampleServerHandle> for ExampleClientHandle { - async fn process(mut instance: ConnectionInstance) { - // Write name - let Ok(_) = instance.write_text("Peter").await else { - panic!("Write text failed!"); - }; - // Read msg - let Ok(result) = instance.read_text().await else { - return; - }; - assert_eq!("Hello Peter!", result); - } -} - -pub(crate) struct ExampleServerHandle; - -impl ServerHandle<ExampleClientHandle> for ExampleServerHandle { - async fn process(mut instance: ConnectionInstance) { - // Read name - let Ok(name) = instance.read_text().await else { - return; - }; - // Write msg - let Ok(_) = instance.write_text(format!("Hello {}!", name)).await else { - panic!("Write text failed!"); - }; - } -} - -#[tokio::test] -async fn test_connection_with_example_handle() { - let host = "localhost:5012"; - - // Server setup - let Ok(server_target) = - TcpServerTarget::<ExampleClientHandle, ExampleServerHandle>::from_domain(host).await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - // Client setup - let Ok(client_target) = - TcpServerTarget::<ExampleClientHandle, ExampleServerHandle>::from_domain(host).await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - let future_server = async move { - // Only process once - let configured_server = server_target.server_cfg(ServerTargetConfig::default().once()); - - // Listen here - let _ = configured_server.listen().await; - }; - - let future_client = async move { - // Wait for server start - let _ = sleep(Duration::from_secs_f32(1.5)).await; - - // Connect here - let _ = client_target.connect().await; - }; - - let _ = async { join!(future_client, future_server) }.await; -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_file_transfer.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_file_transfer.rs deleted file mode 100644 index 4237ea7..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_file_transfer.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::{env::current_dir, time::Duration}; - -use tcp_connection::instance::ConnectionInstance; -use tokio::{ - join, - time::{sleep, timeout}, -}; - -use crate::test_utils::{ - handle::{ClientHandle, ServerHandle}, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; - -pub(crate) struct ExampleFileTransferClientHandle; - -impl ClientHandle<ExampleFileTransferServerHandle> for ExampleFileTransferClientHandle { - async fn process(mut instance: ConnectionInstance) { - let image_path = current_dir() - .unwrap() - .join("res") - .join("image") - .join("test_transfer.png"); - instance.write_file(image_path).await.unwrap(); - } -} - -pub(crate) struct ExampleFileTransferServerHandle; - -impl ServerHandle<ExampleFileTransferClientHandle> for ExampleFileTransferServerHandle { - async fn process(mut instance: ConnectionInstance) { - let save_path = current_dir() - .unwrap() - .join("res") - .join(".temp") - .join("image") - .join("test_transfer.png"); - instance.read_file(save_path).await.unwrap(); - } -} - -#[tokio::test] -async fn test_connection_with_challenge_handle() -> Result<(), std::io::Error> { - let host = "localhost:5010"; - - // Server setup - let Ok(server_target) = TcpServerTarget::< - ExampleFileTransferClientHandle, - ExampleFileTransferServerHandle, - >::from_domain(host) - .await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - // Client setup - let Ok(client_target) = TcpServerTarget::< - ExampleFileTransferClientHandle, - ExampleFileTransferServerHandle, - >::from_domain(host) - .await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - let future_server = async move { - // Only process once - let configured_server = server_target.server_cfg(ServerTargetConfig::default().once()); - - // Listen here - let _ = configured_server.listen().await; - }; - - let future_client = async move { - // Wait for server start - let _ = sleep(Duration::from_secs_f32(1.5)).await; - - // Connect here - let _ = client_target.connect().await; - }; - - let test_timeout = Duration::from_secs(10); - - timeout(test_timeout, async { join!(future_client, future_server) }) - .await - .map_err(|_| { - std::io::Error::new( - std::io::ErrorKind::TimedOut, - format!("Test timed out after {:?}", test_timeout), - ) - })?; - - Ok(()) -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs deleted file mode 100644 index 4c9c870..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs +++ /dev/null @@ -1,103 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::time::Duration; -use tcp_connection::instance::ConnectionInstance; -use tokio::{join, time::sleep}; - -use crate::test_utils::{ - handle::{ClientHandle, ServerHandle}, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; - -#[derive(Debug, PartialEq, Serialize, Deserialize, Default)] -struct TestData { - id: u32, - name: String, -} - -pub(crate) struct MsgPackClientHandle; - -impl ClientHandle<MsgPackServerHandle> for MsgPackClientHandle { - async fn process(mut instance: ConnectionInstance) { - // Test basic MessagePack serialization - let test_data = TestData { - id: 42, - name: "Test MessagePack".to_string(), - }; - - // Write MessagePack data - if let Err(e) = instance.write_msgpack(&test_data).await { - panic!("Write MessagePack failed: {}", e); - } - - // Read response - let response: TestData = match instance.read_msgpack().await { - Ok(data) => data, - Err(e) => panic!("Read MessagePack response failed: {}", e), - }; - - // Verify response - assert_eq!(response.id, test_data.id * 2); - assert_eq!(response.name, format!("Processed: {}", test_data.name)); - } -} - -pub(crate) struct MsgPackServerHandle; - -impl ServerHandle<MsgPackClientHandle> for MsgPackServerHandle { - async fn process(mut instance: ConnectionInstance) { - // Read MessagePack data - let received_data: TestData = match instance.read_msgpack().await { - Ok(data) => data, - Err(_) => return, - }; - - // Process data - let response = TestData { - id: received_data.id * 2, - name: format!("Processed: {}", received_data.name), - }; - - // Write response as MessagePack - if let Err(e) = instance.write_msgpack(&response).await { - panic!("Write MessagePack response failed: {}", e); - } - } -} - -#[tokio::test] -async fn test_msgpack_basic() { - let host = "localhost:5013"; - - // Server setup - let Ok(server_target) = - TcpServerTarget::<MsgPackClientHandle, MsgPackServerHandle>::from_domain(host).await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - // Client setup - let Ok(client_target) = - TcpServerTarget::<MsgPackClientHandle, MsgPackServerHandle>::from_domain(host).await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - let future_server = async move { - // Only process once - let configured_server = server_target.server_cfg(ServerTargetConfig::default().once()); - - // Listen here - let _ = configured_server.listen().await; - }; - - let future_client = async move { - // Wait for server start - let _ = sleep(Duration::from_secs_f32(1.5)).await; - - // Connect here - let _ = client_target.connect().await; - }; - - let _ = async { join!(future_client, future_server) }.await; -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_tcp_target_build.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_tcp_target_build.rs deleted file mode 100644 index aa1ec74..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_tcp_target_build.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::{ - test_connection::{ExampleClientHandle, ExampleServerHandle}, - test_utils::target::TcpServerTarget, -}; - -#[test] -fn test_tcp_test_target_build() { - let host = "127.0.0.1:8080"; - - // Test build target by string - let Ok(target) = - TcpServerTarget::<ExampleClientHandle, ExampleServerHandle>::from_address_str(host) - else { - panic!("Test target built failed from a target addr `{}`", host); - }; - assert_eq!(target.to_string(), "127.0.0.1:8080"); -} - -#[tokio::test] -async fn test_tcp_test_target_build_domain() { - let host = "localhost"; - - // Test build target by DomainName and Connection - let Ok(target) = - TcpServerTarget::<ExampleClientHandle, ExampleServerHandle>::from_domain(host).await - else { - panic!("Test target built failed from a domain named `{}`", host); - }; - - // Test into string - assert_eq!(target.to_string(), "127.0.0.1:8080"); -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils.rs deleted file mode 100644 index badf27d..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod handle; -pub mod target; -pub mod target_configure; -pub mod target_connection; diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/handle.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/handle.rs deleted file mode 100644 index 4f9bdbb..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/handle.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::future::Future; - -use tcp_connection::instance::ConnectionInstance; - -pub trait ClientHandle<RequestServer> { - fn process(instance: ConnectionInstance) -> impl Future<Output = ()> + Send; -} - -pub trait ServerHandle<RequestClient> { - fn process(instance: ConnectionInstance) -> impl Future<Output = ()> + Send; -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target.rs deleted file mode 100644 index 8972b2a..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target.rs +++ /dev/null @@ -1,201 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{ - fmt::{Display, Formatter}, - marker::PhantomData, - net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr}, - str::FromStr, -}; -use tokio::net::lookup_host; - -use crate::test_utils::{ - handle::{ClientHandle, ServerHandle}, - target_configure::{ClientTargetConfig, ServerTargetConfig}, -}; - -const DEFAULT_PORT: u16 = 8080; - -#[derive(Debug, Serialize, Deserialize)] -pub struct TcpServerTarget<Client, Server> -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - /// Client Config - client_cfg: Option<ClientTargetConfig>, - - /// Server Config - server_cfg: Option<ServerTargetConfig>, - - /// Server port - port: u16, - - /// Bind addr - bind_addr: IpAddr, - - /// Client Phantom Data - _client: PhantomData<Client>, - - /// Server Phantom Data - _server: PhantomData<Server>, -} - -impl<Client, Server> Default for TcpServerTarget<Client, Server> -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - fn default() -> Self { - Self { - client_cfg: None, - server_cfg: None, - port: DEFAULT_PORT, - bind_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - _client: PhantomData, - _server: PhantomData, - } - } -} - -impl<Client, Server> From<SocketAddr> for TcpServerTarget<Client, Server> -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - /// Convert SocketAddr to TcpServerTarget - fn from(value: SocketAddr) -> Self { - Self { - port: value.port(), - bind_addr: value.ip(), - ..Self::default() - } - } -} - -impl<Client, Server> From<TcpServerTarget<Client, Server>> for SocketAddr -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - /// Convert TcpServerTarget to SocketAddr - fn from(val: TcpServerTarget<Client, Server>) -> Self { - SocketAddr::new(val.bind_addr, val.port) - } -} - -impl<Client, Server> Display for TcpServerTarget<Client, Server> -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.bind_addr, self.port) - } -} - -impl<Client, Server> TcpServerTarget<Client, Server> -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - /// Create target by address - pub fn from_addr(addr: impl Into<IpAddr>, port: impl Into<u16>) -> Self { - Self { - port: port.into(), - bind_addr: addr.into(), - ..Self::default() - } - } - - /// Try to create target by string - pub fn from_address_str<'a>(addr_str: impl Into<&'a str>) -> Result<Self, AddrParseError> { - let socket_addr = SocketAddr::from_str(addr_str.into()); - match socket_addr { - Ok(socket_addr) => Ok(Self::from_addr(socket_addr.ip(), socket_addr.port())), - Err(err) => Err(err), - } - } - - /// Try to create target by domain name - pub async fn from_domain<'a>(domain: impl Into<&'a str>) -> Result<Self, std::io::Error> { - match domain_to_addr(domain).await { - Ok(domain_addr) => Ok(Self::from(domain_addr)), - Err(e) => Err(e), - } - } - - /// Set client config - pub fn client_cfg(mut self, config: ClientTargetConfig) -> Self { - self.client_cfg = Some(config); - self - } - - /// Set server config - pub fn server_cfg(mut self, config: ServerTargetConfig) -> Self { - self.server_cfg = Some(config); - self - } - - /// Add client config - pub fn add_client_cfg(&mut self, config: ClientTargetConfig) { - self.client_cfg = Some(config); - } - - /// Add server config - pub fn add_server_cfg(&mut self, config: ServerTargetConfig) { - self.server_cfg = Some(config); - } - - /// Get client config ref - pub fn get_client_cfg(&self) -> Option<&ClientTargetConfig> { - self.client_cfg.as_ref() - } - - /// Get server config ref - pub fn get_server_cfg(&self) -> Option<&ServerTargetConfig> { - self.server_cfg.as_ref() - } - - /// Get SocketAddr of TcpServerTarget - pub fn get_addr(&self) -> SocketAddr { - SocketAddr::new(self.bind_addr, self.port) - } -} - -/// Parse Domain Name to IpAddr via DNS -async fn domain_to_addr<'a>(domain: impl Into<&'a str>) -> Result<SocketAddr, std::io::Error> { - let domain = domain.into(); - let default_port: u16 = DEFAULT_PORT; - - if let Ok(socket_addr) = domain.parse::<SocketAddr>() { - return Ok(match socket_addr.ip() { - IpAddr::V4(_) => socket_addr, - IpAddr::V6(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), socket_addr.port()), - }); - } - - if let Ok(_v6_addr) = domain.parse::<std::net::Ipv6Addr>() { - return Ok(SocketAddr::new( - IpAddr::V4(Ipv4Addr::LOCALHOST), - default_port, - )); - } - - let (host, port_str) = if let Some((host, port)) = domain.rsplit_once(':') { - (host.trim_matches(|c| c == '[' || c == ']'), Some(port)) - } else { - (domain, None) - }; - - let port = port_str - .and_then(|p| p.parse::<u16>().ok()) - .map(|p| p.clamp(0, u16::MAX)) - .unwrap_or(default_port); - - let mut socket_iter = lookup_host((host, 0)).await?; - - if let Some(addr) = socket_iter.find(|addr| addr.is_ipv4()) { - return Ok(SocketAddr::new(addr.ip(), port)); - } - - Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_configure.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_configure.rs deleted file mode 100644 index d739ac9..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_configure.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] -pub struct ServerTargetConfig { - /// Only process a single connection, then shut down the server. - once: bool, - - /// Timeout duration in milliseconds. (0 is Closed) - timeout: u64, -} - -impl ServerTargetConfig { - /// Set `once` to True - /// This method configures the `once` field of `ServerTargetConfig`. - pub fn once(mut self) -> Self { - self.once = true; - self - } - - /// Set `timeout` to the given value - /// This method configures the `timeout` field of `ServerTargetConfig`. - pub fn timeout(mut self, timeout: u64) -> Self { - self.timeout = timeout; - self - } - - /// Set `once` to the given value - /// This method configures the `once` field of `ServerTargetConfig`. - pub fn set_once(&mut self, enable: bool) { - self.once = enable; - } - - /// Set `timeout` to the given value - /// This method configures the `timeout` field of `ServerTargetConfig`. - pub fn set_timeout(&mut self, timeout: u64) { - self.timeout = timeout; - } - - /// Check if the server is configured to process only a single connection. - /// Returns `true` if the server will shut down after processing one connection. - pub fn is_once(&self) -> bool { - self.once - } - - /// Get the current timeout value in milliseconds. - /// Returns the timeout duration. A value of 0 indicates the connection is closed. - pub fn get_timeout(&self) -> u64 { - self.timeout - } -} - -#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] -pub struct ClientTargetConfig {} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_connection.rs b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_connection.rs deleted file mode 100644 index d5bf2c3..0000000 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_connection.rs +++ /dev/null @@ -1,89 +0,0 @@ -use tcp_connection::{error::TcpTargetError, instance::ConnectionInstance}; -use tokio::{ - net::{TcpListener, TcpSocket}, - spawn, -}; - -use crate::test_utils::{ - handle::{ClientHandle, ServerHandle}, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; - -impl<Client, Server> TcpServerTarget<Client, Server> -where - Client: ClientHandle<Server>, - Server: ServerHandle<Client>, -{ - /// Attempts to establish a connection to the TCP server. - /// - /// This function initiates a connection to the server address - /// specified in the target configuration. - /// - /// This is a Block operation. - pub async fn connect(&self) -> Result<(), TcpTargetError> { - let addr = self.get_addr(); - let Ok(socket) = TcpSocket::new_v4() else { - return Err(TcpTargetError::from("Create tcp socket failed!")); - }; - let stream = match socket.connect(addr).await { - Ok(stream) => stream, - Err(e) => { - let err = format!("Connect to `{}` failed: {}", addr, e); - return Err(TcpTargetError::from(err)); - } - }; - let instance = ConnectionInstance::from(stream); - Client::process(instance).await; - Ok(()) - } - - /// Attempts to establish a connection to the TCP server. - /// - /// This function initiates a connection to the server address - /// specified in the target configuration. - pub async fn listen(&self) -> Result<(), TcpTargetError> { - let addr = self.get_addr(); - let listener = match TcpListener::bind(addr).await { - Ok(listener) => listener, - Err(_) => { - let err = format!("Bind to `{}` failed", addr); - return Err(TcpTargetError::from(err)); - } - }; - - let cfg: ServerTargetConfig = match self.get_server_cfg() { - Some(cfg) => *cfg, - None => ServerTargetConfig::default(), - }; - - if cfg.is_once() { - // Process once (Blocked) - let (stream, _) = match listener.accept().await { - Ok(result) => result, - Err(e) => { - let err = format!("Accept connection failed: {}", e); - return Err(TcpTargetError::from(err)); - } - }; - let instance = ConnectionInstance::from(stream); - Server::process(instance).await; - } else { - loop { - // Process multiple times (Concurrent) - let (stream, _) = match listener.accept().await { - Ok(result) => result, - Err(e) => { - let err = format!("Accept connection failed: {}", e); - return Err(TcpTargetError::from(err)); - } - }; - let instance = ConnectionInstance::from(stream); - spawn(async move { - Server::process(instance).await; - }); - } - } - Ok(()) - } -} |
