use crate::{Keypair, PublicKey};
use bytes::{Bytes, BytesMut};
use ed25519_dalek::{Signature, SignatureError};
use self_cell::self_cell;
use simple_dns::{
rdata::{RData, A, AAAA, HTTPS, SVCB, TXT},
Name, Packet, ResourceRecord, SimpleDnsError, CLASS,
};
use std::{
char,
fmt::{self, Debug, Display, Formatter},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use serde::{Deserialize, Serialize};
use ntimestamp::Timestamp;
#[derive(Debug, Clone, Default)]
pub struct SignedPacketBuilder {
records: Vec<ResourceRecord<'static>>,
timestamp: Option<Timestamp>,
}
impl SignedPacketBuilder {
pub fn record(mut self, record: ResourceRecord<'_>) -> Self {
self.records.push(record.into_owned());
self
}
pub fn rdata(self, name: Name<'_>, rdata: RData, ttl: u32) -> Self {
self.record(ResourceRecord::new(name.to_owned(), CLASS::IN, ttl, rdata))
}
pub fn a(self, name: Name<'_>, address: Ipv4Addr, ttl: u32) -> Self {
self.rdata(
name,
RData::A(A {
address: address.into(),
}),
ttl,
)
}
pub fn aaaa(self, name: Name<'_>, address: Ipv6Addr, ttl: u32) -> Self {
self.rdata(
name,
RData::AAAA(AAAA {
address: address.into(),
}),
ttl,
)
}
pub fn address(self, name: Name<'_>, address: IpAddr, ttl: u32) -> Self {
match address {
IpAddr::V4(addr) => self.a(name, addr, ttl),
IpAddr::V6(addr) => self.aaaa(name, addr, ttl),
}
}
pub fn cname(self, name: Name<'_>, cname: Name<'_>, ttl: u32) -> Self {
self.rdata(name, RData::CNAME(cname.into()), ttl)
}
pub fn txt(self, name: Name<'_>, text: TXT<'_>, ttl: u32) -> Self {
self.rdata(name, RData::TXT(text), ttl)
}
pub fn https(self, name: Name<'_>, svcb: SVCB, ttl: u32) -> Self {
self.rdata(name, RData::HTTPS(HTTPS(svcb)), ttl)
}
pub fn svcb(self, name: Name<'_>, svcb: SVCB, ttl: u32) -> Self {
self.rdata(name, RData::SVCB(svcb), ttl)
}
pub fn timestamp(mut self, timestamp: Timestamp) -> Self {
self.timestamp = Some(timestamp);
self
}
pub fn build(self, keypair: &Keypair) -> Result<SignedPacket, SignedPacketBuildError> {
self.sign(keypair)
}
pub fn sign(self, keypair: &Keypair) -> Result<SignedPacket, SignedPacketBuildError> {
SignedPacket::new(
keypair,
&self.records,
self.timestamp.unwrap_or(Timestamp::now()),
)
}
}
const DOT: char = '.';
self_cell!(
struct Inner {
owner: Bytes,
#[covariant]
dependent: Packet,
}
impl{PartialEq, Eq}
);
impl Inner {
fn try_from_parts(
public_key: &PublicKey,
signature: &Signature,
timestamp: u64,
encoded_packet: &[u8],
) -> Result<Self, SimpleDnsError> {
let mut bytes = BytesMut::with_capacity(encoded_packet.len() + 104);
bytes.extend_from_slice(public_key.as_bytes());
bytes.extend_from_slice(&signature.to_bytes());
bytes.extend_from_slice(×tamp.to_be_bytes());
bytes.extend_from_slice(encoded_packet);
Self::try_new(bytes.into(), |bytes| Packet::parse(&bytes[104..]))
}
fn try_from_bytes(bytes: Bytes) -> Result<Self, SimpleDnsError> {
Inner::try_new(bytes, |bytes| Packet::parse(&bytes[104..]))
}
}
#[derive(PartialEq, Eq)]
pub struct SignedPacket {
inner: Inner,
last_seen: Timestamp,
}
impl SignedPacket {
pub const MAX_BYTES: u64 = 1104;
pub fn builder() -> SignedPacketBuilder {
SignedPacketBuilder::default()
}
pub fn new(
keypair: &Keypair,
answers: &[ResourceRecord<'_>],
timestamp: Timestamp,
) -> Result<SignedPacket, SignedPacketBuildError> {
let mut packet = Packet::new_reply(0);
let origin = keypair.public_key().to_z32();
let normalized_names: Vec<String> = answers
.iter()
.map(|answer| normalize_name(&origin, answer.name.to_string()))
.collect();
answers.iter().enumerate().for_each(|(index, answer)| {
packet.answers.push(ResourceRecord::new(
Name::new_unchecked(&normalized_names[index]).to_owned(),
answer.class,
answer.ttl,
answer.rdata.clone(),
))
});
let encoded_packet = packet.build_bytes_vec_compressed()?;
if encoded_packet.len() > 1000 {
return Err(SignedPacketBuildError::PacketTooLarge(encoded_packet.len()));
}
let signature = keypair.sign(&signable(timestamp.into(), &encoded_packet));
Ok(SignedPacket {
inner: Inner::try_from_parts(
&keypair.public_key(),
&signature,
timestamp.into(),
&encoded_packet,
)
.expect("SignedPacket::new() try_from_parts should not fail"),
last_seen: Timestamp::now(),
})
}
pub fn from_relay_payload(
public_key: &PublicKey,
payload: &Bytes,
) -> Result<SignedPacket, SignedPacketVerifyError> {
let mut bytes = BytesMut::with_capacity(payload.len() + 32);
bytes.extend_from_slice(public_key.as_bytes());
bytes.extend_from_slice(payload);
SignedPacket::from_bytes(&bytes.into())
}
pub fn as_bytes(&self) -> &Bytes {
self.inner.borrow_owner()
}
pub fn serialize(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(SignedPacket::MAX_BYTES as usize);
bytes.extend_from_slice(&self.last_seen.to_bytes());
bytes.extend_from_slice(self.as_bytes());
bytes
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, SimpleDnsError> {
let mut last_seen = Timestamp::try_from(&bytes[0..8]).unwrap_or_default();
if last_seen > (Timestamp::now() + 60_000_000) {
last_seen = Timestamp::from(0)
}
Ok(SignedPacket {
inner: Inner::try_from_bytes(bytes[8..].to_owned().into())?,
last_seen,
})
}
pub fn to_relay_payload(&self) -> Bytes {
self.inner.borrow_owner().slice(32..)
}
pub fn public_key(&self) -> PublicKey {
PublicKey::try_from(&self.inner.borrow_owner()[0..32]).expect("SignedPacket::public_key()")
}
pub fn signature(&self) -> Signature {
Signature::try_from(&self.inner.borrow_owner()[32..96]).expect("SignedPacket::signature()")
}
pub fn timestamp(&self) -> Timestamp {
let bytes = self.inner.borrow_owner();
let slice: [u8; 8] = bytes[96..104]
.try_into()
.expect("SignedPacket::timestamp()");
u64::from_be_bytes(slice).into()
}
pub fn encoded_packet(&self) -> Bytes {
self.inner.borrow_owner().slice(104..)
}
pub(crate) fn packet(&self) -> &Packet<'_> {
self.inner.borrow_dependent()
}
pub fn last_seen(&self) -> &Timestamp {
&self.last_seen
}
pub fn set_last_seen(&mut self, last_seen: &Timestamp) {
self.last_seen = last_seen.into();
}
pub fn refresh(&mut self) {
self.last_seen = Timestamp::now();
}
pub fn more_recent_than(&self, other: &SignedPacket) -> bool {
if self.timestamp() == other.timestamp() {
self.encoded_packet() > other.encoded_packet()
} else {
self.timestamp() > other.timestamp()
}
}
pub fn is_same_as(&self, other: &SignedPacket) -> bool {
self.as_bytes() == other.as_bytes()
}
pub fn resource_records(&self, name: &str) -> impl Iterator<Item = &ResourceRecord<'_>> {
let origin = self.public_key().to_z32();
let normalized_name = normalize_name(&origin, name.to_string());
let is_wildcard = normalized_name.starts_with('*');
self.all_resource_records().filter(move |rr| {
if is_wildcard {
rr.name
.to_string()
.strip_suffix(&normalized_name[1..])
.map(|m| !m.contains('.'))
.unwrap_or_default()
} else {
rr.name.to_string() == normalized_name
}
})
}
pub fn fresh_resource_records(&self, name: &str) -> impl Iterator<Item = &ResourceRecord<'_>> {
self.resource_records(name)
.filter(move |rr| rr.ttl > self.elapsed())
}
pub fn all_resource_records(&self) -> impl Iterator<Item = &ResourceRecord<'_>> {
self.packet().answers.iter()
}
pub fn expires_in(&self, min: u32, max: u32) -> u32 {
match self.ttl(min, max).overflowing_sub(self.elapsed()) {
(_, true) => 0,
(ttl, false) => ttl,
}
}
pub fn ttl(&self, min: u32, max: u32) -> u32 {
self.packet()
.answers
.iter()
.map(|rr| rr.ttl)
.min()
.map_or(min, |v| v.clamp(min, max))
}
pub fn is_expired(&self, min: u32, max: u32) -> bool {
self.expires_in(min, max) == 0
}
pub fn elapsed(&self) -> u32 {
((Timestamp::now().as_u64() - self.last_seen.as_u64()) / 1_000_000) as u32
}
fn from_bytes(bytes: &Bytes) -> Result<SignedPacket, SignedPacketVerifyError> {
if bytes.len() < 104 {
return Err(SignedPacketVerifyError::InvalidSignedPacketBytesLength(
bytes.len(),
));
}
if (bytes.len() as u64) > SignedPacket::MAX_BYTES {
return Err(SignedPacketVerifyError::PacketTooLarge(bytes.len()));
}
let public_key = PublicKey::try_from(&bytes[..32])?;
let signature = Signature::from_bytes(
bytes[32..96]
.try_into()
.expect("SignedPacket::from_bytes(); Signature from 64 bytes"),
);
let timestamp = u64::from_be_bytes(
bytes[96..104]
.try_into()
.expect("SignedPacket::from_bytes(); Timestamp from 8 bytes"),
);
let encoded_packet = &bytes[104..];
public_key.verify(&signable(timestamp, encoded_packet), &signature)?;
Ok(SignedPacket {
inner: Inner::try_from_bytes(bytes.to_owned())?,
last_seen: Timestamp::now(),
})
}
fn from_bytes_unchecked(bytes: &Bytes, last_seen: impl Into<Timestamp>) -> SignedPacket {
SignedPacket {
inner: Inner::try_from_bytes(bytes.to_owned())
.expect("called SignedPacket::from_bytes_unchecked on invalid bytes"),
last_seen: last_seen.into(),
}
}
}
fn signable(timestamp: u64, v: &[u8]) -> Box<[u8]> {
let mut signable = format!("3:seqi{}e1:v{}:", timestamp, v.len()).into_bytes();
signable.extend(v);
signable.into()
}
fn normalize_name(origin: &str, name: String) -> String {
let name = if name.ends_with(DOT) {
name[..name.len() - 1].to_string()
} else {
name
};
let parts: Vec<&str> = name.split('.').collect();
let last = *parts.last().unwrap_or(&"");
if last == origin {
return name.to_string();
}
if last == "@" || last.is_empty() {
return origin.to_string();
}
format!("{name}.{origin}")
}
#[cfg(dht)]
use mainline::MutableItem;
use super::keys::PublicKeyError;
#[cfg(dht)]
impl From<&SignedPacket> for MutableItem {
fn from(s: &SignedPacket) -> Self {
Self::new_signed_unchecked(
s.public_key().to_bytes(),
s.signature().to_bytes(),
s.inner.borrow_owner()[104..].into(),
s.timestamp().as_u64() as i64,
None,
)
}
}
#[cfg(dht)]
impl TryFrom<&MutableItem> for SignedPacket {
type Error = SignedPacketVerifyError;
fn try_from(i: &MutableItem) -> Result<Self, SignedPacketVerifyError> {
let public_key = PublicKey::try_from(i.key())?;
let seq = i.seq() as u64;
let signature: Signature = i.signature().into();
Ok(Self {
inner: Inner::try_from_parts(&public_key, &signature, seq, i.value())?,
last_seen: Timestamp::now(),
})
}
}
#[cfg(dht)]
impl TryFrom<MutableItem> for SignedPacket {
type Error = SignedPacketVerifyError;
fn try_from(i: MutableItem) -> Result<Self, SignedPacketVerifyError> {
SignedPacket::try_from(&i)
}
}
impl AsRef<[u8]> for SignedPacket {
fn as_ref(&self) -> &[u8] {
self.inner.borrow_owner()
}
}
impl Clone for SignedPacket {
fn clone(&self) -> Self {
Self::from_bytes_unchecked(self.as_bytes(), self.last_seen)
}
}
impl Debug for SignedPacket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SignedPacket")
.field("timestamp", &self.timestamp())
.field("last_seen", &self.last_seen())
.field("public_key", &self.public_key())
.field("signature", &self.signature())
.field("packet", &self.packet())
.finish()
}
}
impl Display for SignedPacket {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"SignedPacket ({}):\n last_seen: {} seconds ago\n timestamp: {} {},\n signature: {}\n records:\n",
&self.public_key(),
&self.elapsed(),
&self.timestamp(),
&self.timestamp().format_http_date(),
&self.signature(),
)?;
for answer in &self.packet().answers {
writeln!(
f,
" {} IN {} {}",
&answer.name,
&answer.ttl,
match &answer.rdata {
RData::A(A { address }) => format!("A {}", Ipv4Addr::from(*address)),
RData::AAAA(AAAA { address }) => format!("AAAA {}", Ipv6Addr::from(*address)),
#[allow(clippy::to_string_in_format_args)]
RData::CNAME(name) => format!("CNAME {}", name.to_string()),
RData::TXT(txt) => {
format!(
"TXT \"{}\"",
txt.clone()
.try_into()
.unwrap_or("__INVALID_TXT_VALUE_".to_string())
)
}
_ => format!("{:?}", answer.rdata),
}
)?;
}
writeln!(f)?;
Ok(())
}
}
impl Serialize for SignedPacket {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.serialize().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for SignedPacket {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes: Vec<u8> = Deserialize::deserialize(deserializer)?;
SignedPacket::deserialize(&bytes).map_err(serde::de::Error::custom)
}
}
#[derive(thiserror::Error, Debug)]
pub enum SignedPacketVerifyError {
#[error(transparent)]
SignatureError(#[from] SignatureError),
#[error(transparent)]
DnsError(#[from] simple_dns::SimpleDnsError),
#[error("Invalid SignedPacket bytes length, expected at least 104 bytes but got: {0}")]
InvalidSignedPacketBytesLength(usize),
#[error("DNS Packet is too large, expected max 1000 bytes but got: {0}")]
PacketTooLarge(usize),
#[error(transparent)]
PublicKeyError(#[from] PublicKeyError),
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum SignedPacketBuildError {
#[error("DNS Packet is too large, expected max 1000 bytes but got: {0}")]
PacketTooLarge(usize),
#[error("Failed to write encoded DNS packet due to I/O error: {0}")]
FailedToWrite(#[from] SimpleDnsError),
}
#[cfg(test)]
mod tests {
use simple_dns::rdata::CNAME;
use super::*;
use crate::{DEFAULT_MAXIMUM_TTL, DEFAULT_MINIMUM_TTL};
#[test]
fn custom_timestamp() {
let timestamp = Timestamp::from(42);
let signed_packet = SignedPacket::builder()
.timestamp(timestamp)
.sign(&Keypair::random())
.unwrap();
assert_eq!(signed_packet.timestamp(), timestamp);
}
#[test]
fn normalize_names() {
let origin = "ed4mn3aoazuf1ahpy9rz1nyswhukbj5483ryefwkue7fbp3egkzo";
assert_eq!(normalize_name(origin, ".".to_string()), origin);
assert_eq!(normalize_name(origin, "@".to_string()), origin);
assert_eq!(normalize_name(origin, "@.".to_string()), origin);
assert_eq!(normalize_name(origin, origin.to_string()), origin);
assert_eq!(
normalize_name(origin, "_derp_region.irorh".to_string()),
format!("_derp_region.irorh.{origin}")
);
assert_eq!(
normalize_name(origin, format!("_derp_region.irorh.{origin}")),
format!("_derp_region.irorh.{origin}")
);
assert_eq!(
normalize_name(origin, format!("_derp_region.irorh.{origin}.")),
format!("_derp_region.irorh.{origin}")
);
}
#[test]
fn sign_verify() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.address(
"_derp_region.iroh.".try_into().unwrap(),
"1.1.1.1".parse().unwrap(),
30,
)
.sign(&keypair)
.unwrap();
assert!(SignedPacket::from_relay_payload(
&signed_packet.public_key(),
&signed_packet.to_relay_payload()
)
.is_ok());
}
#[test]
fn from_too_large_bytes() {
let keypair = Keypair::random();
let bytes = vec![0; 1073];
let error = SignedPacket::from_relay_payload(&keypair.public_key(), &bytes.into());
assert!(error.is_err());
}
#[test]
fn from_too_large_packet() {
let keypair = Keypair::random();
let mut builder = SignedPacket::builder();
for _ in 0..100 {
builder = builder.address(
"_derp_region.iroh.".try_into().unwrap(),
"1.1.1.1".parse().unwrap(),
30,
);
}
let error = builder.sign(&keypair);
assert!(error.is_err());
}
#[test]
fn resource_records_iterator() {
let keypair = Keypair::random();
let target = ResourceRecord::new(
Name::new("_derp_region.iroh.").unwrap(),
simple_dns::CLASS::IN,
30,
RData::A(A {
address: Ipv4Addr::new(1, 1, 1, 1).into(),
}),
);
let signed_packet = SignedPacket::builder()
.record(target.clone())
.address(
"something-else".try_into().unwrap(),
"1.1.1.1".parse().unwrap(),
30,
)
.sign(&keypair)
.unwrap();
let iter = signed_packet.resource_records("_derp_region.iroh");
assert_eq!(iter.count(), 1);
for record in signed_packet.resource_records("_derp_region.iroh") {
assert_eq!(record.rdata, target.rdata);
}
}
#[cfg(dht)]
#[test]
fn to_mutable() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.address(
"_derp_region.iroh.".try_into().unwrap(),
"1.1.1.1".parse().unwrap(),
30,
)
.sign(&keypair)
.unwrap();
let item: MutableItem = (&signed_packet).into();
let seq = signed_packet.timestamp().as_u64() as i64;
let expected = MutableItem::new(
keypair.secret_key().into(),
&signed_packet.packet().build_bytes_vec_compressed().unwrap(),
seq,
None,
);
assert_eq!(item, expected);
}
#[test]
fn compressed_names() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.cname(".".try_into().unwrap(), "foobar".try_into().unwrap(), 30)
.cname(".".try_into().unwrap(), "foobar".try_into().unwrap(), 30)
.sign(&keypair)
.unwrap();
assert_eq!(
signed_packet
.resource_records("@")
.map(|r| r.rdata.clone())
.collect::<Vec<_>>(),
vec![
RData::CNAME(CNAME("foobar".try_into().unwrap())),
RData::CNAME(CNAME("foobar".try_into().unwrap()))
]
)
}
#[test]
fn to_bytes_from_bytes() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 30)
.sign(&keypair)
.unwrap();
let bytes = signed_packet.as_bytes();
let from_bytes = SignedPacket::from_bytes(bytes).unwrap();
assert_eq!(signed_packet.as_bytes(), from_bytes.as_bytes());
let from_bytes2 = SignedPacket::from_bytes_unchecked(bytes, signed_packet.last_seen);
assert_eq!(signed_packet.as_bytes(), from_bytes2.as_bytes());
let public_key = keypair.public_key();
let payload = signed_packet.to_relay_payload();
let from_relay_payload = SignedPacket::from_relay_payload(&public_key, &payload).unwrap();
assert_eq!(signed_packet.as_bytes(), from_relay_payload.as_bytes());
}
#[test]
fn clone() {
let keypair = Keypair::random();
let signed = SignedPacket::builder()
.txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 30)
.sign(&keypair)
.unwrap();
let cloned = signed.clone();
assert_eq!(cloned.as_bytes(), signed.as_bytes());
}
#[test]
fn expires_in_minimum_ttl() {
let keypair = Keypair::random();
let mut signed_packet = SignedPacket::builder()
.txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 10)
.sign(&keypair)
.unwrap();
signed_packet.last_seen -= 20 * 1_000_000_u64;
assert!(
signed_packet.expires_in(30, u32::MAX) > 0,
"input minimum_ttl is 30 so ttl = 30"
);
assert!(
signed_packet.expires_in(0, u32::MAX) == 0,
"input minimum_ttl is 0 so ttl = 10 (smallest in resource records)"
);
}
#[test]
fn expires_in_maximum_ttl() {
let keypair = Keypair::random();
let mut signed_packet = SignedPacket::builder()
.txt(
"_foo".try_into().unwrap(),
"hello".try_into().unwrap(),
3 * DEFAULT_MAXIMUM_TTL,
)
.sign(&keypair)
.unwrap();
signed_packet.last_seen -= 2 * (DEFAULT_MAXIMUM_TTL as u64) * 1_000_000;
assert!(
signed_packet.expires_in(0, DEFAULT_MAXIMUM_TTL) == 0,
"input maximum_ttl is the dfeault 86400 so maximum ttl = 86400"
);
assert!(
signed_packet.expires_in(0, 7 * DEFAULT_MAXIMUM_TTL) > 0,
"input maximum_ttl is 7 * 86400 so ttl = 3 * 86400 (smallest in resource records)"
);
}
#[test]
fn fresh_resource_records() {
let keypair = Keypair::random();
let mut signed_packet = SignedPacket::builder()
.txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 30)
.txt("_foo".try_into().unwrap(), "world".try_into().unwrap(), 60)
.txt("_bar".try_into().unwrap(), "world".try_into().unwrap(), 60)
.sign(&keypair)
.unwrap();
signed_packet.last_seen -= 30 * 1_000_000;
assert_eq!(signed_packet.fresh_resource_records("_foo").count(), 1);
}
#[test]
fn ttl_empty() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder().sign(&keypair).unwrap();
assert_eq!(
signed_packet.ttl(DEFAULT_MINIMUM_TTL, DEFAULT_MAXIMUM_TTL),
300
);
}
#[test]
fn ttl_with_records_less_than_minimum() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.txt(
"_foo".try_into().unwrap(),
"hello".try_into().unwrap(),
DEFAULT_MINIMUM_TTL / 2,
)
.txt(
"_foo".try_into().unwrap(),
"world".try_into().unwrap(),
DEFAULT_MINIMUM_TTL / 4,
)
.sign(&keypair)
.unwrap();
assert_eq!(
signed_packet.ttl(DEFAULT_MINIMUM_TTL, DEFAULT_MAXIMUM_TTL),
DEFAULT_MINIMUM_TTL
);
assert_eq!(
signed_packet.ttl(0, DEFAULT_MAXIMUM_TTL),
DEFAULT_MINIMUM_TTL / 4
);
}
#[test]
fn ttl_with_records_more_than_maximum() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.txt(
"_foo".try_into().unwrap(),
"hello".try_into().unwrap(),
DEFAULT_MAXIMUM_TTL * 2,
)
.txt(
"_foo".try_into().unwrap(),
"world".try_into().unwrap(),
DEFAULT_MAXIMUM_TTL * 4,
)
.sign(&keypair)
.unwrap();
assert_eq!(
signed_packet.ttl(DEFAULT_MINIMUM_TTL, DEFAULT_MAXIMUM_TTL),
DEFAULT_MAXIMUM_TTL
);
assert_eq!(
signed_packet.ttl(0, DEFAULT_MAXIMUM_TTL * 8),
DEFAULT_MAXIMUM_TTL * 2
);
}
#[test]
fn serde() {
use postcard::{from_bytes, to_allocvec};
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.address(
"_derp_region.iroh.".try_into().unwrap(),
"1.1.1.1".parse().unwrap(),
30,
)
.sign(&keypair)
.unwrap();
let serialized = to_allocvec(&signed_packet).unwrap();
let deserialized: SignedPacket = from_bytes(&serialized).unwrap();
assert_eq!(deserialized, signed_packet);
{
let mut bytes = vec![];
bytes.extend_from_slice(&[210, 1]);
bytes.extend_from_slice(&signed_packet.last_seen().as_u64().to_le_bytes());
bytes.extend_from_slice(signed_packet.as_bytes());
let deserialized: SignedPacket = from_bytes(&bytes).unwrap();
assert_eq!(deserialized.as_bytes(), signed_packet.as_bytes());
assert_eq!(deserialized.last_seen(), &Timestamp::from(0));
}
}
#[test]
fn wildcards() {
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.txt("bar.foo.".try_into().unwrap(), "_".try_into().unwrap(), 30)
.txt("x.bar.foo".try_into().unwrap(), "_".try_into().unwrap(), 30)
.txt("foo".try_into().unwrap(), "_".try_into().unwrap(), 60)
.sign(&keypair)
.unwrap();
assert_eq!(signed_packet.fresh_resource_records("*.foo.").count(), 1);
}
}