Skip to main content

cadmpeg_ir/decode/
policy.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Decode modes, resource ceilings, and input-proportional allowances.
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7const MIB: u64 = 1024 * 1024;
8const GIB: u64 = 1024 * MIB;
9
10/// Whether a successful decode may report mandatory transfer losses.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
12#[serde(rename_all = "snake_case")]
13pub enum DecodeMode {
14    /// Reject a completed decode that reports a mandatory transfer loss.
15    Strict,
16    /// Return the decoded result with its loss report.
17    #[default]
18    Salvage,
19}
20
21/// Absolute resource ceilings.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
23pub struct ResourceLimits {
24    /// Maximum physical input bytes read at the root.
25    pub max_input_bytes: u64,
26    /// Maximum cumulative decompressed bytes across all expansions.
27    pub max_decompressed_bytes_total: u64,
28    /// Maximum decompressed bytes produced by any single expansion.
29    pub max_decompressed_bytes_per_expand: u64,
30}
31
32impl ResourceLimits {
33    /// Generous ceilings for interactive desktop use; the default profile.
34    pub const fn desktop() -> Self {
35        Self {
36            max_input_bytes: 4 * GIB,
37            max_decompressed_bytes_total: 8 * GIB,
38            max_decompressed_bytes_per_expand: 2 * GIB,
39        }
40    }
41
42    /// Tight ceilings for unattended service use.
43    pub const fn service() -> Self {
44        Self {
45            max_input_bytes: 256 * MIB,
46            max_decompressed_bytes_total: GIB,
47            max_decompressed_bytes_per_expand: 256 * MIB,
48        }
49    }
50}
51
52impl Default for ResourceLimits {
53    fn default() -> Self {
54        Self::desktop()
55    }
56}
57
58/// Decode mode and resource ceilings.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
60pub struct DecodePolicy {
61    /// How mandatory transfer losses are handled.
62    pub mode: DecodeMode,
63    /// The absolute ceilings.
64    pub limits: ResourceLimits,
65}
66
67impl DecodePolicy {
68    /// The default desktop profile in salvage mode.
69    pub const fn desktop() -> Self {
70        Self {
71            mode: DecodeMode::Salvage,
72            limits: ResourceLimits::desktop(),
73        }
74    }
75
76    /// The tight service profile in salvage mode.
77    pub const fn service() -> Self {
78        Self {
79            mode: DecodeMode::Salvage,
80            limits: ResourceLimits::service(),
81        }
82    }
83}
84
85impl Default for DecodePolicy {
86    fn default() -> Self {
87        Self::desktop()
88    }
89}
90
91/// Resource options for container inspection.
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
93pub struct InspectOptions {
94    /// The absolute ceilings applied during inspection.
95    pub limits: ResourceLimits,
96}
97
98pub(crate) const DECOMPRESSED_TOTAL_BASE: u64 = 16 * MIB;
99pub(crate) const DECOMPRESSED_TOTAL_PER_INPUT_BYTE: u64 = 1000;
100pub(crate) const DECOMPRESSED_PER_EXPAND_BASE: u64 = 16 * MIB;
101pub(crate) const DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE: u64 = 256;
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn desktop_ceilings_are_explicit() {
109        let d = ResourceLimits::desktop();
110        assert_eq!(d.max_input_bytes, 4 * GIB);
111        assert_eq!(d.max_decompressed_bytes_total, 8 * GIB);
112        assert_eq!(d.max_decompressed_bytes_per_expand, 2 * GIB);
113    }
114
115    #[test]
116    fn service_ceilings_are_explicit() {
117        let s = ResourceLimits::service();
118        assert_eq!(s.max_input_bytes, 256 * MIB);
119        assert_eq!(s.max_decompressed_bytes_total, GIB);
120        assert_eq!(s.max_decompressed_bytes_per_expand, 256 * MIB);
121    }
122}