init
This commit is contained in:
commit
63cd9196f9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target/
|
||||
**/*.rs.bk
|
217
Cargo.lock
generated
Normal file
217
Cargo.lock
generated
Normal file
@ -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"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -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]
|
13
lib/dnsbox-base/Cargo.toml
Normal file
13
lib/dnsbox-base/Cargo.toml
Normal file
@ -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"
|
90
lib/dnsbox-base/src/common_types/binary/mod.rs
Normal file
90
lib/dnsbox-base/src/common_types/binary/mod.rs
Normal file
@ -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))
|
||||
}
|
||||
}
|
9
lib/dnsbox-base/src/common_types/mod.rs
Normal file
9
lib/dnsbox-base/src/common_types/mod.rs
Normal file
@ -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;
|
79
lib/dnsbox-base/src/common_types/name/display.rs
Normal file
79
lib/dnsbox-base/src/common_types/name/display.rs
Normal file
@ -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(())
|
||||
}
|
||||
}
|
240
lib/dnsbox-base/src/common_types/name/label.rs
Normal file
240
lib/dnsbox-base/src/common_types/name/label.rs
Normal file
@ -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(())
|
||||
}
|
||||
}
|
509
lib/dnsbox-base/src/common_types/name/mod.rs
Normal file
509
lib/dnsbox-base/src/common_types/name/mod.rs
Normal file
@ -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."
|
||||
);
|
||||
}
|
||||
}
|
265
lib/dnsbox-base/src/common_types/name/name_mutations.rs
Normal file
265
lib/dnsbox-base/src/common_types/name/name_mutations.rs
Normal file
@ -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;
|
||||
}
|
||||
return (data, prefix);
|
||||
} else if old_start > prefix {
|
||||
// too much space in front, (suffix didn't fit into
|
||||
// length restriction, see check above), move to
|
||||
// front
|
||||
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 - (old_start - prefix) as u8;
|
||||
}
|
||||
return (data, prefix);
|
||||
} else {
|
||||
return (data, old_start);
|
||||
}
|
||||
},
|
||||
Err(data) => {
|
||||
let mut new_data = BytesMut::with_capacity(new_len);
|
||||
unsafe { new_data.set_len(new_len); }
|
||||
// copy old data
|
||||
new_data[prefix..prefix + self.total_len as usize].copy_from_slice(&data[old_start..old_start+self.total_len as usize]);
|
||||
// adjust labels
|
||||
for o in label_offsets.iter_mut() {
|
||||
*o = (*o - old_start as u8) + prefix as u8;
|
||||
}
|
||||
return (new_data, prefix);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// return encoded (uncompressed) representation
|
||||
///
|
||||
/// might uncompress the inner representation.
|
||||
pub fn encode(&mut self) -> Bytes {
|
||||
self.uncompress(0, 0);
|
||||
if self.is_root() {
|
||||
return Bytes::from_static(b"\x00");
|
||||
}
|
||||
// at least one label, find first one
|
||||
let from = self.label_offsets.label_pos(0);
|
||||
self.data.slice(from, from + self.total_len as usize)
|
||||
}
|
||||
|
||||
fn uncompress(&mut self, prefix_capacity: usize, suffix_capacity: usize) {
|
||||
if self.label_offsets.is_compressed() {
|
||||
let name = {
|
||||
let labels = self.labels();
|
||||
let label_count = labels.len();
|
||||
let new_len = self.total_len as usize + prefix_capacity + suffix_capacity;
|
||||
assert!(new_len < 256);
|
||||
let mut data = BytesMut::with_capacity(new_len);
|
||||
let mut offsets = SmallVec::<[u8;16]>::with_capacity(label_count);
|
||||
unsafe { data.set_len(prefix_capacity) }
|
||||
let mut pos = prefix_capacity as u8;
|
||||
|
||||
for label in labels {
|
||||
offsets.push(pos);
|
||||
pos += label.len() + 1;
|
||||
data.put_u8(label.len());
|
||||
data.put_slice(label.as_raw());
|
||||
}
|
||||
data.put_u8(0);
|
||||
|
||||
DnsName{
|
||||
data: data.freeze(),
|
||||
label_offsets: LabelOffsets::Uncompressed(offsets),
|
||||
total_len: self.total_len,
|
||||
}
|
||||
};
|
||||
*self = name
|
||||
}
|
||||
}
|
||||