This commit is contained in:
Stefan Bühler 2017-12-21 13:32:14 +01:00
parent 63cd9196f9
commit 2e380af9bb
37 changed files with 2095 additions and 323 deletions

7
Cargo.lock generated
View File

@ -45,6 +45,11 @@ name = "cfg-if"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "data-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dbghelp-sys"
version = "0.2.0"
@ -67,6 +72,7 @@ version = "0.1.0"
dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"data-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dnsbox-derive 0.1.0",
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -198,6 +204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6"
"checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum data-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "099d2591f809713931cd770f2bdf4b8a4d2eb7314bc762da4c375ecaa74af80f"
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"

View File

@ -11,3 +11,7 @@ failure = "0.1.1"
lazy_static = "1.0.0"
log = "0.3"
smallvec = "0.4.4"
data-encoding = "2.1.0"
[features]
no-unsafe = []

View File

@ -0,0 +1,115 @@
use bytes::Bytes;
use data_encoding::{self, HEXLOWER_PERMISSIVE};
use errors::*;
use failure::{Fail, ResultExt};
use ser::packet::{DnsPacketData, remaining_bytes, short_blob};
use ser::text::*;
use std::fmt;
use std::io::Cursor;
static WHITESPACE: &str = "\t\n\x0c\r "; // \f == \x0c formfeed
lazy_static!{
pub(crate) static ref HEXLOWER_PERMISSIVE_ALLOW_WS: data_encoding::Encoding = {
let mut spec = data_encoding::Specification::new();
spec.symbols.push_str("0123456789abcdef");
spec.translate.from.push_str("ABCDEF");
spec.translate.to.push_str("abcdef");
spec.ignore.push_str(WHITESPACE);
spec.encoding().unwrap()
};
static ref BASE64_ALLOW_WS: data_encoding::Encoding = {
let mut spec = data_encoding::Specification::new();
spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
spec.padding = Some('=');
spec.ignore.push_str(WHITESPACE);
spec.encoding().unwrap()
};
}
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) hex
// for text representation; "-" when empty
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct HexShortBlob(Bytes);
impl DnsPacketData for HexShortBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(HexShortBlob(short_blob(data)?))
}
}
impl DnsTextData for HexShortBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let s = next_field(data)?;
if s == "-" {
Ok(HexShortBlob(Bytes::new()))
} else {
let raw = HEXLOWER_PERMISSIVE.decode(s.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", s)))?;
ensure!(raw.len() < 256, "short hex field must be at most 255 bytes long");
Ok(HexShortBlob(raw.into()))
}
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
if self.0.is_empty() {
write!(f, "-")
} else {
write!(f, "{}", HEXLOWER_PERMISSIVE.encode(&self.0))
}
}
}
// No length byte (or restriction), just all data to end of record. uses
// base64 encoding for text representation, whitespace allowed, padding
// required.
//
// No following field allowed, i.e. last field in the record.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Base64RemainingBlob(Bytes);
impl DnsPacketData for Base64RemainingBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Base64RemainingBlob(remaining_bytes(data)))
}
}
impl DnsTextData for Base64RemainingBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
skip_whitespace(data);
let result = BASE64_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid base64: {:?}", data)))?;
*data = "";
Ok(Base64RemainingBlob(result.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", BASE64_ALLOW_WS.encode(&self.0))
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct HexRemainingBlob(Bytes);
// No length byte, just all data to end of record. uses hex encoding for
// text representation (spaces are ignored - last field in record).
impl DnsPacketData for HexRemainingBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(HexRemainingBlob(remaining_bytes(data)))
}
}
impl DnsTextData for HexRemainingBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
skip_whitespace(data);
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;
*data = "";
Ok(HexRemainingBlob(result.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.0))
}
}

View File

@ -1,90 +0,0 @@
use bytes::{Bytes, Buf};
use std::io::Cursor;
use ser::DnsPacketData;
use errors::*;
#[derive(Clone, Debug)]
pub struct HexShortBlob(Bytes);
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) hex
// for text representation; "-" when empty
impl DnsPacketData for HexShortBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
check_enough_data!(data, 1, "HexShortBlob length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "HexShortBlob content");
let pos = data.position() as usize;
let text = data.get_ref().slice(pos, pos + label_len);
data.advance(label_len);
Ok(HexShortBlob(text))
}
}
#[derive(Clone, Debug)]
pub struct Base32ShortBlob(Bytes);
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) base32
// for text representation; "-" when empty
impl DnsPacketData for Base32ShortBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
check_enough_data!(data, 1, "Base32ShortBlob length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "Base32ShortBlob content");
let pos = data.position() as usize;
let text = data.get_ref().slice(pos, pos + label_len);
data.advance(label_len);
Ok(Base32ShortBlob(text))
}
}
#[derive(Clone, Debug)]
pub struct LongText(Vec<Bytes>);
// RFC 1035 names this `One or more <character-string>`. No following
// field allowed.
impl DnsPacketData for LongText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let mut texts = Vec::new();
loop {
check_enough_data!(data, 1, "LongText length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "LongText content");
let pos = data.position() as usize;
texts.push(data.get_ref().slice(pos, pos + label_len));
data.advance(label_len);
if !data.has_remaining() { break; }
}
Ok(LongText(texts))
}
}
#[derive(Clone, Debug)]
pub struct RemainingText(Bytes);
// No length byte, just all data to end of record. uses base64 encoding
// for text representation
impl DnsPacketData for RemainingText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let pos = data.position() as usize;
let len = data.remaining();
let text = data.get_ref().slice(pos, pos + len);
data.advance(len);
Ok(RemainingText(text))
}
}
#[derive(Clone, Debug)]
pub struct HexRemainingBlob(Bytes);
// No length byte, just all data to end of record. uses hex encoding for
// text representation (spaces are ignored - last field in record).
impl DnsPacketData for HexRemainingBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let pos = data.position() as usize;
let len = data.remaining();
let text = data.get_ref().slice(pos, pos + len);
data.advance(len);
Ok(HexRemainingBlob(text))
}
}

View File

@ -0,0 +1,144 @@
use bytes::Bytes;
use errors::*;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use std::fmt;
use std::io::Cursor;
use std::borrow::Cow;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Class(pub u16);
pub const IN : Class = Class(KnownClass::IN as u16);
pub const CH : Class = Class(KnownClass::CH as u16);
pub const HS : Class = Class(KnownClass::HS as u16);
pub const NONE : Class = Class(KnownQClass::NONE as u16);
pub const ANY : Class = Class(KnownQClass::ANY as u16);
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
#[allow(non_camel_case_types)]
pub enum KnownClass {
// try to list "original" rfc
IN = 0x0001, // RFC 1035
CH = 0x0003, // "Chaos"
HS = 0x0004, // "Hesiod"
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
#[allow(non_camel_case_types)]
pub enum KnownQClass {
// try to list "original" rfc
NONE = 0x00fe, // RFC 2136
ANY = 0x00ff, // RFC 1035: "*"
}
impl Class {
pub fn name(self) -> Cow<'static, str> {
Cow::Borrowed(match self {
IN => "IN",
CH => "CH",
HS => "HS",
NONE => "NONE",
ANY => "ANY",
_ => return Cow::Owned(self.generic_name()),
})
}
/// uses generic name for QCLASS values (`is_qclass`)
pub fn class_name(self) -> Cow<'static, str> {
if self.is_qclass() {
Cow::Owned(self.generic_name())
} else {
self.name()
}
}
/// QCLASS names can overlap with (Q)TYPE names
///
/// classes unknown to this implementation never count as QCLASS,
/// but they also are only represented using the generic "CLASS..."
/// names, which don't overlap with (Q)TYPE names.
pub fn is_qclass(self) -> bool {
match self {
NONE => true,
ANY => true,
_ => false,
}
}
pub fn from_name(name: &str) -> Option<Self> {
use std::ascii::AsciiExt;
if let Some(n) = Self::class_from_name(name) { return Some(n); }
// explicit QCLASS names
if name.eq_ignore_ascii_case("NONE") { return Some(NONE); }
if name.eq_ignore_ascii_case("ANY") { return Some(ANY); }
None
}
/// similar to `from_name`, but doesn't accept QCLASS names (it
/// always accepts "CLASS..." names though, even if they are known to
/// be of type QCLASS)
pub fn class_from_name(name: &str) -> Option<Self> {
use std::ascii::AsciiExt;
if name.eq_ignore_ascii_case("IN") { return Some(IN); }
if name.eq_ignore_ascii_case("CH") { return Some(CH); }
if name.eq_ignore_ascii_case("HS") { return Some(HS); }
if name.as_bytes()[0..5].eq_ignore_ascii_case(b"CLASS") {
if let Ok(c) = name[5..].parse::<u16>() {
return Some(Class(c))
}
}
None
}
pub fn write_class_name(self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_qclass() {
self.write_generic_name(f)
} else {
write!(f, "{}", self)
}
}
pub fn write_generic_name(self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CLASS{}", self.0)
}
pub fn generic_name(self) -> String {
format!("CLASS{}", self.0)
}
}
impl fmt::Display for Class {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let n = match *self {
IN => "IN",
CH => "CH",
HS => "HS",
NONE => "NONE",
ANY => "ANY",
_ => {
return self.write_generic_name(f)
},
};
write!(f, "{}", n)
}
}
impl DnsPacketData for Class {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Class(DnsPacketData::deserialize(data)?))
}
}
impl DnsTextData for Class {
fn dns_parse(data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
Class::from_name(field).ok_or_else(|| format_err!("unknown class {:?}", field))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}

View File

@ -1,9 +1,20 @@
pub mod name;
pub mod text;
pub mod binary;
pub mod classes;
pub mod types;
mod nsec;
mod nxt;
mod rr_type;
mod time;
mod uri;
pub use self::name::{DnsName, DnsCompressedName};
pub use self::text::{ShortText};
pub use self::binary::{HexShortBlob, Base32ShortBlob, LongText, RemainingText, HexRemainingBlob};
pub use self::binary::{HexShortBlob, Base64RemainingBlob, HexRemainingBlob};
pub use self::name::{DnsName, DnsCanonicalName, DnsCompressedName};
pub use self::nsec::{NsecTypeBitmap, NextHashedOwnerName};
pub use self::rr_type::Type;
pub use self::classes::Class;
pub use self::text::{ShortText, LongText, UnquotedShortText, RemainingText};
pub use self::uri::UriText;
pub use self::time::Time;
pub use self::nxt::NxtTypeBitmap;

View File

@ -214,26 +214,25 @@ impl<'a> fmt::Debug for DnsLabelRef<'a> {
impl<'a> fmt::Display for DnsLabelRef<'a> {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
use std::str;
let mut done = 0;
for pos in 0..self.label.len() {
let c = self.label[pos];
if c <= 0x21 || c >= 0x7e || b'.' == c || b'\\' == c {
// flush
if done < pos {
w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..pos])})?;
w.write_str(::unsafe_ops::from_utf8_unchecked(&self.label[done..pos]))?;
}
match c {
b'.' => w.write_str(r#"\."#)?,
b'\\' => w.write_str(r#"\\"#)?,
_ => write!(w, r"\{:03o}", c)?,
_ => write!(w, r"\{:03}", c)?,
}
done = pos + 1;
}
}
// final flush
if done < self.label.len() {
w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..])})?;
w.write_str(::unsafe_ops::from_utf8_unchecked(&self.label[done..]))?;
}
Ok(())
}

View File

@ -15,12 +15,13 @@ pub use self::label::*;
mod display;
mod label;
mod name_mutations;
mod name_parser;
mod name_packet_parser;
mod name_text_parser;
#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)]
enum LabelOffset {
LabelStart(u8),
PacketStart(u8),
PacketStart(u16),
}
// the heap meta data is usually at least 2*usize big; assuming 64-bit
@ -78,6 +79,15 @@ pub struct DnsName {
total_len: u8,
}
/// names that should be written in canonical form for DNSSEC according
/// to https://tools.ietf.org/html/rfc4034#section-6.2
///
/// TODO: make it a newtype.
///
/// DnsCompressedName always needs to be written in canonical form for
/// DNSSEC.
pub type DnsCanonicalName = DnsName;
impl DnsName {
/// Create new name representing the DNS root (".")
pub fn new_root() -> Self {
@ -352,12 +362,12 @@ mod tests {
);
check_uncompressed_display(
b"\x07e!am.l\\\x03com\x00",
"e\\041am\\.l\\\\.com.",
"e\\033am\\.l\\\\.com.",
2,
);
check_uncompressed_debug(
b"\x07e!am.l\\\x03com\x00",
r#""e\\041am\\.l\\\\.com.""#,
r#""e\\033am\\.l\\\\.com.""#,
);
}
@ -451,27 +461,38 @@ mod tests {
);
}
#[test]
fn parse_invalid_compressed_name() {
de_compressed(b"\x11com\x00\x07example\xc0\x00", 5).unwrap_err();
de_compressed(b"\x10com\x00\x07example\xc0\x00", 5).unwrap_err();
}
#[test]
fn parse_and_display_compressed_name() {
check_compressed_display(
b"\x03com\x00\x07example\xc0", 5,
b"\x03com\x00\x07example\xc0\x00", 5,
"example.com.",
2,
);
check_compressed_display(
b"\x03com\x00\x07e!am.l\\\xc0", 5,
"e\\041am\\.l\\\\.com.",
b"\x03com\x00\x07e!am.l\\\xc0\x00", 5,
"e\\033am\\.l\\\\.com.",
2,
);
check_compressed_debug(
b"\x03com\x00\x07e!am.l\\\xc0", 5,
r#""e\\041am\\.l\\\\.com.""#,
b"\x03com\x00\x07e!am.l\\\xc0\x00", 5,
r#""e\\033am\\.l\\\\.com.""#,
);
check_compressed_display(
b"\x03com\x00\x07example\xc0\x00\x03www\xc0\x05", 15,
"www.example.com.",
3,
);
}
#[test]
fn modifications_compressed() {
let mut name = de_compressed(b"\x03com\x00\x07example\xc0", 5).unwrap();
let mut name = de_compressed(b"\x03com\x00\x07example\xc0\x00\xc0\x05", 15).unwrap();
name.push_front(DnsLabelRef::new(b"www").unwrap()).unwrap();
assert_eq!(

View File

@ -141,7 +141,8 @@ impl DnsName {
if label_offsets.is_empty() {
// root name
let mut data = data.unwrap_or_else(|_| BytesMut::with_capacity(new_len));
data.put_u8(0);
unsafe { data.set_len(new_len); }
data[0] = 0;
return (data, 0)
}

View File

@ -5,7 +5,7 @@ impl DnsName {
/// `data`: bytes of packet from beginning until at least the end of the name
/// `start_pos`: position of first byte of the name
/// `uncmpr_offsets`: offsets of uncompressed labels so far
/// `label_len`: first compressed label length (`0xc0 | offset`)
/// `label_len`: first compressed label length (`0xc0 | offset-high, offset-low`)
/// `total_len`: length of (uncompressed) label encoding so far
fn parse_name_compressed_cont(data: Bytes, start_pos: usize, uncmpr_offsets: SmallVec<[u8;16]>, mut total_len: usize, mut label_len: u8) -> Result<Self> {
let mut label_offsets = uncmpr_offsets.into_iter()
@ -15,12 +15,14 @@ impl DnsName {
let mut pos = start_pos + total_len;
'next_compressed: loop {
{
let new_pos = (label_len & 0x3f) as usize;
if new_pos >= pos { bail!("Compressed label offset to big") }
ensure!(pos + 1 < data.len(), "not enough data for compressed label");
let new_pos = ((label_len as usize & 0x3f) << 8) | (data[pos + 1] as usize);
ensure!(new_pos < pos, "Compressed label offset to big");
pos = new_pos;
}
loop {
ensure!(pos < data.len(), "not enough data for label");
label_len = data[pos];
if 0 == label_len {
@ -31,14 +33,14 @@ impl DnsName {
})
}
if label_len & 0xc == 0xc { continue 'next_compressed; }
if label_len > 63 { bail!("Invalid label length {}", label_len) }
if label_len & 0xc0 == 0xc0 { continue 'next_compressed; }
ensure!(label_len < 64, "Invalid label length {}", label_len);
total_len += 1 + label_len as usize;
// max len 255, but there also needs to be an empty label at the end
if total_len > 254 { bail!("DNS name too long") }
label_offsets.push(LabelOffset::PacketStart(pos as u8));
label_offsets.push(LabelOffset::PacketStart(pos as u16));
pos += 1 + label_len as usize;
}
}
@ -63,6 +65,9 @@ impl DnsName {
if label_len & 0xc0 == 0xc0 {
// compressed label
if !accept_compressed { bail!("Invalid label compression {}", label_len) }
check_enough_data!(data, 1, "DnsName compressed label target");
// eat second part of compressed label
data.get_u8();
let end_pos = data.position() as usize;
let data = data.get_ref().slice(0, end_pos);

View File

@ -0,0 +1,89 @@
use super::*;
use ser::text::{DnsTextData, DnsTextFormatter, next_field, quoted};
impl DnsName {
/// Parse text representation of a domain name
pub fn parse<'a, O>(value: &str, origin: O) -> ::errors::Result<Self>
where
O: IntoIterator<Item = DnsLabelRef<'a>>
{
let raw = value.as_bytes();
let mut name = DnsName::new_root();
if raw == b"." {
return Ok(name);
} else if raw == b"@" {
for l in origin.into_iter() { name.push_back(l)?; }
return Ok(name);
}
ensure!(!raw.is_empty(), "invalid empty name");
let mut label = Vec::new();
let mut pos = 0;
while pos < raw.len() {
if raw[pos] == b'.' {
ensure!(!label.is_empty(), "empty label in name: {:?}", value);
name.push_back(DnsLabelRef::new(&label)?)?;
label.clear();
} else if raw[pos] == b'\\' {
ensure!(pos + 1 < raw.len(), "unexpected end of name after backslash: {:?}", value);
if raw[pos+1] >= b'0' && raw[pos+1] <= b'9' {
// \ddd escape
ensure!(pos + 3 < raw.len(), "unexpected end of name after backslash with digit: {:?}", value);
ensure!(raw[pos+2] >= b'0' && raw[pos+2] <= b'9' && raw[pos+3] >= b'0' && raw[pos+3] <= b'9', "expected three digits after backslash in name: {:?}", name);
let d1 = (raw[pos+1] - b'0') as u32;
let d2 = (raw[pos+2] - b'0') as u32;
let d3 = (raw[pos+3] - b'0') as u32;
let v = d1 * 100 + d2 * 10 + d3;
ensure!(v < 256, "invalid escape in name, {} > 255: {:?}", v, name);
label.push(v as u8);
} else {
ensure!(!quoted::is_ascii_whitespace(raw[pos+1]), "whitespace cannot be escaped with backslash prefix; encode it as \\{:03} in: {:?}", raw[pos+1], name);
label.push(raw[pos+1]);
}
} else {
ensure!(!quoted::is_ascii_whitespace(raw[pos+1]), "whitespace must be encoded as \\{:03} in: {:?}", raw[pos+1], name);
label.push(raw[pos]);
}
pos += 1;
}
if !label.is_empty() {
// no trailing dot, relative name
name.push_back(DnsLabelRef::new(&label)?)?;
for l in origin.into_iter() { name.push_back(l)?; }
}
Ok(name)
}
}
impl DnsTextData for DnsName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
DnsName::parse(field, &DnsName::new_root())
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl DnsCompressedName {
/// Parse text representation of a domain name
pub fn parse<'a, O>(value: &str, origin: O) -> ::errors::Result<Self>
where
O: IntoIterator<Item = DnsLabelRef<'a>>
{
Ok(DnsCompressedName(DnsName::parse(value, origin)?))
}
}
impl DnsTextData for DnsCompressedName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
DnsCompressedName::parse(field, &DnsName::new_root())
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
self.0.dns_format(f)
}
}

View File

@ -0,0 +1,153 @@
use bytes::{Bytes, Buf};
use common_types::Type;
use data_encoding;
use errors::*;
use failure::{Fail, ResultExt};
use ser::packet::{DnsPacketData, remaining_bytes, short_blob};
use ser::text::{DnsTextData, DnsTextFormatter, skip_whitespace, next_field};
use std::collections::BTreeSet;
use std::fmt;
use std::io::Cursor;
static WHITESPACE: &str = "\t\n\x0c\r "; // \f == \x0c formfeed
lazy_static!{
static ref BASE32HEX_NOPAD_ALLOW_WS: data_encoding::Encoding = {
let mut spec = data_encoding::Specification::new();
spec.symbols.push_str("0123456789ABCDEFGHIJKLMNOPQRSTUV");
spec.translate.from.push_str("abcdefghijklmnopqrstuv");
spec.translate.to.push_str("ABCDEFGHIJKLMNOPQRSTUV");
spec.ignore.push_str(WHITESPACE);
spec.encoding().unwrap()
};
}
/// Last field in a record!
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NsecTypeBitmap {
raw: Bytes,
set: BTreeSet<Type>,
}
impl NsecTypeBitmap {
pub fn from_set(set: BTreeSet<Type>) -> Self {
let mut raw = Vec::new();
let mut current_window_base = None;
let mut window_pos = 0;
let mut window_len = 0;
for t in &set {
let base = (t.0 >> 8) as u8;
if current_window_base != Some(base) {
// new window
current_window_base = Some(base);
window_pos = raw.len();
window_len = 0;
raw.push(base);
raw.push(0);
}
let bit_ndx = t.0 as u8;
let byte_ndx = bit_ndx / 8;
if byte_ndx >= window_len {
// make window bigger, fill with zeroes
let new_window_len = byte_ndx + 1;
let new_raw_len = raw.len() + (new_window_len - window_len) as usize;
raw.resize(new_raw_len, 0);
raw[window_pos + 1] = new_window_len;
window_len = new_window_len;
}
let mask = 0x80 >> (bit_ndx % 8);
raw[window_pos + 2 + byte_ndx as usize] |= mask;
}
NsecTypeBitmap {
raw: raw.into(),
set: set,
}
}
}
impl DnsPacketData for NsecTypeBitmap {
fn deserialize(data: &mut Cursor<Bytes>) -> ::errors::Result<Self> {
// remember raw encoding
let raw = {
let mut data: Cursor<Bytes> = data.clone();
remaining_bytes(&mut data)
};
let mut set = BTreeSet::new();
let mut prev_window = None;
while data.has_remaining() {
let window_base = (data.get_u8() as u16) << 8;
ensure!(Some(window_base) > prev_window, "wrong nsec bitmap window order, {:?} <= {:?}", Some(window_base), prev_window);
prev_window = Some(window_base);
check_enough_data!(data, 1, "nsec bitmap window length");
let window_len = data.get_u8() as u16;
ensure!(window_len <= 32, "nsec bitmap window too long");
check_enough_data!(data, window_len as usize, "nsec bitmap window length");
for i in 0..window_len {
let mut v = data.get_u8();
for j in 0..7 {
if 0 != v & 0x80 {
set.insert(Type(window_base + i*8 + j));
}
v <<= 1;
}
}
}
Ok(NsecTypeBitmap{
raw: raw,
set: set,
})
}
}
impl DnsTextData for NsecTypeBitmap {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let mut set = BTreeSet::new();
skip_whitespace(data);
while !data.is_empty() {
let t = Type::dns_parse(data)?;
set.insert(t);
}
Ok(NsecTypeBitmap::from_set(set))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
for t in &self.set {
write!(f, "{}", t)?;
}
Ok(())
}
}
/// `base32hex` encoding without padding for text representation (RFC
/// 4648). whitespaces not allowed (not the last field).
///
/// Between 1 and 255 bytes long.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NextHashedOwnerName(Bytes);
impl DnsPacketData for NextHashedOwnerName {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let text = short_blob(data)?;
ensure!(text.len() > 0, "NextHashedOwnerName must not be empty");
Ok(NextHashedOwnerName(text))
}
}
impl DnsTextData for NextHashedOwnerName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
let raw = BASE32HEX_NOPAD_ALLOW_WS.decode(field.as_bytes())
.with_context(|e| e.context(format!("invalid base32hex (no padding): {:?}", field)))?;
ensure!(raw.len() > 0, "NextHashedOwnerName must not be empty");
ensure!(raw.len() < 256, "NextHashedOwnerName field must be at most 255 bytes long");
Ok(NextHashedOwnerName(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
if self.0.is_empty() { return Err(fmt::Error); }
write!(f, "{}", BASE32HEX_NOPAD_ALLOW_WS.encode(&self.0))
}
}

View File

@ -0,0 +1,94 @@
use bytes::{Bytes, Buf};
use common_types::Type;
use errors::*;
use ser::packet::{DnsPacketData, remaining_bytes};
use ser::text::{DnsTextData, DnsTextFormatter, skip_whitespace};
use std::collections::BTreeSet;
use std::fmt;
use std::io::Cursor;
/// Type Bitmap for NXT. Last field in a record.
///
/// RFC 2535 says for the bitmap: "If the zero bit of the type bit map
/// is a one, it indicates that a different format is being used which
/// will always be the case if a type number greater than 127 is
/// present."
///
/// But this other format isn't specified anywhere...
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NxtTypeBitmap {
raw: Bytes,
set: BTreeSet<Type>,
}
impl NxtTypeBitmap {
pub fn from_set(set: BTreeSet<Type>) -> Result<Self> {
let mut raw = Vec::new();
for t in &set {
ensure!(t.0 > 0, "type 0 cannot be represented in NXT bitmap");
ensure!(t.0 < 128, "type {} cannot be represented in NXT bitmap", t);
let bit_ndx = t.0 as u8;
let byte_ndx = (bit_ndx / 8) as usize;
if byte_ndx >= raw.len() {
raw.resize(byte_ndx + 1, 0);
}
let mask = 0x80 >> (bit_ndx % 8);
raw[byte_ndx] |= mask;
}
Ok(NxtTypeBitmap {
raw: raw.into(),
set: set,
})
}
}
impl DnsPacketData for NxtTypeBitmap {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
// remember raw encoding
let raw = {
let mut data: Cursor<Bytes> = data.clone();
remaining_bytes(&mut data)
};
let mut set = BTreeSet::new();
let mut current = 0;
while data.has_remaining() {
ensure!(current < 128, "NXT bitmap too long");
let mut v = data.get_u8();
for _ in 0..7 {
if 0 != v & 0x80 {
ensure!(0 != current, "Type 0 not allowed in NXT bitmap");
set.insert(Type(current));
}
v <<= 1;
current += 1;
}
}
Ok(NxtTypeBitmap{
raw: raw,
set: set,
})
}
}
impl DnsTextData for NxtTypeBitmap {
fn dns_parse(data: &mut &str) -> Result<Self> {
let mut set = BTreeSet::new();
skip_whitespace(data);
while !data.is_empty() {
let t = Type::dns_parse(data)?;
set.insert(t);
}
NxtTypeBitmap::from_set(set)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
for t in &self.set {
write!(f, "{}", t)?;
}
Ok(())
}
}

View File

@ -1,8 +1,99 @@
use bytes::Bytes;
use errors::*;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use std::fmt;
use std::io::Cursor;
use records::registry::{name_to_type, type_name};
use common_types::types;
use std::borrow::Cow;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Type(pub u16);
impl ::ser::DnsPacketData for Type {
fn deserialize(data: &mut ::std::io::Cursor<::bytes::Bytes>) -> ::errors::Result<Self> {
Ok(Type(::ser::DnsPacketData::deserialize(data)?))
impl Type {
pub fn name(self) -> Cow<'static, str> {
if let Some(name) = type_name(self) {
Cow::Borrowed(name)
} else {
Cow::Owned(format!("TYPE{}", self.0))
}
}
/// defined in RFC 1035
pub fn well_known(self) -> bool {
// 0x0001 (A) ... 0x0010 (TXT) are defined in RFC 1035
self.0 >= 0x0001 && self.0 <= 0x0010
}
/// require converting to canonical form for DNSSEC (i.e. names
/// must be converted to (ASCII) lower case, no compression)
///
/// See https://tools.ietf.org/html/rfc4034#section-6.2 (updates RFC 3597).
pub fn canonical(self) -> bool {
match self {
types::NS => true,
types::MD => true,
types::MF => true,
types::CNAME => true,
types::SOA => true,
types::MB => true,
types::MG => true,
types::MR => true,
types::PTR => true,
// types::HINFO => true, // doesn't have a name in data
types::MINFO => true,
types::MX => true,
// types::HINFO => true, // see above, also duplicate in the RFCs
types::RP => true,
types::AFSDB => true,
types::RT => true,
types::NSAP_PTR => true, // not listed in the RFCs, but probably should be.
types::SIG => true,
types::PX => true,
types::NXT => true,
types::SRV => true, // moved up to match numeric order
types::NAPTR => true,
types::KX => true,
// types::SRV => true, // moved up to match numeric order
types::A6 => true, // moved up to match numeric order
types::DNAME => true,
// types::A6 => true, // moved up to match numeric order
types::RRSIG => true,
types::NSEC => true,
_ => false,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(name) = type_name(*self) {
write!(f, "{}", name)
} else {
write!(f, "TYPE{}", self.0)
}
}
}
impl DnsPacketData for Type {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Type(DnsPacketData::deserialize(data)?))
}
}
impl DnsTextData for Type {
fn dns_parse(data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
if field.starts_with("TYPE") || field.starts_with("type") {
if let Ok(t) = field[4..].parse::<u16>() {
return Ok(Type(t));
}
}
name_to_type(field).ok_or_else(|| format_err!("unknown type {:?}", field))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}

View File

@ -0,0 +1,115 @@
use bytes::{Bytes, Buf};
use errors::*;
use ser::packet::{DnsPacketData, short_blob, remaining_bytes};
use ser::text::*;
use std::fmt;
use std::io::Cursor;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ShortText(Bytes);
// RFC 1035 names this `<character-string>`
impl DnsPacketData for ShortText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(ShortText(short_blob(data)?))
}
}
impl DnsTextData for ShortText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(raw.len() < 256, "short text must be at most 255 bytes long");
Ok(ShortText(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", quote(&self.0))
}
}
/// RFC 1035 names this `One or more <character-string>`. No following
/// field allowed.
///
/// Used for TXT and SPF.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct LongText(Vec<Bytes>);
impl DnsPacketData for LongText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let mut texts = Vec::new();
loop {
texts.push(short_blob(data)?);
if !data.has_remaining() { break; }
}
Ok(LongText(texts))
}
}
impl DnsTextData for LongText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let mut result = Vec::new();
// `next_quoted_field` should skip trailing whitespace, we only
// need to skip the beginning whitespace for the first
// `is_empty` check.
skip_whitespace(data);
while !data.is_empty() {
let part = next_quoted_field(data)?;
ensure!(part.len() < 256, "long text component must be at most 255 bytes long");
result.push(part.into());
}
Ok(LongText(result))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
// actually emit as "multiple fields" (i.e. space separated)
for part in &self.0 {
write!(f, "{}", quote(part))?;
}
Ok(())
}
}
// similart to ShortText, but text output doesn't use quotes.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UnquotedShortText(Bytes);
impl DnsPacketData for UnquotedShortText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(UnquotedShortText(short_blob(data)?))
}
}
impl DnsTextData for UnquotedShortText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(raw.len() < 256, "short text must be at most 255 bytes long");
Ok(UnquotedShortText(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", escape(&self.0))
}
}
/// A single (possibly quoted) <character-string>, but no length
/// restriction.
///
/// Last field, but no whitespace allowed (unless quoted of course).
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct RemainingText(Bytes);
impl DnsPacketData for RemainingText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(RemainingText(remaining_bytes(data)))
}
}
impl DnsTextData for RemainingText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(RemainingText(next_quoted_field(data)?.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", quote(&self.0))
}
}

