use std::{
collections::{HashMap, HashSet, VecDeque},
sync::Arc,
};
use core::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
time::Duration,
};
use bytes::Bytes;
use futures::future::select_all;
use mdns_proto::{
Name, QuerySpec,
wire::{A, AAAA, NameRef, Ptr, ResourceType, Srv, Txt},
};
use smol_str::SmolStr;
use crate::{
endpoint::Endpoint,
query::{Query, QueryEvent},
};
#[cfg(test)]
mod tests;
pub const DEFAULT_MAX_ENTRIES: usize = 64;
pub(crate) const MAX_ADDRS_PER_HOST: usize = 16;
#[derive(Debug, Clone)]
pub struct ServiceEntry {
pub(crate) instance: Name,
pub(crate) host: Name,
pub(crate) port: u16,
pub(crate) ipv4: Arc<[Ipv4Addr]>,
pub(crate) ipv6: Arc<[Ipv6Addr]>,
pub(crate) txt: Arc<[Bytes]>,
}
impl ServiceEntry {
#[inline]
pub const fn instance_name(&self) -> &Name {
&self.instance
}
#[inline]
pub const fn host(&self) -> &Name {
&self.host
}
#[inline]
pub const fn port(&self) -> u16 {
self.port
}
#[inline]
pub fn ipv4_addresses(&self) -> &[Ipv4Addr] {
&self.ipv4
}
#[inline]
pub fn ipv6_addresses(&self) -> &[Ipv6Addr] {
&self.ipv6
}
pub fn addresses(&self) -> impl Iterator<Item = IpAddr> + '_ {
self
.ipv4
.iter()
.copied()
.map(IpAddr::V4)
.chain(self.ipv6.iter().copied().map(IpAddr::V6))
}
#[inline]
pub fn txt(&self) -> &[Bytes] {
&self.txt
}
}
#[derive(Debug, Clone)]
pub struct QueryParam {
pub(crate) service: Name,
pub(crate) timeout: Duration,
pub(crate) resolve_timeout: Option<Duration>,
pub(crate) unicast_response: bool,
pub(crate) max_entries: usize,
}
impl QueryParam {
#[inline(always)]
pub const fn new(service: Name) -> Self {
Self {
service,
timeout: Duration::from_secs(1),
resolve_timeout: None,
unicast_response: false,
max_entries: DEFAULT_MAX_ENTRIES,
}
}
#[must_use]
#[inline(always)]
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
#[inline(always)]
pub const fn with_resolve_timeout(mut self, timeout: Duration) -> Self {
self.resolve_timeout = Some(timeout);
self
}
#[must_use]
#[inline(always)]
pub const fn with_unicast_response(mut self, unicast: bool) -> Self {
self.unicast_response = unicast;
self
}
#[must_use]
#[inline(always)]
pub const fn with_max_entries(mut self, max: usize) -> Self {
self.max_entries = if max == 0 { 1 } else { max };
self
}
}
pub(crate) fn push_capped<T: PartialEq>(v: &mut Vec<T>, item: T) -> bool {
if v.len() >= MAX_ADDRS_PER_HOST || v.contains(&item) {
return false;
}
v.push(item);
true
}
pub(crate) fn fold(name: &Name) -> SmolStr {
SmolStr::new(name.as_str())
}
fn name_from_ref(nr: &NameRef<'_>) -> Option<Name> {
let mut buf = String::new();
for label in nr.labels() {
let label = label.ok()?;
if label.is_empty() {
break; }
if label.iter().any(|&b| b >= 0x80 || b == b'.') {
return None; }
buf.push_str(core::str::from_utf8(label).ok()?);
buf.push('.');
}
if buf.is_empty() {
return None;
}
Name::try_from_str(&buf).ok()
}
pub(crate) fn parse_name(rdata: &[u8]) -> Option<Name> {
let ptr = Ptr::try_from_message(rdata, 0, rdata.len()).ok()?;
name_from_ref(ptr.target())
}
pub(crate) fn parse_srv(rdata: &[u8]) -> Option<(Name, u16)> {
let srv = Srv::try_from_message(rdata, 0, rdata.len()).ok()?;
let host = name_from_ref(srv.target())?;
Some((host, srv.port()))
}
pub(crate) fn parse_txt(rdata: &[u8]) -> Vec<Bytes> {
Txt::from_rdata(rdata)
.segments()
.map_while(Result::ok)
.map(Bytes::copy_from_slice)
.collect()
}
#[derive(Clone)]
#[allow(clippy::upper_case_acronyms)] pub(crate) enum Step {
Ptr,
Srv(SmolStr),
Txt(SmolStr),
A(SmolStr),
AAAA(SmolStr),
}
#[derive(Clone)]
pub(crate) struct Start {
pub(crate) name: Name,
pub(crate) step: Step,
}
impl Start {
pub(crate) const fn qtype(&self) -> ResourceType {
match self.step {
Step::Srv(_) => ResourceType::Srv,
Step::Txt(_) => ResourceType::Txt,
Step::A(_) => ResourceType::A,
Step::AAAA(_) => ResourceType::AAAA,
Step::Ptr => ResourceType::Ptr,
}
}
}
#[derive(Default)]
pub(crate) struct HostAddrs {
pub(crate) ipv4: Vec<Ipv4Addr>,
pub(crate) ipv6: Vec<Ipv6Addr>,
}
pub(crate) struct Builder {
pub(crate) instance: Name,
pub(crate) host: Option<Name>,
pub(crate) host_key: Option<SmolStr>,
pub(crate) has_srv: bool,
pub(crate) port: u16,
pub(crate) ipv4: Vec<Ipv4Addr>,
pub(crate) ipv6: Vec<Ipv6Addr>,
pub(crate) txt: Option<Vec<Bytes>>,
pub(crate) emitted: bool,
}
impl Builder {
fn new(instance: Name) -> Self {
Self {
instance,
host: None,
host_key: None,
has_srv: false,
port: 0,
ipv4: Vec::new(),
ipv6: Vec::new(),
txt: None,
emitted: false,
}
}
fn complete(&self) -> bool {
self.has_srv && self.txt.is_some() && !(self.ipv4.is_empty() && self.ipv6.is_empty())
}
fn finalize(&self) -> Option<ServiceEntry> {
Some(ServiceEntry {
instance: self.instance.clone(),
host: self.host.clone()?,
port: self.port,
ipv4: self.ipv4.as_slice().into(),
ipv6: self.ipv6.as_slice().into(),
txt: self.txt.as_deref()?.into(),
})
}
}
pub(crate) struct Resolver {
pub(crate) builders: HashMap<SmolStr, Builder>,
pub(crate) host_addrs: HashMap<SmolStr, HostAddrs>,
pub(crate) hosts_queried: HashSet<SmolStr>,
pub(crate) ready: VecDeque<ServiceEntry>,
pub(crate) max_entries: usize,
pub(crate) max_hosts: usize,
pub(crate) dropped: u64,
}
impl Resolver {
pub(crate) fn new(max_entries: usize) -> Self {
Self {
builders: HashMap::new(),
host_addrs: HashMap::new(),
hosts_queried: HashSet::new(),
ready: VecDeque::new(),
max_entries,
max_hosts: max_entries,
dropped: 0,
}
}
pub(crate) fn on_ptr(&mut self, instance: Name) -> Vec<Start> {
let key = fold(&instance);
if self.builders.contains_key(&key) {
return Vec::new(); }
if self.builders.len() >= self.max_entries {
self.dropped = self.dropped.saturating_add(1);
return Vec::new();
}
self
.builders
.insert(key.clone(), Builder::new(instance.clone()));
vec![
Start {
name: instance.clone(),
step: Step::Srv(key.clone()),
},
Start {
name: instance,
step: Step::Txt(key),
},
]
}
pub(crate) fn on_srv(&mut self, inst_key: &str, host: Name, port: u16) -> Vec<Start> {
let host_key = fold(&host);
let cached = self
.host_addrs
.get(&host_key)
.map(|h| (h.ipv4.clone(), h.ipv6.clone()));
let mut changed = false;
if let Some(b) = self.builders.get_mut(inst_key) {
let host_changed = b.host_key.as_deref() != Some(host_key.as_str());
if host_changed {
b.ipv4.clear();
b.ipv6.clear();
}
changed = host_changed || b.port != port;
b.has_srv = true;
b.host = Some(host.clone());
b.host_key = Some(host_key.clone());
b.port = port;
if let Some((v4, v6)) = cached {
for a in v4 {
push_capped(&mut b.ipv4, a);
}
for a in v6 {
push_capped(&mut b.ipv6, a);
}
}
}
self.try_emit(inst_key, changed);
if self.hosts_queried.contains(&host_key) {
return Vec::new(); }
if self.hosts_queried.len() >= self.max_hosts {
self.dropped = self.dropped.saturating_add(1);
return Vec::new();
}
self.hosts_queried.insert(host_key.clone());
vec![
Start {
name: host.clone(),
step: Step::A(host_key.clone()),
},
Start {
name: host,
step: Step::AAAA(host_key),
},
]
}
pub(crate) fn on_txt(&mut self, inst_key: &str, segs: Vec<Bytes>) {
let mut changed = false;
if let Some(b) = self.builders.get_mut(inst_key) {
changed = b.txt.as_deref() != Some(segs.as_slice());
b.txt = Some(segs);
}
self.try_emit(inst_key, changed);
}
pub(crate) fn on_addr(&mut self, host_key: &str, addr: IpAddr) {
let cache = self.host_addrs.entry(SmolStr::from(host_key)).or_default();
match addr {
IpAddr::V4(a) => {
push_capped(&mut cache.ipv4, a);
}
IpAddr::V6(a) => {
push_capped(&mut cache.ipv6, a);
}
}
let keys: Vec<SmolStr> = self
.builders
.iter()
.filter(|(_, b)| b.host_key.as_deref() == Some(host_key))
.map(|(k, _)| k.clone())
.collect();
for k in keys {
let added = match self.builders.get_mut(&k) {
Some(b) => match addr {
IpAddr::V4(a) => push_capped(&mut b.ipv4, a),
IpAddr::V6(a) => push_capped(&mut b.ipv6, a),
},
None => false,
};
if added {
self.try_emit(&k, true);
}
}
}
pub(crate) fn try_emit(&mut self, inst_key: &str, allow_reemit: bool) {
if let Some(b) = self.builders.get_mut(inst_key) {
if !b.complete() {
return;
}
if !b.emitted {
if let Some(e) = b.finalize() {
b.emitted = true;
self.ready.push_back(e);
}
} else if allow_reemit && let Some(e) = b.finalize() {
self.ready.push_back(e);
}
}
}
pub(crate) fn take_ready(&mut self) -> Option<ServiceEntry> {
self.ready.pop_front()
}
}
pub struct Lookup {
pub(crate) endpoint: Endpoint,
pub(crate) queries: Vec<(Query, Step)>,
pub(crate) resolver: Resolver,
pub(crate) resolve_timeout: Duration,
pub(crate) unicast: bool,
pub(crate) finished: bool,
}
impl Lookup {
pub async fn next(&mut self) -> Option<ServiceEntry> {
loop {
if let Some(e) = self.resolver.take_ready() {
return Some(e);
}
if self.finished || self.queries.is_empty() {
self.finished = true;
return None;
}
let (result, idx) = {
let futs: Vec<_> = self
.queries
.iter()
.map(|(q, _)| Box::pin(q.next()))
.collect();
let (result, idx, _remaining) = select_all(futs).await;
(result, idx)
};
match result {
Some(QueryEvent::Answer(answer)) => {
let step = self.queries[idx].1.clone();
let starts = feed(&mut self.resolver, step, QueryEvent::Answer(answer));
self.launch_starts(starts).await;
}
Some(QueryEvent::Terminal(_)) | None => {
self.queries.swap_remove(idx);
if self.queries.is_empty() {
self.finished = true;
}
}
}
}
}
async fn launch_starts(&mut self, starts: Vec<Start>) {
for start in starts {
let qtype = start.qtype();
let step = start.step;
let spec = QuerySpec::new(start.name, qtype)
.with_timeout(self.resolve_timeout)
.with_unicast_response(self.unicast);
if let Ok(q) = self.endpoint.start_query(spec).await {
self.queries.push((q, step));
}
}
}
}
pub(crate) fn feed(resolver: &mut Resolver, step: Step, event: QueryEvent) -> Vec<Start> {
let answer = match event {
QueryEvent::Answer(a) => a,
QueryEvent::Terminal(_) => return Vec::new(),
};
match step {
Step::Ptr => {
if answer.rtype() != ResourceType::Ptr {
return Vec::new();
}
match parse_name(answer.rdata_slice()) {
Some(instance) => resolver.on_ptr(instance),
None => Vec::new(),
}
}
Step::Srv(inst_key) => {
if answer.rtype() != ResourceType::Srv {
return Vec::new();
}
match parse_srv(answer.rdata_slice()) {
Some((host, port)) => resolver.on_srv(&inst_key, host, port),
None => Vec::new(),
}
}
Step::Txt(inst_key) => {
if answer.rtype() != ResourceType::Txt {
return Vec::new();
}
resolver.on_txt(&inst_key, parse_txt(answer.rdata_slice()));
Vec::new()
}
Step::A(host_key) => {
if answer.rtype() == ResourceType::A
&& let Ok(r) = A::try_from_rdata(answer.rdata_slice())
{
resolver.on_addr(&host_key, IpAddr::V4(r.addr()));
}
Vec::new()
}
Step::AAAA(host_key) => {
if answer.rtype() == ResourceType::AAAA
&& let Ok(r) = AAAA::try_from_rdata(answer.rdata_slice())
{
resolver.on_addr(&host_key, IpAddr::V6(r.addr()));
}
Vec::new()
}
}
}