Skip to main content

cadmpeg_core/decode/
companion.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Files that a decoded input names by path.
3
4use std::fmt::Debug;
5
6use crate::CodecError;
7
8use super::context::DecodeContext;
9use super::view::{u64_from_index, View};
10
11/// A file that a [`Companions`] source opened.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Companion {
14    /// The identity of the file: equal for every path that names it, and
15    /// the anchor of the paths that it names in turn.
16    pub id: String,
17    /// The content of the file.
18    pub bytes: Vec<u8>,
19}
20
21/// A source of the files that a decoded input names by path.
22pub trait Companions: Debug {
23    /// Opens the file that `path` names, read relative to the file that
24    /// `anchor` identifies, or to the decoded input when `anchor` is
25    /// `None`. A file longer than `max_bytes` is refused. The error is the
26    /// reason that the file is not available.
27    fn open(&self, anchor: Option<&str>, path: &str, max_bytes: u64) -> Result<Companion, String>;
28
29    /// The identity of the file that `path` names, read relative to
30    /// `anchor` as [`Companions::open`] reads it, without reading the file.
31    /// The identity is the one `open` gives. The default refuses: the
32    /// source opens files only.
33    fn locate(&self, _anchor: Option<&str>, _path: &str) -> Result<String, String> {
34        Err("the companion file source does not locate files".to_owned())
35    }
36}
37
38/// The result of a companion request.
39#[derive(Debug, Clone)]
40pub enum CompanionLoad<'a> {
41    /// The opened file: its identity, and its content in a derived space.
42    Loaded {
43        /// The identity of the file.
44        id: String,
45        /// The content of the file.
46        view: View<'a>,
47    },
48    /// The file is not available, for the reason given.
49    Unavailable(String),
50}
51
52impl<'a> DecodeContext<'a> {
53    /// Attaches the source of the files that the input names.
54    #[must_use]
55    pub fn with_companions(mut self, companions: &'a dyn Companions) -> Self {
56        self.companions = Some(companions);
57        self
58    }
59
60    /// Opens the file that `path` names, relative to the file that
61    /// `anchor` identifies, charges its length to the input budget, and
62    /// places its content in a derived space.
63    pub fn open_companion(
64        &self,
65        anchor: Option<&str>,
66        path: &str,
67    ) -> Result<CompanionLoad<'a>, CodecError> {
68        let Some(companions) = self.companions else {
69            return Ok(CompanionLoad::Unavailable(
70                "the decode has no companion file source".to_owned(),
71            ));
72        };
73        let max = self
74            .policy()
75            .limits
76            .max_input_bytes
77            .checked_sub(self.budget.input_bytes())
78            .ok_or_else(|| self.refuse_input_limit(0, "open companion file"))?;
79        match companions.open(anchor, path, max) {
80            Ok(companion) => {
81                self.budget
82                    .charge_input(u64_from_index(companion.bytes.len()), "open companion file")?;
83                let bytes = self.arena.alloc(companion.bytes.into_boxed_slice());
84                let space = self.allocate_space()?;
85                Ok(CompanionLoad::Loaded {
86                    id: companion.id,
87                    view: View::over_space(bytes, space),
88                })
89            }
90            Err(reason) => Ok(CompanionLoad::Unavailable(reason)),
91        }
92    }
93
94    /// The identity of the file that `path` names, relative to the file that
95    /// `anchor` identifies, without reading the file. The error is the
96    /// reason that the file is not located.
97    pub fn locate_companion(&self, anchor: Option<&str>, path: &str) -> Result<String, String> {
98        match self.companions {
99            Some(companions) => companions.locate(anchor, path),
100            None => Err("the decode has no companion file source".to_owned()),
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests;