1use serde::{Deserialize, Serialize};
8use std::time::Duration;
9
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
12pub enum EncryptionMode {
13 Convergent,
15 ConvergentWithSecret,
17 RandomKey,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Config {
25 pub encryption_mode: EncryptionMode,
27 pub data_shards: u8,
29 pub parity_shards: u8,
31 pub chunk_size: usize,
33 pub compression_enabled: bool,
35 pub compression_level: u8,
37 pub encryption: EncryptionConfig,
39 pub fec: FecConfig,
40 pub storage: StorageConfig,
41 pub gc: GcConfig,
42 pub version: VersionConfig,
43}
44
45impl Config {
46 pub fn new() -> Self {
49 Self {
50 encryption_mode: EncryptionMode::Convergent,
51 data_shards: 16,
52 parity_shards: 4,
53 chunk_size: 64 * 1024, compression_enabled: true,
55 compression_level: 6,
56 encryption: EncryptionConfig::default(),
58 fec: FecConfig::default(),
59 storage: StorageConfig::default(),
60 gc: GcConfig::default(),
61 version: VersionConfig::default(),
62 }
63 }
64
65 pub fn with_encryption_mode(mut self, mode: EncryptionMode) -> Self {
67 self.encryption_mode = mode;
68 self
69 }
70
71 pub fn with_fec_params(mut self, data_shards: u8, parity_shards: u8) -> Self {
74 self.data_shards = data_shards;
75 self.parity_shards = parity_shards;
76 self.fec.data_shares = data_shards as u16;
78 self.fec.parity_shares = parity_shards as u16;
79 self
80 }
81
82 pub fn with_chunk_size(mut self, bytes: usize) -> Self {
84 self.chunk_size = bytes;
85 self.fec.stripe_size = bytes;
87 self
88 }
89
90 pub fn with_compression(mut self, on: bool, level: u8) -> Self {
92 self.compression_enabled = on;
93 self.compression_level = level.clamp(1, 9);
94 self.encryption.compress_before_encrypt = on;
96 self.encryption.compression_level = level as u32;
97 self
98 }
99
100 pub fn high_performance() -> Self {
102 Self {
103 encryption_mode: EncryptionMode::Convergent,
104 data_shards: 16,
105 parity_shards: 4,
106 chunk_size: 128 * 1024,
107 compression_enabled: true,
108 compression_level: 3,
109 encryption: EncryptionConfig {
110 mode: EncryptionMode::Convergent,
111 compress_before_encrypt: true,
112 compression_level: 3,
113 },
114 fec: FecConfig {
115 data_shares: 16,
116 parity_shares: 4,
117 stripe_size: 128 * 1024,
118 auto_params: true,
119 },
120 storage: StorageConfig {
121 backend: StorageBackend::Local {
122 path: "/var/lib/saorsa".into(),
123 },
124 cache_size: 1024 * 1024 * 1024,
125 parallel_operations: 8,
126 },
127 gc: GcConfig {
128 enabled: true,
129 retention_days: 30,
130 min_free_space_gb: 10,
131 run_interval: Duration::from_secs(3600),
132 },
133 version: VersionConfig {
134 max_versions: 100,
135 auto_tag_interval: 10,
136 diff_compression: true,
137 },
138 }
139 }
140
141 pub fn high_reliability() -> Self {
143 Self {
144 encryption_mode: EncryptionMode::RandomKey,
145 data_shards: 10,
146 parity_shards: 10,
147 chunk_size: 64 * 1024,
148 compression_enabled: true,
149 compression_level: 6,
150 encryption: EncryptionConfig {
151 mode: EncryptionMode::RandomKey,
152 compress_before_encrypt: true,
153 compression_level: 6,
154 },
155 fec: FecConfig {
156 data_shares: 10,
157 parity_shares: 10,
158 stripe_size: 64 * 1024,
159 auto_params: false,
160 },
161 storage: StorageConfig {
162 backend: StorageBackend::Multi {
163 backends: vec![
164 StorageBackend::Local {
165 path: "/var/lib/saorsa/primary".into(),
166 },
167 StorageBackend::Local {
168 path: "/var/lib/saorsa/backup".into(),
169 },
170 ],
171 },
172 cache_size: 512 * 1024 * 1024,
173 parallel_operations: 4,
174 },
175 gc: GcConfig {
176 enabled: true,
177 retention_days: 90,
178 min_free_space_gb: 50,
179 run_interval: Duration::from_secs(7200),
180 },
181 version: VersionConfig {
182 max_versions: 1000,
183 auto_tag_interval: 1,
184 diff_compression: true,
185 },
186 }
187 }
188
189 pub fn minimal_storage() -> Self {
191 Self {
192 encryption_mode: EncryptionMode::Convergent,
193 data_shards: 20,
194 parity_shards: 2,
195 chunk_size: 32 * 1024,
196 compression_enabled: true,
197 compression_level: 9,
198 encryption: EncryptionConfig {
199 mode: EncryptionMode::Convergent,
200 compress_before_encrypt: true,
201 compression_level: 9,
202 },
203 fec: FecConfig {
204 data_shares: 20,
205 parity_shares: 2,
206 stripe_size: 32 * 1024,
207 auto_params: true,
208 },
209 storage: StorageConfig {
210 backend: StorageBackend::Local {
211 path: "/var/lib/saorsa".into(),
212 },
213 cache_size: 64 * 1024 * 1024,
214 parallel_operations: 2,
215 },
216 gc: GcConfig {
217 enabled: true,
218 retention_days: 7,
219 min_free_space_gb: 1,
220 run_interval: Duration::from_secs(1800),
221 },
222 version: VersionConfig {
223 max_versions: 10,
224 auto_tag_interval: 0,
225 diff_compression: true,
226 },
227 }
228 }
229
230 pub fn validate(&self) -> anyhow::Result<()> {
232 if self.fec.data_shares == 0 {
233 anyhow::bail!("Data shares must be greater than 0");
234 }
235 if self.fec.parity_shares == 0 {
236 anyhow::bail!("Parity shares must be greater than 0");
237 }
238 if self.fec.data_shares + self.fec.parity_shares > 255 {
239 anyhow::bail!("Total shares cannot exceed 255");
240 }
241 if self.fec.stripe_size == 0 {
242 anyhow::bail!("Stripe size must be greater than 0");
243 }
244 if self.storage.cache_size == 0 {
245 anyhow::bail!("Cache size must be greater than 0");
246 }
247 Ok(())
248 }
249}
250
251impl Default for Config {
252 fn default() -> Self {
253 Self::new()
254 }
255}
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct EncryptionConfig {
259 pub mode: EncryptionMode,
261 pub compress_before_encrypt: bool,
263 pub compression_level: u32,
265}
266impl Default for EncryptionConfig {
267 fn default() -> Self {
268 Self {
269 mode: EncryptionMode::Convergent,
270 compress_before_encrypt: true,
271 compression_level: 6,
272 }
273 }
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct FecConfig {
279 pub data_shares: u16,
281 pub parity_shares: u16,
283 pub stripe_size: usize,
285 pub auto_params: bool,
287}
288
289impl Default for FecConfig {
290 fn default() -> Self {
291 Self {
292 data_shares: 16,
293 parity_shares: 4,
294 stripe_size: 64 * 1024,
295 auto_params: true,
296 }
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct StorageConfig {
303 pub backend: StorageBackend,
305 pub cache_size: usize,
307 pub parallel_operations: usize,
309}
310
311impl Default for StorageConfig {
312 fn default() -> Self {
313 Self {
314 backend: StorageBackend::Local {
315 path: "/var/lib/saorsa".into(),
316 },
317 cache_size: 256 * 1024 * 1024,
318 parallel_operations: 4,
319 }
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325pub enum StorageBackend {
326 Local {
328 path: String,
330 },
331 Network {
333 nodes: Vec<String>,
335 replication: usize,
337 },
338 Multi {
340 backends: Vec<StorageBackend>,
342 },
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct GcConfig {
348 pub enabled: bool,
350 pub retention_days: u32,
352 pub min_free_space_gb: u32,
354 pub run_interval: Duration,
356}
357
358impl Default for GcConfig {
359 fn default() -> Self {
360 Self {
361 enabled: true,
362 retention_days: 30,
363 min_free_space_gb: 10,
364 run_interval: Duration::from_secs(3600),
365 }
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct VersionConfig {
372 pub max_versions: usize,
374 pub auto_tag_interval: usize,
376 pub diff_compression: bool,
378}
379
380impl Default for VersionConfig {
381 fn default() -> Self {
382 Self {
383 max_versions: 100,
384 auto_tag_interval: 10,
385 diff_compression: true,
386 }
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn test_config_default() {
396 let config = Config::default();
397 assert!(config.validate().is_ok());
398 }
399
400 #[test]
401 fn test_config_high_performance() {
402 let config = Config::high_performance();
403 assert!(config.validate().is_ok());
404 assert_eq!(config.fec.data_shares, 16);
405 assert_eq!(config.fec.parity_shares, 4);
406 }
407
408 #[test]
409 fn test_config_high_reliability() {
410 let config = Config::high_reliability();
411 assert!(config.validate().is_ok());
412 assert_eq!(config.fec.data_shares, 10);
413 assert_eq!(config.fec.parity_shares, 10);
414 }
415
416 #[test]
417 fn test_config_minimal_storage() {
418 let config = Config::minimal_storage();
419 assert!(config.validate().is_ok());
420 assert_eq!(config.fec.data_shares, 20);
421 assert_eq!(config.fec.parity_shares, 2);
422 }
423
424 #[test]
425 fn test_config_validation() {
426 let mut config = Config::default();
427
428 config.fec.data_shares = 0;
429 assert!(config.validate().is_err());
430
431 config.fec.data_shares = 200;
432 config.fec.parity_shares = 100;
433 assert!(config.validate().is_err());
434
435 config.fec.data_shares = 16;
436 config.fec.parity_shares = 4;
437 config.fec.stripe_size = 0;
438 assert!(config.validate().is_err());
439 }
440}