1use 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#[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
109impl<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
135impl<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
152impl<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
212impl<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 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) }
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
269impl<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#[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)] 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}