View File

@ -1,21 +0,0 @@
use bytes::{Bytes, Buf};
use std::io::Cursor;
use ser::DnsPacketData;
use errors::*;
#[derive(Clone, Debug)]
pub struct ShortText(Bytes);
// RFC 1035 names this `<character-string>`
impl DnsPacketData for ShortText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
check_enough_data!(data, 1, "ShortText length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "ShortText content");
let pos = data.position() as usize;
let text = data.get_ref().slice(pos, pos + label_len);
data.advance(label_len);
Ok(ShortText(text))
}
}

View File

@ -0,0 +1,38 @@
use bytes::Bytes;
use errors::*;
use ser::packet::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use std::fmt;
use std::io::Cursor;
/// A single quoted non-empty URL.
///
/// Actually shouldn't allow escapes (i.e. no backslash in the content);
/// but to make sure we can export and import any data we allow standard
/// escape mechanisms and even accept unquoted input.
///
/// No whitespace allowed, last field.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Time(u32);
impl DnsPacketData for Time {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Time(DnsPacketData::deserialize(data)?))
}
}
impl DnsTextData for Time {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
let epoch = field.parse::<u32>();
if field.len() == 14 && epoch.is_err() {
unimplemented!()
} else {
Ok(Time(epoch?))
}
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@ -0,0 +1,36 @@
use bytes::Bytes;
use errors::*;
use ser::packet::{DnsPacketData, remaining_bytes};
use ser::text::*;
use std::fmt;
use std::io::Cursor;
/// A single quoted non-empty URL.
///
/// Actually shouldn't allow escapes (i.e. no backslash in the content);
/// but to make sure we can export and import any data we allow standard
/// escape mechanisms and even accept unquoted input.
///
/// No whitespace allowed, last field.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UriText(Bytes);
impl DnsPacketData for UriText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let raw = remaining_bytes(data);
ensure!(!raw.is_empty(), "URI must not be empty");
Ok(UriText(raw))
}
}
impl DnsTextData for UriText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(!raw.is_empty(), "URI must not be empty");
Ok(UriText(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", quote(&self.0))
}
}

