dyns 0.4.0

DNS discovery and resolver support for DHTTP applications
Documentation
use std::collections::HashMap;

use dhttp_identity::name::Name;
use dquic::qbase::net::addr::EndpointAddr;
use snafu::Snafu;

use crate::core::{MdnsPacket, parser::record::endpoint::EndpointAddr as DnsEndpointAddr};

#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum EncodeEndpointPacketError {
    #[snafu(display("failed to encode endpoint address"))]
    EncodeEndpoint,
}

pub(crate) fn endpoint_packet(
    name: &Name<'_>,
    endpoints: impl IntoIterator<Item = EndpointAddr>,
) -> Result<Vec<u8>, EncodeEndpointPacketError> {
    let mut encoded = Vec::new();
    for endpoint in endpoints {
        let Ok(endpoint) = DnsEndpointAddr::try_from(endpoint) else {
            return encode_endpoint_packet_error::EncodeEndpointSnafu.fail();
        };
        encoded.push(endpoint);
    }

    let mut hosts = HashMap::new();
    hosts.insert(name.as_str().to_owned(), encoded);
    Ok(MdnsPacket::answer(0, &hosts).to_bytes())
}

#[cfg(test)]
mod tests {
    use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};

    use dhttp_identity::name::Name;
    use dquic::qbase::net::addr::EndpointAddr as DquicEndpointAddr;

    use super::endpoint_packet;
    use crate::core::parser::{
        packet::be_packet,
        record::{RData, Type},
    };

    #[test]
    fn endpoint_packet_encodes_unsigned_e_records() {
        let name = Name::try_from("alice.dhttp.net").expect("valid dns owner name");
        let endpoint = DquicEndpointAddr::direct(SocketAddr::V4(SocketAddrV4::new(
            Ipv4Addr::new(203, 0, 113, 10),
            4433,
        )));

        let packet = endpoint_packet(&name, [endpoint]).expect("endpoint packet");
        let (remain, parsed) = be_packet(&packet).expect("dns packet parses");
        assert!(remain.is_empty());
        assert_eq!(parsed.answers.len(), 1);
        assert_eq!(parsed.answers[0].name(), "alice.dhttp.net");
        assert_eq!(parsed.answers[0].typ(), Type::E);

        let RData::E(encoded) = parsed.answers[0].data() else {
            panic!("answer must be an E record");
        };
        assert!(!encoded.is_signed());
    }

    #[test]
    fn endpoint_packet_allows_empty_endpoint_set() {
        let name = Name::try_from("alice.dhttp.net").expect("valid dns owner name");

        let packet = endpoint_packet(&name, []).expect("endpoint packet");
        let (remain, parsed) = be_packet(&packet).expect("dns packet parses");
        assert!(remain.is_empty());
        assert!(parsed.answers.is_empty());
    }
}