molframe_core/io/input/limits.rs
1//! Hostile-input ceilings shared by readers and decoders.
2
3use crate::diagnostic::{Code, Diagnostic};
4
5/// 16 GiB: the largest published mmCIF files are under 2 GiB uncompressed, so
6/// the ceiling sits an order of magnitude above any real entry while still
7/// refusing an input that could exhaust a workstation.
8const DEFAULT_DECOMPRESSED_BYTES: u64 = 16 << 30;
9/// 2^28 rows: far beyond the largest deposited structure (tens of millions of
10/// atoms) and consistent with the byte ceiling above.
11const DEFAULT_ROWS_PER_CATEGORY: u64 = 1 << 28;
12/// 2^24 distinct identifiers: real structures intern thousands.
13const DEFAULT_DICTIONARY_ENTRIES: u32 = 1 << 24;
14const DEFAULT_COMPRESSION_RATIO: u64 = 1_000;
15const DEFAULT_NESTING_DEPTH: u32 = 64;
16
17/// Ceilings that turn hostile input into diagnostics rather than exhaustion.
18#[derive(Clone, Copy, PartialEq, Eq, Debug)]
19pub struct Limits {
20 /// Largest accepted size after decompression, in bytes.
21 ///
22 /// Defaults to 16 GiB. The ratio ceiling ([`Self::compression_ratio`])
23 /// bounds expansion relative to the bytes supplied; this absolute ceiling
24 /// additionally stops a hostile or corrupt input from exhausting memory
25 /// before any parse. A caller reading a genuinely larger file raises it, or
26 /// uses [`Limits::unbounded`] to opt out of every absolute ceiling.
27 pub decompressed_bytes: u64,
28 /// Largest accepted ratio of decompressed to compressed size.
29 pub compression_ratio: u64,
30 /// Largest accepted declared row count for one category.
31 ///
32 /// Defaults to 2^28 rows; [`Limits::unbounded`] removes the ceiling.
33 pub rows_per_category: u64,
34 /// Deepest accepted nesting.
35 pub nesting_depth: u32,
36 /// Largest accepted identifier dictionary.
37 ///
38 /// Defaults to 2^24 entries; [`Limits::unbounded`] removes the ceiling.
39 pub dictionary_entries: u32,
40}
41
42impl Default for Limits {
43 fn default() -> Self {
44 Self {
45 decompressed_bytes: DEFAULT_DECOMPRESSED_BYTES,
46 compression_ratio: DEFAULT_COMPRESSION_RATIO,
47 rows_per_category: DEFAULT_ROWS_PER_CATEGORY,
48 nesting_depth: DEFAULT_NESTING_DEPTH,
49 dictionary_entries: DEFAULT_DICTIONARY_ENTRIES,
50 }
51 }
52}
53
54impl Limits {
55 /// Limits with no absolute ceiling, for trusted input of any size.
56 ///
57 /// The compression-ratio and nesting-depth ceilings still apply: they
58 /// protect against malformed input rather than large input.
59 #[must_use]
60 pub const fn unbounded() -> Self {
61 Self {
62 decompressed_bytes: u64::MAX,
63 compression_ratio: DEFAULT_COMPRESSION_RATIO,
64 rows_per_category: u64::MAX,
65 nesting_depth: DEFAULT_NESTING_DEPTH,
66 dictionary_entries: u32::MAX - 1,
67 }
68 }
69
70 /// Builds the registered finding for a violated ceiling.
71 #[must_use]
72 pub fn exceeded(limit: &'static str, value: impl std::fmt::Display) -> Diagnostic {
73 Diagnostic::new(Code::E1901)
74 .with_context("limit", limit)
75 .with_context("value", value.to_string())
76 }
77}