1#![allow(warnings)]
34
35use serde::{Deserialize, Serialize};
36
37use crate::config::{
38 ChunkingConfig, CodecTunables, Defaults, DictionaryConfig, EncryptionConfig, TournamentConfig,
39 WriteConfig,
40};
41
42pub const MAX_RATIO: &str = "max-ratio";
44pub const MAX_SPEED: &str = "max-speed";
45pub const BALANCED: &str = "balanced";
46pub const COMPETITIVE: &str = "competitive";
47pub const MAX_READ: &str = "max-read";
48pub const MAX_WRITE: &str = "max-write";
49pub const MAX_WRITE_RW: &str = "max-write-rw";
50pub const MAX_READ_RW: &str = "max-read-rw";
51pub const BALANCED_RW: &str = "balanced-rw";
52
53#[must_use]
56pub fn select(name: &str) -> Option<WriteConfig> {
57 match name {
58 MAX_RATIO => Some(max_ratio()),
59 MAX_SPEED => Some(max_speed()),
60 BALANCED => Some(balanced()),
61 COMPETITIVE => Some(competitive()),
62 MAX_READ => Some(max_read()),
63 MAX_WRITE => Some(max_write()),
64 MAX_WRITE_RW => Some(max_write_rw()),
65 MAX_READ_RW => Some(max_read_rw()),
66 BALANCED_RW => Some(balanced_rw()),
67 _ => None,
68 }
69}
70
71#[must_use]
80pub fn max_ratio() -> WriteConfig {
81 WriteConfig {
82 defaults: Defaults {
83 text_codec: "brotli".into(),
84 binary_codec: "zstd".into(),
85 metadata_codec: "brotli".into(),
86 metadata_quality: 11,
87 inline_threshold: 8192,
88 },
89 categorizers: crate::config::defaults::all_v0_1(),
90 chunking: ChunkingConfig {
91 name: "fastcdc".into(),
92 avg_chunk_size: 65_536,
93 min_chunk_size: 8192,
94 max_chunk_size: 262_144,
95 },
96 tournament: TournamentConfig {
97 codecs: vec![
98 "store".into(),
99 "lz4".into(),
100 "lz4-hc".into(),
101 "zstd".into(),
102 "brotli".into(),
103 "ppmd".into(),
104 "bzip2".into(),
105 ],
106 min_size_threshold: 256,
107 skip_for_binary: false,
108 short_circuit_threshold: 0,
109 },
110 codec_tunables: CodecTunables {
111 ppmd7: crate::config::Ppmd7Tunables {
112 order: 6,
113 memory_budget_mb: 256,
114 },
115 ppmd8: crate::config::Ppmd8Tunables {
116 order: 8,
117 memory_budget_mb: 128,
118 },
119 brotli: crate::config::BrotliTunables {
120 quality: 11,
121 window: 24,
122 },
123 lzma: crate::config::LzmaTunables {
124 lc: 3,
125 lp: 0,
126 pb: 2,
127 dict_size_mb: 64,
128 use_optimal_parser: true,
129 },
130 bzip2: crate::config::Bzip2Tunables { block_size_kb: 900 },
131 },
132 encryption: EncryptionConfig {
133 aead: "chacha20-poly1305".into(),
134 key_wrap: "x25519-hkdf".into(),
135 },
136 dictionaries: DictionaryConfig {
137 enabled: true,
138 min_class_size: 50,
139 max_dict_size: 131_072,
140 trainer: "frequency".into(),
141 },
142 mode: crate::config::ImageMode::ReadOnly,
143 write_codec: "lz4".into(),
144 turnover_threshold: 0,
145 skip_chunking: false,
146 }
147}
148
149#[must_use]
158pub fn max_speed() -> WriteConfig {
159 WriteConfig {
160 defaults: Defaults {
161 text_codec: "lz4".into(),
162 binary_codec: "lz4".into(),
163 metadata_codec: "lz4".into(),
164 metadata_quality: 1,
165 inline_threshold: 4096,
166 },
167 categorizers: vec![],
168 chunking: ChunkingConfig {
169 name: "fastcdc".into(),
170 avg_chunk_size: 4096,
171 min_chunk_size: 512,
172 max_chunk_size: 16_384,
173 },
174 tournament: TournamentConfig {
175 codecs: vec!["store".into(), "lz4".into()],
176 min_size_threshold: 0,
177 skip_for_binary: true,
178 short_circuit_threshold: 500,
179 },
180 codec_tunables: CodecTunables {
181 brotli: crate::config::BrotliTunables {
182 quality: 0,
183 window: 10,
184 },
185 ..CodecTunables::default()
186 },
187 encryption: EncryptionConfig {
188 aead: "chacha20-poly1305".into(),
189 key_wrap: "x25519-hkdf".into(),
190 },
191 dictionaries: DictionaryConfig {
192 enabled: false,
193 min_class_size: 0,
194 max_dict_size: 0,
195 trainer: "frequency".into(),
196 },
197 mode: crate::config::ImageMode::ReadOnly,
198 write_codec: "lz4".into(),
199 turnover_threshold: 0,
200 skip_chunking: false,
201 }
202}
203
204#[must_use]
212pub fn balanced() -> WriteConfig {
213 WriteConfig {
214 defaults: Defaults {
215 text_codec: "brotli".into(),
216 binary_codec: "lz4".into(),
217 metadata_codec: "zstd".into(),
218 metadata_quality: 3,
219 inline_threshold: 4096,
220 },
221 categorizers: crate::config::defaults::all_v0_1(),
222 chunking: ChunkingConfig {
223 name: "fastcdc".into(),
224 avg_chunk_size: 16_384,
225 min_chunk_size: 2048,
226 max_chunk_size: 65_536,
227 },
228 tournament: TournamentConfig {
229 codecs: vec!["store".into(), "lz4".into(), "brotli".into()],
230 min_size_threshold: 256,
231 skip_for_binary: true,
232 short_circuit_threshold: 250,
233 },
234 codec_tunables: CodecTunables {
235 brotli: crate::config::BrotliTunables {
236 quality: 5,
237 window: 22,
238 },
239 ..CodecTunables::default()
240 },
241 encryption: EncryptionConfig {
242 aead: "chacha20-poly1305".into(),
243 key_wrap: "x25519-hkdf".into(),
244 },
245 dictionaries: DictionaryConfig {
246 enabled: true,
247 min_class_size: 100,
248 max_dict_size: 65_536,
249 trainer: "frequency".into(),
250 },
251 mode: crate::config::ImageMode::ReadOnly,
252 write_codec: "lz4".into(),
253 turnover_threshold: 0,
254 skip_chunking: false,
255 }
256}
257
258#[must_use]
263pub fn competitive() -> WriteConfig {
264 WriteConfig {
265 defaults: Defaults {
266 text_codec: "zstd".into(),
267 binary_codec: "lz4".into(),
268 metadata_codec: "zstd".into(),
269 metadata_quality: 3,
270 inline_threshold: 4096,
271 },
272 categorizers: crate::config::defaults::all_v0_1(),
273 chunking: ChunkingConfig {
274 name: "fastcdc".into(),
275 avg_chunk_size: 8192,
276 min_chunk_size: 1024,
277 max_chunk_size: 65_536,
278 },
279 tournament: TournamentConfig {
280 codecs: vec!["store".into(), "lz4".into(), "brotli".into()],
281 min_size_threshold: 0,
282 skip_for_binary: true,
283 short_circuit_threshold: 250,
284 },
285 codec_tunables: CodecTunables {
286 brotli: crate::config::BrotliTunables {
287 quality: 5,
288 window: 22,
289 },
290 ..CodecTunables::default()
291 },
292 encryption: EncryptionConfig {
293 aead: "chacha20-poly1305".into(),
294 key_wrap: "x25519-hkdf".into(),
295 },
296 dictionaries: DictionaryConfig {
297 enabled: false,
298 min_class_size: 0,
299 max_dict_size: 0,
300 trainer: "frequency".into(),
301 },
302 mode: crate::config::ImageMode::ReadOnly,
303 write_codec: "lz4".into(),
304 turnover_threshold: 0,
305 skip_chunking: false,
306 }
307}
308
309#[must_use]
320pub fn max_read() -> WriteConfig {
321 WriteConfig {
322 defaults: Defaults {
323 text_codec: "zstd".into(),
324 binary_codec: "zstd".into(),
325 metadata_codec: "zstd".into(),
326 metadata_quality: 11,
327 inline_threshold: 8192,
328 },
329 categorizers: crate::config::defaults::all_v0_1(),
330 chunking: ChunkingConfig {
331 name: "fastcdc".into(),
332 avg_chunk_size: 65_536,
333 min_chunk_size: 8192,
334 max_chunk_size: 262_144,
335 },
336 tournament: TournamentConfig {
337 codecs: vec!["store".into(), "lz4".into(), "zstd".into(), "brotli".into()],
338 min_size_threshold: 256,
339 skip_for_binary: false,
340 short_circuit_threshold: 200,
341 },
342 codec_tunables: CodecTunables {
343 brotli: crate::config::BrotliTunables {
344 quality: 11,
345 window: 22,
346 },
347 lzma: crate::config::LzmaTunables {
348 dict_size_mb: 64,
349 use_optimal_parser: true,
350 ..crate::config::LzmaTunables::default()
351 },
352 ..CodecTunables::default()
353 },
354 encryption: EncryptionConfig {
355 aead: "chacha20-poly1305".into(),
356 key_wrap: "x25519-hkdf".into(),
357 },
358 dictionaries: DictionaryConfig {
359 enabled: true,
360 min_class_size: 50,
361 max_dict_size: 131_072,
362 trainer: "frequency".into(),
363 },
364 mode: crate::config::ImageMode::ReadOnly,
365 write_codec: "lz4".into(),
366 turnover_threshold: 0,
367 skip_chunking: false,
368 }
369}
370
371#[must_use]
381pub fn max_write() -> WriteConfig {
382 WriteConfig {
383 defaults: Defaults {
384 text_codec: "lz4".into(),
385 binary_codec: "lz4".into(),
386 metadata_codec: "lz4".into(),
387 metadata_quality: 1,
388 inline_threshold: 4096,
389 },
390 categorizers: vec![],
391 chunking: ChunkingConfig {
392 name: "fastcdc".into(),
393 avg_chunk_size: 131_072,
394 min_chunk_size: 16_384,
395 max_chunk_size: 524_288,
396 },
397 tournament: TournamentConfig {
398 codecs: vec!["store".into(), "lz4".into()],
399 min_size_threshold: 0,
400 skip_for_binary: true,
401 short_circuit_threshold: 500,
402 },
403 codec_tunables: CodecTunables::default(),
404 encryption: EncryptionConfig {
405 aead: "chacha20-poly1305".into(),
406 key_wrap: "x25519-hkdf".into(),
407 },
408 dictionaries: DictionaryConfig {
409 enabled: false,
410 min_class_size: 0,
411 max_dict_size: 0,
412 trainer: "frequency".into(),
413 },
414 mode: crate::config::ImageMode::ReadOnly,
415 write_codec: "lz4".into(),
416 turnover_threshold: 0,
417 skip_chunking: true,
418 }
419}
420
421#[must_use]
430pub fn max_write_rw() -> WriteConfig {
431 WriteConfig {
432 defaults: Defaults {
433 text_codec: "lz4".into(),
434 binary_codec: "lz4".into(),
435 metadata_codec: "lz4".into(),
436 metadata_quality: 1,
437 inline_threshold: 4096,
438 },
439 categorizers: vec![],
440 chunking: ChunkingConfig {
441 name: "fastcdc".into(),
442 avg_chunk_size: 131_072,
443 min_chunk_size: 16_384,
444 max_chunk_size: 524_288,
445 },
446 tournament: TournamentConfig {
447 codecs: vec!["store".into(), "lz4".into()],
448 min_size_threshold: 0,
449 skip_for_binary: true,
450 short_circuit_threshold: 500,
451 },
452 codec_tunables: CodecTunables::default(),
453 mode: crate::config::ImageMode::ReadWrite(crate::config::RWMode::CopyOnWrite),
454 write_codec: "lz4".into(),
455 turnover_threshold: 500,
456 skip_chunking: true,
457 encryption: EncryptionConfig {
458 aead: "chacha20-poly1305".into(),
459 key_wrap: "x25519-hkdf".into(),
460 },
461 dictionaries: DictionaryConfig {
462 enabled: false,
463 min_class_size: 0,
464 max_dict_size: 0,
465 trainer: "frequency".into(),
466 },
467 }
468}
469
470#[must_use]
479pub fn max_read_rw() -> WriteConfig {
480 WriteConfig {
481 defaults: Defaults {
482 text_codec: "zstd".into(),
483 binary_codec: "zstd".into(),
484 metadata_codec: "zstd".into(),
485 metadata_quality: 6,
486 inline_threshold: 8192,
487 },
488 categorizers: crate::config::defaults::all_v0_1(),
489 chunking: ChunkingConfig {
490 name: "fastcdc".into(),
491 avg_chunk_size: 65_536,
492 min_chunk_size: 8192,
493 max_chunk_size: 262_144,
494 },
495 tournament: TournamentConfig {
496 codecs: vec!["store".into(), "lz4".into(), "zstd".into(), "brotli".into()],
497 min_size_threshold: 256,
498 skip_for_binary: false,
499 short_circuit_threshold: 200,
500 },
501 codec_tunables: CodecTunables {
502 brotli: crate::config::BrotliTunables {
503 quality: 11,
504 window: 22,
505 },
506 ..CodecTunables::default()
507 },
508 mode: crate::config::ImageMode::ReadWrite(crate::config::RWMode::UpdateInPlace),
509 write_codec: "zstd".into(),
510 turnover_threshold: 1000,
511 skip_chunking: false,
512 encryption: EncryptionConfig {
513 aead: "chacha20-poly1305".into(),
514 key_wrap: "x25519-hkdf".into(),
515 },
516 dictionaries: DictionaryConfig {
517 enabled: true,
518 min_class_size: 50,
519 max_dict_size: 131_072,
520 trainer: "frequency".into(),
521 },
522 }
523}
524
525#[must_use]
533pub fn balanced_rw() -> WriteConfig {
534 WriteConfig {
535 defaults: Defaults {
536 text_codec: "zstd".into(),
537 binary_codec: "lz4".into(),
538 metadata_codec: "zstd".into(),
539 metadata_quality: 3,
540 inline_threshold: 4096,
541 },
542 categorizers: crate::config::defaults::all_v0_1(),
543 chunking: ChunkingConfig {
544 name: "fastcdc".into(),
545 avg_chunk_size: 16_384,
546 min_chunk_size: 2048,
547 max_chunk_size: 65_536,
548 },
549 tournament: TournamentConfig {
550 codecs: vec!["store".into(), "lz4".into(), "zstd".into()],
551 min_size_threshold: 256,
552 skip_for_binary: true,
553 short_circuit_threshold: 250,
554 },
555 codec_tunables: CodecTunables {
556 brotli: crate::config::BrotliTunables {
557 quality: 5,
558 window: 22,
559 },
560 ..CodecTunables::default()
561 },
562 mode: crate::config::ImageMode::ReadWrite(crate::config::RWMode::UpdateInPlace),
563 write_codec: "zstd".into(),
564 turnover_threshold: 1000,
565 skip_chunking: false,
566 encryption: EncryptionConfig {
567 aead: "chacha20-poly1305".into(),
568 key_wrap: "x25519-hkdf".into(),
569 },
570 dictionaries: DictionaryConfig {
571 enabled: true,
572 min_class_size: 100,
573 max_dict_size: 65_536,
574 trainer: "frequency".into(),
575 },
576 }
577}
578
579#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
582#[serde(untagged)]
583pub enum ProfileSpec {
584 Preset(String),
586 Custom(CustomProfile),
588}
589
590#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
593pub struct CustomProfile {
594 pub name: String,
595 #[serde(default = "default_text")]
596 pub text_codec: String,
597 #[serde(default = "default_binary")]
598 pub binary_codec: String,
599 #[serde(default = "default_quality")]
600 pub brotli_quality: u8,
601 #[serde(default)]
602 pub chunk_avg_size: u32,
603 #[serde(default)]
604 pub skip_tournament_for_binary: bool,
605 #[serde(default = "default_true")]
606 pub enable_flac: bool,
607 #[serde(default = "default_true")]
608 pub enable_ricepp: bool,
609}
610
611fn default_text() -> String {
612 "brotli".into()
613}
614fn default_binary() -> String {
615 "lz4".into()
616}
617fn default_quality() -> u8 {
618 5
619}
620fn default_true() -> bool {
621 true
622}
623
624pub fn resolve(spec: &ProfileSpec) -> Option<WriteConfig> {
626 match spec {
627 ProfileSpec::Preset(name) => select(name),
628 ProfileSpec::Custom(custom) => {
629 let mut config = balanced();
630 if !custom.text_codec.is_empty() {
631 config.defaults.text_codec = custom.text_codec.clone();
632 }
633 if !custom.binary_codec.is_empty() {
634 config.defaults.binary_codec = custom.binary_codec.clone();
635 }
636 if custom.brotli_quality > 0 {
637 config.codec_tunables.brotli.quality = custom.brotli_quality;
638 }
639 if custom.chunk_avg_size > 0 {
640 config.chunking.avg_chunk_size = custom.chunk_avg_size;
641 }
642 config.tournament.skip_for_binary = custom.skip_tournament_for_binary;
643 if !custom.enable_flac {
644 config.categorizers.retain(|c| c.name != "pcm_audio");
645 }
646 if !custom.enable_ricepp {
647 config.categorizers.retain(|c| c.name != "fits");
648 }
649 Some(config)
650 }
651 }
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
659 fn all_builtins_resolve() {
660 for name in [
661 MAX_RATIO,
662 MAX_SPEED,
663 BALANCED,
664 COMPETITIVE,
665 MAX_READ,
666 MAX_WRITE,
667 MAX_WRITE_RW,
668 MAX_READ_RW,
669 BALANCED_RW,
670 ] {
671 let config = select(name).expect("profile exists");
672 config.validate().expect("validates");
673 }
674 }
675
676 #[test]
677 fn competitive_uses_lz4_for_binary() {
678 let config = competitive();
679 assert_eq!(config.binary_codec_id().unwrap(), 0x01); }
681
682 #[test]
683 fn competitive_uses_zstd_for_text() {
684 let config = competitive();
685 assert_eq!(config.text_codec_id().unwrap(), 0x02); }
687
688 #[test]
689 fn max_speed_disables_categorizers() {
690 let config = max_speed();
691 assert!(config.categorizers.is_empty());
692 }
693
694 #[test]
695 fn max_ratio_enables_ppmd() {
696 let config = max_ratio();
697 assert_eq!(config.codec_tunables.ppmd7.memory_budget_mb, 256);
698 }
699
700 #[test]
701 fn custom_profile_inherits_balanced() {
702 let spec = ProfileSpec::Custom(CustomProfile {
703 name: "test".into(),
704 brotli_quality: 9,
705 ..CustomProfile::default()
706 });
707 let config = resolve(&spec).expect("resolves");
708 assert_eq!(config.codec_tunables.brotli.quality, 9);
709 assert_eq!(config.defaults.text_codec, "brotli");
711 }
712}