domain 0.12.0

A DNS library for Rust.
Documentation
//! Record data from [RFC 4255]: SSHFP records.
//!
//! This RFC defines the SSHFP record type and is updated by [RFC 6594],
//! [RFC 7479], and [RFC 8709].
//!
//! [RFC 4255]: https://tools.ietf.org/html/rfc4255
//! [RFC 6594]: https://tools.ietf.org/html/rfc6594
//! [RFC 7479]: https://tools.ietf.org/html/rfc7479
//! [RFC 8709]: https://tools.ietf.org/html/rfc8709

// Currently a false positive on Sshfp. We cannot apply it there because
// the allow attribute doesn't get copied to the code generated by serde.
#![allow(clippy::needless_maybe_sized)]

use crate::base::cmp::CanonicalOrd;
use crate::base::iana::{SshfpAlgorithm, SshfpType};
use crate::base::rdata::{ComposeRecordData, RecordData};
use crate::base::scan::Scanner;
use crate::base::wire::{Composer, ParseError};
use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
use crate::base::Rtype;
use crate::utils::base16;
use core::cmp::Ordering;
use core::{fmt, hash};
use octseq::octets::{Octets, OctetsFrom, OctetsInto};
use octseq::parse::Parser;

//------------ Sshfp ---------------------------------------------------------

#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Sshfp<Octs: ?Sized> {
    algorithm: SshfpAlgorithm,
    fingerprint_type: SshfpType,
    #[cfg_attr(
        feature = "serde",
        serde(
            serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
            deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
            bound(
                serialize = "Octs: octseq::serde::SerializeOctets",
                deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
            )
        )
    )]
    fingerprint: Octs,
}

impl Sshfp<()> {
    /// The rtype of this record data type.
    pub(crate) const RTYPE: Rtype = Rtype::SSHFP;
}

impl<Octs> Sshfp<Octs> {
    pub fn new(
        algorithm: SshfpAlgorithm,
        fingerprint_type: SshfpType,
        fingerprint: Octs,
    ) -> Self {
        Sshfp {
            algorithm,
            fingerprint_type,
            fingerprint,
        }
    }

    /// Get the algorithm field.
    pub fn algorithm(&self) -> SshfpAlgorithm {
        self.algorithm
    }

    /// Get the fingerprint type field.
    pub fn fingerprint_type(&self) -> SshfpType {
        self.fingerprint_type
    }

    /// Get the fingerprint field.
    pub fn fingerprint(&self) -> &Octs {
        &self.fingerprint
    }

    /// Parse the record data from wire format.
    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
        parser: &mut Parser<'a, Src>,
    ) -> Result<Self, ParseError> {
        let algorithm = SshfpAlgorithm::parse(parser)?;
        let fingerprint_type = SshfpType::parse(parser)?;
        let len = parser.remaining();
        let fingerprint = parser.parse_octets(len)?;
        Ok(Self {
            algorithm,
            fingerprint_type,
            fingerprint,
        })
    }

    /// Parse the record data from zonefile format.
    pub fn scan<S: Scanner<Octets = Octs>>(
        scanner: &mut S,
    ) -> Result<Self, S::Error> {
        let algorithm = SshfpAlgorithm::scan(scanner)?;
        let fingerprint_type = SshfpType::scan(scanner)?;
        let fingerprint =
            scanner.convert_entry(base16::SymbolConverter::new())?;

        Ok(Self {
            algorithm,
            fingerprint_type,
            fingerprint,
        })
    }

    pub(super) fn flatten<Target: OctetsFrom<Octs>>(
        self,
    ) -> Result<Sshfp<Target>, Target::Error> {
        self.convert_octets()
    }

    pub(super) fn convert_octets<Target: OctetsFrom<Octs>>(
        self,
    ) -> Result<Sshfp<Target>, Target::Error> {
        let Sshfp {
            algorithm,
            fingerprint_type,
            fingerprint,
        } = self;

        Ok(Sshfp {
            algorithm,
            fingerprint_type,
            fingerprint: fingerprint.try_octets_into()?,
        })
    }
}

impl<Octs> RecordData for Sshfp<Octs> {
    fn rtype(&self) -> Rtype {
        Sshfp::RTYPE
    }
}

impl<Octs: AsRef<[u8]>> ComposeRecordData for Sshfp<Octs> {
    fn rdlen(&self, _compress: bool) -> Option<u16> {
        Some(
            // algorithm + fingerprint_type + fingerprint_len
            u16::try_from(1 + 1 + self.fingerprint.as_ref().len())
                .expect("long SSHFP rdata"),
        )
    }

    fn compose_rdata<Target: Composer + ?Sized>(
        &self,
        target: &mut Target,
    ) -> Result<(), Target::AppendError> {
        target.append_slice(&[self.algorithm.into()])?;
        target.append_slice(&[self.fingerprint_type.into()])?;
        target.append_slice(self.fingerprint.as_ref())
    }

    fn compose_canonical_rdata<Target: Composer + ?Sized>(
        &self,
        target: &mut Target,
    ) -> Result<(), Target::AppendError> {
        self.compose_rdata(target)
    }
}

impl<Octs: AsRef<[u8]>> hash::Hash for Sshfp<Octs> {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.algorithm.hash(state);
        self.fingerprint_type.hash(state);
        self.fingerprint.as_ref().hash(state);
    }
}

