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