Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add HINFO record type #1361

Merged
merged 2 commits into from Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/client/src/serialize/txt/parse_rdata.rs
Expand Up @@ -45,6 +45,7 @@ impl RDataParser for RData {
RecordType::AXFR => panic!("parsing AXFR doesn't make sense"), // valid panic, never should happen
RecordType::CAA => caa::parse(tokens).map(RData::CAA)?,
RecordType::CNAME => RData::CNAME(name::parse(tokens, origin)?),
RecordType::HINFO => RData::HINFO(hinfo::parse(tokens)?),
RecordType::IXFR => panic!("parsing IXFR doesn't make sense"), // valid panic, never should happen
RecordType::MX => RData::MX(mx::parse(tokens, origin)?),
RecordType::NAPTR => RData::NAPTR(naptr::parse(tokens, origin)?),
Expand Down
47 changes: 47 additions & 0 deletions crates/client/src/serialize/txt/rdata_parsers/hinfo.rs
@@ -0,0 +1,47 @@
// Copyright 2015-2021 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! HINFO record for storing host information

use crate::error::*;
use crate::rr::rdata::HINFO;

/// Parse the RData from a set of Tokens
///
/// ```text
/// IN HINFO DEC-2060 TOPS20
/// IN HINFO VAX-11/780 UNIX
/// ```
pub fn parse<'i, I: Iterator<Item = &'i str>>(mut tokens: I) -> ParseResult<HINFO> {
let cpu = tokens
.next()
.ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("cpu".to_string())))
.map(ToString::to_string)?;
let os = tokens
.next()
.ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("os".to_string())))
.map(ToString::to_string)?;
Ok(HINFO::new(cpu, os))
}

#[test]
fn test_parsing() {
// IN HINFO DEC-2060 TOPS20

assert_eq!(
parse(vec!["DEC-2060", "TOPS20"].into_iter()).expect("failed to parse NAPTR"),
HINFO::new("DEC-2060".to_string(), "TOPS20".to_string()),
);
}

#[test]
fn test_parsing_fails() {
// IN HINFO DEC-2060 TOPS20

assert!(parse(vec!["DEC-2060"].into_iter()).is_err());
assert!(parse(vec![].into_iter()).is_err());
}
1 change: 1 addition & 0 deletions crates/client/src/serialize/txt/rdata_parsers/mod.rs
Expand Up @@ -22,6 +22,7 @@
pub mod a;
pub mod aaaa;
pub mod caa;
pub mod hinfo;
pub mod mx;
pub mod name;
pub mod naptr;
Expand Down
188 changes: 188 additions & 0 deletions crates/proto/src/rr/rdata/hinfo.rs
@@ -0,0 +1,188 @@
// Copyright 2015-2021 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! HINFO record for storing host information

use std::fmt;

use crate::error::*;
use crate::serialize::binary::*;

/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987][rfc1035]
///
/// ```text
/// 3.3.2. HINFO RDATA format
///
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// / CPU /
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// / OS /
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
///
/// where:
///
/// CPU A <character-string> which specifies the CPU type.
///
/// OS A <character-string> which specifies the operating
/// system type.
///
/// Standard values for CPU and OS can be found in [RFC-1010].
///
/// HINFO records are used to acquire general information about a host. The
/// main use is for protocols such as FTP that can use special procedures
/// when talking between machines or operating systems of the same type.
/// ```
///
/// [rfc1035]: https://tools.ietf.org/html/rfc1035
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct HINFO {
cpu: Box<[u8]>,
os: Box<[u8]>,
}

