use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SrxConfig {
pub role: NodeRole,
pub transports: TransportConfig,
pub crypto: CryptoConfig,
pub stochastic: StochasticConfig,
pub masking: MaskingConfig,
pub channel: ChannelConfig,
pub replay: ReplayConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NodeRole {
Client,
Server,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransportConfig {
pub tcp_enabled: bool,
pub udp_enabled: bool,
pub quic_enabled: bool,
pub websocket_enabled: bool,
pub grpc_enabled: bool,
pub http2_enabled: bool,
pub port_range: (u16, u16),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CryptoConfig {
pub aead: AeadCipher,
pub pqc_enabled: bool,
pub rekey_interval: (u64, u64),
pub aead_worker_count: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AeadCipher {
ChaCha20Poly1305,
Aes256Gcm,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StochasticConfig {
pub seed_rotation_interval: Duration,
pub channel_hopping: bool,
pub nonlinear_retries: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaskingConfig {
pub jitter_enabled: bool,
pub padding_enabled: bool,
pub cover_traffic_enabled: bool,
pub mimicry_mode: MimicryMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MimicryMode {
None,
Https,
Grpc,
WebRtc,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelConfig {
pub max_channels: usize,
pub health_check_interval: Duration,
pub shadow_session_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayConfig {
pub persist_enabled: bool,
pub backend: ReplayBackend,
pub state_file: PathBuf,
pub storage_key: String,
pub integrity: ReplayIntegrityConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReplayBackend {
FileJson,
Sqlite { path: PathBuf, table: String },
Redis { url: String, key_prefix: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayIntegrityConfig {
pub mode: ReplayIntegrityMode,
pub hmac_key: Option<Vec<u8>>,
pub hmac_key_provider: ReplayHmacKeyProvider,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReplayIntegrityMode {
None,
ChecksumSha256,
HmacSha256,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReplayHmacKeyProvider {
StaticConfig,
EnvVar {
name: String,
encoding: ReplayKeyEncoding,
},
File {
path: PathBuf,
encoding: ReplayKeyEncoding,
},
Custom { provider: String },
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReplayKeyEncoding {
RawUtf8,
Hex,
Base64,
}
impl SrxConfig {
pub fn build_aead_pipeline(
&self,
key: &[u8; 32],
) -> crate::error::Result<crate::crypto::AeadPipeline> {
crate::crypto::AeadPipeline::new(self.crypto.aead, key, self.crypto.aead_worker_count)
}
}
impl Default for SrxConfig {
fn default() -> Self {
Self {
role: NodeRole::Client,
transports: TransportConfig {
tcp_enabled: true,
udp_enabled: true,
quic_enabled: true,
websocket_enabled: true,
grpc_enabled: false,
http2_enabled: false,
port_range: (10000, 60000),
},
crypto: CryptoConfig {
aead: AeadCipher::ChaCha20Poly1305,
pqc_enabled: true,
rekey_interval: (1000, 5000),
aead_worker_count: crate::crypto::DEFAULT_AEAD_WORKER_COUNT,
},
stochastic: StochasticConfig {
seed_rotation_interval: Duration::from_secs(30),
channel_hopping: true,
nonlinear_retries: true,
},
masking: MaskingConfig {
jitter_enabled: true,
padding_enabled: true,
cover_traffic_enabled: true,
mimicry_mode: MimicryMode::Https,
},
channel: ChannelConfig {
max_channels: 4,
health_check_interval: Duration::from_secs(5),
shadow_session_enabled: false,
},
replay: ReplayConfig {
persist_enabled: true,
backend: ReplayBackend::FileJson,
state_file: PathBuf::from(".srx/replay_state.json"),
storage_key: "default".to_string(),
integrity: ReplayIntegrityConfig {
mode: ReplayIntegrityMode::ChecksumSha256,
hmac_key: None,
hmac_key_provider: ReplayHmacKeyProvider::StaticConfig,
},
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_role_and_transport_flags() {
let cfg = SrxConfig::default();
assert_eq!(cfg.role, NodeRole::Client);
assert!(cfg.transports.tcp_enabled);
assert!(cfg.transports.udp_enabled);
assert!(cfg.transports.quic_enabled);
assert!(cfg.transports.websocket_enabled);
assert!(!cfg.transports.grpc_enabled);
assert!(!cfg.transports.http2_enabled);
assert!(cfg.replay.persist_enabled);
assert_eq!(cfg.replay.backend, ReplayBackend::FileJson);
assert_eq!(cfg.replay.storage_key, "default");
assert_eq!(
cfg.replay.integrity.mode,
ReplayIntegrityMode::ChecksumSha256
);
assert!(cfg.replay.integrity.hmac_key.is_none());
assert_eq!(
cfg.replay.integrity.hmac_key_provider,
ReplayHmacKeyProvider::StaticConfig
);
assert!(!cfg.replay.state_file.as_os_str().is_empty());
}
#[test]
fn default_ranges_and_intervals_are_sane() {
let cfg = SrxConfig::default();
let (port_lo, port_hi) = cfg.transports.port_range;
let (rekey_min, rekey_max) = cfg.crypto.rekey_interval;
assert!(port_lo < port_hi);
assert!(rekey_min <= rekey_max);
assert!(cfg.stochastic.seed_rotation_interval > Duration::from_secs(0));
assert!(cfg.channel.health_check_interval > Duration::from_secs(0));
}
#[test]
fn default_crypto_and_masking_values_match_expected() {
let cfg = SrxConfig::default();
assert_eq!(cfg.crypto.aead, AeadCipher::ChaCha20Poly1305);
assert!(cfg.crypto.pqc_enabled);
assert_eq!(
cfg.crypto.aead_worker_count,
crate::crypto::DEFAULT_AEAD_WORKER_COUNT
);
assert!(cfg.masking.jitter_enabled);
assert!(cfg.masking.padding_enabled);
assert!(cfg.masking.cover_traffic_enabled);
assert_eq!(cfg.masking.mimicry_mode, MimicryMode::Https);
}
#[test]
fn build_aead_pipeline_uses_explicit_worker_count() {
let mut cfg = SrxConfig::default();
cfg.crypto.aead_worker_count = 3;
let key = [0x11u8; 32];
let pipe = cfg.build_aead_pipeline(&key).unwrap();
assert_eq!(pipe.worker_count(), 3);
}
#[test]
fn build_aead_pipeline_zero_workers_uses_default() {
let mut cfg = SrxConfig::default();
cfg.crypto.aead_worker_count = 0;
let key = [0x22u8; 32];
let pipe = cfg.build_aead_pipeline(&key).unwrap();
assert_eq!(
pipe.worker_count(),
crate::crypto::DEFAULT_AEAD_WORKER_COUNT
);
}
}