#![cfg(feature = "mdns")]
use std::borrow::Cow;
use std::collections::HashMap;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_util::future::Future;
use proto::rr::rdata::TXT;
use proto::rr::{Name, RecordType};
use proto::xfer::DnsRequestOptions;
use proto::DnsHandle;
use crate::error::*;
use crate::lookup::{ReverseLookup, ReverseLookupIter, TxtLookup};
use crate::name_server::ConnectionProvider;
use crate::AsyncResolver;
pub trait DnsSdHandle {
fn list_services(&self, name: Name) -> ListServicesFuture;
fn service_info(&self, name: Name) -> ServiceInfoFuture;
}
impl<C: DnsHandle<Error = ResolveError>, P: ConnectionProvider<Conn = C>> DnsSdHandle
for AsyncResolver<C, P>
{
fn list_services(&self, name: Name) -> ListServicesFuture {
let this = self.clone();
let ptr_future = async move {
let mut options = DnsRequestOptions::default();
options.expects_multiple_responses = true;
options.use_edns = false;
this.inner_lookup(name, RecordType::PTR, options).await
};
ListServicesFuture(Box::pin(ptr_future))
}
fn service_info(&self, name: Name) -> ServiceInfoFuture {
let this = self.clone();
let ptr_future = async move { this.txt_lookup(name).await };
ServiceInfoFuture(Box::pin(ptr_future))
}
}
pub struct ListServicesFuture(
Pin<Box<dyn Future<Output = Result<ReverseLookup, ResolveError>> + Send + 'static>>,
);
impl Future for ListServicesFuture {
type Output = Result<ListServices, ResolveError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.0.as_mut().poll(cx) {
Poll::Ready(Ok(lookup)) => Poll::Ready(Ok(ListServices(lookup))),
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
}
}
}
pub struct ListServices(ReverseLookup);
impl ListServices {
pub fn iter(&self) -> ListServicesIter<'_> {
ListServicesIter(self.0.iter())
}
}
pub struct ListServicesIter<'i>(ReverseLookupIter<'i>);
impl<'i> Iterator for ListServicesIter<'i> {
type Item = &'i Name;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub struct ServiceInfoFuture(
Pin<Box<dyn Future<Output = Result<TxtLookup, ResolveError>> + Send + 'static>>,
);
impl Future for ServiceInfoFuture {
type Output = Result<ServiceInfo, ResolveError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.0.as_mut().poll(cx) {
Poll::Ready(Ok(lookup)) => Poll::Ready(Ok(ServiceInfo(lookup))),
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
}
}
}
pub struct ServiceInfo(TxtLookup);
impl ServiceInfo {
pub fn to_map(&self) -> HashMap<Cow<'_, str>, Option<Cow<'_, str>>> {
self.0
.iter()
.flat_map(TXT::iter)
.filter_map(|bytes| {
let mut split = bytes.split(|byte| *byte == b'=');
let key = split.next().map(String::from_utf8_lossy);
let value = split.next().map(String::from_utf8_lossy);
if let Some(key) = key {
Some((key, value))
} else {
None
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use std::str::FromStr;
use tokio::runtime::Runtime;
use crate::config::*;
use crate::{TokioAsyncResolver, TokioHandle};
use super::*;
#[test]
#[ignore]
fn test_list_services() {
let io_loop = Runtime::new().unwrap();
let resolver = TokioAsyncResolver::new(
ResolverConfig::default(),
ResolverOpts {
ip_strategy: LookupIpStrategy::Ipv6thenIpv4,
..ResolverOpts::default()
},
TokioHandle,
)
.expect("failed to create resolver");
let response = io_loop
.block_on(resolver.list_services(Name::from_str("_http._tcp.local.").unwrap()))
.expect("failed to run lookup");
for name in response.iter() {
println!("service: {}", name);
let srvs = io_loop
.block_on(resolver.srv_lookup(name.clone()))
.expect("failed to lookup name");
for srv in srvs.iter() {
println!("service: {:#?}", srv);
let info = io_loop
.block_on(resolver.service_info(name.clone()))
.expect("info failed");
let info = info.to_map();
println!("info: {:#?}", info);
}
for ip in srvs.ip_iter() {
println!("ip: {}", ip);
}
}
}
}