use std::net::SocketAddr;
use bytes::Bytes;
use hickory_net::proto::op::{Message, MessageType, ResponseCode};
use hickory_net::proto::rr::rdata::{A, SOA};
use hickory_net::proto::rr::{Name as HickoryName, RData, Record};
use tempfile::TempDir;
use tokio::net::UdpSocket;
use crate::{
codec::{
header::Header,
message::{Qclass, Qtype, Question},
name::Name,
writer::Writer,
},
storage::Db,
};
pub(crate) async fn temp_db() -> (TempDir, Db) {
let dir = TempDir::new().expect("create temp dir");
let db = Db::connect(dir.path().join("test.db"))
.await
.expect("connect to a fresh test database");
(dir, db)
}
pub(crate) fn wire_query(id: u16, name: &str, qtype: u16) -> Bytes {
let mut w = Writer::with_capacity(64);
Header::new(id).with_qdcount(1).with_rd(true).write(&mut w);
let n: Name = name.parse().expect("valid name in test helper");
n.write(&mut w);
w.write_u16(qtype);
w.write_u16(1u16); w.finish()
}
pub(crate) fn a_query(id: u16, name: &str) -> Bytes {
wire_query(id, name, 1)
}
pub(crate) fn aaaa_query(id: u16, name: &str) -> Bytes {
wire_query(id, name, 28)
}
pub(crate) fn ptr_query(id: u16, name: &str) -> Bytes {
wire_query(id, name, 12)
}
pub(crate) fn stock_question() -> Question {
Question {
name: "example.com".parse().expect("valid name"),
qtype: Qtype::A,
qclass: Qclass::In,
}
}
pub(crate) async fn mock_udp_upstream<F>(mut handler: F) -> SocketAddr
where
F: FnMut(Message) -> Option<Message> + Send + 'static,
{
let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let addr = sock.local_addr().unwrap();
tokio::spawn(async move {
let mut buf = vec![0u8; 512];
loop {
let Ok((len, peer)) = sock.recv_from(&mut buf).await else {
break;
};
let Ok(req) = Message::from_vec(&buf[..len]) else {
continue;
};
if let Some(resp) = handler(req)
&& let Ok(resp_bytes) = resp.to_vec()
{
let _ = sock.send_to(&resp_bytes, peer).await;
}
}
});
addr
}
fn response_shell(mut req: Message, code: ResponseCode) -> Message {
req.metadata.message_type = MessageType::Response;
req.metadata.response_code = code;
req
}
pub(crate) fn positive_a_handler(req: Message) -> Option<Message> {
let mut resp = response_shell(req, ResponseCode::NoError);
let name = HickoryName::from_ascii("example.com.").unwrap();
let rdata = RData::A(A::new(93, 184, 216, 34));
resp.add_answer(Record::from_rdata(name, 300, rdata));
Some(resp)
}
pub(crate) fn nxdomain_with_soa_handler(req: Message) -> Option<Message> {
let mut resp = response_shell(req, ResponseCode::NXDomain);
let zone = HickoryName::from_ascii("example.com.").unwrap();
let mname = HickoryName::from_ascii("ns1.example.com.").unwrap();
let rname = HickoryName::from_ascii("hostmaster.example.com.").unwrap();
let soa = SOA::new(mname, rname, 1, 3600, 900, 604800, 60);
resp.add_authority(Record::from_rdata(zone, 120, RData::SOA(soa)));
Some(resp)
}
pub(crate) fn nxdomain_handler(req: Message) -> Option<Message> {
Some(response_shell(req, ResponseCode::NXDomain))
}
pub(crate) fn silent_handler(_req: Message) -> Option<Message> {
None
}