
commit
63cd9196f9
31 changed files with 3303 additions and 0 deletions
@ -0,0 +1,217 @@
|
||||
[[package]] |
||||
name = "backtrace" |
||||
version = "0.3.3" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"backtrace-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "backtrace-sys" |
||||
version = "0.1.14" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "byteorder" |
||||
version = "1.1.0" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "bytes" |
||||
version = "0.4.5" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "cc" |
||||
version = "1.0.0" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "cfg-if" |
||||
version = "0.1.2" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "dbghelp-sys" |
||||
version = "0.2.0" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "dnsbox" |
||||
version = "0.1.0" |
||||
dependencies = [ |
||||
"dnsbox-base 0.1.0", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "dnsbox-base" |
||||
version = "0.1.0" |
||||
dependencies = [ |
||||
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"dnsbox-derive 0.1.0", |
||||
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "dnsbox-derive" |
||||
version = "0.1.0" |
||||
dependencies = [ |
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "failure" |
||||
version = "0.1.1" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"backtrace 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "failure_derive" |
||||
version = "0.1.1" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "iovec" |
||||
version = "0.1.0" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "kernel32-sys" |
||||
version = "0.2.2" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "lazy_static" |
||||
version = "1.0.0" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "libc" |
||||
version = "0.2.31" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "log" |
||||
version = "0.3.8" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "quote" |
||||
version = "0.3.15" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "rustc-demangle" |
||||
version = "0.1.5" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "smallvec" |
||||
version = "0.4.4" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "syn" |
||||
version = "0.11.11" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "synom" |
||||
version = "0.11.3" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "synstructure" |
||||
version = "0.6.1" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
dependencies = [ |
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", |
||||
] |
||||
|
||||
[[package]] |
||||
name = "unicode-xid" |
||||
version = "0.0.4" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "winapi" |
||||
version = "0.2.8" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[[package]] |
||||
name = "winapi-build" |
||||
version = "0.1.1" |
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
||||
[metadata] |
||||
"checksum backtrace 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "99f2ce94e22b8e664d95c57fff45b98a966c2252b60691d0b7aeeccd88d70983" |
||||
"checksum backtrace-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c63ea141ef8fdb10409d0f5daf30ac51f84ef43bff66f16627773d2a292cd189" |
||||
"checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" |
||||
"checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" |
||||
"checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1" |
||||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" |
||||
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" |
||||
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" |
||||
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" |
||||
"checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" |
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" |
||||
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" |
||||
"checksum libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d1419b2939a0bc44b77feb34661583c7546b532b192feab36249ab584b86856c" |
||||
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" |
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" |
||||
"checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" |
||||
"checksum smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4f357e8cd37bf8822e1b964e96fd39e2cb5a0424f8aaa284ccaccc2162411c" |
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" |
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" |
||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" |
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" |
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" |
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" |
@ -0,0 +1,9 @@
|
||||
[package] |
||||
name = "dnsbox" |
||||
version = "0.1.0" |
||||
authors = ["Stefan Bühler <stbuehler@web.de>"] |
||||
|
||||
[dependencies] |
||||
dnsbox-base = { path = "lib/dnsbox-base" } |
||||
|
||||
[workspace] |
@ -0,0 +1,13 @@
|
||||
[package] |
||||
name = "dnsbox-base" |
||||
version = "0.1.0" |
||||
authors = ["Stefan Bühler <stbuehler@web.de>"] |
||||
|
||||
[dependencies] |
||||
byteorder = "1.1.0" |
||||
bytes = "0.4" |
||||
dnsbox-derive = { path = "../dnsbox-derive" } |
||||
failure = "0.1.1" |
||||
lazy_static = "1.0.0" |
||||
log = "0.3" |
||||
smallvec = "0.4.4" |
@ -0,0 +1,90 @@
|
||||
use bytes::{Bytes, Buf}; |
||||
use std::io::Cursor; |
||||
|
||||
use ser::DnsPacketData; |
||||
use errors::*; |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct HexShortBlob(Bytes); |
||||
|
||||
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) hex
|
||||
// for text representation; "-" when empty
|
||||
impl DnsPacketData for HexShortBlob { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
check_enough_data!(data, 1, "HexShortBlob length"); |
||||
let label_len = data.get_u8() as usize; |
||||
check_enough_data!(data, label_len, "HexShortBlob content"); |
||||
let pos = data.position() as usize; |
||||
let text = data.get_ref().slice(pos, pos + label_len); |
||||
data.advance(label_len); |
||||
Ok(HexShortBlob(text)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct Base32ShortBlob(Bytes); |
||||
|
||||
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) base32
|
||||
// for text representation; "-" when empty
|
||||
impl DnsPacketData for Base32ShortBlob { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
check_enough_data!(data, 1, "Base32ShortBlob length"); |
||||
let label_len = data.get_u8() as usize; |
||||
check_enough_data!(data, label_len, "Base32ShortBlob content"); |
||||
let pos = data.position() as usize; |
||||
let text = data.get_ref().slice(pos, pos + label_len); |
||||
data.advance(label_len); |
||||
Ok(Base32ShortBlob(text)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct LongText(Vec<Bytes>); |
||||
|
||||
// RFC 1035 names this `One or more <character-string>`. No following
|
||||
// field allowed.
|
||||
impl DnsPacketData for LongText { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
let mut texts = Vec::new(); |
||||
loop { |
||||
check_enough_data!(data, 1, "LongText length"); |
||||
let label_len = data.get_u8() as usize; |
||||
check_enough_data!(data, label_len, "LongText content"); |
||||
let pos = data.position() as usize; |
||||
texts.push(data.get_ref().slice(pos, pos + label_len)); |
||||
data.advance(label_len); |
||||
if !data.has_remaining() { break; } |
||||
} |
||||
Ok(LongText(texts)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct RemainingText(Bytes); |
||||
|
||||
// No length byte, just all data to end of record. uses base64 encoding
|
||||
// for text representation
|
||||
impl DnsPacketData for RemainingText { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
let pos = data.position() as usize; |
||||
let len = data.remaining(); |
||||
let text = data.get_ref().slice(pos, pos + len); |
||||
data.advance(len); |
||||
Ok(RemainingText(text)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct HexRemainingBlob(Bytes); |
||||
|
||||
// No length byte, just all data to end of record. uses hex encoding for
|
||||
// text representation (spaces are ignored - last field in record).
|
||||
impl DnsPacketData for HexRemainingBlob { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
let pos = data.position() as usize; |
||||
let len = data.remaining(); |
||||
let text = data.get_ref().slice(pos, pos + len); |
||||
data.advance(len); |
||||
Ok(HexRemainingBlob(text)) |
||||
} |
||||
} |
@ -0,0 +1,9 @@
|
||||
pub mod name; |
||||
pub mod text; |
||||
pub mod binary; |
||||
mod rr_type; |
||||
|
||||
pub use self::name::{DnsName, DnsCompressedName}; |
||||
pub use self::text::{ShortText}; |
||||
pub use self::binary::{HexShortBlob, Base32ShortBlob, LongText, RemainingText, HexRemainingBlob}; |
||||
pub use self::rr_type::Type; |
@ -0,0 +1,79 @@
|
||||
use std::fmt; |
||||
use super::DnsLabelRef; |
||||
|
||||
/// Customize formatting of DNS labels
|
||||
///
|
||||
/// The default uses "." as separator and adds a trailing separator.
|
||||
///
|
||||
/// The `Debug` formatters just format as `Display` to a String and then
|
||||
/// format that string as `Debug`.
|
||||
#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] |
||||
pub struct DisplayLabelsOptions { |
||||
/// separator to insert between (escaped) labels
|
||||
pub separator: &'static str, |
||||
/// whether a trailing separator is added.
|
||||
///
|
||||
/// without a trailing separator the root zone is represented as
|
||||
/// empty string!
|
||||
pub trailing: bool, |
||||
} |
||||
|
||||
impl Default for DisplayLabelsOptions { |
||||
fn default() -> Self { |
||||
DisplayLabelsOptions{ |
||||
separator: ".", |
||||
trailing: true, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Wrap anything representing a collection of labels (`DnsLabelRef`) to
|
||||
/// format using the given `options`.
|
||||
///
|
||||
/// As name you can pass any cloneable `(Into)Iterator` with
|
||||
/// `DnsLabelRef` items, e.g:
|
||||
///
|
||||
/// * `&DnsName`
|
||||
/// * `DnsNameIterator`
|
||||
#[derive(Clone)] |
||||
pub struct DisplayLabels<'a, I> |
||||
where |
||||
I: IntoIterator<Item=DnsLabelRef<'a>>+Clone |
||||
{ |
||||
/// Label collection to iterate over
|
||||
pub labels: I, |
||||
/// Options
|
||||
pub options: DisplayLabelsOptions, |
||||
} |
||||
|
||||
impl<'a, I> fmt::Debug for DisplayLabels<'a, I> |
||||
where |
||||
I: IntoIterator<Item=DnsLabelRef<'a>>+Clone |
||||
{ |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
// just escape the display version1
|
||||
format!("{}", self).fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl<'a, I> fmt::Display for DisplayLabels<'a, I> |
||||
where |
||||
I: IntoIterator<Item=DnsLabelRef<'a>>+Clone |
||||
{ |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
let mut i = self.labels.clone().into_iter(); |
||||
if let Some(first_label) = i.next() { |
||||
// first label
|
||||
fmt::Display::fmt(&first_label, w)?; |
||||
// remaining labels
|
||||
while let Some(label) = i.next() { |
||||
w.write_str(self.options.separator)?; |
||||
fmt::Display::fmt(&label, w)?; |
||||
} |
||||
} |
||||
if self.options.trailing { |
||||
w.write_str(self.options.separator)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,240 @@
|
||||
use bytes::Bytes; |
||||
use errors::*; |
||||
use std::fmt; |
||||
use std::cmp::Ordering; |
||||
|
||||
#[inline] |
||||
fn check_label(label: &[u8]) -> Result<()> { |
||||
if label.len() == 0 { |
||||
bail!("label must not be empty") |
||||
} |
||||
if label.len() > 63 { |
||||
bail!("label must not be longer than 63 bytes") |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// A DNS label (any binary string with `0 < length < 64`)
|
||||
#[derive(Clone)] |
||||
pub struct DnsLabel { |
||||
pub(super) label: Bytes, // 0 < len < 64
|
||||
} |
||||
|
||||
impl DnsLabel { |
||||
/// Create new label from existing storage
|
||||
///
|
||||
/// Fails when the length doesn't match the requirement `0 < length < 64`.
|
||||
pub fn new(label: Bytes) -> Result<Self> { |
||||
check_label(&label)?; |
||||
Ok(DnsLabel{label}) |
||||
} |
||||
|
||||
/// Convert to a representation without storage
|
||||
pub fn as_ref<'a>(&'a self) -> DnsLabelRef<'a> { |
||||
DnsLabelRef{label: self.label.as_ref()} |
||||
} |
||||
|
||||
/// Access as raw bytes
|
||||
pub fn as_bytes(&self) -> &Bytes { |
||||
&self.label |
||||
} |
||||
|
||||
/// Access as raw bytes
|
||||
pub fn as_raw(&self) -> &[u8] { |
||||
&self.label |
||||
} |
||||
|
||||
/// Length of label (0 < len < 64)
|
||||
pub fn len(&self) -> u8 { |
||||
self.label.len() as u8 |
||||
} |
||||
|
||||
/// compares two labels (ASCII case insensitive)
|
||||
#[inline] |
||||
pub fn compare<'a, L: Into<DnsLabelRef<'a>>>(&self, rhs: L) -> Ordering { |
||||
compare_ascii_lc(self.as_raw(), rhs.into().as_raw()) |
||||
} |
||||
} |
||||
|
||||
impl<'a> From<DnsLabelRef<'a>> for DnsLabel { |
||||
fn from(label_ref: DnsLabelRef<'a>) -> Self { |
||||
DnsLabel{ |
||||
label: Bytes::from(label_ref.label), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<DnsLabel> for DnsLabel { |
||||
#[inline] |
||||
fn eq(&self, rhs: &DnsLabel) -> bool { |
||||
self.as_ref().eq(&rhs.as_ref()) |
||||
} |
||||
} |
||||
|
||||
impl<'a> PartialEq<DnsLabelRef<'a>> for DnsLabel { |
||||
#[inline] |
||||
fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool { |
||||
self.as_ref().eq(rhs) |
||||
} |
||||
} |
||||
|
||||
impl Eq for DnsLabel{} |
||||
|
||||
impl PartialOrd<DnsLabel> for DnsLabel { |
||||
#[inline] |
||||
fn partial_cmp(&self, rhs: &DnsLabel) -> Option<Ordering> { |
||||
Some(self.compare(rhs)) |
||||
} |
||||
} |
||||
|
||||
impl<'a> PartialOrd<DnsLabelRef<'a>> for DnsLabel { |
||||
#[inline] |
||||
fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option<Ordering> { |
||||
Some(self.compare(*rhs)) |
||||
} |
||||
} |
||||
|
||||
impl Ord for DnsLabel { |
||||
#[inline] |
||||
fn cmp(&self, rhs: &Self) -> Ordering { |
||||
self.compare(rhs) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Debug for DnsLabel { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
self.as_ref().fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for DnsLabel { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
self.as_ref().fmt(w) |
||||
} |
||||
} |
||||
|
||||
/// A DNS label (any binary string with `0 < length < 64`)
|
||||
///
|
||||
/// Storage is provided through lifetime.
|
||||
#[derive(Clone,Copy)] |
||||
pub struct DnsLabelRef<'a> { |
||||
pub(super) label: &'a [u8], // 0 < len < 64
|
||||
} |
||||
|
||||
impl<'a> DnsLabelRef<'a> { |
||||
/// Create new label from existing storage
|
||||
///
|
||||
/// Fails when the length doesn't match the requirement `0 < length < 64`.
|
||||
pub fn new(label: &'a [u8]) -> Result<Self> { |
||||
check_label(label)?; |
||||
Ok(DnsLabelRef{label}) |
||||
} |
||||
|
||||
/// Access as raw bytes
|
||||
pub fn as_raw(&self) -> &'a [u8] { |
||||
self.label |
||||
} |
||||
|
||||
/// Length of label (0 < len < 64)
|
||||
pub fn len(&self) -> u8 { |
||||
self.label.len() as u8 |
||||
} |
||||
|
||||
/// compares two labels (ASCII case insensitive)
|
||||
#[inline] |
||||
pub fn compare<'b, L: Into<DnsLabelRef<'b>>>(&self, rhs: L) -> Ordering { |
||||
compare_ascii_lc(self.as_raw(), rhs.into().as_raw()) |
||||
} |
||||
} |
||||
|
||||
fn compare_ascii_lc(lhs: &[u8], rhs: &[u8]) -> Ordering { |
||||
use std::ascii::AsciiExt; |
||||
use std::cmp::min; |
||||
let l = min(lhs.len(), rhs.len()); |
||||
for i in 0..l { |
||||
let a : u8 = AsciiExt::to_ascii_lowercase(&lhs[i]); |
||||
let b : u8 = AsciiExt::to_ascii_lowercase(&rhs[i]); |
||||
match Ord::cmp(&a, &b) { |
||||
Ordering::Equal => continue, |
||||
c => return c, |
||||
} |
||||
} |
||||
Ord::cmp(&lhs.len(), &rhs.len()) |
||||
} |
||||
|
||||
impl<'a> From<&'a DnsLabel> for DnsLabelRef<'a> { |
||||
fn from(label: &'a DnsLabel) -> Self { |
||||
label.as_ref() |
||||
} |
||||
} |
||||
|
||||
impl<'a, 'b> PartialEq<DnsLabelRef<'a>> for DnsLabelRef<'b> { |
||||
fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool { |
||||
use std::ascii::AsciiExt; |
||||
AsciiExt::eq_ignore_ascii_case(self.as_raw(), rhs.as_raw()) |
||||
} |
||||
} |
||||
|
||||
impl<'a> PartialEq<DnsLabel> for DnsLabelRef<'a> { |
||||
#[inline] |
||||
fn eq(&self, rhs: &DnsLabel) -> bool { |
||||
self.eq(&rhs.as_ref()) |
||||
} |
||||
} |
||||
|
||||
impl<'a> Eq for DnsLabelRef<'a>{} |
||||
|
||||
impl<'a, 'b> PartialOrd<DnsLabelRef<'a>> for DnsLabelRef<'b> { |
||||
#[inline] |
||||
fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option<Ordering> { |
||||
Some(self.compare(*rhs)) |
||||
} |
||||
} |
||||
|
||||
impl<'a> PartialOrd<DnsLabel> for DnsLabelRef<'a> { |
||||
#[inline] |
||||
fn partial_cmp(&self, rhs: &DnsLabel) -> Option<Ordering> { |
||||
Some(self.compare(rhs)) |
||||
} |
||||
} |
||||
|
||||
impl<'a> Ord for DnsLabelRef<'a> { |
||||
#[inline] |
||||
fn cmp(&self, rhs: &Self) -> Ordering { |
||||
self.compare(*rhs) |
||||
} |
||||
} |
||||
|
||||
impl<'a> fmt::Debug for DnsLabelRef<'a> { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
// just escape the display version
|
||||
format!("{}", self).fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl<'a> fmt::Display for DnsLabelRef<'a> { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
use std::str; |
||||
let mut done = 0; |
||||
for pos in 0..self.label.len() { |
||||
let c = self.label[pos]; |
||||
if c <= 0x21 || c >= 0x7e || b'.' == c || b'\\' == c { |
||||
// flush
|
||||
if done < pos { |
||||
w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..pos])})?; |
||||
} |
||||
match c { |
||||
b'.' => w.write_str(r#"\."#)?, |
||||
b'\\' => w.write_str(r#"\\"#)?, |
||||
_ => write!(w, r"\{:03o}", c)?, |
||||
} |
||||
done = pos + 1; |
||||
} |
||||
} |
||||
// final flush
|
||||
if done < self.label.len() { |
||||
w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..])})?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,509 @@
|
||||
#![deny(missing_docs)] |
||||
//! Various structs to represents DNS names and labels
|
||||
|
||||
use bytes::Bytes; |
||||
use errors::*; |
||||
use ser::DnsPacketData; |
||||
use smallvec::SmallVec; |
||||
use std::fmt; |
||||
use std::io::Cursor; |
||||
use std::ops::{Deref, DerefMut}; |
||||
|
||||
pub use self::display::*; |
||||
pub use self::label::*; |
||||
|
||||
mod display; |
||||
mod label; |
||||
mod name_mutations; |
||||
mod name_parser; |
||||
|
||||
#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] |
||||
enum LabelOffset { |
||||
LabelStart(u8), |
||||
PacketStart(u8), |
||||
} |
||||
|
||||
// the heap meta data is usually at least 2*usize big; assuming 64-bit
|
||||
// platforms it should be ok to use 16 bytes in the smallvec.
|
||||
#[derive(Clone,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] |
||||
enum LabelOffsets { |
||||
Uncompressed(SmallVec<[u8;16]>), |
||||
Compressed(usize, SmallVec<[LabelOffset;8]>), |
||||
} |
||||
|
||||
impl LabelOffsets { |
||||
fn len(&self) -> u8 { |
||||
let l = match *self { |
||||
LabelOffsets::Uncompressed(ref offs) => offs.len(), |
||||
LabelOffsets::Compressed(_, ref offs) => offs.len(), |
||||
}; |
||||
debug_assert!(l < 128); |
||||
l as u8 |
||||
} |
||||
|
||||
fn label_pos(&self, ndx: u8) -> usize { |
||||
debug_assert!(ndx < 127); |
||||
match *self { |
||||
LabelOffsets::Uncompressed(ref offs) => offs[ndx as usize] as usize, |
||||
LabelOffsets::Compressed(start, ref offs) => match offs[ndx as usize] { |
||||
LabelOffset::LabelStart(o) => start + (o as usize), |
||||
LabelOffset::PacketStart(o) => o as usize, |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn is_compressed(&self) -> bool { |
||||
match *self { |
||||
LabelOffsets::Uncompressed(_) => false, |
||||
LabelOffsets::Compressed(_, _) => true, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// A DNS name
|
||||
///
|
||||
/// Uses the "original" raw representation for storage (i.e. can share
|
||||
/// memory with a parsed packet)
|
||||
#[derive(Clone,Hash)] |
||||
pub struct DnsName { |
||||
// in uncompressed form always includes terminating null octect;
|
||||
// but even in uncompressed form can include unused bytes at the
|
||||
// beginning
|
||||
//
|
||||
// may be empty for the root name (".", no labels)
|
||||
data: Bytes, |
||||
// either uncompressed or compressed offsets
|
||||
label_offsets: LabelOffsets, |
||||
// length of encoded form
|
||||
total_len: u8, |
||||
} |
||||
|
||||
impl DnsName { |
||||
/// Create new name representing the DNS root (".")
|
||||
pub fn new_root() -> Self { |
||||
DnsName{ |
||||
data: Bytes::new(), |
||||
label_offsets: LabelOffsets::Uncompressed(SmallVec::new()), |
||||
total_len: 1, |
||||
} |
||||
} |
||||
|
||||
/// Create new name representing the DNS root (".") and pre-allocate
|
||||
/// storage
|
||||
pub fn with_capacity(labels: u8, total_len: u8) -> Self { |
||||
DnsName{ |
||||
data: Bytes::with_capacity(total_len as usize), |
||||
label_offsets: LabelOffsets::Uncompressed(SmallVec::with_capacity(labels as usize)), |
||||
total_len: 1, |
||||
} |
||||
} |
||||
|
||||
/// Returns whether name represents the DNS root (".")
|
||||
pub fn is_root(&self) -> bool { |
||||
0 == self.label_count() |
||||
} |
||||
|
||||
/// How many labels the name has (without the trailing empty label,
|
||||
/// at most 127)
|
||||
pub fn label_count(&self) -> u8 { |
||||
self.label_offsets.len() |
||||
} |
||||
|
||||
/// Iterator over the labels (in the order they are stored in memory,
|
||||
/// i.e. top-level name last).
|
||||
pub fn labels<'a>(&'a self) -> DnsNameIterator<'a> { |
||||
DnsNameIterator{ |
||||
name: &self, |
||||
front_label: 0, |
||||
back_label: self.label_offsets.len(), |
||||
} |
||||
} |
||||
|
||||
/// Return label at index `ndx`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// panics if `ndx >= self.label_count()`.
|
||||
pub fn label_ref<'a>(&'a self, ndx: u8) -> DnsLabelRef<'a> { |
||||
let pos = self.label_offsets.label_pos(ndx); |
||||
let label_len = self.data[pos]; |
||||
debug_assert!(label_len < 64); |
||||
let end = pos + 1 + label_len as usize; |
||||
DnsLabelRef{label: &self.data[pos + 1..end]} |
||||
} |
||||
|
||||
/// Return label at index `ndx`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// panics if `ndx >= self.label_count()`.
|
||||
pub fn label(&self, ndx: u8) -> DnsLabel { |
||||
let pos = self.label_offsets.label_pos(ndx); |
||||
let label_len = self.data[pos]; |
||||
debug_assert!(label_len < 64); |
||||
let end = pos + 1 + label_len as usize; |
||||
DnsLabel{label: self.data.slice(pos + 1, end) } |
||||
} |
||||
} |
||||
|
||||
impl<'a> IntoIterator for &'a DnsName { |
||||
type Item = DnsLabelRef<'a>; |
||||
type IntoIter = DnsNameIterator<'a>; |
||||
|
||||
fn into_iter(self) -> Self::IntoIter { |
||||
self.labels() |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<DnsName> for DnsName { |
||||
fn eq(&self, rhs: &DnsName) -> bool { |
||||
let a_labels = self.labels(); |
||||
let b_labels = rhs.labels(); |
||||
if a_labels.len() != b_labels.len() { return false; } |
||||
a_labels.zip(b_labels).all(|(a,b)| a == b) |
||||
} |
||||
} |
||||
|
||||
impl Eq for DnsName{} |
||||
|
||||
impl fmt::Debug for DnsName { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
DisplayLabels{ |
||||
labels: self, |
||||
options: Default::default(), |
||||
}.fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for DnsName { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
DisplayLabels{ |
||||
labels: self, |
||||
options: Default::default(), |
||||
}.fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl DnsPacketData for DnsName { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
DnsName::parse_name(data, false) |
||||
} |
||||
} |
||||
|
||||
/// Similar to `DnsName`, but allows using compressed labels in the
|
||||
/// serialized form
|
||||
#[derive(Clone)] |
||||
pub struct DnsCompressedName(pub DnsName); |
||||
|
||||
impl Deref for DnsCompressedName { |
||||
type Target = DnsName; |
||||
|
||||
fn deref(&self) -> &Self::Target { |
||||
&self.0 |
||||
} |
||||
} |
||||
|
||||
impl DerefMut for DnsCompressedName { |
||||
fn deref_mut(&mut self) -> &mut Self::Target { |
||||
&mut self.0 |
||||
} |
||||
} |
||||
|
||||
impl<'a> IntoIterator for &'a DnsCompressedName { |
||||
type Item = DnsLabelRef<'a>; |
||||
type IntoIter = DnsNameIterator<'a>; |
||||
|
||||
fn into_iter(self) -> Self::IntoIter { |
||||
self.labels() |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<DnsCompressedName> for DnsCompressedName { |
||||
fn eq(&self, rhs: &DnsCompressedName) -> bool { |
||||
self.0 == rhs.0 |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<DnsName> for DnsCompressedName { |
||||
fn eq(&self, rhs: &DnsName) -> bool { |
||||
&self.0 == rhs |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<DnsCompressedName> for DnsName { |
||||
fn eq(&self, rhs: &DnsCompressedName) -> bool { |
||||
self == &rhs.0 |
||||
} |
||||
} |
||||
|
||||
impl Eq for DnsCompressedName{} |
||||
|
||||
impl fmt::Debug for DnsCompressedName { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
self.0.fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for DnsCompressedName { |
||||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { |
||||
self.0.fmt(w) |
||||
} |
||||
} |
||||
|
||||
impl DnsPacketData for DnsCompressedName { |
||||
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> { |
||||
Ok(DnsCompressedName(DnsName::parse_name(data, true)?)) |
||||
} |
||||
} |
||||
|
||||
/// Iterator type for [`DnsName::labels`]
|
||||
///
|
||||
/// [`DnsName::labels`]: struct.DnsName.html#method.labels
|
||||
#[derive(Clone)] |
||||
pub struct DnsNameIterator<'a> { |
||||
name: &'a DnsName, |
||||
front_label: u8, |
||||
back_label: u8, |
||||
} |
||||
|
||||
impl<'a> Iterator for DnsNameIterator<'a> { |
||||
type Item = DnsLabelRef<'a>; |
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
if self.front_label >= self.back_label { return None } |
||||
let label = self.name.label_ref(self.front_label); |
||||
self.front_label += 1; |
||||
Some(label) |
||||
} |
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) { |
||||
let count = self.len(); |
||||
(count, Some(count)) |
||||
} |
||||
|
||||
fn count(self) -> usize { |
||||
self.len() |
||||
} |
||||
} |
||||
|
||||
impl<'a> ExactSizeIterator for DnsNameIterator<'a> { |
||||
fn len(&self) -> usize { |
||||
(self.back_label - self.front_label) as usize |
||||
} |
||||
} |
||||
|
||||
impl<'a> DoubleEndedIterator for DnsNameIterator<'a> { |
||||
fn next_back(&mut self) -> Option<Self::Item> { |
||||
if self.front_label >= self.back_label { return None } |
||||
self.back_label -= 1; |
||||
let label = self.name.label_ref(self.back_label); |
||||
Some(label) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use ser::packet; |
||||
use super::*; |
||||
|
||||
/* |
||||
fn deserialize(bytes: &'static [u8]) -> Result<DnsName> { |
||||
let result = packet::deserialize::<DnsName>(Bytes::from_static(bytes))?; |
||||
{ |
||||
let check_result = packet::deserialize::<DnsName>(result.clone().encode()).unwrap(); |
||||
assert_eq!(check_result, result); |
||||
} |
||||
Ok(result) |
||||
} |
||||
*/ |
||||
|
||||
fn de_uncompressed(bytes: &'static [u8]) -> Result<DnsName> { |
||||
let result = packet::deserialize::<DnsName>(Bytes::from_static(bytes))?; |
||||
assert_eq!(bytes, result.clone().encode()); |
||||
Ok(result) |
||||
} |
||||
|
||||
fn check_uncompressed_display(bytes: &'static [u8], txt: &str, label_count: u8) { |
||||
let name = de_uncompressed(bytes).unwrap(); |
||||
assert_eq!( |
||||
name.labels().count(), |
||||
label_count as usize |
||||
); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
txt |
||||
); |
||||
} |
||||
|
||||
fn check_uncompressed_debug(bytes: &'static [u8], txt: &str) { |
||||
let name = de_uncompressed(bytes).unwrap(); |
||||
assert_eq!( |
||||
format!("{:?}", name), |
||||
txt |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn parse_and_display_name() { |
||||
check_uncompressed_display( |
||||
b"\x07example\x03com\x00", |
||||
"example.com.", |
||||
2, |
||||
); |
||||
check_uncompressed_display( |
||||
b"\x07e!am.l\\\x03com\x00", |
||||
"e\\041am\\.l\\\\.com.", |
||||
2, |
||||
); |
||||
check_uncompressed_debug( |
||||
b"\x07e!am.l\\\x03com\x00", |
||||
r#""e\\041am\\.l\\\\.com.""#, |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn parse_and_reverse_name() { |
||||
let name = de_uncompressed(b"\x03www\x07example\x03com\x00").unwrap(); |
||||
assert_eq!( |
||||
format!( |
||||
"{}", |
||||
DisplayLabels{ |
||||
labels: name.labels().rev(), |
||||
options: DisplayLabelsOptions{ |
||||
separator: " ", |
||||
trailing: false, |
||||
}, |
||||
} |
||||
), |
||||
"com example www" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn modifications() { |
||||
let mut name = de_uncompressed(b"\x07example\x03com\x00").unwrap(); |
||||
|
||||
name.push_front(DnsLabelRef::new(b"www").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"www.example.com." |
||||
); |
||||
|
||||
name.push_back(DnsLabelRef::new(b"org").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"www.example.com.org." |
||||
); |
||||
|
||||
name.pop_front(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"example.com.org." |
||||
); |
||||
|
||||
name.push_front(DnsLabelRef::new(b"mx").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"mx.example.com.org." |
||||
); |
||||
// the "mx" label should fit into the place "www" used before,
|
||||
// make sure the buffer was reused and the name not moved within
|
||||
assert_eq!(1, name.label_offsets.label_pos(0)); |
||||
|
||||
name.push_back(DnsLabelRef::new(b"com").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"mx.example.com.org.com." |
||||
); |
||||
} |
||||
|
||||
|
||||
|
||||
fn de_compressed(bytes: &'static [u8], offset: usize) -> Result<DnsCompressedName> { |
||||
use bytes::Buf; |
||||
|
||||
let mut c = Cursor::new(Bytes::from_static(bytes)); |
||||
c.set_position(offset as u64); |
||||
let result = DnsPacketData::deserialize(&mut c)?; |
||||
if c.remaining() != 0 { |
||||
bail!("data remaining: {}", c.remaining()) |
||||
} |
||||
Ok(result) |
||||
} |
||||
|
||||
fn check_compressed_display(bytes: &'static [u8], offset: usize, txt: &str, label_count: u8) { |
||||
let name = de_compressed(bytes, offset).unwrap(); |
||||
assert_eq!( |
||||
name.labels().count(), |
||||
label_count as usize |
||||
); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
txt |
||||
); |
||||
} |
||||
|
||||
fn check_compressed_debug(bytes: &'static [u8], offset: usize, txt: &str) { |
||||
let name = de_compressed(bytes, offset).unwrap(); |
||||
assert_eq!( |
||||
format!("{:?}", name), |
||||
txt |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn parse_and_display_compressed_name() { |
||||
check_compressed_display( |
||||
b"\x03com\x00\x07example\xc0", 5, |
||||
"example.com.", |
||||
2, |
||||
); |
||||
check_compressed_display( |
||||
b"\x03com\x00\x07e!am.l\\\xc0", 5, |
||||
"e\\041am\\.l\\\\.com.", |
||||
2, |
||||
); |
||||
check_compressed_debug( |
||||
b"\x03com\x00\x07e!am.l\\\xc0", 5, |
||||
r#""e\\041am\\.l\\\\.com.""#, |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn modifications_compressed() { |
||||
let mut name = de_compressed(b"\x03com\x00\x07example\xc0", 5).unwrap(); |
||||
|
||||
name.push_front(DnsLabelRef::new(b"www").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"www.example.com." |
||||
); |
||||
|
||||
name.push_back(DnsLabelRef::new(b"org").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"www.example.com.org." |
||||
); |
||||
|
||||
name.pop_front(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"example.com.org." |
||||
); |
||||
|
||||
name.push_front(DnsLabelRef::new(b"mx").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"mx.example.com.org." |
||||
); |
||||
// the "mx" label should fit into the place "www" used before,
|
||||
// make sure the buffer was reused and the name not moved within
|
||||
assert_eq!(1, name.label_offsets.label_pos(0)); |
||||
|
||||
name.push_back(DnsLabelRef::new(b"com").unwrap()).unwrap(); |
||||
assert_eq!( |
||||
format!("{}", name), |
||||
"mx.example.com.org.com." |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,265 @@
|
||||
use bytes::{BytesMut,BufMut}; |
||||
use super::*; |
||||
|
||||
impl DnsName { |
||||
/// Remove the front label
|
||||
///
|
||||
/// Returns `false` if the name was the root (".")
|
||||
pub fn try_pop_front(&mut self) -> bool { |
||||
match self.label_offsets { |
||||
LabelOffsets::Uncompressed(ref mut offs) => { |
||||
if offs.is_empty() { return false; } |
||||
self.total_len -= self.data[offs[0] as usize] + 1; |
||||
offs.remove(0); |
||||
}, |
||||
LabelOffsets::Compressed(ref mut start_pos, ref mut offs) => { |
||||
if offs.is_empty() { return false; } |
||||
match offs[0] { |
||||
LabelOffset::LabelStart(o) => { |
||||
let label_space = self.data[*start_pos + o as usize] + 1; |
||||
self.total_len -= label_space; |
||||
*start_pos += label_space as usize; |
||||
}, |
||||
LabelOffset::PacketStart(o) => { |
||||
self.total_len -= self.data[o as usize] + 1; |
||||
}, |
||||
} |
||||
offs.remove(0); |
||||
}, |
||||
} |
||||
true |
||||
} |
||||
|
||||
/// Remove the front label
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the name was the root (".")
|
||||
pub fn pop_front(&mut self) { |
||||
if !self.try_pop_front() { panic!("Cannot pop label from root name") } |
||||
} |
||||
|
||||
/// Insert a new label at the front
|
||||
///
|
||||
/// Returns an error if the resulting name would be too long
|
||||
pub fn push_front<'a, L: Into<DnsLabelRef<'a>>>(&mut self, label: L) -> Result<()> { |
||||
let label = label.into(); |
||||
if label.len() > 254 - self.total_len { bail!("Cannot append label, resulting name too long") } |
||||
let (mut data, start) = self.reserve(label.len() as usize + 1, 0); |
||||
|
||||
let new_label_pos = start - (label.len() + 1) as usize; |
||||
data[new_label_pos] = label.len(); |
||||
data[new_label_pos+1..new_label_pos+1+label.len() as usize].copy_from_slice(label.as_raw()); |
||||
self.data = data.freeze(); |
||||
self.total_len += label.len() + 1; |
||||
match self.label_offsets { |
||||
LabelOffsets::Uncompressed(ref mut offs) => { |
||||
offs.insert(0, new_label_pos as u8); |
||||
}, |
||||
LabelOffsets::Compressed(_, _) => unreachable!(), |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Remove the back label
|
||||
///
|
||||
/// Returns `false` if the name was the root (".")
|
||||
pub fn try_pop_back(&mut self) -> bool { |
||||
match self.label_offsets { |
||||
LabelOffsets::Uncompressed(ref mut offs) => { |
||||
if offs.is_empty() { return false; } |
||||
self.total_len -= self.data[offs[offs.len()-1] as usize] + 1; |
||||
offs.pop(); |
||||
}, |
||||
LabelOffsets::Compressed(ref mut start_pos, ref mut offs) => { |
||||
if offs.is_empty() { return false; } |
||||
match offs[offs.len()-1] { |
||||
LabelOffset::LabelStart(o) => { |
||||
self.total_len -= self.data[*start_pos + o as usize] + 1; |
||||
}, |
||||
LabelOffset::PacketStart(o) => { |
||||
self.total_len -= self.data[o as usize] + 1; |
||||
}, |
||||
} |
||||
offs.pop(); |
||||
}, |
||||
} |
||||
true |
||||
} |
||||
|
||||
/// Remove the back label
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the name was the root (".")
|
||||
pub fn pop_back(&mut self) { |
||||
if !self.try_pop_back() { panic!("Cannot pop label from root name") } |
||||
} |
||||
|
||||
/// Insert a new label at the back
|
||||
///
|
||||
/// Returns an error if the resulting name would be too long
|
||||
pub fn push_back<'a, L: Into<DnsLabelRef<'a>>>(&mut self, label: L) -> Result<()> { |
||||
let label = label.into(); |
||||
if label.len() > 254 - self.total_len { bail!("Cannot append label, resulting name too long") } |
||||
let (mut data, start) = self.reserve(0, label.len() as usize + 1); |
||||
|
||||
let new_label_pos = start + self.total_len as usize - 1; |
||||
data[new_label_pos] = label.len(); |
||||
data[new_label_pos+1..new_label_pos+1+label.len() as usize].copy_from_slice(label.as_raw()); |
||||
data[new_label_pos+1+label.len() as usize] = 0; |
||||
self.data = data.freeze(); |
||||
self.total_len += label.len() + 1; |
||||
match self.label_offsets { |
||||
LabelOffsets::Uncompressed(ref mut offs) => { |
||||
offs.push(new_label_pos as u8); |
||||
}, |
||||
LabelOffsets::Compressed(_, _) => unreachable!(), |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
// returns mutable buffer and position of the start of the current
|
||||
// name (null terminated).
|
||||
//
|
||||
// adjusts self.label_offsets accordingly
|
||||
fn reserve(&mut self, prefix: usize, suffix: usize) -> (BytesMut, usize) { |
||||
use std::ptr; |
||||
|
||||
let new_len = self.total_len as usize + prefix + suffix; |
||||
assert!(new_len < 256); |
||||
self.uncompress(prefix, suffix); |
||||
let label_offsets = match self.label_offsets { |
||||
LabelOffsets::Uncompressed(ref mut offs) => offs, |
||||
LabelOffsets::Compressed(_, _) => unreachable!(), |
||||
}; |
||||
// steal buffer from self (so it has a change to be owned)
|
||||
let data = self.data.split_off(0).try_mut(); |
||||
|
||||
if label_offsets.is_empty() { |
||||
// root name
|
||||
let mut data = data.unwrap_or_else(|_| BytesMut::with_capacity(new_len)); |
||||
data.put_u8(0); |
||||
return (data, 0) |
||||
} |
||||
|
||||
let old_start = label_offsets[0] as usize; |
||||
// if current "prefix" space (old_start) is bigger than
|
||||
// requested but fits, just increase the prefix
|
||||
let (prefix, new_len) = if old_start > prefix && self.total_len as usize + old_start + suffix < 256 { |
||||
(old_start, self.total_len as usize + old_start + suffix) |
||||
} else { |
||||
(prefix, new_len) |
||||
}; |
||||
|
||||
// check if we need to reallocate
|
||||
let data = match data { |
||||
Ok(data) => { |
||||
if data.capacity() < new_len { |
||||
// need a new allocation anyway, pretend we couldn't
|
||||
// own the buffer
|
||||
Err(data.freeze()) |
||||
} else { |
||||
Ok(data) |
||||
} |
||||
}, |
||||
Err(data) => Err(data), |
||||
}; |
||||
|
||||
|
||||
match data { |
||||
Ok(mut data) => { |
||||
if data.len() < new_len { |
||||
let add = new_len - data.len(); |
||||
data.reserve(add); |
||||
unsafe { data.set_len(new_len); } |
||||
} |
||||
if old_start < prefix { |
||||
// need more space in front, move back
|
||||
let p_old = &data[old_start as usize] as *const u8; |
||||
let p_new = &mut data[prefix] as *mut u8; |
||||
unsafe { |
||||
ptr::copy(p_old, p_new, self.total_len as usize); |
||||
} |
||||
// adjust labels
|
||||
for o in label_offsets.iter_mut() { |
||||
*o = *o + (prefix - old_start) as u8; |
||||
} |
||||