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