use std::borrow::Borrow;
use std::fmt;
#[cfg(test)]
use quickcheck::{Arbitrary, Gen};
use crate::KeyHandle;
use crate::KeyID;
#[non_exhaustive]
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub enum Fingerprint {
V4([u8;20]),
V5([u8; 32]),
Invalid(Box<[u8]>),
}
assert_send_and_sync!(Fingerprint);
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:X}", self)
}
}
impl fmt::Debug for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Fingerprint")
.field(&self.to_string())
.finish()
}
}
impl fmt::UpperHex for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_to_fmt(f, true)
}
}
impl fmt::LowerHex for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_to_fmt(f, false)
}
}
impl std::str::FromStr for Fingerprint {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if s.chars().filter(|c| ! c.is_whitespace()).count() % 2 == 1 {
return Err(crate::Error::InvalidArgument(
"Odd number of nibbles".into()).into());
}
Ok(Self::from_bytes(&crate::fmt::hex::decode_pretty(s)?[..]))
}
}
impl Fingerprint {
pub fn from_bytes(raw: &[u8]) -> Fingerprint {
if raw.len() == 20 {
let mut fp : [u8; 20] = Default::default();
fp.copy_from_slice(raw);
Fingerprint::V4(fp)
} else if raw.len() == 32 {
let mut fp: [u8; 32] = Default::default();
fp.copy_from_slice(raw);
Fingerprint::V5(fp)
} else {
Fingerprint::Invalid(raw.to_vec().into_boxed_slice())
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Fingerprint::V4(ref fp) => fp,
Fingerprint::V5(fp) => fp,
Fingerprint::Invalid(ref fp) => fp,
}
}
pub fn to_hex(&self) -> String {
use std::fmt::Write;
let mut output = String::with_capacity(
self.as_bytes().len() * 2);
write!(output, "{:X}", self).unwrap();
output
}
pub fn to_spaced_hex(&self) -> String {
use std::fmt::Write;
let raw_len = self.as_bytes().len();
let mut output = String::with_capacity(
raw_len * 2
+
raw_len / 2
+ 1);
write!(output, "{:#X}", self).unwrap();
output
}
pub fn from_hex(s: &str) -> std::result::Result<Self, anyhow::Error> {
std::str::FromStr::from_str(s)
}
fn write_to_fmt(&self, f: &mut fmt::Formatter, upper_case: bool) -> fmt::Result {
use std::fmt::Write;
let raw = self.as_bytes();
let a_letter = if upper_case { b'A' } else { b'a' };
let pretty = f.alternate();
for (i, b) in raw.iter().enumerate() {
if pretty && i > 0 && i % 2 == 0 {
f.write_char(' ')?;
}
if pretty && i > 0 && i * 2 == raw.len() {
f.write_char(' ')?;
}
let top = b >> 4;
let bottom = b & 0xFu8;
if top < 10u8 {
f.write_char((b'0' + top) as char)?;
} else {
f.write_char((a_letter + (top - 10u8)) as char)?;
}
if bottom < 10u8 {
f.write_char((b'0' + bottom) as char)?;
} else {
f.write_char((a_letter + (bottom - 10u8)) as char)?;
}
}
Ok(())
}
pub fn to_icao(&self) -> String {
let mut ret = String::default();
for ch in self.to_hex().chars() {
let word = match ch {
'0' => "Zero",
'1' => "One",
'2' => "Two",
'3' => "Three",
'4' => "Four",
'5' => "Five",
'6' => "Six",
'7' => "Seven",
'8' => "Eight",
'9' => "Niner",
'A' => "Alfa",
'B' => "Bravo",
'C' => "Charlie",
'D' => "Delta",
'E' => "Echo",
'F' => "Foxtrot",
_ => { continue; }
};
if !ret.is_empty() {
ret.push(' ');
}
ret.push_str(word);
}
ret
}
pub fn aliases<H>(&self, other: H) -> bool
where H: Borrow<KeyHandle>
{
let other = other.borrow();
match (self, other) {
(f, KeyHandle::Fingerprint(o)) => {
f == o
},
(Fingerprint::V4(f), KeyHandle::KeyID(KeyID::V4(o))) => {
&f[12..] == o
},
(f, KeyHandle::KeyID(o)) => {
&KeyID::from(f) == o
},
}
}
}
#[cfg(test)]
impl Arbitrary for Fingerprint {
fn arbitrary(g: &mut Gen) -> Self {
if Arbitrary::arbitrary(g) {
let mut fp = [0; 20];
fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g));
Fingerprint::V4(fp)
} else {
let mut fp = [0; 32];
fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g));
Fingerprint::V5(fp)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v4_hex_formatting() {
let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567"
.parse::<Fingerprint>().unwrap();
assert!(matches!(&fp, Fingerprint::V4(_)));
assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF01234567");
assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef01234567");
}
#[test]
fn v5_hex_formatting() -> crate::Result<()> {
let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF \
0123 4567 89AB CDEF 0123 4567 89AB CDEF"
.parse::<Fingerprint>()?;
assert!(matches!(&fp, Fingerprint::V5(_)));
assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF\
0123456789ABCDEF0123456789ABCDEF");
assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef\
0123456789abcdef0123456789abcdef");
Ok(())
}
#[test]
fn aliases() -> crate::Result<()> {
let fp1 = "280C0AB0B94D1302CAAEB71DA299CDCD3884EBEA"
.parse::<Fingerprint>()?;
let fp15 = "1234567890ABCDEF12345678A299CDCD3884EBEA"
.parse::<Fingerprint>()?;
let fp2 = "F8D921C01EE93B65D4C6FEB7B456A7DB5E4274D0"
.parse::<Fingerprint>()?;
let keyid1 = KeyID::from(&fp1);
let keyid15 = KeyID::from(&fp15);
let keyid2 = KeyID::from(&fp2);
eprintln!("fp1: {:?}", fp1);
eprintln!("keyid1: {:?}", keyid1);
eprintln!("fp15: {:?}", fp15);
eprintln!("keyid15: {:?}", keyid15);
eprintln!("fp2: {:?}", fp2);
eprintln!("keyid2: {:?}", keyid2);
assert_ne!(fp1, fp15);
assert_eq!(keyid1, keyid15);
assert!(fp1.aliases(KeyHandle::from(&fp1)));
assert!(! fp1.aliases(KeyHandle::from(&fp15)));
assert!(! fp1.aliases(KeyHandle::from(&fp2)));
assert!(! fp15.aliases(KeyHandle::from(&fp1)));
assert!(fp15.aliases(KeyHandle::from(&fp15)));
assert!(! fp15.aliases(KeyHandle::from(&fp2)));
assert!(! fp2.aliases(KeyHandle::from(&fp1)));
assert!(! fp2.aliases(KeyHandle::from(&fp15)));
assert!(fp2.aliases(KeyHandle::from(&fp2)));
assert!(fp1.aliases(KeyHandle::from(&keyid1)));
assert!(fp1.aliases(KeyHandle::from(&keyid15)));
assert!(! fp1.aliases(KeyHandle::from(&keyid2)));
assert!(fp15.aliases(KeyHandle::from(&keyid1)));
assert!(fp15.aliases(KeyHandle::from(&keyid15)));
assert!(! fp15.aliases(KeyHandle::from(&keyid2)));
assert!(! fp2.aliases(KeyHandle::from(&keyid1)));
assert!(! fp2.aliases(KeyHandle::from(&keyid15)));
assert!(fp2.aliases(KeyHandle::from(&keyid2)));
Ok(())
}
}