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