Skip to main content

ozlrip_decode/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(feature = "paranoid", forbid(unsafe_code))]
3
4extern crate alloc;
5
6use alloc::vec::Vec;
7use ozlrip_core::{FrameInfo, Limits, Result};
8
9mod execute;
10mod parse;
11mod standard;
12
13pub const DEFAULT_PLAN_CACHE_MAX_FRAME_BYTES: usize = 4096;
14
15/// Decoder configuration.
16///
17/// New options should be added here instead of creating new public function
18/// variants.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub struct Options {
21    /// Defensive parser, graph, allocation, and expansion limits.
22    pub limits: Limits,
23    /// Maximum frame size eligible for reusable decoder plan caching.
24    ///
25    /// Set to `0` to disable plan caching.
26    pub plan_cache_max_frame_bytes: usize,
27}
28
29impl Default for Options {
30    fn default() -> Self {
31        Self {
32            limits: Limits::default(),
33            plan_cache_max_frame_bytes: DEFAULT_PLAN_CACHE_MAX_FRAME_BYTES,
34        }
35    }
36}
37
38/// Reusable OpenZL decoder with scratch buffers and codec state.
39pub struct Decoder {
40    options: Options,
41    scratch: execute::DecodeScratch,
42    plan_cache: Option<CachedFramePlan>,
43    #[cfg(feature = "zstd")]
44    zstd: zrip::DecompressContext,
45}
46
47struct CachedFramePlan {
48    frame: Vec<u8>,
49    plan: parse::FramePlan,
50    direct_append_plans: Option<execute::DirectAppendChunkPlans>,
51}
52
53impl Decoder {
54    /// Creates a decoder with [`Options::default`].
55    pub fn new() -> Self {
56        Self::with_options(Options::default())
57    }
58
59    /// Creates a decoder with explicit options.
60    pub fn with_options(options: Options) -> Self {
61        Self {
62            options,
63            scratch: execute::DecodeScratch::new(),
64            plan_cache: None,
65            #[cfg(feature = "zstd")]
66            zstd: zrip::DecompressContext::new(),
67        }
68    }
69
70    pub fn options(&self) -> Options {
71        self.options
72    }
73
74    pub fn limits(&self) -> Limits {
75        self.options.limits
76    }
77
78    /// Decodes one OpenZL frame and appends the decoded bytes to `dst`.
79    ///
80    /// Returns the number of bytes appended. On error, `dst` is restored to its
81    /// original length.
82    pub fn decode_into(&mut self, input: &[u8], dst: &mut Vec<u8>) -> Result<usize> {
83        #[cfg(feature = "zstd")]
84        if let Some(frame) = parse::parse_single_zstd_frame(input, self.options.limits)? {
85            return execute::decode_single_zstd_frame_with_context(
86                input,
87                frame,
88                dst,
89                self.options.limits,
90                &mut self.zstd,
91            );
92        }
93        if let Some(cached) = self.plan_cache.as_ref()
94            && cached.frame == input
95        {
96            if let Some(direct_append_plans) = cached.direct_append_plans.as_ref() {
97                return execute::decode_plan_with_cached_direct_append_plans(
98                    input,
99                    &cached.plan,
100                    direct_append_plans,
101                    dst,
102                    self.options.limits,
103                    &mut self.scratch,
104                    #[cfg(feature = "zstd")]
105                    &mut self.zstd,
106                );
107            }
108            return execute::decode_plan_with_context(
109                input,
110                &cached.plan,
111                dst,
112                self.options.limits,
113                &mut self.scratch,
114                #[cfg(feature = "zstd")]
115                &mut self.zstd,
116            );
117        }
118
119        let plan = parse::parse_frame_plan(input, self.options.limits)?;
120        self.remember_frame_plan(input, &plan);
121        execute::decode_plan_with_context(
122            input,
123            &plan,
124            dst,
125            self.options.limits,
126            &mut self.scratch,
127            #[cfg(feature = "zstd")]
128            &mut self.zstd,
129        )
130    }
131
132    fn remember_frame_plan(&mut self, input: &[u8], plan: &parse::FramePlan) {
133        if self.options.plan_cache_max_frame_bytes == 0
134            || input.len() > self.options.plan_cache_max_frame_bytes
135        {
136            self.plan_cache = None;
137            return;
138        }
139
140        let mut frame = Vec::new();
141        if frame.try_reserve_exact(input.len()).is_err() {
142            self.plan_cache = None;
143            return;
144        }
145        frame.extend_from_slice(input);
146        let direct_append_plans = execute::prepare_direct_append_chunk_plans(plan)
147            .ok()
148            .flatten();
149        self.plan_cache = Some(CachedFramePlan {
150            frame,
151            plan: plan.clone(),
152            direct_append_plans,
153        });
154    }
155
156    /// Decodes one OpenZL frame into a new `Vec`.
157    pub fn decode(&mut self, input: &[u8]) -> Result<Vec<u8>> {
158        let mut output = Vec::new();
159        self.decode_into(input, &mut output)?;
160        Ok(output)
161    }
162
163    /// Parses and validates frame metadata without executing decode nodes.
164    pub fn inspect(&self, input: &[u8]) -> Result<FrameInfo> {
165        parse::inspect_frame(input, self.options.limits)
166    }
167}
168
169impl Default for Decoder {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175/// Decodes one OpenZL frame into a new `Vec` using [`Options::default`].
176pub fn decode(input: &[u8]) -> Result<Vec<u8>> {
177    Decoder::new().decode(input)
178}
179
180/// Decodes one OpenZL frame into a new `Vec` using explicit options.
181pub fn decode_with_options(input: &[u8], options: Options) -> Result<Vec<u8>> {
182    Decoder::with_options(options).decode(input)
183}
184
185/// Decodes one OpenZL frame and appends the decoded bytes to `dst`.
186///
187/// Returns the number of bytes appended. On error, `dst` is restored to its
188/// original length.
189pub fn decode_into(input: &[u8], dst: &mut Vec<u8>) -> Result<usize> {
190    Decoder::new().decode_into(input, dst)
191}
192
193/// Decodes one OpenZL frame into `dst` using explicit options.
194pub fn decode_into_with_options(
195    input: &[u8],
196    dst: &mut Vec<u8>,
197    options: Options,
198) -> Result<usize> {
199    Decoder::with_options(options).decode_into(input, dst)
200}
201
202/// Parses and validates frame metadata using [`Options::default`].
203pub fn inspect(input: &[u8]) -> Result<FrameInfo> {
204    Decoder::default().inspect(input)
205}
206
207/// Parses and validates frame metadata using explicit options.
208pub fn inspect_with_options(input: &[u8], options: Options) -> Result<FrameInfo> {
209    Decoder::with_options(options).inspect(input)
210}