impl HINFO {
/// Creates a new HINFO record data.
///
/// # Arguments
///
/// * `cpu` - A <character-string> which specifies the CPU type.
/// * `os` - A <character-string> which specifies the operating system type.
///
/// # Return value
///
/// The new HINFO record data.
pub fn new(cpu: String, os: String) -> HINFO {
HINFO {
cpu: cpu.into_bytes().into_boxed_slice(),
os: os.into_bytes().into_boxed_slice(),
}
}

/// Creates a new HINFO record data from bytes.
/// Allows creating binary record data.
///
/// # Arguments
///
/// * `cpu` - A <character-string> which specifies the CPU type.
/// * `os` - A <character-string> which specifies the operating system type.
///
/// # Return value
///
/// The new HINFO record data.
pub fn from_bytes(cpu: Box<[u8]>, os: Box<[u8]>) -> HINFO {
HINFO { cpu, os }
}

/// A <character-string> which specifies the CPU type.
pub fn cpu(&self) -> &[u8] {
&self.cpu
}

/// A <character-string> which specifies the operating system type.
pub fn os(&self) -> &[u8] {
&self.os
}
}

/// Read the RData from the given Decoder
pub fn read(decoder: &mut BinDecoder<'_>) -> ProtoResult<HINFO> {
let cpu = decoder.read_character_data()?
.unverified(/*any data should be validate in HINFO CPU usage*/)
.to_vec()
.into_boxed_slice();
let os = decoder.read_character_data()?
.unverified(/*any data should be validate in HINFO OS usage*/)
.to_vec()
.into_boxed_slice();

Ok(HINFO { cpu, os })
}

/// Write the RData from the given Decoder
pub fn emit(encoder: &mut BinEncoder<'_>, hinfo: &HINFO) -> ProtoResult<()> {
encoder.emit_character_data(&hinfo.cpu)?;
encoder.emit_character_data(&hinfo.os)?;

Ok(())
}

/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
///
/// ```text
/// HINFO (Host Info)
///
/// <host> [<ttl>] [<class>] HINFO <hardware> <software>
///
/// The HINFO record gives information about a particular host. The data
/// is two strings separated by whitespace. The first string is a
/// hardware description and the second is software. The hardware is
/// usually a manufacturer name followed by a dash and model designation.
/// The software string is usually the name of the operating system.
///
/// Official HINFO types can be found in the latest Assigned Numbers RFC,
/// the latest of which is RFC-1010. The Hardware type is called the
/// Machine name and the Software type is called the System name.
///
/// Some sample HINFO records:
///
/// SRI-NIC.ARPA. HINFO DEC-2060 TOPS20
/// UCBARPA.Berkeley.EDU. HINFO VAX-11/780 UNIX
/// ```
impl fmt::Display for HINFO {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{cpu} {os}",
cpu = &String::from_utf8_lossy(&self.cpu),
os = &String::from_utf8_lossy(&self.os)
)?;
Ok(())
}
}

#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]

use super::*;

#[test]
pub fn test() {
let rdata = HINFO::new("cpu".to_string(), "os".to_string());

let mut bytes = Vec::new();
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
assert!(emit(&mut encoder, &rdata).is_ok());
let bytes = encoder.into_bytes();

println!("bytes: {:?}", bytes);

let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let read_rdata = read(&mut decoder).expect("Decoding error");
assert_eq!(rdata, read_rdata);
}

#[test]
fn test_binary() {
let bin_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
let rdata = HINFO::from_bytes(
b"cpu".to_vec().into_boxed_slice(),
bin_data.into_boxed_slice(),
);

let mut bytes = Vec::new();
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
assert!(emit(&mut encoder, &rdata).is_ok());
let bytes = encoder.into_bytes();

println!("bytes: {:?}", bytes);

let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let read_rdata = read(&mut decoder).expect("Decoding error");
assert_eq!(rdata, read_rdata);
}
}
2 changes: 2 additions & 0 deletions crates/proto/src/rr/rdata/mod.rs
Expand Up @@ -22,6 +22,7 @@
pub mod a;
pub mod aaaa;
pub mod caa;
pub mod hinfo;
pub mod mx;
pub mod name;
pub mod naptr;
Expand All @@ -35,6 +36,7 @@ pub mod tlsa;
pub mod txt;

pub use self::caa::CAA;
pub use self::hinfo::HINFO;
pub use self::mx::MX;
pub use self::naptr::NAPTR;
pub use self::null::NULL;
Expand Down
40 changes: 39 additions & 1 deletion crates/proto/src/rr/record_data.rs
Expand Up @@ -27,7 +27,7 @@ use log::{trace, warn};

