diff --git a/crates/tor-netdoc/semver.md b/crates/tor-netdoc/semver.md index bef63e1dda60b8b8e828a9e01115fc67d135a448..1f0b46f47f5216c831e1ed85164b49d86b1ff072 100644 --- a/crates/tor-netdoc/semver.md +++ b/crates/tor-netdoc/semver.md @@ -61,3 +61,8 @@ BREAKING: `ConsensusVoterInfo`; several field types changed ADDED: `ConsensusVoterInfo`: `ItemValueParseable`, `ItemValueEncodable`, `Constructor` ADDED: `VoteAuthorityEntry`, `VoteAuthoritySection` ADDED: `SupersededAuthorityKey`, `ConsensusAuthoritySection` +BREAKING: `NormalItemArgument` no longer has `FromStr` and `Display` as supertraits +BREAKING: `Unknown::as_ref` signature changed; `only_known` may be needed too now. +ADDED: `Unknown::only_known` +ADDED: `Unknown` is now an exhaustive enum. +ADDED: `KeywordOrString` diff --git a/crates/tor-netdoc/src/doc/netstatus.rs b/crates/tor-netdoc/src/doc/netstatus.rs index d0b839dd0bd69ffed9103f0e2d9d9bd68744e146..920848a32e300abd53bece37455866cdac86c455 100644 --- a/crates/tor-netdoc/src/doc/netstatus.rs +++ b/crates/tor-netdoc/src/doc/netstatus.rs @@ -85,6 +85,7 @@ use std::sync::Arc; use std::{net, result, time}; use tor_error::{Bug, HasKind, bad_api_usage, internal}; use tor_protover::Protocols; +use void::ResultVoidExt as _; use derive_deftly::{Deftly, define_derive_deftly}; use digest::Digest; @@ -518,6 +519,10 @@ impl ConsensusFlavor { } } +define_directory_signature_hash_algo! { + #[derive_deftly_adhoc] // TODO DIRAUTH; suppresses complaints about attrs used only in poc +} + /// The signature of a single directory authority on a networkstatus document. #[derive(Debug, Clone)] #[non_exhaustive] @@ -526,7 +531,7 @@ pub struct Signature { /// /// Currently sha1 and sh256 are recognized. Here we only support /// sha256. - pub digestname: String, + pub digest_algo: KeywordOrString, /// Fingerprints of the keys for the authority that made /// this signature. pub key_ids: AuthCertKeyIds, @@ -1447,7 +1452,7 @@ impl Signature { .at_pos(item.pos())); } - let (alg, id_fp, sk_fp) = if item.n_args() > 2 { + let (digest_algo, id_fp, sk_fp) = if item.n_args() > 2 { ( item.required_arg(0)?, item.required_arg(1)?, @@ -1457,7 +1462,7 @@ impl Signature { ("sha1", item.required_arg(0)?, item.required_arg(1)?) }; - let digestname = alg.to_string(); + let digest_algo = digest_algo.to_string().parse().void_unwrap(); let id_fingerprint = id_fp.parse::()?.into(); let sk_fingerprint = sk_fp.parse::()?.into(); let key_ids = AuthCertKeyIds { @@ -1467,7 +1472,7 @@ impl Signature { let signature = item.obj("SIGNATURE")?; Ok(Signature { - digestname, + digest_algo, key_ids, signature, }) @@ -1566,9 +1571,12 @@ impl SignatureGroup { continue; } - let d: Option<&[u8]> = match sig.digestname.as_ref() { - "sha256" => self.sha256.as_ref().map(|a| &a[..]), - "sha1" => self.sha1.as_ref().map(|a| &a[..]), + use DirectorySignatureHashAlgo as DSHA; + use KeywordOrString as KOS; + + let d: Option<&[u8]> = match sig.digest_algo { + KOS::Known(DSHA::Sha256) => self.sha256.as_ref().map(|a| &a[..]), + KOS::Known(DSHA::Sha1) => self.sha1.as_ref().map(|a| &a[..]), _ => None, // We don't know how to find this digest. }; if d.is_none() { diff --git a/crates/tor-netdoc/src/encode.rs b/crates/tor-netdoc/src/encode.rs index bce205735534cb138f41b4f448dec523a85c81d8..03abc84707fe9003bd1e3cb60c144944368923ba 100644 --- a/crates/tor-netdoc/src/encode.rs +++ b/crates/tor-netdoc/src/encode.rs @@ -230,7 +230,7 @@ impl Default for NetdocEncoder { } } -impl ItemArgument for T { +impl ItemArgument for T { fn write_arg_onto(&self, out: &mut ItemEncoder<'_>) -> Result<(), Bug> { (*self.to_string()).write_arg_onto(out) } diff --git a/crates/tor-netdoc/src/lib.rs b/crates/tor-netdoc/src/lib.rs index d20e980dece33689ebfbaec67e631ffdf5a6bc03..9ecfb558d47dc9061f06dc0b8dfdb78ee16c10a8 100644 --- a/crates/tor-netdoc/src/lib.rs +++ b/crates/tor-netdoc/src/lib.rs @@ -119,9 +119,11 @@ pub enum AllowAnnotations { /// A type that is represented as a single argument /// whose representation is as for the type's `FromStr` and `Display`. /// -/// Implementing this trait enables a blanket impl of `parse2::ItemArgumentParseable` -/// and `build::ItemArgument`. -pub trait NormalItemArgument: std::str::FromStr + std::fmt::Display {} +/// Implementing this trait enables a blanket impl of +/// [`parse2::ItemArgumentParseable`] (if `FromStr`) +/// and +/// [`encode::ItemArgument`] (if `Display`). +pub trait NormalItemArgument {} // TODO: should we implement ItemArgument for, say, tor_llcrypto::pk::rsa::RsaIdentity ? // It's not clear whether it's always formatted the same way in all parts of the spec. // The Display impl of RsaIdentity adds a `$` which is not supposed to be present diff --git a/crates/tor-netdoc/src/parse2/poc/netstatus.rs b/crates/tor-netdoc/src/parse2/poc/netstatus.rs index ffe05ef926e1567b4286553df66c213ffe3a9921..ba6bfed62f945515b8bbd02fd1ceaf793d58ab94 100644 --- a/crates/tor-netdoc/src/parse2/poc/netstatus.rs +++ b/crates/tor-netdoc/src/parse2/poc/netstatus.rs @@ -134,17 +134,8 @@ define_derive_deftly! { } } -/// `directory-signature` hash algorithm argument -#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::EnumString, Deftly)] -#[derive_deftly(DirectorySignatureHashesAccu)] -#[non_exhaustive] -pub enum DirectorySignatureHashAlgo { - /// SHA-1 - #[deftly(hash_len = "20")] - Sha1, - /// SHA-256 - #[deftly(hash_len = "32")] - Sha256, +define_directory_signature_hash_algo! { + #[derive_deftly(DirectorySignatureHashesAccu)] } /// Unsupported `vote-status` value diff --git a/crates/tor-netdoc/src/parse2/traits.rs b/crates/tor-netdoc/src/parse2/traits.rs index d584eff4b77cc87383f6b592467cb22bd5bdfed1..6826f9e08b3f0ab2f05a11be5507426b31d7ff83 100644 --- a/crates/tor-netdoc/src/parse2/traits.rs +++ b/crates/tor-netdoc/src/parse2/traits.rs @@ -149,7 +149,7 @@ impl ItemArgumentParseable for Arc { } } -impl ItemArgumentParseable for T { +impl ItemArgumentParseable for T { fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result { let v = args .next() diff --git a/crates/tor-netdoc/src/types.rs b/crates/tor-netdoc/src/types.rs index 78762d149b54531cfe2a1d09774224329d2b145b..8cfab65646efde0a1e203fb27f7c62e67fb381d3 100644 --- a/crates/tor-netdoc/src/types.rs +++ b/crates/tor-netdoc/src/types.rs @@ -17,6 +17,7 @@ pub mod version; pub use embedded_cert::*; +pub use misc::KeywordOrString; pub use misc::RetainedOrderVec; pub use misc::{ContactInfo, InvalidNickname, Nickname, NotPresent, NumericBoolean, Unknown}; pub use misc::{Hostname, InternetHost, InvalidHostname, InvalidInternetHost}; diff --git a/crates/tor-netdoc/src/types/misc.rs b/crates/tor-netdoc/src/types/misc.rs index 3a3e8859fe97bf3fbf695506ee14316cccaf07cd..2e70147253e2d2f7a87e76be62648a46d186f14e 100644 --- a/crates/tor-netdoc/src/types/misc.rs +++ b/crates/tor-netdoc/src/types/misc.rs @@ -772,7 +772,7 @@ mod ignored_impl { /// `Unknown` is not `Eq` or `Ord` because we won't want to relate a `Discarded` /// to a `Retained`. That would be a logic error. `partial_cmp` gives `None` for this. #[derive(Debug, PartialEq, Clone, Copy, Hash)] -#[non_exhaustive] +#[allow(clippy::exhaustive_enums)] // this isn't going to change pub enum Unknown { /// The parsing discarded unknown values and they are no longer available. Discarded(PhantomData), @@ -803,7 +803,22 @@ impl Unknown { } /// Obtain an `Unknown` containing (maybe) a reference - pub fn as_ref(&self) -> Option<&T> { + pub fn as_ref(&self) -> Unknown<&T> { + match self { + Unknown::Discarded(_) => Unknown::Discarded(PhantomData), + #[cfg(feature = "retain-unknown")] + Unknown::Retained(t) => Unknown::Retained(t), + } + } + + /// Return the retained unknown data, giving `None` if none was saved + /// + /// This is the function for disregarding the possible previously existence + /// of now-discarded unknown (unrecognised) information. + /// + /// Use [`into_retained`](Self::into_retained) if it would be a bug + /// if unrecognised information had been previously discarded. + pub fn only_known(self) -> Option { match self { Unknown::Discarded(_) => None, #[cfg(feature = "retain-unknown")] @@ -862,6 +877,52 @@ impl PartialOrd for Unknown { // ============================================================ +/// Known keyword (enum) value, or arbitrary string +/// +/// `T` should be a `Copy` enum with unit variants. +/// It should have appropriate `FromStr` and `Display`, +/// as well as [`NormalItemArgument`], impls. +/// +/// Then `KeywordOrString` will implement the same traits. +/// +/// Unlike [`Unknown`], unknown values are always retained as strings. +// +// `RelayFlags` has machinery for parsing flags and retaining unknown values, +// but it uses `Unknown` to maybe discard unknown flags, +// and it is generally quite a lot more complicated. +#[derive(Debug, PartialEq, Clone, Hash)] +#[allow(clippy::exhaustive_enums)] // this isn't going to change +pub enum KeywordOrString { + /// Known and recognised `T` + Known(T), + + /// Unknown value in arbitrary syntax + Unknown(String), +} + +impl NormalItemArgument for KeywordOrString {} + +impl Display for KeywordOrString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + KeywordOrString::Known(t) => Display::fmt(t, f), + KeywordOrString::Unknown(s) => Display::fmt(s, f), + } + } +} + +impl FromStr for KeywordOrString { + type Err = Void; + fn from_str(s: &str) -> Result { + Ok(match s.parse() { + Ok(y) => KeywordOrString::Known(y), + Err(_) => KeywordOrString::Unknown(s.to_owned()), + }) + } +} + +// ============================================================ + /// A sequence of `T` items, with their order retained /// /// Normally when a `Vec` appears in a network document, diff --git a/crates/tor-netdoc/src/types/relay_flags.rs b/crates/tor-netdoc/src/types/relay_flags.rs index 4209d4cf0951649e3465a29190ce9e9a631e13f5..7ba35afae370206d8f41e93b59c9b4df4d0f6b50 100644 --- a/crates/tor-netdoc/src/types/relay_flags.rs +++ b/crates/tor-netdoc/src/types/relay_flags.rs @@ -301,6 +301,7 @@ mod parse2_impl { flags .unknown .as_ref() + .only_known() .map(|u| u.iter()) .into_iter() .flatten() diff --git a/crates/tor-netdoc/src/util.rs b/crates/tor-netdoc/src/util.rs index 5f0df0b59eb0841134fe64cb387ed789cf651a54..317fa29001f06f0cd2351615dd35abadba6b976d 100644 --- a/crates/tor-netdoc/src/util.rs +++ b/crates/tor-netdoc/src/util.rs @@ -91,3 +91,32 @@ fn test_as_mut_compiles() { let _: &mut S<()> = S { t: () }.as_mut(); } + +/// Define `DirectorySignatureHashAlgo`, for `directory-signature` items +/// +/// This macro exists to avoid clone-and-hack between poc and prod code. +/// +/// It is difficult for either of those modules to use the other's definition, +/// because they have different stability, different cfg gating, +/// and want to derive deftly differently. +/// +/// tl;dr: defining this type in doc/netstatus.rs would mean +/// the poc derived structs would end up in the prod module; +/// defining it in poc puts it behind the `incomplete` feature gate. +// +// TODO DIRAUTH after poc abolished, turn back into a normal struct DirectorySignatureHashAlgo +macro_rules! define_directory_signature_hash_algo { { $( $attrs:tt )* } => { + /// `directory-signature` hash algorithm argument + #[derive(Clone, Copy, Debug, Eq, PartialEq, strum::EnumString, Deftly)] + $($attrs)* + #[non_exhaustive] + #[strum(serialize_all = "snake_case")] + pub enum DirectorySignatureHashAlgo { + /// SHA-1 + #[deftly(hash_len = "20")] + Sha1, + /// SHA-256 + #[deftly(hash_len = "32")] + Sha256, + } +} }