use core::fmt;
#[cfg(feature = "siphasher")]
use core::net::IpAddr;
#[cfg(feature = "siphasher")]
use core::ops::Range;
use crate::{
new::base::{
wire::{
AsBytes, BuildBytes, ParseBytes, ParseBytesZC, SplitBytes,
SplitBytesZC,
},
Serial,
},
utils::dst::UnsizedCopy,
};
#[cfg(feature = "siphasher")]
use crate::new::base::wire::TruncationError;
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Hash,
AsBytes,
BuildBytes,
ParseBytes,
ParseBytesZC,
SplitBytes,
SplitBytesZC,
UnsizedCopy,
)]
#[repr(transparent)]
pub struct ClientCookie {
pub octets: [u8; 8],
}
impl ClientCookie {
#[cfg(feature = "rand")]
pub fn random() -> Self {
rand::random::<[u8; 8]>().into()
}
}
impl ClientCookie {
#[cfg(feature = "siphasher")]
pub fn respond_into<'b>(
&self,
addr: IpAddr,
secret: &[u8; 16],
mut bytes: &'b mut [u8],
) -> Result<&'b mut [u8], TruncationError> {
use core::hash::Hasher;
use siphasher::sip::SipHasher24;
use crate::new::base::wire::BuildBytes;
let mut hasher = SipHasher24::new_with_key(secret);
bytes = self.build_bytes(bytes)?;
hasher.write(self.as_bytes());
bytes = [1, 0, 0, 0].build_bytes(bytes)?;
hasher.write(&[1, 0, 0, 0]);
let timestamp = Serial::unix_time();
bytes = timestamp.build_bytes(bytes)?;
hasher.write(timestamp.as_bytes());
match addr {
IpAddr::V4(addr) => hasher.write(&addr.octets()),
IpAddr::V6(addr) => hasher.write(&addr.octets()),
}
let hash = hasher.finish().to_le_bytes();
bytes = hash.build_bytes(bytes)?;
Ok(bytes)
}
}
impl From<[u8; 8]> for ClientCookie {
fn from(value: [u8; 8]) -> Self {
Self { octets: value }
}
}
impl From<ClientCookie> for [u8; 8] {
fn from(value: ClientCookie) -> Self {
value.octets
}
}
impl fmt::Debug for ClientCookie {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ClientCookie({})", self)
}
}
impl fmt::Display for ClientCookie {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016X}", u64::from_be_bytes(self.octets))
}
}
#[derive(
Debug, PartialEq, Eq, Hash, AsBytes, BuildBytes, ParseBytesZC, UnsizedCopy,
)]
#[repr(C)]
pub struct Cookie {
request: ClientCookie,
version: u8,
reserved: [u8; 3],
timestamp: Serial,
hash: [u8],
}
impl Cookie {
pub fn request(&self) -> &ClientCookie {
&self.request
}
pub fn version(&self) -> u8 {
self.version
}
pub fn timestamp(&self) -> Serial {
self.timestamp
}
}
impl Cookie {
#[cfg(feature = "siphasher")]
pub fn verify(
&self,
addr: IpAddr,
secret: &[u8; 16],
validity: Range<Serial>,
) -> Result<(), CookieError> {
use core::hash::Hasher;
use siphasher::sip::SipHasher24;
if self.version != 1
|| self.hash.len() != 8
|| !validity.contains(&self.timestamp)
{
return Err(CookieError);
}
let mut hasher = SipHasher24::new_with_key(secret);
hasher.write(&self.as_bytes()[..16]);
match addr {
IpAddr::V4(addr) => hasher.write(&addr.octets()),
IpAddr::V6(addr) => hasher.write(&addr.octets()),
}
if self.hash == hasher.finish().to_le_bytes() {
Ok(())
} else {
Err(CookieError)
}
}
}
#[cfg(feature = "alloc")]
impl Clone for alloc::boxed::Box<Cookie> {
fn clone(&self) -> Self {
(*self).unsized_copy_into()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CookieError;
impl fmt::Display for CookieError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("A DNS cookie could not be verified")
}
}