use crate::{BrlApiError, Result};
use brlapi_sys::brlapi_connectionSettings_t;
use std::ffi::CString;
use std::ptr;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ConnectionSettings {
auth: Option<String>,
host: Option<String>,
timeout: Duration,
}
impl Default for ConnectionSettings {
fn default() -> Self {
Self {
auth: None,
host: None,
timeout: Duration::from_secs(10), }
}
}
impl ConnectionSettings {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn localhost() -> Self {
Self {
host: Some("localhost".to_string()),
auth: None,
timeout: Duration::from_secs(10),
}
}
#[must_use]
pub fn for_host(host: impl Into<String>) -> Self {
Self {
host: Some(host.into()),
auth: None,
timeout: Duration::from_secs(10),
}
}
#[must_use]
pub fn with_auth_file(auth_path: impl Into<String>) -> Self {
Self {
auth: Some(auth_path.into()),
host: None,
timeout: Duration::from_secs(10),
}
}
pub fn set_auth(&mut self, auth_path: impl Into<String>) -> &mut Self {
self.auth = Some(auth_path.into());
self
}
pub fn set_host(&mut self, host: impl Into<String>) -> &mut Self {
self.host = Some(host.into());
self
}
pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn with_timeout(timeout: Duration) -> Self {
Self {
auth: None,
host: None,
timeout,
}
}
pub fn auth(&self) -> Option<&str> {
self.auth.as_deref()
}
pub fn host(&self) -> Option<&str> {
self.host.as_deref()
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn to_c_settings(
&self,
) -> Result<(
brlapi_connectionSettings_t,
Option<CString>,
Option<CString>,
)> {
let auth_cstr = match &self.auth {
Some(s) => Some(CString::new(s.as_str()).map_err(|_| BrlApiError::InvalidParameter)?),
None => None,
};
let host_cstr = match &self.host {
Some(s) => Some(CString::new(s.as_str()).map_err(|_| BrlApiError::InvalidParameter)?),
None => None,
};
let c_settings = brlapi_connectionSettings_t {
auth: auth_cstr.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
host: host_cstr.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
};
Ok((c_settings, auth_cstr, host_cstr))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_settings() {
let settings = ConnectionSettings::default();
assert!(settings.auth.is_none());
assert!(settings.host.is_none());
}
#[test]
fn test_localhost_settings() {
let settings = ConnectionSettings::localhost();
assert!(settings.auth.is_none());
assert_eq!(settings.host, Some("localhost".to_string()));
}
#[test]
fn test_builder_pattern() {
let mut settings = ConnectionSettings::new();
settings.set_host("example.com").set_auth("/etc/brlapi.key");
assert_eq!(settings.host, Some("example.com".to_string()));
assert_eq!(settings.auth, Some("/etc/brlapi.key".to_string()));
}
#[test]
fn test_c_conversion() {
let settings = ConnectionSettings {
auth: Some("/etc/brlapi.key".to_string()),
host: Some("localhost".to_string()),
timeout: Duration::from_secs(5),
};
let (c_settings, auth_str, host_str) = settings
.to_c_settings()
.expect("Valid settings should convert to C settings");
assert!(auth_str.is_some());
assert!(host_str.is_some());
assert!(!c_settings.auth.is_null());
assert!(!c_settings.host.is_null());
}
#[test]
fn test_timeout_settings() {
let settings = ConnectionSettings::with_timeout(Duration::from_secs(30));
assert_eq!(settings.timeout(), Duration::from_secs(30));
let mut settings = ConnectionSettings::new();
settings.set_timeout(Duration::from_millis(5000));
assert_eq!(settings.timeout(), Duration::from_millis(5000));
}
#[test]
#[should_panic]
fn test_connection_settings_null_byte_in_host() {
let settings = ConnectionSettings::for_host("example.com\0malicious");
let _ = settings
.to_c_settings()
.expect("Settings conversion should succeed for valid settings");
}
#[test]
#[should_panic]
fn test_connection_settings_null_byte_in_auth() {
let settings = ConnectionSettings::with_auth_file("/path/to\0malicious/auth");
let _ = settings
.to_c_settings()
.expect("Settings conversion should succeed for valid settings");
}
#[test]
fn test_connection_settings_validation() {
let settings = ConnectionSettings::for_host("example.com\0malicious");
assert!(settings.to_c_settings().is_err());
let settings = ConnectionSettings::with_auth_file("/path/to\0malicious/auth");
assert!(settings.to_c_settings().is_err());
let settings = ConnectionSettings::for_host("example.com");
assert!(settings.to_c_settings().is_ok());
}
}