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}