flex_dns/rdata/
txt.rs

1use crate::{Buffer, DnsError, DnsMessage, DnsMessageError, MutBuffer};
2use crate::rdata::{RData, RDataParse};
3use crate::write::WriteBytes;
4
5/// # The txt record
6/// This record is used to hold arbitrary text data.
7#[derive(Copy, Clone, Debug, PartialEq)]
8pub struct Txt<'a> {
9    /// The text data
10    data: &'a [u8],
11}
12
13/// Create a new [`Txt`] from byte slices. The data will be concatenated and
14/// converted to DNS wire format. The maximum length of a single text value is
15/// 255 bytes. This macro accepts an expression as input, which is evaluated at
16/// compile time. If you want to create a [`Txt`] from byte slices of unknown
17/// length, use the [`Txt::new`] function instead.
18///
19/// # Example
20/// ```
21/// use flex_dns::rdata::Txt;
22/// use flex_dns::dns_txt;
23///
24/// const TXT: Txt = dns_txt!(
25///     b"Hello",
26///     b"World!"
27/// );
28/// ```
29#[macro_export]
30macro_rules! dns_txt {
31    ($value:expr $(, $values:expr)* $(,)?) => {
32        {
33            const TXT: [u8; { $value.len() + 1 $(+ $values.len() + 1)* }] = {
34                if $value.len() > u8::MAX as usize {
35                    panic!("Txt value too long, maximum length is 255.");
36                }
37
38                let mut result = [0; $value.len() + 1 $(+ $values.len() + 1)*];
39                result[0] = $value.len() as u8;
40
41                let mut index = 1;
42                let mut r_index = 0;
43                loop {
44                    if r_index == $value.len() {
45                        break;
46                    }
47
48                    result[index] = $value[r_index];
49                    r_index += 1;
50                    index += 1;
51                }
52
53                $(
54                    if $values.len() > u8::MAX as usize {
55                        panic!("Txt value too long, maximum length is 255.");
56                    }
57
58                    result[index] = $values.len() as u8;
59                    index += 1;
60
61                    let mut r_index = 0;
62                    loop {
63                        if r_index == $values.len() {
64                            break;
65                        }
66
67                        result[index] = $values[r_index];
68                        r_index += 1;
69                        index += 1;
70                    }
71                )*
72
73                result
74            };
75
76            unsafe { flex_dns::rdata::Txt::new_unchecked(&TXT) }
77        }
78    };
79}
80
81impl<'a> Txt<'a> {
82    /// Creates a new txt record and checks the data. The data needs to be
83    /// in DNS wire format. The maximum length of a single text value is 255.
84    /// If the data is invalid, this function will return an error.
85    ///
86    /// # Example
87    /// ```
88    /// use flex_dns::rdata::Txt;
89    ///
90    /// let txt = Txt::new(
91    ///    b"\x05Hello\x06World!"
92    /// ).unwrap();
93    /// ```
94    #[inline(always)]
95    pub fn new(data: &'a [u8]) -> Result<Self, DnsMessageError> {
96        for r in (TxtIterator {
97            data,
98            pos: 0,
99        }) {
100            r?;
101        }
102
103        Ok(Self {
104            data,
105        })
106    }
107
108    /// Creates a new txt record without checking the data.
109    /// This function is unsafe because it doesn't check the data.
110    /// If the data is invalid it can lead to an invalid DNS message.
111    #[inline(always)]
112    pub const unsafe fn new_unchecked(data: &'a [u8]) -> Self {
113        Self {
114            data,
115        }
116    }
117
118    /// Returns an iterator over the txt record data.
119    #[inline(always)]
120    pub fn iter(&self) -> TxtIterator<'a> {
121        TxtIterator {
122            data: self.data,
123            pos: 0,
124        }
125    }
126}
127
128impl<'a> RDataParse<'a> for Txt<'a> {
129    #[inline]
130    fn parse(rdata: &RData<'a>, i: &mut usize) -> Result<Self, DnsMessageError> {
131        let data = &rdata.buffer[*i..*i + rdata.len];
132        *i += rdata.len;
133
134        Ok(Self {
135            data,
136        })
137    }
138}
139
140impl<'a> WriteBytes for Txt<'a> {
141    #[inline]
142    fn write<
143        const PTR_STORAGE: usize,
144        const DNS_SECTION: usize,
145        B: MutBuffer + Buffer,
146    >(&self, message: &mut DnsMessage<PTR_STORAGE, DNS_SECTION, B>) -> Result<usize, DnsMessageError> {
147        message.write_bytes(self.data)
148    }
149}
150
151/// An iterator over the txt record data.
152#[derive(Copy, Clone, Debug)]
153pub struct TxtIterator<'a> {
154    data: &'a [u8],
155    pos: usize,
156}
157
158impl<'a> Iterator for TxtIterator<'a> {
159    type Item = Result<&'a [u8], DnsMessageError>;
160
161    fn next(&mut self) -> Option<Self::Item> {
162        if self.pos >= self.data.len() {
163            return None;
164        }
165
166        let len = self.data[self.pos] as usize;
167
168        if len == 0 && self.pos + 1 == self.data.len() {
169            return None;
170        } else if len == 0 {
171            return Some(Err(DnsMessageError::DnsError(DnsError::InvalidTxtRecord)));
172        }
173
174        self.pos += 1;
175
176        let end = self.pos + len;
177
178        if end > self.data.len() {
179            return Some(Err(DnsMessageError::DnsError(DnsError::InvalidTxtRecord)));
180        }
181
182        let result = &self.data[self.pos..end];
183        self.pos = end;
184
185        Some(Ok(result))
186    }
187}