impl<Octs, Other> PartialEq<Sshfp<Other>> for Sshfp<Octs>
where
    Octs: AsRef<[u8]> + ?Sized,
    Other: AsRef<[u8]> + ?Sized,
{
    fn eq(&self, other: &Sshfp<Other>) -> bool {
        self.algorithm.eq(&other.algorithm)
            && self.fingerprint_type.eq(&other.fingerprint_type)
            && self.fingerprint.as_ref().eq(other.fingerprint.as_ref())
    }
}

impl<Octs: AsRef<[u8]> + ?Sized> Eq for Sshfp<Octs> {}

impl<Octs: AsRef<[u8]>> fmt::Display for Sshfp<Octs> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} ( ",
            u8::from(self.algorithm),
            u8::from(self.fingerprint_type)
        )?;
        base16::display(&self.fingerprint, f)?;
        write!(f, " )")
    }
}

impl<Octs: AsRef<[u8]>> fmt::Debug for Sshfp<Octs> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("Sshfp(")?;
        fmt::Display::fmt(self, f)?;
        f.write_str(")")
    }
}

impl<Octs: AsRef<[u8]>> ZonefileFmt for Sshfp<Octs> {
    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
        p.block(|p| {
            p.write_token(self.algorithm)?;
            p.write_token(self.fingerprint_type)?;
            p.write_token(base16::encode_display(&self.fingerprint))
        })
    }
}

impl<Octs, Other> PartialOrd<Sshfp<Other>> for Sshfp<Octs>
where
    Octs: AsRef<[u8]>,
    Other: AsRef<[u8]>,
{
    fn partial_cmp(&self, other: &Sshfp<Other>) -> Option<Ordering> {
        match self.algorithm.partial_cmp(&other.algorithm) {
            Some(Ordering::Equal) => {}
            other => return other,
        }
        match self.fingerprint_type.partial_cmp(&other.fingerprint_type) {
            Some(Ordering::Equal) => {}
            other => return other,
        }
        self.fingerprint
            .as_ref()
            .partial_cmp(other.fingerprint.as_ref())
    }
}

impl<Octs, Other> CanonicalOrd<Sshfp<Other>> for Sshfp<Octs>
where
    Octs: AsRef<[u8]>,
    Other: AsRef<[u8]>,
{
    fn canonical_cmp(&self, other: &Sshfp<Other>) -> Ordering {
        match self.algorithm.cmp(&other.algorithm) {
            Ordering::Equal => {}
            other => return other,
        }
        match self.fingerprint_type.cmp(&other.fingerprint_type) {
            Ordering::Equal => {}
            other => return other,
        }
        self.fingerprint.as_ref().cmp(other.fingerprint.as_ref())
    }
}

impl<Octs: AsRef<[u8]>> Ord for Sshfp<Octs> {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.algorithm.cmp(&other.algorithm) {
            Ordering::Equal => {}
            other => return other,
        }
        match self.fingerprint_type.cmp(&other.fingerprint_type) {
            Ordering::Equal => {}
            other => return other,
        }
        self.fingerprint.as_ref().cmp(other.fingerprint.as_ref())
    }
}

#[cfg(test)]
#[cfg(all(feature = "std", feature = "bytes"))]
mod test {
    use super::*;
    use crate::base::rdata::test::{
        test_compose_parse, test_rdlen, test_scan,
    };
    use crate::utils::base16::decode;
    use std::string::ToString;
    use std::vec::Vec;

    #[test]
    // allow redundant_closure because because of lifetime shenanigans
    // in test_compose_parse(Sshfp::parse), "FnOnce is not general enough"
    #[allow(clippy::redundant_closure)]
    fn sshfp_compose_parse_scan() {
        let algorithm = 1.into();
        let fingerprint_type = 1.into();
        let fingerprint_str = "73d3fa022a121062580431316bfe5d56653b91c2";
        let fingerprint: Vec<u8> = decode(fingerprint_str).unwrap();
        let rdata = Sshfp::new(algorithm, fingerprint_type, fingerprint);
        test_rdlen(&rdata);
        test_compose_parse(&rdata, |parser| Sshfp::parse(parser));
        test_scan(
            &[
                &u8::from(algorithm).to_string(),
                &u8::from(fingerprint_type).to_string(),
                fingerprint_str,
            ],
            Sshfp::scan,
            &rdata,
        );
    }

    #[cfg(feature = "zonefile")]
    #[test]
    fn sshfp_parse_zonefile() {
        use crate::base::iana::{SshfpAlgorithm, SshfpType};
        use crate::base::Name;
        use crate::rdata::ZoneRecordData;
        use crate::zonefile::inplace::{Entry, Zonefile};

        // section A.1
        let content = r#"
example.      86400  IN  SOA     ns1 admin 2018031900 (
                                 1800 900 604800 86400 )
              86400  IN  NS      ns1
              86400  IN  NS      ns2
              86400  IN  SSHFP   1 1 (
                                 73d3fa022a121062
                                 580431316bfe5d56
                                 653b91c2 )
ns1           3600   IN  A       203.0.113.63
ns2           3600   IN  AAAA    2001:db8::63
"#;

        let mut zone = Zonefile::load(&mut content.as_bytes()).unwrap();
        zone.set_origin(Name::root());
        while let Some(entry) = zone.next_entry().unwrap() {
            match entry {
                Entry::Record(record) => {
                    if record.rtype() != Rtype::SSHFP {
                        continue;
                    }
                    match record.into_data() {
                        ZoneRecordData::Sshfp(rd) => {
                            assert_eq!(SshfpAlgorithm::RSA, rd.algorithm());
                            assert_eq!(
                                SshfpType::SHA1,
                                rd.fingerprint_type()
                            );
                        }
                        _ => panic!(),
                    }
                }
                _ => panic!(),
            }
        }
    }
}