hickory_proto/rr/rdata/mx.rs
1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! mail exchange, email, record
9
10use alloc::string::ToString;
11use core::fmt;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 error::ProtoResult,
18 rr::{RData, RecordData, RecordType, domain::Name},
19 serialize::{binary::*, txt::ParseError},
20};
21
22/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
23///
24/// ```text
25/// 3.3.9. MX RDATA format
26///
27/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
28/// | PREFERENCE |
29/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30/// / EXCHANGE /
31/// / /
32/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
33///
34/// MX records cause type A additional section processing for the host
35/// specified by EXCHANGE. The use of MX RRs is explained in detail in
36/// [RFC-974].
37///
38/// ```
39#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
40#[derive(Debug, PartialEq, Eq, Hash, Clone)]
41#[non_exhaustive]
42pub struct MX {
43 /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
44 ///
45 /// ```text
46 /// PREFERENCE A 16 bit integer which specifies the preference given to
47 /// this RR among others at the same owner. Lower values
48 /// are preferred.
49 /// ```
50 pub preference: u16,
51
52 /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
53 ///
54 /// ```text
55 /// EXCHANGE A <domain-name> which specifies a host willing to act as
56 /// a mail exchange for the owner name.
57 /// ```
58 pub exchange: Name,
59}
60
61impl MX {
62 /// Constructs a new MX RData
63 ///
64 /// # Arguments
65 ///
66 /// * `preference` - weight of this MX record as opposed to others, lower values have the higher preference
67 /// * `exchange` - Name labels for the mail server
68 ///
69 /// # Returns
70 ///
71 /// A new MX RData for use in a Resource Record
72 pub fn new(preference: u16, exchange: Name) -> Self {
73 Self {
74 preference,
75 exchange,
76 }
77 }
78
79 /// Parse the RData from a set of Tokens
80 pub(crate) fn from_tokens<'i, I: Iterator<Item = &'i str>>(
81 mut tokens: I,
82 origin: Option<&Name>,
83 ) -> Result<Self, ParseError> {
84 let preference: u16 = tokens
85 .next()
86 .ok_or_else(|| ParseError::MissingToken("preference".to_string()))
87 .and_then(|s| s.parse().map_err(Into::into))?;
88 let exchange: Name = tokens
89 .next()
90 .ok_or_else(|| ParseError::MissingToken("exchange".to_string()))
91 .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?;
92
93 Ok(Self::new(preference, exchange))
94 }
95}
96
97impl BinEncodable for MX {
98 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
99 let mut encoder = encoder.with_rdata_behavior(RDataEncoding::StandardRecord);
100
101 encoder.emit_u16(self.preference)?;
102 self.exchange.emit(&mut encoder)?;
103
104 Ok(())
105 }
106}
107
108impl<'r> BinDecodable<'r> for MX {
109 fn read(decoder: &mut BinDecoder<'r>) -> Result<Self, DecodeError> {
110 Ok(Self::new(
111 decoder.read_u16()?.unverified(/*any u16 is valid*/),
112 Name::read(decoder)?,
113 ))
114 }
115}
116
117impl RecordData for MX {
118 fn try_borrow(data: &RData) -> Option<&Self> {
119 match data {
120 RData::MX(csync) => Some(csync),
121 _ => None,
122 }
123 }
124
125 fn record_type(&self) -> RecordType {
126 RecordType::MX
127 }
128
129 fn into_rdata(self) -> RData {
130 RData::MX(self)
131 }
132}
133
134/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
135///
136/// ```text
137/// MX (Mail Exchanger) (See RFC-974 for more details.)
138///
139/// <name> [<ttl>] [<class>] MX <preference> <host>
140///
141/// MX records specify where mail for a domain name should be delivered.
142/// There may be multiple MX records for a particular name. The
143/// preference value specifies the order a mailer should try multiple MX
144/// records when delivering mail. Zero is the highest preference.
145/// Multiple records for the same name may have the same preference.
146///
147/// A host BAR.FOO.COM may want its mail to be delivered to the host
148/// PO.FOO.COM and would then use the MX record:
149///
150/// BAR.FOO.COM. MX 10 PO.FOO.COM.
151///
152/// A host BAZ.FOO.COM may want its mail to be delivered to one of three
153/// different machines, in the following order:
154///
155/// BAZ.FOO.COM. MX 10 PO1.FOO.COM.
156/// MX 20 PO2.FOO.COM.
157/// MX 30 PO3.FOO.COM.
158///
159/// An entire domain of hosts not connected to the Internet may want
160/// their mail to go through a mail gateway that knows how to deliver
161/// mail to them. If they would like mail addressed to any host in the
162/// domain FOO.COM to go through the mail gateway they might use:
163///
164/// FOO.COM. MX 10 RELAY.CS.NET.
165/// *.FOO.COM. MX 20 RELAY.CS.NET.
166///
167/// Note that you can specify a wildcard in the MX record to match on
168/// anything in FOO.COM, but that it won't match a plain FOO.COM.
169impl fmt::Display for MX {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
171 write!(
172 f,
173 "{pref} {ex}",
174 pref = &self.preference,
175 ex = self.exchange
176 )
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 #![allow(clippy::dbg_macro, clippy::print_stdout)]
183
184 use alloc::vec::Vec;
185 #[cfg(feature = "std")]
186 use std::println;
187
188 use super::*;
189
190 #[test]
191 fn test() {
192 use core::str::FromStr;
193
194 let rdata = MX::new(16, Name::from_str("mail.example.com.").unwrap());
195
196 let mut bytes = Vec::new();
197 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
198 assert!(rdata.emit(&mut encoder).is_ok());
199 let bytes = encoder.into_bytes();
200
201 #[cfg(feature = "std")]
202 println!("bytes: {bytes:?}");
203
204 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
205 let read_rdata = MX::read(&mut decoder).expect("Decoding error");
206 assert_eq!(rdata, read_rdata);
207 }
208}