use super::domain::Name;
use super::rdata;
use super::rdata::{CAA, MX, NAPTR, NULL, OPENPGPKEY, OPT, SOA, SRV, SSHFP, TLSA, TXT};
use super::rdata::{CAA, HINFO, MX, NAPTR, NULL, OPENPGPKEY, OPT, SOA, SRV, SSHFP, TLSA, TXT};
use super::record_type::RecordType;
use crate::error::*;
use crate::serialize::binary::*;
Expand Down Expand Up @@ -169,6 +169,32 @@ pub enum RData {
/// ```
CNAME(Name),

/// ```text
/// 3.3.2. HINFO RDATA format
///
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// / CPU /
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// / OS /
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
///
/// where:
///
/// CPU A <character-string> which specifies the CPU type.
///
/// OS A <character-string> which specifies the operating
/// system type.
///
/// Standard values for CPU and OS can be found in [RFC-1010].
///
/// HINFO records are used to acquire general information about a host. The
/// main use is for protocols such as FTP that can use special procedures
/// when talking between machines or operating systems of the same type.
/// ```
///
/// `HINFO` is also used by [RFC 8482](https://tools.ietf.org/html/rfc8482)
HINFO(HINFO),

/// ```text
/// 3.3.9. MX RDATA format
///
Expand Down Expand Up @@ -642,6 +668,10 @@ impl RData {
trace!("reading CNAME");
rdata::name::read(decoder).map(RData::CNAME)
}
RecordType::HINFO => {
trace!("reading HINFO");
rdata::hinfo::read(decoder).map(RData::HINFO)
}
RecordType::ZERO => {
trace!("reading EMPTY");
return Ok(RData::ZERO);
Expand Down Expand Up @@ -800,6 +830,7 @@ impl RData {
RData::CNAME(ref name) | RData::NS(ref name) | RData::PTR(ref name) => {
rdata::name::emit(encoder, name)
}
RData::HINFO(ref hinfo) => rdata::hinfo::emit(encoder, hinfo),
RData::ZERO => Ok(()),
// to_lowercase for rfc4034 and rfc6840
RData::MX(ref mx) => rdata::mx::emit(encoder, mx),
Expand Down Expand Up @@ -838,6 +869,7 @@ impl RData {
RData::ANAME(..) => RecordType::ANAME,
RData::CAA(..) => RecordType::CAA,
RData::CNAME(..) => RecordType::CNAME,
RData::HINFO(..) => RecordType::HINFO,
RData::MX(..) => RecordType::MX,
RData::NAPTR(..) => RecordType::NAPTR,
RData::NS(..) => RecordType::NS,
Expand Down Expand Up @@ -880,6 +912,7 @@ impl fmt::Display for RData {
RData::CAA(ref caa) => w(f, caa),
// to_lowercase for rfc4034 and rfc6840
RData::CNAME(ref name) | RData::NS(ref name) | RData::PTR(ref name) => w(f, name),
RData::HINFO(ref hinfo) => w(f, hinfo),
RData::ZERO => Ok(()),
// to_lowercase for rfc4034 and rfc6840
RData::MX(ref mx) => w(f, mx),
Expand Down Expand Up @@ -1025,6 +1058,10 @@ mod tests {
b'm', b'p', b'l', b'e', 3, b'c', b'o', b'm', 0,
],
),
(
RData::HINFO(HINFO::new("cpu".to_string(), "os".to_string())),
vec![3, b'c', b'p', b'u', 2, b'o', b's'],
),
]
}

Expand Down Expand Up @@ -1120,6 +1157,7 @@ mod tests {
RData::ANAME(..) => RecordType::ANAME,
RData::CAA(..) => RecordType::CAA,
RData::CNAME(..) => RecordType::CNAME,
RData::HINFO(..) => RecordType::HINFO,
RData::MX(..) => RecordType::MX,
RData::NAPTR(..) => RecordType::NAPTR,
RData::NS(..) => RecordType::NS,
Expand Down