Skip to main content

hickory_proto/rr/rdata/
hinfo.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! HINFO record for storing host information
9
10use alloc::{
11    boxed::Box,
12    string::{String, ToString},
13};
14use core::fmt;
15
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19use crate::{
20    error::*,
21    rr::{RData, RecordData, RecordType},
22    serialize::{binary::*, txt::ParseError},
23};
24
25/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987][rfc1035]
26///
27/// ```text
28/// 3.3.2. HINFO RDATA format
29///
30///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
31///     /                      CPU                      /
32///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
33///     /                       OS                      /
34///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
35///
36/// where:
37///
38/// CPU             A <character-string> which specifies the CPU type.
39///
40/// OS              A <character-string> which specifies the operating
41///                 system type.
42///
43/// Standard values for CPU and OS can be found in [RFC-1010].
44///
45/// HINFO records are used to acquire general information about a host.  The
46/// main use is for protocols such as FTP that can use special procedures
47/// when talking between machines or operating systems of the same type.
48/// ```
49///
50/// [rfc1035]: https://tools.ietf.org/html/rfc1035
51#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
52#[derive(Debug, PartialEq, Eq, Hash, Clone)]
53#[non_exhaustive]
54pub struct HINFO {
55    /// A `character-string` which specifies the CPU type.
56    pub cpu: Box<[u8]>,
57
58    /// A `character-string` which specifies the operating system type.
59    pub os: Box<[u8]>,
60}
61
62impl HINFO {
63    /// Creates a new HINFO record data.
64    ///
65    /// # Arguments
66    ///
67    /// * `cpu` - A `character-string` which specifies the CPU type.
68    /// * `os` - A `character-string` which specifies the operating system type.
69    ///
70    /// # Return value
71    ///
72    /// The new HINFO record data.
73    pub fn new(cpu: String, os: String) -> Self {
74        Self {
75            cpu: cpu.into_bytes().into_boxed_slice(),
76            os: os.into_bytes().into_boxed_slice(),
77        }
78    }
79
80    /// Creates a new HINFO record data from bytes.
81    /// Allows creating binary record data.
82    ///
83    /// # Arguments
84    ///
85    /// * `cpu` - A `character-string` which specifies the CPU type.
86    /// * `os` - A `character-string` which specifies the operating system type.
87    ///
88    /// # Return value
89    ///
90    /// The new HINFO record data.
91    pub fn from_bytes(cpu: Box<[u8]>, os: Box<[u8]>) -> Self {
92        Self { cpu, os }
93    }
94
95    /// Parse the RData from a set of Tokens
96    ///
97    /// ```text
98    /// IN HINFO DEC-2060 TOPS20
99    /// IN HINFO VAX-11/780 UNIX
100    /// ```
101    pub(crate) fn from_tokens<'i, I: Iterator<Item = &'i str>>(
102        mut tokens: I,
103    ) -> Result<Self, ParseError> {
104        let cpu = tokens
105            .next()
106            .ok_or_else(|| ParseError::MissingToken("cpu".to_string()))
107            .map(ToString::to_string)?;
108        let os = tokens
109            .next()
110            .ok_or_else(|| ParseError::MissingToken("os".to_string()))
111            .map(ToString::to_string)?;
112        Ok(Self::new(cpu, os))
113    }
114}
115
116impl BinEncodable for HINFO {
117    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
118        encoder.emit_character_data(&self.cpu)?;
119        encoder.emit_character_data(&self.os)?;
120
121        Ok(())
122    }
123}
124
125impl<'r> BinDecodable<'r> for HINFO {
126    fn read(decoder: &mut BinDecoder<'r>) -> Result<Self, DecodeError> {
127        let cpu = decoder.read_character_data()?
128        .unverified(/*any data should be validate in HINFO CPU usage*/)
129        .to_vec()
130        .into_boxed_slice();
131        let os = decoder.read_character_data()?
132        .unverified(/*any data should be validate in HINFO OS usage*/)
133        .to_vec()
134        .into_boxed_slice();
135
136        Ok(Self { cpu, os })
137    }
138}
139
140impl RecordData for HINFO {
141    fn try_borrow(data: &RData) -> Option<&Self> {
142        match data {
143            RData::HINFO(csync) => Some(csync),
144            _ => None,
145        }
146    }
147
148    fn record_type(&self) -> RecordType {
149        RecordType::HINFO
150    }
151
152    fn into_rdata(self) -> RData {
153        RData::HINFO(self)
154    }
155}
156
157/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
158///
159/// ```text
160/// HINFO (Host Info)
161///
162///            <host>   [<ttl>] [<class>]   HINFO   <hardware>   <software>
163///
164///    The HINFO record gives information about a particular host.  The data
165///    is two strings separated by whitespace.  The first string is a
166///    hardware description and the second is software.  The hardware is
167///    usually a manufacturer name followed by a dash and model designation.
168///    The software string is usually the name of the operating system.
169///
170///    Official HINFO types can be found in the latest Assigned Numbers RFC,
171///    the latest of which is RFC-1010.  The Hardware type is called the
172///    Machine name and the Software type is called the System name.
173///
174///    Some sample HINFO records:
175///
176///            SRI-NIC.ARPA.           HINFO   DEC-2060 TOPS20
177///            UCBARPA.Berkeley.EDU.   HINFO   VAX-11/780 UNIX
178/// ```
179impl fmt::Display for HINFO {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
181        write!(
182            f,
183            "{cpu} {os}",
184            cpu = &String::from_utf8_lossy(&self.cpu),
185            os = &String::from_utf8_lossy(&self.os)
186        )?;
187        Ok(())
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    #![allow(clippy::dbg_macro, clippy::print_stdout)]
194
195    use alloc::{string::ToString, vec::Vec};
196    #[cfg(feature = "std")]
197    use std::println;
198
199    use super::*;
200
201    #[test]
202    fn test() {
203        let rdata = HINFO::new("cpu".to_string(), "os".to_string());
204
205        let mut bytes = Vec::new();
206        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
207        assert!(rdata.emit(&mut encoder).is_ok());
208        let bytes = encoder.into_bytes();
209
210        #[cfg(feature = "std")]
211        println!("bytes: {bytes:?}");
212
213        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
214        let read_rdata = HINFO::read(&mut decoder).expect("Decoding error");
215        assert_eq!(rdata, read_rdata);
216    }
217
218    #[test]
219    fn test_binary() {
220        let bin_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
221        let rdata = HINFO::from_bytes(
222            b"cpu".to_vec().into_boxed_slice(),
223            bin_data.into_boxed_slice(),
224        );
225
226        let mut bytes = Vec::new();
227        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
228        assert!(rdata.emit(&mut encoder).is_ok());
229        let bytes = encoder.into_bytes();
230
231        #[cfg(feature = "std")]
232        println!("bytes: {bytes:?}");
233
234        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
235        let read_rdata = HINFO::read(&mut decoder).expect("Decoding error");
236        assert_eq!(rdata, read_rdata);
237    }
238
239    #[test]
240    fn test_parsing() {
241        // IN HINFO DEC-2060 TOPS20
242        assert_eq!(
243            HINFO::from_tokens(vec!["DEC-2060", "TOPS20"].into_iter())
244                .expect("failed to parse NAPTR"),
245            HINFO::new("DEC-2060".to_string(), "TOPS20".to_string()),
246        );
247    }
248
249    #[test]
250    fn test_parsing_fails() {
251        // IN HINFO DEC-2060 TOPS20
252        assert!(HINFO::from_tokens(vec!["DEC-2060"].into_iter()).is_err());
253        assert!(HINFO::from_tokens(vec![].into_iter()).is_err());
254    }
255}