domain/rdata/rfc1035/
hinfo.rs

1//! Record data for the HINFO record.
2//!
3//! This is a private module. It’s content is re-exported by the parent.
4
5use 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//------------ Hinfo --------------------------------------------------------
20
21/// Hinfo record data.
22///
23/// Hinfo records are used to acquire general information about a host,
24/// specifically the CPU type and operating system type.
25///
26/// The Hinfo type is defined in [RFC 1035, section 3.3.2][1].
27///
28/// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.2
29#[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    /// The rtype of this record data type.
48    pub(crate) const RTYPE: Rtype = Rtype::HINFO;
49}
50
51impl<Octs> Hinfo<Octs> {
52    /// Creates a new Hinfo record data from the components.
53    pub fn new(cpu: CharStr<Octs>, os: CharStr<Octs>) -> Self {
54        Hinfo { cpu, os }
55    }
56
57    /// The CPU type of the host.
58    pub fn cpu(&self) -> &CharStr<Octs> {
59        &self.cpu
60    }
61
62    /// The operating system type of the host.
63    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
95//--- OctetsFrom
96
97impl<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
111//--- PartialEq and Eq
112
113impl<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
125//--- PartialOrd, CanonicalOrd, and Ord
126
127impl<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
165//--- Hash
166
167impl<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
174//--- RecordData, ParseRecordData, ComposeRecordData
175
176impl<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
219//--- Display
220
221impl<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
232//--- Debug
233
234impl<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
243//--- ZonefileFmt
244
245impl<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//============ Testing =======================================================
257
258#[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)] // lifetimes ...
269    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}