domain/rdata/
srv.rs

1//! Record data from [RFC 2782]: SRV records.
2//!
3//! This RFC defines the Srv record type.
4//!
5//! [RFC 2782]: https://tools.ietf.org/html/rfc2782
6
7use crate::base::cmp::CanonicalOrd;
8use crate::base::iana::Rtype;
9use crate::base::name::{FlattenInto, ParsedName, ToName};
10use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData};
11use crate::base::scan::{Scan, Scanner};
12use crate::base::wire::{Compose, Composer, Parse, ParseError};
13use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
14use core::cmp::Ordering;
15use core::fmt;
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19//------------ Srv ---------------------------------------------------------
20
21#[derive(Clone, Debug, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct Srv<N> {
24    priority: u16,
25    weight: u16,
26    port: u16,
27    target: N,
28}
29
30impl Srv<()> {
31    /// The rtype of this record data type.
32    pub(crate) const RTYPE: Rtype = Rtype::SRV;
33}
34
35impl<N> Srv<N> {
36    pub fn new(priority: u16, weight: u16, port: u16, target: N) -> Self {
37        Srv {
38            priority,
39            weight,
40            port,
41            target,
42        }
43    }
44
45    pub fn into_target(self) -> N {
46        self.target
47    }
48
49    pub fn priority(&self) -> u16 {
50        self.priority
51    }
52
53    pub fn weight(&self) -> u16 {
54        self.weight
55    }
56
57    pub fn port(&self) -> u16 {
58        self.port
59    }
60
61    pub fn target(&self) -> &N {
62        &self.target
63    }
64
65    pub(super) fn convert_octets<Target: OctetsFrom<N>>(
66        self,
67    ) -> Result<Srv<Target>, Target::Error> {
68        Ok(Srv::new(
69            self.priority,
70            self.weight,
71            self.port,
72            self.target.try_octets_into()?,
73        ))
74    }
75
76    pub(super) fn flatten<TargetName>(
77        self,
78    ) -> Result<Srv<TargetName>, N::AppendError>
79    where
80        N: FlattenInto<TargetName>,
81    {
82        Ok(Srv::new(
83            self.priority,
84            self.weight,
85            self.port,
86            self.target.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(
94            u16::scan(scanner)?,
95            u16::scan(scanner)?,
96            u16::scan(scanner)?,
97            scanner.scan_name()?,
98        ))
99    }
100}
101
102impl<Octs> Srv<ParsedName<Octs>> {
103    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
104        parser: &mut Parser<'a, Src>,
105    ) -> Result<Self, ParseError> {
106        Ok(Self::new(
107            u16::parse(parser)?,
108            u16::parse(parser)?,
109            u16::parse(parser)?,
110            ParsedName::parse(parser)?,
111        ))
112    }
113}
114
115//--- OctetsFrom and FlattenInto
116
117impl<Name, SrcName> OctetsFrom<Srv<SrcName>> for Srv<Name>
118where
119    Name: OctetsFrom<SrcName>,
120{
121    type Error = Name::Error;
122
123    fn try_octets_from(source: Srv<SrcName>) -> Result<Self, Self::Error> {
124        Ok(Srv::new(
125            source.priority,
126            source.weight,
127            source.port,
128            Name::try_octets_from(source.target)?,
129        ))
130    }
131}
132
133impl<Name: FlattenInto<TName>, TName> FlattenInto<Srv<TName>> for Srv<Name> {
134    type AppendError = Name::AppendError;
135
136    fn try_flatten_into(self) -> Result<Srv<TName>, Name::AppendError> {
137        self.flatten()
138    }
139}
140
141//--- PartialEq and Eq
142
143impl<N, NN> PartialEq<Srv<NN>> for Srv<N>
144where
145    N: ToName,
146    NN: ToName,
147{
148    fn eq(&self, other: &Srv<NN>) -> bool {
149        self.priority == other.priority
150            && self.weight == other.weight
151            && self.port == other.port
152            && self.target.name_eq(&other.target)
153    }
154}
155
156impl<N: ToName> Eq for Srv<N> {}
157
158//--- PartialOrd, Ord, and CanonicalOrd
159
160impl<N, NN> PartialOrd<Srv<NN>> for Srv<N>
161where
162    N: ToName,
163    NN: ToName,
164{
165    fn partial_cmp(&self, other: &Srv<NN>) -> Option<Ordering> {
166        match self.priority.partial_cmp(&other.priority) {
167            Some(Ordering::Equal) => {}
168            other => return other,
169        }
170        match self.weight.partial_cmp(&other.weight) {
171            Some(Ordering::Equal) => {}
172            other => return other,
173        }
174        match self.port.partial_cmp(&other.port) {
175            Some(Ordering::Equal) => {}
176            other => return other,
177        }
178        Some(self.target.name_cmp(&other.target))
179    }
180}
181
182impl<N: ToName> Ord for Srv<N> {
183    fn cmp(&self, other: &Self) -> Ordering {
184        match self.priority.cmp(&other.priority) {
185            Ordering::Equal => {}
186            other => return other,
187        }
188        match self.weight.cmp(&other.weight) {
189            Ordering::Equal => {}
190            other => return other,
191        }
192        match self.port.cmp(&other.port) {
193            Ordering::Equal => {}
194            other => return other,
195        }
196        self.target.name_cmp(&other.target)
197    }
198}
199
200impl<N: ToName, NN: ToName> CanonicalOrd<Srv<NN>> for Srv<N> {
201    fn canonical_cmp(&self, other: &Srv<NN>) -> Ordering {
202        match self.priority.cmp(&other.priority) {
203            Ordering::Equal => {}
204            other => return other,
205        }
206        match self.weight.cmp(&other.weight) {
207            Ordering::Equal => {}
208            other => return other,
209        }
210        match self.port.cmp(&other.port) {
211            Ordering::Equal => {}
212            other => return other,
213        }
214        self.target.lowercase_composed_cmp(&other.target)
215    }
216}
217
218//--- RecordData, ParseRecordData, ComposeRecordData
219
220impl<N> RecordData for Srv<N> {
221    fn rtype(&self) -> Rtype {
222        Srv::RTYPE
223    }
224}
225
226impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
227    for Srv<ParsedName<Octs::Range<'a>>>
228{
229    fn parse_rdata(
230        rtype: Rtype,
231        parser: &mut Parser<'a, Octs>,
232    ) -> Result<Option<Self>, ParseError> {
233        if rtype == Srv::RTYPE {
234            Self::parse(parser).map(Some)
235        } else {
236            Ok(None)
237        }
238    }
239}
240
241impl<Name: ToName> ComposeRecordData for Srv<Name> {
242    fn rdlen(&self, _compress: bool) -> Option<u16> {
243        // SRV records are not compressed.
244        Some(self.target.compose_len() + 6)
245    }
246
247    fn compose_rdata<Target: Composer + ?Sized>(
248        &self,
249        target: &mut Target,
250    ) -> Result<(), Target::AppendError> {
251        self.compose_head(target)?;
252        self.target.compose(target)
253    }
254
255    fn compose_canonical_rdata<Target: Composer + ?Sized>(
256        &self,
257        target: &mut Target,
258    ) -> Result<(), Target::AppendError> {
259        self.compose_head(target)?;
260        self.target.compose_canonical(target) // ... but are lowercased.
261    }
262}
263
264impl<Name: ToName> Srv<Name> {
265    fn compose_head<Target: Composer + ?Sized>(
266        &self,
267        target: &mut Target,
268    ) -> Result<(), Target::AppendError> {
269        self.priority.compose(target)?;
270        self.weight.compose(target)?;
271        self.port.compose(target)
272    }
273}
274
275//--- Display
276
277impl<N: fmt::Display> fmt::Display for Srv<N> {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(
280            f,
281            "{} {} {} {}",
282            self.priority, self.weight, self.port, self.target
283        )
284    }
285}
286
287//--- ZonefileFmt
288
289impl<N: ToName> ZonefileFmt for Srv<N> {
290    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
291        p.block(|p| {
292            p.write_token(self.priority)?;
293            p.write_comment("priority")?;
294            p.write_token(self.weight)?;
295            p.write_comment("weight")?;
296            p.write_token(self.port)?;
297            p.write_comment("port")?;
298            p.write_token(self.target.fmt_with_dot())
299        })
300    }
301}
302
303//============ Testing ======================================================
304
305#[cfg(test)]
306#[cfg(all(feature = "std", feature = "bytes"))]
307mod test {
308    use super::*;
309    use crate::base::name::Name;
310    use crate::base::rdata::test::{
311        test_compose_parse, test_rdlen, test_scan,
312    };
313    use core::str::FromStr;
314    use std::vec::Vec;
315
316    #[test]
317    #[allow(clippy::redundant_closure)] // lifetimes ...
318    fn srv_compose_parse_scan() {
319        let rdata = Srv::new(
320            10,
321            11,
322            12,
323            Name::<Vec<u8>>::from_str("example.com.").unwrap(),
324        );
325        test_rdlen(&rdata);
326        test_compose_parse(&rdata, |parser| Srv::parse(parser));
327        test_scan(&["10", "11", "12", "example.com."], Srv::scan, &rdata);
328    }
329}