Skip to main content

onepass_seed/expr/
mod.rs

1//! This module implements the chore schema language for this crate. See [`Expr::parse`] for the
2//! schema language description, [`Expr::write_repr`] for the canonical serialization format, and
3//! the [`Eval`] and [`EvalContext`] instances for the generation scheme.
4
5mod chars;
6mod context;
7mod generator;
8mod node;
9mod parse;
10mod repr;
11mod util;
12
13use std::{
14    io::{Result, Write},
15    sync::LazyLock,
16};
17
18use crypto_bigint::{NonZero, U256};
19use secrecy::ExposeSecretMut;
20
21pub use chars::{CharRange, Chars};
22pub use context::Context;
23pub use generator::{Generator, GeneratorFunc, Word, Words};
24pub use node::Node;
25pub use parse::Error as ParseError;
26
27/// A fully parsed and bound schema expression.
28///
29/// It may be evaluated with [`Eval`] to generate passwords from its domain.
30#[derive(Debug)]
31pub struct Expr<'a> {
32    pub root: Node,
33    pub context: Option<&'a Context<'a>>,
34}
35
36/// The core expression sampling trait for this module.
37///
38/// A type which implements this trait knows how many input strings it contains, and knows how to
39/// write a given one of them to a [`Write`] instance, without any other context.
40pub trait Eval {
41    /// Returns the total number of passwords generated by this type, or some reasonable
42    /// approximation thereof (e.g. a type that constructs and samples its own RNG might return the
43    /// size of the RNG state here), but caution should be used when deviating too far from the
44    /// literal count/enumeration paradigm, as this may cause entropy estimates to be misleading.
45    fn size(&self) -> NonZero<U256>;
46
47    /// Write the string at the given index to the given [`Write`] instance. Typically the string
48    /// at index 0 will be the lowest or lexicographically first password, and the string at
49    /// `self.size() - 1` will be the highest or lexicographically last password, but this is not
50    /// required.
51    fn write_to(&self, w: &mut dyn Write, index: &mut dyn ExposeSecretMut<U256>) -> Result<()>;
52}
53
54/// Delineates a type that knows how to [`Eval`] itself but needs some extra
55/// [`Context`][EvalContext::Context] to do so.
56///
57/// E.g., this may be a call to a custom generator function like `{word}` or `{bip39}`, which needs
58/// the definition of that function to proceed.
59///
60/// Conceptually, you can think of an implementation of this trait like a
61/// `impl<T: EvalContext> Eval for (T, T::Context)`.
62pub trait EvalContext {
63    /// This is the extra context that is needed to evaluate this type.
64    type Context<'a>: ?Sized + 'a;
65
66    /// Like [`Eval::size`] but with context.
67    fn size(&self, context: &Self::Context<'_>) -> NonZero<U256>;
68
69    /// Like [`Eval::write_to`] but with context.
70    fn write_to(
71        &self,
72        context: &Self::Context<'_>,
73        w: &mut dyn Write,
74        index: &mut dyn ExposeSecretMut<U256>,
75    ) -> Result<()>;
76}
77
78static DEFAULT_CONTEXT: LazyLock<Context<'static>> = LazyLock::new(Context::default);
79
80impl Expr<'_> {
81    /// Construct a new expression with the default generator context.
82    pub fn new(root: Node) -> Self {
83        Expr {
84            root,
85            context: None,
86        }
87    }
88}
89
90impl<'a> Expr<'a> {
91    pub fn with_context(root: Node, context: &'a Context<'a>) -> Self {
92        Expr {
93            root,
94            context: Some(context),
95        }
96    }
97
98    pub fn get_context(&self) -> &Context<'a> {
99        self.context.unwrap_or(&DEFAULT_CONTEXT)
100    }
101}
102
103impl Eval for Expr<'_> {
104    fn size(&self) -> NonZero<U256> {
105        self.root.size(self.get_context())
106    }
107
108    fn write_to(&self, w: &mut dyn Write, index: &mut dyn ExposeSecretMut<U256>) -> Result<()> {
109        self.root.write_to(self.get_context(), w, index)
110    }
111}