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