Skip to main content

qrcode_core/
limits.rs

1//! Resource budgets applied at QR-code construction boundaries.
2
3use crate::types::{QrError, QrResult, Version};
4
5/// The largest input accepted by the default resource budget.
6///
7/// This is deliberately a conservative upper bound for a QR byte payload.
8/// Individual versions and error-correction levels usually have a smaller
9/// capacity and remain the final authority during encoding.
10pub const DEFAULT_MAX_DATA_LENGTH: usize = 7_089;
11
12/// The largest rendered dimension allowed by the default resource budget.
13pub const DEFAULT_MAX_RENDER_SIZE: (u32, u32) = (4_096, 4_096);
14
15/// Explicit resource budgets for bounded QR-code construction.
16///
17/// [`QrCode::with_limits`](https://docs.rs/qrcode-rs/latest/qrcode_rs/struct.QrCode.html#method.with_limits)
18/// applies these limits before allocating encoder state and after selecting
19/// the resulting symbol dimensions. With the facade crate's `std` feature,
20/// `encoding_timeout` is checked at synchronous construction boundaries. It is
21/// not a preemptive interrupt for an in-flight CPU step. The dimensions are the
22/// maximum width and height of the symbol passed to a renderer; a renderer may
23/// impose a stricter pixel budget of its own.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct ResourceLimits {
27    /// Maximum number of input bytes accepted before parsing or allocation.
28    pub max_data_length: usize,
29    /// Maximum normal QR version considered by automatic version selection.
30    pub max_version: Version,
31    /// Maximum `(width, height)` accepted for the generated module symbol.
32    pub max_render_size: (u32, u32),
33    /// Optional synchronous encoding timeout budget in milliseconds.
34    ///
35    /// `None` disables timeout checks. `Some(0)` is rejected as a malformed
36    /// budget because it cannot describe useful work.
37    pub encoding_timeout: Option<u64>,
38}
39
40impl Default for ResourceLimits {
41    fn default() -> Self {
42        Self {
43            max_data_length: DEFAULT_MAX_DATA_LENGTH,
44            max_version: Version::Normal(40),
45            max_render_size: DEFAULT_MAX_RENDER_SIZE,
46            encoding_timeout: None,
47        }
48    }
49}
50
51impl ResourceLimits {
52    /// Creates an explicit resource budget.
53    #[must_use]
54    pub const fn new(max_data_length: usize, max_version: Version, max_render_size: (u32, u32)) -> Self {
55        Self { max_data_length, max_version, max_render_size, encoding_timeout: None }
56    }
57
58    /// Returns this budget with an encoding timeout in milliseconds.
59    ///
60    /// The facade crate enforces this budget at synchronous construction
61    /// boundaries when `std` is enabled.
62    #[must_use]
63    pub const fn with_encoding_timeout_millis(mut self, timeout_ms: u64) -> Self {
64        self.encoding_timeout = Some(timeout_ms);
65        self
66    }
67
68    /// Validates the shape of this budget without inspecting input data.
69    ///
70    /// Automatic construction currently targets normal QR versions, so Micro
71    /// versions are rejected here rather than being silently interpreted as a
72    /// normal-version cap. Zero render dimensions cannot describe a symbol.
73    pub fn validate(self) -> QrResult<()> {
74        let valid_version = matches!(self.max_version, Version::Normal(1..=40));
75        let valid_render_size = self.max_render_size.0 != 0 && self.max_render_size.1 != 0;
76        let valid_timeout = self.encoding_timeout != Some(0);
77        if valid_version && valid_render_size && valid_timeout { Ok(()) } else { Err(QrError::InvalidResourceLimits) }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::{DEFAULT_MAX_DATA_LENGTH, DEFAULT_MAX_RENDER_SIZE, ResourceLimits};
84    use crate::types::Version;
85
86    #[test]
87    fn default_budget_is_bounded() {
88        let limits = ResourceLimits::default();
89        assert_eq!(limits.max_data_length, DEFAULT_MAX_DATA_LENGTH);
90        assert_eq!(limits.max_version, Version::Normal(40));
91        assert_eq!(limits.max_render_size, DEFAULT_MAX_RENDER_SIZE);
92        assert_eq!(limits.encoding_timeout, None);
93        assert!(limits.validate().is_ok());
94    }
95
96    #[test]
97    fn invalid_budget_is_rejected() {
98        assert!(ResourceLimits::new(1, Version::Micro(4), (1, 1)).validate().is_err());
99        assert!(ResourceLimits::new(1, Version::Normal(1), (0, 1)).validate().is_err());
100        assert!(ResourceLimits::new(1, Version::Normal(1), (1, 1)).with_encoding_timeout_millis(0).validate().is_err());
101    }
102
103    #[test]
104    fn timeout_budget_is_optional() {
105        let limits = ResourceLimits::new(1, Version::Normal(1), (1, 1)).with_encoding_timeout_millis(10);
106
107        assert_eq!(limits.encoding_timeout, Some(10));
108        assert!(limits.validate().is_ok());
109    }
110}