Skip to main content

ddns/core/
wire.rs

1/// HTTP multi-record response wire format shared between server and all clients.
2///
3/// Wire layout (big-endian, contiguous):
4/// ```text
5/// +-----------+  (repeated `count` times)
6/// | count     |  +-----------+------+-----------+------+
7/// | u32 BE    |  | dns_len   | dns  | cert_len  | cert |
8/// +-----------+  | u32 BE    | ...  | u32 BE    | ...  |
9///                +-----------+------+-----------+------+
10/// ```
11use bytes::BufMut;
12use nom::{IResult, bytes::streaming::take, number::streaming::be_u32};
13
14/// One DNS + certificate pair inside a [`MultiResponse`].
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ResponseRecord {
17    /// Serialised DNS packet bytes.
18    pub dns: Vec<u8>,
19    /// DER-encoded leaf certificate of the publisher, or empty when unavailable.
20    pub cert: Vec<u8>,
21}
22
23impl ResponseRecord {
24    /// SHA-256 fingerprint of the publisher certificate as lowercase hex.
25    /// Returns `None` when the cert field is empty.
26    pub fn cert_fingerprint_hex(&self) -> Option<String> {
27        if self.cert.is_empty() {
28            return None;
29        }
30        use ring::digest::{SHA256, digest};
31        let digest = digest(&SHA256, &self.cert);
32        Some(digest.as_ref().iter().map(|b| format!("{b:02x}")).collect())
33    }
34}
35
36/// HTTP response body carrying zero or more DNS records.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct MultiResponse {
39    pub records: Vec<ResponseRecord>,
40}
41
42impl MultiResponse {
43    pub fn new(iter: impl IntoIterator<Item = (Vec<u8>, Vec<u8>)>) -> Self {
44        Self {
45            records: iter
46                .into_iter()
47                .map(|(dns, cert)| ResponseRecord { dns, cert })
48                .collect(),
49        }
50    }
51
52    pub fn encoding_size(&self) -> usize {
53        4 + self
54            .records
55            .iter()
56            .map(|record| 4 + record.dns.len() + 4 + record.cert.len())
57            .sum::<usize>()
58    }
59
60    pub fn encode(&self) -> Vec<u8> {
61        let mut buf = Vec::with_capacity(self.encoding_size());
62        buf.put_multi_response(self);
63        buf
64    }
65}
66
67pub trait WriteMultiResponse {
68    fn put_multi_response(&mut self, response: &MultiResponse);
69}
70
71impl<B: BufMut> WriteMultiResponse for B {
72    fn put_multi_response(&mut self, response: &MultiResponse) {
73        self.put_u32(response.records.len() as u32);
74        for record in &response.records {
75            self.put_u32(record.dns.len() as u32);
76            self.put_slice(&record.dns);
77            self.put_u32(record.cert.len() as u32);
78            self.put_slice(&record.cert);
79        }
80    }
81}
82
83pub fn be_multi_response(input: &[u8]) -> IResult<&[u8], MultiResponse> {
84    let (mut input, count) = be_u32(input)?;
85    let mut records = Vec::with_capacity(count as usize);
86    for _ in 0..count {
87        let (rest, dns_len) = be_u32(input)?;
88        let (rest, dns) = take(dns_len as usize)(rest)?;
89        let (rest, cert_len) = be_u32(rest)?;
90        let (rest, cert) = take(cert_len as usize)(rest)?;
91        records.push(ResponseRecord {
92            dns: dns.to_vec(),
93            cert: cert.to_vec(),
94        });
95        input = rest;
96    }
97    Ok((input, MultiResponse { records }))
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn multi_response_roundtrips() {
106        let response =
107            MultiResponse::new([(vec![1, 2, 3], vec![4, 5]), (vec![6, 7, 8, 9], Vec::new())]);
108        let encoded = response.encode();
109        let (remain, decoded) = be_multi_response(&encoded).unwrap();
110        assert!(remain.is_empty());
111        assert_eq!(decoded, response);
112    }
113}