From 81c9f47f5d9517ab273a34aeea4b6e40f45aac36 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 26 Sep 2025 14:18:53 +0800 Subject: refactor: Update sheet input handling and fix tests - Modify Sheet::add_input to accept InputPackage instead of separate parameters - Use output_mappings method to generate InputPackage in tests - Update test assertions to match new path transformation logic - Fix mapping count assertions after adding multiple mappings - Clean up string_proc module structure --- crates/utils/string_proc/src/format_processer.rs | 132 +++++++++++++++++++++ crates/utils/string_proc/src/lib.rs | 9 +- crates/utils/string_proc/src/macros.rs | 32 ++--- crates/utils/string_proc/src/simple_processer.rs | 15 +++ crates/utils/string_proc/src/string_processer.rs | 130 -------------------- crates/vcs/src/data/sheet.rs | 116 ++++++++++++++++-- crates/vcs/src/data/vault.rs | 4 +- ...st_sheet_creation_management_and_persistence.rs | 53 ++++++--- 8 files changed, 309 insertions(+), 182 deletions(-) create mode 100644 crates/utils/string_proc/src/format_processer.rs create mode 100644 crates/utils/string_proc/src/simple_processer.rs delete mode 100644 crates/utils/string_proc/src/string_processer.rs diff --git a/crates/utils/string_proc/src/format_processer.rs b/crates/utils/string_proc/src/format_processer.rs new file mode 100644 index 0000000..8d0a770 --- /dev/null +++ b/crates/utils/string_proc/src/format_processer.rs @@ -0,0 +1,132 @@ +pub struct FormatProcesser { + content: Vec, +} + +impl From 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 { + 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::()); + result.push_str(&chars.collect::().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::()); + result.push_str(&chars.collect::().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::()); + result.push_str(&chars.collect::().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 index 1f24028..e5559b9 100644 --- a/crates/utils/string_proc/src/lib.rs +++ b/crates/utils/string_proc/src/lib.rs @@ -1,9 +1,10 @@ +pub mod format_processer; pub mod macros; -pub mod string_processer; +pub mod simple_processer; #[cfg(test)] mod tests { - use crate::string_processer::StringProcesser; + use crate::format_processer::FormatProcesser; #[test] fn test_processer() { @@ -22,7 +23,7 @@ mod tests { ]; for (input, expected) in test_cases { - let processor = StringProcesser::from(input); + let processor = FormatProcesser::from(input); assert_eq!( processor.to_camel_case(), expected, @@ -34,7 +35,7 @@ mod tests { #[test] fn test_conversions() { - let processor = StringProcesser::from("brewCoffee"); + let processor = FormatProcesser::from("brewCoffee"); assert_eq!(processor.to_upper_case(), "BREW COFFEE"); assert_eq!(processor.to_lower_case(), "brew coffee"); diff --git a/crates/utils/string_proc/src/macros.rs b/crates/utils/string_proc/src/macros.rs index 9d85338..135268e 100644 --- a/crates/utils/string_proc/src/macros.rs +++ b/crates/utils/string_proc/src/macros.rs @@ -1,63 +1,63 @@ #[macro_export] macro_rules! camel_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_camel_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_camel_case() }}; } #[macro_export] macro_rules! upper_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_upper_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_upper_case() }}; } #[macro_export] macro_rules! lower_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_lower_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_lower_case() }}; } #[macro_export] macro_rules! title_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_title_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_title_case() }}; } #[macro_export] macro_rules! dot_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_dot_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_dot_case() }}; } #[macro_export] macro_rules! snake_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_snake_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_snake_case() }}; } #[macro_export] macro_rules! kebab_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_kebab_case() + use string_proc::format_processer::FormatProcesser; + FormatProcesser::from($input).to_kebab_case() }}; } #[macro_export] macro_rules! pascal_case { ($input:expr) => {{ - use string_proc::string_processer::StringProcesser; - StringProcesser::from($input).to_pascal_case() + 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 new file mode 100644 index 0000000..2de5dfc --- /dev/null +++ b/crates/utils/string_proc/src/simple_processer.rs @@ -0,0 +1,15 @@ +/// 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>(path: P) -> String { + let path_str = path.as_ref(); + path_str + .chars() + .map(|c| match c { + '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_', + _ => c, + }) + .collect() +} diff --git a/crates/utils/string_proc/src/string_processer.rs b/crates/utils/string_proc/src/string_processer.rs deleted file mode 100644 index 8b51c12..0000000 --- a/crates/utils/string_proc/src/string_processer.rs +++ /dev/null @@ -1,130 +0,0 @@ -pub struct StringProcesser { - content: Vec, -} - -impl From for StringProcesser { - fn from(value: String) -> Self { - Self { - content: Self::process_string(value), - } - } -} - -impl From<&str> for StringProcesser { - fn from(value: &str) -> Self { - Self { - content: Self::process_string(value.to_string()), - } - } -} - -impl StringProcesser { - /// Process the string into an intermediate format - fn process_string(input: String) -> Vec { - 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::()); - result.push_str(&chars.collect::().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::()); - result.push_str(&chars.collect::().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::()); - result.push_str(&chars.collect::().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/vcs/src/data/sheet.rs b/crates/vcs/src/data/sheet.rs index edf307a..a6765c0 100644 --- a/crates/vcs/src/data/sheet.rs +++ b/crates/vcs/src/data/sheet.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, path::PathBuf}; use cfg_file::{ConfigFile, config::ConfigFile}; use serde::{Deserialize, Serialize}; +use string_proc::simple_processer::sanitize_file_path; use crate::{ constants::SERVER_FILE_SHEET, @@ -16,14 +17,24 @@ pub type SheetPathBuf = PathBuf; pub type InputName = String; pub type InputRelativePathBuf = PathBuf; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq)] pub struct InputPackage { /// Name of the input package pub name: InputName, + + /// The sheet from which this input package was created + pub from: SheetName, + /// Files in this input package with their relative paths and virtual file IDs pub files: Vec<(InputRelativePathBuf, VirtualFileId)>, } +impl PartialEq for InputPackage { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + const SHEET_NAME: &str = "{sheet-name}"; pub struct Sheet<'a> { @@ -66,15 +77,15 @@ impl<'a> Sheet<'a> { } /// Add an input package to the sheet - pub fn add_input( - &mut self, - input_name: InputName, - files: Vec<(InputRelativePathBuf, VirtualFileId)>, - ) { - self.data.inputs.push(InputPackage { - name: input_name, - files, - }); + pub fn add_input(&mut self, input_package: InputPackage) -> Result<(), std::io::Error> { + if self.data.inputs.iter().any(|input| input == &input_package) { + return Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!("Input package '{}' already exists", input_package.name), + )); + } + self.data.inputs.push(input_package); + Ok(()) } /// Remove an input package from the sheet @@ -116,4 +127,89 @@ impl<'a> Sheet<'a> { .vault_path() .join(SERVER_FILE_SHEET.replace(SHEET_NAME, name.as_ref())) } + + /// Export files from the current sheet as an InputPackage for importing into other sheets + /// + /// This is the recommended way to create InputPackages. It takes a list of sheet paths + /// and generates an InputPackage with optimized relative paths by removing the longest + /// common prefix from all provided paths, then placing the files under a directory + /// named with the output_name. + /// + /// # Example + /// Given paths: + /// - `MyProject/Art/Character/Model/final.fbx` + /// - `MyProject/Art/Character/Texture/final.png` + /// - `MyProject/Art/Character/README.md` + /// + /// With output_name = "MyExport", the resulting package will contain: + /// - `MyExport/Model/final.fbx` + /// - `MyExport/Texture/final.png` + /// - `MyExport/README.md` + /// + /// # Arguments + /// * `output_name` - Name of the output package (will be used as the root directory) + /// * `paths` - List of sheet paths to include in the package + /// + /// # Returns + /// Returns an InputPackage containing the exported files with optimized paths, + /// or an error if paths are empty or files are not found in the sheet mapping + pub fn output_mappings( + &self, + output_name: InputName, + paths: &[SheetPathBuf], + ) -> Result { + let output_name = sanitize_file_path(output_name); + + // Return error for empty paths since there's no need to generate an empty package + if paths.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot generate output package with empty paths", + )); + } + + // Find the longest common prefix among all paths + let common_prefix = paths.iter().skip(1).fold(paths[0].clone(), |prefix, path| { + Self::common_path_prefix(prefix, path) + }); + + // Create output files with optimized relative paths + let files = paths + .iter() + .map(|path| { + let relative_path = path.strip_prefix(&common_prefix).unwrap_or(path); + let output_path = PathBuf::from(&output_name).join(relative_path); + + self.data + .mapping + .get(path) + .map(|vfid| (output_path, vfid.clone())) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("File not found: {:?}", path), + ) + }) + }) + .collect::, _>>()?; + + Ok(InputPackage { + name: output_name, + from: self.name.clone(), + files, + }) + } + + /// Helper function to find common path prefix between two paths + fn common_path_prefix(path1: impl Into, path2: impl Into) -> PathBuf { + let path1 = path1.into(); + let path2 = path2.into(); + + path1 + .components() + .zip(path2.components()) + .take_while(|(a, b)| a == b) + .map(|(comp, _)| comp) + .collect() + } } diff --git a/crates/vcs/src/data/vault.rs b/crates/vcs/src/data/vault.rs index 5b34c6f..5d17a81 100644 --- a/crates/vcs/src/data/vault.rs +++ b/crates/vcs/src/data/vault.rs @@ -72,9 +72,7 @@ impl Vault { create_dir_all(vault_path.join(SERVER_PATH_VF_ROOT))?; let Some(vault) = Vault::init(config, &vault_path) else { - return Err(std::io::Error::other( - "Failed to initialize vault", - )); + return Err(std::io::Error::other("Failed to initialize vault")); }; // 6. Create host member diff --git a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs index 1c7182b..8abcc4d 100644 --- a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs +++ b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs @@ -48,20 +48,23 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io: // Test 2: Add input packages to the sheet let input_name = "source_files".to_string(); - let files = vec![ - ( - InputRelativePathBuf::from("src/main.rs"), - VirtualFileId::new(), - ), - ( - InputRelativePathBuf::from("src/lib.rs"), - VirtualFileId::new(), - ), - ]; - // Need to get a mutable reference to the sheet + // First add mapping entries that will be used to generate the input package let mut sheet = vault.sheet(&sheet_name).await?; - sheet.add_input(input_name.clone(), files.clone()); + + // Add mapping entries for the files + let main_rs_path = vcs::data::sheet::SheetPathBuf::from("src/main.rs"); + let lib_rs_path = vcs::data::sheet::SheetPathBuf::from("src/lib.rs"); + let main_rs_id = VirtualFileId::new(); + let lib_rs_id = VirtualFileId::new(); + + sheet.add_mapping(main_rs_path.clone(), main_rs_id.clone()); + sheet.add_mapping(lib_rs_path.clone(), lib_rs_id.clone()); + + // Use output_mappings to generate the InputPackage + let paths = vec![main_rs_path, lib_rs_path]; + let input_package = sheet.output_mappings(input_name.clone(), &paths)?; + sheet.add_input(input_package)?; // Verify input was added assert_eq!(sheet.inputs().len(), 1); @@ -70,11 +73,11 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io: assert_eq!(added_input.files.len(), 2); assert_eq!( added_input.files[0].0, - InputRelativePathBuf::from("src/main.rs") + InputRelativePathBuf::from("source_files/main.rs") ); assert_eq!( added_input.files[1].0, - InputRelativePathBuf::from("src/lib.rs") + InputRelativePathBuf::from("source_files/lib.rs") ); // Test 3: Add mapping entries @@ -83,7 +86,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io: sheet.add_mapping(mapping_path.clone(), virtual_file_id.clone()); // Verify mapping was added - assert_eq!(sheet.mapping().len(), 1); + assert_eq!(sheet.mapping().len(), 3); assert_eq!(sheet.mapping().get(&mapping_path), Some(&virtual_file_id)); // Test 4: Persist sheet to disk @@ -93,7 +96,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io: let reloaded_sheet = vault.sheet(&sheet_name).await?; assert_eq!(reloaded_sheet.holder(), &member_id); assert_eq!(reloaded_sheet.inputs().len(), 1); - assert_eq!(reloaded_sheet.mapping().len(), 1); + assert_eq!(reloaded_sheet.mapping().len(), 3); // Test 5: Remove input package let mut sheet_for_removal = vault.sheet(&sheet_name).await?; @@ -107,7 +110,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io: // Test 6: Remove mapping entry let removed_virtual_file_id = sheet_for_removal.remove_mapping(&mapping_path); assert_eq!(removed_virtual_file_id, Some(virtual_file_id)); - assert_eq!(sheet_for_removal.mapping().len(), 0); + assert_eq!(sheet_for_removal.mapping().len(), 2); // Test 7: List all sheets in vault let sheet_names = vault.sheet_names()?; @@ -244,7 +247,7 @@ async fn test_sheet_data_serialization() -> Result<(), std::io::Error> { // Add some inputs let input_name = "source_files".to_string(); - let files = vec![ + let _files = vec![ ( InputRelativePathBuf::from("src/main.rs"), VirtualFileId::new(), @@ -254,7 +257,19 @@ async fn test_sheet_data_serialization() -> Result<(), std::io::Error> { VirtualFileId::new(), ), ]; - sheet.add_input(input_name, files); + // First add mapping entries + let main_rs_path = vcs::data::sheet::SheetPathBuf::from("src/main.rs"); + let lib_rs_path = vcs::data::sheet::SheetPathBuf::from("src/lib.rs"); + let main_rs_id = VirtualFileId::new(); + let lib_rs_id = VirtualFileId::new(); + + sheet.add_mapping(main_rs_path.clone(), main_rs_id.clone()); + sheet.add_mapping(lib_rs_path.clone(), lib_rs_id.clone()); + + // Use output_mappings to generate the InputPackage + let paths = vec![main_rs_path, lib_rs_path]; + let input_package = sheet.output_mappings(input_name.clone(), &paths)?; + sheet.add_input(input_package)?; // Add some mappings sheet.add_mapping( -- cgit From bb4a3f46d9d217fd71ee8a0ebdd78cc2c427fa9f Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 26 Sep 2025 14:30:57 +0800 Subject: docs: Add module-level documentation comments - Add documentation comments to VaultConfig, Sheets, and VirtualFile modules - Improve code documentation for better maintainability --- crates/vcs/src/data/vault/config.rs | 1 + crates/vcs/src/data/vault/sheets.rs | 1 + crates/vcs/src/data/vault/virtual_file.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/crates/vcs/src/data/vault/config.rs b/crates/vcs/src/data/vault/config.rs index e879325..40ba09f 100644 --- a/crates/vcs/src/data/vault/config.rs +++ b/crates/vcs/src/data/vault/config.rs @@ -23,6 +23,7 @@ impl Default for VaultConfig { } } +/// Vault Management impl VaultConfig { // Change name of the vault. pub fn change_name(&mut self, name: impl Into) { diff --git a/crates/vcs/src/data/vault/sheets.rs b/crates/vcs/src/data/vault/sheets.rs index bcd5779..082c076 100644 --- a/crates/vcs/src/data/vault/sheets.rs +++ b/crates/vcs/src/data/vault/sheets.rs @@ -13,6 +13,7 @@ use crate::{ }, }; +/// Vault Sheets Management impl Vault { /// Load all sheets in the vault /// diff --git a/crates/vcs/src/data/vault/virtual_file.rs b/crates/vcs/src/data/vault/virtual_file.rs index 23e964a..83b1c82 100644 --- a/crates/vcs/src/data/vault/virtual_file.rs +++ b/crates/vcs/src/data/vault/virtual_file.rs @@ -70,6 +70,7 @@ impl VirtualFileVersionDescription { } } +/// Virtual File Operations impl Vault { /// Generate a temporary path for receiving pub fn virtual_file_temp_path(&self) -> PathBuf { -- cgit From e8160eda1b68a42b8d861bbec5e9c1dc555ea783 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 26 Sep 2025 17:15:06 +0800 Subject: feat(tcp_connection): add MessagePack serialization support - Add rmp-serde dependency for MessagePack serialization - Implement write_msgpack and read_msgpack methods for basic MessagePack support - Add write_large_msgpack and read_large_msgpack methods for chunked transmission - Add error conversions for rmp-serde errors - Add comprehensive tests for MessagePack functionality - Fix code formatting and improve readability - Make stream field pub(crate) for better access control All tests pass successfully, ensuring backward compatibility. --- Cargo.lock | 30 +++++ crates/utils/tcp_connection/Cargo.toml | 1 + crates/utils/tcp_connection/src/error.rs | 15 +++ crates/utils/tcp_connection/src/instance.rs | 146 +++++++++++++++++---- .../utils/tcp_connection/src/target_connection.rs | 9 +- .../tcp_connection/tcp_connection_test/Cargo.toml | 1 + .../tcp_connection/tcp_connection_test/src/lib.rs | 3 + .../tcp_connection_test/src/test_msgpack.rs | 111 ++++++++++++++++ crates/vcs/src/data/sheet.rs | 9 ++ crates/vcs/src/data/vault/sheets.rs | 18 ++- crates/vcs/src/lib.rs | 2 + crates/vcs/src/service.rs | 2 + crates/vcs/src/service/server_entry.rs | 0 crates/vcs/src/service/standard_handle.rs | 16 +++ 14 files changed, 335 insertions(+), 28 deletions(-) create mode 100644 crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs create mode 100644 crates/vcs/src/service.rs create mode 100644 crates/vcs/src/service/server_entry.rs create mode 100644 crates/vcs/src/service/standard_handle.rs diff --git a/Cargo.lock b/Cargo.lock index 9e44fb5..16bcf4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,6 +657,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -845,6 +851,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "ron" version = "0.11.0" @@ -1125,6 +1153,7 @@ dependencies = [ "pem", "rand 0.10.0-rc.0", "ring", + "rmp-serde", "rsa", "serde", "serde_json", @@ -1137,6 +1166,7 @@ dependencies = [ name = "tcp_connection_test" version = "0.1.0" dependencies = [ + "serde", "tcp_connection", "tokio", ] diff --git a/crates/utils/tcp_connection/Cargo.toml b/crates/utils/tcp_connection/Cargo.toml index e70baf0..22466c8 100644 --- a/crates/utils/tcp_connection/Cargo.toml +++ b/crates/utils/tcp_connection/Cargo.toml @@ -9,6 +9,7 @@ tokio = { version = "1.46.1", features = ["full"] } # Serialization serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" +rmp-serde = "1.3.0" # Error handling thiserror = "1.0.69" diff --git a/crates/utils/tcp_connection/src/error.rs b/crates/utils/tcp_connection/src/error.rs index 171e23d..ffcce6f 100644 --- a/crates/utils/tcp_connection/src/error.rs +++ b/crates/utils/tcp_connection/src/error.rs @@ -32,6 +32,9 @@ pub enum TcpTargetError { #[error("Unsupported operation: {0}")] Unsupported(String), + + #[error("Pool already exists: {0}")] + PoolAlreadyExists(String), } impl From for TcpTargetError { @@ -87,3 +90,15 @@ impl From for TcpTargetError { TcpTargetError::Crypto(error.to_string()) } } + +impl From for TcpTargetError { + fn from(error: rmp_serde::encode::Error) -> Self { + TcpTargetError::Serialization(error.to_string()) + } +} + +impl From 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 index 217b10a..fd620e2 100644 --- a/crates/utils/tcp_connection/src/instance.rs +++ b/crates/utils/tcp_connection/src/instance.rs @@ -48,7 +48,7 @@ impl Default for ConnectionConfig { } pub struct ConnectionInstance { - stream: TcpStream, + pub(crate) stream: TcpStream, config: ConnectionConfig, } @@ -90,6 +90,35 @@ impl ConnectionInstance { Ok(()) } + /// Serialize data to MessagePack and write to the target machine + pub async fn write_msgpack(&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(&mut self) -> Result + 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(&mut self) -> Result where @@ -213,6 +242,73 @@ impl ConnectionInstance { Ok(String::from_utf8_lossy(&buffer).to_string()) } + /// Write large MessagePack data to the target machine (chunked) + pub async fn write_large_msgpack( + &mut self, + data: Data, + chunk_size: impl Into, + ) -> 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( + &mut self, + chunk_size: impl Into, + ) -> Result + 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) -> Result<(), TcpTargetError> { let path = file_path.as_ref(); @@ -319,9 +415,10 @@ impl ConnectionInstance { // Make sure parent directory exists if let Some(parent) = path.parent() - && !parent.exists() { - tokio::fs::create_dir_all(parent).await?; - } + && !parent.exists() + { + tokio::fs::create_dir_all(parent).await?; + } // Read file header (version + size + crc) let mut version_buf = [0u8; 8]; @@ -398,15 +495,16 @@ impl ConnectionInstance { // 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 - ))); - } + && 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?; @@ -576,22 +674,26 @@ fn parse_ed25519_public_key(pem: &str) -> [u8; 32] { 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..]); - } + && 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 { 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)); - } + && 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(), )) diff --git a/crates/utils/tcp_connection/src/target_connection.rs b/crates/utils/tcp_connection/src/target_connection.rs index 87fd1ab..0462f7b 100644 --- a/crates/utils/tcp_connection/src/target_connection.rs +++ b/crates/utils/tcp_connection/src/target_connection.rs @@ -17,7 +17,10 @@ where Server: ServerHandle, { /// Attempts to establish a connection to the TCP server. - /// This function initiates a connection to the server address specified in the target configuration. + /// + /// 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(); @@ -37,7 +40,9 @@ where } /// Attempts to establish a connection to the TCP server. - /// This function initiates a connection to the server address specified in the target configuration. + /// + /// 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 { diff --git a/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml b/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml index e4cba71..397f13a 100644 --- a/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml +++ b/crates/utils/tcp_connection/tcp_connection_test/Cargo.toml @@ -6,3 +6,4 @@ version.workspace = true [dependencies] tcp_connection = { path = "../../tcp_connection" } tokio = { version = "1.46.1", features = ["full"] } +serde = { version = "1.0.219", features = ["derive"] } diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs b/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs index f0eb66e..beba25b 100644 --- a/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs +++ b/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs @@ -9,3 +9,6 @@ pub mod test_challenge; #[cfg(test)] pub mod test_file_transfer; + +#[cfg(test)] +pub mod test_msgpack; 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 new file mode 100644 index 0000000..7344d64 --- /dev/null +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs @@ -0,0 +1,111 @@ +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tcp_connection::{ + handle::{ClientHandle, ServerHandle}, + instance::ConnectionInstance, + target::TcpServerTarget, + target_configure::ServerTargetConfig, +}; +use tokio::{join, time::sleep}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct TestData { + id: u32, + name: String, +} + +impl Default for TestData { + fn default() -> Self { + Self { + id: 0, + name: String::new(), + } + } +} + +pub(crate) struct MsgPackClientHandle; + +impl ClientHandle 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 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::::from_domain(host).await + else { + panic!("Test target built failed from a domain named `{}`", host); + }; + + // Client setup + let Ok(client_target) = + TcpServerTarget::::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/vcs/src/data/sheet.rs b/crates/vcs/src/data/sheet.rs index a6765c0..95599ff 100644 --- a/crates/vcs/src/data/sheet.rs +++ b/crates/vcs/src/data/sheet.rs @@ -71,6 +71,15 @@ impl<'a> Sheet<'a> { &self.data.inputs } + /// Get the names of the inputs of this sheet + pub fn input_names(&self) -> Vec { + self.data + .inputs + .iter() + .map(|input| input.name.clone()) + .collect() + } + /// Get the mapping of this sheet pub fn mapping(&self) -> &HashMap { &self.data.mapping diff --git a/crates/vcs/src/data/vault/sheets.rs b/crates/vcs/src/data/vault/sheets.rs index 082c076..0bba4f5 100644 --- a/crates/vcs/src/data/vault/sheets.rs +++ b/crates/vcs/src/data/vault/sheets.rs @@ -154,6 +154,9 @@ impl Vault { /// and will not be used in the future. /// /// For a safer deletion method, consider using `delete_sheet_safety`. + /// + /// Note: This function is intended for server-side use only and should not be + /// arbitrarily called by other members to prevent unauthorized data deletion. pub async fn delete_sheet(&self, sheet_name: &SheetName) -> Result<(), std::io::Error> { let sheet_name = snake_case!(sheet_name.clone()); @@ -174,11 +177,18 @@ impl Vault { /// Safely delete the sheet /// - /// The sheet will be moved to the trash directory, ensuring it does not appear in the results of `sheets` and `sheet_names` methods. - /// However, if the sheet's holder attempts to access the sheet through the `sheet` method, the system will automatically restore it from the trash directory. - /// This means: the sheet will only permanently remain in the trash directory, waiting for manual cleanup by an administrator, when it is truly no longer in use. + /// The sheet will be moved to the trash directory, ensuring it does not appear in the + /// results of `sheets` and `sheet_names` methods. + /// However, if the sheet's holder attempts to access the sheet through the `sheet` method, + /// the system will automatically restore it from the trash directory. + /// This means: the sheet will only permanently remain in the trash directory, + /// waiting for manual cleanup by an administrator, when it is truly no longer in use. + /// + /// This is a safer deletion method because it provides the possibility of recovery, + /// avoiding irreversible data loss caused by accidental deletion. /// - /// This is a safer deletion method because it provides the possibility of recovery, avoiding irreversible data loss caused by accidental deletion. + /// Note: This function is intended for server-side use only and should not be + /// arbitrarily called by other members to prevent unauthorized data deletion. pub async fn delete_sheet_safely(&self, sheet_name: &SheetName) -> Result<(), std::io::Error> { let sheet_name = snake_case!(sheet_name.clone()); diff --git a/crates/vcs/src/lib.rs b/crates/vcs/src/lib.rs index 1b41391..9a84b4d 100644 --- a/crates/vcs/src/lib.rs +++ b/crates/vcs/src/lib.rs @@ -3,3 +3,5 @@ pub mod current; #[allow(dead_code)] pub mod data; + +pub mod service; diff --git a/crates/vcs/src/service.rs b/crates/vcs/src/service.rs new file mode 100644 index 0000000..53365b8 --- /dev/null +++ b/crates/vcs/src/service.rs @@ -0,0 +1,2 @@ +pub mod server_entry; +pub mod standard_handle; diff --git a/crates/vcs/src/service/server_entry.rs b/crates/vcs/src/service/server_entry.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/vcs/src/service/standard_handle.rs b/crates/vcs/src/service/standard_handle.rs new file mode 100644 index 0000000..0d898b0 --- /dev/null +++ b/crates/vcs/src/service/standard_handle.rs @@ -0,0 +1,16 @@ +use tcp_connection::handle::{ClientHandle, ServerHandle}; + +pub struct StandardClientHandle; +pub struct StandardServerHandle; + +impl ClientHandle for StandardClientHandle { + async fn process(instance: tcp_connection::instance::ConnectionInstance) { + todo!() + } +} + +impl ServerHandle for StandardServerHandle { + async fn process(instance: tcp_connection::instance::ConnectionInstance) { + todo!() + } +} -- cgit From 4951e2e98bab7a2996893939ee77f0279145b556 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 26 Sep 2025 17:38:54 +0800 Subject: refactor: downgrade tcp_connection functionality to test utilities - Remove handle, target, target_configure, target_connection modules from main library - Create test_utils module in test project to contain temporary connection functionality - Update import paths in test files - Keep instance and error modules as core functionality - Adjust vcs_test configurations to adapt to new test structure --- Cargo.lock | 1 + crates/utils/tcp_connection/src/handle.rs | 10 - crates/utils/tcp_connection/src/lib.rs | 6 - crates/utils/tcp_connection/src/target.rs | 198 -------------------- .../utils/tcp_connection/src/target_configure.rs | 53 ------ .../utils/tcp_connection/src/target_connection.rs | 90 --------- .../tcp_connection/tcp_connection_test/src/lib.rs | 3 + .../tcp_connection_test/src/test_challenge.rs | 13 +- .../tcp_connection_test/src/test_connection.rs | 7 +- .../tcp_connection_test/src/test_file_transfer.rs | 13 +- .../tcp_connection_test/src/test_msgpack.rs | 7 +- .../src/test_tcp_target_build.rs | 7 +- .../tcp_connection_test/src/test_utils.rs | 4 + .../tcp_connection_test/src/test_utils/handle.rs | 11 ++ .../tcp_connection_test/src/test_utils/target.rs | 201 +++++++++++++++++++++ .../src/test_utils/target_configure.rs | 53 ++++++ .../src/test_utils/target_connection.rs | 89 +++++++++ crates/vcs/src/service/standard_handle.rs | 15 -- crates/vcs/vcs_test/Cargo.toml | 1 + .../src/test_virtual_file_creation_and_update.rs | 2 +- 20 files changed, 390 insertions(+), 394 deletions(-) delete mode 100644 crates/utils/tcp_connection/src/handle.rs delete mode 100644 crates/utils/tcp_connection/src/target.rs delete mode 100644 crates/utils/tcp_connection/src/target_configure.rs delete mode 100644 crates/utils/tcp_connection/src/target_connection.rs create mode 100644 crates/utils/tcp_connection/tcp_connection_test/src/test_utils.rs create mode 100644 crates/utils/tcp_connection/tcp_connection_test/src/test_utils/handle.rs create mode 100644 crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target.rs create mode 100644 crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_configure.rs create mode 100644 crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_connection.rs diff --git a/Cargo.lock b/Cargo.lock index 16bcf4b..dcce341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,6 +1336,7 @@ version = "0.1.0" dependencies = [ "cfg_file", "tcp_connection", + "tcp_connection_test", "tokio", "vcs", ] diff --git a/crates/utils/tcp_connection/src/handle.rs b/crates/utils/tcp_connection/src/handle.rs deleted file mode 100644 index ee77b43..0000000 --- a/crates/utils/tcp_connection/src/handle.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::instance::ConnectionInstance; -use std::future::Future; - -pub trait ClientHandle { - fn process(instance: ConnectionInstance) -> impl Future + Send; -} - -pub trait ServerHandle { - fn process(instance: ConnectionInstance) -> impl Future + Send; -} diff --git a/crates/utils/tcp_connection/src/lib.rs b/crates/utils/tcp_connection/src/lib.rs index 928457b..a5b5c20 100644 --- a/crates/utils/tcp_connection/src/lib.rs +++ b/crates/utils/tcp_connection/src/lib.rs @@ -1,10 +1,4 @@ -pub mod target; -pub mod target_configure; -pub mod target_connection; - #[allow(dead_code)] pub mod instance; -pub mod handle; - pub mod error; diff --git a/crates/utils/tcp_connection/src/target.rs b/crates/utils/tcp_connection/src/target.rs deleted file mode 100644 index 88b931a..0000000 --- a/crates/utils/tcp_connection/src/target.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::handle::{ClientHandle, ServerHandle}; -use crate::target_configure::{ClientTargetConfig, ServerTargetConfig}; -use serde::{Deserialize, Serialize}; -use std::{ - fmt::{Display, Formatter}, - marker::PhantomData, - net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr}, - str::FromStr, -}; -use tokio::net::lookup_host; - -const DEFAULT_PORT: u16 = 8080; - -#[derive(Debug, Serialize, Deserialize)] -pub struct TcpServerTarget -where - Client: ClientHandle, - Server: ServerHandle, -{ - /// Client Config - client_cfg: Option, - - /// Server Config - server_cfg: Option, - - /// Server port - port: u16, - - /// Bind addr - bind_addr: IpAddr, - - /// Client Phantom Data - _client: PhantomData, - - /// Server Phantom Data - _server: PhantomData, -} - -impl Default for TcpServerTarget -where - Client: ClientHandle, - Server: ServerHandle, -{ - 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 From for TcpServerTarget -where - Client: ClientHandle, - Server: ServerHandle, -{ - /// Convert SocketAddr to TcpServerTarget - fn from(value: SocketAddr) -> Self { - Self { - port: value.port(), - bind_addr: value.ip(), - ..Self::default() - } - } -} - -impl From> for SocketAddr -where - Client: ClientHandle, - Server: ServerHandle, -{ - /// Convert TcpServerTarget to SocketAddr - fn from(val: TcpServerTarget) -> Self { - SocketAddr::new(val.bind_addr, val.port) - } -} - -impl Display for TcpServerTarget -where - Client: ClientHandle, - Server: ServerHandle, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.bind_addr, self.port) - } -} - -impl TcpServerTarget -where - Client: ClientHandle, - Server: ServerHandle, -{ - /// Create target by address - pub fn from_addr(addr: impl Into, port: impl Into) -> 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 { - 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 { - 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 { - let domain = domain.into(); - let default_port: u16 = DEFAULT_PORT; - - if let Ok(socket_addr) = domain.parse::() { - 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::() { - 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::().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/src/target_configure.rs b/crates/utils/tcp_connection/src/target_configure.rs deleted file mode 100644 index d739ac9..0000000 --- a/crates/utils/tcp_connection/src/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/src/target_connection.rs b/crates/utils/tcp_connection/src/target_connection.rs deleted file mode 100644 index 0462f7b..0000000 --- a/crates/utils/tcp_connection/src/target_connection.rs +++ /dev/null @@ -1,90 +0,0 @@ -use tokio::{ - net::{TcpListener, TcpSocket}, - spawn, -}; - -use crate::{ - error::TcpTargetError, - handle::{ClientHandle, ServerHandle}, - instance::ConnectionInstance, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; - -impl TcpServerTarget -where - Client: ClientHandle, - Server: ServerHandle, -{ - /// 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(()) - } -} diff --git a/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs b/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs index beba25b..c9372d4 100644 --- a/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs +++ b/crates/utils/tcp_connection/tcp_connection_test/src/lib.rs @@ -12,3 +12,6 @@ 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 index 95b0e3c..2fc1a87 100644 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_challenge.rs +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_challenge.rs @@ -1,16 +1,17 @@ use std::{env::current_dir, time::Duration}; -use tcp_connection::{ - handle::{ClientHandle, ServerHandle}, - instance::ConnectionInstance, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; +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 for ExampleChallengeClientHandle { 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 index 79aac65..8c3ab01 100644 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_connection.rs +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_connection.rs @@ -1,12 +1,13 @@ use std::time::Duration; -use tcp_connection::{ +use tcp_connection::instance::ConnectionInstance; +use tokio::{join, time::sleep}; + +use crate::test_utils::{ handle::{ClientHandle, ServerHandle}, - instance::ConnectionInstance, target::TcpServerTarget, target_configure::ServerTargetConfig, }; -use tokio::{join, time::sleep}; pub(crate) struct ExampleClientHandle; 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 index 9425d30..4237ea7 100644 --- 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 @@ -1,16 +1,17 @@ use std::{env::current_dir, time::Duration}; -use tcp_connection::{ - handle::{ClientHandle, ServerHandle}, - instance::ConnectionInstance, - target::TcpServerTarget, - target_configure::ServerTargetConfig, -}; +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 for ExampleFileTransferClientHandle { 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 index 7344d64..7a7dc1f 100644 --- a/crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_msgpack.rs @@ -1,12 +1,13 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; -use tcp_connection::{ +use tcp_connection::instance::ConnectionInstance; +use tokio::{join, time::sleep}; + +use crate::test_utils::{ handle::{ClientHandle, ServerHandle}, - instance::ConnectionInstance, target::TcpServerTarget, target_configure::ServerTargetConfig, }; -use tokio::{join, time::sleep}; #[derive(Debug, PartialEq, Serialize, Deserialize)] struct TestData { 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 index bcaada3..aa1ec74 100644 --- 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 @@ -1,6 +1,7 @@ -use tcp_connection::target::TcpServerTarget; - -use crate::test_connection::{ExampleClientHandle, ExampleServerHandle}; +use crate::{ + test_connection::{ExampleClientHandle, ExampleServerHandle}, + test_utils::target::TcpServerTarget, +}; #[test] fn test_tcp_test_target_build() { 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 new file mode 100644 index 0000000..badf27d --- /dev/null +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils.rs @@ -0,0 +1,4 @@ +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 new file mode 100644 index 0000000..4f9bdbb --- /dev/null +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/handle.rs @@ -0,0 +1,11 @@ +use std::future::Future; + +use tcp_connection::instance::ConnectionInstance; + +pub trait ClientHandle { + fn process(instance: ConnectionInstance) -> impl Future + Send; +} + +pub trait ServerHandle { + fn process(instance: ConnectionInstance) -> impl Future + 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 new file mode 100644 index 0000000..8972b2a --- /dev/null +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target.rs @@ -0,0 +1,201 @@ +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 +where + Client: ClientHandle, + Server: ServerHandle, +{ + /// Client Config + client_cfg: Option, + + /// Server Config + server_cfg: Option, + + /// Server port + port: u16, + + /// Bind addr + bind_addr: IpAddr, + + /// Client Phantom Data + _client: PhantomData, + + /// Server Phantom Data + _server: PhantomData, +} + +impl Default for TcpServerTarget +where + Client: ClientHandle, + Server: ServerHandle, +{ + 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 From for TcpServerTarget +where + Client: ClientHandle, + Server: ServerHandle, +{ + /// Convert SocketAddr to TcpServerTarget + fn from(value: SocketAddr) -> Self { + Self { + port: value.port(), + bind_addr: value.ip(), + ..Self::default() + } + } +} + +impl From> for SocketAddr +where + Client: ClientHandle, + Server: ServerHandle, +{ + /// Convert TcpServerTarget to SocketAddr + fn from(val: TcpServerTarget) -> Self { + SocketAddr::new(val.bind_addr, val.port) + } +} + +impl Display for TcpServerTarget +where + Client: ClientHandle, + Server: ServerHandle, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.bind_addr, self.port) + } +} + +impl TcpServerTarget +where + Client: ClientHandle, + Server: ServerHandle, +{ + /// Create target by address + pub fn from_addr(addr: impl Into, port: impl Into) -> 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 { + 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 { + 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 { + let domain = domain.into(); + let default_port: u16 = DEFAULT_PORT; + + if let Ok(socket_addr) = domain.parse::() { + 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::() { + 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::().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 new file mode 100644 index 0000000..d739ac9 --- /dev/null +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_configure.rs @@ -0,0 +1,53 @@ +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 new file mode 100644 index 0000000..d5bf2c3 --- /dev/null +++ b/crates/utils/tcp_connection/tcp_connection_test/src/test_utils/target_connection.rs @@ -0,0 +1,89 @@ +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 TcpServerTarget +where + Client: ClientHandle, + Server: ServerHandle, +{ + /// 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(()) + } +} diff --git a/crates/vcs/src/service/standard_handle.rs b/crates/vcs/src/service/standard_handle.rs index 0d898b0..8b13789 100644 --- a/crates/vcs/src/service/standard_handle.rs +++ b/crates/vcs/src/service/standard_handle.rs @@ -1,16 +1 @@ -use tcp_connection::handle::{ClientHandle, ServerHandle}; -pub struct StandardClientHandle; -pub struct StandardServerHandle; - -impl ClientHandle for StandardClientHandle { - async fn process(instance: tcp_connection::instance::ConnectionInstance) { - todo!() - } -} - -impl ServerHandle for StandardServerHandle { - async fn process(instance: tcp_connection::instance::ConnectionInstance) { - todo!() - } -} diff --git a/crates/vcs/vcs_test/Cargo.toml b/crates/vcs/vcs_test/Cargo.toml index 0ed51d8..1cc43ac 100644 --- a/crates/vcs/vcs_test/Cargo.toml +++ b/crates/vcs/vcs_test/Cargo.toml @@ -5,6 +5,7 @@ version.workspace = true [dependencies] tcp_connection = { path = "../../utils/tcp_connection" } +tcp_connection_test = { path = "../../utils/tcp_connection/tcp_connection_test" } cfg_file = { path = "../../utils/cfg_file", features = ["default"] } vcs = { path = "../../vcs" } diff --git a/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs b/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs index 598e7be..d86c13a 100644 --- a/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs +++ b/crates/vcs/vcs_test/src/test_virtual_file_creation_and_update.rs @@ -1,7 +1,7 @@ use std::time::Duration; use cfg_file::config::ConfigFile; -use tcp_connection::{ +use tcp_connection_test::{ handle::{ClientHandle, ServerHandle}, target::TcpServerTarget, target_configure::ServerTargetConfig, -- cgit