use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use crate::{
constants::{MDNS_IPV4_GROUP, MDNS_PORT},
error::EncodeError,
records::ServiceRecords,
wire::{DEFAULT_COMPRESSION_TABLE, Header, MessageBuilder, Rdata, ResourceClass, ResourceType},
};
pub(crate) fn canonical_rdata_for_hash<'s>(
view: &Rdata<'_>,
scratch: &'s mut std::vec::Vec<u8>,
) -> Result<&'s [u8], crate::error::ParseError> {
scratch.clear();
match view {
Rdata::A(a) => {
scratch.extend_from_slice(&a.addr().octets());
}
Rdata::AAAA(a) => {
scratch.extend_from_slice(&a.addr().octets());
}
Rdata::Ptr(p) => {
write_canonical_name(p.target(), scratch)?;
}
Rdata::Cname(c) => {
write_canonical_name(c.target(), scratch)?;
}
Rdata::Srv(s) => {
scratch.extend_from_slice(&s.priority().to_be_bytes());
scratch.extend_from_slice(&s.weight().to_be_bytes());
scratch.extend_from_slice(&s.port().to_be_bytes());
write_canonical_wire_name(s.target(), scratch)?;
}
Rdata::Txt(t) => {
let mut wrote_any = false;
for seg in t.segments() {
let seg = seg?;
#[allow(clippy::cast_possible_truncation)]
scratch.push(seg.len() as u8);
scratch.extend_from_slice(seg);
wrote_any = true;
}
if !wrote_any {
scratch.push(0);
}
}
Rdata::Nsec(n) => {
scratch.extend_from_slice(n.type_bitmap_slice());
}
Rdata::Other(bytes) => {
scratch.extend_from_slice(bytes);
}
}
Ok(scratch.as_slice())
}
#[allow(single_use_lifetimes)]
pub(crate) fn write_canonical_txt<'a>(
segments: impl Iterator<Item = &'a [u8]>,
out: &mut std::vec::Vec<u8>,
) {
let mut wrote_any = false;
for seg in segments {
if seg.len() <= u8::MAX as usize {
#[allow(clippy::cast_possible_truncation)]
out.push(seg.len() as u8);
out.extend_from_slice(seg);
wrote_any = true;
}
}
if !wrote_any {
out.push(0);
}
}
fn write_canonical_wire_name(
name: &crate::wire::NameRef<'_>,
out: &mut std::vec::Vec<u8>,
) -> Result<(), crate::error::ParseError> {
for label in name.labels() {
let label = label?;
if label.is_empty() {
break;
}
let len = label.len().min(63);
#[allow(clippy::cast_possible_truncation)]
out.push(len as u8);
for &b in label.iter().take(63) {
out.push(b.to_ascii_lowercase());
}
}
out.push(0); Ok(())
}
#[allow(dead_code)]
fn write_canonical_name(
name: &crate::wire::NameRef<'_>,
out: &mut std::vec::Vec<u8>,
) -> Result<(), crate::error::ParseError> {
let mut first = true;
for label in name.labels() {
let label = label?;
if !first {
out.push(b'.');
}
for &b in label {
out.push(b.to_ascii_lowercase());
}
first = false;
}
Ok(())
}
pub(crate) fn write_probe(records: &ServiceRecords, out: &mut [u8]) -> Result<usize, EncodeError> {
let header = Header::new(); let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
b.push_question(
records.instance(),
ResourceType::Any,
ResourceClass::In,
true,
)?;
b.push_srv_authority(
records.instance(),
records.ttl_secs(),
records.priority(),
records.weight(),
records.port(),
records.host(),
)?;
b.push_txt_authority(
records.instance(),
records.ttl_secs(),
records.txt_segments(),
)?;
for a in records.a_addrs_slice() {
b.push_a_authority(records.host(), records.ttl_secs(), *a)?;
}
for a in records.aaaa_addrs_slice() {
b.push_aaaa_authority(records.host(), records.ttl_secs(), *a)?;
}
b.finish()
}
fn push_service_nsec<const COMP_N: usize>(
b: &mut MessageBuilder<'_, COMP_N>,
records: &ServiceRecords,
) {
let checkpoint = b.checkpoint();
if b
.push_nsec_additional(
records.instance(),
records.ttl_secs(),
&[ResourceType::Srv.to_u16(), ResourceType::Txt.to_u16()],
true,
)
.is_err()
{
b.restore(checkpoint);
}
}
pub(crate) fn write_announce(
records: &ServiceRecords,
out: &mut [u8],
) -> Result<usize, EncodeError> {
let header = Header::new().with_flags(
crate::wire::Flags::new()
.with_response()
.with_authoritative(),
);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
b.push_ptr_answer(
records.service_type(),
records.ttl_secs(),
records.instance(),
)?;
for sub in records.subtype_names() {
b.push_ptr_answer(sub, records.ttl_secs(), records.instance())?;
}
b.push_srv_answer(
records.instance(),
records.ttl_secs(),
records.priority(),
records.weight(),
records.port(),
records.host(),
true,
)?;
b.push_txt_answer(
records.instance(),
records.ttl_secs(),
records.txt_segments(),
true,
)?;
for a in records.a_addrs_slice() {
b.push_a_answer(records.host(), records.ttl_secs(), *a, true)?;
}
for a in records.aaaa_addrs_slice() {
b.push_aaaa_answer(records.host(), records.ttl_secs(), *a, true)?;
}
push_service_nsec(&mut b, records);
b.finish()
}
pub(crate) fn write_meta_response(
records: &ServiceRecords,
meta_name: &crate::Name,
out: &mut [u8],
) -> Result<usize, EncodeError> {
let header = Header::new().with_flags(
crate::wire::Flags::new()
.with_response()
.with_authoritative(),
);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
b.push_ptr_answer(meta_name, records.ttl_secs(), records.service_type())?;
b.finish()
}
pub(crate) fn write_legacy_meta_response(
records: &ServiceRecords,
query_id: u16,
meta_name: &crate::Name,
qtype: ResourceType,
qclass: ResourceClass,
out: &mut [u8],
) -> Result<usize, EncodeError> {
let header = Header::new().with_id(query_id).with_flags(
crate::wire::Flags::new()
.with_response()
.with_authoritative(),
);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
b.push_question(meta_name, qtype, qclass, false)?;
let ttl = records.ttl_secs().min(LEGACY_UNICAST_MAX_TTL_SECS);
b.push_ptr_answer(meta_name, ttl, records.service_type())?;
b.finish()
}
pub(crate) const LEGACY_UNICAST_MAX_TTL_SECS: u32 = 10;
pub(crate) fn write_legacy_response(
records: &ServiceRecords,
query_id: u16,
qname: &crate::Name,
qtype: ResourceType,
qclass: ResourceClass,
out: &mut [u8],
) -> Result<(usize, EmittedRecords), EncodeError> {
let header = Header::new().with_id(query_id).with_flags(
crate::wire::Flags::new()
.with_response()
.with_authoritative(),
);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
b.push_question(qname, qtype, qclass, false)?;
let ttl = records.ttl_secs().min(LEGACY_UNICAST_MAX_TTL_SECS);
b.push_ptr_answer(records.service_type(), ttl, records.instance())?;
for sub in records.subtype_names() {
b.push_ptr_answer(sub, ttl, records.instance())?;
}
b.push_srv_answer(
records.instance(),
ttl,
records.priority(),
records.weight(),
records.port(),
records.host(),
false,
)?;
b.push_txt_answer(records.instance(), ttl, records.txt_segments(), false)?;
for a in records.a_addrs_slice() {
b.push_a_answer(records.host(), ttl, *a, false)?;
}
for a in records.aaaa_addrs_slice() {
b.push_aaaa_answer(records.host(), ttl, *a, false)?;
}
let emitted = EmittedRecords {
ptr: true,
srv: true,
txt: true,
a: records.a_addrs_slice().to_vec(),
aaaa: records.aaaa_addrs_slice().to_vec(),
subtypes: !records.subtype_names().is_empty(),
};
Ok((b.finish()?, emitted))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn write_goodbye(
records: &ServiceRecords,
out: &mut [u8],
include_ptr: bool,
include_srv: bool,
include_txt: bool,
include_subtypes: bool,
a_addrs: impl Iterator<Item = Ipv4Addr>,
aaaa_addrs: impl Iterator<Item = Ipv6Addr>,
) -> Result<usize, EncodeError> {
let header = Header::new().with_flags(
crate::wire::Flags::new()
.with_response()
.with_authoritative(),
);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
push_goodbye_records(
&mut b,
records,
include_ptr,
include_srv,
include_txt,
include_subtypes,
a_addrs,
aaaa_addrs,
)?;
b.finish()
}
#[allow(clippy::too_many_arguments)]
fn push_goodbye_records<const COMP_N: usize>(
b: &mut MessageBuilder<'_, COMP_N>,
records: &ServiceRecords,
include_ptr: bool,
include_srv: bool,
include_txt: bool,
include_subtypes: bool,
a_addrs: impl Iterator<Item = Ipv4Addr>,
aaaa_addrs: impl Iterator<Item = Ipv6Addr>,
) -> Result<(), EncodeError> {
if include_ptr {
b.push_ptr_answer(records.service_type(), 0, records.instance())?;
}
if include_srv {
b.push_srv_answer(
records.instance(),
0,
records.priority(),
records.weight(),
records.port(),
records.host(),
true,
)?;
}
if include_txt {
b.push_txt_answer(records.instance(), 0, records.txt_segments(), true)?;
}
if include_subtypes {
for sub in records.subtype_names() {
b.push_ptr_answer(sub, 0, records.instance())?;
}
}
for a in a_addrs {
b.push_a_answer(records.host(), 0, a, true)?;
}
for a in aaaa_addrs {
b.push_aaaa_answer(records.host(), 0, a, true)?;
}
Ok(())
}
#[derive(Clone, Debug, Default)]
pub(crate) struct EmittedRecords {
ptr: bool,
srv: bool,
txt: bool,
a: std::vec::Vec<Ipv4Addr>,
aaaa: std::vec::Vec<Ipv6Addr>,
subtypes: bool,
}
impl EmittedRecords {
pub fn is_empty(&self) -> bool {
!self.ptr
&& !self.srv
&& !self.txt
&& !self.subtypes
&& self.a.is_empty()
&& self.aaaa.is_empty()
}
pub(crate) fn new(
ptr: bool,
srv: bool,
txt: bool,
a: std::vec::Vec<Ipv4Addr>,
aaaa: std::vec::Vec<Ipv6Addr>,
subtypes: bool,
) -> Self {
Self {
ptr,
srv,
txt,
a,
aaaa,
subtypes,
}
}
#[inline(always)]
pub(crate) const fn ptr(&self) -> bool {
self.ptr
}
#[inline(always)]
pub(crate) const fn srv(&self) -> bool {
self.srv
}
#[inline(always)]
pub(crate) const fn txt(&self) -> bool {
self.txt
}
#[inline(always)]
pub(crate) const fn subtypes(&self) -> bool {
self.subtypes
}
#[inline(always)]
pub(crate) const fn a_slice(&self) -> &[Ipv4Addr] {
self.a.as_slice()
}
#[inline(always)]
pub(crate) const fn aaaa_slice(&self) -> &[Ipv6Addr] {
self.aaaa.as_slice()
}
}
pub(crate) fn write_announce_filtered<F>(
records: &ServiceRecords,
out: &mut [u8],
mut hint_matches: F,
) -> Result<(usize, EmittedRecords), EncodeError>
where
F: FnMut(ResourceType, &[u8]) -> bool,
{
let header = crate::wire::Header::new().with_flags(
crate::wire::Flags::new()
.with_response()
.with_authoritative(),
);
let mut b: MessageBuilder<'_, DEFAULT_COMPRESSION_TABLE> = MessageBuilder::try_new(out, header)?;
let mut emitted = EmittedRecords::default();
let mut scratch: std::vec::Vec<u8> = std::vec::Vec::new();
{
scratch.clear();
for (i, label) in records
.instance()
.as_str()
.trim_end_matches('.')
.split('.')
.enumerate()
{
if i > 0 {
scratch.push(b'.');
}
for &b in label.as_bytes() {
scratch.push(b.to_ascii_lowercase());
}
}
if !hint_matches(ResourceType::Ptr, &scratch) {
b.push_ptr_answer(
records.service_type(),
records.ttl_secs(),
records.instance(),
)?;
emitted.ptr = true;
}
}
for sub in records.subtype_names() {
b.push_ptr_answer(sub, records.ttl_secs(), records.instance())?;
emitted.subtypes = true;
}
{
scratch.clear();
scratch.extend_from_slice(&records.priority().to_be_bytes());
scratch.extend_from_slice(&records.weight().to_be_bytes());
scratch.extend_from_slice(&records.port().to_be_bytes());
super::write_canonical_wire_name(records.host().as_str(), &mut scratch);
if !hint_matches(ResourceType::Srv, &scratch) {
b.push_srv_answer(
records.instance(),
records.ttl_secs(),
records.priority(),
records.weight(),
records.port(),
records.host(),
true,
)?;
emitted.srv = true;
}
}
{
scratch.clear();
write_canonical_txt(records.txt_segments(), &mut scratch);
if !hint_matches(ResourceType::Txt, &scratch) {
b.push_txt_answer(
records.instance(),
records.ttl_secs(),
records.txt_segments(),
true,
)?;
emitted.txt = true;
}
}
for a in records.a_addrs_slice() {
let rdata = a.octets();
if !hint_matches(ResourceType::A, &rdata) {
b.push_a_answer(records.host(), records.ttl_secs(), *a, true)?;
emitted.a.push(*a);
}
}
for a in records.aaaa_addrs_slice() {
let rdata = a.octets();
if !hint_matches(ResourceType::AAAA, &rdata) {
b.push_aaaa_answer(records.host(), records.ttl_secs(), *a, true)?;
emitted.aaaa.push(*a);
}
}
if !emitted.is_empty() {
push_service_nsec(&mut b, records);
}
let n = b.finish()?;
Ok((n, emitted))
}
pub(crate) fn multicast_dst() -> SocketAddr {
SocketAddr::new(IpAddr::V4(MDNS_IPV4_GROUP), MDNS_PORT)
}
#[cfg(test)]
#[cfg(any(feature = "alloc", feature = "std"))]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::panic,
clippy::arithmetic_side_effects,
clippy::integer_division
)]
mod tests;