#![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;
#[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<()> {
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,
}
}
pub fn algorithm(&self) -> SshfpAlgorithm {
self.algorithm
}
pub fn fingerprint_type(&self) -> SshfpType {
self.fingerprint_type
}
pub fn fingerprint(&self) -> &Octs {
&self.fingerprint
}
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,
})
}
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(
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(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};
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!(),
}
}
}
}