use std::net::Ipv4Addr;
use smallvec::SmallVec;
use super::name::{decode_first_level, split_name_suffix};
use super::types::{NbnsMessage, NbnsOpcode};
pub fn parse(payload: &[u8]) -> Option<NbnsMessage> {
if payload.len() < 12 {
return None;
}
let transaction_id = u16::from_be_bytes([payload[0], payload[1]]);
let flags = u16::from_be_bytes([payload[2], payload[3]]);
let is_response = (flags & 0x8000) != 0;
let opcode_bits = ((flags >> 11) & 0x0f) as u8;
let opcode = NbnsOpcode::from_bits(opcode_bits);
let qdcount = u16::from_be_bytes([payload[4], payload[5]]);
let ancount = u16::from_be_bytes([payload[6], payload[7]]);
let mut cursor = 12;
let (queried_name, name_suffix) = if qdcount > 0 {
let (decoded, advance) = decode_question(&payload[cursor..])?;
cursor += advance;
let (name, suffix) = split_name_suffix(&decoded);
if name.is_empty() {
(None, Some(suffix))
} else {
(Some(name), Some(suffix))
}
} else {
(None, None)
};
let mut answer_addresses: SmallVec<[Ipv4Addr; 1]> = SmallVec::new();
if ancount > 0
&& let Some(addrs) = parse_first_nb_answer(&payload[cursor..])
{
answer_addresses.extend(addrs);
}
Some(NbnsMessage {
transaction_id,
opcode,
is_response,
queried_name,
name_suffix,
answer_addresses,
})
}
fn decode_question(buf: &[u8]) -> Option<([u8; 16], usize)> {
if buf.len() < 1 + 32 + 1 + 4 {
return None;
}
let length = buf[0];
if length != 32 {
return None;
}
let encoded = &buf[1..33];
let raw = decode_first_level(encoded)?;
if buf[33] != 0x00 {
return None;
}
Some((raw, 1 + 32 + 1 + 4))
}
fn parse_first_nb_answer(buf: &[u8]) -> Option<Vec<Ipv4Addr>> {
let mut cursor = 0;
if buf.first() == Some(&0xc0) {
cursor += 2;
} else {
if buf.len() < 34 {
return None;
}
if buf[0] != 32 {
return None;
}
cursor += 34;
}
if buf.len() < cursor + 10 {
return None;
}
let rr_type = u16::from_be_bytes([buf[cursor], buf[cursor + 1]]);
cursor += 2;
cursor += 6;
let rdlength = u16::from_be_bytes([buf[cursor], buf[cursor + 1]]) as usize;
cursor += 2;
if rr_type != 0x0020 {
return None;
}
if buf.len() < cursor + rdlength {
return None;
}
let rdata = &buf[cursor..cursor + rdlength];
let mut addrs = Vec::new();
let mut p = 0;
while p + 6 <= rdata.len() {
let v4 = Ipv4Addr::new(rdata[p + 2], rdata[p + 3], rdata[p + 4], rdata[p + 5]);
addrs.push(v4);
p += 6;
}
if addrs.is_empty() { None } else { Some(addrs) }
}
#[cfg(test)]
mod tests {
use super::*;
fn build_query(name: &str, suffix: u8, transaction_id: u16) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&transaction_id.to_be_bytes());
out.extend_from_slice(&0x0110u16.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes()); out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&0u16.to_be_bytes()); out.push(32);
out.extend_from_slice(&encode_name(name, suffix));
out.push(0); out.extend_from_slice(&0x0020u16.to_be_bytes()); out.extend_from_slice(&0x0001u16.to_be_bytes()); out
}
fn encode_name(name: &str, suffix: u8) -> Vec<u8> {
let mut raw = [b' '; 16];
let bytes = name.as_bytes();
let n = bytes.len().min(15);
raw[..n].copy_from_slice(&bytes[..n]);
raw[15] = suffix;
let mut encoded = Vec::with_capacity(32);
for b in raw.iter() {
encoded.push(b'A' + (b >> 4));
encoded.push(b'A' + (b & 0x0f));
}
encoded
}
#[test]
fn parses_header_and_first_question() {
let payload = build_query("WORKGROUP", 0x00, 0x1234);
let msg = parse(&payload).expect("parse");
assert_eq!(msg.transaction_id, 0x1234);
assert_eq!(msg.opcode, NbnsOpcode::Query);
assert!(!msg.is_response);
assert_eq!(msg.queried_name.as_deref(), Some("WORKGROUP"));
assert_eq!(msg.name_suffix, Some(0x00));
}
#[test]
fn opcode_decodes_registration() {
let mut payload = build_query("HOST", 0x00, 0);
payload[2] = 0x28;
payload[3] = 0x00;
let msg = parse(&payload).expect("parse");
assert_eq!(msg.opcode, NbnsOpcode::Registration);
}
#[test]
fn qr_bit_sets_is_response() {
let mut payload = build_query("X", 0x00, 0);
payload[2] |= 0x80;
let msg = parse(&payload).expect("parse");
assert!(msg.is_response);
}
#[test]
fn missing_question_yields_no_name() {
let mut header = vec![0u8; 12];
header[2] = 0x01;
header[3] = 0x00;
let msg = parse(&header).expect("header-only is valid");
assert!(msg.queried_name.is_none());
assert!(msg.name_suffix.is_none());
}
#[test]
fn too_short_returns_none() {
assert!(parse(&[0u8; 5]).is_none());
}
#[test]
fn answer_section_carries_ipv4() {
let mut payload = build_query("HOST", 0x00, 0x42);
payload[2] = 0xa8; payload[6] = 0;
payload[7] = 1; payload.push(0xc0);
payload.push(0x0c); payload.extend_from_slice(&0x0020u16.to_be_bytes()); payload.extend_from_slice(&0x0001u16.to_be_bytes()); payload.extend_from_slice(&300u32.to_be_bytes()); payload.extend_from_slice(&6u16.to_be_bytes()); payload.extend_from_slice(&[0u8, 0u8]); payload.extend_from_slice(&[192, 168, 1, 42]); let msg = parse(&payload).expect("parse");
assert_eq!(msg.answer_addresses.len(), 1);
assert_eq!(msg.answer_addresses[0], Ipv4Addr::new(192, 168, 1, 42));
}
#[test]
fn opcode_as_str_slugs_match() {
assert_eq!(NbnsOpcode::Query.as_str(), "query");
assert_eq!(NbnsOpcode::Registration.as_str(), "registration");
assert_eq!(NbnsOpcode::Release.as_str(), "release");
assert_eq!(NbnsOpcode::Wack.as_str(), "wack");
assert_eq!(NbnsOpcode::Refresh.as_str(), "refresh");
assert_eq!(NbnsOpcode::Other(15).as_str(), "other");
}
#[test]
fn malformed_question_length_returns_none_on_question() {
let mut payload = build_query("X", 0x00, 0);
payload[12] = 33;
let msg = parse(&payload);
assert!(msg.is_none(), "malformed question → reject entire msg");
}
}