domain/rdata/rfc1035/
minfo.rs

1//! Record data for the MINFO record.
2//!
3//! This is a private module. It’s content is re-exported by the parent.
4
5use crate::base::cmp::CanonicalOrd;
6use crate::base::iana::Rtype;
7use crate::base::name::{FlattenInto, ParsedName, ToName};
8use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData};
9use crate::base::scan::Scanner;
10use crate::base::wire::{Composer, ParseError};
11use crate::base::zonefile_fmt::{
12    self, Formatter, ZonefileFmt,
13};
14use core::cmp::Ordering;
15use core::fmt;
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19//------------ Minfo --------------------------------------------------------
20
21/// Minfo record data.
22///
23/// The Minfo record specifies a mailbox which is responsible for the mailing
24/// list or mailbox and a mailbox that receives error messages related to the
25/// list or box.
26///
27/// The Minfo record is experimental.
28///
29/// The Minfo record type is defined in RFC 1035, section 3.3.7.
30///
31/// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.7
32#[derive(Clone, Debug, Hash)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct Minfo<N> {
35    rmailbx: N,
36    emailbx: N,
37}
38
39impl Minfo<()> {
40    /// The rtype of this record data type.
41    pub(crate) const RTYPE: Rtype = Rtype::MINFO;
42}
43
44impl<N> Minfo<N> {
45    /// Creates a new Minfo record data from the components.
46    pub fn new(rmailbx: N, emailbx: N) -> Self {
47        Minfo { rmailbx, emailbx }
48    }
49
50    /// The responsible mail box.
51    ///
52    /// The domain name specifies the mailbox which is responsible for the
53    /// mailing list or mailbox. If this domain name is the root, the owner
54    /// of the Minfo record is responsible for itself.
55    pub fn rmailbx(&self) -> &N {
56        &self.rmailbx
57    }
58
59    /// The error mail box.
60    ///
61    /// The domain name specifies a mailbox which is to receive error
62    /// messages related to the mailing list or mailbox specified by the
63    /// owner of the record. If this is the root domain name, errors should
64    /// be returned to the sender of the message.
65    pub fn emailbx(&self) -> &N {
66        &self.emailbx
67    }
68
69    pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<N>>(
70        self,
71    ) -> Result<Minfo<Target>, Target::Error> {
72        Ok(Minfo::new(
73            self.rmailbx.try_octets_into()?,
74            self.emailbx.try_octets_into()?,
75        ))
76    }
77
78    pub(in crate::rdata) fn flatten<TargetName>(
79        self,
80    ) -> Result<Minfo<TargetName>, N::AppendError>
81    where
82        N: FlattenInto<TargetName>,
83    {
84        Ok(Minfo::new(
85            self.rmailbx.try_flatten_into()?,
86            self.emailbx.try_flatten_into()?,
87        ))
88    }
89
90    pub fn scan<S: Scanner<Name = N>>(
91        scanner: &mut S,
92    ) -> Result<Self, S::Error> {
93        Ok(Self::new(scanner.scan_name()?, scanner.scan_name()?))
94    }
95}
96
97impl<Octs> Minfo<ParsedName<Octs>> {
98    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
99        parser: &mut Parser<'a, Src>,
100    ) -> Result<Self, ParseError> {
101        Ok(Self::new(
102            ParsedName::parse(parser)?,
103            ParsedName::parse(parser)?,
104        ))
105    }
106}
107
108//--- OctetsFrom and FlattenInto
109
110impl<Name, SrcName> OctetsFrom<Minfo<SrcName>> for Minfo<Name>
111where
112    Name: OctetsFrom<SrcName>,
113{
114    type Error = Name::Error;
115
116    fn try_octets_from(source: Minfo<SrcName>) -> Result<Self, Self::Error> {
117        Ok(Minfo::new(
118            Name::try_octets_from(source.rmailbx)?,
119            Name::try_octets_from(source.emailbx)?,
120        ))
121    }
122}
123
124impl<Name, TName> FlattenInto<Minfo<TName>> for Minfo<Name>
125where
126    Name: FlattenInto<TName>,
127{
128    type AppendError = Name::AppendError;
129
130    fn try_flatten_into(self) -> Result<Minfo<TName>, Name::AppendError> {
131        self.flatten()
132    }
133}
134
135//--- PartialEq and Eq
136
137impl<N, NN> PartialEq<Minfo<NN>> for Minfo<N>
138where
139    N: ToName,
140    NN: ToName,
141{
142    fn eq(&self, other: &Minfo<NN>) -> bool {
143        self.rmailbx.name_eq(&other.rmailbx)
144            && self.emailbx.name_eq(&other.emailbx)
145    }
146}
147
148impl<N: ToName> Eq for Minfo<N> {}
149
150//--- PartialOrd, Ord, and CanonicalOrd
151
152impl<N, NN> PartialOrd<Minfo<NN>> for Minfo<N>
153where
154    N: ToName,
155    NN: ToName,
156{
157    fn partial_cmp(&self, other: &Minfo<NN>) -> Option<Ordering> {
158        match self.rmailbx.name_cmp(&other.rmailbx) {
159            Ordering::Equal => {}
160            other => return Some(other),
161        }
162        Some(self.emailbx.name_cmp(&other.emailbx))
163    }
164}
165
166impl<N: ToName> Ord for Minfo<N> {
167    fn cmp(&self, other: &Self) -> Ordering {
168        match self.rmailbx.name_cmp(&other.rmailbx) {
169            Ordering::Equal => {}
170            other => return other,
171        }
172        self.emailbx.name_cmp(&other.emailbx)
173    }
174}
175
176impl<N: ToName, NN: ToName> CanonicalOrd<Minfo<NN>> for Minfo<N> {
177    fn canonical_cmp(&self, other: &Minfo<NN>) -> Ordering {
178        match self.rmailbx.lowercase_composed_cmp(&other.rmailbx) {
179            Ordering::Equal => {}
180            other => return other,
181        }
182        self.emailbx.lowercase_composed_cmp(&other.emailbx)
183    }
184}
185
186//--- RecordData, ParseRecordData, ComposeRecordData
187
188impl<N> RecordData for Minfo<N> {
189    fn rtype(&self) -> Rtype {
190        Minfo::RTYPE
191    }
192}
193
194impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
195    for Minfo<ParsedName<Octs::Range<'a>>>
196{
197    fn parse_rdata(
198        rtype: Rtype,
199        parser: &mut Parser<'a, Octs>,
200    ) -> Result<Option<Self>, ParseError> {
201        if rtype == Minfo::RTYPE {
202            Self::parse(parser).map(Some)
203        } else {
204            Ok(None)
205        }
206    }
207}
208
209impl<Name: ToName> ComposeRecordData for Minfo<Name> {
210    fn rdlen(&self, compress: bool) -> Option<u16> {
211        if compress {
212            None
213        } else {
214            Some(self.rmailbx.compose_len() + self.emailbx.compose_len())
215        }
216    }
217
218    fn compose_rdata<Target: Composer + ?Sized>(
219        &self,
220        target: &mut Target,
221    ) -> Result<(), Target::AppendError> {
222        if target.can_compress() {
223            target.append_compressed_name(&self.rmailbx)?;
224            target.append_compressed_name(&self.emailbx)
225        } else {
226            self.rmailbx.compose(target)?;
227            self.emailbx.compose(target)
228        }
229    }
230
231    fn compose_canonical_rdata<Target: Composer + ?Sized>(
232        &self,
233        target: &mut Target,
234    ) -> Result<(), Target::AppendError> {
235        self.rmailbx.compose_canonical(target)?;
236        self.emailbx.compose_canonical(target)
237    }
238}
239
240//--- Display
241
242impl<N: fmt::Display> fmt::Display for Minfo<N> {
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        write!(f, "{}. {}.", self.rmailbx, self.emailbx)
245    }
246}
247
248//--- ZonefileFmt
249
250impl<N: ToName> ZonefileFmt for Minfo<N> {
251    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
252        p.block(|p| {
253            p.write_token(self.rmailbx.fmt_with_dot())?;
254            p.write_comment("responsible mailbox")?;
255            p.write_token(self.emailbx.fmt_with_dot())?;
256            p.write_comment("error mailbox")
257        })
258    }
259}
260
261//============ Testing =======================================================
262
263#[cfg(test)]
264#[cfg(all(feature = "std", feature = "bytes"))]
265mod test {
266    use super::*;
267    use crate::base::name::Name;
268    use crate::base::rdata::test::{
269        test_compose_parse, test_rdlen, test_scan,
270    };
271    use core::str::FromStr;
272    use std::vec::Vec;
273
274    #[test]
275    #[allow(clippy::redundant_closure)] // lifetimes ...
276    fn minfo_compose_parse_scan() {
277        let rdata = Minfo::<Name<Vec<u8>>>::new(
278            Name::from_str("r.example.com").unwrap(),
279            Name::from_str("e.example.com").unwrap(),
280        );
281        test_rdlen(&rdata);
282        test_compose_parse(&rdata, |parser| Minfo::parse(parser));
283        test_scan(&["r.example.com", "e.example.com"], Minfo::scan, &rdata);
284    }
285
286    #[test]
287    fn minfo_octets_into() {
288        let minfo: Minfo<Name<Vec<u8>>> = Minfo::new(
289            "a.example".parse().unwrap(),
290            "b.example".parse().unwrap(),
291        );
292        let minfo_bytes: Minfo<Name<bytes::Bytes>> =
293            minfo.clone().octets_into();
294        assert_eq!(minfo.rmailbx(), minfo_bytes.rmailbx());
295        assert_eq!(minfo.emailbx(), minfo_bytes.emailbx());
296    }
297}