domain/rdata/rfc1035/
mx.rs

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