Skip to main content

onepass_seed/expr/
context.rs

1use core::{error, fmt};
2use std::{collections::HashMap, iter::once, sync::Arc};
3
4use onepass_base::dict::Dict;
5
6use crate::{dict::EFF_WORDLIST, expr::GeneratorFunc};
7
8/// Context for evaluating an <code>[Expr]</code>.
9///
10/// An instance of this type is needed to evaluate <code>[Generator]</code> invocations. This
11/// context also provides a mapping from dictionary hashes to word lists.
12///
13/// [Expr]: crate::expr::Expr
14/// [Generator]: crate::expr::Generator
15#[derive(Clone, Debug)]
16pub struct Context<'a> {
17    generator: Arc<HashMap<&'static str, Arc<dyn GeneratorFunc>>>,
18
19    // TODO(someday): maybe this should be extended into a general-purpose content-addressed
20    // context map, i.e. `Map<[u8; 32], Any>`.
21    dict: Arc<HashMap<[u8; 32], Arc<dyn Dict + 'a>>>,
22
23    pub default_dict: Arc<dyn Dict + 'a>,
24}
25
26/// Error returned on unknown generators or dictionary hashes.
27#[derive(Clone, Copy, Debug)]
28pub struct NotFound;
29
30impl<'a> Context<'a> {
31    pub fn new(
32        generator: impl IntoIterator<Item = Arc<dyn GeneratorFunc>>,
33        dict: impl IntoIterator<Item = Arc<dyn Dict + 'a>>,
34        default_dict: Arc<dyn Dict + 'a>,
35    ) -> Self {
36        let generator = Arc::new(generator.into_iter().map(|g| (g.name(), g)).collect());
37        let dict = Arc::new(
38            once(default_dict.clone())
39                .chain(dict)
40                .map(|d| (*d.hash(), d))
41                .collect(),
42        );
43        Context {
44            generator,
45            dict,
46            default_dict,
47        }
48    }
49
50    /// Returns a context without any generators.
51    pub fn empty() -> Self {
52        Context {
53            generator: Arc::default(),
54            dict: Arc::default(),
55            default_dict: Arc::new(EFF_WORDLIST),
56        }
57    }
58
59    /// Returns a context with the specified default [`Dict`].
60    ///
61    /// The dict is added to the lookup table for the returned context. It may or may not be added
62    /// to the table for the original context, depending whether there are other clones of the
63    /// context or not (see [`Arc::make_mut`].)
64    pub fn with_default_dict(&mut self, default_dict: Arc<dyn Dict + 'a>) -> Self {
65        Arc::make_mut(&mut self.dict).extend([(*default_dict.hash(), default_dict.clone())]);
66        Context {
67            generator: self.generator.clone(),
68            dict: self.dict.clone(),
69            default_dict,
70        }
71    }
72
73    pub fn dict_hash(args: &[&str]) -> Option<[u8; 32]> {
74        let mut out = [0u8; 32];
75        for &arg in args {
76            if hex::decode_to_slice(arg, &mut out).is_ok() {
77                return Some(out);
78            }
79        }
80        None
81    }
82
83    pub fn get_generator(&self, name: &str) -> Result<Arc<dyn GeneratorFunc>, NotFound> {
84        self.generator.get(name).map(Arc::clone).ok_or(NotFound)
85    }
86
87    pub fn get_dict(&self, hash: &Option<[u8; 32]>) -> Result<Arc<dyn Dict + 'a>, NotFound> {
88        let Some(hash) = hash else {
89            return Ok(self.default_dict.clone());
90        };
91        self.dict.get(hash).map(Arc::clone).ok_or(NotFound)
92    }
93}
94
95impl<'a> Extend<Arc<dyn Dict + 'a>> for Context<'a> {
96    fn extend<T: IntoIterator<Item = Arc<dyn Dict + 'a>>>(&mut self, iter: T) {
97        let dict = Arc::make_mut(&mut self.dict);
98        dict.extend(iter.into_iter().map(|d| (*d.hash(), d)));
99    }
100}
101
102impl fmt::Display for NotFound {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        f.write_str("item not found")
105    }
106}
107
108impl error::Error for NotFound {}