simple_dns/dns/rdata/
caa.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::borrow::Cow;

use crate::dns::{CharacterString, WireFormat};

use super::RR;

/// RFC 8659: Allow domain name holders to indicate whether they are authorized to issue digital certificates for particular domain name
/// Used as a security policy for certificate authorities
/// This implementation does not validate the tag or value; it splits based on packet byte structure
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct CAA<'a> {
    /// Critical or noncritical indicator
    pub flag: u8,
    /// Property described in the VALUE field. One of `issue`, `issuewild`, or `iodef`
    pub tag: CharacterString<'a>,
    /// Value associated with property tag
    pub value: Cow<'a, [u8]>,
}

impl<'a> RR for CAA<'a> {
    const TYPE_CODE: u16 = 257;
}

impl<'a> CAA<'a> {
    /// Transforms the inner data into it owned type
    pub fn into_owned<'b>(self) -> CAA<'b> {
        CAA {
            flag: self.flag,
            tag: self.tag.into_owned(),
            value: self.value.into_owned().into(),
        }
    }
}

impl<'a> WireFormat<'a> for CAA<'a> {
    fn parse(data: &'a [u8], position: &mut usize) -> crate::Result<Self>
    where
        Self: Sized,
    {
        let flag = u8::from_be_bytes(data[*position..*position + 1].try_into()?);
        *position += 1;
        let tag = CharacterString::parse(data, position)?;
        let value = Cow::Borrowed(&data[*position..]);
        *position += value.len();

        Ok(Self { flag, tag, value })
    }

    fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
        out.write_all(&self.flag.to_be_bytes())?;
        self.tag.write_to(out)?;
        out.write_all(&self.value)?;
        Ok(())
    }

    fn len(&self) -> usize {
        self.tag.len() + self.value.len() + 1
    }
}

#[cfg(test)]
mod tests {
    use crate::{rdata::RData, Packet, ResourceRecord, CLASS};

    use super::*;

    #[test]
    fn parse_and_write_caa() {
        let caa = CAA {
            flag: 0,
            tag: CharacterString::new(b"issue").unwrap(),
            value: b"\"example.org".into(),
        };

        let mut data = Vec::new();
        assert!(caa.write_to(&mut data).is_ok());

        let caa = CAA::parse(&data, &mut 0);
        assert!(caa.is_ok());
        let caa = caa.unwrap();

        assert_eq!(data.len(), caa.len());
        assert_eq!(0, caa.flag);
        assert_eq!("issue", caa.tag.to_string());
        assert_eq!(b"\"example.org", &caa.value[..]);
    }

    #[test]
    fn parse_rdata_with_multiple_caa_records() {
        let mut packet = Packet::new_query(0);
        packet.answers.push(ResourceRecord::new(
            "caa.xxx.com".try_into().unwrap(),
            CLASS::IN,
            11111,
            crate::rdata::RData::CAA(CAA {
                flag: 128,
                tag: CharacterString::new(b"issuewild").unwrap(),
                value: b"\"example.org".into(),
            }),
        ));

        packet.answers.push(ResourceRecord::new(
            "caa.yyy.com".try_into().unwrap(),
            CLASS::IN,
            11111,
            crate::rdata::RData::CAA(CAA {
                flag: 128,
                tag: CharacterString::new(b"issuewild").unwrap(),
                value: b"\"example_two.org".into(),
            }),
        ));

        let data = packet
            .build_bytes_vec_compressed()
            .expect("Failed to generate packet");

        let mut packet = Packet::parse(&data[..]).expect("Failed to parse packet");
        let RData::CAA(cca_two) = packet.answers.pop().unwrap().rdata else {
            panic!("failed to parse CAA record)")
        };

        let RData::CAA(cca_one) = packet.answers.pop().unwrap().rdata else {
            panic!("failed to parse CAA record")
        };

        assert_eq!(b"\"example.org", &cca_one.value[..]);
        assert_eq!(b"\"example_two.org", &cca_two.value[..]);
    }
}