1use 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#[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 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
115impl<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
141impl<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
158impl<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
218impl<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 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) }
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
275impl<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
287impl<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#[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)] 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}