use core::fmt::{self, Formatter, Write};
use crate::core::r#const::{CLASS, OPCODE, RCODE, TYPE};
use crate::core::{Class, Opcode, Rcode, Serial, Ttl, Type};
use crate::rdata::view::{
Aaaa, Caa, CaaTag, CaaValue, Cname, Malformed, Mx, Ns, Ptr, Soa, Txt, Unknown, A,
};
use crate::view::{
BorrowedView, CharacterString, Extension, Header, Label, Message, Name, Question, Record,
};
pub trait Format {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result;
}
impl fmt::Write for Wrapper<'_, '_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
(self.f)(self.i, &mut self.p, s)
}
}
pub type Writer = dyn Fn(&mut dyn fmt::Write, &mut Position, &str) -> fmt::Result;
fn plain(w: &mut dyn fmt::Write, _: &mut Position, s: &str) -> fmt::Result {
w.write_str(s)
}
pub struct Wrapper<'i, 'f> {
i: &'i mut dyn fmt::Write,
p: Position,
f: &'f Writer,
}
impl<'i, 'f> Wrapper<'i, 'f> {
pub fn plain(i: &'i mut dyn fmt::Write) -> Self {
Self::with_writer(i, &plain)
}
pub fn with_writer(i: &'i mut dyn fmt::Write, f: &'f Writer) -> Self {
Self {
i,
p: Position::default(),
f,
}
}
}
use self::HeaderPosition::*;
use self::MainPosition::*;
use self::QuestionPosition::*;
use self::RecordPosition::*;
use self::SectionPosition::*;
#[derive(Debug, Clone, Default)]
pub struct Position {
pub main: Option<MainPosition>,
pub header: Option<HeaderPosition>,
pub section: Option<SectionPosition>,
pub question: Option<QuestionPosition>,
pub record: Option<RecordPosition>,
}
struct ScopedPosition<'w, 'i, 'f> {
w: &'w mut Wrapper<'i, 'f>,
old_position: Position,
}
impl<'w, 'i, 'f> From<&'w mut Wrapper<'i, 'f>> for ScopedPosition<'w, 'i, 'f> {
fn from(w: &'w mut Wrapper<'i, 'f>) -> Self {
let old_position = w.p.clone();
Self { w, old_position }
}
}
impl Drop for ScopedPosition<'_, '_, '_> {
fn drop(&mut self) {
self.w.p = self.old_position.clone();
}
}
impl ScopedPosition<'_, '_, '_> {
pub fn main(&mut self, m: impl Into<Option<MainPosition>>) -> &mut Self {
self.w.p.main = m.into();
self
}
pub fn header(&mut self, h: impl Into<Option<HeaderPosition>>) -> &mut Self {
self.w.p.header = h.into();
self
}
pub fn section(&mut self, s: impl Into<Option<SectionPosition>>) -> &mut Self {
self.w.p.section = s.into();
self
}
pub fn question(&mut self, q: impl Into<Option<QuestionPosition>>) -> &mut Self {
self.w.p.question = q.into();
self
}
pub fn record(&mut self, r: impl Into<Option<RecordPosition>>) -> &mut Self {
self.w.p.record = r.into();
self
}
}
#[derive(Debug, Clone)]
pub enum MainPosition {
Header,
EdnsHeader,
Qd,
An,
Ns,
Ar,
}
#[derive(Debug, Clone)]
pub enum HeaderPosition {
Rcode,
Id,
Opcode,
Qdcount,
Ancount,
Nscount,
Arcount,
Flag,
}
#[derive(Debug, Clone)]
pub enum SectionPosition {
Heading,
QuestionLine,
RecordLine,
EdnsPlaceholder,
}
#[derive(Debug, Clone)]
pub enum QuestionPosition {
Qname,
Qclass,
Qtype,
}
#[derive(Debug, Clone)]
pub enum RecordPosition {
Name,
Ttl,
Class,
Type,
Rdata,
}
pub struct Plain<'i>(pub &'i dyn Format);
impl core::fmt::Debug for Plain<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut w = Wrapper::plain(f);
Format::fmt(self.0, &mut w)
}
}
impl core::fmt::Display for Plain<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut w = Wrapper::plain(f);
Format::fmt(self.0, &mut w)
}
}
pub struct Pretty<'i, 'f>(pub &'i dyn Format, pub &'f Writer);
impl core::fmt::Debug for Pretty<'_, '_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut w = Wrapper::with_writer(f, self.1);
Format::fmt(self.0, &mut w)
}
}
impl core::fmt::Display for Pretty<'_, '_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut w = Wrapper::with_writer(f, self.1);
Format::fmt(self.0, &mut w)
}
}
impl Format for Message<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let mut p = ScopedPosition::from(w);
p.main(Header);
write!(p.header(None).w, ";; ")?;
self.rcode().fmt(p.header(Rcode).w)?;
write!(p.header(None).w, " ")?;
self.header().fmt(p.w)?;
writeln!(p.w)?;
let opt = self.opt();
if let Some(extension) = opt {
write!(p.main(EdnsHeader).w, ";; ")?;
extension.fmt(p.w)?;
writeln!(p.main(None).w)?;
}
writeln!(p.main(Qd).w)?;
write!(p.section(Heading).w, ";; question section")?;
writeln!(p.section(None).w)?;
for question in self.qd() {
question.fmt(p.section(QuestionLine).w)?;
writeln!(p.section(None).w)?;
}
writeln!(p.main(An).w)?;
write!(p.section(Heading).w, ";; answer section")?;
writeln!(p.section(None).w)?;
for record in self.an() {
record.fmt(p.section(RecordLine).w)?;
writeln!(p.section(None).w)?;
}
writeln!(p.main(Ns).w)?;
write!(p.section(Heading).w, ";; authority section")?;
writeln!(p.section(None).w)?;
for record in self.ns() {
record.fmt(p.section(RecordLine).w)?;
writeln!(p.section(None).w)?;
}
writeln!(p.main(Ar).w)?;
write!(p.section(Heading).w, ";; additional section")?;
writeln!(p.section(None).w)?;
for record in self.ar() {
if opt.is_some_and(|opt| opt.offset() == record.offset()) {
write!(p.section(EdnsPlaceholder).w, "; EDNS OPT RR was here")?;
} else {
record.fmt(p.section(RecordLine).w)?;
}
writeln!(p.section(None).w)?;
}
Ok(())
}
}
impl Format for Header<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let mut p = ScopedPosition::from(w);
p.main(Header);
write!(p.header(Id).w, "#{}", self.id())?;
write!(p.header(None).w, " ")?;
self.opcode().fmt(p.header(Opcode).w)?;
write!(p.header(None).w, " ")?;
write!(p.header(Qdcount).w, "{}", self.qdcount())?;
write!(p.header(None).w, " ")?;
write!(p.header(Ancount).w, "{}", self.ancount())?;
write!(p.header(None).w, " ")?;
write!(p.header(Nscount).w, "{}", self.nscount())?;
write!(p.header(None).w, " ")?;
write!(p.header(Arcount).w, "{}", self.arcount())?;
write!(p.header(None).w, " flags")?;
if self.qr() {
write!(p.header(None).w, " ")?;
write!(p.header(Flag).w, "qr")?;
}
if self.aa() {
write!(p.header(None).w, " ")?;
write!(p.header(Flag).w, "aa")?;
}
if self.tc() {
write!(p.header(None).w, " ")?;
write!(p.header(Flag).w, "tc")?;
}
if self.rd() {
write!(p.header(None).w, " ")?;
write!(p.header(Flag).w, "rd")?;
}
if self.ra() {
write!(p.header(None).w, " ")?;
write!(p.header(Flag).w, "ra")?;
}
Ok(())
}
}
impl Format for Extension<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, "EDNS({}) UDP {} flags", self.version(), self.udp())?;
if self.r#do() {
write!(w, " do")?;
}
Ok(())
}
}
impl Format for Question<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let mut p = ScopedPosition::from(w);
self.qname().fmt(p.question(Qname).w)?;
write!(p.question(None).w, " ")?;
self.qclass().fmt(p.question(Qclass).w)?;
write!(p.question(None).w, " ")?;
self.qtype().fmt(p.question(Qtype).w)?;
Ok(())
}
}
impl Format for Record<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let mut p = ScopedPosition::from(w);
self.name().fmt(p.record(Name).w)?;
write!(p.record(None).w, " ")?;
write!(p.record(Ttl).w, "{}", self.ttl().value())?;
write!(p.record(None).w, " ")?;
self.class().fmt(p.record(RecordPosition::Class).w)?;
write!(p.record(None).w, " ")?;
self.r#type().fmt(p.record(RecordPosition::Type).w)?;
write!(p.record(None).w, " ")?;
let rdata = self.rdata();
let rdata: &dyn Format = rdata.as_ref();
rdata.fmt(p.record(Rdata).w)?;
Ok(())
}
}
impl Format for Name<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let mut first = true;
for label in self.labels_with_null() {
if !label.is_null() {
label.fmt(w)?;
}
if !label.is_null() || first {
write!(w, ".")?;
}
first = false;
}
Ok(())
}
}
impl Format for Label<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let value = self.value().expect("FIXME invalid label");
for &octet in value {
display_octet(w, octet, |x| match x {
b' ' | b';' => OctetStyle::Decimal,
b'-' | b'_' => OctetStyle::Identity,
x if x.is_ascii_alphanumeric() => OctetStyle::Identity,
x if x.is_ascii_graphic() => OctetStyle::PrefixBackslash,
_ => OctetStyle::Decimal,
})?;
}
Ok(())
}
}
impl Format for Opcode {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
if let Some(name) = OPCODE.to_name(self.value().into()) {
write!(w, "{}", name)
} else {
write!(w, "OPCODE?{}", self.value())
}
}
}
impl Format for Rcode {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
if let Some(name) = RCODE.to_name_opt(self.value()) {
write!(w, "{}", name)
} else {
write!(w, "RCODE?{}", self.value())
}
}
}
impl Format for Type {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
if let Some(name) = TYPE.to_name(self.value()) {
write!(w, "{}", name)
} else {
write!(w, "TYPE{}", self.value())
}
}
}
impl Format for Class {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
if let Some(name) = CLASS.to_name(self.value()) {
write!(w, "{}", name)
} else {
write!(w, "CLASS{}", self.value())
}
}
}
impl Format for Ttl {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, "{}", self.value())
}
}
impl Format for Serial {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, "{}", self.value())
}
}
impl Format for CharacterString<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, r#"""#)?;
for &octet in self.value() {
display_octet(w, octet, |x| match x {
b'\\' | b'"' => OctetStyle::Decimal,
b' ' => OctetStyle::Identity,
x if x.is_ascii_graphic() => OctetStyle::Identity,
_ => OctetStyle::Decimal,
})?;
}
write!(w, r#"""#)?;
Ok(())
}
}
impl Format for Ns<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
self.name().fmt(w)
}
}
impl Format for Cname<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
self.name().fmt(w)
}
}
impl Format for Soa<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
self.mname().fmt(w)?;
write!(w, " ")?;
self.rname().fmt(w)?;
write!(w, " ")?;
self.serial().fmt(w)?;
write!(w, " ")?;
self.refresh().fmt(w)?;
write!(w, " ")?;
self.retry().fmt(w)?;
write!(w, " ")?;
self.expire().fmt(w)?;
write!(w, " ")?;
self.minimum().fmt(w)?;
Ok(())
}
}
impl Format for Ptr<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
self.name().fmt(w)
}
}
impl Format for Mx<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, "{} ", self.preference())?;
self.exchange().fmt(w)?;
Ok(())
}
}
impl Format for Txt<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
for string in self.data() {
string.fmt(w)?;
write!(w, " ")?;
}
Ok(())
}
}
impl Format for Caa<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, "{} ", self.flags())?;
self.tag().fmt(w)?;
write!(w, " ")?;
self.value().fmt(w)?;
Ok(())
}
}
impl Format for CaaTag<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
for octet in self.as_bytes() {
display_octet(w, *octet, |x| {
if x.is_ascii_alphanumeric() {
OctetStyle::Identity
} else {
OctetStyle::Decimal
}
})?;
}
Ok(())
}
}
impl Format for CaaValue<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, r#"""#)?;
for octet in self.as_bytes() {
display_octet(w, *octet, |x| match x {
b'\\' | b'"' => OctetStyle::Decimal,
x if x.is_ascii_graphic() => OctetStyle::Identity,
_ => OctetStyle::Decimal,
})?;
}
write!(w, r#"""#)?;
Ok(())
}
}
impl Format for A<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(
w,
"{}.{}.{}.{}",
self.as_bytes()[0],
self.as_bytes()[1],
self.as_bytes()[2],
self.as_bytes()[3],
)
}
}
impl Format for Aaaa<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
let x = self.to_ipv6_addr().segments();
let zero = 0x100
| u16::from(x[0] != 0) << 0
| u16::from(x[1] != 0) << 1
| u16::from(x[2] != 0) << 2
| u16::from(x[3] != 0) << 3
| u16::from(x[4] != 0) << 4
| u16::from(x[5] != 0) << 5
| u16::from(x[6] != 0) << 6
| u16::from(x[7] != 0) << 7;
let (start, len) = (0..8)
.map(|i| (zero >> i).trailing_zeros())
.enumerate()
.filter(|(_, n)| *n > 1) .rev() .max_by_key(|(_, n)| *n) .unwrap_or((0, 0));
let len = usize::try_from(len).expect("len is always ≤ 8");
for i in 0..start {
write!(w, "{}{:x}", if i > 0 { ":" } else { "" }, x[i])?;
}
if len > 0 {
write!(w, "::")?;
}
for i in (start + len)..8 {
write!(w, "{:x}{}", x[i], if i < 7 { ":" } else { "" })?;
}
Ok(())
}
}
impl Format for Malformed<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
self.as_unknown().fmt(w)
}
}
impl Format for Unknown<'_> {
fn fmt(&self, w: &mut Wrapper) -> fmt::Result {
write!(w, "\\# {}", self.len())?;
for octet in self.as_bytes() {
write!(w, " {:02X}", octet)?;
}
Ok(())
}
}
pub enum OctetStyle {
Identity,
PrefixBackslash,
Decimal,
}
pub fn display_octet(
sink: &mut Wrapper,
octet: u8,
style: impl FnOnce(u8) -> OctetStyle,
) -> fmt::Result {
if octet >= 0x80 {
return write!(sink, "\\{:03}", octet);
}
match style(octet) {
OctetStyle::Identity => write!(sink, "{}", octet as char),
OctetStyle::PrefixBackslash => write!(sink, "\\{}", octet as char),
OctetStyle::Decimal => write!(sink, "\\{:03}", octet),
}
}
#[cfg(test)]
mod test {
use core::fmt::Write;
use arrayvec::ArrayString;
use assert_matches::assert_matches;
use crate::{fmt::Plain, view::View};
declare_any_error!(AnyError);
type S12 = ArrayString<4096>;
macro_rules! assert {
($type:ident $source:literal yields $output:literal) => (
assert_matches!(assert!(@write $type $source), (ref x, Ok(_)) if x == $output);
);
($type:ident $source:literal fails) => (
assert_matches!(assert!(@write $type $source), (_, Err(_)))
);
(@write $type:ident $source:literal) => ({
let mut sink = S12::default();
let result = super::$type::view($source, 0..$source.len())
.map(|(view, _)| write!(&mut sink, "{}", Plain(&view)));
(sink, result)
});
}
#[test]
fn record() -> Result<(), AnyError> {
let mut sink = S12::default();
let source = b"\0\x00\x02\x00\x01\x00\x00\x00\x00\x00\x00";
let (record, _) = super::Record::view(source, ..)?;
write!(&mut sink, "{}", Plain(&record))?;
assert_eq!(&*sink, r#". 0 IN NS \# 0"#);
Ok(())
}
#[test]
fn soa() {
assert!(Soa b"\x05daria\x03daz\x03cat\0\x05delan\x07azabani\x03com\0\x78\x57\xF7\xD8\x00\x00\x02\x58\x00\x00\x00\x3C\x00\x1B\xAF\x80\x00\x00\x00\x3C" yields "daria.daz.cat. delan.azabani.com. 2019031000 600 60 1814400 60");
}
#[test]
fn mx() {
assert!(Mx b"\x00\x0D\x05daria\x03daz\x03cat\0" yields "13 daria.daz.cat.");
}
#[test]
fn txt() {
assert!(Txt b"\x05hello\x01 \x05world" yields r#""hello" " " "world" "#);
}
#[test]
fn caa() {
assert!(Caa b"\x80\x05issueletsencrypt.org" yields r#"128 issue "letsencrypt.org""#);
}
#[test]
fn in_address() {
assert!(A b"\xC0\x00\x02\x00" yields "192.0.2.0");
assert!(A b"\xC0\x00\x02" fails);
}
#[test]
fn in_aaaa() {
assert!(Aaaa b"\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01" yields "1:1:1:1:1:1:1:1");
assert!(Aaaa b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" yields "::1");
assert!(Aaaa b"\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" yields "1::1");
assert!(Aaaa b"\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0" yields "1::");
assert!(Aaaa b"\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01" yields "0:1:1:1:1:1:1:1");
assert!(Aaaa b"\0\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01" yields "1:0:1:1:1:1:1:1");
assert!(Aaaa b"\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0" yields "1:1:1:1:1:1:1:0");
assert!(Aaaa b"\0\0\0\0\0\0\0\x01\0\x01\0\0\0\0\0\0" yields "::1:1:0:0:0");
assert!(Aaaa b"\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\x01" yields "::1:0:0:0:1");
assert!(Aaaa b"\0\x01\0\0\0\0\0\0\0\x01\0\0\0\0\0\0" yields "1::1:0:0:0");
assert!(Aaaa b"\0\x01\0\0\0\0\0\x01\0\x01\0\0\0\0\0\x01" yields "1::1:1:0:0:1");
assert!(Aaaa b"\0\xAA\0\xBB\0\xCC\0\xDD\0\xEE\0\xFF\0\0\0\0" yields "aa:bb:cc:dd:ee:ff::");
assert!(Aaaa b"\0\0\0\0\0\0\0\0\0\0\xFF\xFF\x7F\0\0\x01" yields "::ffff:7f00:1");
assert!(Aaaa b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" yields "::1"); assert!(Aaaa b"\x20\x01\x0D\xB8\xAA\xAA\0\0\0\0\0\0\0\0\0\0" yields "2001:db8:aaaa::"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\xAA\xAA\0\0\0\0\0\0\0\0" yields "2001:db8:0:aaaa::"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\xAA\xAA\0\0\0\0\0\0" yields "2001:db8:0:0:aaaa::"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\xAA\xAA\xAA\xAA\0\0\0\0" yields "2001:db8::aaaa:aaaa:0:0"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\0\0\xAA\xAA\0\0\0\0" yields "2001:db8::aaaa:0:0"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\0\0\0\0\xAA\xAA\0\0" yields "2001:db8::aaaa:0"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\0\0\0\0\0\0\xAA\xAA" yields "2001:db8::aaaa"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\0\0\0\0\0\0\0\0" yields "2001:db8::"); assert!(Aaaa b"\x20\x01\x0D\xB8\0\0\0\0\0\0\0\0\0\0\0" fails);
}
#[test]
fn unknown() {
assert!(Unknown b"" yields "\\# 0");
assert!(Unknown b"\xFF" yields "\\# 1 FF");
}
}
#[cfg(all(test, feature = "bench"))]
mod bench {
extern crate test;
use alloc::string::String;
use core::fmt::Write;
use test::bench::black_box as b;
use test::Bencher;
use crate::{
fmt::Plain,
view::{Message, View},
};
declare_any_error!(AnyError);
#[bench]
fn response(bencher: &mut Bencher) -> Result<(), AnyError> {
let source = include_bytes!("../samples/daria.daz.cat.a.dns");
let (message, _) = Message::view(source, ..)?;
bencher.iter(|| -> Result<usize, AnyError> {
let mut out = String::new();
write!(out, "{}", Plain(b(&message)))?;
Ok(out.len())
});
Ok(())
}
}