simple_dns/dns/rdata/
nsec.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use std::borrow::Cow;

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

use super::RR;

/// A NSEC record see [rfc4034](https://datatracker.ietf.org/doc/html/rfc4034#section-4)
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct NSEC<'a> {
    /// The next owner name in the canonical ordering of the zone
    pub next_name: Name<'a>,
    /// The type bit maps representing the RR types present at the NSEC RR's owner name
    pub type_bit_maps: Vec<TypeBitMap<'a>>,
}

/// A Type bit map entry in a NSEC record see [rfc4034](https://datatracker.ietf.org/doc/html/rfc4034#section-4.1.2)
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct TypeBitMap<'a> {
    /// The window block number of this bit map
    pub window_block: u8,
    /// The bitmap containing the RR types present in this window block
    pub bitmap: Cow<'a, [u8]>,
}

impl RR for NSEC<'_> {
    const TYPE_CODE: u16 = 47;
}

impl<'a> WireFormat<'a> for NSEC<'a> {
    const MINIMUM_LEN: usize = 0;
    fn parse_after_check(data: &'a [u8], position: &mut usize) -> crate::Result<Self>
    where
        Self: Sized,
    {
        let next_name = Name::parse(data, position)?;
        let mut type_bit_maps = Vec::new();

        while data.len() > *position {
            let window_block = data[*position];
            *position += 1;

            if type_bit_maps.last().is_some_and(|f: &TypeBitMap<'_>| {
                f.window_block > 0 && f.window_block - 1 != window_block
            }) {
                return Err(crate::SimpleDnsError::AttemptedInvalidOperation);
            }

            if *position >= data.len() {
                return Err(crate::SimpleDnsError::InsufficientData);
            }

            let bitmap_length = data[*position];
            *position += 1;

            let bitmap_end = *position + bitmap_length as usize;

            if bitmap_end > data.len() {
                return Err(crate::SimpleDnsError::InsufficientData);
            }

            let bitmap = &data[*position..bitmap_end];
            *position = bitmap_end;

            type_bit_maps.push(TypeBitMap {
                window_block,
                bitmap: Cow::Borrowed(bitmap),
            });
        }

        Ok(Self {
            next_name,
            type_bit_maps,
        })
    }

    fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
        self.next_name.write_to(out)?;

        let mut sorted = self.type_bit_maps.clone();
        sorted.sort_by(|a, b| a.window_block.cmp(&b.window_block));

        for record in sorted.iter() {
            out.write_all(&[record.window_block])?;
            out.write_all(&[record.bitmap.len() as u8])?;
            out.write_all(&record.bitmap)?;
        }

        Ok(())
    }

    fn len(&self) -> usize {
        self.next_name.len()
    }
}

impl NSEC<'_> {
    /// Transforms the inner data into its owned type
    pub fn into_owned<'b>(self) -> NSEC<'b> {
        let type_bit_maps = self
            .type_bit_maps
            .into_iter()
            .map(|x| TypeBitMap {
                window_block: x.window_block,
                bitmap: x.bitmap.into_owned().into(),
            })
            .collect();
        NSEC {
            next_name: self.next_name.into_owned(),
            type_bit_maps,
        }
    }
}

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

    use super::*;

    #[test]
    fn parse_and_write_nsec() {
        let nsec = NSEC {
            next_name: Name::new("host.example.com.").unwrap(),
            type_bit_maps: vec![TypeBitMap {
                window_block: 0,
                bitmap: vec![64, 1, 0, 0, 0, 1].into(),
            }],
        };
        let mut data = Vec::new();
        nsec.write_to(&mut data).unwrap();

        let nsec = NSEC::parse(&data, &mut 0).unwrap();
        assert_eq!(nsec.next_name, Name::new("host.example.com.").unwrap());
        assert_eq!(nsec.type_bit_maps.len(), 1);
        assert_eq!(nsec.type_bit_maps[0].window_block, 0);
        assert_eq!(nsec.type_bit_maps[0].bitmap, vec![64, 1, 0, 0, 0, 1]);
    }

    #[test]
    fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
        let sample_file = std::fs::read("samples/zonefile/NSEC.sample")?;

        let sample_rdata = match ResourceRecord::parse(&sample_file, &mut 0)?.rdata {
            RData::NSEC(rdata) => rdata,
            _ => unreachable!(),
        };

        assert_eq!(
            sample_rdata.next_name,
            Name::new("host.example.com.").unwrap()
        );
        assert_eq!(sample_rdata.type_bit_maps.len(), 1);
        assert_eq!(sample_rdata.type_bit_maps[0].window_block, 0);
        assert_eq!(
            sample_rdata.type_bit_maps[0].bitmap,
            vec![64, 1, 0, 0, 0, 1]
        );

        Ok(())
    }
}