use iroh::EndpointId as NodeId;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConfig {
pub enable_pubsub: bool,
pub data_store_path: Option<PathBuf>,
pub port: u16,
pub known_peers: Vec<NodeId>,
pub enable_discovery_n0: bool,
pub enable_discovery_mdns: bool,
pub network: NetworkConfig,
pub storage: StorageConfig,
pub gossip: GossipConfig,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
enable_pubsub: true,
data_store_path: Some(PathBuf::from("./iroh_data")),
port: 0, known_peers: vec![],
enable_discovery_n0: true, enable_discovery_mdns: true, network: NetworkConfig::default(),
storage: StorageConfig::default(),
gossip: GossipConfig::default(),
}
}
}
impl ClientConfig {
pub fn development() -> Self {
Self {
enable_pubsub: true,
data_store_path: Some("./tmp/iroh_dev".into()),
port: 0, known_peers: vec![],
enable_discovery_n0: false, enable_discovery_mdns: true, network: NetworkConfig::development(),
storage: StorageConfig::development(),
gossip: GossipConfig::development(),
}
}
pub fn production() -> Self {
Self {
enable_pubsub: true,
data_store_path: Some("/var/lib/iroh".into()),
port: 4001, known_peers: vec![], enable_discovery_n0: true, enable_discovery_mdns: true, network: NetworkConfig::production(),
storage: StorageConfig::production(),
gossip: GossipConfig::production(),
}
}
pub fn testing() -> Self {
Self {
enable_pubsub: true,
data_store_path: None, port: 0, known_peers: vec![],
enable_discovery_n0: false,
enable_discovery_mdns: false,
network: NetworkConfig::testing(),
storage: StorageConfig::testing(),
gossip: GossipConfig::testing(),
}
}
pub fn offline() -> Self {
Self {
enable_pubsub: false,
enable_discovery_n0: false,
enable_discovery_mdns: false,
..Self::development()
}
}
pub fn add_known_peer(&mut self, peer: NodeId) {
if !self.known_peers.contains(&peer) {
self.known_peers.push(peer);
}
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn with_data_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.data_store_path = Some(path.into());
self
}
pub fn validate(&self) -> Result<(), String> {
if self.port > 0 && self.port < 1024 {
return Err("Port < 1024 requires administrator privileges".to_string());
}
if let Some(path) = &self.data_store_path
&& path.as_os_str().is_empty()
{
return Err("Storage path cannot be empty".to_string());
}
if self.storage.max_cache_size == 0 {
return Err("Cache size cannot be zero".to_string());
}
Ok(())
}
pub fn uses_persistent_storage(&self) -> bool {
self.data_store_path.is_some()
}
pub fn has_discovery_enabled(&self) -> bool {
self.enable_discovery_n0 || self.enable_discovery_mdns
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
pub connection_timeout: Duration,
pub max_peers_per_session: usize,
pub io_buffer_size: usize,
pub keepalive_interval: Duration,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
connection_timeout: Duration::from_secs(30),
max_peers_per_session: 100,
io_buffer_size: 64 * 1024, keepalive_interval: Duration::from_secs(60),
}
}
}
impl NetworkConfig {
pub fn development() -> Self {
Self {
connection_timeout: Duration::from_secs(10),
max_peers_per_session: 10,
io_buffer_size: 16 * 1024, keepalive_interval: Duration::from_secs(30),
}
}
pub fn production() -> Self {
Self {
connection_timeout: Duration::from_secs(60),
max_peers_per_session: 1000,
io_buffer_size: 128 * 1024, keepalive_interval: Duration::from_secs(120),
}
}
pub fn testing() -> Self {
Self {
connection_timeout: Duration::from_secs(5),
max_peers_per_session: 5,
io_buffer_size: 8 * 1024, keepalive_interval: Duration::from_secs(15),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
pub enable_memory_cache: bool,
pub max_cache_size: usize,
pub max_blob_size: usize,
pub enable_gc: bool,
pub gc_interval: Duration,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
enable_memory_cache: true,
max_cache_size: 100 * 1024 * 1024, max_blob_size: 10 * 1024 * 1024, enable_gc: true,
gc_interval: Duration::from_secs(3600), }
}
}
impl StorageConfig {
pub fn development() -> Self {
Self {
enable_memory_cache: true,
max_cache_size: 10 * 1024 * 1024, max_blob_size: 5 * 1024 * 1024, enable_gc: false, gc_interval: Duration::from_secs(3600),
}
}
pub fn production() -> Self {
Self {
enable_memory_cache: true,
max_cache_size: 1024 * 1024 * 1024, max_blob_size: 100 * 1024 * 1024, enable_gc: true,
gc_interval: Duration::from_secs(1800), }
}
pub fn testing() -> Self {
Self {
enable_memory_cache: false,
max_cache_size: 1024 * 1024, max_blob_size: 512 * 1024, enable_gc: false,
gc_interval: Duration::from_secs(3600),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GossipConfig {
pub max_message_size: usize,
pub message_buffer_size: usize,
pub operation_timeout: Duration,
pub heartbeat_interval: Duration,
pub max_topics: usize,
}
impl Default for GossipConfig {
fn default() -> Self {
Self {
max_message_size: 1024 * 1024, message_buffer_size: 1000,
operation_timeout: Duration::from_secs(30),
heartbeat_interval: Duration::from_secs(1),
max_topics: 100,
}
}
}
impl GossipConfig {
pub fn development() -> Self {
Self {
max_message_size: 64 * 1024, message_buffer_size: 100,
operation_timeout: Duration::from_secs(10),
heartbeat_interval: Duration::from_secs(2),
max_topics: 10,
}
}
pub fn production() -> Self {
Self {
max_message_size: 10 * 1024 * 1024, message_buffer_size: 10000,
operation_timeout: Duration::from_secs(60),
heartbeat_interval: Duration::from_millis(500),
max_topics: 1000,
}
}
pub fn testing() -> Self {
Self {
max_message_size: 1024 * 1024, message_buffer_size: 10,
operation_timeout: Duration::from_secs(5),
heartbeat_interval: Duration::from_secs(5),
max_topics: 5,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ClientConfig::default();
assert!(config.enable_pubsub);
assert!(config.enable_discovery_n0);
assert!(config.enable_discovery_mdns);
assert!(config.validate().is_ok());
}
#[test]
fn test_development_config() {
let config = ClientConfig::development();
assert!(config.enable_pubsub);
assert!(!config.enable_discovery_n0); assert!(config.enable_discovery_mdns);
assert_eq!(config.port, 0); }
#[test]
fn test_production_config() {
let config = ClientConfig::production();
assert!(config.enable_pubsub);
assert!(config.enable_discovery_n0);
assert!(config.enable_discovery_mdns);
assert_eq!(config.port, 4001); }
#[test]
fn test_testing_config() {
let config = ClientConfig::testing();
assert!(config.enable_pubsub);
assert!(!config.enable_discovery_n0);
assert!(!config.enable_discovery_mdns);
assert_eq!(config.port, 0);
}
#[test]
fn test_offline_config() {
let config = ClientConfig::offline();
assert!(!config.enable_pubsub);
assert!(!config.enable_discovery_n0);
assert!(!config.enable_discovery_mdns);
}
#[test]
fn test_config_validation() {
let mut config = ClientConfig::default();
assert!(config.validate().is_ok());
config.port = 80;
assert!(config.validate().is_err());
config.port = 0;
assert!(config.validate().is_ok());
config.port = 4001;
assert!(config.validate().is_ok());
}
#[test]
fn test_add_known_peer() {
use iroh::SecretKey;
let mut config = ClientConfig::default();
let secret = SecretKey::generate();
let peer = secret.public();
config.add_known_peer(peer);
assert!(config.known_peers.contains(&peer));
config.add_known_peer(peer);
assert_eq!(config.known_peers.len(), 1);
}
#[test]
fn test_with_data_path() {
let config = ClientConfig::default().with_data_path("/custom/path");
assert_eq!(config.data_store_path, Some(PathBuf::from("/custom/path")));
}
#[test]
fn test_with_port() {
let config = ClientConfig::default().with_port(8080);
assert_eq!(config.port, 8080);
}
#[test]
fn test_persistent_storage_detection() {
let persistent_config = ClientConfig::development();
assert!(persistent_config.uses_persistent_storage());
let memory_config = ClientConfig::testing();
assert!(!memory_config.uses_persistent_storage());
}
#[test]
fn test_discovery_detection() {
let with_discovery = ClientConfig::default();
assert!(with_discovery.has_discovery_enabled());
let without_discovery = ClientConfig::offline();
assert!(!without_discovery.has_discovery_enabled());
}
}