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::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#[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
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
114impl<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
140impl<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
157impl<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
217impl<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 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) }
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
274impl<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
286impl<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#[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)] 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}