use anyhow::Result;
use bytes::BytesMut;
use lazy_static::lazy_static;
use packed_struct::prelude::*;
use crate::codec::{domain_name, message, rdata};
use crate::specs::enums_generated::{ResourceType, ResponseCode};
use crate::specs::message::{IntEnum, Message, Question, OPT};
lazy_static! {
pub static ref ENCODER: DNSMessageEncoder = DNSMessageEncoder::new();
}
pub struct DNSMessageEncoder {}
static CACHED_NXDOMAIN_TTL: u32 = 300;
impl DNSMessageEncoder {
pub fn new() -> DNSMessageEncoder {
DNSMessageEncoder {}
}
pub fn encode_local_response(
&self,
response_code: ResponseCode,
orig_header_id: u16,
orig_question: &Question,
orig_opt: &Option<OPT>,
filter_info: &String,
ip: Option<std::net::IpAddr>,
udp_size_override: Option<u16>,
buf: &mut BytesMut,
) -> Result<()> {
let mut ptr_offsets = domain_name::LabelOffsets::new();
message::write_header_bits(
message::HeaderBits {
id: orig_header_id,
is_response: true,
op_code: Integer::from(0 ),
authoritative: false,
truncated: false,
recursion_desired: true,
recursion_available: true,
reserved_9: false,
authentic_data: false,
checking_disabled: false,
response_code: Integer::from(response_code as u8),
question_count: 1,
answer_count: match ip {
Some(_) => 1, None => 0,
},
authority_count: match ip {
Some(_) => 0,
None => 1, },
additional_count: match orig_opt {
Some(_) => 2,
None => 1,
},
},
buf,
)?;
message::write_question(orig_question, buf, &mut ptr_offsets)?;
match ip {
Some(ip) => message::write_resource_fields(
&message::ResourceFields {
name: orig_question.name.as_str(),
resource_type: orig_question.resource_type,
resource_class: orig_question.resource_class,
ttl: 300,
rdata: message::RDataFields::IP(ip),
},
buf,
&mut ptr_offsets,
)?,
None => {
message::write_resource_fields(
&message::ResourceFields {
name: orig_question.name.as_str(),
resource_type: IntEnum::Enum(ResourceType::SOA),
resource_class: orig_question.resource_class,
ttl: CACHED_NXDOMAIN_TTL,
rdata: match ip {
Some(ip) => message::RDataFields::IP(ip),
None => message::RDataFields::SOA(rdata::SOAFields {
mname: orig_question.name.as_str(),
rname: "kapiti.nickbp.com.",
serial: 42069,
refresh: CACHED_NXDOMAIN_TTL,
retry: 60 * 60,
expire: 3 * 24 * 60 * 60,
minimum: CACHED_NXDOMAIN_TTL,
}),
},
},
buf,
&mut ptr_offsets,
)?;
}
}
if let Some(opt) = orig_opt {
message::write_opt(&opt, udp_size_override, buf)?;
}
message::write_resource_fields(
&message::ResourceFields {
name: orig_question.name.as_str(),
resource_type: IntEnum::Enum(ResourceType::TXT),
resource_class: orig_question.resource_class,
ttl: CACHED_NXDOMAIN_TTL,
rdata: message::RDataFields::TXT(filter_info),
},
buf,
&mut ptr_offsets,
)?;
Ok(())
}
pub fn encode(
&self,
message: &Message,
udp_size_override: Option<u16>,
buf: &mut BytesMut,
) -> Result<()> {
let mut ptr_offsets = domain_name::LabelOffsets::new();
message::write_header_id(message, message.header.id, buf)?;
for q in &message.question {
message::write_question(&q, buf, &mut ptr_offsets)?;
}
for a in &message.answer {
message::write_resource(&a, buf, &mut ptr_offsets)?;
}
for a in &message.authority {
message::write_resource(a, buf, &mut ptr_offsets)?;
}
if let Some(opt) = &message.opt {
message::write_opt(opt, udp_size_override, buf)?;
}
for a in &message.additional {
message::write_resource(a, buf, &mut ptr_offsets)?;
}
Ok(())
}
}