1use aura_core::effects::RandomEffects;
8use serde::{Deserialize, Serialize};
9use std::env;
10use std::time::Duration;
11
12#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct SyncConfig {
15 pub network: NetworkConfig,
17 pub retry: RetryConfig,
19 pub batching: BatchConfig,
21 pub peer_management: PeerManagementConfig,
23 pub protocols: ProtocolConfigs,
25 pub performance: PerformanceConfig,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct NetworkConfig {
32 pub base_sync_interval: Duration,
34 pub min_sync_interval: Duration,
36 pub sync_timeout: Duration,
38 pub cleanup_interval: Duration,
40}
41
42impl Default for NetworkConfig {
43 fn default() -> Self {
44 Self {
45 base_sync_interval: Duration::from_secs(30),
46 min_sync_interval: Duration::from_secs(10),
47 sync_timeout: Duration::from_secs(120),
48 cleanup_interval: Duration::from_secs(300),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct RetryConfig {
56 pub max_retries: u32,
58 pub base_delay: Duration,
60 pub max_delay: Duration,
62 pub jitter_factor: f64,
64}
65
66impl Default for RetryConfig {
67 fn default() -> Self {
68 Self {
69 max_retries: 3,
70 base_delay: Duration::from_millis(500),
71 max_delay: Duration::from_secs(30),
72 jitter_factor: 0.1,
73 }
74 }
75}
76
77impl RetryConfig {
78 pub async fn delay_for_attempt<R: RandomEffects + ?Sized>(
80 &self,
81 attempt: u32,
82 random: &R,
83 ) -> Duration {
84 let base_ms = self.base_delay.as_millis() as f64;
85 let exponential_delay = base_ms * 2_f64.powi(attempt as i32);
86 let jitter_sample = random.random_range(0, 10_000).await as f64 / 10_000.0;
87 let jittered_delay = exponential_delay * (1.0 + self.jitter_factor * jitter_sample);
88 let clamped_delay = jittered_delay.min(self.max_delay.as_millis() as f64);
89 Duration::from_millis(clamped_delay as u64)
90 }
91
92 pub fn should_retry(&self, attempt: u32) -> bool {
94 attempt < self.max_retries
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct BatchConfig {
101 pub default_batch_size: u32,
103 pub max_operations_per_round: u32,
105 pub enable_compression: bool,
107 pub min_batch_size: u32,
109 pub batch_timeout: Duration,
111}
112
113impl Default for BatchConfig {
114 fn default() -> Self {
115 Self {
116 default_batch_size: 128,
117 max_operations_per_round: 1000,
118 enable_compression: true,
119 min_batch_size: 10,
120 batch_timeout: Duration::from_secs(5),
121 }
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct PeerManagementConfig {
128 pub max_concurrent_syncs: u32,
130 pub min_priority_threshold: u32,
132 pub pending_operations_boost: u32,
134 pub failure_penalty: u32,
136 pub failure_backoff_duration: Duration,
138}
139
140impl Default for PeerManagementConfig {
141 fn default() -> Self {
142 Self {
143 max_concurrent_syncs: 5,
144 min_priority_threshold: 10,
145 pending_operations_boost: 20,
146 failure_penalty: 15,
147 failure_backoff_duration: Duration::from_secs(300),
148 }
149 }
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, Default)]
154pub struct ProtocolConfigs {
155 pub ota_upgrade: OTAConfig,
157 pub verification: VerificationConfig,
159 pub anti_entropy: AntiEntropyConfig,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct OTAConfig {
166 pub soft_fork_timeout: Duration,
168 pub hard_fork_timeout: Duration,
170 pub default_threshold: u32,
172 pub max_session_duration: Duration,
174 pub enable_auto_validation: bool,
176}
177
178impl Default for OTAConfig {
179 fn default() -> Self {
180 Self {
181 soft_fork_timeout: Duration::from_secs(3600), hard_fork_timeout: Duration::from_secs(7 * 24 * 3600), default_threshold: 2,
184 max_session_duration: Duration::from_secs(24 * 3600), enable_auto_validation: true,
186 }
187 }
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct VerificationConfig {
193 pub verification_timeout: Duration,
195 pub required_confirmations: u32,
197 pub max_verification_attempts: u32,
199}
200
201impl Default for VerificationConfig {
202 fn default() -> Self {
203 Self {
204 verification_timeout: Duration::from_secs(30),
205 required_confirmations: 2,
206 max_verification_attempts: 3,
207 }
208 }
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct AntiEntropyConfig {
214 pub min_sync_interval: Duration,
216 pub digest_timeout: Duration,
218 pub max_digest_entries: u32,
220}
221
222impl Default for AntiEntropyConfig {
223 fn default() -> Self {
224 Self {
225 min_sync_interval: Duration::from_secs(60),
226 digest_timeout: Duration::from_secs(10),
227 max_digest_entries: 1000,
228 }
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct PerformanceConfig {
235 pub max_cpu_usage: u32,
237 pub max_network_bandwidth: u64,
239 pub adaptive_scheduling: bool,
241 pub memory_limit: u64,
243}
244
245impl Default for PerformanceConfig {
246 fn default() -> Self {
247 Self {
248 max_cpu_usage: 80,
249 max_network_bandwidth: 10 * 1024 * 1024, adaptive_scheduling: true,
251 memory_limit: 100 * 1024 * 1024, }
253 }
254}
255
256impl PerformanceConfig {
257 pub fn validate(&self) -> Result<(), String> {
259 if self.max_cpu_usage > 100 {
260 return Err("max_cpu_usage must be <= 100".to_string());
261 }
262 if self.max_network_bandwidth == 0 {
263 return Err("max_network_bandwidth must be > 0".to_string());
264 }
265 if self.memory_limit < 1024 * 1024 {
266 return Err("memory_limit must be >= 1MB".to_string());
268 }
269 Ok(())
270 }
271}
272
273impl SyncConfig {
274 pub fn for_testing() -> Self {
276 Self {
277 network: NetworkConfig {
278 base_sync_interval: Duration::from_millis(100),
279 min_sync_interval: Duration::from_millis(50),
280 sync_timeout: Duration::from_secs(5),
281 cleanup_interval: Duration::from_secs(10),
282 },
283 retry: RetryConfig {
284 max_retries: 2,
285 base_delay: Duration::from_millis(10),
286 max_delay: Duration::from_millis(100),
287 jitter_factor: 0.0, },
289 batching: BatchConfig {
290 default_batch_size: 10,
291 max_operations_per_round: 50,
292 enable_compression: false, min_batch_size: 1,
294 batch_timeout: Duration::from_millis(100),
295 },
296 performance: PerformanceConfig {
297 max_cpu_usage: 100, max_network_bandwidth: u64::MAX,
299 adaptive_scheduling: false, memory_limit: u64::MAX,
301 },
302 ..Self::default()
303 }
304 }
305
306 pub fn for_production() -> Self {
308 Self {
309 network: NetworkConfig {
310 base_sync_interval: Duration::from_secs(60), sync_timeout: Duration::from_secs(300), ..NetworkConfig::default()
313 },
314 retry: RetryConfig {
315 max_retries: 5, max_delay: Duration::from_secs(60), ..RetryConfig::default()
318 },
319 performance: PerformanceConfig {
320 max_cpu_usage: 60, adaptive_scheduling: true,
322 ..PerformanceConfig::default()
323 },
324 ..Self::default()
325 }
326 }
327
328 pub fn validate(&self) -> Result<(), String> {
330 self.performance.validate()?;
332
333 if self.network.min_sync_interval >= self.network.base_sync_interval {
335 return Err("min_sync_interval must be less than base_sync_interval".to_string());
336 }
337
338 if self.retry.jitter_factor < 0.0 || self.retry.jitter_factor > 1.0 {
340 return Err("jitter_factor must be between 0.0 and 1.0".to_string());
341 }
342
343 if self.batching.min_batch_size > self.batching.default_batch_size {
345 return Err("min_batch_size must be <= default_batch_size".to_string());
346 }
347
348 Ok(())
349 }
350
351 pub fn from_env() -> Self {
353 let mut config = Self::default();
354
355 config.network.base_sync_interval = duration_secs(
357 "AURA_SYNC_BASE_SYNC_INTERVAL_SECS",
358 config.network.base_sync_interval,
359 );
360 config.network.min_sync_interval = duration_secs(
361 "AURA_SYNC_MIN_SYNC_INTERVAL_SECS",
362 config.network.min_sync_interval,
363 );
364 config.network.sync_timeout =
365 duration_secs("AURA_SYNC_TIMEOUT_SECS", config.network.sync_timeout);
366 config.network.cleanup_interval = duration_secs(
367 "AURA_SYNC_CLEANUP_INTERVAL_SECS",
368 config.network.cleanup_interval,
369 );
370
371 config.retry.max_retries =
373 parse_u32("AURA_SYNC_RETRY_MAX_RETRIES", config.retry.max_retries);
374 config.retry.base_delay =
375 duration_millis("AURA_SYNC_RETRY_BASE_DELAY_MS", config.retry.base_delay);
376 config.retry.max_delay =
377 duration_millis("AURA_SYNC_RETRY_MAX_DELAY_MS", config.retry.max_delay);
378 config.retry.jitter_factor =
379 parse_f64("AURA_SYNC_RETRY_JITTER", config.retry.jitter_factor);
380
381 config.batching.default_batch_size = parse_u32(
383 "AURA_SYNC_DEFAULT_BATCH_SIZE",
384 config.batching.default_batch_size,
385 );
386 config.batching.max_operations_per_round = parse_u32(
387 "AURA_SYNC_MAX_OPS_PER_ROUND",
388 config.batching.max_operations_per_round,
389 );
390 config.batching.enable_compression = parse_bool(
391 "AURA_SYNC_ENABLE_COMPRESSION",
392 config.batching.enable_compression,
393 );
394 config.batching.min_batch_size =
395 parse_u32("AURA_SYNC_MIN_BATCH_SIZE", config.batching.min_batch_size);
396 config.batching.batch_timeout =
397 duration_millis("AURA_SYNC_BATCH_TIMEOUT_MS", config.batching.batch_timeout);
398
399 config.peer_management.max_concurrent_syncs = parse_u32(
401 "AURA_SYNC_MAX_CONCURRENT_SYNCS",
402 config.peer_management.max_concurrent_syncs,
403 );
404 config.peer_management.min_priority_threshold = parse_u32(
405 "AURA_SYNC_MIN_PRIORITY_THRESHOLD",
406 config.peer_management.min_priority_threshold,
407 );
408 config.peer_management.pending_operations_boost = parse_u32(
409 "AURA_SYNC_PENDING_OPS_BOOST",
410 config.peer_management.pending_operations_boost,
411 );
412 config.peer_management.failure_penalty = parse_u32(
413 "AURA_SYNC_FAILURE_PENALTY",
414 config.peer_management.failure_penalty,
415 );
416 config.peer_management.failure_backoff_duration = duration_secs(
417 "AURA_SYNC_FAILURE_BACKOFF_SECS",
418 config.peer_management.failure_backoff_duration,
419 );
420
421 config.protocols.anti_entropy.min_sync_interval = duration_secs(
423 "AURA_SYNC_ANTI_ENTROPY_MIN_INTERVAL_SECS",
424 config.protocols.anti_entropy.min_sync_interval,
425 );
426 config.protocols.anti_entropy.digest_timeout = duration_secs(
427 "AURA_SYNC_ANTI_ENTROPY_DIGEST_TIMEOUT_SECS",
428 config.protocols.anti_entropy.digest_timeout,
429 );
430 config.protocols.anti_entropy.max_digest_entries = parse_u32(
431 "AURA_SYNC_ANTI_ENTROPY_MAX_DIGEST_ENTRIES",
432 config.protocols.anti_entropy.max_digest_entries,
433 );
434
435 config.protocols.verification.verification_timeout = duration_secs(
436 "AURA_SYNC_VERIFICATION_TIMEOUT_SECS",
437 config.protocols.verification.verification_timeout,
438 );
439 config.protocols.verification.required_confirmations = parse_u32(
440 "AURA_SYNC_VERIFICATION_CONFIRMATIONS",
441 config.protocols.verification.required_confirmations,
442 );
443 config.protocols.verification.max_verification_attempts = parse_u32(
444 "AURA_SYNC_VERIFICATION_MAX_ATTEMPTS",
445 config.protocols.verification.max_verification_attempts,
446 );
447
448 config.protocols.ota_upgrade.soft_fork_timeout = duration_secs(
449 "AURA_SYNC_OTA_SOFT_FORK_TIMEOUT_SECS",
450 config.protocols.ota_upgrade.soft_fork_timeout,
451 );
452 config.protocols.ota_upgrade.hard_fork_timeout = duration_secs(
453 "AURA_SYNC_OTA_HARD_FORK_TIMEOUT_SECS",
454 config.protocols.ota_upgrade.hard_fork_timeout,
455 );
456 config.protocols.ota_upgrade.default_threshold = parse_u32(
457 "AURA_SYNC_OTA_DEFAULT_THRESHOLD",
458 config.protocols.ota_upgrade.default_threshold,
459 );
460 config.protocols.ota_upgrade.max_session_duration = duration_secs(
461 "AURA_SYNC_OTA_MAX_SESSION_DURATION_SECS",
462 config.protocols.ota_upgrade.max_session_duration,
463 );
464 config.protocols.ota_upgrade.enable_auto_validation = parse_bool(
465 "AURA_SYNC_OTA_ENABLE_AUTO_VALIDATION",
466 config.protocols.ota_upgrade.enable_auto_validation,
467 );
468
469 config.performance.max_cpu_usage =
471 parse_u32("AURA_SYNC_MAX_CPU_USAGE", config.performance.max_cpu_usage);
472 config.performance.max_network_bandwidth = parse_u64(
473 "AURA_SYNC_MAX_NETWORK_BANDWIDTH",
474 config.performance.max_network_bandwidth,
475 );
476 config.performance.adaptive_scheduling = parse_bool(
477 "AURA_SYNC_ADAPTIVE_SCHEDULING",
478 config.performance.adaptive_scheduling,
479 );
480 config.performance.memory_limit = parse_u64(
481 "AURA_SYNC_MEMORY_LIMIT_BYTES",
482 config.performance.memory_limit,
483 );
484
485 config
486 }
487
488 pub fn builder() -> SyncConfigBuilder {
490 SyncConfigBuilder::new()
491 }
492}
493
494pub struct SyncConfigBuilder {
496 config: SyncConfig,
497}
498
499impl SyncConfigBuilder {
500 pub fn new() -> Self {
502 Self {
503 config: SyncConfig::default(),
504 }
505 }
506
507 pub fn network(mut self, network: NetworkConfig) -> Self {
509 self.config.network = network;
510 self
511 }
512
513 pub fn retry(mut self, retry: RetryConfig) -> Self {
515 self.config.retry = retry;
516 self
517 }
518
519 pub fn batching(mut self, batching: BatchConfig) -> Self {
521 self.config.batching = batching;
522 self
523 }
524
525 pub fn peer_management(mut self, peer_management: PeerManagementConfig) -> Self {
527 self.config.peer_management = peer_management;
528 self
529 }
530
531 pub fn protocols(mut self, protocols: ProtocolConfigs) -> Self {
533 self.config.protocols = protocols;
534 self
535 }
536
537 pub fn performance(mut self, performance: PerformanceConfig) -> Self {
539 self.config.performance = performance;
540 self
541 }
542
543 pub fn build(self) -> Result<SyncConfig, String> {
545 self.config.validate()?;
546 Ok(self.config)
547 }
548}
549
550fn parse_bool(key: &str, default: bool) -> bool {
551 env::var(key)
552 .ok()
553 .as_deref()
554 .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
555 .unwrap_or(default)
556}
557
558fn parse_u32(key: &str, default: u32) -> u32 {
559 env::var(key)
560 .ok()
561 .and_then(|v| v.parse::<u32>().ok())
562 .unwrap_or(default)
563}
564
565fn parse_u64(key: &str, default: u64) -> u64 {
566 env::var(key)
567 .ok()
568 .and_then(|v| v.parse::<u64>().ok())
569 .unwrap_or(default)
570}
571
572fn parse_f64(key: &str, default: f64) -> f64 {
573 env::var(key)
574 .ok()
575 .and_then(|v| v.parse::<f64>().ok())
576 .unwrap_or(default)
577}
578
579fn duration_secs(key: &str, default: Duration) -> Duration {
580 env::var(key)
581 .ok()
582 .and_then(|v| v.parse::<u64>().ok())
583 .map(Duration::from_secs)
584 .unwrap_or(default)
585}
586
587fn duration_millis(key: &str, default: Duration) -> Duration {
588 env::var(key)
589 .ok()
590 .and_then(|v| v.parse::<u64>().ok())
591 .map(Duration::from_millis)
592 .unwrap_or(default)
593}
594
595impl Default for SyncConfigBuilder {
596 fn default() -> Self {
597 Self::new()
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 #[test]
606 fn test_default_config_is_valid() {
607 let config = SyncConfig::default();
608 assert!(config.validate().is_ok());
609 }
610
611 #[test]
612 fn test_testing_config() {
613 let config = SyncConfig::for_testing();
614 assert!(config.validate().is_ok());
615 assert_eq!(config.retry.jitter_factor, 0.0);
616 assert!(!config.batching.enable_compression);
617 }
618
619 #[test]
620 fn test_production_config() {
621 let config = SyncConfig::for_production();
622 assert!(config.validate().is_ok());
623 assert_eq!(config.retry.max_retries, 5);
624 assert_eq!(config.performance.max_cpu_usage, 60);
625 }
626
627 #[tokio::test]
628 async fn test_retry_config_delay_calculation() {
629 let retry_config = RetryConfig::default();
630 let fixture = aura_testkit::create_test_fixture()
631 .await
632 .unwrap_or_else(|_| panic!("Failed to create test fixture"));
633 let device_id = fixture.device_id();
634 let composer = aura_testkit::foundation::TestEffectComposer::new(
635 aura_core::effects::ExecutionMode::Testing,
636 device_id,
637 );
638 let handler = composer
639 .build_mock_handler()
640 .unwrap_or_else(|err| panic!("build mock handler for retry tests: {err}"));
641
642 let delay1 = retry_config.delay_for_attempt(0, handler.as_ref()).await;
643 let delay2 = retry_config.delay_for_attempt(1, handler.as_ref()).await;
644
645 assert!(delay2 > delay1);
647
648 let delay_long = retry_config.delay_for_attempt(10, handler.as_ref()).await;
650 assert!(delay_long <= retry_config.max_delay);
651 }
652
653 #[test]
654 fn test_config_validation() {
655 let mut config = SyncConfig::default();
656
657 config.performance.max_cpu_usage = 150;
659 assert!(config.validate().is_err());
660
661 config.performance.max_cpu_usage = 80;
662
663 config.retry.jitter_factor = 2.0;
665 assert!(config.validate().is_err());
666
667 config.retry.jitter_factor = 0.1;
668 assert!(config.validate().is_ok());
669 }
670
671 #[test]
672 fn test_config_builder() {
673 let config = SyncConfig::builder()
674 .retry(RetryConfig {
675 max_retries: 10,
676 ..RetryConfig::default()
677 })
678 .build()
679 .unwrap();
680
681 assert_eq!(config.retry.max_retries, 10);
682 }
683
684 #[test]
685 fn test_builder_validation() {
686 let result = SyncConfig::builder()
687 .performance(PerformanceConfig {
688 max_cpu_usage: 150, ..PerformanceConfig::default()
690 })
691 .build();
692
693 assert!(result.is_err());
694 }
695}