From 703baf105592a00525aee5c818f20689d3a5ee81 Mon Sep 17 00:00:00 2001 From: Marshall Pierce Date: Fri, 13 Mar 2026 15:37:55 -0600 Subject: [PATCH 1/3] Update MSRV to 1.75.0 and fix all rustc and clippy warnings --- .gitignore | 1 + CHANGELOG.md | 8 +++ Cargo.toml | 1 + examples/sync_tcp_client.rs | 11 ++-- examples/sync_udp_client.rs | 11 ++-- rust-toolchain.toml | 2 + src/builder.rs | 4 +- src/enums.rs | 14 ++--- src/lib.rs | 1 + src/name.rs | 12 ++--- src/parser.rs | 105 ++++++++++++++++++------------------ src/rdata/cname.rs | 2 +- src/rdata/mx.rs | 18 +++---- src/rdata/soa.rs | 9 ++-- src/rdata/srv.rs | 22 ++++---- src/rdata/txt.rs | 8 +-- 16 files changed, 118 insertions(+), 111 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 rust-toolchain.toml diff --git a/.gitignore b/.gitignore index 4cd31f5..d089c2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.vagga /target /Cargo.lock +/.idea \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5ad57de --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Next + +- Update MSRV to 1.75.0, currently the older of rustc in Ubuntu LTS (Noble Numbat, 1.75.0) and Debian Stable (trixie, 1.85.0) + - Small syntactic tweaks to address all compiler and clippy warnings + +# 0.8.0 + +Current version when changelog started \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 455c3bc..53445b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ serde_derive = { version = "1.0", optional = true } [dev-dependencies] matches = "0.1.2" +itertools = "0.14.0" diff --git a/examples/sync_tcp_client.rs b/examples/sync_tcp_client.rs index 732e8d3..e8be91c 100644 --- a/examples/sync_tcp_client.rs +++ b/examples/sync_tcp_client.rs @@ -26,7 +26,7 @@ fn main() { process::exit(code); } -fn resolve(name: &str) -> Result<(), Box> { +fn resolve(name: &str) -> Result<(), Box> { let mut conn = TcpStream::connect("127.0.0.1:53")?; let mut builder = Builder::new_query(1, true); builder.add_question(name, false, QueryType::A, QueryClass::IN); @@ -62,15 +62,12 @@ fn resolve(name: &str) -> Result<(), Box> { if pkt.header.response_code != ResponseCode::NoError { return Err(pkt.header.response_code.into()); } - if pkt.answers.len() == 0 { + if pkt.answers.is_empty() { return Err("No records received".into()); } for ans in pkt.answers { - match ans.data { - RData::A(Record(ip)) => { - println!("{}", ip); - } - _ => {} // ignore + if let RData::A(Record(ip)) = ans.data { + println!("{}", ip); } } Ok(()) diff --git a/examples/sync_udp_client.rs b/examples/sync_udp_client.rs index f8bf426..e9ed4a3 100644 --- a/examples/sync_udp_client.rs +++ b/examples/sync_udp_client.rs @@ -25,7 +25,7 @@ fn main() { process::exit(code); } -fn resolve(name: &str) -> Result<(), Box> { +fn resolve(name: &str) -> Result<(), Box> { let sock = UdpSocket::bind("127.0.0.1:0")?; sock.connect("127.0.0.1:53")?; let mut builder = Builder::new_query(1, true); @@ -38,15 +38,12 @@ fn resolve(name: &str) -> Result<(), Box> { if pkt.header.response_code != ResponseCode::NoError { return Err(pkt.header.response_code.into()); } - if pkt.answers.len() == 0 { + if pkt.answers.is_empty() { return Err("No records received".into()); } for ans in pkt.answers { - match ans.data { - RData::A(Record(ip)) => { - println!("{}", ip); - } - _ => {} // ignore + if let RData::A(Record(ip)) = ans.data { + println!("{}", ip); } } Ok(()) diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..4dd8e5c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.75.0" \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index 22aff0b..362bc76 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -19,7 +19,7 @@ impl Builder { pub fn new_query(id: u16, recursion: bool) -> Builder { let mut buf = Vec::with_capacity(512); let head = Header { - id: id, + id, query: true, opcode: Opcode::StandardQuery, authoritative: false, @@ -36,7 +36,7 @@ impl Builder { }; buf.extend([0u8; 12].iter()); head.write(&mut buf[..12]); - Builder { buf: buf } + Builder { buf } } /// Adds a question to the packet /// diff --git a/src/enums.rs b/src/enums.rs index 9f58ddf..bf6ef24 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -167,10 +167,10 @@ impl From for Opcode { } } } -impl Into for Opcode { - fn into(self) -> u16 { +impl From for u16 { + fn from(value: Opcode) -> u16 { use self::Opcode::*; - match self { + match value { StandardQuery => 0, InverseQuery => 1, ServerStatusRequest => 2, @@ -189,15 +189,15 @@ impl From for ResponseCode { 3 => NameError, 4 => NotImplemented, 5 => Refused, - 6...15 => Reserved(code), + 6..=15 => Reserved(code), x => panic!("Invalid response code {}", x), } } } -impl Into for ResponseCode { - fn into(self) -> u8 { +impl From for u8 { + fn from(value: ResponseCode) -> u8 { use self::ResponseCode::*; - match self { + match value { NoError => 0, FormatError => 1, ServerFailure => 2, diff --git a/src/lib.rs b/src/lib.rs index e89c005..1fd0bff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ extern crate byteorder; #[cfg(test)] #[macro_use] extern crate matches; #[macro_use(quick_error)] extern crate quick_error; #[cfg(feature = "with-serde")] #[macro_use] extern crate serde_derive; +#[cfg(test)] extern crate itertools; mod enums; mod structs; diff --git a/src/name.rs b/src/name.rs index 8763259..3079f7c 100644 --- a/src/name.rs +++ b/src/name.rs @@ -55,7 +55,7 @@ impl<'a> Name<'a> { // Set value for return_pos which is the pos in the original // data buffer that should be used to return after validating // the offsetted labels. - if let None = return_pos { + if return_pos.is_none() { return_pos = Some(pos); } @@ -85,9 +85,9 @@ impl<'a> Name<'a> { byte = parse_data[pos]; } if let Some(return_pos) = return_pos { - return Ok(Name {labels: &data[..return_pos+2], original: original}); + Ok(Name {labels: &data[..return_pos+2], original }) } else { - return Ok(Name {labels: &data[..pos+1], original: original }); + Ok(Name {labels: &data[..pos+1], original }) } } /// Number of bytes serialized name occupies @@ -109,16 +109,16 @@ impl<'a> fmt::Display for Name<'a> { let off = (BigEndian::read_u16(&data[pos..pos+2]) & !0b1100_0000_0000_0000) as usize; if pos != 0 { - try!(fmt.write_char('.')); + fmt.write_char('.')?; } return fmt::Display::fmt( &Name::scan(&original[off..], original).unwrap(), fmt) } else if byte & 0b1100_0000 == 0 { if pos != 0 { - try!(fmt.write_char('.')); + fmt.write_char('.')?; } let end = pos + byte as usize + 1; - try!(fmt.write_str(from_utf8(&data[pos+1..end]).unwrap())); + fmt.write_str(from_utf8(&data[pos + 1..end]).unwrap())?; pos = end; continue; } else { diff --git a/src/parser.rs b/src/parser.rs index 0718798..6a3f266 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12,58 +12,58 @@ impl<'a> Packet<'a> { /// Parse a full DNS Packet and return a structure that has all the /// data borrowed from the passed buffer. pub fn parse(data: &[u8]) -> Result { - let header = try!(Header::parse(data)); + let header = Header::parse(data)?; let mut offset = Header::size(); let mut questions = Vec::with_capacity(header.questions as usize); for _ in 0..header.questions { - let name = try!(Name::scan(&data[offset..], data)); + let name = Name::scan(&data[offset..], data)?; offset += name.byte_len(); if offset + 4 > data.len() { return Err(Error::UnexpectedEOF); } - let qtype = try!(QueryType::parse( - BigEndian::read_u16(&data[offset..offset+2]))); + let qtype = QueryType::parse( + BigEndian::read_u16(&data[offset..offset + 2]))?; offset += 2; - let (prefer_unicast, qclass) = try!(parse_qclass_code( - BigEndian::read_u16(&data[offset..offset+2]))); + let (prefer_unicast, qclass) = parse_qclass_code( + BigEndian::read_u16(&data[offset..offset + 2]))?; offset += 2; questions.push(Question { qname: name, - qtype: qtype, - prefer_unicast: prefer_unicast, - qclass: qclass, + qtype, + prefer_unicast, + qclass, }); } let mut answers = Vec::with_capacity(header.answers as usize); for _ in 0..header.answers { - answers.push(try!(parse_record(data, &mut offset))); + answers.push(parse_record(data, &mut offset)?); } let mut nameservers = Vec::with_capacity(header.nameservers as usize); for _ in 0..header.nameservers { - nameservers.push(try!(parse_record(data, &mut offset))); + nameservers.push(parse_record(data, &mut offset)?); } let mut additional = Vec::with_capacity(header.additional as usize); let mut opt = None; for _ in 0..header.additional { if offset + 3 <= data.len() && data[offset..offset+3] == OPT_RR_START { if opt.is_none() { - opt = Some(try!(parse_opt_record(data, &mut offset))); + opt = Some(parse_opt_record(data, &mut offset)?); } else { return Err(Error::AdditionalOPT); } } else { - additional.push(try!(parse_record(data, &mut offset))); + additional.push(parse_record(data, &mut offset)?); } } Ok(Packet { - header: header, - questions: questions, - answers: answers, - nameservers: nameservers, - additional: additional, - opt: opt, + header, + questions, + answers, + nameservers, + additional, + opt, }) } } @@ -72,7 +72,7 @@ fn parse_qclass_code(value: u16) -> Result<(bool, QueryClass), Error> { let prefer_unicast = value & 0x8000 == 0x8000; let qclass_code = value & 0x7FFF; - let qclass = try!(QueryClass::parse(qclass_code)); + let qclass = QueryClass::parse(qclass_code)?; Ok((prefer_unicast, qclass)) } @@ -80,23 +80,23 @@ fn parse_class_code(value: u16) -> Result<(bool, Class), Error> { let is_unique = value & 0x8000 == 0x8000; let class_code = value & 0x7FFF; - let cls = try!(Class::parse(class_code)); + let cls = Class::parse(class_code)?; Ok((is_unique, cls)) } // Generic function to parse answer, nameservers, and additional records. fn parse_record<'a>(data: &'a [u8], offset: &mut usize) -> Result, Error> { - let name = try!(Name::scan(&data[*offset..], data)); + let name = Name::scan(&data[*offset..], data)?; *offset += name.byte_len(); if *offset + 10 > data.len() { return Err(Error::UnexpectedEOF); } - let typ = try!(Type::parse( - BigEndian::read_u16(&data[*offset..*offset+2]))); + let typ = Type::parse( + BigEndian::read_u16(&data[*offset..*offset + 2]))?; *offset += 2; let class_code = BigEndian::read_u16(&data[*offset..*offset+2]); - let (multicast_unique, cls) = try!(parse_class_code(class_code)); + let (multicast_unique, cls) = parse_class_code(class_code)?; *offset += 2; let mut ttl = BigEndian::read_u32(&data[*offset..*offset+4]); @@ -109,15 +109,15 @@ fn parse_record<'a>(data: &'a [u8], offset: &mut usize) -> Result data.len() { return Err(Error::UnexpectedEOF); } - let data = try!(RData::parse(typ, - &data[*offset..*offset+rdlen], data)); + let data = RData::parse(typ, + &data[*offset..*offset + rdlen], data)?; *offset += rdlen; Ok(ResourceRecord { - name: name, - multicast_unique: multicast_unique, - cls: cls, - ttl: ttl, - data: data, + name, + multicast_unique, + cls, + ttl, + data, }) } @@ -127,8 +127,8 @@ fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result, E return Err(Error::UnexpectedEOF); } *offset += 1; - let typ = try!(Type::parse( - BigEndian::read_u16(&data[*offset..*offset+2]))); + let typ = Type::parse( + BigEndian::read_u16(&data[*offset..*offset + 2]))?; if typ != Type::OPT { return Err(Error::InvalidType(typ as u16)); } @@ -146,16 +146,16 @@ fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result, E if *offset + rdlen > data.len() { return Err(Error::UnexpectedEOF); } - let data = try!(RData::parse(typ, - &data[*offset..*offset+rdlen], data)); + let data = RData::parse(typ, + &data[*offset..*offset + rdlen], data)?; *offset += rdlen; Ok(Opt { - udp: udp, - extrcode: extrcode, - version: version, - flags: flags, - data: data, + udp, + extrcode, + version, + flags, + data, }) } @@ -163,6 +163,7 @@ fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result, E mod test { use std::net::Ipv4Addr; + use itertools::Itertools; use {Packet, Header}; use Opcode::*; use ResponseCode::NoError; @@ -228,7 +229,7 @@ mod test { assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com"); assert_eq!(packet.answers.len(), 1); assert_eq!(&packet.answers[0].name.to_string()[..], "example.com"); - assert_eq!(packet.answers[0].multicast_unique, false); + assert!(!packet.answers[0].multicast_unique); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 1272); match packet.answers[0].data { @@ -248,7 +249,7 @@ mod test { let packet = Packet::parse(response).unwrap(); assert_eq!(packet.answers.len(), 1); - assert_eq!(packet.answers[0].multicast_unique, true); + assert!(packet.answers[0].multicast_unique); assert_eq!(packet.answers[0].cls, C::IN); } @@ -352,7 +353,7 @@ mod test { assert_eq!(packet.questions[0].qclass, QC::IN); assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); assert_eq!(packet.answers.len(), 6); - let ips = vec![ + let ips = [ Ipv4Addr::new(64, 233, 164, 100), Ipv4Addr::new(64, 233, 164, 139), Ipv4Addr::new(64, 233, 164, 113), @@ -360,13 +361,13 @@ mod test { Ipv4Addr::new(64, 233, 164, 101), Ipv4Addr::new(64, 233, 164, 138), ]; - for i in 0..6 { - assert_eq!(&packet.answers[i].name.to_string()[..], "google.com"); - assert_eq!(packet.answers[i].cls, C::IN); - assert_eq!(packet.answers[i].ttl, 239); - match packet.answers[i].data { + for (answer, ip) in packet.answers.iter().zip_eq(ips.iter()) { + assert_eq!(&answer.name.to_string()[..], "google.com"); + assert_eq!(answer.cls, C::IN); + assert_eq!(answer.ttl, 239); + match answer.data { RData::A(addr) => { - assert_eq!(addr.0, ips[i]); + assert_eq!(addr.0, *ip); } ref x => panic!("Wrong rdata {:?}", x), } @@ -397,7 +398,7 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::SRV); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(packet.questions[0].prefer_unicast, false); + assert!(!packet.questions[0].prefer_unicast); assert_eq!(&packet.questions[0].qname.to_string()[..], "_xmpp-server._tcp.gmail.com"); assert_eq!(packet.answers.len(), 0); @@ -412,7 +413,7 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(packet.questions[0].prefer_unicast, true); + assert!(packet.questions[0].prefer_unicast); } #[test] diff --git a/src/rdata/cname.rs b/src/rdata/cname.rs index 0dcb469..c8dcb99 100644 --- a/src/rdata/cname.rs +++ b/src/rdata/cname.rs @@ -80,7 +80,7 @@ mod test { ref x => panic!("Wrong rdata {:?}", x), } - let ips = vec![ + let ips = [ Ipv4Addr::new(104, 16, 103, 204), Ipv4Addr::new(104, 16, 107, 204), Ipv4Addr::new(104, 16, 104, 204), diff --git a/src/rdata/mx.rs b/src/rdata/mx.rs index 33c5c41..4bd3a55 100644 --- a/src/rdata/mx.rs +++ b/src/rdata/mx.rs @@ -25,7 +25,7 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { - + use itertools::Itertools; use {Packet, Header}; use Opcode::*; use ResponseCode::NoError; @@ -68,22 +68,22 @@ mod test { assert_eq!(&packet.questions[0].qname.to_string()[..], "gmail.com"); assert_eq!(packet.answers.len(), 5); - let items = vec![ + let items = [ ( 5, "gmail-smtp-in.l.google.com"), (10, "alt1.gmail-smtp-in.l.google.com"), (40, "alt4.gmail-smtp-in.l.google.com"), (20, "alt2.gmail-smtp-in.l.google.com"), (30, "alt3.gmail-smtp-in.l.google.com"), ]; - for i in 0..5 { - assert_eq!(&packet.answers[i].name.to_string()[..], + for (answer, item) in packet.answers.iter().zip_eq(items.iter()) { + assert_eq!(&answer.name.to_string()[..], "gmail.com"); - assert_eq!(packet.answers[i].cls, C::IN); - assert_eq!(packet.answers[i].ttl, 1148); - match *&packet.answers[i].data { + assert_eq!(answer.cls, C::IN); + assert_eq!(answer.ttl, 1148); + match answer.data { RData::MX( Record { preference, exchange }) => { - assert_eq!(preference, items[i].0); - assert_eq!(exchange.to_string(), (items[i].1).to_string()); + assert_eq!(preference, item.0); + assert_eq!(exchange.to_string(), item.1.to_string()); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/soa.rs b/src/rdata/soa.rs index e6d5f17..b28ae63 100644 --- a/src/rdata/soa.rs +++ b/src/rdata/soa.rs @@ -14,21 +14,20 @@ pub struct Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 6; fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { let mut pos = 0; - let primary_name_server = try!(Name::scan(rdata, original)); + let primary_name_server = Name::scan(rdata, original)?; pos += primary_name_server.byte_len(); - let mailbox = try!(Name::scan(&rdata[pos..], original)); + let mailbox = Name::scan(&rdata[pos..], original)?; pos += mailbox.byte_len(); if rdata[pos..].len() < 20 { return Err(Error::WrongRdataLength); } let record = Record { primary_ns: primary_name_server, - mailbox: mailbox, + mailbox, serial: BigEndian::read_u32(&rdata[pos..(pos+4)]), refresh: BigEndian::read_u32(&rdata[(pos+4)..(pos+8)]), retry: BigEndian::read_u32(&rdata[(pos+8)..(pos+12)]), @@ -83,7 +82,7 @@ mod test { assert_eq!(packet.nameservers.len(), 1); assert_eq!(&packet.nameservers[0].name.to_string()[..], "youtube.com"); assert_eq!(packet.nameservers[0].cls, C::IN); - assert_eq!(packet.nameservers[0].multicast_unique, false); + assert!(!packet.nameservers[0].multicast_unique); assert_eq!(packet.nameservers[0].ttl, 10800); match packet.nameservers[0].data { RData::SOA(ref soa_rec) => { diff --git a/src/rdata/srv.rs b/src/rdata/srv.rs index dbc151d..0d12a08 100644 --- a/src/rdata/srv.rs +++ b/src/rdata/srv.rs @@ -29,7 +29,7 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { - + use itertools::Itertools; use {Packet, Header}; use Opcode::*; use ResponseCode::NoError; @@ -76,24 +76,24 @@ mod test { assert_eq!(&packet.questions[0].qname.to_string()[..], "_xmpp-server._tcp.gmail.com"); assert_eq!(packet.answers.len(), 5); - let items = vec![ + let items = [ (5, 0, 5269, "xmpp-server.l.google.com"), (20, 0, 5269, "alt3.xmpp-server.l.google.com"), (20, 0, 5269, "alt1.xmpp-server.l.google.com"), (20, 0, 5269, "alt2.xmpp-server.l.google.com"), (20, 0, 5269, "alt4.xmpp-server.l.google.com"), ]; - for i in 0..5 { - assert_eq!(&packet.answers[i].name.to_string()[..], + for (answer, item) in packet.answers.iter().zip_eq(items.iter()) { + assert_eq!(&answer.name.to_string()[..], "_xmpp-server._tcp.gmail.com"); - assert_eq!(packet.answers[i].cls, C::IN); - assert_eq!(packet.answers[i].ttl, 900); - match *&packet.answers[i].data { + assert_eq!(answer.cls, C::IN); + assert_eq!(answer.ttl, 900); + match answer.data { RData::SRV(Record { priority, weight, port, target }) => { - assert_eq!(priority, items[i].0); - assert_eq!(weight, items[i].1); - assert_eq!(port, items[i].2); - assert_eq!(target.to_string(), (items[i].3).to_string()); + assert_eq!(priority, item.0); + assert_eq!(weight, item.1); + assert_eq!(port, item.2); + assert_eq!(target.to_string(), item.3.to_string()); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/txt.rs b/src/rdata/txt.rs index 8f5f5fc..01fe4ea 100644 --- a/src/rdata/txt.rs +++ b/src/rdata/txt.rs @@ -13,14 +13,14 @@ pub struct RecordIter<'a> { impl<'a> Iterator for RecordIter<'a> { type Item = &'a [u8]; fn next(&mut self) -> Option<&'a [u8]> { - if self.bytes.len() >= 1 { + if !self.bytes.is_empty() { let len = self.bytes[0] as usize; - debug_assert!(self.bytes.len() >= len+1); + debug_assert!(self.bytes.len() > len); let (head, tail) = self.bytes[1..].split_at(len); self.bytes = tail; return Some(head); } - return None; + None } } @@ -104,7 +104,7 @@ mod test { assert_eq!(&packet.questions[0].qname.to_string()[..], "facebook.com"); assert_eq!(packet.answers.len(), 1); assert_eq!(&packet.answers[0].name.to_string()[..], "facebook.com"); - assert_eq!(packet.answers[0].multicast_unique, false); + assert!(!packet.answers[0].multicast_unique); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 86333); match packet.answers[0].data { From b265bb91c7f7a5f3049cb351b7bf17806242220e Mon Sep 17 00:00:00 2001 From: Marshall Pierce Date: Fri, 13 Mar 2026 15:48:56 -0600 Subject: [PATCH 2/3] Apply `cargo fmt` I don't claim that rustfmt is always right, but rather that this will ease future collaboration. Without this, I have to manually format new code since I can't let the autoformatter do its work. Based on #50 --- examples/sync_tcp_client.rs | 18 +- examples/sync_udp_client.rs | 8 +- src/builder.rs | 27 +-- src/enums.rs | 137 +++++++------- src/header.rs | 195 +++++++++++--------- src/lib.rs | 38 ++-- src/name.rs | 71 ++++---- src/parser.rs | 348 +++++++++++++++++++----------------- src/rdata/a.rs | 3 +- src/rdata/aaaa.rs | 55 +++--- src/rdata/all.rs | 1 - src/rdata/axfr.rs | 1 - src/rdata/cname.rs | 51 +++--- src/rdata/hinfo.rs | 1 - src/rdata/maila.rs | 1 - src/rdata/mailb.rs | 1 - src/rdata/mb.rs | 1 - src/rdata/mf.rs | 1 - src/rdata/mg.rs | 1 - src/rdata/minfo.rs | 1 - src/rdata/mod.rs | 42 ++--- src/rdata/mr.rs | 1 - src/rdata/mx.rs | 61 ++++--- src/rdata/ns.rs | 96 +++++----- src/rdata/nsec.rs | 1 - src/rdata/null.rs | 1 - src/rdata/opt.rs | 1 - src/rdata/ptr.rs | 54 +++--- src/rdata/soa.rs | 98 +++++----- src/rdata/srv.rs | 64 ++++--- src/rdata/txt.rs | 74 ++++---- src/rdata/wks.rs | 1 - src/structs.rs | 9 +- 33 files changed, 771 insertions(+), 692 deletions(-) diff --git a/examples/sync_tcp_client.rs b/examples/sync_tcp_client.rs index e8be91c..1b9b4df 100644 --- a/examples/sync_tcp_client.rs +++ b/examples/sync_tcp_client.rs @@ -6,17 +6,15 @@ use std::io::{Read, Write}; use std::net::TcpStream; use std::process; - -use dns_parser::{Builder, Packet, RData, ResponseCode}; use dns_parser::rdata::a::Record; -use dns_parser::{QueryType, QueryClass}; - +use dns_parser::{Builder, Packet, RData, ResponseCode}; +use dns_parser::{QueryClass, QueryType}; fn main() { let mut code = 0; for name in env::args().skip(1) { match resolve(&name) { - Ok(()) => {}, + Ok(()) => {} Err(e) => { eprintln!("Error resolving {:?}: {}", name, e); code = 1; @@ -31,8 +29,10 @@ fn resolve(name: &str) -> Result<(), Box> { let mut builder = Builder::new_query(1, true); builder.add_question(name, false, QueryType::A, QueryClass::IN); let packet = builder.build().map_err(|_| "truncated packet")?; - let psize = [((packet.len() >> 8) & 0xFF) as u8, - (packet.len() & 0xFF) as u8]; + let psize = [ + ((packet.len() >> 8) & 0xFF) as u8, + (packet.len() & 0xFF) as u8, + ]; conn.write_all(&psize[..])?; conn.write_all(&packet)?; let mut buf = vec![0u8; 4096]; @@ -44,7 +44,9 @@ fn resolve(name: &str) -> Result<(), Box> { match conn.read(&mut buf[off..]) { Ok(num) => { off += num; - if off < 2 { continue; } + if off < 2 { + continue; + } let bytes = ((buf[0] as usize) << 8) | buf[1] as usize; if off < bytes + 2 { continue; diff --git a/examples/sync_udp_client.rs b/examples/sync_udp_client.rs index e9ed4a3..5aa462b 100644 --- a/examples/sync_udp_client.rs +++ b/examples/sync_udp_client.rs @@ -5,17 +5,15 @@ use std::error::Error; use std::net::UdpSocket; use std::process; - -use dns_parser::{Builder, Packet, RData, ResponseCode}; use dns_parser::rdata::a::Record; -use dns_parser::{QueryType, QueryClass}; - +use dns_parser::{Builder, Packet, RData, ResponseCode}; +use dns_parser::{QueryClass, QueryType}; fn main() { let mut code = 0; for name in env::args().skip(1) { match resolve(&name) { - Ok(()) => {}, + Ok(()) => {} Err(e) => { eprintln!("Error resolving {:?}: {}", name, e); code = 1; diff --git a/src/builder.rs b/src/builder.rs index 362bc76..40917aa 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,6 +1,6 @@ -use byteorder::{ByteOrder, BigEndian, WriteBytesExt}; +use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -use {Opcode, ResponseCode, Header, QueryType, QueryClass}; +use {Header, Opcode, QueryClass, QueryType, ResponseCode}; /// Allows to build a DNS packet /// @@ -45,22 +45,27 @@ impl Builder { /// * Answers, nameservers or additional section has already been written /// * There are already 65535 questions in the buffer. /// * When name is invalid - pub fn add_question(&mut self, qname: &str, prefer_unicast: bool, - qtype: QueryType, qclass: QueryClass) - -> &mut Builder - { + pub fn add_question( + &mut self, + qname: &str, + prefer_unicast: bool, + qtype: QueryType, + qclass: QueryClass, + ) -> &mut Builder { if &self.buf[6..12] != b"\x00\x00\x00\x00\x00\x00" { panic!("Too late to add a question"); } self.write_name(qname); self.buf.write_u16::(qtype as u16).unwrap(); let prefer_unicast: u16 = if prefer_unicast { 0x8000 } else { 0x0000 }; - self.buf.write_u16::(qclass as u16 | prefer_unicast).unwrap(); + self.buf + .write_u16::(qclass as u16 | prefer_unicast) + .unwrap(); let oldq = BigEndian::read_u16(&self.buf[4..6]); if oldq == 65535 { panic!("Too many questions"); } - BigEndian::write_u16(&mut self.buf[4..6], oldq+1); + BigEndian::write_u16(&mut self.buf[4..6], oldq + 1); self } fn write_name(&mut self, name: &str) { @@ -86,7 +91,7 @@ impl Builder { /// appropriate. // TODO(tailhook) does the truncation make sense for TCP, and how // to treat it for EDNS0? - pub fn build(mut self) -> Result,Vec> { + pub fn build(mut self) -> Result, Vec> { // TODO(tailhook) optimize labels if self.buf.len() > 512 { Header::set_truncated(&mut self.buf[..12]); @@ -99,9 +104,9 @@ impl Builder { #[cfg(test)] mod test { - use QueryType as QT; - use QueryClass as QC; use super::Builder; + use QueryClass as QC; + use QueryType as QT; #[test] fn build_query() { diff --git a/src/enums.rs b/src/enums.rs index bf6ef24..2d241c0 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -1,6 +1,6 @@ -use {Error}; use rdata::Record; use rdata::*; +use Error; /// The TYPE value according to RFC 1035 /// @@ -97,7 +97,6 @@ pub enum QueryType { All = all::Record::TYPE, } - /// The CLASS value according to RFC 1035 #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Class { @@ -183,14 +182,14 @@ impl From for ResponseCode { fn from(code: u8) -> ResponseCode { use self::ResponseCode::*; match code { - 0 => NoError, - 1 => FormatError, - 2 => ServerFailure, - 3 => NameError, - 4 => NotImplemented, - 5 => Refused, - 6..=15 => Reserved(code), - x => panic!("Invalid response code {}", x), + 0 => NoError, + 1 => FormatError, + 2 => ServerFailure, + 3 => NameError, + 4 => NotImplemented, + 5 => Refused, + 6..=15 => Reserved(code), + x => panic!("Invalid response code {}", x), } } } @@ -198,13 +197,13 @@ impl From for u8 { fn from(value: ResponseCode) -> u8 { use self::ResponseCode::*; match value { - NoError => 0, - FormatError => 1, - ServerFailure => 2, - NameError => 3, - NotImplemented => 4, - Refused => 5, - Reserved(code) => code, + NoError => 0, + FormatError => 1, + ServerFailure => 2, + NameError => 3, + NotImplemented => 4, + Refused => 5, + Reserved(code) => code, } } } @@ -214,28 +213,28 @@ impl QueryType { pub fn parse(code: u16) -> Result { use self::QueryType::*; match code as isize { - a::Record::TYPE => Ok(A), - ns::Record::TYPE => Ok(NS), - mf::Record::TYPE => Ok(MF), - cname::Record::TYPE => Ok(CNAME), - soa::Record::TYPE => Ok(SOA), - mb::Record::TYPE => Ok(MB), - mg::Record::TYPE => Ok(MG), - mr::Record::TYPE => Ok(MR), - null::Record::TYPE => Ok(NULL), - wks::Record::TYPE => Ok(WKS), - ptr::Record::TYPE => Ok(PTR), - hinfo::Record::TYPE => Ok(HINFO), - minfo::Record::TYPE => Ok(MINFO), - mx::Record::TYPE => Ok(MX), - txt::Record::TYPE => Ok(TXT), - aaaa::Record::TYPE => Ok(AAAA), - srv::Record::TYPE => Ok(SRV), - axfr::Record::TYPE => Ok(AXFR), - mailb::Record::TYPE => Ok(MAILB), - maila::Record::TYPE => Ok(MAILA), - all::Record::TYPE => Ok(All), - x => Err(Error::InvalidQueryType(x as u16)), + a::Record::TYPE => Ok(A), + ns::Record::TYPE => Ok(NS), + mf::Record::TYPE => Ok(MF), + cname::Record::TYPE => Ok(CNAME), + soa::Record::TYPE => Ok(SOA), + mb::Record::TYPE => Ok(MB), + mg::Record::TYPE => Ok(MG), + mr::Record::TYPE => Ok(MR), + null::Record::TYPE => Ok(NULL), + wks::Record::TYPE => Ok(WKS), + ptr::Record::TYPE => Ok(PTR), + hinfo::Record::TYPE => Ok(HINFO), + minfo::Record::TYPE => Ok(MINFO), + mx::Record::TYPE => Ok(MX), + txt::Record::TYPE => Ok(TXT), + aaaa::Record::TYPE => Ok(AAAA), + srv::Record::TYPE => Ok(SRV), + axfr::Record::TYPE => Ok(AXFR), + mailb::Record::TYPE => Ok(MAILB), + maila::Record::TYPE => Ok(MAILA), + all::Record::TYPE => Ok(All), + x => Err(Error::InvalidQueryType(x as u16)), } } } @@ -245,12 +244,12 @@ impl QueryClass { pub fn parse(code: u16) -> Result { use self::QueryClass::*; match code { - 1 => Ok(IN), - 2 => Ok(CS), - 3 => Ok(CH), - 4 => Ok(HS), + 1 => Ok(IN), + 2 => Ok(CS), + 3 => Ok(CH), + 4 => Ok(HS), 255 => Ok(Any), - x => Err(Error::InvalidQueryClass(x)), + x => Err(Error::InvalidQueryClass(x)), } } } @@ -260,26 +259,26 @@ impl Type { pub fn parse(code: u16) -> Result { use self::Type::*; match code as isize { - a::Record::TYPE => Ok(A), - ns::Record::TYPE => Ok(NS), - mf::Record::TYPE => Ok(MF), - cname::Record::TYPE => Ok(CNAME), - soa::Record::TYPE => Ok(SOA), - mb::Record::TYPE => Ok(MB), - mg::Record::TYPE => Ok(MG), - mr::Record::TYPE => Ok(MR), - null::Record::TYPE => Ok(NULL), - wks::Record::TYPE => Ok(WKS), - ptr::Record::TYPE => Ok(PTR), - hinfo::Record::TYPE => Ok(HINFO), - minfo::Record::TYPE => Ok(MINFO), - mx::Record::TYPE => Ok(MX), - txt::Record::TYPE => Ok(TXT), - aaaa::Record::TYPE => Ok(AAAA), - srv::Record::TYPE => Ok(SRV), - opt::Record::TYPE => Ok(OPT), - nsec::Record::TYPE => Ok(NSEC), - x => Err(Error::InvalidType(x as u16)), + a::Record::TYPE => Ok(A), + ns::Record::TYPE => Ok(NS), + mf::Record::TYPE => Ok(MF), + cname::Record::TYPE => Ok(CNAME), + soa::Record::TYPE => Ok(SOA), + mb::Record::TYPE => Ok(MB), + mg::Record::TYPE => Ok(MG), + mr::Record::TYPE => Ok(MR), + null::Record::TYPE => Ok(NULL), + wks::Record::TYPE => Ok(WKS), + ptr::Record::TYPE => Ok(PTR), + hinfo::Record::TYPE => Ok(HINFO), + minfo::Record::TYPE => Ok(MINFO), + mx::Record::TYPE => Ok(MX), + txt::Record::TYPE => Ok(TXT), + aaaa::Record::TYPE => Ok(AAAA), + srv::Record::TYPE => Ok(SRV), + opt::Record::TYPE => Ok(OPT), + nsec::Record::TYPE => Ok(NSEC), + x => Err(Error::InvalidType(x as u16)), } } } @@ -289,11 +288,11 @@ impl Class { pub fn parse(code: u16) -> Result { use self::Class::*; match code { - 1 => Ok(IN), - 2 => Ok(CS), - 3 => Ok(CH), - 4 => Ok(HS), - x => Err(Error::InvalidClass(x)), + 1 => Ok(IN), + 2 => Ok(CS), + 3 => Ok(CH), + 4 => Ok(HS), + x => Err(Error::InvalidClass(x)), } } } diff --git a/src/header.rs b/src/header.rs index 01d18ad..403cef5 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,18 +1,18 @@ use byteorder::{BigEndian, ByteOrder}; -use {Error, ResponseCode, Opcode}; +use {Error, Opcode, ResponseCode}; mod flag { - pub const QUERY: u16 = 0b1000_0000_0000_0000; - pub const OPCODE_MASK: u16 = 0b0111_1000_0000_0000; - pub const AUTHORITATIVE: u16 = 0b0000_0100_0000_0000; - pub const TRUNCATED: u16 = 0b0000_0010_0000_0000; - pub const RECURSION_DESIRED: u16 = 0b0000_0001_0000_0000; + pub const QUERY: u16 = 0b1000_0000_0000_0000; + pub const OPCODE_MASK: u16 = 0b0111_1000_0000_0000; + pub const AUTHORITATIVE: u16 = 0b0000_0100_0000_0000; + pub const TRUNCATED: u16 = 0b0000_0010_0000_0000; + pub const RECURSION_DESIRED: u16 = 0b0000_0001_0000_0000; pub const RECURSION_AVAILABLE: u16 = 0b0000_0000_1000_0000; - pub const AUTHENTICATED_DATA: u16 = 0b0000_0000_0010_0000; - pub const CHECKING_DISABLED: u16 = 0b0000_0000_0001_0000; - pub const RESERVED_MASK: u16 = 0b0000_0000_0100_0000; - pub const RESPONSE_CODE_MASK: u16 = 0b0000_0000_0000_1111; + pub const AUTHENTICATED_DATA: u16 = 0b0000_0000_0010_0000; + pub const CHECKING_DISABLED: u16 = 0b0000_0000_0001_0000; + pub const RESERVED_MASK: u16 = 0b0000_0000_0100_0000; + pub const RESPONSE_CODE_MASK: u16 = 0b0000_0000_0000_1111; } /// Represents parsed header of the packet @@ -48,15 +48,14 @@ impl Header { let header = Header { id: BigEndian::read_u16(&data[..2]), query: flags & flag::QUERY == 0, - opcode: ((flags & flag::OPCODE_MASK) - >> flag::OPCODE_MASK.trailing_zeros()).into(), + opcode: ((flags & flag::OPCODE_MASK) >> flag::OPCODE_MASK.trailing_zeros()).into(), authoritative: flags & flag::AUTHORITATIVE != 0, truncated: flags & flag::TRUNCATED != 0, recursion_desired: flags & flag::RECURSION_DESIRED != 0, recursion_available: flags & flag::RECURSION_AVAILABLE != 0, authenticated_data: flags & flag::AUTHENTICATED_DATA != 0, checking_disabled: flags & flag::CHECKING_DISABLED != 0, - response_code: From::from((flags&flag::RESPONSE_CODE_MASK) as u8), + response_code: From::from((flags & flag::RESPONSE_CODE_MASK) as u8), questions: BigEndian::read_u16(&data[4..6]), answers: BigEndian::read_u16(&data[6..8]), nameservers: BigEndian::read_u16(&data[8..10]), @@ -74,14 +73,23 @@ impl Header { panic!("Header size is exactly 12 bytes"); } let mut flags = 0u16; - flags |= Into::::into(self.opcode) - << flag::OPCODE_MASK.trailing_zeros(); + flags |= Into::::into(self.opcode) << flag::OPCODE_MASK.trailing_zeros(); flags |= Into::::into(self.response_code) as u16; - if !self.query { flags |= flag::QUERY; } - if self.authoritative { flags |= flag::AUTHORITATIVE; } - if self.recursion_desired { flags |= flag::RECURSION_DESIRED; } - if self.recursion_available { flags |= flag::RECURSION_AVAILABLE; } - if self.truncated { flags |= flag::TRUNCATED; } + if !self.query { + flags |= flag::QUERY; + } + if self.authoritative { + flags |= flag::AUTHORITATIVE; + } + if self.recursion_desired { + flags |= flag::RECURSION_DESIRED; + } + if self.recursion_available { + flags |= flag::RECURSION_AVAILABLE; + } + if self.truncated { + flags |= flag::TRUNCATED; + } BigEndian::write_u16(&mut data[..2], self.id); BigEndian::write_u16(&mut data[2..4], flags); BigEndian::write_u16(&mut data[4..6], self.questions); @@ -96,14 +104,15 @@ impl Header { BigEndian::write_u16(&mut data[2..4], oldflags & flag::TRUNCATED); } /// Returns a size of the header (always 12 bytes) - pub fn size() -> usize { 12 } + pub fn size() -> usize { + 12 + } } - #[cfg(test)] mod test { - use {Header}; + use Header; use Opcode::*; use ResponseCode::NoError; @@ -112,22 +121,25 @@ mod test { let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ \x07example\x03com\x00\x00\x01\x00\x01"; let header = Header::parse(query).unwrap(); - assert_eq!(header, Header { - id: 1573, - query: true, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: false, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 0, - nameservers: 0, - additional: 0, - }); + assert_eq!( + header, + Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + } + ); } #[test] @@ -137,22 +149,25 @@ mod test { \xc0\x0c\x00\x01\x00\x01\x00\x00\x04\xf8\ \x00\x04]\xb8\xd8\""; let header = Header::parse(response).unwrap(); - assert_eq!(header, Header { - id: 1573, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 0, - additional: 0, - }); + assert_eq!( + header, + Header { + id: 1573, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + } + ); } #[test] @@ -160,22 +175,25 @@ mod test { let query = b"\x06%\x01\x20\x00\x01\x00\x00\x00\x00\x00\x00\ \x07example\x03com\x00\x00\x01\x00\x01"; let header = Header::parse(query).unwrap(); - assert_eq!(header, Header { - id: 1573, - query: true, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: false, - authenticated_data: true, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 0, - nameservers: 0, - additional: 0, - }); + assert_eq!( + header, + Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: true, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + } + ); } #[test] @@ -183,21 +201,24 @@ mod test { let query = b"\x06%\x01\x10\x00\x01\x00\x00\x00\x00\x00\x00\ \x07example\x03com\x00\x00\x01\x00\x01"; let header = Header::parse(query).unwrap(); - assert_eq!(header, Header { - id: 1573, - query: true, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: false, - authenticated_data: false, - checking_disabled: true, - response_code: NoError, - questions: 1, - answers: 0, - nameservers: 0, - additional: 0, - }); + assert_eq!( + header, + Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: true, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + } + ); } } diff --git a/src/lib.rs b/src/lib.rs index 1fd0bff..aaabb29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![recursion_limit="100"] +#![recursion_limit = "100"] //! The network-agnostic DNS parser library //! //! [Documentation](https://docs.rs/dns-parser) | @@ -16,25 +16,31 @@ #![warn(missing_debug_implementations)] extern crate byteorder; -#[cfg(test)] #[macro_use] extern crate matches; -#[macro_use(quick_error)] extern crate quick_error; -#[cfg(feature = "with-serde")] #[macro_use] extern crate serde_derive; -#[cfg(test)] extern crate itertools; +#[cfg(test)] +#[macro_use] +extern crate matches; +#[macro_use(quick_error)] +extern crate quick_error; +#[cfg(feature = "with-serde")] +#[macro_use] +extern crate serde_derive; +#[cfg(test)] +extern crate itertools; +mod builder; mod enums; -mod structs; -mod name; -mod parser; mod error; mod header; -mod builder; +mod name; +mod parser; +mod structs; pub mod rdata; -pub use enums::{Type, QueryType, Class, QueryClass, ResponseCode, Opcode}; -pub use structs::{Question, ResourceRecord, Packet}; -pub use name::{Name}; -pub use error::{Error}; -pub use header::{Header}; -pub use rdata::{RData}; -pub use builder::{Builder}; +pub use builder::Builder; +pub use enums::{Class, Opcode, QueryClass, QueryType, ResponseCode, Type}; +pub use error::Error; +pub use header::Header; +pub use name::Name; +pub use rdata::RData; +pub use structs::{Packet, Question, ResourceRecord}; diff --git a/src/name.rs b/src/name.rs index 3079f7c..e25a57b 100644 --- a/src/name.rs +++ b/src/name.rs @@ -8,14 +8,14 @@ use std::ascii::AsciiExt; use byteorder::{BigEndian, ByteOrder}; -use {Error}; +use Error; /// The DNS name as stored in the original packet /// /// This contains just a reference to a slice that contains the data. /// You may turn this into a string using `.to_string()` #[derive(Clone, Copy)] -pub struct Name<'a>{ +pub struct Name<'a> { labels: &'a [u8], /// This is the original buffer size. The compressed names in original /// are calculated in this buffer @@ -28,7 +28,7 @@ impl<'a> Name<'a> { /// The `data` should be a part of `original` where name should start. /// The `original` is the data starting a the start of a packet, so /// that offsets in compressed name starts from the `original`. - pub fn scan(data: &'a[u8], original: &'a[u8]) -> Result, Error> { + pub fn scan(data: &'a [u8], original: &'a [u8]) -> Result, Error> { let mut parse_data = data; let mut return_pos = None; let mut pos = 0; @@ -44,11 +44,11 @@ impl<'a> Name<'a> { return Err(Error::UnexpectedEOF); } if byte & 0b1100_0000 == 0b1100_0000 { - if parse_data.len() < pos+2 { + if parse_data.len() < pos + 2 { return Err(Error::UnexpectedEOF); } - let off = (BigEndian::read_u16(&parse_data[pos..pos+2]) - & !0b1100_0000_0000_0000) as usize; + let off = (BigEndian::read_u16(&parse_data[pos..pos + 2]) & !0b1100_0000_0000_0000) + as usize; if off >= original.len() { return Err(Error::UnexpectedEOF); } @@ -72,7 +72,7 @@ impl<'a> Name<'a> { if parse_data.len() < end { return Err(Error::UnexpectedEOF); } - if !parse_data[pos+1..end].is_ascii() { + if !parse_data[pos + 1..end].is_ascii() { return Err(Error::LabelIsNotAscii); } pos = end; @@ -85,9 +85,15 @@ impl<'a> Name<'a> { byte = parse_data[pos]; } if let Some(return_pos) = return_pos { - Ok(Name {labels: &data[..return_pos+2], original }) + Ok(Name { + labels: &data[..return_pos + 2], + original, + }) } else { - Ok(Name {labels: &data[..pos+1], original }) + Ok(Name { + labels: &data[..pos + 1], + original, + }) } } /// Number of bytes serialized name occupies @@ -106,13 +112,12 @@ impl<'a> fmt::Display for Name<'a> { if byte == 0 { return Ok(()); } else if byte & 0b1100_0000 == 0b1100_0000 { - let off = (BigEndian::read_u16(&data[pos..pos+2]) - & !0b1100_0000_0000_0000) as usize; + let off = + (BigEndian::read_u16(&data[pos..pos + 2]) & !0b1100_0000_0000_0000) as usize; if pos != 0 { fmt.write_char('.')?; } - return fmt::Display::fmt( - &Name::scan(&original[off..], original).unwrap(), fmt) + return fmt::Display::fmt(&Name::scan(&original[off..], original).unwrap(), fmt); } else if byte & 0b1100_0000 == 0 { if pos != 0 { fmt.write_char('.')?; @@ -129,9 +134,7 @@ impl<'a> fmt::Display for Name<'a> { } impl<'a> fmt::Debug for Name<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_tuple("Name") - .field(&format!("{}", self)) - .finish() + fmt.debug_tuple("Name").field(&format!("{}", self)).finish() } } @@ -145,8 +148,10 @@ mod test { // A buffer where an offset points to itself, // which is a bad compression pointer. let same_offset = vec![192, 2, 192, 2]; - let is_match = matches!(Name::scan(&same_offset, &same_offset), - Err(Error::BadPointer)); + let is_match = matches!( + Name::scan(&same_offset, &same_offset), + Err(Error::BadPointer) + ); assert!(is_match); } @@ -156,8 +161,10 @@ mod test { // A buffer where the offsets points back to each other which causes // infinite recursion if never checked, a bad compression pointer. let forwards_offset = vec![192, 2, 192, 4, 192, 2]; - let is_match = matches!(Name::scan(&forwards_offset, &forwards_offset), - Err(Error::BadPointer)); + let is_match = matches!( + Name::scan(&forwards_offset, &forwards_offset), + Err(Error::BadPointer) + ); assert!(is_match); } @@ -167,17 +174,17 @@ mod test { // A buffer where an offset points to itself, a bad compression pointer. let buf = b"\x02xx\x00\x02yy\xc0\x00\x02zz\xc0\x04"; - assert_eq!(Name::scan(&buf[..], buf).unwrap().to_string(), - "xx"); - assert_eq!(Name::scan(&buf[..], buf).unwrap().labels, - b"\x02xx\x00"); - assert_eq!(Name::scan(&buf[4..], buf).unwrap().to_string(), - "yy.xx"); - assert_eq!(Name::scan(&buf[4..], buf).unwrap().labels, - b"\x02yy\xc0\x00"); - assert_eq!(Name::scan(&buf[9..], buf).unwrap().to_string(), - "zz.yy.xx"); - assert_eq!(Name::scan(&buf[9..], buf).unwrap().labels, - b"\x02zz\xc0\x04"); + assert_eq!(Name::scan(&buf[..], buf).unwrap().to_string(), "xx"); + assert_eq!(Name::scan(&buf[..], buf).unwrap().labels, b"\x02xx\x00"); + assert_eq!(Name::scan(&buf[4..], buf).unwrap().to_string(), "yy.xx"); + assert_eq!( + Name::scan(&buf[4..], buf).unwrap().labels, + b"\x02yy\xc0\x00" + ); + assert_eq!(Name::scan(&buf[9..], buf).unwrap().to_string(), "zz.yy.xx"); + assert_eq!( + Name::scan(&buf[9..], buf).unwrap().labels, + b"\x02zz\xc0\x04" + ); } } diff --git a/src/parser.rs b/src/parser.rs index 6a3f266..500fa99 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,9 +2,9 @@ use std::i32; use byteorder::{BigEndian, ByteOrder}; -use {Header, Packet, Error, Question, Name, QueryType, QueryClass}; -use {Type, Class, ResourceRecord, RData}; use rdata::opt::Record as Opt; +use {Class, RData, ResourceRecord, Type}; +use {Error, Header, Name, Packet, QueryClass, QueryType, Question}; const OPT_RR_START: [u8; 3] = [0, 0, 41]; @@ -21,12 +21,11 @@ impl<'a> Packet<'a> { if offset + 4 > data.len() { return Err(Error::UnexpectedEOF); } - let qtype = QueryType::parse( - BigEndian::read_u16(&data[offset..offset + 2]))?; + let qtype = QueryType::parse(BigEndian::read_u16(&data[offset..offset + 2]))?; offset += 2; - let (prefer_unicast, qclass) = parse_qclass_code( - BigEndian::read_u16(&data[offset..offset + 2]))?; + let (prefer_unicast, qclass) = + parse_qclass_code(BigEndian::read_u16(&data[offset..offset + 2]))?; offset += 2; questions.push(Question { @@ -47,7 +46,7 @@ impl<'a> Packet<'a> { let mut additional = Vec::with_capacity(header.additional as usize); let mut opt = None; for _ in 0..header.additional { - if offset + 3 <= data.len() && data[offset..offset+3] == OPT_RR_START { + if offset + 3 <= data.len() && data[offset..offset + 3] == OPT_RR_START { if opt.is_none() { opt = Some(parse_opt_record(data, &mut offset)?); } else { @@ -91,26 +90,24 @@ fn parse_record<'a>(data: &'a [u8], offset: &mut usize) -> Result data.len() { return Err(Error::UnexpectedEOF); } - let typ = Type::parse( - BigEndian::read_u16(&data[*offset..*offset + 2]))?; + let typ = Type::parse(BigEndian::read_u16(&data[*offset..*offset + 2]))?; *offset += 2; - let class_code = BigEndian::read_u16(&data[*offset..*offset+2]); + let class_code = BigEndian::read_u16(&data[*offset..*offset + 2]); let (multicast_unique, cls) = parse_class_code(class_code)?; *offset += 2; - let mut ttl = BigEndian::read_u32(&data[*offset..*offset+4]); + let mut ttl = BigEndian::read_u32(&data[*offset..*offset + 4]); if ttl > i32::MAX as u32 { ttl = 0; } *offset += 4; - let rdlen = BigEndian::read_u16(&data[*offset..*offset+2]) as usize; + let rdlen = BigEndian::read_u16(&data[*offset..*offset + 2]) as usize; *offset += 2; if *offset + rdlen > data.len() { return Err(Error::UnexpectedEOF); } - let data = RData::parse(typ, - &data[*offset..*offset + rdlen], data)?; + let data = RData::parse(typ, &data[*offset..*offset + rdlen], data)?; *offset += rdlen; Ok(ResourceRecord { name, @@ -127,27 +124,25 @@ fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result, E return Err(Error::UnexpectedEOF); } *offset += 1; - let typ = Type::parse( - BigEndian::read_u16(&data[*offset..*offset + 2]))?; + let typ = Type::parse(BigEndian::read_u16(&data[*offset..*offset + 2]))?; if typ != Type::OPT { return Err(Error::InvalidType(typ as u16)); } *offset += 2; - let udp = BigEndian::read_u16(&data[*offset..*offset+2]); + let udp = BigEndian::read_u16(&data[*offset..*offset + 2]); *offset += 2; let extrcode = data[*offset]; *offset += 1; let version = data[*offset]; *offset += 1; - let flags = BigEndian::read_u16(&data[*offset..*offset+2]); + let flags = BigEndian::read_u16(&data[*offset..*offset + 2]); *offset += 2; - let rdlen = BigEndian::read_u16(&data[*offset..*offset+2]) as usize; + let rdlen = BigEndian::read_u16(&data[*offset..*offset + 2]) as usize; *offset += 2; if *offset + rdlen > data.len() { return Err(Error::UnexpectedEOF); } - let data = RData::parse(typ, - &data[*offset..*offset + rdlen], data)?; + let data = RData::parse(typ, &data[*offset..*offset + rdlen], data)?; *offset += rdlen; Ok(Opt { @@ -162,37 +157,40 @@ fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result, E #[cfg(test)] mod test { - use std::net::Ipv4Addr; use itertools::Itertools; - use {Packet, Header}; + use std::net::Ipv4Addr; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_example_query() { let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ \x07example\x03com\x00\x00\x01\x00\x01"; let packet = Packet::parse(query).unwrap(); - assert_eq!(packet.header, Header { - id: 1573, - query: true, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: false, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 0, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); @@ -207,22 +205,25 @@ mod test { \xc0\x0c\x00\x01\x00\x01\x00\x00\x04\xf8\ \x00\x04]\xb8\xd8\""; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 1573, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 1573, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); @@ -253,9 +254,9 @@ mod test { assert_eq!(packet.answers[0].cls, C::IN); } - #[test] - fn parse_additional_record_response() { - let response = b"\x4a\xf0\x81\x80\x00\x01\x00\x01\x00\x01\x00\x01\ + #[test] + fn parse_additional_record_response() { + let response = b"\x4a\xf0\x81\x80\x00\x01\x00\x01\x00\x01\x00\x01\ \x03www\x05skype\x03com\x00\x00\x01\x00\x01\ \xc0\x0c\x00\x05\x00\x01\x00\x00\x0e\x10\ \x00\x1c\x07\x6c\x69\x76\x65\x63\x6d\x73\x0e\x74\ @@ -266,58 +267,64 @@ mod test { \xc0\x42\ \x01\x61\xc0\x55\x00\x01\x00\x01\x00\x00\xa3\x1c\ \x00\x04\xc0\x05\x06\x1e"; - let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 19184, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 1, - additional: 1, - }); - assert_eq!(packet.questions.len(), 1); - assert_eq!(packet.questions[0].qtype, QT::A); - assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); - assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); - assert_eq!(packet.answers[0].cls, C::IN); - assert_eq!(packet.answers[0].ttl, 3600); - match packet.answers[0].data { - RData::CNAME(cname) => { - assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); - } - ref x => panic!("Wrong rdata {:?}", x), - } - assert_eq!(packet.nameservers.len(), 1); - assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); - assert_eq!(packet.nameservers[0].cls, C::IN); - assert_eq!(packet.nameservers[0].ttl, 120275); - match packet.nameservers[0].data { - RData::NS(ns) => { - assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); - } - ref x => panic!("Wrong rdata {:?}", x), - } - assert_eq!(packet.additional.len(), 1); - assert_eq!(&packet.additional[0].name.to_string()[..], "a.gtld-servers.net"); - assert_eq!(packet.additional[0].cls, C::IN); - assert_eq!(packet.additional[0].ttl, 41756); - match packet.additional[0].data { - RData::A(addr) => { - assert_eq!(addr.0, Ipv4Addr::new(192, 5, 6, 30)); - } - ref x => panic!("Wrong rdata {:?}", x), - } - } + let packet = Packet::parse(response).unwrap(); + assert_eq!( + packet.header, + Header { + id: 19184, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 1, + additional: 1, + } + ); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 3600); + match packet.answers[0].data { + RData::CNAME(cname) => { + assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + assert_eq!(packet.nameservers.len(), 1); + assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); + assert_eq!(packet.nameservers[0].cls, C::IN); + assert_eq!(packet.nameservers[0].ttl, 120275); + match packet.nameservers[0].data { + RData::NS(ns) => { + assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + assert_eq!(packet.additional.len(), 1); + assert_eq!( + &packet.additional[0].name.to_string()[..], + "a.gtld-servers.net" + ); + assert_eq!(packet.additional[0].cls, C::IN); + assert_eq!(packet.additional[0].ttl, 41756); + match packet.additional[0].data { + RData::A(addr) => { + assert_eq!(addr.0, Ipv4Addr::new(192, 5, 6, 30)); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } #[test] fn parse_multiple_answers() { @@ -332,22 +339,25 @@ mod test { \xe9\xa4e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\ \x00\x04@\xe9\xa4\x8a"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 40425, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 6, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 40425, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 6, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); @@ -379,28 +389,33 @@ mod test { let query = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01"; let packet = Packet::parse(query).unwrap(); - assert_eq!(packet.header, Header { - id: 23513, - query: true, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: false, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 0, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 23513, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::SRV); assert_eq!(packet.questions[0].qclass, QC::IN); assert!(!packet.questions[0].prefer_unicast); - assert_eq!(&packet.questions[0].qname.to_string()[..], - "_xmpp-server._tcp.gmail.com"); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "_xmpp-server._tcp.gmail.com" + ); assert_eq!(packet.answers.len(), 0); } @@ -422,22 +437,25 @@ mod test { \x06google\x03com\x00\x00\x01\x00\ \x01\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00"; let packet = Packet::parse(query).unwrap(); - assert_eq!(packet.header, Header { - id: 38350, - query: true, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: false, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 0, - nameservers: 0, - additional: 1, - }); + assert_eq!( + packet.header, + Header { + id: 38350, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 1, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); @@ -449,8 +467,8 @@ mod test { assert_eq!(opt.extrcode, 0); assert_eq!(opt.version, 0); assert_eq!(opt.flags, 0); - }, - None => panic!("Missing OPT RR") + } + None => panic!("Missing OPT RR"), } } } diff --git a/src/rdata/a.rs b/src/rdata/a.rs index f4196d5..714b0a3 100644 --- a/src/rdata/a.rs +++ b/src/rdata/a.rs @@ -1,13 +1,12 @@ use std::net::Ipv4Addr; -use Error; use byteorder::{BigEndian, ByteOrder}; +use Error; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Record(pub Ipv4Addr); impl<'a> super::Record<'a> for Record { - const TYPE: isize = 1; fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/aaaa.rs b/src/rdata/aaaa.rs index e59937a..b1884e6 100644 --- a/src/rdata/aaaa.rs +++ b/src/rdata/aaaa.rs @@ -1,13 +1,12 @@ use std::net::Ipv6Addr; -use Error; use byteorder::{BigEndian, ByteOrder}; +use Error; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Record(pub Ipv6Addr); impl<'a> super::Record<'a> for Record { - const TYPE: isize = 28; fn parse(rdata: &'a [u8], _record: &'a [u8]) -> super::RDataResult<'a> { @@ -23,7 +22,7 @@ impl<'a> super::Record<'a> for Record { BigEndian::read_u16(&rdata[10..12]), BigEndian::read_u16(&rdata[12..14]), BigEndian::read_u16(&rdata[14..16]), - ); + ); let record = Record(address); Ok(super::RData::AAAA(record)) } @@ -32,14 +31,14 @@ impl<'a> super::Record<'a> for Record { #[cfg(test)] mod test { - use {Packet, Header}; + use super::*; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; - use super::*; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response() { @@ -48,22 +47,25 @@ mod test { \x00\x8b\x00\x10*\x00\x14P@\t\x08\x12\x00\x00\x00\x00\x00\x00 \x0e"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 43481, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 43481, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::AAAA); @@ -75,8 +77,9 @@ mod test { assert_eq!(packet.answers[0].ttl, 139); match packet.answers[0].data { RData::AAAA(addr) => { - assert_eq!(addr.0, Ipv6Addr::new( - 0x2A00, 0x1450, 0x4009, 0x812, 0, 0, 0, 0x200e) + assert_eq!( + addr.0, + Ipv6Addr::new(0x2A00, 0x1450, 0x4009, 0x812, 0, 0, 0, 0x200e) ); } ref x => panic!("Wrong rdata {:?}", x), diff --git a/src/rdata/all.rs b/src/rdata/all.rs index 106c91b..4b563ba 100644 --- a/src/rdata/all.rs +++ b/src/rdata/all.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 255; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/axfr.rs b/src/rdata/axfr.rs index b48dc1a..00502af 100644 --- a/src/rdata/axfr.rs +++ b/src/rdata/axfr.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 252; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/cname.rs b/src/rdata/cname.rs index c8dcb99..887f597 100644 --- a/src/rdata/cname.rs +++ b/src/rdata/cname.rs @@ -11,7 +11,6 @@ impl<'a> ToString for Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 5; fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { @@ -25,13 +24,13 @@ impl<'a> super::Record<'a> for Record<'a> { mod test { use std::net::Ipv4Addr; - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response() { @@ -48,27 +47,33 @@ mod test { \x00\x99L\x00\x04\xad\xf5;\x04"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 64669, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 6, - nameservers: 2, - additional: 2, - }); + assert_eq!( + packet.header, + Header { + id: 64669, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 6, + nameservers: 2, + additional: 2, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "cdn.sstatic.net"); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "cdn.sstatic.net" + ); assert_eq!(packet.answers.len(), 6); assert_eq!(&packet.answers[0].name.to_string()[..], "cdn.sstatic.net"); assert_eq!(packet.answers[0].cls, C::IN); @@ -93,7 +98,7 @@ mod test { assert_eq!(packet.answers[i].ttl, 102); match packet.answers[i].data { RData::A(addr) => { - assert_eq!(addr.0, ips[i-1]); + assert_eq!(addr.0, ips[i - 1]); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/hinfo.rs b/src/rdata/hinfo.rs index 6ee160b..f1f30da 100644 --- a/src/rdata/hinfo.rs +++ b/src/rdata/hinfo.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 13; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/maila.rs b/src/rdata/maila.rs index ab1166c..ccd921f 100644 --- a/src/rdata/maila.rs +++ b/src/rdata/maila.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 254; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/mailb.rs b/src/rdata/mailb.rs index 9f043c6..636d596 100644 --- a/src/rdata/mailb.rs +++ b/src/rdata/mailb.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 253; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/mb.rs b/src/rdata/mb.rs index d14e7b4..7b44667 100644 --- a/src/rdata/mb.rs +++ b/src/rdata/mb.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 7; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/mf.rs b/src/rdata/mf.rs index 11c935d..74a61ef 100644 --- a/src/rdata/mf.rs +++ b/src/rdata/mf.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 4; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/mg.rs b/src/rdata/mg.rs index 4fce456..cc3b053 100644 --- a/src/rdata/mg.rs +++ b/src/rdata/mg.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 8; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/minfo.rs b/src/rdata/minfo.rs index 29b3a45..8547b67 100644 --- a/src/rdata/minfo.rs +++ b/src/rdata/minfo.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 14; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/mod.rs b/src/rdata/mod.rs index 770f46b..c4d21a0 100644 --- a/src/rdata/mod.rs +++ b/src/rdata/mod.rs @@ -26,7 +26,7 @@ pub mod srv; pub mod txt; pub mod wks; -use {Type, Error}; +use {Error, Type}; pub use self::a::Record as A; pub use self::aaaa::Record as Aaaa; @@ -58,7 +58,7 @@ pub enum RData<'a> { Unknown(Type, &'a [u8]), } -pub (crate) trait Record<'a> { +pub(crate) trait Record<'a> { const TYPE: isize; fn parse(rdata: &'a [u8], original: &'a [u8]) -> RDataResult<'a>; @@ -68,16 +68,16 @@ impl<'a> RData<'a> { /// Parse an RR data and return RData enumeration pub fn parse(typ: Type, rdata: &'a [u8], original: &'a [u8]) -> RDataResult<'a> { match typ { - Type::A => A::parse(rdata, original), - Type::AAAA => Aaaa::parse(rdata, original), - Type::CNAME => Cname::parse(rdata, original), - Type::NS => Ns::parse(rdata, original), - Type::MX => Mx::parse(rdata, original), - Type::PTR => Ptr::parse(rdata, original), - Type::SOA => Soa::parse(rdata, original), - Type::SRV => Srv::parse(rdata, original), - Type::TXT => Txt::parse(rdata, original), - _ => Ok(RData::Unknown(typ, rdata)), + Type::A => A::parse(rdata, original), + Type::AAAA => Aaaa::parse(rdata, original), + Type::CNAME => Cname::parse(rdata, original), + Type::NS => Ns::parse(rdata, original), + Type::MX => Mx::parse(rdata, original), + Type::PTR => Ptr::parse(rdata, original), + Type::SOA => Soa::parse(rdata, original), + Type::SRV => Srv::parse(rdata, original), + Type::TXT => Txt::parse(rdata, original), + _ => Ok(RData::Unknown(typ, rdata)), } } @@ -86,15 +86,15 @@ impl<'a> RData<'a> { /// Code can be converted to an integer `packet.type_code() as isize` pub fn type_code(self) -> Type { match self { - RData::A(..) => Type::A, - RData::AAAA(..) => Type::AAAA, - RData::CNAME(..) => Type::CNAME, - RData::NS(..) => Type::NS, - RData::MX(..) => Type::MX, - RData::PTR(..) => Type::PTR, - RData::SOA(..) => Type::SOA, - RData::SRV(..) => Type::SRV, - RData::TXT(..) => Type::TXT, + RData::A(..) => Type::A, + RData::AAAA(..) => Type::AAAA, + RData::CNAME(..) => Type::CNAME, + RData::NS(..) => Type::NS, + RData::MX(..) => Type::MX, + RData::PTR(..) => Type::PTR, + RData::SOA(..) => Type::SOA, + RData::SRV(..) => Type::SRV, + RData::TXT(..) => Type::TXT, RData::Unknown(t, _) => t, } } diff --git a/src/rdata/mr.rs b/src/rdata/mr.rs index e313372..e8454ec 100644 --- a/src/rdata/mr.rs +++ b/src/rdata/mr.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 9; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/mx.rs b/src/rdata/mx.rs index 4bd3a55..db4c553 100644 --- a/src/rdata/mx.rs +++ b/src/rdata/mx.rs @@ -1,5 +1,5 @@ -use {Name, Error}; use byteorder::{BigEndian, ByteOrder}; +use {Error, Name}; #[derive(Debug, Clone, Copy)] pub struct Record<'a> { @@ -8,7 +8,6 @@ pub struct Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 15; fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { @@ -25,15 +24,15 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { + use super::*; use itertools::Itertools; - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; - use super::*; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response() { @@ -46,42 +45,46 @@ mod test { \x00\x04|\x00\t\x00\x14\x04alt2\xc0)\xc0\x0c\x00\x0f\ \x00\x01\x00\x00\x04|\x00\t\x00\x1e\x04alt3\xc0)"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 58344, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 5, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 58344, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 5, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::MX); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], - "gmail.com"); + assert_eq!(&packet.questions[0].qname.to_string()[..], "gmail.com"); assert_eq!(packet.answers.len(), 5); let items = [ - ( 5, "gmail-smtp-in.l.google.com"), + (5, "gmail-smtp-in.l.google.com"), (10, "alt1.gmail-smtp-in.l.google.com"), (40, "alt4.gmail-smtp-in.l.google.com"), (20, "alt2.gmail-smtp-in.l.google.com"), (30, "alt3.gmail-smtp-in.l.google.com"), ]; for (answer, item) in packet.answers.iter().zip_eq(items.iter()) { - assert_eq!(&answer.name.to_string()[..], - "gmail.com"); + assert_eq!(&answer.name.to_string()[..], "gmail.com"); assert_eq!(answer.cls, C::IN); assert_eq!(answer.ttl, 1148); match answer.data { - RData::MX( Record { preference, exchange }) => { + RData::MX(Record { + preference, + exchange, + }) => { assert_eq!(preference, item.0); assert_eq!(exchange.to_string(), item.1.to_string()); } diff --git a/src/rdata/ns.rs b/src/rdata/ns.rs index 42a2f29..1550ddf 100644 --- a/src/rdata/ns.rs +++ b/src/rdata/ns.rs @@ -11,7 +11,6 @@ impl<'a> ToString for Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 2; fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { @@ -24,13 +23,13 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response() { @@ -43,46 +42,49 @@ mod test { \xc0\x42\x00\x02\x00\x01\x00\x01\xd5\xd3\x00\x11\ \x01\x67\x0c\x67\x74\x6c\x64\x2d\x73\x65\x72\x76\x65\x72\x73\ \xc0\x42"; - let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 19184, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 1, - additional: 0, - }); - assert_eq!(packet.questions.len(), 1); - assert_eq!(packet.questions[0].qtype, QT::A); - assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); - assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); - assert_eq!(packet.answers[0].cls, C::IN); - assert_eq!(packet.answers[0].ttl, 3600); - match packet.answers[0].data { - RData::CNAME(cname) => { - assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); - } - ref x => panic!("Wrong rdata {:?}", x), - } - assert_eq!(packet.nameservers.len(), 1); - assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); - assert_eq!(packet.nameservers[0].cls, C::IN); - assert_eq!(packet.nameservers[0].ttl, 120275); - match packet.nameservers[0].data { - RData::NS(ns) => { - assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); - } - ref x => panic!("Wrong rdata {:?}", x), - } - } + let packet = Packet::parse(response).unwrap(); + assert_eq!( + packet.header, + Header { + id: 19184, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 1, + additional: 0, + } + ); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 3600); + match packet.answers[0].data { + RData::CNAME(cname) => { + assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + assert_eq!(packet.nameservers.len(), 1); + assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); + assert_eq!(packet.nameservers[0].cls, C::IN); + assert_eq!(packet.nameservers[0].ttl, 120275); + match packet.nameservers[0].data { + RData::NS(ns) => { + assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } } diff --git a/src/rdata/nsec.rs b/src/rdata/nsec.rs index 354c512..5075904 100644 --- a/src/rdata/nsec.rs +++ b/src/rdata/nsec.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 47; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/null.rs b/src/rdata/null.rs index b3bfcd3..6bdc2dd 100644 --- a/src/rdata/null.rs +++ b/src/rdata/null.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 10; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/opt.rs b/src/rdata/opt.rs index 694d393..c30b87b 100644 --- a/src/rdata/opt.rs +++ b/src/rdata/opt.rs @@ -9,7 +9,6 @@ pub struct Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 41; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/rdata/ptr.rs b/src/rdata/ptr.rs index 315c379..03d607c 100644 --- a/src/rdata/ptr.rs +++ b/src/rdata/ptr.rs @@ -11,7 +11,6 @@ impl<'a> ToString for Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 12; fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { @@ -24,13 +23,13 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response() { @@ -40,28 +39,37 @@ mod test { \xc0\x0c\x00\x0c\x00\x01\x00\x01\x51\x80\x00\x1e\ \x10pool-72-75-93-69\x07verizon\x03net\x00"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 21462, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 21462, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::PTR); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "69.93.75.72.in-addr.arpa"); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "69.93.75.72.in-addr.arpa" + ); assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "69.93.75.72.in-addr.arpa"); + assert_eq!( + &packet.answers[0].name.to_string()[..], + "69.93.75.72.in-addr.arpa" + ); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 86400); match packet.answers[0].data { diff --git a/src/rdata/soa.rs b/src/rdata/soa.rs index b28ae63..a9f3923 100644 --- a/src/rdata/soa.rs +++ b/src/rdata/soa.rs @@ -1,5 +1,5 @@ -use {Name, Error}; use byteorder::{BigEndian, ByteOrder}; +use {Error, Name}; /// The SOA (Start of Authority) record #[derive(Debug, Clone, Copy)] @@ -28,11 +28,11 @@ impl<'a> super::Record<'a> for Record<'a> { let record = Record { primary_ns: primary_name_server, mailbox, - serial: BigEndian::read_u32(&rdata[pos..(pos+4)]), - refresh: BigEndian::read_u32(&rdata[(pos+4)..(pos+8)]), - retry: BigEndian::read_u32(&rdata[(pos+8)..(pos+12)]), - expire: BigEndian::read_u32(&rdata[(pos+12)..(pos+16)]), - minimum_ttl: BigEndian::read_u32(&rdata[(pos+16)..(pos+20)]), + serial: BigEndian::read_u32(&rdata[pos..(pos + 4)]), + refresh: BigEndian::read_u32(&rdata[(pos + 4)..(pos + 8)]), + retry: BigEndian::read_u32(&rdata[(pos + 8)..(pos + 12)]), + expire: BigEndian::read_u32(&rdata[(pos + 12)..(pos + 16)]), + minimum_ttl: BigEndian::read_u32(&rdata[(pos + 16)..(pos + 20)]), }; Ok(super::RData::SOA(record)) } @@ -41,51 +41,57 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NameError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; + use ResponseCode::NameError; + use {Header, Packet}; - #[test] - fn parse_response() { - let response = b"\x9f\xc5\x85\x83\x00\x01\x00\x00\x00\x01\x00\x00\ + #[test] + fn parse_response() { + let response = b"\x9f\xc5\x85\x83\x00\x01\x00\x00\x00\x01\x00\x00\ \x0edlkfjkdjdslfkj\x07youtube\x03com\x00\x00\x01\x00\x01\ \xc0\x1b\x00\x06\x00\x01\x00\x00\x2a\x30\x00\x1e\xc0\x1b\ \x05admin\xc0\x1b\x77\xed\x2a\x73\x00\x00\x51\x80\x00\x00\ \x0e\x10\x00\x00\x3a\x80\x00\x00\x2a\x30"; - let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 40901, - query: false, - opcode: StandardQuery, - authoritative: true, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NameError, - questions: 1, - answers: 0, - nameservers: 1, - additional: 0, - }); - assert_eq!(packet.questions.len(), 1); - assert_eq!(packet.questions[0].qtype, QT::A); - assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "dlkfjkdjdslfkj.youtube.com"); - assert_eq!(packet.answers.len(), 0); + let packet = Packet::parse(response).unwrap(); + assert_eq!( + packet.header, + Header { + id: 40901, + query: false, + opcode: StandardQuery, + authoritative: true, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NameError, + questions: 1, + answers: 0, + nameservers: 1, + additional: 0, + } + ); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "dlkfjkdjdslfkj.youtube.com" + ); + assert_eq!(packet.answers.len(), 0); - assert_eq!(packet.nameservers.len(), 1); - assert_eq!(&packet.nameservers[0].name.to_string()[..], "youtube.com"); - assert_eq!(packet.nameservers[0].cls, C::IN); - assert!(!packet.nameservers[0].multicast_unique); - assert_eq!(packet.nameservers[0].ttl, 10800); - match packet.nameservers[0].data { - RData::SOA(ref soa_rec) => { + assert_eq!(packet.nameservers.len(), 1); + assert_eq!(&packet.nameservers[0].name.to_string()[..], "youtube.com"); + assert_eq!(packet.nameservers[0].cls, C::IN); + assert!(!packet.nameservers[0].multicast_unique); + assert_eq!(packet.nameservers[0].ttl, 10800); + match packet.nameservers[0].data { + RData::SOA(ref soa_rec) => { assert_eq!(&soa_rec.primary_ns.to_string()[..], "youtube.com"); assert_eq!(&soa_rec.mailbox.to_string()[..], "admin.youtube.com"); assert_eq!(soa_rec.serial, 2012031603); @@ -93,8 +99,8 @@ mod test { assert_eq!(soa_rec.retry, 3600); assert_eq!(soa_rec.expire, 14976); assert_eq!(soa_rec.minimum_ttl, 10800); - } - ref x => panic!("Wrong rdata {:?}", x), - } - } + } + ref x => panic!("Wrong rdata {:?}", x), + } + } } diff --git a/src/rdata/srv.rs b/src/rdata/srv.rs index 0d12a08..42ef1b6 100644 --- a/src/rdata/srv.rs +++ b/src/rdata/srv.rs @@ -1,5 +1,5 @@ -use {Name, Error}; use byteorder::{BigEndian, ByteOrder}; +use {Error, Name}; #[derive(Debug, Clone, Copy)] pub struct Record<'a> { @@ -10,7 +10,6 @@ pub struct Record<'a> { } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 33; fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { @@ -29,15 +28,15 @@ impl<'a> super::Record<'a> for Record<'a> { #[cfg(test)] mod test { + use super::*; use itertools::Itertools; - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; - use super::*; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response() { @@ -54,27 +53,32 @@ mod test { \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\ \x14\x95\x04alt4\x0bxmpp-server\x01l\x06google\x03com\x00"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 23513, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 5, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 23513, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 5, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::SRV); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], - "_xmpp-server._tcp.gmail.com"); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "_xmpp-server._tcp.gmail.com" + ); assert_eq!(packet.answers.len(), 5); let items = [ (5, 0, 5269, "xmpp-server.l.google.com"), @@ -84,12 +88,16 @@ mod test { (20, 0, 5269, "alt4.xmpp-server.l.google.com"), ]; for (answer, item) in packet.answers.iter().zip_eq(items.iter()) { - assert_eq!(&answer.name.to_string()[..], - "_xmpp-server._tcp.gmail.com"); + assert_eq!(&answer.name.to_string()[..], "_xmpp-server._tcp.gmail.com"); assert_eq!(answer.cls, C::IN); assert_eq!(answer.ttl, 900); match answer.data { - RData::SRV(Record { priority, weight, port, target }) => { + RData::SRV(Record { + priority, + weight, + port, + target, + }) => { assert_eq!(priority, item.0); assert_eq!(weight, item.1); assert_eq!(port, item.2); diff --git a/src/rdata/txt.rs b/src/rdata/txt.rs index 01fe4ea..766f1e0 100644 --- a/src/rdata/txt.rs +++ b/src/rdata/txt.rs @@ -25,17 +25,13 @@ impl<'a> Iterator for RecordIter<'a> { } impl<'a> Record<'a> { - // Returns iterator over text chunks pub fn iter(&self) -> RecordIter<'a> { - RecordIter { - bytes: self.bytes, - } + RecordIter { bytes: self.bytes } } } impl<'a> super::Record<'a> for Record<'a> { - const TYPE: isize = 16; fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { @@ -53,9 +49,7 @@ impl<'a> super::Record<'a> for Record<'a> { } pos += rdlen; } - Ok(super::RData::TXT(Record { - bytes: rdata, - })) + Ok(super::RData::TXT(Record { bytes: rdata })) } } @@ -64,13 +58,13 @@ mod test { use std::str::from_utf8; - use {Packet, Header}; + use Class as C; use Opcode::*; - use ResponseCode::NoError; - use QueryType as QT; use QueryClass as QC; - use Class as C; + use QueryType as QT; use RData; + use ResponseCode::NoError; + use {Header, Packet}; #[test] fn parse_response_multiple_strings() { @@ -82,22 +76,25 @@ mod test { \x0c\x66\x61\x63\x65\x62\x6f\x6f\x6b\x2e\x63\x6f\x6d"; let packet = Packet::parse(response).unwrap(); - assert_eq!(packet.header, Header { - id: 1573, - query: false, - opcode: StandardQuery, - authoritative: false, - truncated: false, - recursion_desired: true, - recursion_available: true, - authenticated_data: false, - checking_disabled: false, - response_code: NoError, - questions: 1, - answers: 1, - nameservers: 0, - additional: 0, - }); + assert_eq!( + packet.header, + Header { + id: 1573, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + } + ); assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::TXT); assert_eq!(packet.questions[0].qclass, QC::IN); @@ -109,15 +106,22 @@ mod test { assert_eq!(packet.answers[0].ttl, 86333); match packet.answers[0].data { RData::TXT(ref text) => { - assert_eq!(text.iter() - .map(|x| from_utf8(x).unwrap()) - .collect::>() - .concat(), "v=spf1 redirect=_spf.facebook.com"); + assert_eq!( + text.iter() + .map(|x| from_utf8(x).unwrap()) + .collect::>() + .concat(), + "v=spf1 redirect=_spf.facebook.com" + ); // also assert boundaries are kept - assert_eq!(text.iter().collect::>(), - ["v=spf1 redirect=_spf.".as_bytes(), - "facebook.com".as_bytes()]); + assert_eq!( + text.iter().collect::>(), + [ + "v=spf1 redirect=_spf.".as_bytes(), + "facebook.com".as_bytes() + ] + ); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/wks.rs b/src/rdata/wks.rs index ff6e5f1..24e2793 100644 --- a/src/rdata/wks.rs +++ b/src/rdata/wks.rs @@ -2,7 +2,6 @@ pub struct Record; impl<'a> super::Record<'a> for Record { - const TYPE: isize = 11; fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { diff --git a/src/structs.rs b/src/structs.rs index 4f60639..983621c 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,10 +1,9 @@ -use {QueryType, QueryClass, Name, Class, Header, RData}; use rdata::opt; - +use {Class, Header, Name, QueryClass, QueryType, RData}; /// Parsed DNS packet #[derive(Debug)] -#[allow(missing_docs)] // should be covered by spec +#[allow(missing_docs)] // should be covered by spec pub struct Packet<'a> { pub header: Header, pub questions: Vec>, @@ -21,7 +20,7 @@ pub struct Packet<'a> { /// A parsed chunk of data in the Query section of the packet #[derive(Debug)] -#[allow(missing_docs)] // should be covered by spec +#[allow(missing_docs)] // should be covered by spec pub struct Question<'a> { pub qname: Name<'a>, /// Whether or not we prefer unicast responses. @@ -37,7 +36,7 @@ pub struct Question<'a> { /// limited we have some types of packets which are parsed and other provided /// as unparsed slice of bytes. #[derive(Debug)] -#[allow(missing_docs)] // should be covered by spec +#[allow(missing_docs)] // should be covered by spec pub struct ResourceRecord<'a> { pub name: Name<'a>, /// Whether or not the set of resource records is fully contained in the From af9c8650fe19c51186431a45da2df7630b399b5b Mon Sep 17 00:00:00 2001 From: Marshall Pierce Date: Tue, 17 Mar 2026 13:20:47 -0600 Subject: [PATCH 3/3] Reimplement `Name` parsing to support iterating labels, and non-text labels The parser is similar to the previous structure, but re-cast as an `Iterator`. Accessing text names is still doable via `StrName`. For use cases that need precise label boundaries, iterating over labels (which was always done internally) is now exposed to the caller if they choose. --- CHANGELOG.md | 12 +- Cargo.toml | 7 +- fuzz/.gitignore | 4 + fuzz/Cargo.toml | 21 + fuzz/fuzz_targets/parse_packet.rs | 10 + rust-toolchain.toml | 2 +- src/error.rs | 10 +- src/lib.rs | 5 +- src/name.rs | 713 +++++++++++++++++++++++++----- src/parser.rs | 67 ++- src/rdata/aaaa.rs | 10 +- src/rdata/cname.rs | 21 +- src/rdata/mx.rs | 15 +- src/rdata/ns.rs | 36 +- src/rdata/ptr.rs | 16 +- src/rdata/soa.rs | 21 +- src/rdata/srv.rs | 12 +- src/rdata/txt.rs | 10 +- 18 files changed, 804 insertions(+), 188 deletions(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/parse_packet.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad57de..30cb8be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # Next - -- Update MSRV to 1.75.0, currently the older of rustc in Ubuntu LTS (Noble Numbat, 1.75.0) and Debian Stable (trixie, 1.85.0) - - Small syntactic tweaks to address all compiler and clippy warnings + +- `Name` no longer assumes that labels are all ASCII, or text at all, since the DNS spec doesn't require it either. + - Notably, this allows parsing UTF-8 labels as used in mDNS. + - Unfortunately, this means that several types can no longer implement `ToString`, as there isn't an infallible + canonical text representation. + - `StrName` is provided for the common case of UTF-8 labels +- Update MSRV to 1.80.0 to allow `slice::split_at_checked`. This is older than rustc in Debian Stable (trixie, 1.85.0), + though newer than Ubuntu LTS (Noble Numbat, 1.75.0). +- Small syntactic tweaks to address all compiler and clippy warnings # 0.8.0 diff --git a/Cargo.toml b/Cargo.toml index 53445b1..458f8a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = ["fuzz"] + [package] name = "dns-parser" description = """ @@ -12,6 +15,7 @@ homepage = "https://github.com/tailhook/dns-parser" documentation = "https://docs.rs/dns-parser" version = "0.8.0" authors = ["Paul Colomiets "] +edition = "2015" [features] with-serde = ["serde", "serde_derive"] @@ -23,6 +27,7 @@ byteorder = "1" serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } +hex = "0.4.3" + [dev-dependencies] -matches = "0.1.2" itertools = "0.14.0" diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..80b71ab --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "dns-parser-fuzz" +version = "0.0.0" +publish = false +edition = "2015" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.dns-parser] +path = ".." + +[[bin]] +name = "parse_packet" +path = "fuzz_targets/parse_packet.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/parse_packet.rs b/fuzz/fuzz_targets/parse_packet.rs new file mode 100644 index 0000000..9560311 --- /dev/null +++ b/fuzz/fuzz_targets/parse_packet.rs @@ -0,0 +1,10 @@ +#![no_main] + +extern crate libfuzzer_sys; + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // not panicking is the goal + let _ = dns_parser::Packet::parse(data); +}); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4dd8e5c..dc2d42d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.75.0" \ No newline at end of file +channel = "1.80.0" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index ae6a397..2364271 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use std::str::Utf8Error; quick_error! { /// Error parsing DNS packet - #[derive(Debug)] + #[derive(Debug, PartialEq)] pub enum Error { /// Invalid compression pointer not pointing backwards /// when parsing label @@ -30,6 +30,10 @@ quick_error! { UnknownLabelFormat { description("label in domain name has unknown label format") } + /// Name length is invalid + InvalidNameLen { + description("name length in domain name is invalid") + } /// Query type code is invalid InvalidQueryType(code: u16) { description("query type code is invalid") @@ -50,10 +54,6 @@ quick_error! { description("class code is invalid") display("class {} is invalid", code) } - /// Invalid characters encountered while reading label - LabelIsNotAscii { - description("invalid characters encountered while reading label") - } /// Invalid characters encountered while reading TXT TxtDataIsNotUTF8(error: Utf8Error) { description("invalid characters encountered while reading TXT") diff --git a/src/lib.rs b/src/lib.rs index aaabb29..dd37251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,6 @@ #![warn(missing_debug_implementations)] extern crate byteorder; -#[cfg(test)] -#[macro_use] -extern crate matches; #[macro_use(quick_error)] extern crate quick_error; #[cfg(feature = "with-serde")] @@ -41,6 +38,6 @@ pub use builder::Builder; pub use enums::{Class, Opcode, QueryClass, QueryType, ResponseCode, Type}; pub use error::Error; pub use header::Header; -pub use name::Name; +pub use name::{Name, StrName}; pub use rdata::RData; pub use structs::{Packet, Question, ResourceRecord}; diff --git a/src/name.rs b/src/name.rs index e25a57b..fcb40d3 100644 --- a/src/name.rs +++ b/src/name.rs @@ -1,145 +1,302 @@ use std::fmt; -use std::fmt::Write; use std::str::from_utf8; -// Deprecated since rustc 1.23 -#[allow(unused_imports, deprecated)] -use std::ascii::AsciiExt; - -use byteorder::{BigEndian, ByteOrder}; - use Error; /// The DNS name as stored in the original packet /// /// This contains just a reference to a slice that contains the data. -/// You may turn this into a string using `.to_string()` +/// +/// Since DNS names or labels do not specify any constraints on their contents other than lengths, +/// labels are just byte slices (`&[u8]`). In typical Internet usage, they are ASCII text, while in +/// mDNS, they are UTF-8. +/// +/// It is preferable to iterate over labels (see [`Self::iter_labels`]) rather than splitting on +/// `.` in the dot-separated string form, since it is possible for DNS labels to themselves contain +/// a `.`. #[derive(Clone, Copy)] pub struct Name<'a> { + /// Starting point for parsing a valid name. + /// + /// All labels starting from here must parse successfully. + /// + /// This contains only the data read before jumping elsewhere in `original` to follow a pointer. labels: &'a [u8], - /// This is the original buffer size. The compressed names in original - /// are calculated in this buffer + /// This is the original buffer size. The compressed names referred to in `labels` are relative + /// to this. original: &'a [u8], + /// True iff all labels can be parsed and are UTF-8 + all_labels_utf8: bool, } impl<'a> Name<'a> { /// Scan the data to get Name object /// /// The `data` should be a part of `original` where name should start. - /// The `original` is the data starting a the start of a packet, so + /// The `original` is the data starting at the start of a packet, so /// that offsets in compressed name starts from the `original`. pub fn scan(data: &'a [u8], original: &'a [u8]) -> Result, Error> { - let mut parse_data = data; - let mut return_pos = None; - let mut pos = 0; - if parse_data.len() <= pos { - return Err(Error::UnexpectedEOF); + let mut iter = LabelIter::new(data, original); + // We need to parse at least to the first pointer to know how much of `data` was consumed. + // However, we might as well parse everything so we can check if all labels are UTF-8. + // The `.by_ref()` allows us to iterate, and advance the length counter, without consuming + // the iterator. + let all_labels_utf8 = iter.by_ref().try_fold(true, |all_previous_utf8, r| { + r.map(|bytes| from_utf8(bytes).is_ok() && all_previous_utf8) + })?; + + Ok(Self { + labels: data + .get(..iter.orig_name_len()) + .ok_or(Error::UnexpectedEOF)?, + original, + all_labels_utf8, + }) + } + + /// Number of bytes serialized name occupies, not counting any bytes after reading the first + /// pointer + pub fn byte_len(&self) -> usize { + self.labels.len() + } + + /// Iterate over the labels in the name + pub fn iter_labels(&self) -> impl Iterator { + LabelIter::new(self.labels, self.original) + // Name's contract guarantees that all labels can be parsed + .filter_map(|r| r.ok()) + } + + /// If all labels are UTF-8, returns a `StrName` that provides access to `&str` labels + pub fn as_str_name(&self) -> Option> { + if self.all_labels_utf8 { + Some(StrName { name: *self }) + } else { + None } - // By setting the largest_pos to be the original len, a side effect - // is that the pos variable can move forwards in the buffer once. - let mut largest_pos = original.len(); - let mut byte = parse_data[pos]; - while byte != 0 { - if parse_data.len() <= pos { - return Err(Error::UnexpectedEOF); + } +} + +impl<'a> fmt::Debug for Name<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Name(")?; + + for (index, label_bytes) in self.iter_labels().enumerate() { + if index != 0 { + write!(fmt, ".")?; } - if byte & 0b1100_0000 == 0b1100_0000 { - if parse_data.len() < pos + 2 { - return Err(Error::UnexpectedEOF); - } - let off = (BigEndian::read_u16(&parse_data[pos..pos + 2]) & !0b1100_0000_0000_0000) - as usize; - if off >= original.len() { - return Err(Error::UnexpectedEOF); + match from_utf8(label_bytes) { + Ok(l) => { + write!(fmt, "{}", l)?; } - // Set value for return_pos which is the pos in the original - // data buffer that should be used to return after validating - // the offsetted labels. - if return_pos.is_none() { - return_pos = Some(pos); + Err(_e) => { + write!(fmt, "<0x{}>", hex::encode(label_bytes))?; } + } + } - // Check then set largest_pos to ensure we never go backwards - // in the buffer. - if off >= largest_pos { + write!(fmt, ")") + } +} + +/// A [`Name`] whose labels are all UTF-8. +/// +/// The [`fmt::Display`] implementation produces the typical dot-separated text form of a DNS name, +/// though no escaping of any `.` characters that may exist in a label is performed (which is +/// possible, if unusual). +/// +/// For precise label by label access, see [`Self::iter_labels`]. +#[derive(Clone, Copy)] +pub struct StrName<'a> { + /// All labels must already have been checked to be UTF-8. + name: Name<'a>, +} + +impl<'a> StrName<'a> { + /// Iterate over the labels in the name, all of which are UTF-8 + pub fn iter_labels(&self) -> impl Iterator { + self.name + .iter_labels() + // all labels are UTF-8, so this won't drop anything + .filter_map(|bytes| from_utf8(bytes).ok()) + } +} + +impl<'a> fmt::Debug for StrName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "StrName({})", self) + } +} + +impl<'a> fmt::Display for StrName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (index, l) in self.iter_labels().enumerate() { + if index != 0 { + write!(f, ".")?; + } + write!(f, "{}", l)?; + } + Ok(()) + } +} + +/// An iterator over labels in a [`Name`]. +/// +/// Stops iterating (produces `None`) at the end of the name, or once an error has been encountered. +/// +/// See and +struct LabelIter<'a> { + /// The next name bytes to read labels from + remaining: &'a [u8], + /// The complete DNS packet, which offsets in compressed labels are relative to + original: &'a [u8], + /// The last offset jumped to. + /// + /// Since we don't know where a name starts in the RR, we can't do exactly the anti-cycle + /// check laid out in RFC 9267. However, we can still keep track of the _first_ pointer, and + /// only allow the offset to decrease from there. + last_offset: Option, + /// True when we've reached the end, or encountered an error + done: bool, + /// Number of encoded bytes read when decoding the name. If it exceeds 255, it's an error per + /// RFC 1035 2.3.4. + /// + /// It's not entirely clear what 4.1.4, which expands upon this, means by using the length of + /// the compressed name. If multiple pointers are followed, for instance, when do we stop + /// counting bytes towards the 255 limit? + /// + /// RFC 9267 suggests limiting to 255 bytes, including the decompressed size. So, we just count + /// _all_ bytes read, including pointers, to be safe. + encoded_name_len_read: usize, + /// The number of bytes read from the original name data, after reading the first pointer. + orig_name_len: Option, +} + +impl<'a> LabelIter<'a> { + const COMPRESSED_MARKER: u8 = 0xc0; + + /// See + const MAX_NAME_LEN: usize = 255; + + fn new(name: &'a [u8], original: &'a [u8]) -> Self { + Self { + remaining: name, + original, + last_offset: None, + done: false, + encoded_name_len_read: 0, + orig_name_len: None, + } + } + + /// Read the next label in the name. + /// + /// Returns `None` if no labels are left. + fn read_label(&mut self) -> Result, Error> { + loop { + let byte = self.read_byte()?; + + // Check for pointer per RFC 9267 Fig 2 + if byte & Self::COMPRESSED_MARKER == Self::COMPRESSED_MARKER { + let target_offset = + u16::from_be_bytes([byte & !Self::COMPRESSED_MARKER, self.read_byte()?]).into(); + + // This allows one forward jump since we don't know the name's position in the packet, + // but this still prevents loops since all jumps must go backwards from there. + let pointer_valid = match self.last_offset { + Some(o) => target_offset < o, + None => { + // record what we've previously read in the original name + self.orig_name_len = Some(self.encoded_name_len_read); + true + } + }; + if !pointer_valid { return Err(Error::BadPointer); } - largest_pos = off; - pos = 0; - parse_data = &original[off..]; - } else if byte & 0b1100_0000 == 0 { - let end = pos + byte as usize + 1; - if parse_data.len() < end { - return Err(Error::UnexpectedEOF); - } - if !parse_data[pos + 1..end].is_ascii() { - return Err(Error::LabelIsNotAscii); - } - pos = end; - if parse_data.len() <= pos { - return Err(Error::UnexpectedEOF); - } + self.last_offset = Some(target_offset); + + self.remaining = self + .original + .get(target_offset..) + .ok_or(Error::UnexpectedEOF)?; + } else if byte == 0 { + return Ok(None); + } else if byte < 64 { + let len = byte.into(); + self.mark_bytes_read(len)?; + let (label, rem) = self + .remaining + .split_at_checked(len) + .ok_or(Error::UnexpectedEOF)?; + self.remaining = rem; + return Ok(Some(label)); } else { + // Nonzero bit patterns in the top 2 bits except for 0xC0 above are invalid return Err(Error::UnknownLabelFormat); } - byte = parse_data[pos]; } - if let Some(return_pos) = return_pos { - Ok(Name { - labels: &data[..return_pos + 2], - original, - }) + } + + /// Read the next byte from `self.name` + fn read_byte(&mut self) -> Result { + self.mark_bytes_read(1)?; + + let (first, rem) = self.remaining.split_first().ok_or(Error::UnexpectedEOF)?; + self.remaining = rem; + Ok(*first) + } + + /// Returns an error if `len` would push the total bytes read over the name length limit. + /// + /// Otherwise, records the new total length including `len`. + fn mark_bytes_read(&mut self, len: usize) -> Result<(), Error> { + let sum = self + .encoded_name_len_read + .checked_add(len) + .ok_or(Error::InvalidNameLen)?; + if sum > Self::MAX_NAME_LEN { + Err(Error::InvalidNameLen) } else { - Ok(Name { - labels: &data[..pos + 1], - original, - }) + self.encoded_name_len_read = sum; + Ok(()) } } - /// Number of bytes serialized name occupies - pub fn byte_len(&self) -> usize { - self.labels.len() + + /// Returns the number of bytes read from the original name buffer, before following any + /// pointers + fn orig_name_len(&self) -> usize { + // if we didn't hit a pointer, the overall encoded length read is the number we want + self.orig_name_len.unwrap_or(self.encoded_name_len_read) } } -impl<'a> fmt::Display for Name<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let data = self.labels; - let original = self.original; - let mut pos = 0; - loop { - let byte = data[pos]; - if byte == 0 { - return Ok(()); - } else if byte & 0b1100_0000 == 0b1100_0000 { - let off = - (BigEndian::read_u16(&data[pos..pos + 2]) & !0b1100_0000_0000_0000) as usize; - if pos != 0 { - fmt.write_char('.')?; - } - return fmt::Display::fmt(&Name::scan(&original[off..], original).unwrap(), fmt); - } else if byte & 0b1100_0000 == 0 { - if pos != 0 { - fmt.write_char('.')?; - } - let end = pos + byte as usize + 1; - fmt.write_str(from_utf8(&data[pos + 1..end]).unwrap())?; - pos = end; - continue; - } else { - unreachable!(); +impl<'a> Iterator for LabelIter<'a> { + type Item = Result<&'a [u8], Error>; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + + match self.read_label() { + Ok(Some(l)) => Some(Ok(l)), + Ok(None) => { + self.done = true; + None + } + Err(e) => { + self.done = true; + Some(Err(e)) } } } } -impl<'a> fmt::Debug for Name<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_tuple("Name").field(&format!("{}", self)).finish() - } -} #[cfg(test)] mod test { + use itertools::Itertools; + use name::LabelIter; use Error; use Name; @@ -148,12 +305,10 @@ mod test { // A buffer where an offset points to itself, // which is a bad compression pointer. let same_offset = vec![192, 2, 192, 2]; - let is_match = matches!( - Name::scan(&same_offset, &same_offset), - Err(Error::BadPointer) + assert_eq!( + Error::BadPointer, + Name::scan(&same_offset, &same_offset).unwrap_err() ); - - assert!(is_match); } #[test] @@ -161,12 +316,11 @@ mod test { // A buffer where the offsets points back to each other which causes // infinite recursion if never checked, a bad compression pointer. let forwards_offset = vec![192, 2, 192, 4, 192, 2]; - let is_match = matches!( - Name::scan(&forwards_offset, &forwards_offset), - Err(Error::BadPointer) - ); - assert!(is_match); + assert_eq!( + Error::BadPointer, + Name::scan(&forwards_offset, &forwards_offset).unwrap_err(), + ) } #[test] @@ -174,17 +328,354 @@ mod test { // A buffer where an offset points to itself, a bad compression pointer. let buf = b"\x02xx\x00\x02yy\xc0\x00\x02zz\xc0\x04"; - assert_eq!(Name::scan(&buf[..], buf).unwrap().to_string(), "xx"); + assert_eq!( + Name::scan(&buf[..], buf) + .unwrap() + .as_str_name() + .unwrap() + .to_string(), + "xx" + ); assert_eq!(Name::scan(&buf[..], buf).unwrap().labels, b"\x02xx\x00"); - assert_eq!(Name::scan(&buf[4..], buf).unwrap().to_string(), "yy.xx"); + assert_eq!( + Name::scan(&buf[4..], buf) + .unwrap() + .as_str_name() + .unwrap() + .to_string(), + "yy.xx" + ); assert_eq!( Name::scan(&buf[4..], buf).unwrap().labels, b"\x02yy\xc0\x00" ); - assert_eq!(Name::scan(&buf[9..], buf).unwrap().to_string(), "zz.yy.xx"); + assert_eq!( + Name::scan(&buf[9..], buf) + .unwrap() + .as_str_name() + .unwrap() + .to_string(), + "zz.yy.xx" + ); assert_eq!( Name::scan(&buf[9..], buf).unwrap().labels, b"\x02zz\xc0\x04" ); } + + #[test] + fn name_all_utf8_labels_ok() { + let bytes = b"\x03foo\x03bar\0"; + + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + assert_eq!("Name(foo.bar)", format!("{name:?}")); + + let str_name = name.as_str_name().unwrap(); + + assert_eq!("foo.bar", str_name.to_string()); + assert_eq!("StrName(foo.bar)", format!("{str_name:?}")); + } + + #[test] + fn name_all_valid_but_not_utf8_labels_ok_no_str_name() { + let bytes = b"\x03foo\x03ba\xFF\0"; + + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + assert_eq!("Name(foo.<0x6261ff>)", format!("{name:?}")); + + assert!(name.as_str_name().is_none()); + } + + #[test] + fn name_not_all_valid_labels_err() { + // pointer past EOF + let bytes = b"\x03foo\xC0\xFF"; + + assert_eq!( + Error::UnexpectedEOF, + Name::scan(&bytes[..], &bytes[..]).unwrap_err() + ); + } + + #[test] + fn iter_labels_invalid_label_type() { + let name = b"\x02ok\xA0"; + + assert_eq!( + vec![Ok("ok"), Err(Error::UnknownLabelFormat)], + iter_labels(&name[..], &name[..]) + ); + } + + #[test] + fn iter_labels_binary_and_utf8_labels() { + // 3-byte unicode snowman, then, 2 non-UTF-8 bytes + let name = b"\x03\xE2\x98\x83\x02\xFF\xFF\x00"; + + assert_eq!( + vec![Ok("☃".as_bytes()), Ok(b"\xFF\xFF"),], + LabelIter::new(name, name).collect_vec() + ); + } + + #[test] + fn iter_labels_label_len_eof_err() { + // eof when reading the label itself + let name = b"\x03foo\x04bar"; + + assert_eq!( + vec![Ok("foo"), Err(Error::UnexpectedEOF)], + iter_labels(&name[..], &name[..]) + ); + } + + #[test] + fn iter_labels_eof_reading_next_byte_err() { + // read the trailing nul as part of the second label, so it'll fail when looking for what + // should be the nul suffix + let name = b"\x03foo\x04bar\x00"; + + assert_eq!( + vec![Ok("foo"), Ok("bar\x00"), Err(Error::UnexpectedEOF)], + iter_labels(&name[..], &name[..]) + ); + } + + #[test] + fn iter_labels_ptr_to_self_err() { + let name = b"\x01a\xC0\x02"; + + assert_eq!( + vec![Ok("a"), Err(Error::BadPointer)], + iter_labels(&name[..], &name[..]) + ); + } + + #[test] + fn iter_labels_ptr_eof_err() { + let name = b"\x01a\xC0\x0A"; + + assert_eq!( + vec![Ok("a"), Err(Error::UnexpectedEOF)], + iter_labels(&name[..], &name[..]) + ); + } + + #[test] + fn iter_labels_ptr_second_byte_eof_err() { + let name = b"\x01a\xC0"; + + assert_eq!( + vec![Ok("a"), Err(Error::UnexpectedEOF)], + iter_labels(&name[..], &name[..]) + ); + } + + #[test] + fn iter_labels_forward_ptr_then_backwards_ok() { + // jump from first ptr to second is ok, and so is the backwards ptr + let name = b"\x02ok\x00\xC0\x06\xC0\x00"; + + assert_eq!(vec![Ok("ok")], iter_labels(&name[..], &name[..])); + } + + #[test] + fn iter_labels_two_forward_ptrs_error() { + // jump from first ptr to second is ok, but from second to third is not + let name = b"\xC0\x02\xC0\x04\x02hi"; + + assert_eq!( + vec![Err(Error::BadPointer)], + iter_labels(&name[..], &name[..]), + ); + } + + #[test] + fn iter_labels_max_len_error_long_labels_limit_at_nul_byte() { + // too long because of nul byte: + // 4 labels of 1 + 59 = 60, 1 chunk of 1 + 14, + nul = 256 + let mut name = vec![]; + name.push(59); + name.extend_from_slice(&[b'a'; 59]); + name.push(59); + name.extend_from_slice(&[b'b'; 59]); + name.push(59); + name.extend_from_slice(&[b'c'; 59]); + name.push(59); + name.extend_from_slice(&[b'd'; 59]); + name.push(14); + name.extend_from_slice(&[b'e'; 14]); + name.push(0); + + assert_eq!(256, name.len()); + assert_eq!( + vec![ + Ok("a".repeat(59).as_str()), + Ok("b".repeat(59).as_str()), + Ok("c".repeat(59).as_str()), + Ok("d".repeat(59).as_str()), + Ok("e".repeat(14).as_str()), + Err(Error::InvalidNameLen) + ], + iter_labels(&name, &name) + ); + } + + #[test] + fn iter_labels_max_len_error_long_labels() { + // too long because of last label: + // 4 labels of 1 + 59 = 60, 1 chunk of 1 + 15, + nul = 257 + let mut name = vec![]; + name.push(59); + name.extend_from_slice(&[b'a'; 59]); + name.push(59); + name.extend_from_slice(&[b'b'; 59]); + name.push(59); + name.extend_from_slice(&[b'c'; 59]); + name.push(59); + name.extend_from_slice(&[b'd'; 59]); + name.push(15); + name.extend_from_slice(&[b'e'; 15]); + name.push(0); + + assert_eq!(257, name.len()); + assert_eq!( + vec![ + Ok("a".repeat(59).as_str()), + Ok("b".repeat(59).as_str()), + Ok("c".repeat(59).as_str()), + Ok("d".repeat(59).as_str()), + Err(Error::InvalidNameLen) + ], + iter_labels(&name, &name) + ); + } + + #[test] + fn iter_labels_max_len_error_recursive_pointers() { + let mut name = b"\x04quux\x00".to_vec(); + // 125 pointers each going back by 2, totaling 250 bytes, plus the 6 byte prefix = 256 + for i in 0..=124 { + name.push(0xC0); + if i == 0 { + name.push(0); + } else { + name.push(6 + (i - 1) * 2) + } + } + assert_eq!(256, name.len()); + assert_eq!( + vec![Ok("quux"), Err(Error::InvalidNameLen)], + // start with last 2 bytes + iter_labels(&name[254..], &name) + ); + } + + #[test] + fn iter_labels_max_len_ok_recursive_pointers() { + let mut name = b"\x01a\x00".to_vec(); + // 126 pointers each going back by 2, totaling 252 bytes, plus the 3 byte prefix = 255 + for i in 0..=125 { + name.push(0xC0); + if i == 0 { + name.push(0); + } else { + name.push(3 + (i - 1) * 2) + } + } + assert_eq!(255, name.len()); + assert_eq!( + vec![Ok("a")], + // start with last 2 bytes + iter_labels(&name[253..], &name) + ); + } + + #[test] + fn orig_name_parse_len_empty() { + let bytes = b"\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + assert_eq!(1, name.labels.len()); + } + + #[test] + fn orig_name_parse_len_no_pointers() { + let bytes = b"\x03foo\x03bar\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + assert_eq!(9, name.labels.len()); + } + + #[test] + fn orig_name_parse_len_starts_with_ptr() { + let bytes = b"\x03baz\x00\xC0\x00"; + let name = Name::scan(&bytes[5..], &bytes[..]).unwrap(); + // just the pointer + assert_eq!(2, name.labels.len()); + } + + #[test] + fn orig_name_parse_len_stops_at_first_pointer_after_some_labels() { + let bytes = b"\x03baz\x00\x03foo\x03bar\xC0\x00"; + let name = Name::scan(&bytes[5..], &bytes[..]).unwrap(); + // two labels plus the pointer + assert_eq!(10, name.labels.len()); + } + + #[test] + fn name_as_string_empty() { + let bytes = b"\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + + assert_eq!("", name.as_str_name().unwrap().to_string()); + } + + #[test] + fn name_as_string_all_labels_text() { + let bytes = b"\x03foo\x03bar\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + + assert_eq!("foo.bar", name.as_str_name().unwrap().to_string()); + } + + #[test] + fn name_as_string_some_labels_binary() { + let bytes = b"\x03foo\x03\xFF\xFA\xF0\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + + assert!(name.as_str_name().is_none()); + } + + #[test] + fn name_debug_empty() { + let bytes = b"\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + + assert_eq!("Name()", format!("{name:?}")); + } + + #[test] + fn name_debug_all_labels_text() { + let bytes = b"\x03foo\x03bar\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + + assert_eq!("Name(foo.bar)", format!("{name:?}")); + assert_eq!( + "StrName(foo.bar)", + format!("{:?}", name.as_str_name().unwrap()) + ); + } + + #[test] + fn name_debug_some_labels_binary() { + let bytes = b"\x03foo\x03\xFF\xFA\xF0\x00"; + let name = Name::scan(&bytes[..], &bytes[..]).unwrap(); + + assert_eq!("Name(foo.<0xfffaf0>)", format!("{name:?}")); + } + + fn iter_labels<'a>(name: &'a [u8], original: &'a [u8]) -> Vec> { + LabelIter::new(name, original) + .map(|r| r.map(|l| std::str::from_utf8(l).unwrap())) + .collect_vec() + } } diff --git a/src/parser.rs b/src/parser.rs index 500fa99..5d1ac2a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,3 @@ -use std::i32; - use byteorder::{BigEndian, ByteOrder}; use rdata::opt::Record as Opt; @@ -11,7 +9,7 @@ const OPT_RR_START: [u8; 3] = [0, 0, 41]; impl<'a> Packet<'a> { /// Parse a full DNS Packet and return a structure that has all the /// data borrowed from the passed buffer. - pub fn parse(data: &[u8]) -> Result { + pub fn parse(data: &[u8]) -> Result, Error> { let header = Header::parse(data)?; let mut offset = Header::size(); let mut questions = Vec::with_capacity(header.questions as usize); @@ -194,7 +192,10 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "example.com" + ); assert_eq!(packet.answers.len(), 0); } @@ -227,9 +228,15 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "example.com" + ); assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "example.com"); + assert_eq!( + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], + "example.com" + ); assert!(!packet.answers[0].multicast_unique); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 1272); @@ -290,30 +297,49 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "www.skype.com" + ); assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); + assert_eq!( + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], + "www.skype.com" + ); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 3600); match packet.answers[0].data { RData::CNAME(cname) => { - assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); + assert_eq!( + &cname.0.as_str_name().unwrap().to_string()[..], + "livecms.trafficmanager.net" + ); } ref x => panic!("Wrong rdata {:?}", x), } assert_eq!(packet.nameservers.len(), 1); - assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); + assert_eq!( + &packet.nameservers[0] + .name + .as_str_name() + .unwrap() + .to_string()[..], + "net" + ); assert_eq!(packet.nameservers[0].cls, C::IN); assert_eq!(packet.nameservers[0].ttl, 120275); match packet.nameservers[0].data { RData::NS(ns) => { - assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); + assert_eq!( + &ns.0.as_str_name().unwrap().to_string()[..], + "g.gtld-servers.net" + ); } ref x => panic!("Wrong rdata {:?}", x), } assert_eq!(packet.additional.len(), 1); assert_eq!( - &packet.additional[0].name.to_string()[..], + &packet.additional[0].name.as_str_name().unwrap().to_string()[..], "a.gtld-servers.net" ); assert_eq!(packet.additional[0].cls, C::IN); @@ -361,7 +387,10 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "google.com" + ); assert_eq!(packet.answers.len(), 6); let ips = [ Ipv4Addr::new(64, 233, 164, 100), @@ -372,7 +401,10 @@ mod test { Ipv4Addr::new(64, 233, 164, 138), ]; for (answer, ip) in packet.answers.iter().zip_eq(ips.iter()) { - assert_eq!(&answer.name.to_string()[..], "google.com"); + assert_eq!( + &answer.name.as_str_name().unwrap().to_string()[..], + "google.com" + ); assert_eq!(answer.cls, C::IN); assert_eq!(answer.ttl, 239); match answer.data { @@ -413,7 +445,7 @@ mod test { assert_eq!(packet.questions[0].qclass, QC::IN); assert!(!packet.questions[0].prefer_unicast); assert_eq!( - &packet.questions[0].qname.to_string()[..], + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], "_xmpp-server._tcp.gmail.com" ); assert_eq!(packet.answers.len(), 0); @@ -459,7 +491,10 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "google.com" + ); assert_eq!(packet.answers.len(), 0); match packet.opt { Some(opt) => { diff --git a/src/rdata/aaaa.rs b/src/rdata/aaaa.rs index b1884e6..294f784 100644 --- a/src/rdata/aaaa.rs +++ b/src/rdata/aaaa.rs @@ -70,9 +70,15 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::AAAA); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "google.com" + ); assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "google.com"); + assert_eq!( + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], + "google.com" + ); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 139); match packet.answers[0].data { diff --git a/src/rdata/cname.rs b/src/rdata/cname.rs index 887f597..c85ed07 100644 --- a/src/rdata/cname.rs +++ b/src/rdata/cname.rs @@ -3,13 +3,6 @@ use Name; #[derive(Debug, Clone, Copy)] pub struct Record<'a>(pub Name<'a>); -impl<'a> ToString for Record<'a> { - #[inline] - fn to_string(&self) -> String { - self.0.to_string() - } -} - impl<'a> super::Record<'a> for Record<'a> { const TYPE: isize = 5; @@ -71,16 +64,19 @@ mod test { assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); assert_eq!( - &packet.questions[0].qname.to_string()[..], + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], "cdn.sstatic.net" ); assert_eq!(packet.answers.len(), 6); - assert_eq!(&packet.answers[0].name.to_string()[..], "cdn.sstatic.net"); + assert_eq!( + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], + "cdn.sstatic.net" + ); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 102); match packet.answers[0].data { RData::CNAME(cname) => { - assert_eq!(&cname.0.to_string(), "sstatic.net"); + assert_eq!(&cname.0.as_str_name().unwrap().to_string(), "sstatic.net"); } ref x => panic!("Wrong rdata {:?}", x), } @@ -93,7 +89,10 @@ mod test { Ipv4Addr::new(104, 16, 105, 204), ]; for i in 1..6 { - assert_eq!(&packet.answers[i].name.to_string()[..], "sstatic.net"); + assert_eq!( + &packet.answers[i].name.as_str_name().unwrap().to_string()[..], + "sstatic.net" + ); assert_eq!(packet.answers[i].cls, C::IN); assert_eq!(packet.answers[i].ttl, 102); match packet.answers[i].data { diff --git a/src/rdata/mx.rs b/src/rdata/mx.rs index db4c553..5e63c74 100644 --- a/src/rdata/mx.rs +++ b/src/rdata/mx.rs @@ -67,7 +67,10 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::MX); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "gmail.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "gmail.com" + ); assert_eq!(packet.answers.len(), 5); let items = [ (5, "gmail-smtp-in.l.google.com"), @@ -77,7 +80,10 @@ mod test { (30, "alt3.gmail-smtp-in.l.google.com"), ]; for (answer, item) in packet.answers.iter().zip_eq(items.iter()) { - assert_eq!(&answer.name.to_string()[..], "gmail.com"); + assert_eq!( + &answer.name.as_str_name().unwrap().to_string()[..], + "gmail.com" + ); assert_eq!(answer.cls, C::IN); assert_eq!(answer.ttl, 1148); match answer.data { @@ -86,7 +92,10 @@ mod test { exchange, }) => { assert_eq!(preference, item.0); - assert_eq!(exchange.to_string(), item.1.to_string()); + assert_eq!( + exchange.as_str_name().unwrap().to_string(), + item.1.to_string() + ); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/ns.rs b/src/rdata/ns.rs index 1550ddf..a04adc8 100644 --- a/src/rdata/ns.rs +++ b/src/rdata/ns.rs @@ -3,13 +3,6 @@ use Name; #[derive(Debug, Clone, Copy)] pub struct Record<'a>(pub Name<'a>); -impl<'a> ToString for Record<'a> { - #[inline] - fn to_string(&self) -> String { - self.0.to_string() - } -} - impl<'a> super::Record<'a> for Record<'a> { const TYPE: isize = 2; @@ -65,24 +58,43 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "www.skype.com" + ); assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); + assert_eq!( + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], + "www.skype.com" + ); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 3600); match packet.answers[0].data { RData::CNAME(cname) => { - assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); + assert_eq!( + &cname.0.as_str_name().unwrap().to_string()[..], + "livecms.trafficmanager.net" + ); } ref x => panic!("Wrong rdata {:?}", x), } assert_eq!(packet.nameservers.len(), 1); - assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); + assert_eq!( + &packet.nameservers[0] + .name + .as_str_name() + .unwrap() + .to_string()[..], + "net" + ); assert_eq!(packet.nameservers[0].cls, C::IN); assert_eq!(packet.nameservers[0].ttl, 120275); match packet.nameservers[0].data { RData::NS(ns) => { - assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); + assert_eq!( + &ns.0.as_str_name().unwrap().to_string()[..], + "g.gtld-servers.net" + ); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/ptr.rs b/src/rdata/ptr.rs index 03d607c..edc9e65 100644 --- a/src/rdata/ptr.rs +++ b/src/rdata/ptr.rs @@ -3,13 +3,6 @@ use Name; #[derive(Debug, Clone, Copy)] pub struct Record<'a>(pub Name<'a>); -impl<'a> ToString for Record<'a> { - #[inline] - fn to_string(&self) -> String { - self.0.to_string() - } -} - impl<'a> super::Record<'a> for Record<'a> { const TYPE: isize = 12; @@ -62,19 +55,22 @@ mod test { assert_eq!(packet.questions[0].qtype, QT::PTR); assert_eq!(packet.questions[0].qclass, QC::IN); assert_eq!( - &packet.questions[0].qname.to_string()[..], + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], "69.93.75.72.in-addr.arpa" ); assert_eq!(packet.answers.len(), 1); assert_eq!( - &packet.answers[0].name.to_string()[..], + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], "69.93.75.72.in-addr.arpa" ); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 86400); match packet.answers[0].data { RData::PTR(name) => { - assert_eq!(&name.0.to_string()[..], "pool-72-75-93-69.verizon.net"); + assert_eq!( + &name.0.as_str_name().unwrap().to_string()[..], + "pool-72-75-93-69.verizon.net" + ); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/soa.rs b/src/rdata/soa.rs index a9f3923..391c69a 100644 --- a/src/rdata/soa.rs +++ b/src/rdata/soa.rs @@ -80,20 +80,33 @@ mod test { assert_eq!(packet.questions[0].qtype, QT::A); assert_eq!(packet.questions[0].qclass, QC::IN); assert_eq!( - &packet.questions[0].qname.to_string()[..], + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], "dlkfjkdjdslfkj.youtube.com" ); assert_eq!(packet.answers.len(), 0); assert_eq!(packet.nameservers.len(), 1); - assert_eq!(&packet.nameservers[0].name.to_string()[..], "youtube.com"); + assert_eq!( + &packet.nameservers[0] + .name + .as_str_name() + .unwrap() + .to_string()[..], + "youtube.com" + ); assert_eq!(packet.nameservers[0].cls, C::IN); assert!(!packet.nameservers[0].multicast_unique); assert_eq!(packet.nameservers[0].ttl, 10800); match packet.nameservers[0].data { RData::SOA(ref soa_rec) => { - assert_eq!(&soa_rec.primary_ns.to_string()[..], "youtube.com"); - assert_eq!(&soa_rec.mailbox.to_string()[..], "admin.youtube.com"); + assert_eq!( + &soa_rec.primary_ns.as_str_name().unwrap().to_string()[..], + "youtube.com" + ); + assert_eq!( + &soa_rec.mailbox.as_str_name().unwrap().to_string()[..], + "admin.youtube.com" + ); assert_eq!(soa_rec.serial, 2012031603); assert_eq!(soa_rec.refresh, 20864); assert_eq!(soa_rec.retry, 3600); diff --git a/src/rdata/srv.rs b/src/rdata/srv.rs index 42ef1b6..6fb82f5 100644 --- a/src/rdata/srv.rs +++ b/src/rdata/srv.rs @@ -76,7 +76,7 @@ mod test { assert_eq!(packet.questions[0].qtype, QT::SRV); assert_eq!(packet.questions[0].qclass, QC::IN); assert_eq!( - &packet.questions[0].qname.to_string()[..], + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], "_xmpp-server._tcp.gmail.com" ); assert_eq!(packet.answers.len(), 5); @@ -88,7 +88,10 @@ mod test { (20, 0, 5269, "alt4.xmpp-server.l.google.com"), ]; for (answer, item) in packet.answers.iter().zip_eq(items.iter()) { - assert_eq!(&answer.name.to_string()[..], "_xmpp-server._tcp.gmail.com"); + assert_eq!( + &answer.name.as_str_name().unwrap().to_string()[..], + "_xmpp-server._tcp.gmail.com" + ); assert_eq!(answer.cls, C::IN); assert_eq!(answer.ttl, 900); match answer.data { @@ -101,7 +104,10 @@ mod test { assert_eq!(priority, item.0); assert_eq!(weight, item.1); assert_eq!(port, item.2); - assert_eq!(target.to_string(), item.3.to_string()); + assert_eq!( + target.as_str_name().unwrap().to_string(), + item.3.to_string() + ); } ref x => panic!("Wrong rdata {:?}", x), } diff --git a/src/rdata/txt.rs b/src/rdata/txt.rs index 766f1e0..f454ada 100644 --- a/src/rdata/txt.rs +++ b/src/rdata/txt.rs @@ -98,9 +98,15 @@ mod test { assert_eq!(packet.questions.len(), 1); assert_eq!(packet.questions[0].qtype, QT::TXT); assert_eq!(packet.questions[0].qclass, QC::IN); - assert_eq!(&packet.questions[0].qname.to_string()[..], "facebook.com"); + assert_eq!( + &packet.questions[0].qname.as_str_name().unwrap().to_string()[..], + "facebook.com" + ); assert_eq!(packet.answers.len(), 1); - assert_eq!(&packet.answers[0].name.to_string()[..], "facebook.com"); + assert_eq!( + &packet.answers[0].name.as_str_name().unwrap().to_string()[..], + "facebook.com" + ); assert!(!packet.answers[0].multicast_unique); assert_eq!(packet.answers[0].cls, C::IN); assert_eq!(packet.answers[0].ttl, 86333);