//! 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!(),
}
}
}
}