1use crate::base::charstr::CharStr;
6use crate::base::cmp::CanonicalOrd;
7use crate::base::iana::Rtype;
8use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData};
9use crate::base::scan::Scanner;
10use crate::base::wire::{Composer, ParseError};
11use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
12use core::cmp::Ordering;
13use core::{fmt, hash};
14#[cfg(feature = "serde")]
15use octseq::builder::{EmptyBuilder, FromBuilder};
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19#[derive(Clone)]
30#[cfg_attr(
31 feature = "serde",
32 derive(serde::Serialize, serde::Deserialize),
33 serde(bound(
34 serialize = "Octs: AsRef<[u8]> + octseq::serde::SerializeOctets",
35 deserialize = "Octs: \
36 FromBuilder \
37 + octseq::serde::DeserializeOctets<'de>, \
38 <Octs as FromBuilder>::Builder: AsRef<[u8]> + EmptyBuilder ",
39 ))
40)]
41pub struct Hinfo<Octs> {
42 cpu: CharStr<Octs>,
43 os: CharStr<Octs>,
44}
45
46impl Hinfo<()> {
47 pub(crate) const RTYPE: Rtype = Rtype::HINFO;
49}
50
51impl<Octs> Hinfo<Octs> {
52 pub fn new(cpu: CharStr<Octs>, os: CharStr<Octs>) -> Self {
54 Hinfo { cpu, os }
55 }
56
57 pub fn cpu(&self) -> &CharStr<Octs> {
59 &self.cpu
60 }
61
62 pub fn os(&self) -> &CharStr<Octs> {
64 &self.os
65 }
66
67 pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<Octs>>(
68 self,
69 ) -> Result<Hinfo<Target>, Target::Error> {
70 Ok(Hinfo::new(
71 self.cpu.try_octets_into()?,
72 self.os.try_octets_into()?,
73 ))
74 }
75
76 pub(in crate::rdata) fn flatten<Target: OctetsFrom<Octs>>(
77 self,
78 ) -> Result<Hinfo<Target>, Target::Error> {
79 self.convert_octets()
80 }
81
82 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
83 parser: &mut Parser<'a, Src>,
84 ) -> Result<Self, ParseError> {
85 Ok(Self::new(CharStr::parse(parser)?, CharStr::parse(parser)?))
86 }
87
88 pub fn scan<S: Scanner<Octets = Octs>>(
89 scanner: &mut S,
90 ) -> Result<Self, S::Error> {
91 Ok(Self::new(scanner.scan_charstr()?, scanner.scan_charstr()?))
92 }
93}
94
95impl<Octs, SrcOcts> OctetsFrom<Hinfo<SrcOcts>> for Hinfo<Octs>
98where
99 Octs: OctetsFrom<SrcOcts>,
100{
101 type Error = Octs::Error;
102
103 fn try_octets_from(source: Hinfo<SrcOcts>) -> Result<Self, Self::Error> {
104 Ok(Hinfo::new(
105 CharStr::try_octets_from(source.cpu)?,
106 CharStr::try_octets_from(source.os)?,
107 ))
108 }
109}
110
111impl<Octs, Other> PartialEq<Hinfo<Other>> for Hinfo<Octs>
114where
115 Octs: AsRef<[u8]>,
116 Other: AsRef<[u8]>,
117{
118 fn eq(&self, other: &Hinfo<Other>) -> bool {
119 self.cpu.eq(&other.cpu) && self.os.eq(&other.os)
120 }
121}
122
123impl<Octs: AsRef<[u8]>> Eq for Hinfo<Octs> {}
124
125impl<Octs, Other> PartialOrd<Hinfo<Other>> for Hinfo<Octs>
128where
129 Octs: AsRef<[u8]>,
130 Other: AsRef<[u8]>,
131{
132 fn partial_cmp(&self, other: &Hinfo<Other>) -> Option<Ordering> {
133 match self.cpu.partial_cmp(&other.cpu) {
134 Some(Ordering::Equal) => {}
135 other => return other,
136 }
137 self.os.partial_cmp(&other.os)
138 }
139}
140
141impl<Octs, Other> CanonicalOrd<Hinfo<Other>> for Hinfo<Octs>
142where
143 Octs: AsRef<[u8]>,
144 Other: AsRef<[u8]>,
145{
146 fn canonical_cmp(&self, other: &Hinfo<Other>) -> Ordering {
147 match self.cpu.canonical_cmp(&other.cpu) {
148 Ordering::Equal => {}
149 other => return other,
150 }
151 self.os.canonical_cmp(&other.os)
152 }
153}
154
155impl<Octs: AsRef<[u8]>> Ord for Hinfo<Octs> {
156 fn cmp(&self, other: &Self) -> Ordering {
157 match self.cpu.cmp(&other.cpu) {
158 Ordering::Equal => {}
159 other => return other,
160 }
161 self.os.cmp(&other.os)
162 }
163}
164
165impl<Octs: AsRef<[u8]>> hash::Hash for Hinfo<Octs> {
168 fn hash<H: hash::Hasher>(&self, state: &mut H) {
169 self.cpu.hash(state);
170 self.os.hash(state);
171 }
172}
173
174impl<Octs> RecordData for Hinfo<Octs> {
177 fn rtype(&self) -> Rtype {
178 Hinfo::RTYPE
179 }
180}
181
182impl<'a, Octs> ParseRecordData<'a, Octs> for Hinfo<Octs::Range<'a>>
183where
184 Octs: Octets + ?Sized,
185{
186 fn parse_rdata(
187 rtype: Rtype,
188 parser: &mut Parser<'a, Octs>,
189 ) -> Result<Option<Self>, ParseError> {
190 if rtype == Hinfo::RTYPE {
191 Self::parse(parser).map(Some)
192 } else {
193 Ok(None)
194 }
195 }
196}
197
198impl<Octs: AsRef<[u8]>> ComposeRecordData for Hinfo<Octs> {
199 fn rdlen(&self, _compress: bool) -> Option<u16> {
200 Some(self.cpu.compose_len() + self.os.compose_len())
201 }
202
203 fn compose_rdata<Target: Composer + ?Sized>(
204 &self,
205 target: &mut Target,
206 ) -> Result<(), Target::AppendError> {
207 self.cpu.compose(target)?;
208 self.os.compose(target)
209 }
210
211 fn compose_canonical_rdata<Target: Composer + ?Sized>(
212 &self,
213 target: &mut Target,
214 ) -> Result<(), Target::AppendError> {
215 self.compose_rdata(target)
216 }
217}
218
219impl<Octs: AsRef<[u8]>> fmt::Display for Hinfo<Octs> {
222 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223 write!(
224 f,
225 "{} {}",
226 self.cpu.display_quoted(),
227 self.os.display_quoted()
228 )
229 }
230}
231
232impl<Octs: AsRef<[u8]>> fmt::Debug for Hinfo<Octs> {
235 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236 f.debug_struct("Hinfo")
237 .field("cpu", &self.cpu)
238 .field("os", &self.os)
239 .finish()
240 }
241}
242
243impl<Octs: AsRef<[u8]>> ZonefileFmt for Hinfo<Octs> {
246 fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
247 p.block(|p| {
248 p.write_token(self.cpu.display_quoted())?;
249 p.write_comment("cpu")?;
250 p.write_token(self.os.display_quoted())?;
251 p.write_comment("os")
252 })
253 }
254}
255
256#[cfg(test)]
259#[cfg(all(feature = "std", feature = "bytes"))]
260mod test {
261 use super::*;
262 use crate::base::rdata::test::{
263 test_compose_parse, test_rdlen, test_scan,
264 };
265 use std::vec::Vec;
266
267 #[test]
268 #[allow(clippy::redundant_closure)] fn hinfo_compose_parse_scan() {
270 let rdata = Hinfo::new(
271 CharStr::from_octets("cpu").unwrap(),
272 CharStr::from_octets("os").unwrap(),
273 );
274 test_rdlen(&rdata);
275 test_compose_parse(&rdata, |parser| Hinfo::parse(parser));
276 test_scan(&["cpu", "os"], Hinfo::scan, &rdata);
277 }
278
279 #[test]
280 fn hinfo_octets_into() {
281 let hinfo: Hinfo<Vec<u8>> =
282 Hinfo::new("1234".parse().unwrap(), "abcd".parse().unwrap());
283 let hinfo_bytes: Hinfo<bytes::Bytes> = hinfo.clone().octets_into();
284 assert_eq!(hinfo.cpu(), hinfo_bytes.cpu());
285 assert_eq!(hinfo.os(), hinfo_bytes.os());
286 }
287
288 #[test]
289 fn hinfo_display() {
290 let hinfo: Hinfo<Vec<u8>> = Hinfo::new(
291 "Windows".parse().unwrap(),
292 "Windows Server".parse().unwrap(),
293 );
294 assert_eq!(format!("{}", hinfo), r#""Windows" "Windows Server""#);
295 }
296}