Skip to main content

pkarr_client/
signed_packet.rs

1//! Signed DNS packet
2
3use crate::{Keypair, PublicKey};
4use bytes::{Bytes, BytesMut};
5use ed25519_dalek::{Signature, SignatureError};
6use self_cell::self_cell;
7use simple_dns::{
8    rdata::{RData, A, AAAA, HTTPS, SVCB, TXT},
9    Name, Packet, ResourceRecord, SimpleDnsError, CLASS,
10};
11use std::{
12    char,
13    fmt::{self, Debug, Display, Formatter},
14    net::{IpAddr, Ipv4Addr, Ipv6Addr},
15};
16
17use serde::{Deserialize, Serialize};
18
19use ntimestamp::Timestamp;
20
21#[derive(Debug, Clone, Default)]
22/// A builder for [SignedPacket] with many convenient methods,
23/// see [SignedPacket::builder] documentation for examples of how to use this builder.
24pub struct SignedPacketBuilder {
25    records: Vec<ResourceRecord<'static>>,
26    timestamp: Option<Timestamp>,
27}
28
29impl SignedPacketBuilder {
30    /// Insert a [ResourceRecord]
31    pub fn record(mut self, record: ResourceRecord<'_>) -> Self {
32        self.records.push(record.into_owned());
33        self
34    }
35
36    /// Insert any type of [RData]
37    ///
38    /// You can set the name to `.` to point or the Apex
39    /// (the public key, of the keypair used in [Self::sign])
40    pub fn rdata(self, name: Name<'_>, rdata: RData, ttl: u32) -> Self {
41        self.record(ResourceRecord::new(name.to_owned(), CLASS::IN, ttl, rdata))
42    }
43
44    /// Insert an `A` record.
45    ///
46    /// You can set the name to `.` to point or the Apex
47    /// (the public key, of the keypair used in [Self::sign])
48    pub fn a(self, name: Name<'_>, address: Ipv4Addr, ttl: u32) -> Self {
49        self.rdata(
50            name,
51            RData::A(A {
52                address: address.into(),
53            }),
54            ttl,
55        )
56    }
57
58    /// Insert an `AAAA` record.
59    ///
60    /// You can set the name to `.` to point or the Apex
61    /// (the public key, of the keypair used in [Self::sign])
62    pub fn aaaa(self, name: Name<'_>, address: Ipv6Addr, ttl: u32) -> Self {
63        self.rdata(
64            name,
65            RData::AAAA(AAAA {
66                address: address.into(),
67            }),
68            ttl,
69        )
70    }
71
72    /// Insert an `A` or `AAAA` record.
73    ///
74    /// You can set the name to `.` to point or the Apex
75    /// (the public key, of the keypair used in [Self::sign])
76    pub fn address(self, name: Name<'_>, address: IpAddr, ttl: u32) -> Self {
77        match address {
78            IpAddr::V4(addr) => self.a(name, addr, ttl),
79            IpAddr::V6(addr) => self.aaaa(name, addr, ttl),
80        }
81    }
82
83    /// Insert a `CNAME` record.
84    ///
85    /// You can set the name to `.` to point or the Apex
86    /// (the public key, of the keypair used in [Self::sign])
87    pub fn cname(self, name: Name<'_>, cname: Name<'_>, ttl: u32) -> Self {
88        self.rdata(name, RData::CNAME(cname.into()), ttl)
89    }
90
91    /// Insert a `TXT` record.
92    ///
93    /// You can set the name to `.` to point or the Apex
94    /// (the public key, of the keypair used in [Self::sign])
95    pub fn txt(self, name: Name<'_>, text: TXT<'_>, ttl: u32) -> Self {
96        self.rdata(name, RData::TXT(text), ttl)
97    }
98
99    /// Insert an `HTTPS` record
100    ///
101    /// You can set the name to `.` to point or the Apex
102    /// (the public key, of the keypair used in [Self::sign])
103    pub fn https(self, name: Name<'_>, svcb: SVCB, ttl: u32) -> Self {
104        self.rdata(name, RData::HTTPS(HTTPS(svcb)), ttl)
105    }
106
107    /// Insert an `SVCB record
108    ///
109    /// You can set the name to `.` to point or the Apex
110    /// (the public key, of the keypair used in [Self::sign])
111    pub fn svcb(self, name: Name<'_>, svcb: SVCB, ttl: u32) -> Self {
112        self.rdata(name, RData::SVCB(svcb), ttl)
113    }
114
115    /// Add a custom [Timestamp].
116    pub fn timestamp(mut self, timestamp: Timestamp) -> Self {
117        self.timestamp = Some(timestamp);
118
119        self
120    }
121
122    /// Alias to [Self::sign]
123    pub fn build(self, keypair: &Keypair) -> Result<SignedPacket, SignedPacketBuildError> {
124        self.sign(keypair)
125    }
126
127    /// Create a [Packet] from the [ResourceRecord]s inserted so far and sign
128    /// it with the given [Keypair].
129    ///
130    /// Read more about how names will be normalized in [SignedPacket::new].
131    pub fn sign(self, keypair: &Keypair) -> Result<SignedPacket, SignedPacketBuildError> {
132        SignedPacket::new(
133            keypair,
134            &self.records,
135            self.timestamp.unwrap_or(Timestamp::now()),
136        )
137    }
138}
139
140const DOT: char = '.';
141
142self_cell!(
143    struct Inner {
144        owner: Bytes,
145
146        #[covariant]
147        dependent: Packet,
148    }
149
150    impl{PartialEq, Eq}
151);
152
153impl Inner {
154    fn try_from_parts(
155        public_key: &PublicKey,
156        signature: &Signature,
157        timestamp: u64,
158        encoded_packet: &[u8],
159    ) -> Result<Self, SimpleDnsError> {
160        // Create the inner bytes from <public_key><signature>timestamp><v>
161        let mut bytes = BytesMut::with_capacity(encoded_packet.len() + 104);
162
163        bytes.extend_from_slice(public_key.as_bytes());
164        bytes.extend_from_slice(&signature.to_bytes());
165        bytes.extend_from_slice(&timestamp.to_be_bytes());
166        bytes.extend_from_slice(encoded_packet);
167
168        Self::try_new(bytes.into(), |bytes| Packet::parse(&bytes[104..]))
169    }
170
171    fn try_from_bytes(bytes: Bytes) -> Result<Self, SimpleDnsError> {
172        Inner::try_new(bytes, |bytes| Packet::parse(&bytes[104..]))
173    }
174}
175
176#[derive(PartialEq, Eq)]
177/// Signed DNS packet
178pub struct SignedPacket {
179    inner: Inner,
180    last_seen: Timestamp,
181}
182
183impl SignedPacket {
184    /// Maximum legal size of the binary representation of a SignedPacket.
185    ///
186    /// Calculated from the maximum size of a value in Mainline DHT's mutable values; 1000 bytes,
187    /// plus the number of bytes in a Signature (64), and a [Timestamp] (8) and a public key (32).
188    pub const MAX_BYTES: u64 = 1104;
189
190    /// Create a [SignedPacket] using a builder.
191    ///
192    /// ```
193    ///  use pkarr::{SignedPacket, Keypair, dns::rdata::SVCB};
194    ///  
195    ///  let keypair = Keypair::random();
196    ///
197    ///  let signed_packet = SignedPacket::builder()
198    ///     // A record
199    ///     .address(
200    ///         "_derp_region.iroh.".try_into().unwrap(),
201    ///         "1.1.1.1".parse().unwrap(),
202    ///         30,
203    ///     )
204    ///     // AAAA record
205    ///     .address(
206    ///         "_derp_region.iroh.".try_into().unwrap(),
207    ///         "3002:0bd6:0000:0000:0000:ee00:0033:6778".parse().unwrap(),
208    ///         30,
209    ///     )
210    ///     // CNAME record
211    ///     .cname(
212    ///         "subdomain.".try_into().unwrap(),
213    ///         "example.com".try_into().unwrap(),
214    ///         30
215    ///     )
216    ///     // TXT record
217    ///     .txt(
218    ///         "_proto".try_into().unwrap(),
219    ///         "foo=bar".try_into().unwrap(),
220    ///         30
221    ///     )
222    ///     // HTTPS record
223    ///     .https(
224    ///         // You can make a record at the Apex (at the same TLD as your public key)
225    ///         ".".try_into().unwrap(),
226    ///         SVCB::new(0, "https.example.com".try_into().unwrap()),
227    ///         3600,
228    ///     )
229    ///     // SVCB record
230    ///     .https(
231    ///         ".".try_into().unwrap(),
232    ///         SVCB::new(0, "https.example.com".try_into().unwrap()),
233    ///         3600,
234    ///     )
235    ///     .sign(&keypair)
236    ///     .unwrap();
237    /// ```
238    pub fn builder() -> SignedPacketBuilder {
239        SignedPacketBuilder::default()
240    }
241
242    /// Creates a new [SignedPacket] from a [Keypair] and [ResourceRecord]s as the `answers`
243    /// section of a DNS [Packet], and a [Timestamp].
244    ///
245    /// It will also normalize the names of the [ResourceRecord]s to be relative to the origin,
246    /// which would be the z-base32 encoded [PublicKey] of the [Keypair] used to sign the Packet.
247    ///
248    /// If any name is empty or just a `.`, it will be normalized to the public key of the keypair.
249    pub fn new(
250        keypair: &Keypair,
251        answers: &[ResourceRecord<'_>],
252        timestamp: Timestamp,
253    ) -> Result<SignedPacket, SignedPacketBuildError> {
254        let mut packet = Packet::new_reply(0);
255
256        let origin = keypair.public_key().to_z32();
257
258        // Normalize names to the origin TLD
259        let normalized_names: Vec<String> = answers
260            .iter()
261            .map(|answer| normalize_name(&origin, answer.name.to_string()))
262            .collect();
263
264        answers.iter().enumerate().for_each(|(index, answer)| {
265            packet.answers.push(ResourceRecord::new(
266                Name::new_unchecked(&normalized_names[index]).to_owned(),
267                answer.class,
268                answer.ttl,
269                answer.rdata.clone(),
270            ))
271        });
272
273        // Encode the packet as `v` and verify its length
274        let encoded_packet = packet.build_bytes_vec_compressed()?;
275
276        if encoded_packet.len() > 1000 {
277            return Err(SignedPacketBuildError::PacketTooLarge(encoded_packet.len()));
278        }
279
280        let signature = keypair.sign(&signable(timestamp.into(), &encoded_packet));
281
282        Ok(SignedPacket {
283            inner: Inner::try_from_parts(
284                &keypair.public_key(),
285                &signature,
286                timestamp.into(),
287                &encoded_packet,
288            )
289            .expect("SignedPacket::new() try_from_parts should not fail"),
290            last_seen: Timestamp::now(),
291        })
292    }
293
294    /// Creates a [SignedPacket] from a [PublicKey] and the [relays](https://pkarr.org/relays) payload.
295    pub fn from_relay_payload(
296        public_key: &PublicKey,
297        payload: &Bytes,
298    ) -> Result<SignedPacket, SignedPacketVerifyError> {
299        let mut bytes = BytesMut::with_capacity(payload.len() + 32);
300
301        bytes.extend_from_slice(public_key.as_bytes());
302        bytes.extend_from_slice(payload);
303
304        SignedPacket::from_bytes(&bytes.into())
305    }
306
307    // === Getters ===
308
309    /// Returns the serialized signed packet:
310    /// `<32 bytes public_key><64 bytes signature><8 bytes big-endian timestamp in microseconds><encoded DNS packet>`
311    pub fn as_bytes(&self) -> &Bytes {
312        self.inner.borrow_owner()
313    }
314
315    /// Returns a serialized representation of this [SignedPacket] including
316    /// the [SignedPacket::last_seen] timestamp followed by the returned value from [SignedPacket::as_bytes].
317    pub fn serialize(&self) -> Vec<u8> {
318        let mut bytes = Vec::with_capacity(SignedPacket::MAX_BYTES as usize);
319        bytes.extend_from_slice(&self.last_seen.to_bytes());
320        bytes.extend_from_slice(self.as_bytes());
321
322        bytes
323    }
324
325    /// Deserialize [SignedPacket] from a serialized version for persistent storage using
326    /// [SignedPacket::serialize].
327    ///
328    /// If deserializing the [SignedPacket::last_seen] failed, or is far in the future,
329    /// it will be unwrapped to default, i.e the UNIX_EPOCH.
330    ///
331    /// That is useful for backwards compatibility if you
332    /// ever stored the [SignedPacket::last_seen] as Little Endian in previous versions.
333    pub fn deserialize(bytes: &[u8]) -> Result<Self, SimpleDnsError> {
334        let mut last_seen = Timestamp::try_from(&bytes[0..8]).unwrap_or_default();
335
336        if last_seen > (Timestamp::now() + 60_000_000) {
337            last_seen = Timestamp::from(0)
338        }
339
340        Ok(SignedPacket {
341            inner: Inner::try_from_bytes(bytes[8..].to_owned().into())?,
342            last_seen,
343        })
344    }
345
346    /// Returns a slice of the serialized [SignedPacket] omitting the leading public_key,
347    /// to be sent as a request/response body to or from [relays](https://pkarr.org/relays).
348    pub fn to_relay_payload(&self) -> Bytes {
349        self.inner.borrow_owner().slice(32..)
350    }
351
352    /// Returns the [PublicKey] of the signer of this [SignedPacket]
353    pub fn public_key(&self) -> PublicKey {
354        PublicKey::try_from(&self.inner.borrow_owner()[0..32]).expect("SignedPacket::public_key()")
355    }
356
357    /// Returns the [Signature] of the the bencoded sequence number concatenated with the
358    /// encoded and compressed packet, as defined in [BEP_0044](https://www.bittorrent.org/beps/bep_0044.html)
359    pub fn signature(&self) -> Signature {
360        Signature::try_from(&self.inner.borrow_owner()[32..96]).expect("SignedPacket::signature()")
361    }
362
363    /// Returns the timestamp in microseconds since the [UNIX_EPOCH](std::time::UNIX_EPOCH).
364    ///
365    /// This timestamp is authored by the controller of the keypair,
366    /// and it is trusted as a way to order which packets where authored after which,
367    /// but it shouldn't be used for caching for example, instead, use [Self::last_seen]
368    /// which is set when you create a new packet.
369    pub fn timestamp(&self) -> Timestamp {
370        let bytes = self.inner.borrow_owner();
371        let slice: [u8; 8] = bytes[96..104]
372            .try_into()
373            .expect("SignedPacket::timestamp()");
374
375        u64::from_be_bytes(slice).into()
376    }
377
378    /// Returns the DNS [Packet] compressed and encoded.
379    pub fn encoded_packet(&self) -> Bytes {
380        self.inner.borrow_owner().slice(104..)
381    }
382
383    /// Return the DNS [Packet].
384    pub(crate) fn packet(&self) -> &Packet<'_> {
385        self.inner.borrow_dependent()
386    }
387
388    /// Unix last_seen time in microseconds
389    pub fn last_seen(&self) -> &Timestamp {
390        &self.last_seen
391    }
392
393    // === Setters ===
394
395    /// Set the [Self::last_seen] property
396    pub fn set_last_seen(&mut self, last_seen: &Timestamp) {
397        self.last_seen = last_seen.into();
398    }
399
400    // === Public Methods ===
401
402    /// Set the [Self::last_seen] to the current system time
403    pub fn refresh(&mut self) {
404        self.last_seen = Timestamp::now();
405    }
406
407    /// Return whether this [SignedPacket] is more recent than the given one.
408    /// If the timestamps are erqual, the one with the largest value is considered more recent.
409    /// Usefel for determining which packet contains the latest information from the Dht.
410    /// Assumes that both packets have the same [PublicKey], you shouldn't compare packets from
411    /// different keys.
412    pub fn more_recent_than(&self, other: &SignedPacket) -> bool {
413        // In the rare occasion of timestamp collision,
414        // we use the one with the largest value
415        if self.timestamp() == other.timestamp() {
416            self.encoded_packet() > other.encoded_packet()
417        } else {
418            self.timestamp() > other.timestamp()
419        }
420    }
421
422    /// Returns true if both packets have the same timestamp and packet,
423    /// and only differ in [Self::last_seen]
424    pub fn is_same_as(&self, other: &SignedPacket) -> bool {
425        self.as_bytes() == other.as_bytes()
426    }
427
428    /// Return and iterator over the [ResourceRecord]s in the Answers section of the DNS [Packet]
429    /// that matches the given name. The name will be normalized to the origin TLD of this packet.
430    ///
431    /// You can use `@` to filter the resource records at the Apex (the public key).
432    ///
433    /// Wildcards are also supported, so `*.foo.<key>` will match `bar.foo.<key>`.
434    pub fn resource_records(&self, name: &str) -> impl Iterator<Item = &ResourceRecord<'_>> {
435        let origin = self.public_key().to_z32();
436        let normalized_name = normalize_name(&origin, name.to_string());
437        let is_wildcard = normalized_name.starts_with('*');
438
439        self.all_resource_records().filter(move |rr| {
440            if is_wildcard {
441                rr.name
442                    .to_string()
443                    .strip_suffix(&normalized_name[1..])
444                    .map(|m| !m.contains('.'))
445                    .unwrap_or_default()
446            } else {
447                rr.name.to_string() == normalized_name
448            }
449        })
450    }
451
452    /// Similar to [resource_records](SignedPacket::resource_records), but filters out
453    /// expired records, according the the [Self::last_seen] value and each record's `ttl`.
454    pub fn fresh_resource_records(&self, name: &str) -> impl Iterator<Item = &ResourceRecord<'_>> {
455        self.resource_records(name)
456            .filter(move |rr| rr.ttl > self.elapsed())
457    }
458
459    /// Returns all resource records in this packet
460    pub fn all_resource_records(&self) -> impl Iterator<Item = &ResourceRecord<'_>> {
461        self.packet().answers.iter()
462    }
463
464    /// calculates the remaining seconds by comparing the [Self::ttl] (clamped by `min` and `max`)
465    /// to the [Self::last_seen].
466    ///
467    /// # Panics
468    ///
469    /// Panics if `min` < `max`
470    pub fn expires_in(&self, min: u32, max: u32) -> u32 {
471        match self.ttl(min, max).overflowing_sub(self.elapsed()) {
472            (_, true) => 0,
473            (ttl, false) => ttl,
474        }
475    }
476
477    /// Returns the smallest `ttl` in the resource records,
478    /// calmped with `min` and `max`.
479    ///
480    /// # Panics
481    ///
482    /// Panics if `min` < `max`
483    pub fn ttl(&self, min: u32, max: u32) -> u32 {
484        self.packet()
485            .answers
486            .iter()
487            .map(|rr| rr.ttl)
488            .min()
489            .map_or(min, |v| v.clamp(min, max))
490    }
491
492    /// Returns whether or not this packet is considered expired according to
493    /// a given `min` and `max` TTLs, by comparing it to this [SignedPacket::ttl].
494    pub fn is_expired(&self, min: u32, max: u32) -> bool {
495        self.expires_in(min, max) == 0
496    }
497
498    /// Time since the [Self::last_seen] in seconds
499    pub fn elapsed(&self) -> u32 {
500        ((Timestamp::now().as_u64() - self.last_seen.as_u64()) / 1_000_000) as u32
501    }
502
503    // === Private Methods ===
504
505    /// Creates a [Self] from the serialized representation:
506    /// `<32 bytes public_key><64 bytes signature><8 bytes big-endian timestamp in microseconds><encoded DNS packet>`
507    ///
508    /// Performs the following validations:
509    /// - Bytes minimum length
510    /// - Validates the PublicKey
511    /// - Verifies the Signature
512    /// - Validates the DNS packet encoding
513    ///
514    /// You can skip all these validations by using [Self::from_bytes_unchecked] instead.
515    ///
516    /// You can use [Self::from_relay_payload] instead if you are receiving a response from an HTTP relay.
517    fn from_bytes(bytes: &Bytes) -> Result<SignedPacket, SignedPacketVerifyError> {
518        if bytes.len() < 104 {
519            return Err(SignedPacketVerifyError::InvalidSignedPacketBytesLength(
520                bytes.len(),
521            ));
522        }
523        if (bytes.len() as u64) > SignedPacket::MAX_BYTES {
524            return Err(SignedPacketVerifyError::PacketTooLarge(bytes.len()));
525        }
526        let public_key = PublicKey::try_from(&bytes[..32])?;
527        let signature = Signature::from_bytes(
528            bytes[32..96]
529                .try_into()
530                .expect("SignedPacket::from_bytes(); Signature from 64 bytes"),
531        );
532        let timestamp = u64::from_be_bytes(
533            bytes[96..104]
534                .try_into()
535                .expect("SignedPacket::from_bytes(); Timestamp from 8 bytes"),
536        );
537
538        let encoded_packet = &bytes[104..];
539
540        public_key.verify(&signable(timestamp, encoded_packet), &signature)?;
541
542        Ok(SignedPacket {
543            inner: Inner::try_from_bytes(bytes.to_owned())?,
544            last_seen: Timestamp::now(),
545        })
546    }
547
548    /// Useful for cloning a [SignedPacket], or cerating one from a previously checked bytes,
549    /// like ones stored on disk or in a database.
550    fn from_bytes_unchecked(bytes: &Bytes, last_seen: impl Into<Timestamp>) -> SignedPacket {
551        SignedPacket {
552            inner: Inner::try_from_bytes(bytes.to_owned())
553                .expect("called SignedPacket::from_bytes_unchecked on invalid bytes"),
554            last_seen: last_seen.into(),
555        }
556    }
557}
558
559fn signable(timestamp: u64, v: &[u8]) -> Box<[u8]> {
560    let mut signable = format!("3:seqi{}e1:v{}:", timestamp, v.len()).into_bytes();
561    signable.extend(v);
562    signable.into()
563}
564
565fn normalize_name(origin: &str, name: String) -> String {
566    let name = if name.ends_with(DOT) {
567        name[..name.len() - 1].to_string()
568    } else {
569        name
570    };
571
572    let parts: Vec<&str> = name.split('.').collect();
573    let last = *parts.last().unwrap_or(&"");
574
575    if last == origin {
576        // Already normalized.
577        return name.to_string();
578    }
579
580    if last == "@" || last.is_empty() {
581        // Shorthand of origin
582        return origin.to_string();
583    }
584
585    format!("{name}.{origin}")
586}
587
588#[cfg(dht)]
589use crate::mainline::MutableItem;
590
591use super::keys::PublicKeyError;
592
593#[cfg(dht)]
594impl From<&SignedPacket> for MutableItem {
595    fn from(s: &SignedPacket) -> Self {
596        Self::new_signed_unchecked(
597            s.public_key().to_bytes(),
598            s.signature().to_bytes(),
599            // Packet
600            s.inner.borrow_owner()[104..].into(),
601            s.timestamp().as_u64() as i64,
602            None,
603        )
604    }
605}
606
607#[cfg(dht)]
608impl TryFrom<&MutableItem> for SignedPacket {
609    type Error = SignedPacketVerifyError;
610
611    fn try_from(i: &MutableItem) -> Result<Self, SignedPacketVerifyError> {
612        let public_key = PublicKey::try_from(i.key())?;
613        let seq = i.seq() as u64;
614        let signature: Signature = i.signature().into();
615
616        Ok(Self {
617            inner: Inner::try_from_parts(&public_key, &signature, seq, i.value())?,
618            last_seen: Timestamp::now(),
619        })
620    }
621}
622
623#[cfg(dht)]
624impl TryFrom<MutableItem> for SignedPacket {
625    type Error = SignedPacketVerifyError;
626
627    fn try_from(i: MutableItem) -> Result<Self, SignedPacketVerifyError> {
628        SignedPacket::try_from(&i)
629    }
630}
631
632impl AsRef<[u8]> for SignedPacket {
633    /// Returns the SignedPacket as a bytes slice with the format:
634    /// `<public_key><signature><6 bytes timestamp in microseconds><compressed dns packet>`
635    fn as_ref(&self) -> &[u8] {
636        self.inner.borrow_owner()
637    }
638}
639
640impl Clone for SignedPacket {
641    fn clone(&self) -> Self {
642        Self::from_bytes_unchecked(self.as_bytes(), self.last_seen)
643    }
644}
645
646impl Debug for SignedPacket {
647    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
648        f.debug_struct("SignedPacket")
649            .field("timestamp", &self.timestamp())
650            .field("last_seen", &self.last_seen())
651            .field("public_key", &self.public_key())
652            .field("signature", &self.signature())
653            .field("packet", &self.packet())
654            .finish()
655    }
656}
657
658impl Display for SignedPacket {
659    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
660        write!(
661            f,
662            "SignedPacket ({}):\n    last_seen: {} seconds ago\n    timestamp: {} {},\n    signature: {}\n    records:\n",
663            &self.public_key(),
664            &self.elapsed(),
665            &self.timestamp(),
666            &self.timestamp().format_http_date(),
667            &self.signature(),
668        )?;
669
670        for answer in &self.packet().answers {
671            writeln!(
672                f,
673                "        {}  IN  {}  {}",
674                &answer.name,
675                &answer.ttl,
676                match &answer.rdata {
677                    RData::A(A { address }) => format!("A  {}", Ipv4Addr::from(*address)),
678                    RData::AAAA(AAAA { address }) => format!("AAAA  {}", Ipv6Addr::from(*address)),
679                    #[allow(clippy::to_string_in_format_args)]
680                    RData::CNAME(name) => format!("CNAME  {}", name.to_string()),
681                    RData::TXT(txt) => {
682                        format!(
683                            "TXT  \"{}\"",
684                            txt.clone()
685                                .try_into()
686                                .unwrap_or("__INVALID_TXT_VALUE_".to_string())
687                        )
688                    }
689                    _ => format!("{:?}", answer.rdata),
690                }
691            )?;
692        }
693
694        writeln!(f)?;
695
696        Ok(())
697    }
698}
699
700// === Serialization ===
701
702impl Serialize for SignedPacket {
703    /// Serialize a [SignedPacket] for persistent storage.
704    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
705    where
706        S: serde::Serializer,
707    {
708        self.serialize().serialize(serializer)
709    }
710}
711
712impl<'de> Deserialize<'de> for SignedPacket {
713    /// Deserialize a [SignedPacket] from persistent storage.
714    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
715    where
716        D: serde::Deserializer<'de>,
717    {
718        let bytes: Vec<u8> = Deserialize::deserialize(deserializer)?;
719
720        SignedPacket::deserialize(&bytes).map_err(serde::de::Error::custom)
721    }
722}
723
724#[derive(thiserror::Error, Debug)]
725/// Errors trying to parse or create a [SignedPacket]
726pub enum SignedPacketVerifyError {
727    #[error(transparent)]
728    /// Errors which may occur while processing signatures and keypairs.
729    SignatureError(#[from] SignatureError),
730
731    #[error(transparent)]
732    /// Transparent [simple_dns::SimpleDnsError]
733    DnsError(#[from] simple_dns::SimpleDnsError),
734
735    #[error("Invalid SignedPacket bytes length, expected at least 104 bytes but got: {0}")]
736    /// Serialized signed packets are `<32 bytes publickey><64 bytes signature><8 bytes
737    /// timestamp><less than or equal to 1000 bytes encoded dns packet>`.
738    InvalidSignedPacketBytesLength(usize),
739
740    #[error("DNS Packet is too large, expected max 1000 bytes but got: {0}")]
741    /// DNS packet endocded and compressed is larger than 1000 bytes
742    PacketTooLarge(usize),
743
744    #[error(transparent)]
745    /// Errors while trying to create a [PublicKey]
746    PublicKeyError(#[from] PublicKeyError),
747}
748
749#[derive(thiserror::Error, Debug, PartialEq, Eq)]
750/// Errors trying to create a new [SignedPacket]
751pub enum SignedPacketBuildError {
752    #[error("DNS Packet is too large, expected max 1000 bytes but got: {0}")]
753    /// DNS packet endocded and compressed is larger than 1000 bytes
754    PacketTooLarge(usize),
755
756    #[error("Failed to write encoded DNS packet due to I/O error: {0}")]
757    /// Failed to write encoded DNS packet due to I/O error
758    FailedToWrite(#[from] SimpleDnsError),
759}
760
761#[cfg(test)]
762mod tests {
763    use simple_dns::rdata::CNAME;
764
765    use super::*;
766
767    use crate::{DEFAULT_MAXIMUM_TTL, DEFAULT_MINIMUM_TTL};
768
769    #[test]
770    fn custom_timestamp() {
771        let timestamp = Timestamp::from(42);
772
773        let signed_packet = SignedPacket::builder()
774            .timestamp(timestamp)
775            .sign(&Keypair::random())
776            .unwrap();
777
778        assert_eq!(signed_packet.timestamp(), timestamp);
779    }
780
781    #[test]
782    fn normalize_names() {
783        let origin = "ed4mn3aoazuf1ahpy9rz1nyswhukbj5483ryefwkue7fbp3egkzo";
784
785        assert_eq!(normalize_name(origin, ".".to_string()), origin);
786        assert_eq!(normalize_name(origin, "@".to_string()), origin);
787        assert_eq!(normalize_name(origin, "@.".to_string()), origin);
788        assert_eq!(normalize_name(origin, origin.to_string()), origin);
789        assert_eq!(
790            normalize_name(origin, "_derp_region.irorh".to_string()),
791            format!("_derp_region.irorh.{origin}")
792        );
793        assert_eq!(
794            normalize_name(origin, format!("_derp_region.irorh.{origin}")),
795            format!("_derp_region.irorh.{origin}")
796        );
797        assert_eq!(
798            normalize_name(origin, format!("_derp_region.irorh.{origin}.")),
799            format!("_derp_region.irorh.{origin}")
800        );
801    }
802
803    #[test]
804    fn sign_verify() {
805        let keypair = Keypair::random();
806
807        let signed_packet = SignedPacket::builder()
808            .address(
809                "_derp_region.iroh.".try_into().unwrap(),
810                "1.1.1.1".parse().unwrap(),
811                30,
812            )
813            .sign(&keypair)
814            .unwrap();
815
816        assert!(SignedPacket::from_relay_payload(
817            &signed_packet.public_key(),
818            &signed_packet.to_relay_payload()
819        )
820        .is_ok());
821    }
822
823    #[test]
824    fn from_too_large_bytes() {
825        let keypair = Keypair::random();
826
827        let bytes = vec![0; 1073];
828        let error = SignedPacket::from_relay_payload(&keypair.public_key(), &bytes.into());
829
830        assert!(error.is_err());
831    }
832
833    #[test]
834    fn from_too_large_packet() {
835        let keypair = Keypair::random();
836
837        let mut builder = SignedPacket::builder();
838
839        for _ in 0..100 {
840            builder = builder.address(
841                "_derp_region.iroh.".try_into().unwrap(),
842                "1.1.1.1".parse().unwrap(),
843                30,
844            );
845        }
846
847        let error = builder.sign(&keypair);
848
849        assert!(error.is_err());
850    }
851
852    #[test]
853    fn resource_records_iterator() {
854        let keypair = Keypair::random();
855
856        let target = ResourceRecord::new(
857            Name::new("_derp_region.iroh.").unwrap(),
858            simple_dns::CLASS::IN,
859            30,
860            RData::A(A {
861                address: Ipv4Addr::new(1, 1, 1, 1).into(),
862            }),
863        );
864
865        let signed_packet = SignedPacket::builder()
866            .record(target.clone())
867            .address(
868                "something-else".try_into().unwrap(),
869                "1.1.1.1".parse().unwrap(),
870                30,
871            )
872            .sign(&keypair)
873            .unwrap();
874
875        let iter = signed_packet.resource_records("_derp_region.iroh");
876        assert_eq!(iter.count(), 1);
877
878        for record in signed_packet.resource_records("_derp_region.iroh") {
879            assert_eq!(record.rdata, target.rdata);
880        }
881    }
882
883    #[cfg(dht)]
884    #[test]
885    fn to_mutable() {
886        let keypair = Keypair::random();
887
888        let signed_packet = SignedPacket::builder()
889            .address(
890                "_derp_region.iroh.".try_into().unwrap(),
891                "1.1.1.1".parse().unwrap(),
892                30,
893            )
894            .sign(&keypair)
895            .unwrap();
896
897        let item: MutableItem = (&signed_packet).into();
898        let seq = signed_packet.timestamp().as_u64() as i64;
899
900        let expected = MutableItem::new(
901            &keypair.secret_key().into(),
902            &signed_packet.packet().build_bytes_vec_compressed().unwrap(),
903            seq,
904            None,
905        );
906
907        assert_eq!(item, expected);
908    }
909
910    #[test]
911    fn compressed_names() {
912        let keypair = Keypair::random();
913
914        let signed_packet = SignedPacket::builder()
915            .cname(".".try_into().unwrap(), "foobar".try_into().unwrap(), 30)
916            .cname(".".try_into().unwrap(), "foobar".try_into().unwrap(), 30)
917            .sign(&keypair)
918            .unwrap();
919
920        assert_eq!(
921            signed_packet
922                .resource_records("@")
923                .map(|r| r.rdata.clone())
924                .collect::<Vec<_>>(),
925            vec![
926                RData::CNAME(CNAME("foobar".try_into().unwrap())),
927                RData::CNAME(CNAME("foobar".try_into().unwrap()))
928            ]
929        )
930    }
931
932    #[test]
933    fn to_bytes_from_bytes() {
934        let keypair = Keypair::random();
935
936        let signed_packet = SignedPacket::builder()
937            .txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 30)
938            .sign(&keypair)
939            .unwrap();
940
941        let bytes = signed_packet.as_bytes();
942        let from_bytes = SignedPacket::from_bytes(bytes).unwrap();
943        assert_eq!(signed_packet.as_bytes(), from_bytes.as_bytes());
944        let from_bytes2 = SignedPacket::from_bytes_unchecked(bytes, signed_packet.last_seen);
945        assert_eq!(signed_packet.as_bytes(), from_bytes2.as_bytes());
946
947        let public_key = keypair.public_key();
948        let payload = signed_packet.to_relay_payload();
949        let from_relay_payload = SignedPacket::from_relay_payload(&public_key, &payload).unwrap();
950        assert_eq!(signed_packet.as_bytes(), from_relay_payload.as_bytes());
951    }
952
953    #[test]
954    fn clone() {
955        let keypair = Keypair::random();
956
957        let signed = SignedPacket::builder()
958            .txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 30)
959            .sign(&keypair)
960            .unwrap();
961
962        let cloned = signed.clone();
963
964        assert_eq!(cloned.as_bytes(), signed.as_bytes());
965    }
966
967    #[test]
968    fn expires_in_minimum_ttl() {
969        let keypair = Keypair::random();
970
971        let mut signed_packet = SignedPacket::builder()
972            .txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 10)
973            .sign(&keypair)
974            .unwrap();
975
976        signed_packet.last_seen -= 20 * 1_000_000_u64;
977
978        assert!(
979            signed_packet.expires_in(30, u32::MAX) > 0,
980            "input minimum_ttl is 30 so ttl = 30"
981        );
982
983        assert!(
984            signed_packet.expires_in(0, u32::MAX) == 0,
985            "input minimum_ttl is 0 so ttl = 10 (smallest in resource records)"
986        );
987    }
988
989    #[test]
990    fn expires_in_maximum_ttl() {
991        let keypair = Keypair::random();
992
993        let mut signed_packet = SignedPacket::builder()
994            .txt(
995                "_foo".try_into().unwrap(),
996                "hello".try_into().unwrap(),
997                3 * DEFAULT_MAXIMUM_TTL,
998            )
999            .sign(&keypair)
1000            .unwrap();
1001
1002        signed_packet.last_seen -= 2 * (DEFAULT_MAXIMUM_TTL as u64) * 1_000_000;
1003
1004        assert!(
1005            signed_packet.expires_in(0, DEFAULT_MAXIMUM_TTL) == 0,
1006            "input maximum_ttl is the dfeault 86400 so maximum ttl = 86400"
1007        );
1008
1009        assert!(
1010            signed_packet.expires_in(0, 7 * DEFAULT_MAXIMUM_TTL) > 0,
1011            "input maximum_ttl is 7 * 86400 so ttl = 3 * 86400 (smallest in resource records)"
1012        );
1013    }
1014
1015    #[test]
1016    fn fresh_resource_records() {
1017        let keypair = Keypair::random();
1018
1019        let mut signed_packet = SignedPacket::builder()
1020            .txt("_foo".try_into().unwrap(), "hello".try_into().unwrap(), 30)
1021            .txt("_foo".try_into().unwrap(), "world".try_into().unwrap(), 60)
1022            .txt("_bar".try_into().unwrap(), "world".try_into().unwrap(), 60)
1023            .sign(&keypair)
1024            .unwrap();
1025
1026        signed_packet.last_seen -= 30 * 1_000_000;
1027
1028        assert_eq!(signed_packet.fresh_resource_records("_foo").count(), 1);
1029    }
1030
1031    #[test]
1032    fn ttl_empty() {
1033        let keypair = Keypair::random();
1034
1035        let signed_packet = SignedPacket::builder().sign(&keypair).unwrap();
1036
1037        assert_eq!(
1038            signed_packet.ttl(DEFAULT_MINIMUM_TTL, DEFAULT_MAXIMUM_TTL),
1039            300
1040        );
1041    }
1042
1043    #[test]
1044    fn ttl_with_records_less_than_minimum() {
1045        let keypair = Keypair::random();
1046
1047        let signed_packet = SignedPacket::builder()
1048            .txt(
1049                "_foo".try_into().unwrap(),
1050                "hello".try_into().unwrap(),
1051                DEFAULT_MINIMUM_TTL / 2,
1052            )
1053            .txt(
1054                "_foo".try_into().unwrap(),
1055                "world".try_into().unwrap(),
1056                DEFAULT_MINIMUM_TTL / 4,
1057            )
1058            .sign(&keypair)
1059            .unwrap();
1060
1061        assert_eq!(
1062            signed_packet.ttl(DEFAULT_MINIMUM_TTL, DEFAULT_MAXIMUM_TTL),
1063            DEFAULT_MINIMUM_TTL
1064        );
1065
1066        assert_eq!(
1067            signed_packet.ttl(0, DEFAULT_MAXIMUM_TTL),
1068            DEFAULT_MINIMUM_TTL / 4
1069        );
1070    }
1071
1072    #[test]
1073    fn ttl_with_records_more_than_maximum() {
1074        let keypair = Keypair::random();
1075
1076        let signed_packet = SignedPacket::builder()
1077            .txt(
1078                "_foo".try_into().unwrap(),
1079                "hello".try_into().unwrap(),
1080                DEFAULT_MAXIMUM_TTL * 2,
1081            )
1082            .txt(
1083                "_foo".try_into().unwrap(),
1084                "world".try_into().unwrap(),
1085                DEFAULT_MAXIMUM_TTL * 4,
1086            )
1087            .sign(&keypair)
1088            .unwrap();
1089
1090        assert_eq!(
1091            signed_packet.ttl(DEFAULT_MINIMUM_TTL, DEFAULT_MAXIMUM_TTL),
1092            DEFAULT_MAXIMUM_TTL
1093        );
1094
1095        assert_eq!(
1096            signed_packet.ttl(0, DEFAULT_MAXIMUM_TTL * 8),
1097            DEFAULT_MAXIMUM_TTL * 2
1098        );
1099    }
1100
1101    #[test]
1102    fn serde() {
1103        use postcard::{from_bytes, to_allocvec};
1104
1105        let keypair = Keypair::random();
1106
1107        let signed_packet = SignedPacket::builder()
1108            .address(
1109                "_derp_region.iroh.".try_into().unwrap(),
1110                "1.1.1.1".parse().unwrap(),
1111                30,
1112            )
1113            .sign(&keypair)
1114            .unwrap();
1115
1116        let serialized = to_allocvec(&signed_packet).unwrap();
1117        let deserialized: SignedPacket = from_bytes(&serialized).unwrap();
1118
1119        assert_eq!(deserialized, signed_packet);
1120
1121        // Backwards compat
1122        {
1123            let mut bytes = vec![];
1124
1125            bytes.extend_from_slice(&[210, 1]);
1126            bytes.extend_from_slice(&signed_packet.last_seen().as_u64().to_le_bytes());
1127            bytes.extend_from_slice(signed_packet.as_bytes());
1128
1129            let deserialized: SignedPacket = from_bytes(&bytes).unwrap();
1130
1131            assert_eq!(deserialized.as_bytes(), signed_packet.as_bytes());
1132            assert_eq!(deserialized.last_seen(), &Timestamp::from(0));
1133        }
1134    }
1135
1136    #[test]
1137    fn wildcards() {
1138        let keypair = Keypair::random();
1139
1140        let signed_packet = SignedPacket::builder()
1141            .txt("bar.foo.".try_into().unwrap(), "_".try_into().unwrap(), 30)
1142            .txt("x.bar.foo".try_into().unwrap(), "_".try_into().unwrap(), 30)
1143            .txt("foo".try_into().unwrap(), "_".try_into().unwrap(), 60)
1144            .sign(&keypair)
1145            .unwrap();
1146
1147        assert_eq!(signed_packet.fresh_resource_records("*.foo.").count(), 1);
1148    }
1149}