diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000..2e06d1584 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-consts = true +allow-unwrap-in-tests = true diff --git a/Cargo.toml b/Cargo.toml index 866c98d48..76339a17c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,43 @@ tls_codec_derive = { path = "./tls_codec/derive" } x509-tsp = { path = "./x509-tsp" } x509-cert = { path = "./x509-cert" } x509-ocsp = { path = "./x509-ocsp" } + +[workspace.lints.clippy] +borrow_as_ptr = "warn" +cast_lossless = "warn" +cast_possible_truncation = "warn" +cast_possible_wrap = "warn" +cast_precision_loss = "warn" +cast_sign_loss = "warn" +checked_conversions = "warn" +doc_markdown = "warn" +from_iter_instead_of_collect = "warn" +implicit_saturating_sub = "warn" +manual_assert = "warn" +map_unwrap_or = "warn" +missing_errors_doc = "warn" +missing_panics_doc = "warn" +mod_module_files = "warn" +must_use_candidate = "warn" +needless_range_loop = "allow" +ptr_as_ptr = "warn" +redundant_closure_for_method_calls = "warn" +ref_as_ptr = "warn" +return_self_not_must_use = "warn" +semicolon_if_nothing_returned = "warn" +trivially_copy_pass_by_ref = "warn" +std_instead_of_alloc = "warn" +std_instead_of_core = "warn" +undocumented_unsafe_blocks = "warn" +unnecessary_safety_comment = "warn" +unwrap_in_result = "warn" +unwrap_used = "warn" + +[workspace.lints.rust] +missing_copy_implementations = "warn" +missing_debug_implementations = "warn" +missing_docs = "warn" +trivial_casts = "warn" +trivial_numeric_casts = "warn" +unused_lifetimes = "warn" +unused_qualifications = "warn" diff --git a/der/Cargo.toml b/der/Cargo.toml index f27d56fb7..fbfc1cdbd 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -44,6 +44,9 @@ oid = ["dep:const-oid"] pem = ["dep:pem-rfc7468", "alloc", "zeroize"] real = [] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/der/src/asn1/any.rs b/der/src/asn1/any.rs index 2646bc1d4..6524918bf 100644 --- a/der/src/asn1/any.rs +++ b/der/src/asn1/any.rs @@ -37,6 +37,9 @@ impl<'a> AnyRef<'a> { }; /// Create a new [`AnyRef`] from the provided [`Tag`] and DER bytes. + /// + /// # Errors + /// Returns [`Error`] with [`ErrorKind::Length`] if `bytes` is too long. pub const fn new(tag: Tag, bytes: &'a [u8]) -> Result { match BytesRef::new(bytes) { Ok(value) => Ok(Self { tag, value }), @@ -50,16 +53,21 @@ impl<'a> AnyRef<'a> { } /// Get the raw value for this [`AnyRef`] type as a byte slice. + #[must_use] pub fn value(self) -> &'a [u8] { self.value.as_slice() } /// Returns [`Tag`] and [`Length`] of self. + #[must_use] pub fn header(&self) -> Header { Header::new(self.tag, self.value.len()) } /// Attempt to decode this [`AnyRef`] type into the inner value. + /// + /// # Errors + /// Returns `T::Error` if a decoding error occurred. pub fn decode_as(self) -> Result>::Error> where T: Choice<'a> + DecodeValue<'a>, @@ -68,6 +76,9 @@ impl<'a> AnyRef<'a> { } /// Attempt to decode this [`AnyRef`] type into the inner value. + /// + /// # Errors + /// Returns `T::Error` if a decoding error occurred. pub fn decode_as_encoding( self, encoding: EncodingRules, @@ -86,12 +97,16 @@ impl<'a> AnyRef<'a> { } /// Is this value an ASN.1 `NULL` value? + #[must_use] pub fn is_null(self) -> bool { self == Self::NULL } /// Attempt to decode this value an ASN.1 `SEQUENCE`, creating a new /// nested reader and calling the provided argument with it. + /// + /// # Errors + /// Returns `E` in the event an error is returned from `F` or if a decoding error occurs. pub fn sequence(self, f: F) -> Result where F: FnOnce(&mut SliceReader<'a>) -> Result, @@ -192,22 +207,30 @@ mod allocating { impl Any { /// Create a new [`Any`] from the provided [`Tag`] and DER bytes. + /// + /// # Errors + /// If `bytes` is too long. pub fn new(tag: Tag, bytes: impl Into>) -> Result { let value = BytesOwned::new(bytes)?; Ok(Self { tag, value }) } /// Allow access to value + #[must_use] pub fn value(&self) -> &[u8] { self.value.as_slice() } /// Returns [`Tag`] and [`Length`] of self. + #[must_use] pub fn header(&self) -> Header { Header::new(self.tag, self.value.len()) } /// Attempt to decode this [`Any`] type into the inner value. + /// + /// # Errors + /// Returns `T::Error` if a decoding error occurred. pub fn decode_as<'a, T>(&'a self) -> Result>::Error> where T: Choice<'a> + DecodeValue<'a>, @@ -216,6 +239,9 @@ mod allocating { } /// Attempt to decode this [`Any`] type into the inner value with the given encoding rules. + /// + /// # Errors + /// Returns `T::Error` if a decoding error occurred. pub fn decode_as_encoding<'a, T>( &'a self, encoding: EncodingRules, @@ -227,6 +253,9 @@ mod allocating { } /// Encode the provided type as an [`Any`] value. + /// + /// # Errors + /// If an encoding error occurred. pub fn encode_from(msg: &T) -> Result where T: Tagged + EncodeValue, @@ -239,6 +268,9 @@ mod allocating { /// Attempt to decode this value an ASN.1 `SEQUENCE`, creating a new /// nested reader and calling the provided argument with it. + /// + /// # Errors + /// If a decoding error occurred. pub fn sequence<'a, F, T, E>(&'a self, f: F) -> Result where F: FnOnce(&mut SliceReader<'a>) -> Result, @@ -248,6 +280,7 @@ mod allocating { } /// [`Any`] representation of the ASN.1 `NULL` type. + #[must_use] pub fn null() -> Self { Self { tag: Tag::Null, @@ -256,6 +289,7 @@ mod allocating { } /// Create a new [`AnyRef`] from the provided [`Any`] owned tag and bytes. + #[must_use] pub fn to_ref(&self) -> AnyRef<'_> { AnyRef { tag: self.tag, @@ -350,6 +384,7 @@ mod allocating { impl Any { /// Is this value an ASN.1 `NULL` value? + #[must_use] pub fn is_null(&self) -> bool { self.owned_to_ref() == AnyRef::NULL } diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs index f28c5cfbe..3ce19f169 100644 --- a/der/src/asn1/bit_string.rs +++ b/der/src/asn1/bit_string.rs @@ -32,6 +32,12 @@ impl<'a> BitStringRef<'a> { /// /// Accepts an optional number of "unused bits" (0-7) which are omitted /// from the final octet. This number is 0 if the value is octet-aligned. + /// + /// # Errors + /// Returns [`Error`] if any of the following occur: + /// - `unused_bits` is invalid + /// - `bytes` is too long + /// - an overflow occurred calculating the bit length pub fn new(unused_bits: u8, bytes: &'a [u8]) -> Result { let unused_bits = UnusedBits::new(unused_bits, bytes)?; let inner = BytesRef::new(bytes).map_err(|_| Self::TAG.length_error())?; @@ -52,16 +58,21 @@ impl<'a> BitStringRef<'a> { /// Create a new ASN.1 `BIT STRING` from the given bytes. /// /// The "unused bits" are set to 0. + /// + /// # Errors + /// Has the same error cases as [`BitStringRef::new`]. pub fn from_bytes(bytes: &'a [u8]) -> Result { Self::new(0, bytes) } /// Get the number of unused bits in this byte slice. + #[must_use] pub fn unused_bits(&self) -> u8 { *self.unused_bits } /// Is the number of unused bits a value other than 0? + #[must_use] pub fn has_unused_bits(&self) -> bool { *self.unused_bits != 0 } @@ -77,6 +88,7 @@ impl<'a> BitStringRef<'a> { } /// Get the length of this `BIT STRING` in bits. + #[must_use] pub fn bit_len(&self) -> usize { let bit_len = self.bit_len_checked(); debug_assert!(bit_len.is_some()); @@ -87,11 +99,13 @@ impl<'a> BitStringRef<'a> { /// Get the number of bytes/octets needed to represent this `BIT STRING` /// when serialized in an octet-aligned manner. + #[must_use] pub fn byte_len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } @@ -103,6 +117,7 @@ impl<'a> BitStringRef<'a> { /// /// Use [`BitString::raw_bytes`] to obtain access to the raw value /// regardless of the presence of unused bits. + #[must_use] pub fn as_bytes(&self) -> Option<&'a [u8]> { if self.has_unused_bits() { None @@ -116,11 +131,13 @@ impl<'a> BitStringRef<'a> { /// Note that the byte string may contain extra unused bits in the final /// octet. If the number of unused bits is expected to be 0, the /// [`BitStringRef::as_bytes`] function can be used instead. + #[must_use] pub fn raw_bytes(&self) -> &'a [u8] { self.inner.as_slice() } /// Iterator over the bits of this `BIT STRING`. + #[must_use] pub fn bits(self) -> BitStringIter<'a> { BitStringIter { bit_string: self, @@ -129,6 +146,7 @@ impl<'a> BitStringRef<'a> { } /// Returns Some(bit) if index is valid + #[must_use] pub fn get(&self, position: usize) -> Option { if position >= self.bit_len() { return None; @@ -312,6 +330,12 @@ mod allocating { /// /// Accepts an optional number of "unused bits" (0-7) which are omitted /// from the final octet. This number is 0 if the value is octet-aligned. + /// + /// # Errors + /// Returns [`Error`] if any of the following occur: + /// - `unused_bits` is invalid + /// - `bytes` is too long + /// - an overflow occurred calculating the bit length pub fn new(unused_bits: u8, bytes: impl Into>) -> Result { let inner = bytes.into(); @@ -327,17 +351,22 @@ mod allocating { /// Create a new ASN.1 `BIT STRING` from the given bytes. /// /// The "unused bits" are set to 0. + /// + /// # Errors + /// If `bytes` is too long. pub fn from_bytes(bytes: &[u8]) -> Result { Self::new(0, bytes) } /// Get the number of unused bits in the octet serialization of this /// `BIT STRING`. + #[must_use] pub fn unused_bits(&self) -> u8 { *self.unused_bits } /// Is the number of unused bits a value other than 0? + #[must_use] pub fn has_unused_bits(&self) -> bool { *self.unused_bits != 0 } @@ -356,6 +385,7 @@ mod allocating { } /// Get the length of this `BIT STRING` in bits. + #[must_use] pub fn bit_len(&self) -> usize { let bit_len = self.bit_len_checked(); debug_assert!(bit_len.is_some()); @@ -365,6 +395,7 @@ mod allocating { } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } @@ -376,6 +407,7 @@ mod allocating { /// /// Use [`BitString::raw_bytes`] to obtain access to the raw value /// regardless of the presence of unused bits. + #[must_use] pub fn as_bytes(&self) -> Option<&[u8]> { if self.has_unused_bits() { None @@ -385,16 +417,19 @@ mod allocating { } /// Borrow the raw bytes of this `BIT STRING`. + #[must_use] pub fn raw_bytes(&self) -> &[u8] { self.inner.as_slice() } /// Iterator over the bits of this `BIT STRING`. + #[must_use] pub fn bits(&self) -> BitStringIter<'_> { BitStringRef::from(self).bits() } /// Returns Some(bit) if index is valid + #[must_use] pub fn get(&self, position: usize) -> Option { BitStringRef::from(self).get(position) } @@ -475,7 +510,7 @@ mod allocating { fn try_from(bit_string: BitStringRef<'a>) -> Result> { bit_string .as_bytes() - .map(|bytes| bytes.to_vec()) + .map(<[u8]>::to_vec) .ok_or_else(|| Tag::BitString.value_error().into()) } } @@ -525,6 +560,7 @@ mod allocating { } /// Iterator over the bits of a [`BitString`]. +#[derive(Debug)] pub struct BitStringIter<'a> { /// [`BitString`] being iterated over. bit_string: BitStringRef<'a>, @@ -683,7 +719,7 @@ mod tests { assert_eq!(bits.len(), 18); for bit in [0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1] { - assert_eq!(u8::from(bits.next().unwrap()), bit) + assert_eq!(u8::from(bits.next().unwrap()), bit); } // Ensure `None` is returned on successive calls @@ -696,7 +732,7 @@ mod tests { assert_eq!( parse_bitstring(&[0x03]).err().unwrap().kind(), Tag::BitString.value_error() - ) + ); } #[test] diff --git a/der/src/asn1/bit_string/allowed_len_bit_string.rs b/der/src/asn1/bit_string/allowed_len_bit_string.rs index ee861046e..baa4b99f8 100644 --- a/der/src/asn1/bit_string/allowed_len_bit_string.rs +++ b/der/src/asn1/bit_string/allowed_len_bit_string.rs @@ -2,7 +2,7 @@ use core::ops::RangeInclusive; use crate::{Error, ErrorKind, Tag}; -/// Trait on automatically derived by BitString macro. +/// Trait on automatically derived by `BitString` macro. /// Used for checking if binary data fits into defined struct. /// /// ``` @@ -31,7 +31,10 @@ pub trait AllowedLenBitString { /// Implementer must specify how many bits are allowed const ALLOWED_LEN_RANGE: RangeInclusive; - /// Returns an error if the bitstring is not in expected length range + /// Check the big length. + /// + /// # Errors + /// Returns an error if the bitstring is not in expected length range. fn check_bit_len(bit_len: u16) -> Result<(), Error> { let allowed_len_range = Self::ALLOWED_LEN_RANGE; diff --git a/der/src/asn1/bmp_string.rs b/der/src/asn1/bmp_string.rs index 1e72eff5a..4df310a80 100644 --- a/der/src/asn1/bmp_string.rs +++ b/der/src/asn1/bmp_string.rs @@ -18,6 +18,9 @@ pub struct BmpString { impl BmpString { /// Create a new [`BmpString`] from its UCS-2 encoding. + /// + /// # Errors + /// If `bytes` contains out-of-range characters. pub fn from_ucs2(bytes: impl Into>) -> Result { let bytes = bytes.into(); @@ -42,6 +45,9 @@ impl BmpString { } /// Create a new [`BmpString`] from a UTF-8 string. + /// + /// # Errors + /// If a length calculation overflowed or an internal conversion failed. pub fn from_utf8(utf8: &str) -> Result { let capacity = utf8 .len() @@ -58,17 +64,20 @@ impl BmpString { } /// Borrow the encoded UCS-2 as bytes. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.bytes.as_ref() } /// Obtain the inner bytes. #[inline] + #[must_use] pub fn into_bytes(self) -> Box<[u8]> { self.bytes.into() } /// Get an iterator over characters in the string. + #[allow(clippy::missing_panics_doc)] pub fn chars(&self) -> impl Iterator + '_ { char::decode_utf16(self.codepoints()) .map(|maybe_char| maybe_char.expect("unpaired surrogates checked in constructor")) @@ -76,7 +85,7 @@ impl BmpString { /// Get an iterator over the `u16` codepoints. pub fn codepoints(&self) -> impl Iterator + '_ { - // TODO(tarcieri): use `array_chunks` + // TODO(tarcieri): use `as_chunks` self.as_bytes() .chunks_exact(2) .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])) diff --git a/der/src/asn1/general_string.rs b/der/src/asn1/general_string.rs index c1133f2d1..11375970c 100644 --- a/der/src/asn1/general_string.rs +++ b/der/src/asn1/general_string.rs @@ -8,6 +8,7 @@ pub struct GeneralStringRef<'a> { } impl<'a> GeneralStringRef<'a> { /// This is currently `&[u8]` internally, as `GeneralString` is not fully implemented yet + #[must_use] pub fn as_bytes(&self) -> &'a [u8] { self.inner.as_slice() } diff --git a/der/src/asn1/generalized_time.rs b/der/src/asn1/generalized_time.rs index a8ed0fc8e..322cd5a5e 100644 --- a/der/src/asn1/generalized_time.rs +++ b/der/src/asn1/generalized_time.rs @@ -35,17 +35,22 @@ impl GeneralizedTime { const LENGTH: usize = 15; /// Create a [`GeneralizedTime`] from a [`DateTime`]. + #[must_use] pub const fn from_date_time(datetime: DateTime) -> Self { Self(datetime) } /// Convert this [`GeneralizedTime`] into a [`DateTime`]. + #[must_use] pub const fn to_date_time(&self) -> DateTime { self.0 } /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH` - /// (a.k.a. "Unix time") + /// (a.k.a. "Unix time"). + /// + /// # Errors + /// Returns [`Error`] with a value error kind in the event `unix_duration` could not be parsed. pub fn from_unix_duration(unix_duration: Duration) -> Result { DateTime::from_unix_duration(unix_duration) .map(Into::into) @@ -53,11 +58,15 @@ impl GeneralizedTime { } /// Get the duration of this timestamp since `UNIX_EPOCH`. + #[must_use] pub fn to_unix_duration(&self) -> Duration { self.0.unix_duration() } /// Instantiate from [`SystemTime`]. + /// + /// # Errors + /// If the time conversion failed. #[cfg(feature = "std")] pub fn from_system_time(time: SystemTime) -> Result { DateTime::try_from(time) @@ -67,6 +76,7 @@ impl GeneralizedTime { /// Convert to [`SystemTime`]. #[cfg(feature = "std")] + #[must_use] pub fn to_system_time(&self) -> SystemTime { self.0.to_system_time() } diff --git a/der/src/asn1/ia5_string.rs b/der/src/asn1/ia5_string.rs index 3dbb2b87b..f8b79ad95 100644 --- a/der/src/asn1/ia5_string.rs +++ b/der/src/asn1/ia5_string.rs @@ -42,6 +42,9 @@ pub struct Ia5StringRef<'a> { impl<'a> Ia5StringRef<'a> { /// Create a new `IA5String`. + /// + /// # Errors + /// In the event characters are outside `IA5String`'s allowed set. pub fn new(input: &'a T) -> Result where T: AsRef<[u8]> + ?Sized, @@ -59,6 +62,7 @@ impl<'a> Ia5StringRef<'a> { } /// Borrow the inner `str`. + #[must_use] pub fn as_str(&self) -> &'a str { self.inner.as_str() } @@ -118,6 +122,9 @@ mod allocation { impl Ia5String { /// Create a new `IA5String`. + /// + /// # Errors + /// If characters are out of range. pub fn new(input: &T) -> Result where T: AsRef<[u8]> + ?Sized, diff --git a/der/src/asn1/integer.rs b/der/src/asn1/integer.rs index ffdb76d84..e00e3fcd7 100644 --- a/der/src/asn1/integer.rs +++ b/der/src/asn1/integer.rs @@ -10,10 +10,7 @@ use crate::{EncodeValue, Result, encode::encode_value_to_slice}; /// Is the highest bit of the first byte in the slice set to `1`? (if present) #[inline] fn is_highest_bit_set(bytes: &[u8]) -> bool { - bytes - .first() - .map(|byte| byte & 0b10000000 != 0) - .unwrap_or(false) + bytes.first().is_some_and(|byte| byte & 0b10000000 != 0) } /// Compare two integer values diff --git a/der/src/asn1/integer/int.rs b/der/src/asn1/integer/int.rs index 2dec0559a..0da6c93de 100644 --- a/der/src/asn1/integer/int.rs +++ b/der/src/asn1/integer/int.rs @@ -107,6 +107,9 @@ pub struct IntRef<'a> { impl<'a> IntRef<'a> { /// Create a new [`IntRef`] from a byte slice. + /// + /// # Errors + /// Returns [`Error`] if `bytes` is too long. pub fn new(bytes: &'a [u8]) -> Result { let inner = BytesRef::new(strip_leading_ones(bytes)) .map_err(|_| ErrorKind::Length { tag: Self::TAG })?; @@ -116,16 +119,19 @@ impl<'a> IntRef<'a> { /// Borrow the inner byte slice which contains the least significant bytes /// of a big endian integer value with all leading ones stripped. + #[must_use] pub fn as_bytes(&self) -> &'a [u8] { self.inner.as_slice() } /// Get the length of this [`IntRef`] in bytes. + #[must_use] pub fn len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } @@ -201,6 +207,9 @@ mod allocating { impl Int { /// Create a new [`Int`] from a byte slice. + /// + /// # Errors + /// If `bytes` is too long. pub fn new(bytes: &[u8]) -> Result { let inner = BytesOwned::new(strip_leading_ones(bytes)) .map_err(|_| ErrorKind::Length { tag: Self::TAG })?; @@ -210,16 +219,19 @@ mod allocating { /// Borrow the inner byte slice which contains the least significant bytes /// of a big endian integer value with all leading ones stripped. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } /// Get the length of this [`Int`] in bytes. + #[must_use] pub fn len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } diff --git a/der/src/asn1/integer/uint.rs b/der/src/asn1/integer/uint.rs index baecdd0b8..c0b6f87c9 100644 --- a/der/src/asn1/integer/uint.rs +++ b/der/src/asn1/integer/uint.rs @@ -94,6 +94,9 @@ pub struct UintRef<'a> { impl<'a> UintRef<'a> { /// Create a new [`UintRef`] from a byte slice. + /// + /// # Errors + /// Returns [`Error`] in the event `bytes` is too long. pub fn new(bytes: &'a [u8]) -> Result { let inner = BytesRef::new(strip_leading_zeroes(bytes)) .map_err(|_| ErrorKind::Length { tag: Self::TAG })?; @@ -103,16 +106,19 @@ impl<'a> UintRef<'a> { /// Borrow the inner byte slice which contains the least significant bytes /// of a big endian integer value with all leading zeros stripped. + #[must_use] pub fn as_bytes(&self) -> &'a [u8] { self.inner.as_slice() } /// Get the length of this [`UintRef`] in bytes. + #[must_use] pub fn len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } @@ -189,6 +195,9 @@ mod allocating { impl Uint { /// Create a new [`Uint`] from a byte slice. + /// + /// # Errors + /// If `bytes` is too long. pub fn new(bytes: &[u8]) -> Result { let inner = BytesOwned::new(strip_leading_zeroes(bytes)) .map_err(|_| ErrorKind::Length { tag: Self::TAG })?; @@ -198,16 +207,19 @@ mod allocating { /// Borrow the inner byte slice which contains the least significant bytes /// of a big endian integer value with all leading zeros stripped. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } /// Get the length of this [`Uint`] in bytes. + #[must_use] pub fn len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } diff --git a/der/src/asn1/internal_macros.rs b/der/src/asn1/internal_macros.rs index 1d5c1c7a6..fd947d1eb 100644 --- a/der/src/asn1/internal_macros.rs +++ b/der/src/asn1/internal_macros.rs @@ -103,12 +103,14 @@ macro_rules! impl_custom_class { /// in extension fields, which are denoted in an ASN.1 schema using /// the `...` ellipsis extension marker: /// + /// - Returns `Ok(Some(..))` if tag number matches. #[doc = concat!("- Returns `Ok(None)` if class other than [`Class::", stringify!($class_enum_name), "`] tag")] /// is encountered. /// - Returns `Ok(None)` if a field with a different tag number is encountered. /// These fields are not consumed in this case. - /// - Returns [`ErrorKind::Noncanonical`] if constructed bit is primitive. - /// - Returns `Ok(Some(..))` if tag number matches. + /// + /// # Errors + /// Returns [`ErrorKind::Noncanonical`] if constructed bit is primitive. pub fn decode_explicit<'a, R: Reader<'a>>( reader: &mut R, tag_number: TagNumber, @@ -132,6 +134,9 @@ macro_rules! impl_custom_class { /// Differences from `EXPLICIT`: /// - Returns [`ErrorKind::Noncanonical`] if constructed bit /// does not match constructed bit of the base encoding. + /// + /// # Errors + /// Returns `T::Error` in the event of a decoding error. pub fn decode_implicit<'a, R: Reader<'a>>( reader: &mut R, tag_number: TagNumber, diff --git a/der/src/asn1/octet_string.rs b/der/src/asn1/octet_string.rs index e080a4d11..c3ee70450 100644 --- a/der/src/asn1/octet_string.rs +++ b/der/src/asn1/octet_string.rs @@ -19,6 +19,9 @@ pub struct OctetStringRef { impl OctetStringRef { /// Create a new ASN.1 `OCTET STRING` from a byte slice. + /// + /// # Errors + /// Returns [`Error`] with [`ErrorKind::Length`] in the event `slice` is too long. pub fn new(slice: &[u8]) -> Result<&Self, Error> { BytesRef::new(slice) .map(Self::from_bytes_ref) @@ -37,21 +40,27 @@ impl OctetStringRef { } /// Borrow the inner byte slice. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } /// Get the length of the inner byte slice. + #[must_use] pub fn len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } /// Parse `T` from this `OCTET STRING`'s contents. + /// + /// # Errors + /// Returns `T::Error` in the event a decoding error occurred. pub fn decode_into<'a, T: Decode<'a>>(&'a self) -> Result { Decode::from_der(self.as_bytes()) } @@ -188,6 +197,9 @@ mod allocating { impl OctetString { /// Create a new ASN.1 `OCTET STRING`. + /// + /// # Errors + /// If `bytes` is too long. pub fn new(bytes: impl Into>) -> Result { let inner = BytesOwned::new(bytes)?; @@ -198,21 +210,25 @@ mod allocating { } /// Borrow the inner byte slice. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } /// Take ownership of the octet string. + #[must_use] pub fn into_bytes(self) -> Box<[u8]> { self.inner.into() } /// Get the length of the inner byte slice. + #[must_use] pub fn len(&self) -> Length { self.inner.len() } /// Is the inner byte slice empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } diff --git a/der/src/asn1/printable_string.rs b/der/src/asn1/printable_string.rs index a8e2a39ec..2f3f772b3 100644 --- a/der/src/asn1/printable_string.rs +++ b/der/src/asn1/printable_string.rs @@ -59,6 +59,9 @@ pub struct PrintableStringRef<'a> { impl<'a> PrintableStringRef<'a> { /// Create a new ASN.1 `PrintableString`. + /// + /// # Errors + /// If `input` contains characters outside the allowed range. pub fn new(input: &'a T) -> Result where T: AsRef<[u8]> + ?Sized, @@ -93,6 +96,7 @@ impl<'a> PrintableStringRef<'a> { } /// Borrow the inner `str`. + #[must_use] pub fn as_str(&self) -> &'a str { self.inner.as_str() } @@ -169,6 +173,9 @@ mod allocation { impl PrintableString { /// Create a new ASN.1 `PrintableString`. + /// + /// # Errors + /// If any characters are out-of-range. pub fn new(input: &T) -> Result where T: AsRef<[u8]> + ?Sized, diff --git a/der/src/asn1/real.rs b/der/src/asn1/real.rs index b4d2791a4..7169434de 100644 --- a/der/src/asn1/real.rs +++ b/der/src/asn1/real.rs @@ -202,10 +202,7 @@ impl FixedTag for f64 { /// NOTE: this function is zero indexed pub(crate) fn is_nth_bit_one(bytes: &[u8]) -> bool { if N < 8 { - bytes - .first() - .map(|byte| byte & (1 << N) != 0) - .unwrap_or(false) + bytes.first().is_some_and(|byte| byte & (1 << N) != 0) } else { false } diff --git a/der/src/asn1/sequence.rs b/der/src/asn1/sequence.rs index 27211ddbf..cfa6d76ad 100644 --- a/der/src/asn1/sequence.rs +++ b/der/src/asn1/sequence.rs @@ -36,7 +36,10 @@ pub struct SequenceRef { } impl SequenceRef { - /// Create a new ASN.1 `OCTET STRING` from a byte slice. + /// Create a new ASN.1 `SEQUENCE` from a byte slice. + /// + /// # Errors + /// Returns [`Error`] in the event `slice` is too long. pub fn new(slice: &[u8]) -> Result<&Self> { BytesRef::new(slice) .map(Self::from_bytes_ref) @@ -55,6 +58,7 @@ impl SequenceRef { } /// Borrow the inner byte slice. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.body.as_slice() } diff --git a/der/src/asn1/sequence_of.rs b/der/src/asn1/sequence_of.rs index 7bf9cfcb4..8eef402d3 100644 --- a/der/src/asn1/sequence_of.rs +++ b/der/src/asn1/sequence_of.rs @@ -26,6 +26,7 @@ pub struct SequenceOf { #[cfg(feature = "heapless")] impl SequenceOf { /// Create a new [`SequenceOf`]. + #[must_use] pub fn new() -> Self { Self { inner: heapless::Vec::new(), @@ -33,6 +34,9 @@ impl SequenceOf { } /// Add an element to this [`SequenceOf`]. + /// + /// # Errors + /// If this sequence is overlength. pub fn add(&mut self, element: T) -> Result<(), Error> { self.inner .push(element) diff --git a/der/src/asn1/set_of.rs b/der/src/asn1/set_of.rs index a91698efc..33b30f59c 100644 --- a/der/src/asn1/set_of.rs +++ b/der/src/asn1/set_of.rs @@ -41,6 +41,7 @@ where T: DerOrd, { /// Create a new [`SetOf`]. + #[must_use] pub fn new() -> Self { Self { inner: heapless::Vec::default(), @@ -50,12 +51,18 @@ where /// Add an item to this [`SetOf`]. /// /// Items MUST be added in lexicographical order according to the [`DerOrd`] impl on `T`. + /// + /// # Errors + /// If items are added out-of-order or there isn't sufficient space. #[deprecated(since = "0.7.6", note = "use `insert` or `insert_ordered` instead")] pub fn add(&mut self, new_elem: T) -> Result<(), Error> { self.insert_ordered(new_elem) } /// Insert an item into this [`SetOf`]. + /// + /// # Errors + /// If there's a duplicate or sorting error. pub fn insert(&mut self, item: T) -> Result<(), Error> { check_duplicate(&item, self.iter())?; self.try_push(item)?; @@ -65,6 +72,9 @@ where /// Insert an item into this [`SetOf`]. /// /// Items MUST be added in lexicographical order according to the [`DerOrd`] impl on `T`. + /// + /// # Errors + /// If items are added out-of-order or there isn't sufficient space. pub fn insert_ordered(&mut self, item: T) -> Result<(), Error> { // Ensure set elements are lexicographically ordered if let Some(last) = self.inner.last() { @@ -265,6 +275,7 @@ where T: DerOrd, { /// Create a new [`SetOfVec`]. + #[must_use] pub fn new() -> Self { Self { inner: Vec::default(), @@ -273,8 +284,11 @@ where /// Create a new [`SetOfVec`] from the given iterator. /// - /// Note: this is an inherent method instead of an impl of the - /// [`FromIterator`] trait in order to be fallible. + /// Note: this is an inherent method instead of an impl of the [`FromIterator`] trait in order + /// to be fallible. + /// + /// # Errors + /// If a sorting error occurred. #[allow(clippy::should_implement_trait)] pub fn from_iter(iter: I) -> Result where @@ -287,6 +301,9 @@ where /// /// Items MUST be added in lexicographical order according to the /// [`DerOrd`] impl on `T`. + /// + /// # Errors + /// If a sorting error occurred. #[deprecated(since = "0.7.6", note = "use `insert` or `insert_ordered` instead")] pub fn add(&mut self, item: T) -> Result<(), Error> { self.insert_ordered(item) @@ -294,8 +311,11 @@ where /// Extend a [`SetOfVec`] using an iterator. /// - /// Note: this is an inherent method instead of an impl of the - /// [`Extend`] trait in order to be fallible. + /// Note: this is an inherent method instead of an impl of the [`Extend`] trait in order to + /// be fallible. + /// + /// # Errors + /// If a sorting error occurred. pub fn extend(&mut self, iter: I) -> Result<(), Error> where I: IntoIterator, @@ -305,6 +325,9 @@ where } /// Insert an item into this [`SetOfVec`]. Must be unique. + /// + /// # Errors + /// If `item` is a duplicate or a sorting error occurred. pub fn insert(&mut self, item: T) -> Result<(), Error> { check_duplicate(&item, self.iter())?; self.inner.push(item); @@ -313,8 +336,10 @@ where /// Insert an item into this [`SetOfVec`]. Must be unique. /// - /// Items MUST be added in lexicographical order according to the - /// [`DerOrd`] impl on `T`. + /// Items MUST be added in lexicographical order according to the [`DerOrd`] impl on `T`. + /// + /// # Errors + /// If a sorting error occurred. pub fn insert_ordered(&mut self, item: T) -> Result<(), Error> { // Ensure set elements are lexicographically ordered if let Some(last) = self.inner.last() { @@ -326,21 +351,25 @@ where } /// Borrow the elements of this [`SetOfVec`] as a slice. + #[must_use] pub fn as_slice(&self) -> &[T] { self.inner.as_slice() } /// Get the nth element from this [`SetOfVec`]. + #[must_use] pub fn get(&self, index: usize) -> Option<&T> { self.inner.get(index) } /// Convert this [`SetOfVec`] into the inner [`Vec`]. + #[must_use] pub fn into_vec(self) -> Vec { self.inner } /// Iterate over the elements of this [`SetOfVec`]. + #[must_use] pub fn iter(&self) -> SetOfIter<'_, T> { SetOfIter { inner: self.inner.iter(), @@ -348,11 +377,13 @@ where } /// Is this [`SetOfVec`] empty? + #[must_use] pub fn is_empty(&self) -> bool { self.inner.is_empty() } /// Number of elements in this [`SetOfVec`]. + #[must_use] pub fn len(&self) -> usize { self.inner.len() } diff --git a/der/src/asn1/teletex_string.rs b/der/src/asn1/teletex_string.rs index 57d3d0c8a..8ac3b3b33 100644 --- a/der/src/asn1/teletex_string.rs +++ b/der/src/asn1/teletex_string.rs @@ -49,6 +49,9 @@ pub struct TeletexStringRef<'a> { impl<'a> TeletexStringRef<'a> { /// Create a new ASN.1 `TeletexString`. + /// + /// # Errors + /// If `input` contains characters outside the allowed range. pub fn new(input: &'a T) -> Result where T: AsRef<[u8]> + ?Sized, @@ -66,6 +69,7 @@ impl<'a> TeletexStringRef<'a> { } /// Borrow the inner `str`. + #[must_use] pub fn as_str(&self) -> &'a str { self.inner.as_str() } @@ -119,7 +123,7 @@ mod allocation { /// # Supported characters /// /// The standard defines a complex character set allowed in this type. However, quoting the ASN.1 - /// mailing list, "a sizable volume of software in the world treats TeletexString (T61String) as a + /// mailing list, "a sizable volume of software in the world treats `TeletexString` (`T61String`) as a /// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding". /// #[derive(Clone, Eq, PartialEq, PartialOrd, Ord)] @@ -130,6 +134,9 @@ mod allocation { impl TeletexString { /// Create a new ASN.1 `TeletexString`. + /// + /// # Errors + /// If characters are out-of-range. pub fn new(input: &T) -> Result where T: AsRef<[u8]> + ?Sized, diff --git a/der/src/asn1/utc_time.rs b/der/src/asn1/utc_time.rs index 32b0e3c16..a5477cf78 100644 --- a/der/src/asn1/utc_time.rs +++ b/der/src/asn1/utc_time.rs @@ -40,6 +40,9 @@ impl UtcTime { pub const MAX_YEAR: u16 = 2049; /// Create a [`UtcTime`] from a [`DateTime`]. + /// + /// # Errors + /// Returns [`Error`] in the event `datetime` has a year that exceeds [`UtcTime::MAX_YEAR`]. pub fn from_date_time(datetime: DateTime) -> Result { if datetime.year() <= UtcTime::MAX_YEAR { Ok(Self(datetime)) @@ -49,22 +52,30 @@ impl UtcTime { } /// Convert this [`UtcTime`] into a [`DateTime`]. + #[must_use] pub fn to_date_time(&self) -> DateTime { self.0 } /// Create a new [`UtcTime`] given a [`Duration`] since `UNIX_EPOCH` /// (a.k.a. "Unix time") + /// + /// # Errors + /// If [`DateTime`] couldn't be created from `unix_duration` successfully. pub fn from_unix_duration(unix_duration: Duration) -> Result { DateTime::from_unix_duration(unix_duration)?.try_into() } /// Get the duration of this timestamp since `UNIX_EPOCH`. + #[must_use] pub fn to_unix_duration(&self) -> Duration { self.0.unix_duration() } /// Instantiate from [`SystemTime`]. + /// + /// # Errors + /// If a time conversion error occurred. #[cfg(feature = "std")] pub fn from_system_time(time: SystemTime) -> Result { DateTime::try_from(time) @@ -74,6 +85,7 @@ impl UtcTime { /// Convert to [`SystemTime`]. #[cfg(feature = "std")] + #[must_use] pub fn to_system_time(&self) -> SystemTime { self.0.to_system_time() } @@ -209,6 +221,7 @@ impl From for SystemTime { // The DateTime type has a way bigger range of valid years than UtcTime, // so the DateTime year is mapped into a valid range to throw away less inputs. #[cfg(feature = "arbitrary")] +#[allow(clippy::unwrap_in_result)] impl<'a> arbitrary::Arbitrary<'a> for UtcTime { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { const MIN_YEAR: u16 = 1970; diff --git a/der/src/asn1/utf8_string.rs b/der/src/asn1/utf8_string.rs index 9105a4cc2..e8c926c6b 100644 --- a/der/src/asn1/utf8_string.rs +++ b/der/src/asn1/utf8_string.rs @@ -34,6 +34,9 @@ pub struct Utf8StringRef<'a> { impl<'a> Utf8StringRef<'a> { /// Create a new ASN.1 `UTF8String`. + /// + /// # Errors + /// If `input` contains invalid characters. pub fn new(input: &'a T) -> Result where T: AsRef<[u8]> + ?Sized, @@ -42,6 +45,7 @@ impl<'a> Utf8StringRef<'a> { } /// Borrow the inner `str`. + #[must_use] pub fn as_str(&self) -> &'a str { self.inner.as_str() } diff --git a/der/src/asn1/videotex_string.rs b/der/src/asn1/videotex_string.rs index 063db6a39..7e6f848ee 100644 --- a/der/src/asn1/videotex_string.rs +++ b/der/src/asn1/videotex_string.rs @@ -15,7 +15,7 @@ use core::{fmt, ops::Deref}; /// /// # Supported characters /// -/// For the practical purposes VideotexString is treated as IA5string, disallowing non-ASCII chars. +/// For the practical purposes `VideotexString` is treated as `IA5string`, disallowing non-ASCII chars. /// #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct VideotexStringRef<'a> { @@ -25,6 +25,9 @@ pub struct VideotexStringRef<'a> { impl<'a> VideotexStringRef<'a> { /// Create a new ASN.1 `VideotexString`. + /// + /// # Errors + /// If input contains out-of-range characters. pub fn new(input: &'a T) -> Result where T: AsRef<[u8]> + ?Sized, diff --git a/der/src/bytes.rs b/der/src/bytes.rs index 8d5516393..bd7184da1 100644 --- a/der/src/bytes.rs +++ b/der/src/bytes.rs @@ -27,13 +27,13 @@ impl BytesRef { // SAFETY: `Self` is a `repr(transparent)` newtype for `[u8]` #[allow(unsafe_code)] unsafe { - &*(slice as *const [u8] as *const Self) + &*(core::ptr::from_ref::<[u8]>(slice) as *const Self) } } /// Get a pointer to this [`BytesRef`]. pub(crate) const fn as_ptr(&self) -> *const BytesRef { - self as *const BytesRef + core::ptr::from_ref::(self) } /// Borrow the inner byte slice diff --git a/der/src/datetime.rs b/der/src/datetime.rs index 87d1ce027..2f188111f 100644 --- a/der/src/datetime.rs +++ b/der/src/datetime.rs @@ -70,6 +70,9 @@ impl DateTime { }; /// Create a new [`DateTime`] from the given UTC time components. + /// + /// # Errors + /// Returns [`Error`] with [`ErrorKind::DateTime`] in the event the date is invalid. pub const fn new( year: u16, month: u8, @@ -161,7 +164,8 @@ impl DateTime { /// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`. /// - /// Returns `Err` if the value is outside the supported date range. + /// # Errors + /// Returns error if the value is outside the supported date range. // TODO(tarcieri): checked arithmetic #[allow(clippy::arithmetic_side_effects)] pub fn from_unix_duration(unix_duration: Duration) -> Result { @@ -241,41 +245,51 @@ impl DateTime { } /// Get the year. + #[must_use] pub fn year(&self) -> u16 { self.year } /// Get the month. + #[must_use] pub fn month(&self) -> u8 { self.month } /// Get the day. + #[must_use] pub fn day(&self) -> u8 { self.day } /// Get the hour. + #[must_use] pub fn hour(&self) -> u8 { self.hour } /// Get the minutes. + #[must_use] pub fn minutes(&self) -> u8 { self.minutes } /// Get the seconds. + #[must_use] pub fn seconds(&self) -> u8 { self.seconds } /// Compute [`Duration`] since `UNIX_EPOCH` from the given calendar date. + #[must_use] pub fn unix_duration(&self) -> Duration { self.unix_duration } /// Instantiate from [`SystemTime`]. + /// + /// # Errors + /// If a time conversion error occurred. #[cfg(feature = "std")] pub fn from_system_time(time: SystemTime) -> Result { time.duration_since(UNIX_EPOCH) @@ -285,6 +299,7 @@ impl DateTime { /// Convert to [`SystemTime`]. #[cfg(feature = "std")] + #[must_use] pub fn to_system_time(&self) -> SystemTime { UNIX_EPOCH + self.unix_duration() } @@ -318,7 +333,7 @@ impl FromStr for DateTime { b'Z', ] => { let tag = Tag::GeneralizedTime; - let year = decode_year(&[year1, year2, year3, year4])?; + let year = decode_year([year1, year2, year3, year4])?; let month = decode_decimal(tag, month1, month2).map_err(|_| ErrorKind::DateTime)?; let day = decode_decimal(tag, day1, day2).map_err(|_| ErrorKind::DateTime)?; let hour = decode_decimal(tag, hour1, hour2).map_err(|_| ErrorKind::DateTime)?; @@ -448,7 +463,7 @@ where /// Decode 4-digit year. // TODO(tarcieri): checked arithmetic #[allow(clippy::arithmetic_side_effects)] -fn decode_year(year: &[u8; 4]) -> Result { +fn decode_year(year: [u8; 4]) -> Result { let tag = Tag::GeneralizedTime; let hi = decode_decimal(tag, year[0], year[1]).map_err(|_| ErrorKind::DateTime)?; let lo = decode_decimal(tag, year[2], year[3]).map_err(|_| ErrorKind::DateTime)?; diff --git a/der/src/decode.rs b/der/src/decode.rs index 12fc538d5..7b2c37c98 100644 --- a/der/src/decode.rs +++ b/der/src/decode.rs @@ -57,12 +57,18 @@ pub trait Decode<'a>: Sized + 'a { type Error: core::error::Error + From + 'static; /// Attempt to decode this TLV message using the provided decoder. + /// + /// # Errors + /// Returns [`Self::Error`] in the event a decoding error occurred. fn decode>(decoder: &mut R) -> Result; /// Parse `Self` from the provided BER-encoded byte slice. /// /// Note that most usages should probably use [`Decode::from_der`]. This method allows some /// BER productions which are not allowed under DER. + /// + /// # Errors + /// Returns [`Self::Error`] in the event a decoding error occurred. #[cfg(feature = "ber")] fn from_ber(bytes: &'a [u8]) -> Result { let mut reader = SliceReader::new_with_encoding_rules(bytes, EncodingRules::Ber)?; @@ -73,7 +79,8 @@ pub trait Decode<'a>: Sized + 'a { /// Parse `Self` from the provided DER-encoded byte slice. /// - /// Returns [`ErrorKind::TrailingData`] if message is incomplete. + /// # Errors + /// Returns [`Self::Error`] in the event a decoding error occurred. fn from_der(bytes: &'a [u8]) -> Result { let mut reader = SliceReader::new(bytes)?; let result = Self::decode(&mut reader)?; @@ -84,6 +91,9 @@ pub trait Decode<'a>: Sized + 'a { /// Parse `Self` from the provided DER-encoded byte slice. /// /// Returns remaining byte slice, without checking for incomplete message. + /// + /// # Errors + /// Returns [`Self::Error`] in the event a decoding error occurred. fn from_der_partial(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), Self::Error> { let mut reader = SliceReader::new(bytes)?; let result = Self::decode(&mut reader)?; @@ -144,6 +154,9 @@ impl DecodeOwned for T where T: for<'a> Decode<'a> {} )] pub trait DecodePem: DecodeOwned + PemLabel { /// Try to decode this type from PEM. + /// + /// # Errors + /// If a PEM or DER decoding error occurred. fn from_pem(pem: impl AsRef<[u8]>) -> Result>::Error>; } @@ -156,7 +169,7 @@ impl + PemLabel> DecodePem for T { } } -/// DecodeValue trait parses the value part of a Tag-Length-Value object, +/// `DecodeValue` trait parses the value part of a Tag-Length-Value object, /// sans the [`Tag`] and [`Length`]. /// /// As opposed to [`Decode`], implementer is expected to read the inner content only, @@ -196,6 +209,9 @@ pub trait DecodeValue<'a>: Sized { type Error: core::error::Error + From + 'static; /// Attempt to decode this value using the provided [`Reader`]. + /// + /// # Errors + /// Returns [`Self::Error`] in the event a decoding error occurred. fn decode_value>(reader: &mut R, header: Header) -> Result; } diff --git a/der/src/document.rs b/der/src/document.rs index 2e7a293ae..de70185f1 100644 --- a/der/src/document.rs +++ b/der/src/document.rs @@ -38,39 +38,50 @@ pub struct Document { impl Document { /// Get the ASN.1 DER-encoded bytes of this document. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.der_bytes.as_slice() } /// Convert to a [`SecretDocument`]. #[cfg(feature = "zeroize")] + #[must_use] pub fn into_secret(self) -> SecretDocument { SecretDocument(self) } /// Convert to an ASN.1 DER-encoded byte vector. + #[must_use] pub fn into_vec(self) -> Vec { self.der_bytes } /// Return an ASN.1 DER-encoded byte vector. + #[must_use] pub fn to_vec(&self) -> Vec { self.der_bytes.clone() } /// Get the length of the encoded ASN.1 DER in bytes. + #[must_use] pub fn len(&self) -> Length { self.length } /// Try to decode the inner ASN.1 DER message contained in this /// [`Document`] as the given type. + /// + /// # Errors + /// If a decoding error occurred. pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result { T::from_der(self.as_bytes()) } /// Encode the provided type as ASN.1 DER, storing the resulting encoded DER /// as a [`Document`]. + /// + /// # Errors + /// If an encoding error occurred. pub fn encode_msg(msg: &T) -> Result { msg.to_der()?.try_into() } @@ -78,6 +89,9 @@ impl Document { /// Decode ASN.1 DER document from PEM. /// /// Returns the PEM label and decoded [`Document`] on success. + /// + /// # Errors + /// If a decoding error occurred. #[cfg(feature = "pem")] pub fn from_pem(pem: &str) -> Result<(&str, Self), Error> { let (label, der_bytes) = pem::decode_vec(pem.as_bytes())?; @@ -86,6 +100,9 @@ impl Document { /// Encode ASN.1 DER document as a PEM string with encapsulation boundaries /// containing the provided PEM type `label` (e.g. `CERTIFICATE`). + /// + /// # Errors + /// If an encoding error occurred. #[cfg(feature = "pem")] pub fn to_pem( &self, @@ -96,24 +113,36 @@ impl Document { } /// Read ASN.1 DER document from a file. + /// + /// # Errors + /// If the file could not be read, or a decoding error occurred. #[cfg(feature = "std")] pub fn read_der_file(path: impl AsRef) -> Result { fs::read(path)?.try_into() } /// Write ASN.1 DER document to a file. + /// + /// # Errors + /// If the file could not be written to, or an encoding error occurred. #[cfg(feature = "std")] pub fn write_der_file(&self, path: impl AsRef) -> Result<(), Error> { Ok(fs::write(path, self.as_bytes())?) } /// Read PEM-encoded ASN.1 DER document from a file. + /// + /// # Errors + /// If the file could not be read, or a decoding error occurred. #[cfg(all(feature = "pem", feature = "std"))] pub fn read_pem_file(path: impl AsRef) -> Result<(String, Self), Error> { Self::from_pem(&fs::read_to_string(path)?).map(|(label, doc)| (label.to_owned(), doc)) } /// Write PEM-encoded ASN.1 DER document to a file. + /// + /// # Errors + /// If the file could not be written to, or an encoding error occurred. #[cfg(all(feature = "pem", feature = "std"))] pub fn write_pem_file( &self, @@ -209,37 +238,52 @@ pub struct SecretDocument(Document); #[cfg(feature = "zeroize")] impl SecretDocument { /// Borrow the inner serialized bytes of this document. + #[must_use] pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } /// Return an allocated ASN.1 DER serialization as a byte vector. + #[must_use] pub fn to_bytes(&self) -> Zeroizing> { Zeroizing::new(self.0.to_vec()) } /// Get the length of the encoded ASN.1 DER in bytes. + #[must_use] pub fn len(&self) -> Length { self.0.len() } /// Try to decode the inner ASN.1 DER message as the given type. + /// + /// # Errors + /// Returns `T::Error` if a decoding error occurred. pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result { self.0.decode_msg() } /// Encode the provided type as ASN.1 DER. + /// + /// # Errors + /// If an encoding error occurred. pub fn encode_msg(msg: &T) -> Result { Document::encode_msg(msg).map(Self) } /// Decode ASN.1 DER document from PEM. + /// + /// # Errors + /// If a decoding error occurred. #[cfg(feature = "pem")] pub fn from_pem(pem: &str) -> Result<(&str, Self), Error> { Document::from_pem(pem).map(|(label, doc)| (label, Self(doc))) } /// Encode ASN.1 DER document as a PEM string. + /// + /// # Errors + /// If an encoding error occurred. #[cfg(feature = "pem")] pub fn to_pem( &self, @@ -250,24 +294,36 @@ impl SecretDocument { } /// Read ASN.1 DER document from a file. + /// + /// # Errors + /// If file could not be read, or a decoding error occurred. #[cfg(feature = "std")] pub fn read_der_file(path: impl AsRef) -> Result { Document::read_der_file(path).map(Self) } /// Write ASN.1 DER document to a file. + /// + /// # Errors + /// If file could not be written, or an encoding error occurred. #[cfg(feature = "std")] pub fn write_der_file(&self, path: impl AsRef) -> Result<(), Error> { write_secret_file(path, self.as_bytes()) } /// Read PEM-encoded ASN.1 DER document from a file. + /// + /// # Errors + /// If file could not be read, or a decoding error occurred. #[cfg(all(feature = "pem", feature = "std"))] pub fn read_pem_file(path: impl AsRef) -> Result<(String, Self), Error> { Document::read_pem_file(path).map(|(label, doc)| (label, Self(doc))) } /// Write PEM-encoded ASN.1 DER document to a file. + /// + /// # Errors + /// If file could not be written, or an encoding error occurred. #[cfg(all(feature = "pem", feature = "std"))] pub fn write_pem_file( &self, diff --git a/der/src/encode.rs b/der/src/encode.rs index 8a1328bf3..2103c05f5 100644 --- a/der/src/encode.rs +++ b/der/src/encode.rs @@ -56,13 +56,22 @@ use crate::{FixedTag, Tag}; )] pub trait Encode { /// Compute the length of this TLV object in bytes when encoded as ASN.1 DER. + /// + /// # Errors + /// Returns an error if the length could not be computed (e.g. overflow). fn encoded_len(&self) -> Result; /// Encode this TLV object as ASN.1 DER using the provided [`Writer`]. + /// + /// # Errors + /// In the event an encoding error occurred. fn encode(&self, writer: &mut impl Writer) -> Result<()>; /// Encode this TLV object to the provided byte slice, returning a sub-slice /// containing the encoded message. + /// + /// # Errors + /// In the event an encoding error occurred. fn encode_to_slice<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> { let mut writer = SliceWriter::new(buf); self.encode(&mut writer)?; @@ -71,6 +80,9 @@ pub trait Encode { /// Encode this TLV object as ASN.1 DER, appending it to the provided /// byte vector. + /// + /// # Errors + /// In the event an encoding error occurred. #[cfg(feature = "alloc")] fn encode_to_vec(&self, buf: &mut Vec) -> Result { let expected_len = usize::try_from(self.encoded_len()?)?; @@ -92,6 +104,9 @@ pub trait Encode { } /// Encode this TLV object as ASN.1 DER, returning a byte vector. + /// + /// # Errors + /// In the event an encoding error occurred. #[cfg(feature = "alloc")] fn to_der(&self) -> Result> { let mut buf = Vec::new(); @@ -104,12 +119,10 @@ impl Encode for T where T: EncodeValue + Tagged + ?Sized, { - /// Compute the length of this TLV object in bytes when encoded as ASN.1 DER. fn encoded_len(&self) -> Result { self.value_len().and_then(|len| len.for_tlv(self.tag())) } - /// Encode this TLV object as ASN.1 DER using the provided [`Writer`]. fn encode(&self, writer: &mut impl Writer) -> Result<()> { self.header()?.encode(writer)?; self.encode_value(writer) @@ -141,6 +154,9 @@ where )] pub trait EncodePem: Encode + PemLabel { /// Try to encode this type as PEM. + /// + /// # Errors + /// If a PEM encoding error occurred. fn to_pem(&self, line_ending: LineEnding) -> Result; } @@ -203,6 +219,9 @@ where /// ``` pub trait EncodeValue { /// Get the [`Header`] used to encode this value. + /// + /// # Errors + /// Returns an error if the header could not be computed. fn header(&self) -> Result
where Self: Tagged, @@ -212,10 +231,16 @@ pub trait EncodeValue { /// Compute the length of this value (sans [`Tag`]+[`Length`] header) when /// encoded as ASN.1 DER. + /// + /// # Errors + /// Returns an error if the value length could not be computed. fn value_len(&self) -> Result; /// Encode value (sans [`Tag`]+[`Length`] header) as ASN.1 DER using the /// provided [`Writer`]. + /// + /// # Errors + /// In the event an encoding error occurred. fn encode_value(&self, writer: &mut impl Writer) -> Result<()>; } diff --git a/der/src/encode_ref.rs b/der/src/encode_ref.rs index d069eb2f0..85941f2ef 100644 --- a/der/src/encode_ref.rs +++ b/der/src/encode_ref.rs @@ -6,6 +6,7 @@ use core::cmp::Ordering; /// Reference encoder: wrapper type which impls `Encode` for any reference to a /// type which impls the same. +#[derive(Debug)] pub struct EncodeRef<'a, T>(pub &'a T); impl AsRef for EncodeRef<'_, T> { @@ -31,6 +32,7 @@ where /// for any reference type which impls the same. /// /// By virtue of the blanket impl, this type also impls `Encode`. +#[derive(Debug)] pub struct EncodeValueRef<'a, T>(pub &'a T); impl AsRef for EncodeValueRef<'_, T> { diff --git a/der/src/encoding_rules.rs b/der/src/encoding_rules.rs index 0070f90cc..4051e3ceb 100644 --- a/der/src/encoding_rules.rs +++ b/der/src/encoding_rules.rs @@ -24,11 +24,13 @@ pub enum EncodingRules { impl EncodingRules { /// Are we using Basic Encoding Rules? #[cfg(feature = "ber")] + #[must_use] pub const fn is_ber(self) -> bool { matches!(self, EncodingRules::Ber) } /// Are we using Distinguished Encoding Rules? + #[must_use] pub const fn is_der(self) -> bool { matches!(self, EncodingRules::Der) } diff --git a/der/src/error.rs b/der/src/error.rs index 6fbd12685..9f48c5014 100644 --- a/der/src/error.rs +++ b/der/src/error.rs @@ -44,6 +44,7 @@ pub struct Error { impl Error { /// Create a new [`Error`]. + #[must_use] pub const fn new(kind: ErrorKind, position: Length) -> Error { Error { kind, @@ -61,6 +62,7 @@ impl Error { /// Create a new [`ErrorKind::Incomplete`] for the given length. /// /// Computes the expected len as being one greater than `actual_len`. + #[must_use] pub fn incomplete(actual_len: Length) -> Self { match actual_len + Length::ONE { Ok(expected_len) => ErrorKind::Incomplete { @@ -73,11 +75,13 @@ impl Error { } /// Get the [`ErrorKind`] which occurred. + #[must_use] pub fn kind(self) -> ErrorKind { self.kind } /// Get the position inside of the message where the error occurred. + #[must_use] pub fn position(self) -> Option { self.position } @@ -333,11 +337,13 @@ pub enum ErrorKind { impl ErrorKind { /// Annotate an [`ErrorKind`] with context about where it occurred, /// returning an error. + #[must_use] pub fn at(self, position: Length) -> Error { Error::new(self, position) } /// Convert to an error, omitting position information. + #[must_use] pub fn to_error(self) -> Error { Error::from_kind(self) } diff --git a/der/src/header.rs b/der/src/header.rs index 00ad66906..7c3c12389 100644 --- a/der/src/header.rs +++ b/der/src/header.rs @@ -42,6 +42,7 @@ pub struct Header { impl Header { /// Create a new [`Header`] from a [`Tag`] and a [`Length`]. + #[must_use] pub fn new(tag: Tag, length: Length) -> Self { #[cfg(feature = "ber")] let constructed = tag.is_constructed() || length.is_indefinite(); @@ -55,21 +56,25 @@ impl Header { } /// [`Tag`] of this header. + #[must_use] pub fn tag(&self) -> Tag { self.tag } /// [`Length`] of this header. + #[must_use] pub fn length(&self) -> Length { self.length } /// True if the [`Tag`] of this header has its constructed bit set. + #[must_use] pub fn is_constructed(&self) -> bool { self.constructed } /// Copy of header with adjusted length. + #[must_use] pub fn with_length(&self, length: Length) -> Self { Self { tag: self.tag, @@ -81,6 +86,9 @@ impl Header { /// Peek forward in the reader, attempting to decode a [`Header`] at the current position. /// /// Does not modify the reader's state. + /// + /// # Errors + /// Returns [`Error`] in the event a header decoding error occurred. pub fn peek<'a>(reader: &impl Reader<'a>) -> Result { Header::decode(&mut reader.clone()) } diff --git a/der/src/length.rs b/der/src/length.rs index 3228903ed..40a9874fa 100644 --- a/der/src/length.rs +++ b/der/src/length.rs @@ -75,6 +75,7 @@ impl Length { /// Create a new [`Length`] for any value which fits inside of a [`u16`]. /// /// This function is const-safe and therefore useful for [`Length`] constants. + #[must_use] pub const fn new(value: u32) -> Self { Self { inner: value, @@ -97,6 +98,7 @@ impl Length { } /// Is this length equal to zero? + #[must_use] pub const fn is_zero(self) -> bool { self.inner == 0 } @@ -109,16 +111,21 @@ impl Length { /// Get the length of DER Tag-Length-Value (TLV) encoded data if `self` /// is the length of the inner "value" portion of the message. + /// + /// # Errors + /// Returns an error if an overflow occurred computing the length. pub fn for_tlv(self, tag: Tag) -> Result { tag.encoded_len()? + self.encoded_len()? + self } /// Perform saturating addition of two lengths. + #[must_use] pub fn saturating_add(self, rhs: Self) -> Self { Self::new(self.inner.saturating_add(rhs.inner)) } /// Perform saturating subtraction of two lengths. + #[must_use] pub fn saturating_sub(self, rhs: Self) -> Self { Self::new(self.inner.saturating_sub(rhs.inner)) } @@ -476,7 +483,7 @@ mod tests { fn add_overflows_when_max_length_exceeded() { let result = Length::MAX + Length::ONE; assert_eq!( - result.err().map(|err| err.kind()), + result.err().map(super::super::error::Error::kind), Some(ErrorKind::Overflow) ); } diff --git a/der/src/ord.rs b/der/src/ord.rs index beab2f28d..ca430d771 100644 --- a/der/src/ord.rs +++ b/der/src/ord.rs @@ -13,6 +13,9 @@ use core::{cmp::Ordering, marker::PhantomData}; pub trait DerOrd { /// Return an [`Ordering`] between `self` and `other` when serialized as /// ASN.1 DER. + /// + /// # Errors + /// If an encoding error occurred during the comparison. fn der_cmp(&self, other: &Self) -> Result; } @@ -22,6 +25,9 @@ pub trait DerOrd { pub trait ValueOrd { /// Return an [`Ordering`] between value portion of TLV-encoded `self` and /// `other` when serialized as ASN.1 DER. + /// + /// # Errors + /// If an encoding error occurred during the comparison. fn value_cmp(&self, other: &Self) -> Result; } @@ -79,14 +85,14 @@ where Ok(length_ord) } -/// Provide a no-op implementation for PhantomData +/// Provide a no-op implementation for `PhantomData` impl ValueOrd for PhantomData { fn value_cmp(&self, _other: &Self) -> Result { Ok(Ordering::Equal) } } -/// Provide a no-op implementation for PhantomData +/// Provide a no-op implementation for `PhantomData` impl DerOrd for PhantomData { fn der_cmp(&self, _other: &Self) -> Result { Ok(Ordering::Equal) diff --git a/der/src/reader.rs b/der/src/reader.rs index 20eb919a5..c34713d6b 100644 --- a/der/src/reader.rs +++ b/der/src/reader.rs @@ -33,6 +33,9 @@ pub trait Reader<'r>: Clone { fn position(&self) -> Length; /// Read nested data of the given length. + /// + /// # Errors + /// If `f` returns an error. fn read_nested(&mut self, len: Length, f: F) -> Result where E: From, @@ -41,14 +44,16 @@ pub trait Reader<'r>: Clone { /// Attempt to read data borrowed directly from the input as a slice, /// updating the internal cursor position. /// - /// # Returns - /// - `Ok(slice)` on success + /// # Errors /// - `Err(ErrorKind::Incomplete)` if there is not enough data /// - `Err(ErrorKind::Reader)` if the reader can't borrow from the input fn read_slice(&mut self, len: Length) -> Result<&'r [u8], Error>; /// Attempt to decode an ASN.1 `CONTEXT-SPECIFIC` field with the /// provided [`TagNumber`]. + /// + /// # Errors + /// If a decoding error occurred. fn context_specific( &mut self, tag_number: TagNumber, @@ -65,11 +70,17 @@ pub trait Reader<'r>: Clone { } /// Decode a value which impls the [`Decode`] trait. + /// + /// # Errors + /// Returns `T::Error` if a decoding error occurred. fn decode>(&mut self) -> Result { T::decode(self) } /// Drain the given amount of data from the reader, discarding it. + /// + /// # Errors + /// If an error occurred reading the given `amount` of data. fn drain(&mut self, mut amount: Length) -> Result<(), Error> { const BUFFER_SIZE: usize = 16; let mut buffer = [0u8; BUFFER_SIZE]; @@ -98,7 +109,10 @@ pub trait Reader<'r>: Clone { } /// Finish decoding, returning `Ok(())` if there is no - /// remaining data, or an error otherwise + /// remaining data, or an error otherwise. + /// + /// # Errors + /// If there is trailing data remaining in the reader. fn finish(self) -> Result<(), Error> { if !self.is_finished() { Err(ErrorKind::TrailingData { @@ -132,9 +146,10 @@ pub trait Reader<'r>: Clone { } /// Peek at the decoded data without updating the internal state, writing into the provided - /// output buffer. + /// output buffer. Attempts to fill the entire buffer. /// - /// Attempts to fill the entire buffer, returning an error if there is not enough data. + /// # Errors + /// If there is not enough data. fn peek_into(&self, buf: &mut [u8]) -> Result<(), Error> { let mut reader = self.clone(); reader.read_into(buf)?; @@ -145,18 +160,27 @@ pub trait Reader<'r>: Clone { /// the data at the current position in the decoder. /// /// Does not modify the decoder's state. - #[deprecated(since = "0.8.0-rc.1", note = "use `Header::peek` instead")] + /// + /// # Errors + /// If [`Header::peek`] returns an error. + #[deprecated(since = "0.8.0", note = "use `Header::peek` instead")] fn peek_header(&self) -> Result { Header::peek(self) } /// Peek at the next tag in the reader. - #[deprecated(since = "0.8.0-rc.1", note = "use `Tag::peek` instead")] + /// + /// # Errors + /// If [`Tag::peek`] returns an error. + #[deprecated(since = "0.8.0", note = "use `Tag::peek` instead")] fn peek_tag(&self) -> Result { Tag::peek(self) } /// Read a single byte. + /// + /// # Errors + /// If the byte could not be read. fn read_byte(&mut self) -> Result { let mut buf = [0]; self.read_into(&mut buf)?; @@ -166,9 +190,8 @@ pub trait Reader<'r>: Clone { /// Attempt to read input data, writing it into the provided buffer, and /// returning a slice on success. /// - /// # Returns - /// - `Ok(slice)` if there is sufficient data - /// - `Err(ErrorKind::Incomplete)` if there is not enough data + /// # Errors + /// - `ErrorKind::Incomplete` if there is not enough data fn read_into<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o [u8], Error> { let input = self.read_slice(buf.len().try_into()?)?; buf.copy_from_slice(input); @@ -176,6 +199,9 @@ pub trait Reader<'r>: Clone { } /// Read a byte vector of the given length. + /// + /// # Errors + /// If a read error occurred. #[cfg(feature = "alloc")] fn read_vec(&mut self, len: Length) -> Result, Error> { let mut bytes = vec![0u8; usize::try_from(len)?]; @@ -191,6 +217,9 @@ pub trait Reader<'r>: Clone { /// Read an ASN.1 `SEQUENCE`, creating a nested [`Reader`] for the body and /// calling the provided closure with it. + /// + /// # Errors + /// If `f` returns an error, or if a decoding error occurred. fn sequence(&mut self, f: F) -> Result where F: FnOnce(&mut Self) -> Result, @@ -202,6 +231,9 @@ pub trait Reader<'r>: Clone { } /// Obtain a slice of bytes containing a complete TLV production suitable for parsing later. + /// + /// # Errors + /// If a decoding error occurred, or a length calculation overflowed. fn tlv_bytes(&mut self) -> Result<&'r [u8], Error> { let header = Header::peek(self)?; let header_len = header.encoded_len()?; diff --git a/der/src/reader/pem.rs b/der/src/reader/pem.rs index 2d9a04119..27ce72910 100644 --- a/der/src/reader/pem.rs +++ b/der/src/reader/pem.rs @@ -2,6 +2,7 @@ use super::{Reader, position::Position}; use crate::{EncodingRules, Error, ErrorKind, Length, Result}; +use core::fmt; use pem_rfc7468::Decoder; /// `Reader` type which decodes PEM on-the-fly. @@ -21,6 +22,9 @@ impl<'i> PemReader<'i> { /// Create a new PEM reader which decodes data on-the-fly. /// /// Uses the default 64-character line wrapping. + /// + /// # Errors + /// If a decoding error occurred. pub fn new(pem: &'i [u8]) -> Result { let decoder = Decoder::new(pem)?; let input_len = Length::try_from(decoder.remaining_len())?; @@ -34,6 +38,7 @@ impl<'i> PemReader<'i> { /// Get the PEM label which will be used in the encapsulation boundaries /// for this document. + #[must_use] pub fn type_label(&self) -> &'i str { self.decoder.type_label() } @@ -76,3 +81,12 @@ impl<'i> Reader<'static> for PemReader<'i> { Ok(buf) } } + +impl fmt::Debug for PemReader<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PemReader") + .field("position", &self.position) + .field("encoding_rules", &self.encoding_rules) + .finish_non_exhaustive() + } +} diff --git a/der/src/reader/slice.rs b/der/src/reader/slice.rs index cf769ba85..a0ec9b6dd 100644 --- a/der/src/reader/slice.rs +++ b/der/src/reader/slice.rs @@ -20,11 +20,17 @@ pub struct SliceReader<'a> { impl<'a> SliceReader<'a> { /// Create a new slice reader for the given byte slice. + /// + /// # Errors + /// If `bytes` is too long. pub fn new(bytes: &'a [u8]) -> Result { Self::new_with_encoding_rules(bytes, EncodingRules::default()) } /// Create a new slice reader with the given encoding rules. + /// + /// # Errors + /// If `bytes` is too long. pub fn new_with_encoding_rules( bytes: &'a [u8], encoding_rules: EncodingRules, @@ -45,6 +51,7 @@ impl<'a> SliceReader<'a> { } /// Did the decoding operation fail due to an error? + #[must_use] pub fn is_failed(&self) -> bool { self.failed } diff --git a/der/src/referenced.rs b/der/src/referenced.rs index 22060b93b..8adfaa6e3 100644 --- a/der/src/referenced.rs +++ b/der/src/referenced.rs @@ -43,6 +43,7 @@ where where T: 'a; + #[allow(clippy::redundant_closure_for_method_calls, reason = "MSRV")] fn owned_to_ref(&self) -> Self::Borrowed<'_> { self.as_ref().map(|o| o.owned_to_ref()) } @@ -54,6 +55,8 @@ where T::Owned: OwnedToRef, { type Owned = Option; + + #[allow(clippy::redundant_closure_for_method_calls, reason = "MSRV")] fn ref_to_owned(&self) -> Self::Owned { self.as_ref().map(|o| o.ref_to_owned()) } diff --git a/der/src/string.rs b/der/src/string.rs index 0dd79c763..3f1d54f76 100644 --- a/der/src/string.rs +++ b/der/src/string.rs @@ -24,7 +24,7 @@ impl StringRef { // SAFETY: `Self` is a `repr(transparent)` newtype for `str` #[allow(unsafe_code)] unsafe { - &*(s as *const str as *const Self) + &*(core::ptr::from_ref::(s) as *const Self) } } diff --git a/der/src/tag.rs b/der/src/tag.rs index 74b18815e..cbe70630d 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -333,6 +333,9 @@ impl Tag { /// Peek at the next byte in the reader and attempt to decode it as a [`Tag`] value. /// /// Does not modify the reader's state. + /// + /// # Errors + /// If a decoding error occurred. pub fn peek<'a>(reader: &impl Reader<'a>) -> Result { Self::decode(&mut reader.clone()) } @@ -353,7 +356,8 @@ impl Tag { /// Assert that this [`Tag`] matches the provided expected tag. /// - /// On mismatch, returns an [`Error`] with [`ErrorKind::TagUnexpected`]. + /// # Errors + /// Returns an [`Error`] with [`ErrorKind::TagUnexpected`] on mismatch. pub fn assert_eq(self, expected: Tag) -> Result { if self == expected { Ok(self) @@ -363,6 +367,7 @@ impl Tag { } /// Get the [`Class`] that corresponds to this [`Tag`]. + #[must_use] pub const fn class(self) -> Class { match self { Tag::Application { .. } => Class::Application, @@ -373,6 +378,7 @@ impl Tag { } /// Get the [`TagNumber`] for this tag. + #[must_use] pub const fn number(self) -> TagNumber { match self { Tag::Boolean => TagNumber(1), @@ -404,6 +410,7 @@ impl Tag { } /// Does this tag represent a constructed (as opposed to primitive) field? + #[must_use] pub const fn is_constructed(self) -> bool { match self { Tag::Sequence | Tag::Set => true, @@ -415,38 +422,45 @@ impl Tag { } /// Is this an application tag? + #[must_use] pub const fn is_application(self) -> bool { matches!(self.class(), Class::Application) } /// Is this a context-specific tag? + #[must_use] pub const fn is_context_specific(self) -> bool { matches!(self.class(), Class::ContextSpecific) } /// Is this a private tag? + #[must_use] pub const fn is_private(self) -> bool { matches!(self.class(), Class::Private) } /// Is this a universal tag? + #[must_use] pub const fn is_universal(self) -> bool { matches!(self.class(), Class::Universal) } /// Create an [`Error`] for an invalid [`Length`]. + #[must_use] pub fn length_error(self) -> ErrorKind { ErrorKind::Length { tag: self } } /// Create an [`Error`] for an non-canonical value with the ASN.1 type /// identified by this tag. + #[must_use] pub fn non_canonical_error(self) -> ErrorKind { ErrorKind::Noncanonical { tag: self } } /// Create an [`Error`] because the current tag was unexpected, with an /// optional expected tag. + #[must_use] pub fn unexpected_error(self, expected: Option) -> ErrorKind { ErrorKind::TagUnexpected { expected, @@ -456,6 +470,7 @@ impl Tag { /// Create an [`Error`] for an invalid value with the ASN.1 type identified /// by this tag. + #[must_use] pub fn value_error(self) -> ErrorKind { ErrorKind::Value { tag: self } } diff --git a/der/src/tag/class.rs b/der/src/tag/class.rs index 144118118..f0e6b031c 100644 --- a/der/src/tag/class.rs +++ b/der/src/tag/class.rs @@ -53,11 +53,13 @@ impl fmt::Display for Class { impl Class { /// Returns class as 2 most-significant bits (mask 0b11000000) + #[must_use] pub const fn bits(&self) -> u8 { *self as u8 } /// Returns class extracted from 2 most-significant bits (mask 0b11000000) + #[must_use] pub const fn from_bits(bits: u8) -> Self { match (bits >> 6) & 0b11 { 0b00 => Class::Universal, diff --git a/der/src/tag/number.rs b/der/src/tag/number.rs index 74592010c..a5b702e92 100644 --- a/der/src/tag/number.rs +++ b/der/src/tag/number.rs @@ -28,11 +28,13 @@ impl TagNumber { since = "0.8.0", note = "use TagNumber(value) directly as inner field is now pub" )] + #[must_use] pub const fn new(value: u32) -> Self { Self(value) } /// Create an `APPLICATION` tag with this tag number. + #[must_use] pub fn application(self, constructed: bool) -> Tag { Tag::Application { constructed, @@ -41,6 +43,7 @@ impl TagNumber { } /// Create a `CONTEXT-SPECIFIC` tag with this tag number. + #[must_use] pub fn context_specific(self, constructed: bool) -> Tag { Tag::ContextSpecific { constructed, @@ -49,6 +52,7 @@ impl TagNumber { } /// Create a `PRIVATE` tag with this tag number. + #[must_use] pub fn private(self, constructed: bool) -> Tag { Tag::Private { constructed, @@ -57,6 +61,7 @@ impl TagNumber { } /// Get the inner value. + #[must_use] pub fn value(self) -> u32 { self.0 } diff --git a/der/src/writer.rs b/der/src/writer.rs index 164b215f7..486dd0065 100644 --- a/der/src/writer.rs +++ b/der/src/writer.rs @@ -12,9 +12,15 @@ use std::io; /// Writer trait which outputs encoded DER. pub trait Writer { /// Write the given DER-encoded bytes as output. + /// + /// # Errors + /// If the write operation failed. fn write(&mut self, slice: &[u8]) -> Result<()>; /// Write a single byte. + /// + /// # Errors + /// If the write operation failed. fn write_byte(&mut self, byte: u8) -> Result<()> { self.write(&[byte]) } diff --git a/der/src/writer/pem.rs b/der/src/writer/pem.rs index 87a6f8fd8..2c6f635b6 100644 --- a/der/src/writer/pem.rs +++ b/der/src/writer/pem.rs @@ -2,6 +2,7 @@ use super::Writer; use crate::Result; +use core::fmt; use pem_rfc7468::{Encoder, LineEnding}; /// `Writer` type which outputs PEM-encoded data. @@ -11,6 +12,9 @@ impl<'w> PemWriter<'w> { /// Create a new PEM writer which outputs into the provided buffer. /// /// Uses the default 64-character line wrapping. + /// + /// # Errors + /// If the type label is invalid. pub fn new( type_label: &'static str, line_ending: LineEnding, @@ -21,6 +25,7 @@ impl<'w> PemWriter<'w> { /// Get the PEM label which will be used in the encapsulation boundaries /// for this document. + #[must_use] pub fn type_label(&self) -> &'static str { self.0.type_label() } @@ -28,6 +33,9 @@ impl<'w> PemWriter<'w> { /// Finish encoding PEM, writing the post-encapsulation boundary. /// /// On success, returns the total number of bytes written to the output buffer. + /// + /// # Errors + /// If internal finalization failed. pub fn finish(self) -> Result { Ok(self.0.finish()?) } @@ -39,3 +47,9 @@ impl Writer for PemWriter<'_> { Ok(()) } } + +impl fmt::Debug for PemWriter<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PemWriter").finish_non_exhaustive() + } +} diff --git a/der/src/writer/slice.rs b/der/src/writer/slice.rs index 0d527b2f0..88a8f42f5 100644 --- a/der/src/writer/slice.rs +++ b/der/src/writer/slice.rs @@ -29,9 +29,12 @@ impl<'a> SliceWriter<'a> { } /// Encode a value which impls the [`Encode`] trait. + /// + /// # Errors + /// Returns an error if encoding failed. pub fn encode(&mut self, encodable: &T) -> Result<()> { if self.is_failed() { - self.error(ErrorKind::Failed)? + self.error(ErrorKind::Failed)?; } encodable.encode(self).map_err(|e| { @@ -42,18 +45,25 @@ impl<'a> SliceWriter<'a> { /// Return an error with the given [`ErrorKind`], annotating it with /// context about where the error occurred. + /// + /// # Errors + /// This function is designed to generate errors. pub fn error(&mut self, kind: ErrorKind) -> Result { self.failed = true; Err(kind.at(self.position)) } /// Did the decoding operation fail due to an error? + #[must_use] pub fn is_failed(&self) -> bool { self.failed } /// Finish encoding to the buffer, returning a slice containing the data /// written to the buffer. + /// + /// # Errors + /// If we're overlength, or writing already failed. pub fn finish(self) -> Result<&'a [u8]> { let position = self.position; @@ -67,6 +77,9 @@ impl<'a> SliceWriter<'a> { } /// Encode a `CONTEXT-SPECIFIC` field with the provided tag number and mode. + /// + /// # Errors + /// If an encoding error occurred. pub fn context_specific( &mut self, tag_number: TagNumber, @@ -88,6 +101,9 @@ impl<'a> SliceWriter<'a> { /// /// Spawns a nested slice writer which is expected to be exactly the /// specified length upon completion. + /// + /// # Errors + /// If an encoding error occurred. pub fn sequence(&mut self, length: Length, f: F) -> Result<()> where F: FnOnce(&mut SliceWriter<'_>) -> Result<()>, diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 24d9eafe6..0bee15899 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -8,10 +8,14 @@ //! $ cargo expand --test derive --all-features #![cfg(all(feature = "derive", feature = "alloc"))] -// TODO: fix needless_question_mark in the derive crate -#![allow(clippy::bool_assert_comparison, clippy::needless_question_mark)] - -#[derive(Debug)] +#![allow( + clippy::bool_assert_comparison, + clippy::needless_question_mark, // TODO: fix needless_question_mark in the derive crate + clippy::std_instead_of_core +)] + +/// Custom error type. +#[derive(Clone, Copy, Debug)] #[allow(dead_code)] pub struct CustomError(der::Error); @@ -352,7 +356,7 @@ mod sequence { )] pub only_contains_attribute_certs: bool, - /// Test handling of PhantomData. + /// Test handling of `PhantomData`. pub phantom: PhantomData<()>, } @@ -977,9 +981,7 @@ mod bitstring { assert_eq!(reencoded, BITSTRING_EXAMPLE); } - /// this BitString will allow only 3..=4 bits in Decode - /// - /// but will always Encode 4 bits + /// This `BitString` will allow only `3..=4` bits in `Decode`, but will always `Encode` 4-bits. #[derive(BitString)] pub struct MyBitString3or4 { pub bit_0: bool, @@ -1116,60 +1118,60 @@ mod bitstring { /// ``` #[derive(Clone, Debug, Eq, PartialEq, BitString)] pub struct PasswordFlags { - /// case-sensitive (0) + /// `case-sensitive` (0) pub case_sensitive: bool, - /// local (1) + /// `local` (1) pub local: bool, - /// change-disabled (2) + /// `change-disabled` (2) pub change_disabled: bool, - /// unblock-disabled (3) + /// `unblock-disabled` (3) pub unblock_disabled: bool, - /// initialized (4) + /// `initialized` (4) pub initialized: bool, - /// needs-padding (5) + /// `needs-padding` (5) pub needs_padding: bool, - /// unblockingPassword (6) + /// `unblockingPassword` (6) pub unblocking_password: bool, - /// soPassword (7) + /// `soPassword` (7) pub so_password: bool, - /// disable-allowed (8) + /// `disable-allowed` (8) pub disable_allowed: bool, - /// integrity-protected (9) + /// `integrity-protected` (9) pub integrity_protected: bool, - /// confidentiality-protected (10) + /// `confidentiality-protected` (10) pub confidentiality_protected: bool, - /// exchangeRefData (11) + /// `exchangeRefData` (11) pub exchange_ref_data: bool, /// Second edition 2016-05-15 - /// resetRetryCounter1 (12) + /// `resetRetryCounter1` (12) #[asn1(optional = "true")] pub reset_retry_counter1: bool, - /// resetRetryCounter2 (13) + /// `resetRetryCounter2` (13) #[asn1(optional = "true")] pub reset_retry_counter2: bool, - /// context-dependent (14) + /// `context-dependent` (14) #[asn1(optional = "true")] pub context_dependent: bool, - /// multiStepProtocol (15) + /// `multiStepProtocol` (15) #[asn1(optional = "true")] pub multi_step_protocol: bool, - /// fake_bit_for_testing (16) + /// `fake_bit_for_testing` (16) #[asn1(optional = "true")] pub fake_bit_for_testing: bool, } @@ -1196,7 +1198,7 @@ mod bitstring { } } mod infer_default { - //! When another crate might define a PartialEq for another type, the use of + //! When another crate might define a `PartialEq` for another type, the use of //! `default="Default::default"` in the der derivation will not provide enough //! information for `der_derive` crate to figure out. //! diff --git a/der/tests/derive_no_alloc.rs b/der/tests/derive_no_alloc.rs index 94e1874cb..0593e8e2b 100644 --- a/der/tests/derive_no_alloc.rs +++ b/der/tests/derive_no_alloc.rs @@ -5,7 +5,9 @@ //! To expand the Rust code generated by the proc macro when debugging //! issues related to these tests, run: //! +//! ```text //! $ cargo expand --test derive_heapless --all-features +//! ``` #![cfg(feature = "derive")] // TODO: fix needless_question_mark in the derive crate diff --git a/der/tests/pem.rs b/der/tests/pem.rs index bb4a4efe0..14c980a0e 100644 --- a/der/tests/pem.rs +++ b/der/tests/pem.rs @@ -1,6 +1,7 @@ //! PEM decoding and encoding tests. #![cfg(all(feature = "derive", feature = "oid", feature = "pem"))] +#![allow(missing_docs)] use der::{ Any, Decode, DecodePem, EncodePem, Sequence, diff --git a/der/tests/set_of.rs b/der/tests/set_of.rs index e6c2f5897..371fa9b57 100644 --- a/der/tests/set_of.rs +++ b/der/tests/set_of.rs @@ -1,6 +1,7 @@ //! `SetOf` tests. #![cfg(all(any(unix, windows), feature = "alloc", feature = "heapless"))] +#![allow(clippy::std_instead_of_alloc)] use der::{DerOrd, asn1::SetOfVec}; use proptest::{prelude::*, string::*};