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}