//! [`KeySpecifier`] derive-deftly macro and its support code //! //! # STABILITY - NOTHING IN THIS MODULE IS PART OF THE STABLE PUBLIC API //! //! The `pub` items in this module are accessible as `$crate::key_specifier_derive`, //! but `#[doc(hidden)]` is applied at the top level. //! //! (Recall that the actual derive-deftly macro //! `KeySpecifier` ends up in the crate toplevel, //! so that *does* form part of our public API.) use std::iter; use derive_deftly::define_derive_deftly; use itertools::{EitherOrBoth, Itertools, izip}; use super::*; use crate::DENOTATOR_SEP; pub use crate::KeyPathInfoBuilder; pub use tor_error::{Bug, internal, into_internal}; /// Trait for (only) formatting as a [`KeySpecifierComponent`] /// /// Like the formatting part of `KeySpecifierComponent` /// but implemented for Option and &str too. pub trait RawKeySpecifierComponent { /// Append `self`s `KeySpecifierComponent` string representation to `s` // // This is not quite like `KeySpecifierComponent::to_slug`, // since that *returns* a String (effectively) and we *append*. // At some future point we may change KeySpecifierComponent, // although the current API has the nice feature that // the syntax of the appended string is checked before we receive it here. fn append_to(&self, s: &mut String) -> Result<(), Bug>; } impl RawKeySpecifierComponent for T { fn append_to(&self, s: &mut String) -> Result<(), Bug> { self.to_slug()?.as_str().append_to(s) } } impl RawKeySpecifierComponent for Option { fn append_to(&self, s: &mut String) -> Result<(), Bug> { let v: &dyn RawKeySpecifierComponent = match self.as_ref() { Some(v) => v, None => &"*", }; v.append_to(s) } } impl<'s> RawKeySpecifierComponent for &'s str { fn append_to(&self, s: &mut String) -> Result<(), Bug> { s.push_str(self); Ok(()) } } /// Make a string like `pc/pc/pc/lc_lc_lc` fn arti_path_string_from_components( path_comps: &[&dyn RawKeySpecifierComponent], leaf_comps: &[&dyn RawKeySpecifierComponent], ) -> Result { let mut path = String::new(); for comp in path_comps { comp.append_to(&mut path)?; path.push('/'); } for (delim, comp) in izip!( iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))), leaf_comps, ) { if let Some(delim) = delim { path.push(delim); } comp.append_to(&mut path)?; } Ok(path) } /// Make a string like `pc/pc/pc/pd+pd+pd@cd+cd+cd+cd` fn cert_arti_path_string_from_components( subj_comp: &str, leaf_comps: &[&dyn RawKeySpecifierComponent], ) -> Result { if leaf_comps.is_empty() { return Ok(subj_comp.to_string()); } let mut path = if subj_comp.contains('+') { format!("{subj_comp}@") } else { format!("{subj_comp}+@") }; for (delim, comp) in izip!( iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))), leaf_comps, ) { if let Some(delim) = delim { path.push(delim); } comp.append_to(&mut path)?; } Ok(path) } /// Make an `ArtiPath` like `pc/pc/pc/lc_lc_lc` /// /// This is the engine for the `KeySpecifier` macro's `arti_path()` impls. /// /// The macro-generated code sets up couple of vectors. /// Each vector entry is a pointer to the field in the original struct, /// plus a vtable pointer saying what to do with it. /// /// For fixed elements in the path, /// the vtable entry's data pointer is a pointer to a constant &str. /// /// In the macro, this is done by the user-defined expansion `ARTI_FROM_COMPONENTS_ARGS`. /// /// Doing it this way minimises the amount of macro-generated machine code. pub fn arti_path_from_components( path_comps: &[&dyn RawKeySpecifierComponent], leaf_comps: &[&dyn RawKeySpecifierComponent], ) -> Result { Ok(arti_path_string_from_components(path_comps, leaf_comps)? .try_into() .map_err(into_internal!("bad ArtiPath from good components"))?) } /// Make a `KeyPathPattern::Arti` like `pc/pc/pc/lc_lc_lc` pub fn arti_pattern_from_components( path_comps: &[&dyn RawKeySpecifierComponent], leaf_comps: &[&dyn RawKeySpecifierComponent], ) -> Result { Ok(KeyPathPattern::Arti(arti_path_string_from_components( path_comps, leaf_comps, )?)) } /// Make a `KeyPathPattern::Arti` for a certificate specifier pub fn cert_arti_pattern_from_components( subj_path: &str, leaf_comps: &[&dyn RawKeySpecifierComponent], ) -> Result { Ok(KeyPathPattern::Arti(cert_arti_path_string_from_components( subj_path, leaf_comps, )?)) } /// Error returned from [`RawKeySpecifierComponentParser::parse`] #[derive(Debug)] #[allow(clippy::exhaustive_enums)] // Not part of public API pub enum RawComponentParseResult { /// This was a field /// /// The `Option` has been filled with the actual value. /// It has an entry in the `keys` argument to [`parse_arti_path`]. ParsedField, /// This was a literal, and it matched MatchedLiteral, /// Becomes [`ArtiPathError::PatternNotMatched`] PatternNotMatched, /// `InvalidKeyPathComponentValue` Invalid(InvalidKeyPathComponentValue), } use RawComponentParseResult as RCPR; /// Trait for parsing a path component, used by [`parse_arti_path`] /// /// Implemented for `Option`, /// and guarantees to fill in the Option if it succeeds. /// /// Also implemented for `&str`: just checks that the string is right, /// (and, doesn't modify `*self`). pub trait RawKeySpecifierComponentParser { /// Check that `comp` is as expected, and store any results in `self`. fn parse(&mut self, comp: &Slug) -> RawComponentParseResult; } impl RawKeySpecifierComponentParser for Option { fn parse(&mut self, comp: &Slug) -> RawComponentParseResult { let v = match T::from_slug(comp) { Ok(v) => v, Err(e) => return RCPR::Invalid(e), }; *self = Some(v); RCPR::ParsedField } } impl<'s> RawKeySpecifierComponentParser for &'s str { fn parse(&mut self, comp: &Slug) -> RawComponentParseResult { if comp.as_str() == *self { RCPR::MatchedLiteral } else { RCPR::PatternNotMatched } } } /// List of parsers for fields type Parsers<'p> = [&'p mut dyn RawKeySpecifierComponentParser]; /// Split a string into components and parse each one fn extract( input: Option<&str>, delim: char, parsers: &mut Parsers, keys: &mut &[&str], ) -> Result<(), ArtiPathError> { for ent in Itertools::zip_longest( input.map(|input| input.split(delim)).into_iter().flatten(), parsers, ) { let EitherOrBoth::Both(comp, parser) = ent else { // wrong number of components return Err(ArtiPathError::PatternNotMatched); }; // TODO would be nice to avoid allocating again here, // but I think that needs an `SlugRef`. let comp = Slug::new(comp.to_owned()) .map_err(ArtiPathSyntaxError::Slug) .map_err(ArtiPathError::InvalidArtiPath)?; let missing_keys = || internal!("keys list too short, bad args to parse_arti_path"); match parser.parse(&comp) { RCPR::PatternNotMatched => Err(ArtiPathError::PatternNotMatched), RCPR::Invalid(error) => Err(ArtiPathError::InvalidKeyPathComponentValue { error, key: keys.first().ok_or_else(missing_keys)?.to_string(), value: comp, }), RCPR::ParsedField => { *keys = keys.split_first().ok_or_else(missing_keys)?.1; Ok(()) } RCPR::MatchedLiteral => Ok(()), }?; } Ok(()) } /// Parse a `KeyPath` as an `ArtiPath` like pc/pc/pc/lc_lc_lc /// /// `keys` is the field names for each of the path_parsers and leaf_parsers, /// *but* only the ones which will return `RCPR::ParsedField` (or `::Invalid`). /// /// As with `arti_path_string_components` etc., we try to minimise /// the amount of macro-generated machine code. /// /// The macro-generated impl again assembles two vectors, /// one for the path components and one for the leaf components. /// /// For a field, the vector entry is a pointer to `&mut Option<...>` /// for the field, along with a `RawKeySpecifierComponentParser` vtable entry. /// (The macro-generated impl must unwrap each of these Options, /// to assemble the final struct. In principle this could be avoided with /// use of `MaybeUninit` and unsafe.) /// /// For a fixed string component, the vector entry data pointer points to its `&str`. /// "Parsing" consists of checking that the string is as expected. /// /// We also need the key names for error reporting. /// We pass this as a *single* array, and a double-reference to the slice, /// since that resolves to one pointer to a static structure. pub fn parse_arti_path( arti_path: &ArtiPath, keys: &&[&str], path_parsers: &mut Parsers, leaf_parsers: &mut Parsers, ) -> Result<(), ArtiPathError> { let path = arti_path.as_str(); let (path, leaf) = match path.rsplit_once('/') { Some((path, leaf)) => (Some(path), leaf), None => (None, path), }; let mut keys: &[&str] = keys; extract(path, '/', path_parsers, &mut keys)?; extract(Some(leaf), DENOTATOR_SEP, leaf_parsers, &mut keys)?; Ok(()) } /// Parse the denotators from the `ArtiPath` of a certificate. /// /// The specified `cert_denos` should be the substring of an `ArtiPath` /// containing the certificate denotator group. /// /// The `leaf_parses` and `keys` arguments serve the same purpose as in /// [`parse_arti_path`]. pub fn parse_cert_denotators( cert_denos: &str, keys: &&[&str], leaf_parsers: &mut Parsers, ) -> Result<(), ArtiPathError> { let mut keys: &[&str] = keys; extract(Some(cert_denos), DENOTATOR_SEP, leaf_parsers, &mut keys)?; Ok(()) } /// Build a `KeyPathInfo` given the information about a key specifier /// /// Calling pattern, to minimise macro-generated machine code, /// is similar `arti_path_from_components`. /// /// The macro-generated code parses the path into its KeySpecifier impl /// (as an owned value) and then feeds references to the various fields /// to `describe_via_components`. pub fn describe_via_components( summary: &&str, role: &dyn RawKeySpecifierComponent, extra_keys: &&[&str], extra_info: &[&dyn KeySpecifierComponent], ) -> Result { let mut info = KeyPathInfoBuilder::default(); info.summary(summary.to_string()); info.role({ let mut s = String::new(); role.append_to(&mut s)?; s }); for (key, value) in izip!(*extra_keys, extra_info) { let value = KeySpecifierComponentPrettyHelper(*value).to_string(); info.extra_info(*key, value); } info.build() .map_err(into_internal!("failed to build KeyPathInfo")) } define_derive_deftly! { /// A helper for implementing [`KeySpecifier`]s. /// /// Applies to a struct that has some static components (`prefix`, `role`), /// and a number of variable components represented by its fields. /// /// Implements `KeySpecifier` etc. /// /// Each field is either a path field (which becomes a component in the `ArtiPath`), /// or a denotator (which becomes *part* of the final component in the `ArtiPath`). /// /// The `prefix` is the first component of the [`ArtiPath`] of the [`KeySpecifier`]. /// /// The role should be the name of the key in the Tor Specifications. /// The **lowercased** `role` is used as the _prefix of the last component_ /// of the [`ArtiPath`] of the specifier. /// The `role` is followed by the denotators of the key. /// /// The denotator fields, if there are any, /// should be annotated with `#[denotator]`. /// /// The declaration order of the fields is important. /// The inner components of the [`ArtiPath`] of the specifier are built /// from the string representation of its path fields, taken in declaration order, /// followed by the encoding of its denotators, also taken in the order they were declared. /// As such, all path fields, must implement [`KeySpecifierComponent`]. /// and all denotators must implement [`KeySpecifierComponent`]. /// The denotators are separated from the rest of the path, and from each other, /// by `+` characters. /// /// For example, a key specifier with `prefix` `"foo"` and `role` `"bar"` /// will have an [`ArtiPath`] of the form /// `"foo///../bar[+]"`. /// /// A key specifier of this form, with denotators that encode to "d1" and "d2", /// would look like this: `"foo///../bar+d1+d2"`. /// /// ### Results of applying this macro /// /// `#[derive(Deftly)] #[derive_deftly(KeySpecifier)] struct SomeKeySpec ...` /// generates: /// /// * `impl `[`KeySpecifier`]` for SomeKeySpec` /// * `struct SomeKeySpecPattern`, /// a derived struct which contains an `Option` for each field. /// `None` in the pattern means "any". /// * `impl `[`KeySpecifierPattern`]` for SomeKeySpecPattern` /// * `impl TryFrom<`[`KeyPath`]> for SomeKeySpec` /// * Registration of an impl of [`KeyPathInfoExtractor`] /// (on a private unit struct `SomeKeySpecInfoExtractor`) /// /// ### Custom attributes /// /// * **`#[deftly(prefix)]`** (toplevel): /// Specifies the fixed prefix (the first path component). /// Must be a literal string. /// /// * **`#[deftly(role = "...")]`** (toplevel): /// Specifies the role - the initial portion of the leafname. /// This should be the name of the key in the Tor Specifications. /// Must be a literal string. /// This or the field-level `#[deftly(role)]` must be specified. /// /// * **`[deftly(role)]` (field): /// Specifies that the role is determined at runtime. /// The field type must implement [`KeyDenotator`]. /// /// * **`#[deftly(summary = "...")]`** (summary, mandatory): /// Specifies the summary; ends up as the `summary` field in [`KeyPathInfo`]. /// (See [`KeyPathInfoBuilder::summary()`].) /// Must be a literal string. /// /// * **`#[deftly(denotator)]`** (field): /// Designates a field that should be represented /// in the key file leafname, after the role. /// /// * **`#[deftly(ctor_path = VARIANT)]`** (toplevel): /// Specifies that this kind of key has a representation in C Tor keystores, /// and provides the appropriate [`CTorPath`] variant in `VARIANT`. /// /// Used for implementing [`CTorKeySpecifier`]. /// /// If specified, the generated [`KeySpecifier::ctor_path`] implementation /// will return [`CTorPath`]::`VARIANT` populated with the fields extracted /// from this type. Therefore, your type **must** have exactly the same fields /// as the specified `CTorPath` variant. /// /// If not specified, the generated [`KeySpecifier::ctor_path`] /// implementation will always return `None`. /// /// * **`#[deftly(fixed_path_component = STR)]`** (field): /// Before this field insert a fixed path component `STR`. /// (Can be even used before a denotator component, /// to add a final fixed path component.) /// /// * **`#[deftly(keypair_specifier = TYPE)]`** (field): /// If this is the specifier for a public key, the specifier for /// the corresponding keypair type. /// /// If not specified, the generated [`KeySpecifier::keypair_specifier`] /// implementation will always return `None`. // // NOTE: The `KeySpecifier::keypair_specifier` implementation // of the `ArtiPath` of a public key will always return `None`, // even if the public key specifier it represents has a keypair specifier. // /// export KeySpecifier for struct, beta_deftly, meta_quoted rigorous: // A condition that evaluates to `true` for path fields. ${defcond F_IS_PATH not(any(fmeta(denotator), fmeta(role)))} ${defcond F_IS_ROLE all(fmeta(role), not(tmeta(role)))} #[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")] #[allow(dead_code)] // Not everyone will need the pattern feature #[non_exhaustive] $tvis struct $<$tname Pattern><$tdefgens> where $twheres ${vdefbody $vname $( ${fattrs doc} /// /// `None` to match keys with any value for this field. $fvis $fname: Option<$ftype>, ) } // ** MAIN KNOWLEDGE OF HOW THE PATH IS CONSTRUCTED ** // // These two user-defined expansions, // $ARTI_PATH_COMPONENTS // $ARTI_LEAF_COMPONENTS // expand to code for handling each path and leaf component, // in the order in which they appear in the ArtiPath. // // The "code for handling", by default, is: // - for a field, take a reference to the field in `self` // - for a fixed component, take a reference to a &'static str // in each case with a comma appended. // So this is suitable for including in a &[&dyn ...]. // // The call site can override the behaviour by locally redefining, // the two user-defined expansions DO_FIELD and DO_LITERAL. // // DO_FIELD should expand to the code necessary to handle a field. // It probably wants to refer to $fname. // // DO_LITERAL should expand to the code necessary to handle a literal value. // When DO_LITERAL is called the user-defined expansion LIT will expand to // something like `${fmeta(...) as str}`, which will in turn expand to // a string literal. // // For use sites which want to distinguish the role from other fields: // DO_ROLE_FIELD and DO_ROLE_LITERAL are used for the role. // They default to expanding $DO_FIELD and $DO_LITERAL respectively. // // This is the *only* place that knows how ArtiPaths are constructed, // when the path syntax is defined using the KeySpecifier d-a macro. // // The actual code here is necessarily rather abstract. ${define ARTI_PATH_COMPONENTS { // #[deftly(prefix = ...)] ${define LIT ${tmeta(prefix) as str}} $DO_LITERAL ${for fields { // #[deftly(fixed_path_component = ...)] ${if fmeta(fixed_path_component) { // IWVNI d-a allowed arguments to use-defined expansions, but this will do ${define LIT ${fmeta(fixed_path_component) as str}} $DO_LITERAL }} // Path fields ${if F_IS_PATH { $DO_FIELD }} }} }} ${define ARTI_LEAF_COMPONENTS { ${if tmeta(role) { // #[deftly(role = ...)] on the toplevel ${define LIT { stringify!(${snake_case ${tmeta(role)}}) }} $DO_ROLE_LITERAL }} ${for fields { // #[deftly(role)] on a field ${if F_IS_ROLE { $DO_ROLE_FIELD }} }} ${for fields { // #[deftly(denotator)] ${if fmeta(denotator) { $DO_FIELD }} }} }} ${define DO_FIELD { &self.$fname, }} ${define DO_LITERAL { &$LIT, }} ${define DO_ROLE_FIELD { $DO_FIELD }} ${define DO_ROLE_LITERAL { $DO_LITERAL }} impl<$tgens> $crate::KeySpecifier for $ttype where $twheres { fn arti_path( &self, ) -> std::result::Result<$crate::ArtiPath, $crate::ArtiPathUnavailableError> { use $crate::key_specifier_derive::*; arti_path_from_components( &[ $ARTI_PATH_COMPONENTS ], &[ $ARTI_LEAF_COMPONENTS ], ) } fn ctor_path(&self) -> Option<$crate::CTorPath> { ::ctor_path(self) } fn keypair_specifier(&self) -> Option> { ${if tmeta(keypair_specifier) { Some(Box::new(std::convert::Into::< ${tmeta(keypair_specifier) as ty} >::into(self))) } else { None }} } } impl<$tgens> $crate::KeySpecifierPattern for $<$tname Pattern><$tdefgens> where $twheres { fn arti_pattern( &self, ) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> { use $crate::key_specifier_derive::*; arti_pattern_from_components( &[ $ARTI_PATH_COMPONENTS ], &[ $ARTI_LEAF_COMPONENTS ], ) } fn new_any() -> Self { $< $tname Pattern > { $( $fname: None, ) } } } struct $< $tname InfoExtractor >; impl<$tgens> $crate::KeyPathInfoExtractor for $< $tname InfoExtractor > where $twheres { fn describe( &self, path: &$crate::KeyPath, ) -> std::result::Result<$crate::KeyPathInfo, $crate::KeyPathError> { use $crate::key_specifier_derive::*; // Parse this path #[allow(unused_variables)] // Unused if no fields let spec = $ttype::try_from(path)?; // none of this cares about non-role literals // all the others three be explicitly defined each time ${define DO_LITERAL {}} static NON_ROLE_FIELD_KEYS: &[&str] = &[ ${define DO_FIELD { stringify!($fname), }} ${define DO_ROLE_FIELD {}} ${define DO_ROLE_LITERAL {}} $ARTI_PATH_COMPONENTS $ARTI_LEAF_COMPONENTS ]; describe_via_components( &${tmeta(summary) as str}, // role ${define DO_FIELD {}} ${define DO_ROLE_FIELD { &spec.$fname, }} ${define DO_ROLE_LITERAL { &$LIT, }} $ARTI_LEAF_COMPONENTS &NON_ROLE_FIELD_KEYS, &[ ${define DO_FIELD { &spec.$fname, }} ${define DO_ROLE_FIELD {}} ${define DO_ROLE_LITERAL {}} $ARTI_PATH_COMPONENTS $ARTI_LEAF_COMPONENTS ], ).map_err($crate::KeyPathError::Bug) } } impl<$tgens> TryFrom<&$crate::KeyPath> for $tname where $twheres { type Error = $crate::KeyPathError; fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> { use $crate::key_specifier_derive::*; static FIELD_KEYS: &[&str] = &[ ${define DO_LITERAL {}} ${define DO_FIELD { stringify!($fname), }} $ARTI_PATH_COMPONENTS $ARTI_LEAF_COMPONENTS ]; #[allow(unused_mut)] // not needed if there are no fields #[allow(unused_variables)] // not needed if there are no fields let mut builder = <$<$tname Pattern>::<$tgens> as $crate::KeySpecifierPattern>::new_any(); ${define DO_FIELD { &mut builder.$fname, }} ${define DO_LITERAL { &mut $LIT, }} #[allow(unused_variables)] // CTorPath is only used with ctor_path(..) match path { $crate::KeyPath::Arti(path) => { parse_arti_path( path, &FIELD_KEYS, &mut [ $ARTI_PATH_COMPONENTS ], &mut [ $ARTI_LEAF_COMPONENTS ], ).map_err(|err| $crate::KeyPathError::Arti { path: path.clone(), err })?; }, $crate::KeyPath::CTor(path) => { return ::from_ctor_path(path.clone()) .map_err(|err| $crate::KeyPathError::CTor { path: path.clone(), err }); }, #[allow(unreachable_patterns)] // This is reachable if used outside of tor-keymgr &_ => { return Err(internal!("unrecognized key path?!").into()); } }; #[allow(unused_variables)] // not needed if there are no fields let handle_none = || internal!("bad RawKeySpecifierComponentParser impl"); Ok($tname { $( $fname: builder.$fname.ok_or_else(handle_none)?, ) }) } } ${if tmeta(ctor_path) { ${define CTOR_PATH_VARIANT ${tmeta(ctor_path) as path}} impl<$tgens> $crate::CTorKeySpecifier for $ttype where $twheres { fn ctor_path(&self) -> Option<$crate::CTorPath> { Some($crate::CTorPath :: $CTOR_PATH_VARIANT { $( $fname: self.$fname.clone(), ) }) } fn from_ctor_path( path: $crate::CTorPath ) -> std::result::Result { match path { $crate::CTorPath :: $CTOR_PATH_VARIANT { $( $fname, )} => { Ok( Self { $( $fname, ) }) }, _ => Err($crate::CTorPathError::KeySpecifierMismatch(stringify!($tname).into())), } } } } else { impl<$tgens> $crate::CTorKeySpecifier for $ttype where $twheres { fn ctor_path(&self) -> Option<$crate::CTorPath> { None } fn from_ctor_path( _: $crate::CTorPath ) -> std::result::Result { Err($crate::CTorPathError::MissingCTorPath(stringify!($tname).to_string())) } } }} // Register the info extractor with `KeyMgr`. $crate::inventory::submit!(&$< $tname InfoExtractor > as &dyn $crate::KeyPathInfoExtractor); } #[cfg(feature = "experimental-api")] define_derive_deftly! { /// A helper for implementing [`KeyCertificateSpecifier`]s. /// /// ### Results of applying this macro /// /// `#[derive(Deftly)] #[derive_deftly(CertSpecifier)] struct SomeCertSpec ...` /// generates: /// /// * `impl `[`KeyCertificateSpecifier`]` for SomeCertSpec` /// * `struct SomeCertSpecPattern`, /// a derived struct which contains an `Option` for each denotator field, /// and a non-optional field for the subject key `KeyPathPattern`. /// `None` in the pattern means "any". /// * `impl `[`CertSpecifierPattern`]` for SomeCertSpecPattern` /// * `impl TryFrom<`[`KeyPath`]> for SomeCertSpec` /// /// /// ### Custom attributes /// /// * **`#[deftly(subject)]`** (mandatory, field): /// Designates a field that represents the subject key specifier. /// This should only be applied to **one** field. /// /// * **`#[deftly(denotator)]`** (field): /// Designates a field that should be represented /// in the key file leafname. /// The `ArtiPath` of the certificate is derived from the `ArtiPath` /// of the subject key, by concatenating the `ArtiPath` of the subject /// key with provided denotators provided. /// If no there are no denotators, the `ArtiPath` of the certificate /// is the same as the `ArtiPath` of the subject key. export CertSpecifier beta_deftly, for struct: // Ensure exactly one field annotated with #[deftly(subject)] ${if not(approx_equal(${for fields { ${when fmeta(subject)} 1 }}, 1)) { ${error "Exactly one field must be #[deftly(subject)]"} } } // All fields must be either #[deftly(subject)] or #[deftly(denotator)] $( ${when not(any( fmeta(subject), fmeta(denotator), ))} ${error message=${concat $fname " must be #[deftly(subject)] or #[deftly(denotator)]"} } ) ${define SUBJ_FNAME ${for fields { ${if fmeta(subject) { &self.$fname }} }} } ${define SUBJ_FTYPE ${for fields { ${if fmeta(subject) { $ftype }} }} } ${define SUBJ_PATTERN_FTYPE ${for fields { ${if fmeta(subject) { $<$ftype Pattern> }} }} } impl<$tgens> $crate::KeyCertificateSpecifier for $tname<$tdefgens> where $twheres { fn cert_denotators(&self) -> Vec<&dyn $crate::KeySpecifierComponent> { vec![ ${for fields { // #[deftly(denotator)] ${if fmeta(denotator) { &self.$fname, }} }} ] } fn subject_key_specifier(&self) -> &dyn $crate::KeySpecifier { ${SUBJ_FNAME} } } #[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")] #[allow(dead_code)] // Not everyone will need the pattern feature #[non_exhaustive] $tvis struct $<$tname Pattern><$tdefgens> where $twheres { ${for fields { // The subject key specifier pattern is non-optional, // because we derive the fixed part of the pattern from it ${if fmeta(subject) { ${fattrs doc} $fvis $fname: $<$ftype Pattern>, }} // The denotators are optional ${if fmeta(denotator) { ${fattrs doc} $fvis $fname: Option<$ftype>, }} }} } ${define DO_FIELD { &self.$fname, }} ${define ARTI_LEAF_COMPONENTS { ${for fields { // #[deftly(denotator)] ${if fmeta(denotator) { $DO_FIELD }} }} }} impl<$tgens> $crate::CertSpecifierPattern for $<$tname Pattern><$tdefgens> where $twheres { type SubjectKeySpecifierPattern = ${SUBJ_PATTERN_FTYPE}; fn arti_pattern( &self, ) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> { use $crate::key_specifier_derive::*; use $crate::KeyPathPattern::*; use $crate::KeySpecifierPattern as _; let subj_path_pat = ${SUBJ_FNAME}.arti_pattern()?; let subj_path = match subj_path_pat { Arti(path) => path, _ => { return Err( tor_error::internal!("subject key pattern is not an Arti pattern?!").into() ); } }; cert_arti_pattern_from_components( &subj_path, &[ $ARTI_LEAF_COMPONENTS ], ) } fn new_any() -> Self { // Build the "any" pattern of the subject key. let spec = < <$<$tname Pattern>::<$tgens> as $crate::CertSpecifierPattern>::SubjectKeySpecifierPattern as $crate::KeySpecifierPattern>::new_any(); $< $tname Pattern > { $( ${if fmeta(subject) { $fname: spec, }} ) $( ${if fmeta(denotator) { $fname: None, }} ) } } } impl<$tgens> TryFrom<&$crate::KeyPath> for $tname where $twheres { type Error = $crate::KeyPathError; fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> { use $crate::key_specifier_derive::*; let arti_path = match path { $crate::KeyPath::Arti(path) => path, &_ => { return Err(tor_error::bad_api_usage!("Cert specifiers never have non-ArtiPaths").into()); } }; #[allow(unused_mut)] // not needed if there are no fields #[allow(unused_variables)] // not needed if there are no fields let mut builder = <$<$tname Pattern>::<$tgens> as $crate::CertSpecifierPattern>::new_any(); let subj_key = if let Some((key_path, cert_denos)) = arti_path.split_once($crate::DENOTATOR_GROUP_SEP) { // Handle the special case where the subject key ArtiPath has no denotators, // but the cert ArtiPath *does*. This translates to paths that contain // the slightly odd +@ construction, where + designates the beginning // of the denotator section, followed by an empty denotator group. let key_path = match key_path.strip_suffix('+') { Some(p) => p, None => key_path, }; let key_arti_path = $crate::ArtiPath::new(key_path.to_string()) .map_err(tor_error::into_internal!("cert path contains invalid key ArtiPath?!"))?; static FIELD_KEYS: &[&str] = &[ ${for fields { // #[deftly(denotator)] ${if fmeta(denotator) { stringify!($fname), }} }} ]; parse_cert_denotators( cert_denos, &FIELD_KEYS, ${define DO_FIELD { &mut builder.$fname, }} &mut [ $ARTI_LEAF_COMPONENTS ], ).map_err(|err| $crate::KeyPathError::Arti { path: arti_path.clone(), err })?; let key_spec = $crate::KeyPath::Arti(key_arti_path); $SUBJ_FTYPE::try_from(&key_spec)? } else { // Cert has no denotators, so the whole path is the path // of the subject key specifier $SUBJ_FTYPE::try_from(path)? }; #[allow(unused_variables)] // not needed if there are no fields let handle_none = || internal!("bad RawKeySpecifierComponentParser impl"); Ok($tname { ${for fields { // The denotators are optional... ${if fmeta(denotator) { $fname: builder.$fname.ok_or_else(handle_none)?, }} // ...but subject key specifier pattern is not ${if fmeta(subject) { $fname: subj_key, }} }} }) } } // TODO: generate and register a KeyPathInfoExtractor impl for cert specifiers // (so that KeyMgr::describe() can describe them) } pub use derive_deftly_template_KeySpecifier; #[cfg(feature = "experimental-api")] pub use derive_deftly_template_CertSpecifier;