1use 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#[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 pub(crate) const RTYPE: Rtype = Rtype::MX;
39}
40
41impl<N> Mx<N> {
42 pub fn new(preference: u16, exchange: N) -> Self {
44 Mx {
45 preference,
46 exchange,
47 }
48 }
49
50 pub fn preference(&self) -> u16 {
55 self.preference
56 }
57
58 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
91impl<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
118impl<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
133impl<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
169impl<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
223impl<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
231impl<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#[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)] 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