diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f7da29..9a02846 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [1.70.0, stable, beta, nightly] + rust: [1.73.0, stable, beta, nightly] steps: - name: Checkout repository uses: actions/checkout@v1 diff --git a/Cargo.toml b/Cargo.toml index 93276ba..fd0376c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "rpki" version = "0.18.4" edition = "2021" -rust-version = "1.70" +rust-version = "1.73" authors = ["NLnet Labs "] description = "A library for validating and creating RPKI data." documentation = "https://docs.rs/rpki/" diff --git a/Changelog.md b/Changelog.md index 277cf7d..99dc33b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,9 +12,18 @@ New * Added `uri::{Rsync,Https}::path_into_dir` ([#302]) * Added `Ipv4Block` and `Ipv6Block` and `FromIterator` impls for `Ipv4Blocks` and `Ipv6Blocks`. ([#298]) +* Made `AddressRange` public and added methods to convert ranges into + a set of prefixes. ([#306]) +* Updated the ASPA RTR PDU to conform with version -14 of + draft-ietf-sidrops-8210bis. ([#309]) +* Exposed `ca::idcert::TbsIdCert::validity`. ([#310]); +* The ASPA `ProviderAsSet` now keeps track of its length and exposes it + via the new `len` method. ([#315]) Bug fixes +* Do not allow backslashes in idexchange handles. ([#304]) + Other changes * The minimum supported Rust version is now 1.70. ([#303]) @@ -25,6 +34,11 @@ Other changes [#300]: https://github.com/NLnetLabs/rpki-rs/pull/300 [#302]: https://github.com/NLnetLabs/rpki-rs/pull/302 [#303]: https://github.com/NLnetLabs/rpki-rs/pull/303 +[#304]: https://github.com/NLnetLabs/rpki-rs/pull/304 +[#306]: https://github.com/NLnetLabs/rpki-rs/pull/306 +[#309]: https://github.com/NLnetLabs/rpki-rs/pull/309 +[#310]: https://github.com/NLnetLabs/rpki-rs/pull/310 +[#315]: https://github.com/NLnetLabs/rpki-rs/pull/315 ## 0.18.4 diff --git a/src/ca/idcert.rs b/src/ca/idcert.rs index a8f27ec..63d4c2e 100644 --- a/src/ca/idcert.rs +++ b/src/ca/idcert.rs @@ -419,6 +419,10 @@ impl TbsIdCert { pub fn subject(&self) -> &Name { &self.subject } + + pub fn validity(&self) -> &Validity { + &self.validity + } } /// # Decoding and Encoding diff --git a/src/ca/idexchange.rs b/src/ca/idexchange.rs index 3a7927a..c5d5141 100644 --- a/src/ca/idexchange.rs +++ b/src/ca/idexchange.rs @@ -482,7 +482,7 @@ impl ChildRequest { writer.done() } - /// Writes the ChildRequest's XML representation to a new Vec. + /// Writes the ChildRequest's XML representation to a new `Vec`. pub fn to_xml_vec(&self) -> Vec { let mut vec = vec![]; self.write_xml(&mut vec).unwrap(); // safe @@ -730,7 +730,7 @@ impl ParentResponse { validate_idcert_at(&self.id_cert, when) } - /// Writes the ParentResponse's XML representation to a new Vec. + /// Writes the ParentResponse's XML representation to a new `Vec`. pub fn to_xml_vec(&self) -> Vec { let mut vec = vec![]; self.write_xml(&mut vec).unwrap(); // safe @@ -756,12 +756,12 @@ impl fmt::Display for ParentResponse { //------------ PublisherRequest ---------------------------------------------- -/// Type representing a +/// Type representing a `` /// /// This is the XML message with identity information that a CA sends to a /// Publication Server. /// -/// For more info, see: https://tools.ietf.org/html/rfc8183#section-5.2.3 +/// For more info, see: #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct PublisherRequest { /// The self-signed IdCert containing the publisher's public key. @@ -912,7 +912,7 @@ impl PublisherRequest { writer.done() } - /// Writes the PublisherRequest's XML representation to a new Vec. + /// Writes the PublisherRequest's XML representation to a new `Vec`. pub fn to_xml_vec(&self) -> Vec { let mut vec = vec![]; self.write_xml(&mut vec).unwrap(); // safe @@ -945,7 +945,7 @@ impl fmt::Display for PublisherRequest { /// This is the response sent to a CA by the publication server. It contains /// the details needed by the CA to send publication messages to the server. /// -/// See https://tools.ietf.org/html/rfc8183#section-5.2.4 +/// See #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] pub struct RepositoryResponse { /// The Publication Server Identity Certificate @@ -1155,7 +1155,7 @@ impl RepositoryResponse { validate_idcert_at(&self.id_cert, when) } - /// Writes the RepositoryResponse's XML representation to a new Vec. + /// Writes the RepositoryResponse's XML representation to a new `Vec`. pub fn to_xml_vec(&self) -> Vec { let mut vec = vec![]; self.write_xml(&mut vec).unwrap(); // safe diff --git a/src/ca/provisioning.rs b/src/ca/provisioning.rs index 85c40a8..2863169 100644 --- a/src/ca/provisioning.rs +++ b/src/ca/provisioning.rs @@ -270,7 +270,7 @@ impl Message { /// # Decoding from XML /// impl Message { - /// Parses an RFC 6492 + /// Parses an RFC 6492 `` pub fn decode(reader: R) -> Result { let mut reader = xml::decode::Reader::new(reader); @@ -375,7 +375,7 @@ impl Payload { /// # Encoding to XML /// impl Payload { - /// Value for the type attribute in the element. + /// Value for the type attribute in the `` element. pub fn payload_type(&self) -> PayloadType { match self { Payload::List => PayloadType::List, @@ -723,7 +723,7 @@ impl IssuanceResponse { /// Note that the IssuanceRequest will be rejected by the parent, if the limit /// exceeds the child's entitlements. /// -/// See: https://tools.ietf.org/html/rfc6492#section-3.4.1 +/// See: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct RequestResourceLimit { #[serde( @@ -998,7 +998,7 @@ impl From<&RevocationRequest> for RevocationResponse { //------------ KeyElement ---------------------------------------------------- -/// This type represents a element as used in both the Certificate +/// This type represents a `` element as used in both the Certificate /// Revocation Request and Response, sections 3.5.1 and 3.5.2 respectively, /// of RFC6492. #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] @@ -1096,7 +1096,7 @@ impl fmt::Display for KeyElement { /// This structure is what is called the "Resource Class List Response" /// in section 3.3.2 of RFC6492. /// -/// This response can have 0 or more elements containing the +/// This response can have 0 or more `` elements containing the /// entitlements for 0 or more corresponding resource classes. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct ResourceClassListResponse { diff --git a/src/ca/publication.rs b/src/ca/publication.rs index 7f2461d..2ca46dc 100644 --- a/src/ca/publication.rs +++ b/src/ca/publication.rs @@ -222,7 +222,7 @@ impl Message { /// # Decoding from XML /// impl Message { - /// Parses an RFC 8181 + /// Parses an RFC 8181 `` pub fn decode(reader: R) -> Result { let mut reader = xml::decode::Reader::new(reader); @@ -539,7 +539,7 @@ impl QueryPdu { //------------ PublishDelta ------------------------------------------------ /// This type represents a multi element query as described in -/// https://tools.ietf.org/html/rfc8181#section-3.7 +/// #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct PublishDelta(Vec); @@ -613,7 +613,7 @@ impl PublishDeltaElement { //------------ Publish ------------------------------------------------------- /// Represents a publish element, that does not update any existing object. -/// See: https://tools.ietf.org/html/rfc8181#section-3.1 +/// See: #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Publish { tag: Option, @@ -679,7 +679,7 @@ impl Publish { //------------ Update -------------------------------------------------------- /// Represents a publish element, that replaces an existing object. -/// See: https://tools.ietf.org/html/rfc8181#section-3.2 +/// See: #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Update { tag: Option, @@ -766,7 +766,7 @@ impl Update { //------------ Withdraw ------------------------------------------------------ /// Represents a withdraw element that removes an object. -/// See: https://tools.ietf.org/html/rfc8181#section-3.3 +/// See: #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Withdraw { tag: Option, @@ -1062,7 +1062,7 @@ impl ReplyPdu { //------------ ListReply ----------------------------------------------------- /// This type represents the list reply as described in -/// https://tools.ietf.org/html/rfc8181#section-2.3 +/// #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct ListReply { elements: Vec, @@ -1156,7 +1156,7 @@ impl ListElement { //------------ ErrorReply ---------------------------------------------------- /// This type represents the error report as described in -/// https://tools.ietf.org/html/rfc8181#section-3.5 and 3.6 +/// and 3.6 #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ErrorReply { errors: Vec, diff --git a/src/ca/sigmsg.rs b/src/ca/sigmsg.rs index 523c86e..95fff5e 100644 --- a/src/ca/sigmsg.rs +++ b/src/ca/sigmsg.rs @@ -28,8 +28,11 @@ use super::idcert::IdCert; /// but similar structure. /// /// Most important differences to watch out for: -/// = This uses [`IdCert`] instead of [`Cert`] as the EE (no INRs needed) -/// = This MUST include a CRL +/// - This uses [`IdCert`] instead of [`Cert`] as the EE (no INRs needed) +/// - This MUST include a CRL +/// +/// [`Cert`]: crate::repository::cert::Cert +/// [`SignedObject`]: crate::repository::sigobj::SignedObject #[derive(Clone, Debug)] pub struct SignedMessage { //--- From SignedData diff --git a/src/crypto/digest.rs b/src/crypto/digest.rs index 430f38a..22d7aa2 100644 --- a/src/crypto/digest.rs +++ b/src/crypto/digest.rs @@ -103,7 +103,7 @@ impl DigestAlgorithm { /// The functions and methods in this section allow decoding and encoding /// such values. /// -/// [`SignatureAlgorithm`]: ../signature/struct.SignatureAlgorithm.html +/// [`SignatureAlgorithm`]: super::SignatureAlgorithm /// [RFC 5754]: https://tools.ietf.org/html/rfc5754 /// [RFC 7935]: https://tools.ietf.org/html/rfc7935 impl DigestAlgorithm { @@ -180,7 +180,7 @@ impl DigestAlgorithm { encode::sequence(oid::SHA256.encode()) } - /// Provides an encoder for a indentifer as the sole value of a set. + /// Provides an encoder for a identifier as the sole value of a set. pub fn encode_set(self) -> impl encode::Values { encode::set( self.encode() diff --git a/src/crypto/keys.rs b/src/crypto/keys.rs index caa4e89..0224173 100644 --- a/src/crypto/keys.rs +++ b/src/crypto/keys.rs @@ -78,7 +78,7 @@ impl PublicKeyFormat { /// defined by [RFC 4055] and the parameters must be present and NULL. /// When parsing, we generously also allow it to be absent altogether. /// -/// For ECDSA keys, the object identifer needs to be `ecPublicKey` defined +/// For ECDSA keys, the object identifier needs to be `ecPublicKey` defined /// in [RFC 5480] with the parameter being the object identifier `secp256r1` /// defined in the same RFC. /// @@ -113,7 +113,7 @@ impl PublicKeyFormat{ } } - /// Provides an encoder for the algorihm identifier. + /// Provides an encoder for the algorithm identifier. pub fn encode(self) -> impl encode::Values { match self { PublicKeyFormat::Rsa => { @@ -177,7 +177,7 @@ impl PublicKey { /// Creates an RSA Public Key based on the supplied exponent and modulus. /// /// See: - /// [RFC 4055]: https://tools.ietf.org/html/rfc4055 + /// [RFC 4055]: /// /// An RSA Public Key uses the following DER encoded structure inside its /// BitString component: @@ -421,7 +421,7 @@ impl PrimitiveContent for PublicKeyCn { pub struct KeyIdentifier([u8; 20]); impl KeyIdentifier { - /// Returns an octet slice of the key identifer’s value. + /// Returns an octet slice of the key identifier’s value. pub fn as_slice(&self) -> &[u8] { self.0.as_ref() } @@ -456,7 +456,7 @@ impl KeyIdentifier { cons.take_opt_value_if(bcder::Tag::OCTET_STRING, Self::from_content) } - /// Parses an encoded key identifer from encoded content. + /// Parses an encoded key identifier from encoded content. pub fn from_content( content: &mut decode::Content ) -> Result> { @@ -481,7 +481,7 @@ impl KeyIdentifier { } } - /// Skips over an encoded key indentifier. + /// Skips over an encoded key identifier. pub fn skip_opt_in( cons: &mut decode::Constructed ) -> Result, DecodeError> { @@ -609,7 +609,7 @@ impl<'de> serde::Deserialize<'de> for KeyIdentifier { ) -> Result { struct KeyIdentifierVisitor; - impl<'de> serde::de::Visitor<'de> for KeyIdentifierVisitor { + impl serde::de::Visitor<'_> for KeyIdentifierVisitor { type Value = KeyIdentifier; fn expecting( diff --git a/src/repository/aspa.rs b/src/repository/aspa.rs index d676311..59eb327 100644 --- a/src/repository/aspa.rs +++ b/src/repository/aspa.rs @@ -181,7 +181,7 @@ impl AsProviderAttestation { encode::sequence(( encode::sequence_as(Tag::CTX_0, 1u8.encode()), self.customer_as.encode(), - &self.provider_as_set.0, + &self.provider_as_set.captured, )) } @@ -203,9 +203,17 @@ impl AsProviderAttestation { /// the AS in this set are ordered, free of duplicates and there is at least /// one AS. #[derive(Clone, Debug)] -pub struct ProviderAsSet(Captured); +pub struct ProviderAsSet { + captured: Captured, + len: usize, +} impl ProviderAsSet { + #[allow(clippy::len_without_is_empty)] // never empty + pub fn len(&self) -> usize { + self.len + } + pub fn to_set(&self) -> SmallAsnSet { unsafe { SmallAsnSet::from_vec_unchecked( @@ -215,14 +223,15 @@ impl ProviderAsSet { } pub fn iter(&self) -> ProviderAsIter { - ProviderAsIter(self.0.as_slice().into_source()) + ProviderAsIter(self.captured.as_slice().into_source()) } fn take_from( cons: &mut decode::Constructed, customer_as: Asn, ) -> Result> { - cons.take_sequence(|cons| { + let mut len = 0; + let captured = cons.take_sequence(|cons| { cons.capture(|cons| { let mut last: Option = None; while let Some(asn) = Asn::take_opt_from( @@ -249,6 +258,7 @@ impl ProviderAsSet { } } last = Some(asn); + len += 1; } if last.is_none() { return Err(cons.content_err( @@ -257,7 +267,8 @@ impl ProviderAsSet { } Ok(()) }) - }).map(ProviderAsSet) + })?; + Ok(Self { captured, len }) } } @@ -267,7 +278,7 @@ impl ProviderAsSet { #[derive(Clone, Debug)] pub struct ProviderAsIter<'a>(SliceSource<'a>); -impl<'a> Iterator for ProviderAsIter<'a> { +impl Iterator for ProviderAsIter<'_> { type Item = Asn; fn next(&mut self) -> Option { @@ -336,7 +347,10 @@ impl AspaBuilder { ) ); - let provider_as_set = ProviderAsSet(provider_as_set_captured); + let provider_as_set = ProviderAsSet { + captured: provider_as_set_captured, + len: self.providers.len() + }; AsProviderAttestation { customer_as: self.customer_as, diff --git a/src/repository/cert.rs b/src/repository/cert.rs index f13f264..583abe0 100644 --- a/src/repository/cert.rs +++ b/src/repository/cert.rs @@ -12,8 +12,6 @@ //! //! In addition, there are several types for the components of a certificate. //! -//! [`Cert`]: struct.Cert.html -//! [`ResourceCert`]: struct.ResourceCert.html //! [RFC 5280]: https://tools.ietf.org/html/rfc5280 //! [RFC 6487]: https://tools.ietf.org/html/rfc5487 @@ -73,13 +71,12 @@ use super::x509::{ /// further processing. In addition, various methods exist to access /// information contained in the certificate. /// -/// [`ResourceCert`]: struct.ResourceCert.html -/// [`decode`]: #method.decode -/// [`take_from`]: #method.take_from -/// [`from_constructed`]: #method.from_constructed -/// [`validate_ca`]: #method.validate_ca -/// [`validate_ee`]: #method.validate_ee -/// [`validate_ta`]: #method.validate_ta +/// [`decode`]: Cert::decode +/// [`take_from`]: Cert::take_from +/// [`from_constructed`]: Cert::from_constructed +/// [`validate_ca`]: Cert::validate_ca +/// [`validate_ee`]: Cert::validate_ee +/// [`validate_ta`]: Cert::validate_ta #[derive(Clone, Debug)] pub struct Cert { /// The outer structure of the certificate. @@ -256,7 +253,7 @@ impl Cert { self.verify_ee_at(issuer, strict, now).map_err(Into::into) } - /// Validates the certificate as a detached EE certficate. + /// Validates the certificate as a detached EE certificate. /// /// Such a certificate is used by signed objects that are not published /// through RPKI repositories. @@ -384,7 +381,7 @@ impl Cert { // 4.8.1. Basic Constraints: Must not be present. if self.basic_ca.is_some(){ return Err(InspectionError::new( - "Basic Contraints extension \ + "Basic Constraints extension \ not allowed in end entity certificate" )) } @@ -422,7 +419,7 @@ impl Cert { Ok(()) } - /// Inspects the certificate as a detached EE certficate. + /// Inspects the certificate as a detached EE certificate. /// /// Checks that the certificate fulfills all formal requirements of such /// a certificate. @@ -435,7 +432,7 @@ impl Cert { // 4.8.1. Basic Constraints: Must not be present. if self.basic_ca.is_some(){ return Err(InspectionError::new( - "Basic Contraints extension \ + "Basic Constraints extension \ not allowed in end entity certificate" )) } @@ -506,7 +503,7 @@ impl Cert { // 4.8.1. Basic Constraints. Must not be present. if self.basic_ca.is_some(){ return Err(InspectionError::new( - "Basic Contraints extension \ + "Basic Constraints extension \ not allowed in end entity certificate" )) } @@ -517,7 +514,7 @@ impl Cert { self.subject_public_key_info().key_identifier() { return Err(InspectionError::new( - "Subject Key Identifer extension doesn't match \ + "Subject Key Identifier extension doesn't match \ the public key" )) } @@ -588,7 +585,7 @@ impl Cert { } if self.as_resources().is_inherited() { return Err(InspectionError::new( - "inherited AS Resources in router certifiate" + "inherited AS Resources in router certificate" )) } diff --git a/src/repository/crl.rs b/src/repository/crl.rs index 43bd070..9337c52 100644 --- a/src/repository/crl.rs +++ b/src/repository/crl.rs @@ -10,10 +10,6 @@ //! //! The RPKI CRL profile is defined in RFC 6487 based on the Internet RPIX //! profile defined in RFC 5280. -//! -//! [`Crl`]: struct.Crl.html -//! [`CrlStore`]: struct.CrlStore.html - use std::{fmt, ops}; use std::collections::HashSet; use std::str::FromStr; @@ -341,7 +337,7 @@ impl TbsCertList { &self.authority_key_id } - /// Sets the authority key identifer. + /// Sets the authority key identifier. pub fn set_authority_key_identifier(&mut self, id: KeyIdentifier) { self.authority_key_id = id } @@ -627,7 +623,7 @@ impl CrlEntry { cons.take_sequence(Self::from_constructed) } - /// Takes an optional CRL entry from the beginning of a contructed value. + /// Takes an optional CRL entry from the beginning of a constructed value. pub fn take_opt_from( cons: &mut decode::Constructed ) -> Result, DecodeError> { diff --git a/src/repository/manifest.rs b/src/repository/manifest.rs index 9790c42..b8b733f 100644 --- a/src/repository/manifest.rs +++ b/src/repository/manifest.rs @@ -6,9 +6,6 @@ //! This module defines the type [`Manifest`] that represents a decoded //! manifest and the type [`ManifestContent`] for the content of a validated //! manifest, as well as some helper types for accessing the content. -//! -//! [`Manifest`]: struct.Manifest.html -//! [`ManifestContent`]: struct.ManifestContent.html use std::{borrow, fmt, ops}; use bcder::{decode, encode}; diff --git a/src/repository/resources/ipres.rs b/src/repository/resources/ipres.rs index 1874342..2b5a701 100644 --- a/src/repository/resources/ipres.rs +++ b/src/repository/resources/ipres.rs @@ -12,6 +12,7 @@ use std::fmt::Display; use std::net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr}; use std::num::ParseIntError; use std::str::FromStr; +use std::cmp; use bcder::{decode, encode}; use bcder::{BitString, Mode, OctetString, Tag}; use bcder::decode::{ContentError, DecodeError}; @@ -268,7 +269,7 @@ impl<'a> IpBlocksForFamily<'a> { } } -impl<'a> fmt::Display for IpBlocksForFamily<'a> { +impl fmt::Display for IpBlocksForFamily<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut blocks_iter = self.blocks.iter(); @@ -295,7 +296,7 @@ impl<'a> fmt::Display for IpBlocksForFamily<'a> { /// A sequence of address ranges for one address family. /// -/// Values of this type are guaranteed to contain a sequence of `IpBlocks` +/// Values of this type are guaranteed to contain a sequence of [`IpBlock`]s /// that fulfills the requirements of RFC 3779. Specifically, the blocks will /// not overlap, will not be consecutive (i.e., there’s at least one address /// between neighbouring blocks), will be in order, and anything that can be @@ -739,7 +740,7 @@ impl IpBlock { DisplayV4Block(self) } - /// Returns an object that displays the block as an IPv4 block. + /// Returns an object that displays the block as an IPv6 block. pub fn display_v6(self) -> DisplayV6Block { DisplayV6Block(self) } @@ -1037,6 +1038,84 @@ impl AddressRange { } } + /// Convert a range into a set of V6 prefixes + pub fn to_v6_prefixes(self) -> impl Iterator { + let mut start = self.min.to_bits(); + let end = self.max.to_bits(); + + std::iter::from_fn(move || { + // The idea is to take the largest prefix possible from the start + // then move the start to the address after the last address in + // that prefix, and do it again until there are no addresses left. + + // Based loosely on + if start > end { + return None; + } + + // Determine how many of the last bits of the address are prefixable + // e.g. for 2001:DB8:: that would be 99 + let addr_host_bits = start.trailing_zeros(); + + // Determine how many of the first bits are shared between the + // start and the end, to determine an upper bound for the prefix + // e.g. 2001:DB8:: and 2001:DB8::8000 share 112 bits, so the max + // is 16 + let mut max_allowed = 128 - (start ^ end).leading_zeros(); + if end.trailing_ones() < max_allowed { + // Prevent overshooting the prefix + // e.g. for 2001:DB8::8000 the trailing_ones = 0, so the max + // is now 15 to prevent covering space after 2001:DB8::8000 + max_allowed -= 1; + } + + // Obtain the bits at the end that are the same, which is the + // shortest of either the amount of 0 bits at the current address + // or the amount of bits not shared at the start + let same_bits = cmp::min(addr_host_bits, max_allowed); + let prefix_len = 128 - same_bits; + + debug_assert!(prefix_len <= 128); + let prefix = Prefix::new(Addr::from_bits(start), prefix_len as u8); + + start += 2_u128.pow(same_bits); + + Some(prefix) + }) + } + + /// Convert a range into a set of V4 prefixes + pub fn to_v4_prefixes(self) -> impl Iterator { + let mut start = (self.min.to_bits() >> 96) as u32; + let end = (self.max.to_bits() >> 96) as u32; + + std::iter::from_fn(move || { + // This works the same as `to_v6_prefixes` above + if start > end { + return None; + } + + let addr_host_bits = start.trailing_zeros(); + let mut max_allowed = 32 - (start ^ end).leading_zeros(); + if end.trailing_ones() < max_allowed { + max_allowed -= 1; + } + + let same_bits = cmp::min(addr_host_bits, max_allowed); + let prefix_len = 32 - same_bits; + + debug_assert!(prefix_len <= 32); + let prefix = Prefix::new( + Addr::from(Ipv4Addr::from(start)), + prefix_len as u8 + ); + + start += 2u32.pow(same_bits); + + Some(prefix) + }) + } + /// Formats the range as an IPv4 range. pub fn fmt_v4(self, f: &mut fmt::Formatter) -> fmt::Result { let min = self.min.to_v4(); @@ -2207,7 +2286,7 @@ impl From for VerificationError { //============ Tests ========================================================= #[cfg(test)] -mod test { +mod tests { use bcder::encode::Values; use super::*; @@ -2658,6 +2737,30 @@ mod test { 0x123f_ffff_ffff_ffff_ffff_ffff_ffff_ffff ); } + + #[test] + fn to_prefixes() { + { + let range = AddressRange::new( + Addr::from(Ipv6Addr::from_str("::1").unwrap()), + Addr::from(Ipv6Addr::from_str("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe").unwrap()) + ); + + let prefixes = range.to_v6_prefixes(); + + assert_eq!(254, prefixes.count()); + } + { + let range = AddressRange::new( + Addr::from(Ipv4Addr::from_str("192.168.0.0").unwrap()), + Addr::from(Ipv4Addr::from_str("192.168.2.255").unwrap()) + ); + + let prefixes = range.to_v4_prefixes(); + + assert_eq!(2, prefixes.count()); + } + } } #[cfg(all(test, feature="compat"))] diff --git a/src/repository/resources/mod.rs b/src/repository/resources/mod.rs index 9c057cb..5d5f909 100644 --- a/src/repository/resources/mod.rs +++ b/src/repository/resources/mod.rs @@ -9,8 +9,6 @@ //! Delegation Extension and [`AsResources`] implements the Autonomous System //! Identifier Delegation Extension. //! -//! [`AsResources`]: struct.AsResources.html -//! [`IpResources`]: struct.IpResources.html //! [RFC 3779]: https://tools.ietf.org/html/rfc3779 //! [RFC 6487]: https://tools.ietf.org/html/rfc6487 @@ -20,9 +18,9 @@ pub use self::asres::{ }; pub use self::choice::ResourcesChoice; pub use self::ipres::{ - Addr, AddressFamily, InheritedIpResources, IpBlock, IpBlocks, Ipv4Block, - Ipv4Blocks, Ipv6Block, Ipv6Blocks, IpBlocksBuilder, IpBlocksForFamily, - IpResources, IpResourcesBuilder, OverclaimedIpResources, + Addr, AddressFamily, AddressRange, InheritedIpResources, IpBlock, IpBlocks, + Ipv4Block, Ipv4Blocks, Ipv6Block, Ipv6Blocks, IpBlocksBuilder, + IpBlocksForFamily, IpResources, IpResourcesBuilder, OverclaimedIpResources, OverclaimedIpv4Resources, OverclaimedIpv6Resources, Prefix }; pub use self::set::{ diff --git a/src/repository/roa.rs b/src/repository/roa.rs index 0fcfc8f..a012185 100644 --- a/src/repository/roa.rs +++ b/src/repository/roa.rs @@ -312,7 +312,7 @@ impl RoaIpAddresses { #[derive(Clone, Debug)] pub struct RoaIpAddressIter<'a>(SliceSource<'a>); -impl<'a> Iterator for RoaIpAddressIter<'a> { +impl Iterator for RoaIpAddressIter<'_> { type Item = RoaIpAddress; fn next(&mut self) -> Option { diff --git a/src/repository/sigobj.rs b/src/repository/sigobj.rs index 0ab4447..cbdc2fc 100644 --- a/src/repository/sigobj.rs +++ b/src/repository/sigobj.rs @@ -1270,9 +1270,9 @@ mod signer_test { /// [RFC 5652]: https://tools.ietf.org/html/rfc5652 /// [RFC 6488]: https://tools.ietf.org/html/rfc6488 /// [RFC 7935]: https://tools.ietf.org/html/rfc7935 -/// [`Cert`]: ../../cert/struct.Cert.html -/// [`DigestAlgorithm`]: ../../crypto/keys/struct.DigestAlgorithm.html -/// [`oid`]: ../../oid/index.html +/// [`Cert`]: super::cert::Cert +/// [`DigestAlgorithm`]: crate::crypto::digest::DigestAlgorithm +/// [`oid`]: crate::oid pub mod spec { } diff --git a/src/repository/x509.rs b/src/repository/x509.rs index 897763c..cbfc1d2 100644 --- a/src/repository/x509.rs +++ b/src/repository/x509.rs @@ -561,7 +561,7 @@ impl<'de> serde::Deserialize<'de> for Serial { ) -> Result { struct SerialVisitor; - impl<'de> serde::de::Visitor<'de> for SerialVisitor { + impl serde::de::Visitor<'_> for SerialVisitor { type Value = Serial; fn expecting( @@ -665,7 +665,7 @@ impl Eq for SignedData {} #[derive(Clone, Copy, Debug)] pub struct SignatureValueContent<'a, Alg>(&'a SignedData); -impl<'a, Alg> PrimitiveContent for SignatureValueContent<'a, Alg> { +impl PrimitiveContent for SignatureValueContent<'_, Alg> { const TAG: Tag = Tag::BIT_STRING; fn encoded_len(&self, _: Mode) -> usize { diff --git a/src/resources/addr.rs b/src/resources/addr.rs index 8ebd71b..741da7d 100644 --- a/src/resources/addr.rs +++ b/src/resources/addr.rs @@ -327,6 +327,23 @@ impl Prefix { }) } + /// Creates a prefix from a string even if the host portion is non-zero. + pub fn from_str_relaxed(s: &str) -> Result { + if s.is_empty() { + return Err(ParsePrefixError::Empty) + } + let slash = s.find('/').ok_or(ParsePrefixError::MissingLen)?; + let addr = IpAddr::from_str(&s[..slash]).map_err( + ParsePrefixError::InvalidAddr + )?; + let len = u8::from_str(&s[slash + 1..]).map_err( + ParsePrefixError::InvalidLen + )?; + Prefix::new_relaxed( + addr, len + ).map_err(ParsePrefixError::InvalidPrefix) + } + /// Returns whether the prefix is for an IPv4 address. pub fn is_v4(self) -> bool { self.family_and_len.is_v4() @@ -472,7 +489,7 @@ impl<'de> serde::Deserialize<'de> for Prefix { ) -> Result { struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { + impl serde::de::Visitor<'_> for Visitor { type Value = Prefix; fn expecting( @@ -981,6 +998,36 @@ mod test { Err(ParsePrefixError::InvalidLen(_)) ) ); + assert!( + matches!( + Prefix::from_str("127.0.0.1/33"), + Err(ParsePrefixError::InvalidPrefix( + PrefixError::LenOverflow + )) + ) + ); + assert!( + matches!( + Prefix::from_str("2001:db8::/129"), + Err(ParsePrefixError::InvalidPrefix( + PrefixError::LenOverflow + )) + ) + ); + assert!( + matches!( + Prefix::from_str("2001:db8::/9000"), + Err(ParsePrefixError::InvalidLen(_)) + ) + ); + assert!( + matches!( + Prefix::from_str("127.0.0.1/24"), + Err(ParsePrefixError::InvalidPrefix( + PrefixError::NonZeroHost + )) + ) + ); assert!( matches!( Prefix::from_str(""), @@ -989,6 +1036,83 @@ mod test { ); } + #[test] + fn prefix_from_str_relaxed() { + assert_eq!( + Prefix::from_str_relaxed("127.0.0.0/12").unwrap().addr_and_len(), + (IpAddr::from_str("127.0.0.0").unwrap(), 12) + ); + assert_eq!( + Prefix::from_str_relaxed( + "2001:db8:10:20::/64" + ).unwrap().addr_and_len(), + (IpAddr::from_str("2001:db8:10:20::").unwrap(), 64) + ); + assert_eq!( + Prefix::from_str_relaxed("0.0.0.0/0").unwrap().addr_and_len(), + (IpAddr::from_str("0.0.0.0").unwrap(), 0) + ); + assert_eq!( + Prefix::from_str_relaxed("::/0").unwrap().addr_and_len(), + (IpAddr::from_str("::").unwrap(), 0) + ); + + assert_eq!( + Prefix::from_str_relaxed("127.0.0.0"), + Err(ParsePrefixError::MissingLen) + ); + assert_eq!( + Prefix::from_str_relaxed("2001:db8::"), + Err(ParsePrefixError::MissingLen) + ); + assert!( + matches!( + Prefix::from_str_relaxed("127.0.0.0/"), + Err(ParsePrefixError::InvalidLen(_)) + ) + ); + assert!( + matches!( + Prefix::from_str_relaxed("2001:db8::/"), + Err(ParsePrefixError::InvalidLen(_)) + ) + ); + assert!( + matches!( + Prefix::from_str_relaxed("127.0.0.1/33"), + Err(ParsePrefixError::InvalidPrefix( + PrefixError::LenOverflow + )) + ) + ); + assert!( + matches!( + Prefix::from_str_relaxed("2001:db8::/129"), + Err(ParsePrefixError::InvalidPrefix( + PrefixError::LenOverflow + )) + ) + ); + assert!( + matches!( + Prefix::from_str_relaxed("2001:db8::/9000"), + Err(ParsePrefixError::InvalidLen(_)) + ) + ); + assert_eq!( + Prefix::from_str_relaxed( + "127.0.0.1/24" + ).unwrap().addr_and_len(), + (IpAddr::from_str("127.0.0.0").unwrap(), 24) + ); + assert!( + matches!( + Prefix::from_str_relaxed(""), + Err(ParsePrefixError::Empty) + ) + ); + } + #[test] fn ordering() { assert!( diff --git a/src/resources/asn.rs b/src/resources/asn.rs index 54bfee9..52c83d9 100644 --- a/src/resources/asn.rs +++ b/src/resources/asn.rs @@ -169,7 +169,7 @@ impl Asn { ) -> Result { struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { + impl serde::de::Visitor<'_> for Visitor { type Value = Asn; fn expecting( @@ -198,7 +198,7 @@ impl Asn { ) -> Result { struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { + impl serde::de::Visitor<'_> for Visitor { type Value = Asn; fn expecting( @@ -390,7 +390,7 @@ pub struct SmallSetDifference<'a> { right: Peekable>, } -impl<'a> Iterator for SmallSetDifference<'a> { +impl Iterator for SmallSetDifference<'_> { type Item = Asn; fn next(&mut self) -> Option { @@ -423,7 +423,7 @@ pub struct SmallSetSymmetricDifference<'a> { right: Peekable>, } -impl<'a> Iterator for SmallSetSymmetricDifference<'a> { +impl Iterator for SmallSetSymmetricDifference<'_> { type Item = Asn; fn next(&mut self) -> Option { @@ -455,7 +455,7 @@ pub struct SmallSetIntersection<'a> { right: Peekable>, } -impl<'a> Iterator for SmallSetIntersection<'a> { +impl Iterator for SmallSetIntersection<'_> { type Item = Asn; fn next(&mut self) -> Option { @@ -489,7 +489,7 @@ pub struct SmallSetUnion<'a> { right: Peekable>, } -impl<'a> Iterator for SmallSetUnion<'a> { +impl Iterator for SmallSetUnion<'_> { type Item = Asn; fn next(&mut self) -> Option { diff --git a/src/rrdp.rs b/src/rrdp.rs index 8533fd6..aed0ea8 100644 --- a/src/rrdp.rs +++ b/src/rrdp.rs @@ -222,13 +222,13 @@ impl NotificationFile { delta_limit: Option, ) -> Result { let mut reader = Reader::new(reader); - let mut session_id = None; let mut serial = None; - let mut outer = reader.start_with_limit(|element| { - if element.name() != NOTIFICATION { - return Err(XmlError::Malformed) - } + let mut outer = reader.start_with_limit( + |element| { + if element.name() != NOTIFICATION { + return Err(XmlError::Malformed) + } element.attributes(|name, value| { match name { @@ -252,7 +252,6 @@ impl NotificationFile { }, MAX_HEADER_SIZE)?; let mut snapshot = None; - let mut deltas = Ok(vec![]); while let Some(mut content) = outer.take_opt_element_with_limit(&mut reader, @@ -576,7 +575,7 @@ impl DeltaElement { } } -///--- From +//--- From impl From for DeltaElement { fn from(src: PublishElement) -> Self { @@ -1441,7 +1440,7 @@ pub struct ObjectReader<'a>( base64::XmlDecoderReader<'a> ); -impl<'a> ObjectReader<'a> { +impl ObjectReader<'_> { /// Processes an element with optional XML PCDATA as object content. /// /// An object reader is created and passed to the closure `op` for @@ -1488,7 +1487,7 @@ impl<'a> ObjectReader<'a> { } } -impl<'a> io::Read for ObjectReader<'a> { +impl io::Read for ObjectReader<'_> { fn read(&mut self, buf: &mut [u8]) -> Result { self.0.read(buf) } diff --git a/src/rtr/mod.rs b/src/rtr/mod.rs index 1ed444e..2266ad5 100644 --- a/src/rtr/mod.rs +++ b/src/rtr/mod.rs @@ -21,8 +21,8 @@ //! You can read more about RPKI in [RFC 6480]. RTR is currently specified in //! [RFC 8210]. //! -//! [`Client`]: client/struct.Client.html -//! [`Server`]: server/struct.Server.html +//! [`Client`]: client::Client +//! [`Server`]: server::Server //! [Tokio]: https://crates.io/crates/tokio //! [RFC 6480]: https://tools.ietf.org/html/rfc6480 //! [RFC 8210]: https://tools.ietf.org/html/rfc8210 diff --git a/src/rtr/payload.rs b/src/rtr/payload.rs index 30766d1..772e2ec 100644 --- a/src/rtr/payload.rs +++ b/src/rtr/payload.rs @@ -287,7 +287,7 @@ pub enum PayloadRef<'a> { //--- From -impl<'a> From for PayloadRef<'a> { +impl From for PayloadRef<'_> { fn from(src: RouteOrigin) -> Self { PayloadRef::Origin(src) } diff --git a/src/rtr/pdu.rs b/src/rtr/pdu.rs index 6ae63cd..0d38bec 100644 --- a/src/rtr/pdu.rs +++ b/src/rtr/pdu.rs @@ -787,11 +787,6 @@ pub struct Aspa { #[repr(packed)] struct AspaFixed { header: Header, - flags: u8, - afi_flags: u8, - - #[allow(dead_code)] - provider_count: u16, customer: u32, } @@ -818,10 +813,11 @@ impl Aspa { ).expect("ASPA RTR PDU size overflow"); Aspa { fixed: AspaFixed { - header: Header::new(version, Self::PDU, 0, len), - flags, - afi_flags: 0, - provider_count: providers.asn_count().to_be(), + header: Header::new( + version, Self::PDU, + (flags as u16) << 8, + len + ), customer: customer.into_u32().to_be(), }, providers @@ -848,7 +844,7 @@ impl Aspa { /// The only flag currently used is the least significant bit that is /// 1 for an announcement and 0 for a withdrawal. pub fn flags(&self) -> u8 { - self.fixed.flags + (self.fixed.header.session >> 8) as u8 } /// Returns the customer ASN. @@ -899,7 +895,15 @@ impl Aspa { let provider_len = match header.pdu_len()?.checked_sub(mem::size_of::()) { - Some(len) => len, + Some(len) => { + if len % 4 != 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid length for ASPA PDU" + )) + } + len + } None => { return Err(io::Error::new( io::ErrorKind::InvalidData, @@ -909,16 +913,6 @@ impl Aspa { }; let mut fixed = AspaFixed { header, .. Default::default() }; sock.read_exact(&mut fixed.as_mut()[Header::LEN..]).await?; - if provider_len - != usize::from( - u16::from_be(fixed.provider_count) - ) * mem::size_of::() - { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "invalid length for ASPA PDU" - )) - } let providers = ProviderAsns::read(sock, provider_len).await?; Ok(Aspa { fixed, providers }) } @@ -1831,9 +1825,9 @@ mod test { ]).unwrap(), ), [ - 2, 11, 0, 0, 0, 0, 0, 24, - 1, 0, 0, 2, 0, 1, 0, 15, - 0, 1, 0, 13, 0, 1, 0, 14, + 2, 11, 1, 0, 0, 0, 0, 20, + 0, 1, 0, 15, 0, 1, 0, 13, + 0, 1, 0, 14, ] ); } diff --git a/src/rtr/server.rs b/src/rtr/server.rs index 77b3d8c..81dac06 100644 --- a/src/rtr/server.rs +++ b/src/rtr/server.rs @@ -2,9 +2,7 @@ //! //! This module implements a generic RTR server through [`Server`]. The server //! receives its data from a type implementing [`PayloadSource`]. -//! -//! [`Server`]: struct.Server.html -//! [`VrpSource`]: trait.VrpSource.html + use std::io; use futures_util::future; use futures_util::pin_mut; @@ -73,7 +71,7 @@ pub trait PayloadSource: Clone + Sync + Send + 'static { /// Returns the current state and an iterator over the full set of VRPs. fn full(&self) -> (State, Self::Set); - /// Returns the current state and an interator over differences in VPRs. + /// Returns the current state and an iterator over differences in VPRs. /// /// The difference is between the state given in `state` and the current /// state. If the source cannot provide this difference, for instance @@ -101,7 +99,7 @@ pub trait PayloadDiff: Sync + Send + 'static { /// A stream socket to be used for an RTR connection. /// -/// Apart from being abile to read and write asynchronously and being spawned +/// Apart from being able to read and write asynchronously and being spawned /// as an async task, the trait allows additional processing when the client /// has successfully updated. pub trait Socket: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static { @@ -125,8 +123,6 @@ impl Socket for tokio::net::TcpStream { } /// a VRP source and serves RTR data. In order to also serve notifications /// whenever new data is available, the server uses a notification dispatch /// system via the [`Dispatch`] system. -/// -/// [`Dispatch`]: struct.Dispatch.html pub struct Server { /// The listener socket. listener: Listener, @@ -151,7 +147,7 @@ impl Server { /// Runs the server. /// /// The asynchronous function will return successfully when the listener - /// socket (which is a stream over new connectons) finishes. It will + /// socket (which is a stream over new connections) finishes. It will /// return with an error if the listener socket errors out. pub async fn run(mut self) -> Result<(), io::Error> where @@ -202,7 +198,7 @@ impl Connection { /// Returns the protocol version we agreed on. /// - /// If there hasn’t been a negotation yet, returns the lowest protocol + /// If there hasn’t been a negotiation yet, returns the lowest protocol /// version we support, which currently is 0. fn version(&self) -> u8 { self.version.unwrap_or(0) diff --git a/src/rtr/state.rs b/src/rtr/state.rs index 678e4da..0da6004 100644 --- a/src/rtr/state.rs +++ b/src/rtr/state.rs @@ -4,9 +4,6 @@ //! particular RTR server. The complete state, encapsulated in the type //! [`State`] consists of a sixteen bit session id and a serial number. Since //! the serial number follows special rules, it has its own type [`Serial`]. -//! -//! [`Serial`]: struct.Serial.html -//! [`State`]: struct.State.html use std::{cmp, fmt, hash, str}; use std::time::SystemTime; @@ -72,8 +69,6 @@ impl State { /// /// Serial number may wrap but that’s totally fine. See [`Serial`] for /// more details. - /// - /// [`Serial`]: struct.Serial.html pub fn inc(&mut self) { self.serial = self.serial.add(1) } diff --git a/src/slurm.rs b/src/slurm.rs index 5bcfa02..fe28d57 100644 --- a/src/slurm.rs +++ b/src/slurm.rs @@ -660,7 +660,7 @@ impl<'de> Deserialize<'de> for Base64KeyInfo { ) -> Result { struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { + impl serde::de::Visitor<'_> for Visitor { type Value = Base64KeyInfo; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -736,7 +736,7 @@ mod serde_key_identifier { ) -> Result { struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { + impl serde::de::Visitor<'_> for Visitor { type Value = KeyIdentifier; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/uri.rs b/src/uri.rs index 05a3644..b282273 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -481,7 +481,7 @@ impl<'de> Deserialize<'de> for Rsync { //--- PrimitiveContent #[cfg(feature = "repository")] -impl<'a> encode::PrimitiveContent for &'a Rsync { +impl encode::PrimitiveContent for &'_ Rsync { const TAG: Tag = Tag::IA5_STRING; fn encoded_len(&self, _: Mode) -> usize { @@ -779,7 +779,7 @@ impl<'de> Deserialize<'de> for Https { //--- PrimitiveContent #[cfg(feature = "repository")] -impl<'a> encode::PrimitiveContent for &'a Https { +impl encode::PrimitiveContent for &'_ Https { const TAG: Tag = Tag::IA5_STRING; fn encoded_len(&self, _: Mode) -> usize { @@ -871,7 +871,7 @@ impl Default for UriVisitor { } #[cfg(feature = "serde")] -impl<'de, V> serde::de::Visitor<'de> for UriVisitor +impl serde::de::Visitor<'_> for UriVisitor where V: FromStr + TryFrom, ::Err: fmt::Display, @@ -980,7 +980,7 @@ mod arbitrary { } } - impl<'a> Arbitrary<'a> for super::Https { + impl Arbitrary<'_> for super::Https { fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { let mut res = String::from("https://"); append_host(&mut res, u)?; diff --git a/src/util/base64.rs b/src/util/base64.rs index 87367e4..9b77476 100644 --- a/src/util/base64.rs +++ b/src/util/base64.rs @@ -166,7 +166,7 @@ impl<'a> SkipWhitespace<'a> { } } -impl<'a> io::Read for SkipWhitespace<'a> { +impl io::Read for SkipWhitespace<'_> { fn read(&mut self, mut buf: &mut[u8]) -> Result { let mut res = 0; @@ -205,7 +205,7 @@ pub struct XmlDecoderReader<'a>( > ); -impl<'a> io::Read for XmlDecoderReader<'a> { +impl io::Read for XmlDecoderReader<'_> { fn read(&mut self, buf: &mut [u8]) -> Result { self.0.read(buf) } diff --git a/src/xml/decode.rs b/src/xml/decode.rs index 19e4dde..a45870b 100644 --- a/src/xml/decode.rs +++ b/src/xml/decode.rs @@ -8,64 +8,7 @@ use quick_xml::name::Namespace; use crate::util::base64; -/// A simple BufRead passthrough proxy that acts as a "trip computer" -/// -/// It keeps track of the amount of bytes read since it was last reset. -/// If a limit is set, it will return an IO error when attempting to read -/// past that limit. -struct BufReadCounter { - reader: R, - trip: u128, - limit: u128 -} - -impl BufReadCounter { - - /// Create a new trip computer (resetting counter) for a BufRead - /// Acts transparently to the implementation of a BufRead below - pub fn new(reader: R) -> Self { - BufReadCounter { - reader, - trip: 0, - limit: 0 - } - } - - /// Reset the amount of bytes read back to 0 - pub fn reset(&mut self) { - self.trip = 0; - } - - /// Set a limit or pass 0 to disable the limit to the maximum bytes to - /// read. This overrides the previous limit. - pub fn limit(&mut self, limit: u128) { - self.limit = limit; - } -} - -impl io::Read for BufReadCounter { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.reader.read(buf) - } -} - -impl io::BufRead for BufReadCounter { - fn fill_buf(&mut self) -> io::Result<&[u8]> { - if self.limit > 0 && self.trip > self.limit { - return Err( - io::Error::new(io::ErrorKind::Other, - format!("Trip is over limit ({:?}/{:?})", - &self.trip, &self.limit)) - ); - } - self.reader.fill_buf() - } - - fn consume(&mut self, amt: usize) { - self.trip += u128::try_from(amt).unwrap_or_default(); - self.reader.consume(amt) - } -} +//------------ Reader -------------------------------------------------------- /// An XML reader. /// @@ -87,7 +30,7 @@ impl Reader { } } - pub fn reset_and_limit(&mut self, limit: u128) { + pub fn reset_and_limit(&mut self, limit: u64) { self.reader.get_mut().reset(); self.reader.get_mut().limit(limit); } @@ -124,7 +67,7 @@ impl Reader { } pub fn start_with_limit( - &mut self, op: F, limit: u128) -> Result + &mut self, op: F, limit: u64) -> Result where F: FnOnce(Element) -> Result<(), E>, E: From { self.reset_and_limit(limit); self.start(op) @@ -242,7 +185,7 @@ impl Content { &self, reader: &mut Reader, op: F, - limit: u128 + limit: u64 ) -> Result where R: io::BufRead, F: FnOnce(Element) -> Result<(), E>, E: From { reader.reset_and_limit(limit); @@ -297,7 +240,7 @@ impl Content { &mut self, reader: &mut Reader, op: F, - limit: u128 + limit: u64 ) -> Result, E> where R: io::BufRead, @@ -341,7 +284,7 @@ impl Content { &mut self, reader: &mut Reader, op: F, - limit: u128 + limit: u64 ) -> Result where R: io::BufRead, @@ -486,7 +429,7 @@ impl<'n, 'l> Name<'n, 'l> { } } -impl<'n, 'l> fmt::Debug for Name<'n, 'l> { +impl fmt::Debug for Name<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Name(")?; if let Some(ns) = self.namespace { @@ -496,13 +439,13 @@ impl<'n, 'l> fmt::Debug for Name<'n, 'l> { } } -impl<'n, 'l> From<&'l [u8]> for Name<'n, 'l> { +impl<'l> From<&'l [u8]> for Name<'_, 'l> { fn from(local: &'l [u8]) -> Self { Name::unqualified(local) } } -impl<'n, 'l> From<&'l str> for Name<'n, 'l> { +impl<'l> From<&'l str> for Name<'_, 'l> { fn from(local: &'l str) -> Self { Name::unqualified(local.as_bytes()) } @@ -527,7 +470,7 @@ impl<'n, 'l> From<(&'n str, &'l str)> for Name<'n, 'l> { #[derive(Clone)] pub struct AttrValue<'a>(quick_xml::events::attributes::Attribute<'a>); -impl<'a> AttrValue<'a> { +impl AttrValue<'_> { pub fn ascii_into(self) -> Result { let s = self.0.unescape_value()?; if !s.is_ascii() { @@ -550,7 +493,7 @@ impl<'a> AttrValue<'a> { pub struct Text<'a>(quick_xml::events::BytesText<'a>); -impl<'a> Text<'a> { +impl Text<'_> { pub fn to_utf8(&self) -> Result, Error> { Ok(self.0.unescape()?) } @@ -568,6 +511,70 @@ impl<'a> Text<'a> { } +//------------ BufReadCounter ------------------------------------------------ + +/// A simple BufRead passthrough proxy that acts as a "trip computer" +/// +/// It keeps track of the amount of bytes read since it was last reset. +/// If a limit is set, it will return an IO error when attempting to read +/// past that limit. +struct BufReadCounter { + reader: R, + trip: u64, + limit: u64, +} + +impl BufReadCounter { + /// Create a new trip computer (resetting counter) for a BufRead. + /// + /// Acts transparently to the implementation of a BufRead below. + pub fn new(reader: R) -> Self { + BufReadCounter { + reader, + trip: 0, + limit: 0 + } + } + + /// Reset the amount of bytes read back to 0 + pub fn reset(&mut self) { + self.trip = 0; + } + + /// Set a limit or pass 0 to disable the limit to the maximum bytes to + /// read. This overrides the previous limit. + pub fn limit(&mut self, limit: u64) { + self.limit = limit; + } +} + +impl io::Read for BufReadCounter { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.reader.read(buf) + } +} + +impl io::BufRead for BufReadCounter { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + if self.limit > 0 && self.trip > self.limit { + return Err( + io::Error::new(io::ErrorKind::Other, + format!("Trip is over limit ({:?}/{:?})", + &self.trip, &self.limit)) + ); + } + self.reader.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.trip = self.trip.saturating_add( + u64::try_from(amt).unwrap_or_default() + ); + self.reader.consume(amt) + } +} + + //------------ Error --------------------------------------------------------- #[derive(Debug)] diff --git a/src/xml/encode.rs b/src/xml/encode.rs index eb82cae..6a4b839 100644 --- a/src/xml/encode.rs +++ b/src/xml/encode.rs @@ -225,7 +225,7 @@ impl<'a, W: io::Write> Element<'a, W> { } } -impl<'a, W: io::Write> Drop for Element<'a, W> { +impl Drop for Element<'_, W> { fn drop(&mut self) { if let Err(err) = self.end() { self.writer.store_error(err) @@ -246,7 +246,7 @@ pub struct Content<'a, W> { writer: &'a mut Writer, } -impl<'a, W: io::Write> Content<'a, W> { +impl Content<'_, W> { /// Add an element with the given tag. /// /// This will write the beginning of the tag to the writer and therefore @@ -417,8 +417,8 @@ impl<'a, W: io::Write> DisplayText<'a, W> { } } -impl<'a, W: io::Write> fmt::Write for DisplayText<'a, W> { - fn write_str(&mut self, s: &str) -> fmt::Result { +impl fmt::Write for DisplayText<'_, W> { + fn write_str(&mut self, s: &str) -> fmt::Result { match self.escape.write_escaped(s.as_bytes(), self.inner) { Ok(()) => Ok(()), Err(err) => { diff --git a/tests/rrdp-resilience.rs b/tests/rrdp-resilience.rs index 2c2213d..c1c10ca 100644 --- a/tests/rrdp-resilience.rs +++ b/tests/rrdp-resilience.rs @@ -12,7 +12,10 @@ mod tests { use tokio::net::TcpListener; use rpki::rrdp::NotificationFile; - async fn serve(test_bytes: &'static[u8],_: Request) -> Result>, Infallible> { + async fn serve( + test_bytes: &'static[u8], + _: Request + ) -> Result>, Infallible> { let body = Full::new(Bytes::from_static(test_bytes)); Ok(Response::builder() @@ -65,7 +68,9 @@ mod tests { #[tokio::test] async fn test_snapshot_uri() { - let bytes = include_bytes!("../test-data/rrdp/bomb-snapshot-uri.xml.gz"); + let bytes = include_bytes!( + "../test-data/rrdp/bomb-snapshot-uri.xml.gz" + ); assert!(run(bytes).await.is_ok()); } @@ -74,4 +79,4 @@ mod tests { let bytes = include_bytes!("../test-data/rrdp/bomb-whitespace.xml.gz"); assert!(run(bytes).await.is_ok()); } -} \ No newline at end of file +}