#![cfg(feature = "dns")]
use std::time::Duration;
use flowscope::{
DatagramParser, FlowSide, Timestamp,
dns::{DnsExchangeParser, DnsOutcome},
};
fn build_msg(tx_id: u16, qname: &str, flags: u16, rcode_lo: u8) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&tx_id.to_be_bytes());
let flags_with_rcode = flags | rcode_lo as u16;
v.extend_from_slice(&flags_with_rcode.to_be_bytes());
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&0u16.to_be_bytes());
v.extend_from_slice(&0u16.to_be_bytes());
v.extend_from_slice(&0u16.to_be_bytes());
for label in qname.split('.') {
v.push(label.len() as u8);
v.extend_from_slice(label.as_bytes());
}
v.push(0);
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&1u16.to_be_bytes()); v
}
#[test]
fn query_response_yields_completed() {
let mut p = DnsExchangeParser::new();
let q = build_msg(0x1234, "example.com", 0x0100, 0);
let r = build_msg(0x1234, "example.com", 0x8100, 0);
let mut qs = Vec::new();
p.parse(&q, FlowSide::Initiator, Timestamp::new(0, 0), &mut qs);
assert!(qs.is_empty(), "query alone shouldn't emit exchange");
let mut rs = Vec::new();
p.parse(&r, FlowSide::Responder, Timestamp::new(1, 0), &mut rs);
assert_eq!(rs.len(), 1);
assert_eq!(rs[0].outcome, DnsOutcome::Completed);
assert_eq!(rs[0].transaction_id, 0x1234);
assert_eq!(rs[0].question.name, "example.com");
}
#[test]
fn nxdomain_response_yields_failed() {
let mut p = DnsExchangeParser::new();
let q = build_msg(0xabcd, "missing.example", 0x0100, 0);
let r = build_msg(0xabcd, "missing.example", 0x8100, 3); let mut sink = Vec::new();
p.parse(&q, FlowSide::Initiator, Timestamp::new(0, 0), &mut sink);
sink.clear();
p.parse(&r, FlowSide::Responder, Timestamp::new(1, 0), &mut sink);
assert_eq!(sink.len(), 1);
assert!(matches!(sink[0].outcome, DnsOutcome::Failed { .. }));
}
#[test]
fn unanswered_query_yields_no_response_on_tick() {
let mut cfg = flowscope::dns::DnsConfig::default();
cfg.query_timeout = Duration::from_secs(1);
cfg.max_pending = 1024;
let mut p = flowscope::dns::DnsExchangeParser::with_config(cfg);
let q = build_msg(0x9999, "slow.example", 0x0100, 0);
let mut sink = Vec::new();
p.parse(&q, FlowSide::Initiator, Timestamp::new(0, 0), &mut sink);
let mut ticks = Vec::new();
p.on_tick(Timestamp::new(10, 0), &mut ticks);
assert_eq!(ticks.len(), 1);
assert_eq!(ticks[0].outcome, DnsOutcome::NoResponse);
}
#[test]
fn parser_kind_is_dns_exchange() {
let p = DnsExchangeParser::new();
assert_eq!(p.parser_kind().as_str(), "dns-exchange");
}