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