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