tor_keymgr/key_specifier/
derive.rs

1//! [`KeySpecifier`] derive-adhoc macro and its support code
2//!
3//! # STABILITY - NOTHING IN THIS MODULE IS PART OF THE STABLE PUBLIC API
4//!
5//! The `pub` items in this module are accessible as `$crate::key_specifier_derive`,
6//! but `#[doc(hidden)]` is applied at the top level.
7//!
8//! (Recall that the actual derive-adhoc macro
9//! `KeySpecifier` ends up in the crate toplevel,
10//! so that *does* form part of our public API.)
11
12use std::iter;
13
14use derive_deftly::define_derive_deftly;
15use itertools::{izip, EitherOrBoth, Itertools};
16
17use super::*;
18use crate::DENOTATOR_SEP;
19
20pub use crate::KeyPathInfoBuilder;
21pub use tor_error::{internal, into_internal, Bug};
22
23/// Trait for (only) formatting as a [`KeySpecifierComponent`]
24///
25/// Like the formatting part of `KeySpecifierComponent`
26/// but implemented for Option and &str too.
27pub trait RawKeySpecifierComponent {
28    /// Append `self`s `KeySpecifierComponent` string representation to `s`
29    //
30    // This is not quite like `KeySpecifierComponent::to_slug`,
31    // since that *returns* a String (effectively) and we *append*.
32    // At some future point we may change KeySpecifierComponent,
33    // although the current API has the nice feature that
34    // the syntax of the appended string is checked before we receive it here.
35    fn append_to(&self, s: &mut String) -> Result<(), Bug>;
36}
37impl<T: KeySpecifierComponent> RawKeySpecifierComponent for T {
38    fn append_to(&self, s: &mut String) -> Result<(), Bug> {
39        self.to_slug()?.as_str().append_to(s)
40    }
41}
42impl<T: KeySpecifierComponent> RawKeySpecifierComponent for Option<T> {
43    fn append_to(&self, s: &mut String) -> Result<(), Bug> {
44        let v: &dyn RawKeySpecifierComponent = match self.as_ref() {
45            Some(v) => v,
46            None => &"*",
47        };
48        v.append_to(s)
49    }
50}
51impl<'s> RawKeySpecifierComponent for &'s str {
52    fn append_to(&self, s: &mut String) -> Result<(), Bug> {
53        s.push_str(self);
54        Ok(())
55    }
56}
57
58/// Make a string like `pc/pc/pc/lc_lc_lc`
59fn arti_path_string_from_components(
60    path_comps: &[&dyn RawKeySpecifierComponent],
61    leaf_comps: &[&dyn RawKeySpecifierComponent],
62) -> Result<String, Bug> {
63    let mut path = String::new();
64
65    for comp in path_comps {
66        comp.append_to(&mut path)?;
67        path.push('/');
68    }
69    for (delim, comp) in izip!(
70        iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))),
71        leaf_comps,
72    ) {
73        if let Some(delim) = delim {
74            path.push(delim);
75        }
76        comp.append_to(&mut path)?;
77    }
78
79    Ok(path)
80}
81
82/// Make an `ArtiPath` like `pc/pc/pc/lc_lc_lc`
83///
84/// This is the engine for the `KeySpecifier` macro's `arti_path()` impls.
85///
86/// The macro-generated code sets up couple of vectors.
87/// Each vector entry is a pointer to the field in the original struct,
88/// plus a vtable pointer saying what to do with it.
89///
90/// For fixed elements in the path,
91/// the vtable entry's data pointer is a pointer to a constant &str.
92///
93/// In the macro, this is done by the user-defined expansion `ARTI_FROM_COMPONENTS_ARGS`.
94///
95/// Doing it this way minimises the amount of macro-generated machine code.
96pub fn arti_path_from_components(
97    path_comps: &[&dyn RawKeySpecifierComponent],
98    leaf_comps: &[&dyn RawKeySpecifierComponent],
99) -> Result<ArtiPath, ArtiPathUnavailableError> {
100    Ok(arti_path_string_from_components(path_comps, leaf_comps)?
101        .try_into()
102        .map_err(into_internal!("bad ArtiPath from good components"))?)
103}
104
105/// Make a `KeyPathPattern::Arti` like `pc/pc/pc/lc_lc_lc`
106pub fn arti_pattern_from_components(
107    path_comps: &[&dyn RawKeySpecifierComponent],
108    leaf_comps: &[&dyn RawKeySpecifierComponent],
109) -> Result<KeyPathPattern, Bug> {
110    Ok(KeyPathPattern::Arti(arti_path_string_from_components(
111        path_comps, leaf_comps,
112    )?))
113}
114
115/// Error returned from [`RawKeySpecifierComponentParser::parse`]
116#[derive(Debug)]
117#[allow(clippy::exhaustive_enums)] // Not part of public API
118pub enum RawComponentParseResult {
119    /// This was a field
120    ///
121    /// The `Option` has been filled with the actual value.
122    /// It has an entry in the `keys` argument to [`parse_key_path`].
123    ParsedField,
124    /// This was a literal, and it matched
125    MatchedLiteral,
126    /// Becomes [`KeyPathError::PatternNotMatched`]
127    PatternNotMatched,
128    /// `InvalidKeyPathComponentValue`
129    Invalid(InvalidKeyPathComponentValue),
130}
131
132use RawComponentParseResult as RCPR;
133
134/// Trait for parsing a path component, used by [`parse_key_path`]
135///
136/// Implemented for `Option<impl KeySpecifierComponent>`,
137/// and guarantees to fill in the Option if it succeeds.
138///
139/// Also implemented for `&str`: just checks that the string is right,
140/// (and, doesn't modify `*self`).
141pub trait RawKeySpecifierComponentParser {
142    /// Check that `comp` is as expected, and store any results in `self`.
143    fn parse(&mut self, comp: &Slug) -> RawComponentParseResult;
144}
145
146impl<T: KeySpecifierComponent> RawKeySpecifierComponentParser for Option<T> {
147    fn parse(&mut self, comp: &Slug) -> RawComponentParseResult {
148        let v = match T::from_slug(comp) {
149            Ok(v) => v,
150            Err(e) => return RCPR::Invalid(e),
151        };
152        *self = Some(v);
153        RCPR::ParsedField
154    }
155}
156impl<'s> RawKeySpecifierComponentParser for &'s str {
157    fn parse(&mut self, comp: &Slug) -> RawComponentParseResult {
158        if comp.as_str() == *self {
159            RCPR::MatchedLiteral
160        } else {
161            RCPR::PatternNotMatched
162        }
163    }
164}
165
166/// List of parsers for fields
167type Parsers<'p> = [&'p mut dyn RawKeySpecifierComponentParser];
168
169/// Parse a `KeyPath` as an `ArtiPath` like pc/pc/pc/lc_lc_lc
170///
171/// `keys` is the field names for each of the path_parsers and leaf_parsers,
172/// *but* only the ones which will return `RCPR::ParsedField` (or `::Invalid`).
173///
174/// As with `arti_path_string_components` etc., we try to minimise
175/// the amount of macro-generated machine code.
176///
177/// The macro-generated impl again assembles two vectors,
178/// one for the path components and one for the leaf components.
179///
180/// For a field, the vector entry is a pointer to `&mut Option<...>`
181/// for the field, along with a `RawKeySpecifierComponentParser` vtable entry.
182/// (The macro-generated impl must unwrap each of these Options,
183/// to assemble the final struct.  In principle this could be avoided with
184/// use of `MaybeUninit` and unsafe.)
185///
186/// For a fixed string component, the vector entry data pointer points to its `&str`.
187/// "Parsing" consists of checking that the string is as expected.
188///
189/// We also need the key names for error reporting.
190/// We pass this as a *single* array, and a double-reference to the slice,
191/// since that resolves to one pointer to a static structure.
192pub fn parse_key_path(
193    path: &KeyPath,
194    keys: &&[&str],
195    path_parsers: &mut Parsers,
196    leaf_parsers: &mut Parsers,
197) -> Result<(), KeyPathError> {
198    let (path, arti_path) = match path {
199        KeyPath::Arti(path) => (path.as_str(), path),
200        KeyPath::CTor(_path) => {
201            // TODO (#858): support ctor stores
202            return Err(internal!("not implemented").into());
203        }
204    };
205
206    let (path, leaf) = match path.rsplit_once('/') {
207        Some((path, leaf)) => (Some(path), leaf),
208        None => (None, path),
209    };
210
211    let mut keys: &[&str] = keys;
212
213    /// Split a string into components and parse each one
214    fn extract(
215        arti_path: &ArtiPath,
216        input: Option<&str>,
217        delim: char,
218        parsers: &mut Parsers,
219        keys: &mut &[&str],
220    ) -> Result<(), KeyPathError> {
221        for ent in Itertools::zip_longest(
222            input.map(|input| input.split(delim)).into_iter().flatten(),
223            parsers,
224        ) {
225            let EitherOrBoth::Both(comp, parser) = ent else {
226                // wrong number of components
227                return Err(KeyPathError::PatternNotMatched(arti_path.clone()));
228            };
229
230            // TODO would be nice to avoid allocating again here,
231            // but I think that needs an `SlugRef`.
232            let comp = Slug::new(comp.to_owned())
233                .map_err(ArtiPathSyntaxError::Slug)
234                .map_err(|error| KeyPathError::InvalidArtiPath {
235                    error,
236                    path: arti_path.clone(),
237                })?;
238
239            let missing_keys = || internal!("keys list too short, bad args to parse_key_path");
240
241            match parser.parse(&comp) {
242                RCPR::PatternNotMatched => Err(KeyPathError::PatternNotMatched(arti_path.clone())),
243                RCPR::Invalid(error) => Err(KeyPathError::InvalidKeyPathComponentValue {
244                    error,
245                    key: keys.first().ok_or_else(missing_keys)?.to_string(),
246                    path: arti_path.clone(),
247                    value: comp,
248                }),
249                RCPR::ParsedField => {
250                    *keys = keys.split_first().ok_or_else(missing_keys)?.1;
251                    Ok(())
252                }
253                RCPR::MatchedLiteral => Ok(()),
254            }?;
255        }
256        Ok(())
257    }
258
259    extract(arti_path, path, '/', path_parsers, &mut keys)?;
260    extract(
261        arti_path,
262        Some(leaf),
263        DENOTATOR_SEP,
264        leaf_parsers,
265        &mut keys,
266    )?;
267    Ok(())
268}
269
270/// Build a `KeyPathInfo` given the information about a key specifier
271///
272/// Calling pattern, to minimise macro-generated machine code,
273/// is similar `arti_path_from_components`.
274///
275/// The macro-generated code parses the path into its KeySpecifier impl
276/// (as an owned value) and then feeds references to the various fields
277/// to `describe_via_components`.
278pub fn describe_via_components(
279    summary: &&str,
280    role: &dyn RawKeySpecifierComponent,
281    extra_keys: &&[&str],
282    extra_info: &[&dyn KeySpecifierComponent],
283) -> Result<KeyPathInfo, KeyPathError> {
284    let mut info = KeyPathInfoBuilder::default();
285    info.summary(summary.to_string());
286    info.role({
287        let mut s = String::new();
288        role.append_to(&mut s)?;
289        s
290    });
291    for (key, value) in izip!(*extra_keys, extra_info) {
292        let value = KeySpecifierComponentPrettyHelper(*value).to_string();
293        info.extra_info(*key, value);
294    }
295    Ok(info
296        .build()
297        .map_err(into_internal!("failed to build KeyPathInfo"))?)
298}
299
300define_derive_deftly! {
301    /// A helper for implementing [`KeySpecifier`]s.
302    ///
303    /// Applies to a struct that has some static components (`prefix`, `role`),
304    /// and a number of variable components represented by its fields.
305    ///
306    /// Implements `KeySpecifier` etc.
307    ///
308    /// Each field is either a path field (which becomes a component in the `ArtiPath`),
309    /// or a denotator (which becomes *part* of the final component in the `ArtiPath`).
310    ///
311    /// The `prefix` is the first component of the [`ArtiPath`] of the [`KeySpecifier`].
312    ///
313    /// The role should be the name of the key in the Tor Specifications.
314    /// The **lowercased** `role` is used as the _prefix of the last component_
315    /// of the [`ArtiPath`] of the specifier.
316    /// The `role` is followed by the denotators of the key.
317    ///
318    /// The denotator fields, if there are any,
319    /// should be annotated with `#[denotator]`.
320    ///
321    /// The declaration order of the fields is important.
322    /// The inner components of the [`ArtiPath`] of the specifier are built
323    /// from the string representation of its path fields, taken in declaration order,
324    /// followed by the encoding of its denotators, also taken in the order they were declared.
325    /// As such, all path fields, must implement [`KeySpecifierComponent`].
326    /// and all denotators must implement [`KeySpecifierComponent`].
327    /// The denotators are separated from the rest of the path, and from each other,
328    /// by `+` characters.
329    ///
330    /// For example, a key specifier with `prefix` `"foo"` and `role` `"bar"`
331    /// will have an [`ArtiPath`] of the form
332    /// `"foo/<field1_str>/<field2_str>/../bar[+<denotators>]"`.
333    ///
334    /// A key specifier of this form, with denotators that encode to "d1" and "d2",
335    /// would look like this: `"foo/<field1_str>/<field2_str>/../bar+d1+d2"`.
336    ///
337    /// ### Results of applying this macro
338    ///
339    /// `#[derive(Deftly)] #[derive_deftly(KeySpecifier)] struct SomeKeySpec ...`
340    /// generates:
341    ///
342    ///  * `impl `[`KeySpecifier`]` for SomeKeySpec`
343    ///  * `struct SomeKeySpecPattern`,
344    ///    a derived struct which contains an `Option` for each field.
345    ///    `None` in the pattern means "any".
346    ///  * `impl `[`KeySpecifierPattern`]` for SomeKeySpecPattern`
347    ///  * `impl TryFrom<`[`KeyPath`]> for SomeKeySpec`
348    ///  * Registration of an impl of [`KeyPathInfoExtractor`]
349    ///    (on a private unit struct `SomeKeySpecInfoExtractor`)
350    ///
351    /// ### Custom attributes
352    ///
353    ///  * **`#[deftly(prefix)]`** (toplevel):
354    ///    Specifies the fixed prefix (the first path component).
355    ///    Must be a literal string.
356    ///
357    ///  * **`#[deftly(role = "...")]`** (toplevel):
358    ///    Specifies the role - the initial portion of the leafname.
359    ///    This should be the name of the key in the Tor Specifications.
360    ///    Must be a literal string.
361    ///    This or the field-level `#[deftly(role)]` must be specified.
362    ///
363    ///  * **`[adhoc(role)]` (field):
364    ///    Specifies that the role is determined at runtime.
365    ///    The field type must implement [`KeyDenotator`].
366    ///
367    ///  * **`#[deftly(summary = "...")]`** (summary, mandatory):
368    ///    Specifies the summary; ends up as the `summary` field in [`KeyPathInfo`].
369    ///    (See [`KeyPathInfoBuilder::summary()`].)
370    ///    Must be a literal string.
371    ///
372    ///  * **`#[deftly(denotator)]`** (field):
373    ///    Designates a field that should be represented
374    ///    in the key file leafname, after the role.
375    ///
376    ///  * **`#[deftly(ctor_path = "expression")]`** (toplevel):
377    ///    Specifies that this kind of key has a representation in C Tor keystores,
378    ///    and provides an expression for computing the path.
379    ///    The expression should have type `impl Fn(&Self) -> CTorPath`.
380    ///
381    ///    If not specified, the generated [`KeySpecifier::ctor_path`]
382    ///    implementation will always return `None`.
383    ///
384    ///  * **`#[deftly(fixed_path_component = "component")]`** (field):
385    ///    Before this field insert a fixed path component `component`.
386    ///    (Can be even used before a denotator component,
387    ///    to add a final fixed path component.)
388    ///
389    ///  * **`#[deftly(key_specifier = "type")]`** (field):
390    ///    If this is the specifier for a public key, the specifier for
391    ///    the corresponding keypair type.
392    ///
393    ///    If not specified, the generated [`KeySpecifier::keypair_specifier`]
394    ///    implementation will always return `None`.
395    //
396    //     NOTE: The `KeySpecifier::keypair_specifier` implementation
397    //     of the `ArtiPath` of a public key will always return `None`,
398    //     even if the public key specifier it represents has a keypair specifier.
399    //
400    ///
401    export KeySpecifier for struct:
402
403    // A condition that evaluates to `true` for path fields.
404    ${defcond F_IS_PATH not(any(fmeta(denotator), fmeta(role)))}
405    ${defcond F_IS_ROLE all(fmeta(role), not(tmeta(role)))}
406
407    #[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")]
408    #[allow(dead_code)] // Not everyone will need the pattern feature
409    #[non_exhaustive]
410    $tvis struct $<$tname Pattern><$tdefgens>
411    where $twheres
412    ${vdefbody $vname $(
413        ${fattrs doc}
414        ///
415        /// `None` to match keys with any value for this field.
416        $fvis $fname: Option<$ftype>,
417    ) }
418
419    // ** MAIN KNOWLEDGE OF HOW THE PATH IS CONSTRUCTED **
420    //
421    // These two user-defined expansions,
422    //   $ARTI_PATH_COMPONENTS
423    //   $ARTI_LEAF_COMPONENTS
424    // expand to code for handling each path and leaf component,
425    // in the order in which they appear in the ArtiPath.
426    //
427    // The "code for handling", by default, is:
428    //   - for a field, take a reference to the field in `self`
429    //   - for a fixed component, take a reference to a &'static str
430    // in each case with a comma appended.
431    // So this is suitable for including in a &[&dyn ...].
432    //
433    // The call site can override the behaviour by locally redefining,
434    // the two user-defined expansions DO_FIELD and DO_LITERAL.
435    //
436    // DO_FIELD should expand to the code necessary to handle a field.
437    // It probably wants to refer to $fname.
438    //
439    // DO_LITERAL should expand to the code necessary to handle a literal value.
440    // When DO_LITERAL is called the user-defined expansion LIT will expand to
441    // something like `${fmeta(...) as str}`, which will in turn expand to
442    // a string literal.
443    //
444    // For use sites which want to distinguish the role from other fields:
445    // DO_ROLE_FIELD and DO_ROLE_LITERAL are used for the role.
446    // They default to expanding $DO_FIELD and $DO_LITERAL respectively.
447    //
448    // This is the *only* place that knows how ArtiPaths are constructed,
449    // when the path syntax is defined using the KeySpecifier d-a macro.
450    //
451    // The actual code here is necessarily rather abstract.
452    ${define ARTI_PATH_COMPONENTS {
453        // #[deftly(prefix = ...)]
454        ${define LIT ${tmeta(prefix) as str}}
455        $DO_LITERAL
456
457        ${for fields {
458            // #[deftly(fixed_path_component = ...)]
459            ${if fmeta(fixed_path_component) {
460                // IWVNI d-a allowed arguments to use-defined expansions, but this will do
461                ${define LIT ${fmeta(fixed_path_component) as str}}
462                $DO_LITERAL
463            }}
464            // Path fields
465            ${if F_IS_PATH { $DO_FIELD }}
466        }}
467    }}
468    ${define ARTI_LEAF_COMPONENTS {
469        ${if tmeta(role) {
470            // #[deftly(role = ...)] on the toplevel
471            ${define LIT { stringify!(${snake_case ${tmeta(role)}}) }}
472            $DO_ROLE_LITERAL
473        }}
474        ${for fields {
475            // #[deftly(role)] on a field
476            ${if F_IS_ROLE { $DO_ROLE_FIELD }}
477        }}
478        ${for fields {
479            // #[deftly(denotator)]
480            ${if fmeta(denotator) { $DO_FIELD }}
481        }}
482    }}
483
484    ${define DO_FIELD { &self.$fname, }}
485    ${define DO_LITERAL { &$LIT, }}
486    ${define DO_ROLE_FIELD { $DO_FIELD }}
487    ${define DO_ROLE_LITERAL { $DO_LITERAL }}
488
489    impl<$tgens> $crate::KeySpecifier for $ttype
490    where $twheres
491    {
492        fn arti_path(
493            &self,
494        ) -> std::result::Result<$crate::ArtiPath, $crate::ArtiPathUnavailableError> {
495            use $crate::key_specifier_derive::*;
496
497            arti_path_from_components(
498                &[ $ARTI_PATH_COMPONENTS ],
499                &[ $ARTI_LEAF_COMPONENTS ],
500            )
501        }
502
503        fn ctor_path(&self) -> Option<$crate::CTorPath> {
504            ${if tmeta(ctor_path) {
505                Some( ${tmeta(ctor_path) as token_stream} (self) )
506            } else {
507                None
508            }}
509        }
510
511        fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
512            ${if tmeta(keypair_specifier) {
513                Some(Box::new(std::convert::Into::<
514                    ${tmeta(keypair_specifier) as token_stream}
515                >::into(self)))
516            } else {
517                None
518            }}
519        }
520    }
521
522    impl<$tgens> $crate::KeySpecifierPattern for $<$tname Pattern><$tdefgens>
523    where $twheres
524    {
525        fn arti_pattern(
526            &self,
527        ) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> {
528            use $crate::key_specifier_derive::*;
529
530            arti_pattern_from_components(
531                &[ $ARTI_PATH_COMPONENTS ],
532                &[ $ARTI_LEAF_COMPONENTS ],
533            )
534        }
535
536        fn new_any() -> Self {
537            $< $tname Pattern > {
538                $( $fname: None, )
539            }
540        }
541    }
542
543    struct $< $tname InfoExtractor >;
544
545    impl<$tgens> $crate::KeyPathInfoExtractor for $< $tname InfoExtractor >
546    where $twheres
547    {
548        fn describe(
549            &self,
550            path: &$crate::KeyPath,
551        ) -> std::result::Result<$crate::KeyPathInfo, $crate::KeyPathError> {
552            use $crate::key_specifier_derive::*;
553
554            // Parse this path
555            #[allow(unused_variables)] // Unused if no fields
556            let spec = $ttype::try_from(path)?;
557
558            // none of this cares about non-role literals
559            // all the others three be explicitly defined each time
560            ${define DO_LITERAL {}}
561
562            static NON_ROLE_FIELD_KEYS: &[&str] = &[
563                ${define DO_FIELD { stringify!($fname), }}
564                ${define DO_ROLE_FIELD {}}
565                ${define DO_ROLE_LITERAL {}}
566                $ARTI_PATH_COMPONENTS
567                $ARTI_LEAF_COMPONENTS
568            ];
569
570            describe_via_components(
571                &${tmeta(summary) as str},
572
573                // role
574                ${define DO_FIELD {}}
575                ${define DO_ROLE_FIELD { &spec.$fname, }}
576                ${define DO_ROLE_LITERAL { &$LIT, }}
577                $ARTI_LEAF_COMPONENTS
578
579                &NON_ROLE_FIELD_KEYS,
580
581                &[
582                    ${define DO_FIELD { &spec.$fname, }}
583                    ${define DO_ROLE_FIELD {}}
584                    ${define DO_ROLE_LITERAL {}}
585                    $ARTI_PATH_COMPONENTS
586                    $ARTI_LEAF_COMPONENTS
587                ],
588            )
589        }
590    }
591
592    impl<$tgens> TryFrom<&$crate::KeyPath> for $tname
593    where $twheres
594    {
595        type Error = $crate::KeyPathError;
596
597        fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> {
598            use $crate::key_specifier_derive::*;
599
600            static FIELD_KEYS: &[&str] = &[
601                ${define DO_LITERAL {}}
602                ${define DO_FIELD { stringify!($fname), }}
603                $ARTI_PATH_COMPONENTS
604                $ARTI_LEAF_COMPONENTS
605            ];
606
607            #[allow(unused_mut)] // not needed if there are no fields
608            #[allow(unused_variables)] // not needed if there are no fields
609            let mut builder =
610                <$<$tname Pattern>::<$tgens> as $crate::KeySpecifierPattern>::new_any();
611
612            ${define DO_FIELD { &mut builder.$fname, }}
613            ${define DO_LITERAL { &mut $LIT, }}
614
615            parse_key_path(
616                path,
617                &FIELD_KEYS,
618                &mut [ $ARTI_PATH_COMPONENTS ],
619                &mut [ $ARTI_LEAF_COMPONENTS ],
620            )?;
621
622            #[allow(unused_variables)] // not needed if there are no fields
623            let handle_none = || internal!("bad RawKeySpecifierComponentParser impl");
624
625            Ok($tname { $(
626                $fname: builder.$fname.ok_or_else(handle_none)?,
627            ) })
628        }
629    }
630
631    // Register the info extractor with `KeyMgr`.
632    $crate::inventory::submit!(&$< $tname InfoExtractor > as &dyn $crate::KeyPathInfoExtractor);
633}