View File

@ -1,5 +1,6 @@
pub extern crate byteorder;
pub extern crate bytes;
pub extern crate data_encoding;
#[macro_use]
pub extern crate failure;
@ -9,6 +10,8 @@ extern crate smallvec;
#[macro_use]
extern crate lazy_static;
mod unsafe_ops;
#[macro_use]
pub mod errors;
pub mod common_types;

View File

@ -1,8 +1,10 @@
mod weird_structs;
mod structs;
mod unknown;
pub mod registry;
pub mod types;
pub use self::structs::*;
pub use self::unknown::*;
#[cfg(test)]
mod tests;

View File

@ -1,9 +1,11 @@
use std::collections::HashMap;
use std::ascii::AsciiExt;
use std::marker::PhantomData;
use records::structs;
use records::types;
use common_types::types;
use common_types::Type;
use ser::RRData;
use ser::{RRData, StaticRRData};
lazy_static!{
static ref REGISTRY: Registry = Registry::init();
@ -15,7 +17,7 @@ fn registry() -> &'static Registry {
pub fn known_name_to_type(name: &str) -> Option<Type> {
let registry = registry();
let &t = registry.names_to_type.get(name)?;
let t = name_to_type(name)?;
registry.type_parser.get(&t)?;
Some(t)
@ -27,8 +29,16 @@ pub fn name_to_type(name: &str) -> Option<Type> {
Some(t)
}
pub(crate) fn type_name(rrtype: Type) -> Option<&'static str> {
let registry = registry();
registry.type_names.get(&rrtype).map(|s| s as _)
}
struct TagRRDataType<T: RRData>(PhantomData<T>);
struct Registry {
names_to_type: HashMap<String, Type>,
type_names: HashMap<Type, String>,
type_parser: HashMap<Type, ()>,
// make sure registrations are in order
prev_type: Option<Type>,
@ -38,6 +48,7 @@ impl Registry {
fn init() -> Self {
let mut r = Registry {
names_to_type: HashMap::new(),
type_names: HashMap::new(),
type_parser: HashMap::new(),
prev_type: None,
};
@ -51,8 +62,8 @@ impl Registry {
r.register_known::<structs::MB>();
r.register_known::<structs::MG>();
r.register_known::<structs::MR>();
r.register_known::<structs::NULL>();
r.register_known::<structs::WKS>();
r.register_unknown("NULL" , types::NULL);
r.register_unknown("WKS" , types::WKS);
r.register_known::<structs::PTR>();
r.register_known::<structs::HINFO>();
r.register_known::<structs::MINFO>();
@ -62,16 +73,16 @@ impl Registry {
r.register_known::<structs::AFSDB>();
r.register_unknown("X25" , types::X25);
r.register_unknown("ISDN" , types::ISDN);
r.register_unknown("RT" , types::RT);
r.register_known::<structs::RT>();
r.register_unknown("NSAP" , types::NSAP);
r.register_unknown("NSAP-PTR" , types::NSAP_PTR);
r.register_unknown("SIG" , types::SIG);
r.register_known::<structs::NSAP_PTR>();
r.register_known::<structs::SIG>();
r.register_known::<structs::KEY>();
r.register_unknown("PX" , types::PX);
r.register_known::<structs::PX>();
r.register_unknown("GPOS" , types::GPOS);
r.register_known::<structs::AAAA>();
r.register_known::<structs::LOC>();
r.register_unknown("NXT" , types::NXT);
r.register_known::<structs::NXT>();
r.register_unknown("EID" , types::EID);
r.register_unknown("NIMLOC" , types::NIMLOC);
r.register_known::<structs::SRV>();
@ -79,7 +90,7 @@ impl Registry {
r.register_known::<structs::NAPTR>();
r.register_known::<structs::KX>();
r.register_known::<structs::CERT>();
r.register_unknown("A6" , types::A6);
r.register_known::<structs::A6>();
r.register_known::<structs::DNAME>();
r.register_unknown("SINK" , types::SINK);
r.register_unknown("OPT" , types::OPT);
@ -121,8 +132,8 @@ impl Registry {
r.register_unknown("MAILB" , types::MAILB);
r.register_unknown("MAILA" , types::MAILA);
r.register_unknown("ANY" , types::ANY);
r.register_unknown("URI" , types::URI);
r.register_unknown("CAA" , types::CAA);
r.register_known::<structs::URI>();
r.register_known::<structs::CAA>();
r.register_unknown("AVC" , types::AVC);
r.register_unknown("DOA" , types::DOA);
r.register_unknown("DLV" , types::DLV);
@ -132,18 +143,23 @@ impl Registry {
r
}
fn register_unknown(&mut self, name: &'static str, rrtype: Type) {
fn register_name(&mut self, name: &str, rrtype: Type) {
assert!(self.prev_type < Some(rrtype), "registration not in order");
self.prev_type = Some(rrtype);
assert!(self.names_to_type.insert(name.into(), rrtype).is_none());
let mut name: String = name.into();
name.make_ascii_uppercase();
assert!(self.names_to_type.insert(name.clone(), rrtype).is_none());
self.type_names.insert(rrtype, name);
}
fn register_known<T: RRData>(&mut self) {
let n = T::rr_type_name();
let t = T::rr_type();
assert!(self.prev_type < Some(t), "registration not in order");
self.prev_type = Some(t);
assert!(self.names_to_type.insert(n.into_owned(), t).is_none());
self.type_parser.insert(t, ());
fn register_unknown(&mut self, name: &'static str, rrtype: Type) {
self.register_name(name, rrtype);
}
fn register_known<T: StaticRRData>(&mut self) {
let rrtype = T::TYPE;
let name = T::NAME;
self.register_name(name, rrtype);
self.type_parser.insert(rrtype, ());
}
}

View File

@ -1,42 +1,44 @@
use bytes::Bytes;
use ser::DnsPacketData;
use common_types::*;
use std::net::{Ipv4Addr, Ipv6Addr};
// unless otherwise documented, class should probably be IN (0x0001)
// deriving RRData will add a unit test to make sure the type is
// registered; there must be a records::types::$name `Type` constant
// with the same name as the struct.
// class IN
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct A {
addr: Ipv4Addr,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct NS {
nsdname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MD {
madname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MF {
madname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct CNAME {
cname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct SOA {
mname: DnsCompressedName,
rname: DnsCompressedName,
@ -48,66 +50,68 @@ pub struct SOA {
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MB {
madname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MG {
mgmname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MR {
newname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
pub struct NULL {
anything: RemainingText,
}
// not allowed in zone files anyway, i.e. no text representation.
// content not restricted either, just some bytes. no need to parse it.
//
// class independent pub struct NULL;
// text representation like: `WKS 127.0.0.1 TCP smtp http 110`. would
// have to parse protocol and service names.
//
// class IN
#[derive(Clone, Debug, DnsPacketData, RRData)]
pub struct WKS {
address: Ipv4Addr,
protocol: u8,
bitmap: RemainingText,
}
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct WKS {
// address: Ipv4Addr,
// protocol: u8,
// bitmap: ..., // remaining bytes
// }
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct PTR {
ptrdname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct HINFO {
cpu: ShortText,
os: ShortText,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MINFO {
rmailbx: DnsCompressedName,
emailbx: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct MX {
preference: u16,
mxname: DnsCompressedName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct TXT {
text: LongText,
}
@ -115,132 +119,153 @@ pub struct TXT {
// end of RFC 1035: no DnsCompressedName below!
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct RP {
mbox: DnsName,
txt: DnsName,
mbox: DnsCanonicalName,
txt: DnsCanonicalName,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct AFSDB {
subtype: u16,
hostname: DnsName,
hostname: DnsCanonicalName,
}
// https://tools.ietf.org/html/rfc1183#section-3.1 says "its format in
// master files is a <character-string>" which say nothing about the
// binary encoding; later it says "<PSDN-address> is a string of decimal
// digits", so it would seem that there is no length encoding or
// restriction.
//
// wireshark and bind use <character-string> though (bind also wants at
// least 4 bytes in the field: probably due to "beginning with the 4
// digit DNIC").
//
// class independent
// pub struct X25 {
// psdn_address: ShortText,
// }
// class independent
// pub struct ISDN {
// isdn_address: ShortText,
// subaddress: Option<ShortText>,
// }
// class independent
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct RT {
preference: u16,
intermediate: DnsCanonicalName,
}
// pub struct NSAP;
#[allow(non_camel_case_types)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRTypeName = "NSAP-PTR"]
pub struct NSAP_PTR {
owner: DnsCanonicalName,
}
// class independent
// pub struct X25;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct SIG {
rr_type: Type,
algorithm: u8,
labels: u8,
// RFC says this can be omitted in text form if it is the same as
// the TTL on the SIG record. not supported to be omitted here
// (TODO?).
original_ttl: u32,
signature_expiration: Time,
signature_inception: Time,
key_tag: u16,
signers_name: DnsCanonicalName,
signature: Base64RemainingBlob,
}
// class independent
// pub struct ISDN;
// class independent
// pub struct RT;
// pub struct NSAP;
// pub struct NSAP_PTR;
// pub struct SIG;
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct KEY {
flags: u16,
protocol: u8,
algorithm: u8,
certificate: RemainingText,
public_key: Base64RemainingBlob,
}
// class IN
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct PX {
preference: u16,
map822: DnsCanonicalName,
mapx400: DnsCanonicalName,
}
// pub struct PX;
// pub struct GPOS;
// class IN
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct AAAA {
addr: Ipv6Addr,
}
// class independent
#[derive(Clone, Debug, RRData)]
pub enum LOC {
Version0(LOC0),
UnknownVersion{
version: u8,
data: RemainingText
},
pub use super::weird_structs::LOC;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct NXT {
next: DnsCanonicalName,
types: NxtTypeBitmap,
}
impl DnsPacketData for LOC {
fn deserialize(data: &mut ::std::io::Cursor<Bytes>) -> ::errors::Result<Self> {
let version: u8 = DnsPacketData::deserialize(data)?;
if 0 == version {
Ok(LOC::Version0(DnsPacketData::deserialize(data)?))
} else {
Ok(LOC::UnknownVersion{
version: version,
data: DnsPacketData::deserialize(data)?,
})
}
}
}
#[derive(Clone, Debug, DnsPacketData)]
pub struct LOC0 {
size: u8,
horizontal_precision: u8,
vertical_precision: u8,
latitude: u32,
longitude: u32,
altitude: u32,
}
// pub struct NXT;
// pub struct EID;
// pub struct NIMLOC;
// class IN
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct SRV {
preference: u16,
weight: u16,
port: u16,
target: DnsName,
target: DnsCanonicalName,
}
// pub struct ATMA;
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct NAPTR {
order: u16,
preference: u16,
flags: ShortText,
service: ShortText,
regexp: ShortText,
replacement: DnsName,
replacement: DnsCanonicalName,
}
// class IN
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct KX {
preference: u16,
exchanger: DnsName,
exchanger: DnsCanonicalName,
}
// class ??
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct CERT {
cert_type: u16,
key_tag: u16,
algorithm: u8,
certificate: RemainingText,
certificate: Base64RemainingBlob,
}
// pub struct A6;
pub use super::weird_structs::A6;
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct DNAME {
target: DnsName,
target: DnsCanonicalName,
}
// pub struct SINK;
@ -251,7 +276,7 @@ pub struct DNAME {
// pub struct APL;
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct DS {
key_tag: u16,
algorithm: u8,
@ -259,67 +284,64 @@ pub struct DS {
digest: HexRemainingBlob,
}
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct SSHFP {
algorithm: u8,
fingerprint_type: u8,
// RFC 4255 doesn't specify whether whitespace is allowed.
// `HexRemainingBlob` allows whitespace.
fingerprint: HexRemainingBlob,
}
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct IPSECKEY;
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct RRSIG {
rr_type: Type,
algorithm: u8,
labels: u8,
original_ttl: u32,
signature_expiration: u32,
signature_inception: u32,
signature_expiration: Time,
signature_inception: Time,
key_tag: u16,
signers_name: DnsName,
signature: RemainingText,
}
#[derive(Clone, Debug, DnsPacketData)]
pub struct NsecTypeBitmap {
types: RemainingText, // TODO: actually parse it
signers_name: DnsCanonicalName,
signature: Base64RemainingBlob,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct NSEC {
next: DnsName,
bitmap: NsecTypeBitmap,
next: DnsCanonicalName,
types: NsecTypeBitmap,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct DNSKEY {
flags: u16,
protocol: u8,
algorithm: u8,
public_key: RemainingText,
public_key: Base64RemainingBlob,
}
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct DHCID;
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct NSEC3 {
hash_algorithm: u8,
flags: u8,
iterations: u16,
salt: HexShortBlob,
next_hashed: Base32ShortBlob,
bitmap: NsecTypeBitmap,
next_hashed: NextHashedOwnerName,
types: NsecTypeBitmap,
}
// class independent
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct NSEC3PARAM {
hash_algorithm: u8,
flags: u8,
@ -327,32 +349,32 @@ pub struct NSEC3PARAM {
salt: HexShortBlob,
}
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct TLSA;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct SMIMEA;
// pub struct HIP;
// pub struct NINFO;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct RKEY;
// pub struct TALINK;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct CDS;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct CDNSKEY;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct OPENPGPKEY;
// pub struct CSYNC;
#[derive(Clone, Debug, DnsPacketData, RRData)]
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct SPF {
text: LongText,
}
@ -366,16 +388,16 @@ pub struct SPF {
// pub struct L64;
// pub struct LP;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct EUI48;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct EUI64;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct TKEY;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct TSIG;
// pub struct IXFR; // qtype only?
@ -384,19 +406,27 @@ pub struct SPF {
// pub struct MAILA; // qtype only?
// pub struct ANY; // qtype only?
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// pub struct URI;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct URI {
priority: u16,
weight: u16,
target: UriText,
}
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// pub struct CAA;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
pub struct CAA {
flags: u8,
tag: UnquotedShortText,
value: RemainingText,
}
// pub struct AVC;
// pub struct DOA;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct DLV;
// pub struct ADDR;
// #[derive(Clone, Debug, DnsPacketData, RRData)]
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// pub struct ALIAS;

View File

@ -1,16 +1,118 @@
use bytes::Bytes;
use bytes::{Bytes, Buf};
use failure::ResultExt;
use records::structs;
use ser::packet::deserialize;
use ser::RRData;
use ser::{packet, text, StaticRRData};
use std::fmt;
use std::io::Cursor;
fn check<T>(txt: &str, data: &'static [u8])
fn rrdata_de<T>(data: &'static [u8]) -> ::errors::Result<T>
where
T: RRData
T: StaticRRData
{
let d: T = deserialize(Bytes::from_static(data)).expect("couldn't parse record");
let mut data = Cursor::new(Bytes::from_static(data));
let result = T::deserialize_rr_data(3600, 0x0001, T::TYPE, &mut data)?;
ensure!(!data.has_remaining(), "rrdata not read completely");
Ok(result)
}
fn check<T>(txt: &str, data: &'static [u8]) -> ::errors::Result<()>
where
T: StaticRRData + fmt::Debug + PartialEq
{
let d1: T = rrdata_de(data).context("couldn't parse binary record")?;
let d2: T = text::parse(txt).context("couldn't parse text record")?;
ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2);
Ok(())
}
fn check2<T>(txt: &str, data: &'static [u8], canon: &str) -> ::errors::Result<()>
where
T: StaticRRData + fmt::Debug + PartialEq
{
let d1: T = rrdata_de(data).context("couldn't parse binary record")?;
let d2: T = text::parse(txt).context("couldn't parse text record")?;
ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2);
Ok(())
}
#[test]
fn test_a() {
check::<structs::A>("127.0.0.1", b"\x7f\x00\x00\x01");
check::<structs::A>("127.0.0.1", b"\x7f\x00\x00\x01").unwrap();
}
fn test_txt_for<T>()
where
T: StaticRRData + fmt::Debug + PartialEq
{
// at least one "segment" (which could be empty)
check2::<T>(r#" "" "#, b"", r#" "" "#).unwrap_err();
check2::<T>(r#""#, b"\x00", r#" "" "#).unwrap_err();
// one empty segment
check2::<T>(r#" "" "#, b"\x00", r#" "" "#).unwrap();
// one segment
check::<T>(r#" "foo" "#, b"\x03foo").unwrap();
// two segments
check::<T>(r#" "foo" "bar!" "#, b"\x03foo\x04bar!").unwrap();
// segment with too many bytes in text form
{
let mut s = String::new();
s.push('"');
for _ in 0..256 { s.push('a'); }
s.push('"');
text::parse::<T>(&s).unwrap_err();
}
}
#[test]
fn test_txt() {
test_txt_for::<structs::TXT>();
}
#[test]
fn test_ds() {
check::<structs::DS>(" 1 2 3 ", b"\x00\x01\x02\x03").unwrap();
check::<structs::DS>(" 1 2 3 abcd", b"\x00\x01\x02\x03\xab\xcd").unwrap();
check::<structs::DS>(" 1 2 3 a b c d", b"\x00\x01\x02\x03\xab\xcd").unwrap();
}
#[test]
fn test_nsec() {
check::<structs::NSEC>("foo.bar. ", b"\x03foo\x03bar\x00").unwrap();
check::<structs::NSEC>("foo.bar. A NS ", b"\x03foo\x03bar\x00\x00\x01\x60").unwrap();
check::<structs::NSEC>("foo.bar. A NS TYPE256 TYPE65280 ", b"\x03foo\x03bar\x00\x00\x01\x60\x01\x01\x80\xff\x01\x80").unwrap();
}
#[test]
fn test_dnskey() {
check::<structs::DNSKEY>("256 2 3", b"\x01\x00\x02\x03").unwrap();
check::<structs::DNSKEY>("256 2 3 /w==", b"\x01\x00\x02\x03\xff").unwrap();
check::<structs::DNSKEY>("256 2 3 /w ==", b"\x01\x00\x02\x03\xff").unwrap();
}
#[test]
fn test_nsec3() {
check::<structs::NSEC3>("1 2 300 - vs", b"\x01\x02\x01\x2c\x00\x01\xff").unwrap();
check::<structs::NSEC3>("1 2 300 - vs A NS", b"\x01\x02\x01\x2c\x00\x01\xff\x00\x01\x60").unwrap();
check::<structs::NSEC3>("1 2 300 ab vs A NS", b"\x01\x02\x01\x2c\x01\xab\x01\xff\x00\x01\x60").unwrap();
// invalid base32 texts
text::parse::<structs::NSEC3>("1 2 300 - v").unwrap_err();
text::parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err();
// invalid (empty) next-hashed values
packet::deserialize::<structs::NSEC3>(Bytes::from_static(b"\x01\x02\x01\x2c\x00\x00")).unwrap_err();
}
#[test]
fn test_nsec3param() {
check::<structs::NSEC3PARAM>("1 2 300 -", b"\x01\x02\x01\x2c\x00").unwrap();
check::<structs::NSEC3PARAM>("1 2 300 ab", b"\x01\x02\x01\x2c\x01\xab").unwrap();
// `salt` hex string must not contain spaces
text::parse::<structs::NSEC3PARAM>("1 2 300 a b").unwrap_err();
}
#[test]
fn test_spf() {
test_txt_for::<structs::SPF>();
}

View File

@ -0,0 +1,47 @@
use bytes::Bytes;
use common_types::*;
use failure::{ResultExt, Fail};
use ser::text::{DnsTextFormatter, next_field};
use ser::packet::remaining_bytes;
use common_types::binary::HEXLOWER_PERMISSIVE_ALLOW_WS;
use std::fmt;
use errors::*;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UnknownRecord {
rr_type: Type,
raw: Bytes,
}
impl UnknownRecord {
pub fn new(rr_type: Type, raw: Bytes) -> Self {
UnknownRecord {
rr_type: rr_type,
raw: raw,
}
}
pub fn deserialize(rr_type: Type, data: &mut ::std::io::Cursor<Bytes>) -> Result<Self> {
Ok(UnknownRecord::new(rr_type, remaining_bytes(data)))
}
pub fn dns_parse(rr_type: Type, data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
ensure!(field == r"\#", "expect \\# token to mark generic encoding");
let field = next_field(data).context("generic record data length")?;
let len: usize = field.parse()?;
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;
ensure!(len == result.len(), "length {} doesn't match length of encoded data {}", len, result.len());
Ok(UnknownRecord {
rr_type,
raw: result.into(),
})
}
pub fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "TYPE{} \\# {} {}", self.rr_type.0, self.raw.len(), HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.raw))
}
}

View File

@ -0,0 +1,153 @@
use bytes::{Bytes, Buf};
use common_types::*;
use failure::ResultExt;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter};
use std::fmt;
use std::io::Read;
use std::net::Ipv6Addr;
// deriving RRData will add a unit test to make sure the type is
// registered; there must be a records::types::$name `Type` constant
// with the same name as the struct.
// class independent
#[derive(Clone, PartialEq, Eq, Debug, RRData)]
pub enum LOC {
Version0(LOC0),
UnknownVersion{
version: u8,
data: Bytes,
},
}
impl DnsPacketData for LOC {
fn deserialize(data: &mut ::std::io::Cursor<Bytes>) -> ::errors::Result<Self> {
let version: u8 = DnsPacketData::deserialize(data)?;
if 0 == version {
Ok(LOC::Version0(DnsPacketData::deserialize(data)?))
} else {
Ok(LOC::UnknownVersion{
version: version,
data: ::ser::packet::remaining_bytes(data),
})
}
}
}
impl DnsTextData for LOC {
fn dns_parse(_data: &mut &str) -> ::errors::Result<Self> {
unimplemented!()
}
fn dns_format(&self, _f: &mut DnsTextFormatter) -> fmt::Result {
// always prefer binary representation
Err(fmt::Error)
}
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData)]
pub struct LOC0 {
size: u8,
horizontal_precision: u8,
vertical_precision: u8,
latitude: u32,
longitude: u32,
altitude: u32,
}
// class IN
#[derive(Clone, PartialEq, Eq, Debug, RRData)]
pub struct A6 {
prefix: u8, // [0...128]
// might include non-zero padding
dirty_suffix: Ipv6Addr,
suffix: Ipv6Addr,
prefix_name: Option<DnsCanonicalName>,
}
impl DnsPacketData for A6 {
fn deserialize(data: &mut ::std::io::Cursor<Bytes>) -> ::errors::Result<Self> {
let prefix: u8 = DnsPacketData::deserialize(data)
.context("failed parsing field A6::prefix")?;
ensure!(prefix <= 128, "invalid A6::prefix {}", prefix);
let suffix_offset = (prefix / 8) as usize;
debug_assert!(suffix_offset <= 16);
let suffix_len = 16 - suffix_offset;
check_enough_data!(data, suffix_len, "A6::suffix");
let mut addr = [0u8; 16];
data.read_exact(&mut addr[suffix_offset..16])?;
let dirty_suffix = Ipv6Addr::from(addr);
if suffix_offset < 16 {
let mask = 0xff >> (prefix % 8);
addr[suffix_offset] &= mask;
}
let suffix = Ipv6Addr::from(addr);
let prefix_name = if data.has_remaining() {
Some(DnsPacketData::deserialize(data)?)
} else {
None
};
Ok(A6 {
prefix,
dirty_suffix,
suffix,
prefix_name,
})
}
}
impl DnsTextData for A6 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let prefix: u8 = DnsTextData::dns_parse(data)
.context("failed parsing field A6::prefix")?;
ensure!(prefix <= 128, "invalid A6::prefix {}", prefix);
let suffix_offset = (prefix / 8) as usize;
debug_assert!(suffix_offset <= 16);
let suffix: Ipv6Addr = DnsTextData::dns_parse(data)
.context("failed parsing field A6::suffix")?;
// clear prefix bits
let mut suffix = suffix.octets();
for i in 0..suffix_offset {
suffix[i] = 0;
}
if suffix_offset < 16 {
let mask = 0xff >> (prefix % 8);
suffix[suffix_offset] &= mask;
}
let suffix = Ipv6Addr::from(suffix);
let prefix_name = if !data.is_empty() {
Some(DnsTextData::dns_parse(data)
.context("failed parsing field A6::prefix_name")?)
} else {
None
};
Ok(A6 {
prefix,
dirty_suffix: suffix.clone(),
suffix,
prefix_name,
})
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
if self.dirty_suffix != self.suffix {
// parsing text clears the padding, so we would loose data.
// use binary representation instead.
return Err(fmt::Error);
}
write!(f, "{} {}", self.prefix, self.suffix)?;
if let Some(ref prefix_name) = self.prefix_name {
write!(f, "{}", prefix_name)?;
}
Ok(())
}
}

View File

@ -4,4 +4,4 @@ mod rrdata;
pub use self::packet::DnsPacketData;
pub use self::text::DnsTextData;
pub use self::rrdata::RRData;
pub use self::rrdata::{RRDataPacket, RRData, StaticRRData};

View File

@ -19,3 +19,21 @@ where
}
Ok(result)
}
pub fn remaining_bytes(data: &mut Cursor<Bytes>) -> Bytes {
let pos = data.position() as usize;
let len = data.remaining();
let result = data.get_ref().slice(pos, pos + len);
data.advance(len);
result
}
pub fn short_blob(data: &mut Cursor<Bytes>) -> Result<Bytes> {
check_enough_data!(data, 1, "short blob length");
let blob_len = data.get_u8() as usize;
check_enough_data!(data, blob_len, "short blob content");
let pos = data.position() as usize;
let blob = data.get_ref().slice(pos, pos + blob_len);
data.advance(blob_len);
Ok(blob)
}

View File

@ -1,8 +1,24 @@
use std::borrow::Cow;
use bytes::Bytes;
use common_types::Type;
use errors::*;
use std::io::Cursor;
use ser::DnsPacketData;
pub trait RRData: super::DnsPacketData {
fn rr_type() -> Type;
fn rr_type_name() -> Cow<'static, str>;
pub trait RRDataPacket: Sized {
fn deserialize_rr_data(ttl: u32, rr_class: u16, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Self>;
}
impl<T: DnsPacketData> RRDataPacket for T {
fn deserialize_rr_data(_ttl: u32, _rr_class: u16, _rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Self> {
T::deserialize(data)
}
}
pub trait RRData: RRDataPacket + super::DnsTextData {
fn rr_type(&self) -> Type;
}
pub trait StaticRRData: RRData {
const TYPE: Type;
const NAME: &'static str;
}

View File

@ -1,4 +1,112 @@
pub trait DnsTextData: Sized {
fn dns_parse(data: &str) -> ::errors::Result<Self>;
fn dns_format(&self) -> ::errors::Result<String>;
use std::fmt;
mod std_impls;
pub mod quoted;
pub fn skip_whitespace(data: &mut &str) {
*data = (*data).trim_left();
}
pub fn next_field<'a>(data: &mut &'a str) -> ::errors::Result<&'a str> {
*data = (*data).trim_left();
if data.is_empty() { bail!("missing field"); }
match data.find(char::is_whitespace) {
None => {
let result = *data;
*data = "";
Ok(result)
},
Some(next) => {
let result = &(*data)[..next];
*data = &(*data)[next..].trim_left();
Ok(result)
},
}
}
pub fn next_quoted_field(data: &mut &str) -> ::errors::Result<Vec<u8>> {
*data = (*data).trim_left();
if data.is_empty() { bail!("missing field"); }
let result = quoted::UnquoteIterator::new(data).collect::<Result<Vec<_>, _>>()?;
Ok(result)
}
pub fn quote(data: &[u8]) -> String {
let mut result = String::with_capacity(data.len() + 2);
result.push('"');
for qc in quoted::EncodeIterator::new_quoted(data) {
result += &qc;
}
result.push('"');
result
}
// also escapes whitespace, but doesn't use quotes to surround it
pub fn escape(data: &[u8]) -> String {
let mut result = String::with_capacity(data.len());
for qc in quoted::EncodeIterator::new_encode_whitespace(data) {
result += &qc;
}
result
}
/// Each call to write!() makes sure a space is emitted to separate it
/// from previous fields.
pub struct DnsTextFormatter<'a> {
f: &'a mut fmt::Formatter<'a>,
need_space: bool,
}
impl<'a> DnsTextFormatter<'a> {
pub fn new(f: &'a mut fmt::Formatter<'a>) -> Self {
DnsTextFormatter {
f: f,
need_space: false,
}
}
/// make sure a field separator was emitted
pub fn next_field(&mut self) -> fmt::Result {
if self.need_space {
write!(self.f, " ")?;
self.need_space = false;
}
Ok(())
}
/// a field was emitted through `inner`; next field needs a separator
pub fn end_field(&mut self) {
self.need_space = true;
}
/// direct access to underlying output; you'll need to call
/// `next_field` and `end_field` manually.
pub fn inner(&mut self) -> &mut fmt::Formatter<'a> {
self.f
}
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
self.next_field()?;
self.f.write_fmt(args)?;
self.end_field();
Ok(())
}
}
pub trait DnsTextData: Sized {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self>;
// format might fail if there is no (known) text representation.
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result;
}
pub fn parse<T>(data: &str) -> ::errors::Result<T>
where
T: DnsTextData
{
let mut data = data;
let result = T::dns_parse(&mut data)?;
let data = data.trim();
ensure!(data.is_empty(), "didn't parse complete text, remaining: {:?}", data);
Ok(result)
}

View File

@ -0,0 +1,225 @@
use std::fmt;
pub struct EncodedByte {
storage: [u8; 4], // max: `\000`
used: u8,
}
impl ::std::ops::Deref for EncodedByte {
type Target = str;
fn deref(&self) -> &Self::Target {
::unsafe_ops::from_utf8_unchecked(&self.storage[..self.used as usize])
}
}
pub struct EncodeIterator<'a> {
encode_whitespace: bool,
data: &'a [u8]
}
impl<'a> EncodeIterator<'a> {
pub fn new_quoted(value: &'a [u8]) -> Self {
EncodeIterator{
encode_whitespace: false,
data: value,
}
}
pub fn new_encode_whitespace(value: &'a [u8]) -> Self {
EncodeIterator{
encode_whitespace: true,
data: value,
}
}
}
impl<'a> Iterator for EncodeIterator<'a> {
type Item = EncodedByte;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() { return None; }
let b = self.data[0];
self.data = &self.data[1..];
if b < 32 || b > 127 || (self.encode_whitespace && is_ascii_whitespace(b)) {
// `\ddd`
let d1 = b / 100;
let d2 = (b / 10) % 10;
let d3 = b % 10;
Some(EncodedByte{
storage: [b'\\', b'0' + d1, b'0' + d2, b'0' + d3],
used: 4,
})
} else if b == b'"' || b == b'\\' {
// `\c`
Some(EncodedByte{
storage: [b'\\', b, 0, 0],
used: 2,
})
} else {
Some(EncodedByte{
storage: [b, 0, 0, 0],
used: 1,
})
}
}
}
#[derive(Debug)]
pub struct UnquoteError {
data: String,
position: usize,
msg: &'static str,
}
impl fmt::Display for UnquoteError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "unquote error at position {} in {:?}: {}", self.position, self.data, self.msg)
}
}
impl ::failure::Fail for UnquoteError {}
pub struct UnquoteIterator<'a, 'b: 'a> {
quoted: bool,
data: &'a mut &'b str,
pos: usize,
}
/// when walked to end without hitting errors between, the terminating
/// `"` and following whitespace will be removed from `*data`.
impl<'a, 'b: 'a> UnquoteIterator<'a, 'b> {
pub fn new(data: &'a mut &'b str) -> Self {
UnquoteIterator {
quoted: false,
data: data,
pos: 0,
}
}
fn err<T>(&mut self, msg: &'static str) -> Option<Result<T, UnquoteError>> {
Some(Err(UnquoteError{
data: (*self.data).into(),
position: self.pos,
msg: msg,
}))
}
}
pub(crate) fn is_ascii_whitespace(c: u8) -> bool {
match c {
0x09 => true, // horizontal tab: \t
0x0a => true, // line feed: \n
0x0c => true, // form feed: \f
0x0d => true, // form feed: \r
0x20 => true, // space: ' '
_ => false,
}
}
impl<'a, 'b: 'a> Iterator for UnquoteIterator<'a, 'b> {
type Item = Result<u8, UnquoteError>;
fn next(&mut self) -> Option<Self::Item> {
let raw = self.data.as_bytes();
if raw.is_empty() { return self.err("empty input"); }
if 0 == self.pos {
// check for starting quote:
if raw[0] == b'"' {
self.quoted = true;
self.pos += 1;
}
}
if self.pos >= raw.len() {
if self.quoted {
return self.err("unexpected end of string");
} else {
*self.data = "";
return None;
}
}
if raw[self.pos] == b'"' {
if self.quoted {
// eat terminating quote
// pos+1 is obviously a good utf-8 boundary
*self.data = self.data[self.pos+1..].trim_left();
return None;
} else {
return self.err("quote in the middle of unquoted string");
}
} else if !self.quoted && is_ascii_whitespace(raw[self.pos]) {
// pos is obviously a good utf-8 boundary
*self.data = self.data[self.pos..].trim_left();
return None;
} else if raw[self.pos] == b'\\' {
if self.pos + 1 >= raw.len() { return self.err("unexpected end of string after backslash"); }
if raw[self.pos+1] < b'0' || raw[self.pos+1] > b'9' {
let result = raw[self.pos+1];
if !self.quoted && is_ascii_whitespace(result) {
return self.err("(escaped) whitespace not allowed in unquoted field");
}
self.pos += 2;
return Some(Ok(result));
}
// otherwise require 3 decimal digits
if self.pos + 3 >= raw.len() { return self.err("unexpected end of string after backslash with decimal"); }
// raw[self.pos+1] already checked for digit above
if raw[self.pos+2] < b'0' || raw[self.pos+2] > b'9' || raw[self.pos+3] < b'0' || raw[self.pos+3] > b'9' {
return self.err("expecting 3 digits after backslash with decimal");
}
let d1 = raw[self.pos+1] - b'0';
let d2 = raw[self.pos+2] - b'0';
let d3 = raw[self.pos+3] - b'0';
let val = (d1 as u32 * 100) + (d2 as u32 * 10) + (d3 as u32);
if val > 255 { return self.err("invalid decimal escape"); }
self.pos += 4;
Some(Ok(val as u8))
} else {
let result = raw[self.pos];
self.pos += 1;
Some(Ok(result))
}
}
}
#[cfg(test)]
mod tests {
use ser::text::{next_quoted_field, quote};
fn check_quote(data: &[u8], quoted: &str) {
assert_eq!(
quote(data),
quoted
);
}
fn check_unquote(mut input: &str, data: &[u8]) {
assert_eq!(
next_quoted_field(&mut input).unwrap(),
data
);
assert!(input.is_empty());
}
#[test]
fn test_escapes() {
check_quote(b"\"hello \\ \xc3\xa4", r#""\"hello \\ \195\164""#);
}
#[test]
fn test_parser() {
check_unquote(r#""\"hello \\ \195\164""#, b"\"hello \\ \xc3\xa4");
check_unquote(r#" "\"hello \\ \195\164" "#, b"\"hello \\ \xc3\xa4");
check_unquote(r#""\"hello \\ ä""#, b"\"hello \\ \xc3\xa4");
check_unquote(r#" "\"hello \\ ä" "#, b"\"hello \\ \xc3\xa4");
// unquoted input
check_unquote(r#"foobarä"#, b"foobar\xc3\xa4");
check_unquote(r#"foobar\195\164"#, b"foobar\xc3\xa4");
check_unquote(r#" foobarä "#, b"foobar\xc3\xa4");
// random (unnecessary) escapes:
check_unquote(r#" "\x\%\@\." "#, b"x%@.");
}
}

View File

@ -0,0 +1,100 @@
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
/* only decimal representations are used for numbers */
impl DnsTextData for u8 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl DnsTextData for u16 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl DnsTextData for u32 {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
/* only decimal representations are needed for octets */
impl DnsTextData for Ipv4Addr {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
/* representation as in RFC 3513: https://tools.ietf.org/html/rfc3513#section-2.2 */
impl DnsTextData for Ipv6Addr {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(next_field(data)?.parse()?)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn deserialize<T: DnsTextData>(data: &str) -> ::errors::Result<T> {
let mut data = data;
let res = T::dns_parse(&mut data)?;
let data = data.trim();
ensure!(data.is_empty(), "didn't read data completely");
Ok(res)
}
#[test]
fn test_ipv6() {
assert_eq!(
deserialize::<Ipv6Addr>("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210").unwrap(),
Ipv6Addr::new(
0xfedc, 0xba98, 0x7654, 0x3210, 0xfedc, 0xba98, 0x7654, 0x3210
)
);
assert_eq!(
deserialize::<Ipv6Addr>("1080::8:800:200C:417A").unwrap(),
Ipv6Addr::new(
0x1080, 0, 0, 0, 0x8, 0x800, 0x200c, 0x417a
)
);
assert_eq!(
deserialize::<Ipv6Addr>("::13.1.68.3").unwrap(),
Ipv6Addr::new(
0, 0, 0, 0, 0, 0, 0x0d01, 0x4403
)
);
assert_eq!(
deserialize::<Ipv6Addr>("::FFFF:129.144.52.38").unwrap(),
Ipv6Addr::new(
0, 0, 0, 0, 0, 0xffff, 0x8190, 0x3426
)
);
}
}

View File

@ -0,0 +1,11 @@
#[cfg(not(feature = "no-unsafe"))]
pub fn from_utf8_unchecked(v: &[u8]) -> &str {
unsafe {
::std::str::from_utf8_unchecked(v)
}
}
#[cfg(feature = "no-unsafe")]
pub fn from_utf8_unchecked(v: &[u8]) -> &str {
::std::str::from_utf8(v).expect("from_utf8_unchecked")
}

View File

@ -1,7 +1,7 @@
use syn;
use quote;
pub fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
let fields = match ast.body {
syn::Body::Enum(_) => panic!("Deriving DnsPacketData not supported for enum types"),
syn::Body::Struct(syn::VariantData::Struct(ref s)) => s,
@ -25,9 +25,10 @@ pub fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
let mut parse_fields = quote!{};
for field in fields {
let field_name = field.ident.as_ref().unwrap();
let ctx_msg = format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name));
parse_fields = quote!{#parse_fields
#field_name: DnsPacketData::deserialize(_data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?,
#field_name: DnsPacketData::deserialize(_data).context(#ctx_msg)?,
};
}

View File

@ -0,0 +1,56 @@
use syn;
use quote;
pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
let fields = match ast.body {
syn::Body::Enum(_) => panic!("Deriving DnsTextData not supported for enum types"),
syn::Body::Struct(syn::VariantData::Struct(ref s)) => s,
syn::Body::Struct(_) => panic!("Deriving DnsTextData not supported for unit / tuple struct types"),
};
if !ast.generics.ty_params.is_empty() {
panic!("Type parameters not supported for deriving DnsTextData");
}
if !ast.generics.where_clause.predicates.is_empty() {
panic!("Where clauses not supported for deriving DnsTextData");
}
if !ast.generics.lifetimes.is_empty() {
panic!("Lifetimes not supported for deriving DnsTextData");
}
let name = &ast.ident;
let mut parse_fields = quote!{};
let mut format_fields = quote!{};
for field in fields {
let field_name = field.ident.as_ref().unwrap();
parse_fields = quote!{#parse_fields
#field_name: DnsTextData::dns_parse(_data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?,
};
format_fields = quote!{#format_fields
DnsTextData::dns_format(&self.#field_name, f)?;
};
}
quote!{
#[allow(unused_imports)]
impl ::dnsbox_base::ser::DnsTextData for #name {
fn dns_parse(_data: &mut &str) -> ::dnsbox_base::errors::Result<Self> {
use dnsbox_base::failure::ResultExt;
use dnsbox_base::ser::DnsTextData;
Ok(#name{ #parse_fields })
}
fn dns_format(&self, f: &mut ::dnsbox_base::ser::text::DnsTextFormatter) -> ::std::fmt::Result {
use dnsbox_base::ser::DnsTextData;
use std::fmt::{self, Write};
#format_fields
Ok(())
}
}
}
}

View File

@ -1,3 +1,5 @@
#![recursion_limit="128"]
extern crate proc_macro;
extern crate syn;
#[macro_use]
@ -6,26 +8,56 @@ extern crate quote;
use proc_macro::TokenStream;
mod dns_packet_data;
mod dns_text_data;
mod rrdata;
fn attr_get_single_list_arg(attr: &syn::Attribute) -> quote::Tokens {
match attr.value {
syn::MetaItem::List(_, ref l) => {
if l.len() != 1 {
panic!("{:?} attribute requires exactly one argument", attr.name());
}
let arg = &l[0];
quote!{#arg}
},
syn::MetaItem::NameValue(_, ref l) => {
quote!{#l}
},
_ => {
panic!("{:?} argument requires one argument like: [#{}(...)]", attr.name(), attr.name());
},
}
}
#[proc_macro_derive(DnsPacketData)]
pub fn derive_dns_packet_data(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_derive_input(&s).unwrap();
let gen = dns_packet_data::impl_hello_world(&ast);
let gen = dns_packet_data::build(&ast);
gen.parse().unwrap()
}
#[proc_macro_derive(RRData)]
#[proc_macro_derive(DnsTextData)]
pub fn derive_dns_text_data(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_derive_input(&s).unwrap();
let gen = dns_text_data::build(&ast);
gen.parse().unwrap()
}
#[proc_macro_derive(RRData, attributes(RRTypeName))]
pub fn derive_rr_data(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_derive_input(&s).unwrap();
let gen = rrdata::impl_rr_data(&ast);
let gen = rrdata::build(&ast);
gen.parse().unwrap()
}

View File

@ -1,7 +1,34 @@
use syn;
use quote;
pub fn impl_rr_data(ast: &syn::DeriveInput) -> quote::Tokens {
use super::{attr_get_single_list_arg};
#[derive(Clone,Debug)]
enum StructAttribute {
RRTypeName(quote::Tokens),
}
struct StructAttributeParser<'a>(pub &'a [syn::Attribute]);
impl<'a> Iterator for StructAttributeParser<'a> {
type Item = StructAttribute;
fn next(&mut self) -> Option<Self::Item> {
while !self.0.is_empty() {
let a = &self.0[0];
self.0 = &self.0[1..];
if a.is_sugared_doc { continue; }
match a.value.name() {
"RRTypeName" => {
return Some(StructAttribute::RRTypeName(attr_get_single_list_arg(a)));
},
_ => (),
}
}
None
}
}
pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
if !ast.generics.ty_params.is_empty() {
panic!("Type parameters not supported for deriving RRData");
}
@ -15,31 +42,44 @@ pub fn impl_rr_data(ast: &syn::DeriveInput) -> quote::Tokens {
}
let name = &ast.ident;
let mut name_str = {
let name_str: &str = name.as_ref();
quote!{#name_str}
};
for attr in StructAttributeParser(&ast.attrs) {
match attr {
StructAttribute::RRTypeName(name) => {
name_str = name;
},
}
}
let test_mod_name = syn::Ident::from(format!("test_rr_{}", name));
quote!{
impl ::dnsbox_base::ser::RRData for #name {
fn rr_type() -> ::dnsbox_base::common_types::Type {
::dnsbox_base::records::types::#name
fn rr_type(&self) -> ::dnsbox_base::common_types::Type {
::dnsbox_base::common_types::types::#name
}
}
fn rr_type_name() -> ::std::borrow::Cow<'static, str> {
::std::borrow::Cow::Borrowed(#name_str)
}
impl ::dnsbox_base::ser::StaticRRData for #name {
const TYPE: ::dnsbox_base::common_types::Type = ::dnsbox_base::common_types::types::#name;
const NAME: &'static str = #name_str;
}
// #[cfg(test)]
#[allow(non_snake_case, unused_imports)]
mod #test_mod_name {
use ::dnsbox_base::records::registry;
use ::dnsbox_base::records::types;
use dnsbox_base::records::registry;
use dnsbox_base::common_types::types;
use dnsbox_base::ser::StaticRRData;
#[test]
fn test_registry() {
assert_eq!(
registry::known_name_to_type(#name_str),
registry::known_name_to_type(#name::NAME),
Some(types::#name)
);
}