domain 0.12.0

A DNS library for Rust.
Documentation
//! OPENPGPKEY record data.
//!
//! The OPENPGPKEY Resource Record carries a single OpenPGP Transferable Public Key.
//!
//! [RFC 7929]: https://tools.ietf.org/html/rfc7929

// Currently a false positive on Openpgpkey. 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::Rtype;
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::utils::base64;
use core::cmp::Ordering;
use core::{fmt, hash};
use octseq::octets::{Octets, OctetsFrom, OctetsInto};
use octseq::parse::Parser;

/// The OPENPGPKEY Resource Record carries a single OpenPGP Transferable Public Key.
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Openpgpkey<Octs: ?Sized> {
    #[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>",
            )
        )
    )]
    key: Octs,
}

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

impl<Octs> Openpgpkey<Octs> {
    /// Create a Openpgpkey record data from provided parameters.
    pub fn new(key: Octs) -> Self {
        Self { key }
    }

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

    /// 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 len = parser.remaining();
        let key = parser.parse_octets(len)?;
        Ok(Self { key })
    }

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

        Ok(Self { key })
    }

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

    pub(super) fn convert_octets<Target: OctetsFrom<Octs>>(
        self,
    ) -> Result<Openpgpkey<Target>, Target::Error> {
        let Openpgpkey { key } = self;

        Ok(Openpgpkey {
            key: key.try_octets_into()?,
        })
    }
}

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

impl<Octs: AsRef<[u8]>> ComposeRecordData for Openpgpkey<Octs> {
    fn rdlen(&self, _compress: bool) -> Option<u16> {
        Some(
            // key_len
            u16::try_from(self.key.as_ref().len())
                .expect("long OPENPGPKEY rdata"),
        )
    }

    fn compose_rdata<Target: Composer + ?Sized>(
        &self,
        target: &mut Target,
    ) -> Result<(), Target::AppendError> {
        target.append_slice(self.key.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 Openpgpkey<Octs> {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.key.as_ref().hash(state);
    }
}

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

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

impl<Octs: AsRef<[u8]>> fmt::Display for Openpgpkey<Octs> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "( ",)?;
        base64::display(&self.key, f)?;
        write!(f, " )")
    }
}

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

impl<Octs: AsRef<[u8]>> ZonefileFmt for Openpgpkey<Octs> {
    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
        p.block(|p| p.write_token(base64::encode_display(&self.key)))
    }
}

impl<Octs, Other> PartialOrd<Openpgpkey<Other>> for Openpgpkey<Octs>
where
    Octs: AsRef<[u8]>,
    Other: AsRef<[u8]>,
{
    fn partial_cmp(&self, other: &Openpgpkey<Other>) -> Option<Ordering> {
        self.key.as_ref().partial_cmp(other.key.as_ref())
    }
}

impl<Octs, Other> CanonicalOrd<Openpgpkey<Other>> for Openpgpkey<Octs>
where
    Octs: AsRef<[u8]>,
    Other: AsRef<[u8]>,
{
    fn canonical_cmp(&self, other: &Openpgpkey<Other>) -> Ordering {
        self.key.as_ref().cmp(other.key.as_ref())
    }
}

impl<Octs: AsRef<[u8]>> Ord for Openpgpkey<Octs> {
    fn cmp(&self, other: &Self) -> Ordering {
        self.key.as_ref().cmp(other.key.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::base64::decode;
    use std::vec::Vec;

    #[test]
    // allow redundant_closure because because of lifetime shenanigans
    // in test_compose_parse(Openpgpkey::parse), "FnOnce is not general enough"
    #[allow(clippy::redundant_closure)]
    fn openpgpkey_compose_parse_scan() {
        let key_str = "mDMEaLmjchYJKwYBBAHaRw8BAQdAaO6PfPJsT8to5dksKP1JsCmR0DqOTmVYLOv7mFeQPC+0HVRlc3QgVXNlciA8dGVzdEBubG5ldGxhYnMubmw+iJYEExYKAD4WIQT/B5WhrMOftpJIwIkxXU3+oVEKegUCaLmjcgIbAwUJBaOagAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAxXU3+oVEKemSgAP97Zvz+PWEJC9vhlSN4gVRPR9VZYhzGwfpixgRI4sqKfwD9FxJhsj43vGEbOLdsWwf/lQvkajRov5FpofS1IFy/dgi4OARouaNyEgorBgEEAZdVAQUBAQdAUQr9riJNCFWRzQ6q70B/H/o+uwvL6nGJRhWSg1v7mRkDAQgHiH4EGBYKACYWIQT/B5WhrMOftpJIwIkxXU3+oVEKegUCaLmjcgIbDAUJBaOagAAKCRAxXU3+oVEKeuX3APkB5piWOSbOPLvtiElIVTHT6gWlu1wSpVVzZEmgtnOpiQD+Kk/IFjHpT0RbgsIvI3qhnXWwHvIw4JxHS1a/piLwkwM=";
        let key: Vec<u8> = decode(key_str).unwrap();
        let rdata = Openpgpkey::new(key);
        test_rdlen(&rdata);
        test_compose_parse(&rdata, |parser| Openpgpkey::parse(parser));
        test_scan(&[key_str], Openpgpkey::scan, &rdata);
    }

    #[cfg(feature = "zonefile")]
    #[test]
    fn openpgpkey_parse_zonefile() {
        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  OPENPGPKEY (
                                 mDMEaLmjchYJKwYBBAHaRw8BAQdAaO6P
                                 fPJsT8to5dksKP1JsCmR0DqOTmVYLOv7
                                 mFeQPC+0HVRlc3QgVXNlciA8dGVzdEBu
                                 bG5ldGxhYnMubmw+iJYEExYKAD4WIQT/
                                 B5WhrMOftpJIwIkxXU3+oVEKegUCaLmj
                                 cgIbAwUJBaOagAULCQgHAgYVCgkICwIE
                                 FgIDAQIeAQIXgAAKCRAxXU3+oVEKemSg
                                 AP97Zvz+PWEJC9vhlSN4gVRPR9VZYhzG
                                 wfpixgRI4sqKfwD9FxJhsj43vGEbOLds
                                 Wwf/lQvkajRov5FpofS1IFy/dgi4OARo
                                 uaNyEgorBgEEAZdVAQUBAQdAUQr9riJN
                                 CFWRzQ6q70B/H/o+uwvL6nGJRhWSg1v7
                                 mRkDAQgHiH4EGBYKACYWIQT/B5WhrMOf
                                 tpJIwIkxXU3+oVEKegUCaLmjcgIbDAUJ
                                 BaOagAAKCRAxXU3+oVEKeuX3APkB5piW
                                 OSbOPLvtiElIVTHT6gWlu1wSpVVzZEmg
                                 tnOpiQD+Kk/IFjHpT0RbgsIvI3qhnXWw
                                 HvIw4JxHS1a/piLwkwM= )
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::OPENPGPKEY {
                        continue;
                    }
                    match record.into_data() {
                        ZoneRecordData::Openpgpkey(_) => {}
                        _ => panic!(),
                    }
                }
                _ => panic!(),
            }
        }
    }
}