use core::mem;
use alloc::{
format,
string::{String, ToString},
};
use domain::new::{
base::{
Record,
name::{NameBuf, RevNameBuf},
},
rdata::Srv,
};
use thiserror::Error;
use url::Url;
use crate::{
coroutine::{DiscoveryCoroutine, DiscoveryCoroutineState, DiscoveryYield},
rfc6186::types::SrvService,
rfc6764::{
srv::{DiscoveryDnsSrv, DiscoveryDnsSrvError},
types::Rfc6764Report,
},
};
#[derive(Debug, Error)]
pub enum DiscoveryRfc6764Error {
#[error("DNS SRV lookup for `_caldav._tcp` failed: {0}")]
Caldav(#[source] DiscoveryDnsSrvError),
#[error("DNS SRV lookup for `_caldavs._tcp` failed: {0}")]
Caldavs(#[source] DiscoveryDnsSrvError),
#[error("DNS SRV lookup for `_carddav._tcp` failed: {0}")]
Carddav(#[source] DiscoveryDnsSrvError),
#[error("DNS SRV lookup for `_carddavs._tcp` failed: {0}")]
Carddavs(#[source] DiscoveryDnsSrvError),
}
#[derive(Default)]
enum State {
Caldav(DiscoveryDnsSrv),
Caldavs(DiscoveryDnsSrv),
Carddav(DiscoveryDnsSrv),
Carddavs(DiscoveryDnsSrv),
#[default]
Done,
}
pub struct DiscoveryRfc6764 {
state: State,
domain: String,
resolver: Url,
report: Rfc6764Report,
}
impl DiscoveryRfc6764 {
pub fn new(domain: impl AsRef<str>, resolver: Url) -> Self {
let domain = domain.as_ref().trim_matches('.').to_string();
let caldav = DiscoveryDnsSrv::new(format!("_caldav._tcp.{domain}"), resolver.clone());
Self {
state: State::Caldav(caldav),
domain,
resolver,
report: Rfc6764Report::default(),
}
}
}
impl DiscoveryCoroutine for DiscoveryRfc6764 {
type Yield = DiscoveryYield;
type Return = Result<Rfc6764Report, DiscoveryRfc6764Error>;
fn resume(&mut self, arg: Option<&[u8]>) -> DiscoveryCoroutineState<Self::Yield, Self::Return> {
match mem::take(&mut self.state) {
State::Caldav(mut srv) => match srv.resume(arg) {
DiscoveryCoroutineState::Complete(Ok(records)) => {
self.report.caldav = records.into_iter().next().map(into_service);
self.state = State::Caldavs(DiscoveryDnsSrv::new(
format!("_caldavs._tcp.{}", self.domain),
self.resolver.clone(),
));
self.resume(None)
}
DiscoveryCoroutineState::Yielded(y) => {
self.state = State::Caldav(srv);
DiscoveryCoroutineState::Yielded(y)
}
DiscoveryCoroutineState::Complete(Err(err)) => {
DiscoveryCoroutineState::Complete(Err(DiscoveryRfc6764Error::Caldav(err)))
}
},
State::Caldavs(mut srv) => match srv.resume(arg) {
DiscoveryCoroutineState::Complete(Ok(records)) => {
self.report.caldavs = records.into_iter().next().map(into_service);
self.state = State::Carddav(DiscoveryDnsSrv::new(
format!("_carddav._tcp.{}", self.domain),
self.resolver.clone(),
));
self.resume(None)
}
DiscoveryCoroutineState::Yielded(y) => {
self.state = State::Caldavs(srv);
DiscoveryCoroutineState::Yielded(y)
}
DiscoveryCoroutineState::Complete(Err(err)) => {
DiscoveryCoroutineState::Complete(Err(DiscoveryRfc6764Error::Caldavs(err)))
}
},
State::Carddav(mut srv) => match srv.resume(arg) {
DiscoveryCoroutineState::Complete(Ok(records)) => {
self.report.carddav = records.into_iter().next().map(into_service);
self.state = State::Carddavs(DiscoveryDnsSrv::new(
format!("_carddavs._tcp.{}", self.domain),
self.resolver.clone(),
));
self.resume(None)
}
DiscoveryCoroutineState::Yielded(y) => {
self.state = State::Carddav(srv);
DiscoveryCoroutineState::Yielded(y)
}
DiscoveryCoroutineState::Complete(Err(err)) => {
DiscoveryCoroutineState::Complete(Err(DiscoveryRfc6764Error::Carddav(err)))
}
},
State::Carddavs(mut srv) => match srv.resume(arg) {
DiscoveryCoroutineState::Complete(Ok(records)) => {
self.report.carddavs = records.into_iter().next().map(into_service);
DiscoveryCoroutineState::Complete(Ok(mem::take(&mut self.report)))
}
DiscoveryCoroutineState::Yielded(y) => {
self.state = State::Carddavs(srv);
DiscoveryCoroutineState::Yielded(y)
}
DiscoveryCoroutineState::Complete(Err(err)) => {
DiscoveryCoroutineState::Complete(Err(DiscoveryRfc6764Error::Carddavs(err)))
}
},
State::Done => panic!("DiscoveryRfc6764::resume called after completion"),
}
}
}
fn into_service(record: Record<RevNameBuf, Srv<NameBuf>>) -> SrvService {
SrvService {
host: record
.rdata
.target
.to_string()
.trim_end_matches('.')
.to_string(),
port: record.rdata.port.get(),
priority: record.rdata.priority.get(),
weight: record.rdata.weight.get(),
}
}