tor_netdoc/parse2/traits.rs
1//! Core model for netdoc parsing
2
3use super::*;
4
5/// A document or section that can be parsed
6///
7/// Normally [derived](derive_deftly_template_NetdocParseable).
8pub trait NetdocParseable: Sized {
9 /// Document type for errors, normally its intro keyword
10 fn doctype_for_error() -> &'static str;
11
12 /// Is `Keyword` an intro Item Keyword for this kind of document?
13 ///
14 /// This is used with 1-keyword lookahead, to allow us to push or pop
15 /// the parsing state into or out of a sub-document.
16 ///
17 /// For signatures sections, this should report *every* recognised keyword.
18 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool;
19
20 /// Parse the document from a stream of Items
21 ///
22 /// Should stop before reading any keyword matching `stop_at`.
23 /// (Except, right at the start.)
24 ///
25 /// Should also stop before reading a 2nd intro keyword,
26 /// so that successive calls to this function can parse
27 /// successive sub-documents of this kind.
28 ///
29 /// Otherwise, should continue until EOF.
30 ///
31 /// Must check whether the first item is this document's `is_intro_item_keyword`,
32 /// and error if not.
33 fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, ErrorProblem>;
34
35 /// Is `Keyword` a structural keyword for this kind of document?
36 ///
37 /// Returns `Some(IsStructural)` for:
38 /// - this type's intro item keyword (`is_intro_item_keyword`)
39 /// - the intro items or structural items for any of its sub-documents and sections
40 /// `#[deftly(netdoc(subdoc))]`
41 ///
42 /// (This means it returns true for *any* item in a signatures subdocument
43 /// ie any field in a struct decorated `#[deftly(netdoc(signatures))]`
44 /// since those are considered intro items.)
45 ///
46 /// Used for avoiding parsing ambiguity when a netdoc from a semi-trusted source
47 /// is embedded into another netdoc.
48 /// See <https://spec.torproject.org/dir-spec/creating-key-certificates.html#nesting>.
49 ///
50 /// # Return type and relationship to `is_intro_item_keyword`
51 ///
52 /// Returns `Option<IsStructural>`
53 /// so that it has a different type to [`NetdocParseable::is_intro_item_keyword`],
54 /// preventing accidental confusion between the two kinds of keyword property enquiry.
55 ///
56 /// Our parsing algorithms actually only care about *intro keywords* for sub-documents.
57 /// We don't need to worry about anything else;
58 /// notably, we don't need to care about other structural items within those sub-documents.
59 ///
60 /// Except for authcerts in votes,, which are nested documents
61 /// with partially trusted content.
62 /// That is what this method is for.
63 ///
64 /// So, we privilege `is_intro_item_keyword` by having it return `bool`
65 /// and by the affordances in [`StopAt`].
66 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural>;
67}
68
69/// A collection of fields that can be parsed within a section
70///
71/// None of the items can be structural.
72///
73/// Normally [derived](derive_deftly_template_NetdocParseableFields).
74pub trait NetdocParseableFields: Sized {
75 /// The partially-parsed set of items.
76 type Accumulator: Sized + Debug + Send + Sync + 'static;
77
78 /// Is this one of the keywords in this struct
79 fn is_item_keyword(kw: KeywordRef<'_>) -> bool;
80
81 /// Accumulate an item in this struct
82 ///
83 /// # Panics
84 ///
85 /// The caller must have first checked the `item`'s keyword with `is_item_keyword`.
86 /// If this *isn't* an item for this structure, may panic.
87 fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem<'_>) -> Result<(), EP>;
88
89 /// Finish
90 ///
91 /// Resolves the `Accumulator` into the output type.
92 /// Generally, this means throwing an error if expected fields were not present.
93 fn finish(acc: Self::Accumulator) -> Result<Self, EP>;
94}
95
96/// A network document with (unverified) signatures
97///
98/// Typically implemented automatically, for `FooSigned` structs, as defined by
99/// [`#[derive_deftly(NetdocSigned)]`](derive_deftly_template_NetdocSigned).
100//
101// TODO is this only useable for parsing? It needs to be renamed, or maybe impooved and moved
102pub trait NetdocSigned {
103 /// The body, ie not including the signatures
104 type Body: Sized;
105 /// The signatures (the whole signature section)
106 type Signatures: Sized;
107
108 /// Inspect the document (and its signatures)
109 ///
110 /// # Security hazard
111 ///
112 /// The signature has not been verified, so the returned data must not be trusted.
113 fn inspect_unverified(&self) -> (&Self::Body, &Self::Signatures);
114
115 /// Obtain the actual document (and signatures), without verifying
116 ///
117 /// # Security hazard
118 ///
119 /// The signature has not been verified, so the returned data must not be trusted.
120 fn unwrap_unverified(self) -> (Self::Body, Self::Signatures);
121
122 /// Construct a new `NetdocSigned` from a body and signatures
123 ///
124 /// (Called by code generated by `#[derive_deftly(NetdocSigned)]`.)
125 fn from_parts(body: Self::Body, signatures: Self::Signatures) -> Self;
126}
127
128/// An item (value) that can be parsed in a netdoc
129///
130/// This is the type `T` of a field `item: T` in a netdoc type.
131///
132/// An implementation is provided for tuples of `ItemArgumentParseable`,
133/// which parses each argument in turn,
134/// ignores additional arguments,
135/// and rejects any Object.
136///
137/// Typically derived with
138/// [`#[derive_deftly(ItemValueParseable)]`](derive_deftly_template_ItemValueParseable).
139///
140/// Signature items are special, and implement [`SignatureItemParseable`] instead.
141pub trait ItemValueParseable: Sized {
142 /// Parse the item's value
143 fn from_unparsed(item: UnparsedItem<'_>) -> Result<Self, ErrorProblem>;
144}
145
146/// An (individual) argument that can be parsed from in a netdoc
147///
148/// An implementations is provided for **`T: FromStr`**,
149/// which expects a single argument and passes it to `FromStr`.
150///
151/// For netdoc arguments whose specified syntax spans multiple space-separated words,
152/// use a manual implementation or a wrapper type.
153pub trait ItemArgumentParseable: Sized {
154 /// Parse the argument
155 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError>;
156}
157
158/// An Object value that be parsed from a netdoc
159pub trait ItemObjectParseable: Sized {
160 /// Check that the Label is right
161 fn check_label(label: &str) -> Result<(), ErrorProblem>;
162
163 /// Convert the bytes of the Object (which was present) into the actual value
164 ///
165 /// `input` has been base64-decoded.
166 fn from_bytes(input: &[u8]) -> Result<Self, ErrorProblem>;
167}
168
169/// Token indicating that a keyword is structural
170///
171/// Returned by [`NetdocParseable::is_structural_keyword`]
172#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
173#[allow(clippy::exhaustive_structs)]
174pub struct IsStructural;
175
176//---------- provided blanket impls ----------
177
178impl<T: NormalItemArgument> ItemArgumentParseable for T {
179 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, AE> {
180 let v = args
181 .next()
182 .ok_or(AE::Missing)?
183 .parse()
184 .map_err(|_e| AE::Missing)?;
185 Ok(v)
186 }
187}
188
189impl<T: ItemValueParseable> ItemValueParseable for Arc<T> {
190 fn from_unparsed(item: UnparsedItem<'_>) -> Result<Self, ErrorProblem> {
191 T::from_unparsed(item).map(Arc::new)
192 }
193}
194
195impl<T: NetdocParseable> NetdocParseable for Arc<T> {
196 fn doctype_for_error() -> &'static str {
197 T::doctype_for_error()
198 }
199 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
200 T::is_intro_item_keyword(kw)
201 }
202 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
203 T::is_structural_keyword(kw)
204 }
205 fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, EP> {
206 T::from_items(input, stop_at).map(Arc::new)
207 }
208}
209impl<T: NetdocParseableFields> NetdocParseableFields for Arc<T> {
210 type Accumulator = T::Accumulator;
211 fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
212 T::is_item_keyword(kw)
213 }
214 fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem<'_>) -> Result<(), EP> {
215 T::accumulate_item(acc, item)
216 }
217 fn finish(acc: Self::Accumulator) -> Result<Self, EP> {
218 T::finish(acc).map(Arc::new)
219 }
220}
221
222/// implement [`ItemValueParseable`] for a particular tuple size
223macro_rules! item_value_parseable_for_tuple {
224 { $($i:literal)* } => { paste! {
225 impl< $( [<T$i>]: ItemArgumentParseable, )* >
226 ItemValueParseable for ( $( [<T$i>], )* )
227 {
228 fn from_unparsed(
229 #[allow(unused_mut)]
230 mut item: UnparsedItem<'_>,
231 ) -> Result<Self, ErrorProblem> {
232 let r = ( $(
233 <[<T$i>] as ItemArgumentParseable>::from_args(
234 item.args_mut(),
235 ).map_err(item.args().error_handler(stringify!($i)))?,
236 )* );
237 item.check_no_object()?;
238 Ok(r)
239 }
240 }
241 } }
242}
243
244item_value_parseable_for_tuple! {}
245item_value_parseable_for_tuple! { 0 }
246item_value_parseable_for_tuple! { 0 1 }
247item_value_parseable_for_tuple! { 0 1 2 }
248item_value_parseable_for_tuple! { 0 1 2 3 }
249item_value_parseable_for_tuple! { 0 1 2 3 4 }
250item_value_parseable_for_tuple! { 0 1 2 3 4 5 }
251item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 }
252item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 7 }
253item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 7 8 }
254item_value_parseable_for_tuple! { 0 1 2 3 4 5 6 7 8 9 }