#![allow(clippy::needless_maybe_sized)]
use crate::base::cmp::CanonicalOrd;
use crate::base::iana::{
Rtype, TlsaCertificateUsage, TlsaMatchingType, TlsaSelector,
};
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::base16;
use core::cmp::Ordering;
use core::{fmt, hash};
use octseq::octets::{Octets, OctetsFrom, OctetsInto};
use octseq::parse::Parser;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tlsa<Octs: ?Sized> {
usage: TlsaCertificateUsage,
selector: TlsaSelector,
matching_type: TlsaMatchingType,
#[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>",
)
)
)]
data: Octs,
}
impl Tlsa<()> {
pub(crate) const RTYPE: Rtype = Rtype::TLSA;
}
impl<Octs> Tlsa<Octs> {
pub fn new(
usage: TlsaCertificateUsage,
selector: TlsaSelector,
matching_type: TlsaMatchingType,
data: Octs,
) -> Self {
Self {
usage,
selector,
matching_type,
data,
}
}
pub fn usage(&self) -> TlsaCertificateUsage {
self.usage
}
pub fn selector(&self) -> TlsaSelector {
self.selector
}
pub fn matching_type(&self) -> TlsaMatchingType {
self.matching_type
}
pub fn data(&self) -> &Octs {
&self.data
}
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError> {
let usage = TlsaCertificateUsage::parse(parser)?;
let selector = TlsaSelector::parse(parser)?;
let matching_type = TlsaMatchingType::parse(parser)?;
let len = parser.remaining();
let data = parser.parse_octets(len)?;
Ok(Self {
usage,
selector,
matching_type,
data,
})
}
pub fn scan<S: Scanner<Octets = Octs>>(
scanner: &mut S,
) -> Result<Self, S::Error> {
let usage = TlsaCertificateUsage::scan(scanner)?;
let selector = TlsaSelector::scan(scanner)?;
let matching_type = TlsaMatchingType::scan(scanner)?;
let data = scanner.convert_entry(base16::SymbolConverter::new())?;
Ok(Self {
usage,
selector,
matching_type,
data,
})
}
pub(super) fn flatten<Target: OctetsFrom<Octs>>(
self,
) -> Result<Tlsa<Target>, Target::Error> {
self.convert_octets()
}
pub(super) fn convert_octets<Target: OctetsFrom<Octs>>(
self,
) -> Result<Tlsa<Target>, Target::Error> {
let Tlsa {
usage,
selector,
matching_type,
data,
} = self;
Ok(Tlsa {
usage,
selector,
matching_type,
data: data.try_octets_into()?,
})
}
}
impl<Octs> RecordData for Tlsa<Octs> {
fn rtype(&self) -> Rtype {
Tlsa::RTYPE
}
}
impl<Octs: AsRef<[u8]>> ComposeRecordData for Tlsa<Octs> {
fn rdlen(&self, _compress: bool) -> Option<u16> {
Some(
u16::try_from(1 + 1 + 1 + self.data.as_ref().len())
.expect("long TLSA rdata"),
)
}
fn compose_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_slice(&[self.usage.into()])?;
target.append_slice(&[self.selector.into()])?;
target.append_slice(&[self.matching_type.into()])?;
target.append_slice(self.data.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 Tlsa<Octs> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.usage.hash(state);
self.selector.hash(state);
self.matching_type.hash(state);
self.data.as_ref().hash(state);
}
}
impl<Octs, Other> PartialEq<Tlsa<Other>> for Tlsa<Octs>
where
Octs: AsRef<[u8]> + ?Sized,
Other: AsRef<[u8]> + ?Sized,
{
fn eq(&self, other: &Tlsa<Other>) -> bool {
self.usage.eq(&other.usage)
&& self.selector.eq(&other.selector)
&& self.matching_type.eq(&other.matching_type)
&& self.data.as_ref().eq(other.data.as_ref())
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Eq for Tlsa<Octs> {}
impl<Octs: AsRef<[u8]>> fmt::Display for Tlsa<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {} ( ",
u8::from(self.usage),
u8::from(self.selector),
u8::from(self.matching_type)
)?;
base16::display(&self.data, f)?;
write!(f, " )")
}
}
impl<Octs: AsRef<[u8]>> fmt::Debug for Tlsa<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tlsa(")?;
fmt::Display::fmt(self, f)?;
f.write_str(")")
}
}
impl<Octs: AsRef<[u8]>> ZonefileFmt for Tlsa<Octs> {
fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
p.block(|p| {
p.write_token(self.usage)?;
p.write_show(self.selector)?;
p.write_show(self.matching_type)?;
p.write_token(base16::encode_display(&self.data))
})
}
}
impl<Octs, Other> PartialOrd<Tlsa<Other>> for Tlsa<Octs>
where
Octs: AsRef<[u8]>,
Other: AsRef<[u8]>,
{
fn partial_cmp(&self, other: &Tlsa<Other>) -> Option<Ordering> {
match self.usage.partial_cmp(&other.usage) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.selector.partial_cmp(&other.selector) {
Some(Ordering::Equal) => {}
other => return other,
}
match self.matching_type.partial_cmp(&other.matching_type) {
Some(Ordering::Equal) => {}
other => return other,
}
self.data.as_ref().partial_cmp(other.data.as_ref())
}
}
impl<Octs, Other> CanonicalOrd<Tlsa<Other>> for Tlsa<Octs>
where
Octs: AsRef<[u8]>,
Other: AsRef<[u8]>,
{
fn canonical_cmp(&self, other: &Tlsa<Other>) -> Ordering {
match self.usage.cmp(&other.usage) {
Ordering::Equal => {}
other => return other,
}
match self.selector.cmp(&other.selector) {
Ordering::Equal => {}
other => return other,
}
match self.matching_type.cmp(&other.matching_type) {
Ordering::Equal => {}
other => return other,
}
self.data.as_ref().cmp(other.data.as_ref())
}
}
impl<Octs: AsRef<[u8]>> Ord for Tlsa<Octs> {
fn cmp(&self, other: &Self) -> Ordering {
match self.usage.cmp(&other.usage) {
Ordering::Equal => {}
other => return other,
}
match self.selector.cmp(&other.selector) {
Ordering::Equal => {}
other => return other,
}
match self.matching_type.cmp(&other.matching_type) {
Ordering::Equal => {}
other => return other,
}
self.data.as_ref().cmp(other.data.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(clippy::redundant_closure)]
fn tlsa_compose_parse_scan() {
let usage = 0.into();
let selector = 0.into();
let matching_type = 1.into();
let data_str = "d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971";
let data: Vec<u8> = decode(data_str).unwrap();
let rdata = Tlsa::new(usage, selector, matching_type, data);
test_rdlen(&rdata);
test_compose_parse(&rdata, |parser| Tlsa::parse(parser));
test_scan(
&[
&u8::from(usage).to_string(),
&u8::from(selector).to_string(),
&u8::from(matching_type).to_string(),
data_str,
],
Tlsa::scan,
&rdata,
);
}
#[cfg(feature = "zonefile")]
#[test]
fn tlsa_parse_zonefile() {
use crate::base::iana::{
TlsaCertificateUsage, TlsaMatchingType, TlsaSelector,
};
use crate::base::Name;
use crate::rdata::ZoneRecordData;
use crate::zonefile::inplace::{Entry, Zonefile};
let content = r#"
example. 86400 IN SOA ns1 admin 2018031900 (
1800 900 604800 86400 )
86400 IN NS ns1
86400 IN NS ns2
86400 IN TLSA 0 0 1 (
d2abde240d7cd3ee6b4b28c54df034b9
7983a1d16e8a410e4561cb106618e971 )
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::TLSA {
continue;
}
match record.into_data() {
ZoneRecordData::Tlsa(rd) => {
assert_eq!(
TlsaCertificateUsage::PKIX_TA,
rd.usage()
);
assert_eq!(TlsaSelector::CERT, rd.selector());
assert_eq!(
TlsaMatchingType::SHA2_256,
rd.matching_type()
);
}
_ => panic!(),
}
}
_ => panic!(),
}
}
}
}