domain/rdata/rfc1035/
null.rs

1//! Record data for the NULL record.
2//!
3//! This is a private module. It’s content is re-exported by the parent.
4
5// Currently a false positive on Null. We cannot apply it there because
6// the allow attribute doesn't get copied to the code generated by serde.
7#![allow(clippy::needless_maybe_sized)]
8
9use crate::base::cmp::CanonicalOrd;
10use crate::base::iana::Rtype;
11use crate::base::rdata::{
12    ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
13};
14use crate::base::wire::{Composer, ParseError};
15use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
16use core::cmp::Ordering;
17use core::{fmt, hash, mem};
18use octseq::octets::{Octets, OctetsFrom, OctetsInto};
19use octseq::parse::Parser;
20
21//------------ Null ---------------------------------------------------------
22
23/// Null record data.
24///
25/// Null records can contain whatever data. They are experimental and not
26/// allowed in zone files.
27///
28/// The Null record type is defined in [RFC 1035, section 3.3.10][1].
29///
30/// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.10
31#[derive(Clone)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[repr(transparent)]
34pub struct Null<Octs: ?Sized> {
35    #[cfg_attr(
36        feature = "serde",
37        serde(
38            serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
39            deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
40            bound(
41                serialize = "Octs: octseq::serde::SerializeOctets",
42                deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
43            )
44        )
45    )]
46    data: Octs,
47}
48
49impl Null<()> {
50    /// The rtype of this record data type.
51    pub(crate) const RTYPE: Rtype = Rtype::NULL;
52}
53
54impl<Octs> Null<Octs> {
55    /// Creates new NULL record data from the given octets.
56    ///
57    /// The function will fail if `data` is longer than 65,535 octets.
58    pub fn from_octets(data: Octs) -> Result<Self, LongRecordData>
59    where
60        Octs: AsRef<[u8]>,
61    {
62        Null::check_slice(data.as_ref())?;
63        Ok(unsafe { Self::from_octets_unchecked(data) })
64    }
65
66    /// Creates new NULL record data without checking.
67    ///
68    /// # Safety
69    ///
70    /// The caller has to ensure that `data` is at most 65,535 octets long.
71    pub unsafe fn from_octets_unchecked(data: Octs) -> Self {
72        Null { data }
73    }
74}
75
76impl Null<[u8]> {
77    /// Creates new NULL record data from an octets slice.
78    ///
79    /// The function will fail if `data` is longer than 65,535 octets.
80    pub fn from_slice(data: &[u8]) -> Result<&Self, LongRecordData> {
81        Self::check_slice(data)?;
82        Ok(unsafe { Self::from_slice_unchecked(data) })
83    }
84
85    /// Creates new NULL record from an octets slice data without checking.
86    ///
87    /// # Safety
88    ///
89    /// The caller has to ensure that `data` is at most 65,535 octets long.
90    #[must_use]
91    pub unsafe fn from_slice_unchecked(data: &[u8]) -> &Self {
92        // SAFETY: Null has repr(transparent)
93        mem::transmute(data)
94    }
95
96    /// Checks that a slice can be used for NULL record data.
97    fn check_slice(slice: &[u8]) -> Result<(), LongRecordData> {
98        LongRecordData::check_len(slice.len())
99    }
100}
101
102impl<Octs: ?Sized> Null<Octs> {
103    /// The raw content of the record.
104    pub fn data(&self) -> &Octs {
105        &self.data
106    }
107}
108
109impl<Octs: AsRef<[u8]>> Null<Octs> {
110    pub fn len(&self) -> usize {
111        self.data.as_ref().len()
112    }
113
114    pub fn is_empty(&self) -> bool {
115        self.data.as_ref().is_empty()
116    }
117}
118
119impl<Octs> Null<Octs> {
120    pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<Octs>>(
121        self,
122    ) -> Result<Null<Target>, Target::Error> {
123        Ok(unsafe {
124            Null::from_octets_unchecked(self.data.try_octets_into()?)
125        })
126    }
127
128    pub(in crate::rdata) fn flatten<Target: OctetsFrom<Octs>>(
129        self,
130    ) -> Result<Null<Target>, Target::Error> {
131        self.convert_octets()
132    }
133}
134
135impl<Octs> Null<Octs> {
136    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
137        parser: &mut Parser<'a, Src>,
138    ) -> Result<Self, ParseError> {
139        let len = parser.remaining();
140        parser
141            .parse_octets(len)
142            .map(|res| unsafe { Self::from_octets_unchecked(res) })
143            .map_err(Into::into)
144    }
145}
146
147//--- OctetsFrom
148
149impl<Octs, SrcOcts> OctetsFrom<Null<SrcOcts>> for Null<Octs>
150where
151    Octs: OctetsFrom<SrcOcts>,
152{
153    type Error = Octs::Error;
154
155    fn try_octets_from(source: Null<SrcOcts>) -> Result<Self, Self::Error> {
156        Octs::try_octets_from(source.data)
157            .map(|res| unsafe { Self::from_octets_unchecked(res) })
158    }
159}
160
161//--- PartialEq and Eq
162
163impl<Octs, Other> PartialEq<Null<Other>> for Null<Octs>
164where
165    Octs: AsRef<[u8]> + ?Sized,
166    Other: AsRef<[u8]> + ?Sized,
167{
168    fn eq(&self, other: &Null<Other>) -> bool {
169        self.data.as_ref().eq(other.data.as_ref())
170    }
171}
172
173impl<Octs: AsRef<[u8]> + ?Sized> Eq for Null<Octs> {}
174
175//--- PartialOrd, CanonicalOrd, and Ord
176
177impl<Octs, Other> PartialOrd<Null<Other>> for Null<Octs>
178where
179    Octs: AsRef<[u8]> + ?Sized,
180    Other: AsRef<[u8]> + ?Sized,
181{
182    fn partial_cmp(&self, other: &Null<Other>) -> Option<Ordering> {
183        self.data.as_ref().partial_cmp(other.data.as_ref())
184    }
185}
186
187impl<Octs, Other> CanonicalOrd<Null<Other>> for Null<Octs>
188where
189    Octs: AsRef<[u8]> + ?Sized,
190    Other: AsRef<[u8]> + ?Sized,
191{
192    fn canonical_cmp(&self, other: &Null<Other>) -> Ordering {
193        self.data.as_ref().cmp(other.data.as_ref())
194    }
195}
196
197impl<Octs: AsRef<[u8]> + ?Sized> Ord for Null<Octs> {
198    fn cmp(&self, other: &Self) -> Ordering {
199        self.data.as_ref().cmp(other.data.as_ref())
200    }
201}
202
203//--- Hash
204
205impl<Octs: AsRef<[u8]> + ?Sized> hash::Hash for Null<Octs> {
206    fn hash<H: hash::Hasher>(&self, state: &mut H) {
207        self.data.as_ref().hash(state)
208    }
209}
210
211//--- RecordData, ParseRecordData, ComposeRecordData
212
213impl<Octs: ?Sized> RecordData for Null<Octs> {
214    fn rtype(&self) -> Rtype {
215        Null::RTYPE
216    }
217}
218
219impl<'a, Octs> ParseRecordData<'a, Octs> for Null<Octs::Range<'a>>
220where
221    Octs: Octets + ?Sized,
222{
223    fn parse_rdata(
224        rtype: Rtype,
225        parser: &mut Parser<'a, Octs>,
226    ) -> Result<Option<Self>, ParseError> {
227        if rtype == Null::RTYPE {
228            Self::parse(parser).map(Some)
229        } else {
230            Ok(None)
231        }
232    }
233}
234
235impl<Octs: AsRef<[u8]> + ?Sized> ComposeRecordData for Null<Octs> {
236    fn rdlen(&self, _compress: bool) -> Option<u16> {
237        Some(
238            u16::try_from(self.data.as_ref().len()).expect("long NULL rdata"),
239        )
240    }
241
242    fn compose_rdata<Target: Composer + ?Sized>(
243        &self,
244        target: &mut Target,
245    ) -> Result<(), Target::AppendError> {
246        target.append_slice(self.data.as_ref())
247    }
248
249    fn compose_canonical_rdata<Target: Composer + ?Sized>(
250        &self,
251        target: &mut Target,
252    ) -> Result<(), Target::AppendError> {
253        self.compose_rdata(target)
254    }
255}
256
257//--- AsRef
258
259impl<Octs: AsRef<Other>, Other> AsRef<Other> for Null<Octs> {
260    fn as_ref(&self) -> &Other {
261        self.data.as_ref()
262    }
263}
264
265//--- Display and Debug
266
267impl<Octs: AsRef<[u8]>> fmt::Display for Null<Octs> {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        write!(f, "\\# {}", self.data.as_ref().len())?;
270        for ch in self.data.as_ref().iter() {
271            write!(f, " {:02x}", ch)?;
272        }
273        Ok(())
274    }
275}
276
277impl<Octs: AsRef<[u8]>> fmt::Debug for Null<Octs> {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        f.write_str("Null(")?;
280        fmt::Display::fmt(self, f)?;
281        f.write_str(")")
282    }
283}
284
285//--- ZonefileFmt
286
287impl<Octs: AsRef<[u8]>> ZonefileFmt for Null<Octs> {
288    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
289        struct Data<'a>(&'a [u8]);
290
291        impl fmt::Display for Data<'_> {
292            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293                write!(f, "\\# {}", self.0.len())?;
294                for ch in self.0 {
295                    write!(f, " {:02x}", *ch)?
296                }
297                Ok(())
298            }
299        }
300
301        p.write_token(Data(self.data.as_ref()))
302    }
303}
304
305//============ Testing =======================================================
306
307#[cfg(test)]
308#[cfg(all(feature = "std", feature = "bytes"))]
309mod test {
310    use super::*;
311    use crate::base::rdata::test::{test_compose_parse, test_rdlen};
312
313    #[test]
314    #[allow(clippy::redundant_closure)] // lifetimes ...
315    fn null_compose_parse_scan() {
316        let rdata = Null::from_octets("foo").unwrap();
317        test_rdlen(&rdata);
318        test_compose_parse(&rdata, |parser| Null::parse(parser));
319    }
320}