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 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
88 shared_inline: true,
89 max_drop_size: 0,
93 seekable_drops: false,
94 inline_threshold: 8192,
95 },
96 categorizers: crate::config::defaults::all_v0_1(),
97 chunking: ChunkingConfig {
98 name: "fastcdc".into(),
99 avg_chunk_size: 65_536,
100 min_chunk_size: 8192,
101 max_chunk_size: 262_144,
102 },
103 tournament: TournamentConfig {
104 codecs: vec![
105 "store".into(),
106 "lz4".into(),
107 "lz4-hc".into(),
108 "zstd".into(),
109 "brotli".into(),
110 "ppmd".into(),
111 "bzip2".into(),
112 ],
113 min_size_threshold: 256,
114 skip_for_binary: false,
115 short_circuit_threshold: 0,
116 },
117 codec_tunables: CodecTunables {
118 ppmd7: crate::config::Ppmd7Tunables {
119 order: 6,
120 memory_budget_mb: 256,
121 },
122 ppmd8: crate::config::Ppmd8Tunables {
123 order: 8,
124 memory_budget_mb: 128,
125 },
126 brotli: crate::config::BrotliTunables {
127 quality: 11,
128 window: 24,
129 },
130 lzma: crate::config::LzmaTunables {
131 lc: 3,
132 lp: 0,
133 pb: 2,
134 dict_size_mb: 64,
135 use_optimal_parser: true,
136 },
137 bzip2: crate::config::Bzip2Tunables { block_size_kb: 900 },
138 },
139 encryption: EncryptionConfig {
140 aead: "chacha20-poly1305".into(),
141 key_wrap: "x25519-hkdf".into(),
142 },
143 dictionaries: DictionaryConfig {
144 enabled: true,
145 min_class_size: 50,
146 max_dict_size: 131_072,
147 trainer: "frequency".into(),
148 },
149 mode: crate::config::ImageMode::ReadOnly,
150 write_codec: "lz4".into(),
151 turnover_threshold: 0,
152 skip_chunking: false,
153 }
154}
155
156#[must_use]
165pub fn max_speed() -> WriteConfig {
166 WriteConfig {
167 defaults: Defaults {
168 text_codec: "lz4".into(),
169 binary_codec: "lz4".into(),
170 metadata_codec: "lz4".into(),
171 metadata_quality: 1,
172 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
173 shared_inline: true,
174 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
175 seekable_drops: true,
176 inline_threshold: 4096,
177 },
178 categorizers: vec![],
179 chunking: ChunkingConfig {
180 name: "fastcdc".into(),
181 avg_chunk_size: 4096,
182 min_chunk_size: 512,
183 max_chunk_size: 16_384,
184 },
185 tournament: TournamentConfig {
186 codecs: vec!["store".into(), "lz4".into()],
187 min_size_threshold: 0,
188 skip_for_binary: true,
189 short_circuit_threshold: 500,
190 },
191 codec_tunables: CodecTunables {
192 brotli: crate::config::BrotliTunables {
193 quality: 0,
194 window: 10,
195 },
196 ..CodecTunables::default()
197 },
198 encryption: EncryptionConfig {
199 aead: "chacha20-poly1305".into(),
200 key_wrap: "x25519-hkdf".into(),
201 },
202 dictionaries: DictionaryConfig {
203 enabled: false,
204 min_class_size: 0,
205 max_dict_size: 0,
206 trainer: "frequency".into(),
207 },
208 mode: crate::config::ImageMode::ReadOnly,
209 write_codec: "lz4".into(),
210 turnover_threshold: 0,
211 skip_chunking: false,
212 }
213}
214
215#[must_use]
223pub fn balanced() -> WriteConfig {
224 WriteConfig {
225 defaults: Defaults {
226 text_codec: "brotli".into(),
227 binary_codec: "lz4".into(),
228 metadata_codec: "zstd".into(),
229 metadata_quality: 3,
230 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
231 shared_inline: true,
232 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
233 seekable_drops: true,
234 inline_threshold: 4096,
235 },
236 categorizers: crate::config::defaults::all_v0_1(),
237 chunking: ChunkingConfig {
238 name: "fastcdc".into(),
239 avg_chunk_size: 16_384,
240 min_chunk_size: 2048,
241 max_chunk_size: 65_536,
242 },
243 tournament: TournamentConfig {
244 codecs: vec!["store".into(), "lz4".into(), "brotli".into()],
245 min_size_threshold: 256,
246 skip_for_binary: true,
247 short_circuit_threshold: 250,
248 },
249 codec_tunables: CodecTunables {
250 brotli: crate::config::BrotliTunables {
251 quality: 5,
252 window: 22,
253 },
254 ..CodecTunables::default()
255 },
256 encryption: EncryptionConfig {
257 aead: "chacha20-poly1305".into(),
258 key_wrap: "x25519-hkdf".into(),
259 },
260 dictionaries: DictionaryConfig {
261 enabled: true,
262 min_class_size: 100,
263 max_dict_size: 65_536,
264 trainer: "frequency".into(),
265 },
266 mode: crate::config::ImageMode::ReadOnly,
267 write_codec: "lz4".into(),
268 turnover_threshold: 0,
269 skip_chunking: false,
270 }
271}
272
273#[must_use]
278pub fn competitive() -> WriteConfig {
279 WriteConfig {
280 defaults: Defaults {
281 text_codec: "zstd".into(),
282 binary_codec: "lz4".into(),
283 metadata_codec: "zstd".into(),
284 metadata_quality: 3,
285 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
286 shared_inline: true,
287 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
288 seekable_drops: true,
289 inline_threshold: 4096,
290 },
291 categorizers: crate::config::defaults::all_v0_1(),
292 chunking: ChunkingConfig {
293 name: "fastcdc".into(),
294 avg_chunk_size: 8192,
295 min_chunk_size: 1024,
296 max_chunk_size: 65_536,
297 },
298 tournament: TournamentConfig {
299 codecs: vec!["store".into(), "lz4".into(), "brotli".into()],
300 min_size_threshold: 0,
301 skip_for_binary: true,
302 short_circuit_threshold: 250,
303 },
304 codec_tunables: CodecTunables {
305 brotli: crate::config::BrotliTunables {
306 quality: 5,
307 window: 22,
308 },
309 ..CodecTunables::default()
310 },
311 encryption: EncryptionConfig {
312 aead: "chacha20-poly1305".into(),
313 key_wrap: "x25519-hkdf".into(),
314 },
315 dictionaries: DictionaryConfig {
316 enabled: false,
317 min_class_size: 0,
318 max_dict_size: 0,
319 trainer: "frequency".into(),
320 },
321 mode: crate::config::ImageMode::ReadOnly,
322 write_codec: "lz4".into(),
323 turnover_threshold: 0,
324 skip_chunking: false,
325 }
326}
327
328#[must_use]
339pub fn max_read() -> WriteConfig {
340 WriteConfig {
341 defaults: Defaults {
342 text_codec: "zstd".into(),
343 binary_codec: "zstd".into(),
344 metadata_codec: "zstd".into(),
345 metadata_quality: 11,
346 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
347 shared_inline: true,
348 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
349 seekable_drops: true,
350 inline_threshold: 8192,
351 },
352 categorizers: crate::config::defaults::all_v0_1(),
353 chunking: ChunkingConfig {
354 name: "fastcdc".into(),
355 avg_chunk_size: 65_536,
356 min_chunk_size: 8192,
357 max_chunk_size: 262_144,
358 },
359 tournament: TournamentConfig {
360 codecs: vec!["store".into(), "lz4".into(), "zstd".into(), "brotli".into()],
361 min_size_threshold: 256,
362 skip_for_binary: false,
363 short_circuit_threshold: 200,
364 },
365 codec_tunables: CodecTunables {
366 brotli: crate::config::BrotliTunables {
367 quality: 11,
368 window: 22,
369 },
370 lzma: crate::config::LzmaTunables {
371 dict_size_mb: 64,
372 use_optimal_parser: true,
373 ..crate::config::LzmaTunables::default()
374 },
375 ..CodecTunables::default()
376 },
377 encryption: EncryptionConfig {
378 aead: "chacha20-poly1305".into(),
379 key_wrap: "x25519-hkdf".into(),
380 },
381 dictionaries: DictionaryConfig {
382 enabled: true,
383 min_class_size: 50,
384 max_dict_size: 131_072,
385 trainer: "frequency".into(),
386 },
387 mode: crate::config::ImageMode::ReadOnly,
388 write_codec: "lz4".into(),
389 turnover_threshold: 0,
390 skip_chunking: false,
391 }
392}
393
394#[must_use]
404pub fn max_write() -> WriteConfig {
405 WriteConfig {
406 defaults: Defaults {
407 text_codec: "lz4".into(),
408 binary_codec: "lz4".into(),
409 metadata_codec: "lz4".into(),
410 metadata_quality: 1,
411 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
412 shared_inline: true,
413 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
414 seekable_drops: true,
415 inline_threshold: 4096,
416 },
417 categorizers: vec![],
418 chunking: ChunkingConfig {
419 name: "fastcdc".into(),
420 avg_chunk_size: 131_072,
421 min_chunk_size: 16_384,
422 max_chunk_size: 524_288,
423 },
424 tournament: TournamentConfig {
425 codecs: vec!["store".into(), "lz4".into()],
426 min_size_threshold: 0,
427 skip_for_binary: true,
428 short_circuit_threshold: 500,
429 },
430 codec_tunables: CodecTunables::default(),
431 encryption: EncryptionConfig {
432 aead: "chacha20-poly1305".into(),
433 key_wrap: "x25519-hkdf".into(),
434 },
435 dictionaries: DictionaryConfig {
436 enabled: false,
437 min_class_size: 0,
438 max_dict_size: 0,
439 trainer: "frequency".into(),
440 },
441 mode: crate::config::ImageMode::ReadOnly,
442 write_codec: "lz4".into(),
443 turnover_threshold: 0,
444 skip_chunking: true,
445 }
446}
447
448#[must_use]
457pub fn max_write_rw() -> WriteConfig {
458 WriteConfig {
459 defaults: Defaults {
460 text_codec: "lz4".into(),
461 binary_codec: "lz4".into(),
462 metadata_codec: "lz4".into(),
463 metadata_quality: 1,
464 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
465 shared_inline: true,
466 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
467 seekable_drops: true,
468 inline_threshold: 4096,
469 },
470 categorizers: vec![],
471 chunking: ChunkingConfig {
472 name: "fastcdc".into(),
473 avg_chunk_size: 131_072,
474 min_chunk_size: 16_384,
475 max_chunk_size: 524_288,
476 },
477 tournament: TournamentConfig {
478 codecs: vec!["store".into(), "lz4".into()],
479 min_size_threshold: 0,
480 skip_for_binary: true,
481 short_circuit_threshold: 500,
482 },
483 codec_tunables: CodecTunables::default(),
484 mode: crate::config::ImageMode::ReadWrite(crate::config::RWMode::CopyOnWrite),
485 write_codec: "lz4".into(),
486 turnover_threshold: 500,
487 skip_chunking: true,
488 encryption: EncryptionConfig {
489 aead: "chacha20-poly1305".into(),
490 key_wrap: "x25519-hkdf".into(),
491 },
492 dictionaries: DictionaryConfig {
493 enabled: false,
494 min_class_size: 0,
495 max_dict_size: 0,
496 trainer: "frequency".into(),
497 },
498 }
499}
500
501#[must_use]
510pub fn max_read_rw() -> WriteConfig {
511 WriteConfig {
512 defaults: Defaults {
513 text_codec: "zstd".into(),
514 binary_codec: "zstd".into(),
515 metadata_codec: "zstd".into(),
516 metadata_quality: 6,
517 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
518 shared_inline: true,
519 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
520 seekable_drops: true,
521 inline_threshold: 8192,
522 },
523 categorizers: crate::config::defaults::all_v0_1(),
524 chunking: ChunkingConfig {
525 name: "fastcdc".into(),
526 avg_chunk_size: 65_536,
527 min_chunk_size: 8192,
528 max_chunk_size: 262_144,
529 },
530 tournament: TournamentConfig {
531 codecs: vec!["store".into(), "lz4".into(), "zstd".into(), "brotli".into()],
532 min_size_threshold: 256,
533 skip_for_binary: false,
534 short_circuit_threshold: 200,
535 },
536 codec_tunables: CodecTunables {
537 brotli: crate::config::BrotliTunables {
538 quality: 11,
539 window: 22,
540 },
541 ..CodecTunables::default()
542 },
543 mode: crate::config::ImageMode::ReadWrite(crate::config::RWMode::UpdateInPlace),
544 write_codec: "zstd".into(),
545 turnover_threshold: 1000,
546 skip_chunking: false,
547 encryption: EncryptionConfig {
548 aead: "chacha20-poly1305".into(),
549 key_wrap: "x25519-hkdf".into(),
550 },
551 dictionaries: DictionaryConfig {
552 enabled: true,
553 min_class_size: 50,
554 max_dict_size: 131_072,
555 trainer: "frequency".into(),
556 },
557 }
558}
559
560#[must_use]
568pub fn balanced_rw() -> WriteConfig {
569 WriteConfig {
570 defaults: Defaults {
571 text_codec: "zstd".into(),
572 binary_codec: "lz4".into(),
573 metadata_codec: "zstd".into(),
574 metadata_quality: 3,
575 metadata_externalize_threshold: crate::METADATA_EXTERNALIZE_THRESHOLD,
576 shared_inline: true,
577 max_drop_size: crate::config::DEFAULT_MAX_DROP_SIZE,
578 seekable_drops: true,
579 inline_threshold: 4096,
580 },
581 categorizers: crate::config::defaults::all_v0_1(),
582 chunking: ChunkingConfig {
583 name: "fastcdc".into(),
584 avg_chunk_size: 16_384,
585 min_chunk_size: 2048,
586 max_chunk_size: 65_536,
587 },
588 tournament: TournamentConfig {
589 codecs: vec!["store".into(), "lz4".into(), "zstd".into()],
590 min_size_threshold: 256,
591 skip_for_binary: true,
592 short_circuit_threshold: 250,
593 },
594 codec_tunables: CodecTunables {
595 brotli: crate::config::BrotliTunables {
596 quality: 5,
597 window: 22,
598 },
599 ..CodecTunables::default()
600 },
601 mode: crate::config::ImageMode::ReadWrite(crate::config::RWMode::UpdateInPlace),
602 write_codec: "zstd".into(),
603 turnover_threshold: 1000,
604 skip_chunking: false,
605 encryption: EncryptionConfig {
606 aead: "chacha20-poly1305".into(),
607 key_wrap: "x25519-hkdf".into(),
608 },
609 dictionaries: DictionaryConfig {
610 enabled: true,
611 min_class_size: 100,
612 max_dict_size: 65_536,
613 trainer: "frequency".into(),
614 },
615 }
616}
617
618#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
621#[serde(untagged)]
622pub enum ProfileSpec {
623 Preset(String),
625 Custom(CustomProfile),
627}
628
629#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
632pub struct CustomProfile {
633 pub name: String,
634 #[serde(default = "default_text")]
635 pub text_codec: String,
636 #[serde(default = "default_binary")]
637 pub binary_codec: String,
638 #[serde(default = "default_quality")]
639 pub brotli_quality: u8,
640 #[serde(default)]
641 pub chunk_avg_size: u32,
642 #[serde(default)]
643 pub skip_tournament_for_binary: bool,
644 #[serde(default = "default_true")]
645 pub enable_flac: bool,
646 #[serde(default = "default_true")]
647 pub enable_ricepp: bool,
648}
649
650fn default_text() -> String {
651 "brotli".into()
652}
653fn default_binary() -> String {
654 "lz4".into()
655}
656fn default_quality() -> u8 {
657 5
658}
659fn default_true() -> bool {
660 true
661}
662
663pub fn resolve(spec: &ProfileSpec) -> Option<WriteConfig> {
665 match spec {
666 ProfileSpec::Preset(name) => select(name),
667 ProfileSpec::Custom(custom) => {
668 let mut config = balanced();
669 if !custom.text_codec.is_empty() {
670 config.defaults.text_codec = custom.text_codec.clone();
671 }
672 if !custom.binary_codec.is_empty() {
673 config.defaults.binary_codec = custom.binary_codec.clone();
674 }
675 if custom.brotli_quality > 0 {
676 config.codec_tunables.brotli.quality = custom.brotli_quality;
677 }
678 if custom.chunk_avg_size > 0 {
679 config.chunking.avg_chunk_size = custom.chunk_avg_size;
680 }
681 config.tournament.skip_for_binary = custom.skip_tournament_for_binary;
682 if !custom.enable_flac {
683 config.categorizers.retain(|c| c.name != "pcm_audio");
684 }
685 if !custom.enable_ricepp {
686 config.categorizers.retain(|c| c.name != "fits");
687 }
688 Some(config)
689 }
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 #[test]
698 fn all_builtins_resolve() {
699 for name in [
700 MAX_RATIO,
701 MAX_SPEED,
702 BALANCED,
703 COMPETITIVE,
704 MAX_READ,
705 MAX_WRITE,
706 MAX_WRITE_RW,
707 MAX_READ_RW,
708 BALANCED_RW,
709 ] {
710 let config = select(name).expect("profile exists");
711 config.validate().expect("validates");
712 }
713 }
714
715 #[test]
716 fn competitive_uses_lz4_for_binary() {
717 let config = competitive();
718 assert_eq!(config.binary_codec_id().unwrap(), 0x01); }
720
721 #[test]
722 fn competitive_uses_zstd_for_text() {
723 let config = competitive();
724 assert_eq!(config.text_codec_id().unwrap(), 0x02); }
726
727 #[test]
728 fn max_speed_disables_categorizers() {
729 let config = max_speed();
730 assert!(config.categorizers.is_empty());
731 }
732
733 #[test]
734 fn max_ratio_enables_ppmd() {
735 let config = max_ratio();
736 assert_eq!(config.codec_tunables.ppmd7.memory_budget_mb, 256);
737 }
738
739 #[test]
740 fn custom_profile_inherits_balanced() {
741 let spec = ProfileSpec::Custom(CustomProfile {
742 name: "test".into(),
743 brotli_quality: 9,
744 ..CustomProfile::default()
745 });
746 let config = resolve(&spec).expect("resolves");
747 assert_eq!(config.codec_tunables.brotli.quality, 9);
748 assert_eq!(config.defaults.text_codec, "brotli");
750 }
751}