its at least better lol

This commit is contained in:
Alex Janka 2024-02-24 18:46:31 +11:00
parent 2104a2f0b0
commit 43c76585bc
5 changed files with 749 additions and 332 deletions

966
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@ hex = { version = "0.4", features = ["serde"] }
http = "1.0" http = "1.0"
httparse = "1.8" httparse = "1.8"
tokio = { version = "1.36", features = ["net"] } tokio = { version = "1.36", features = ["net"] }
zeroconf = "0.14"
strum = "0.26" strum = "0.26"
strum_macros = "0.26" strum_macros = "0.26"
mdns = "3.0.0"
futures-util = "0.3.30"

View file

@ -1,21 +1,16 @@
use std::{ use std::{collections::HashMap, time::Duration};
collections::HashMap,
sync::{Arc, RwLock},
time::Duration,
};
use chacha20poly1305::{ use chacha20poly1305::{
aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce, aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce,
}; };
use futures_util::{pin_mut, StreamExt};
use http::{Method, Request}; use http::{Method, Request};
use mdns::RecordKind;
use thiserror::Error; use thiserror::Error;
use tokio::{ use tokio::{
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream, net::TcpStream,
}; time::timeout,
use zeroconf::{
browser::TMdnsBrowser, event_loop::TEventLoop, txt_record::TTxtRecord, MdnsBrowser,
ServiceDiscovery, ServiceType,
}; };
use crate::{ use crate::{
@ -49,46 +44,72 @@ impl SocketEncryption {
} }
} }
pub async fn discover(duration_seconds: u64) -> Result<Vec<ServiceDiscovery>, DiscoveryError> { const SERVICE_NAME: &str = "_hap._tcp.local";
let service_type = ServiceType::new("hap", "tcp")?;
let mut browser = MdnsBrowser::new(service_type);
let discovered: Arc<RwLock<Vec<ServiceDiscovery>>> = Arc::new(RwLock::new(Vec::new())); pub async fn discover(
let d2 = discovered.clone(); duration_seconds: u64,
pairing_id: &str,
) -> Result<(String, u16), DiscoveryError> {
let stream = mdns::discover::all(SERVICE_NAME, Duration::from_secs(1))?.listen();
browser.set_service_discovered_callback(Box::new(move |r, _| { pin_mut!(stream);
if let Ok(r) = r { while let Ok(Some(Ok(response))) =
discovered.write().unwrap().push(r) timeout(Duration::from_secs(duration_seconds), stream.next()).await
{
if let Some(name) = response.additional.iter().find_map(|record| {
if let RecordKind::TXT(v) = &record.kind {
if v.contains(&format!("id={pairing_id}")) {
return Some(record.name.clone());
}
}
None
}) {
log::info!("got name {name}");
if let Some((target, port)) = response.additional.iter().find_map(|record| {
if record.name == name {
if let RecordKind::SRV {
priority: _,
weight: _,
port,
target,
} = &record.kind
{
return Some((target.clone(), *port));
}
}
None
}) {
if let Some(ip) = response.additional.iter().find_map(|record| {
if record.name == target {
if let RecordKind::A(ip) = record.kind {
return Some(ip);
}
}
None
}) {
return Ok((ip.to_string(), port));
}
}
} }
}));
let event_loop = browser.browse_services()?; // if let Some(addr) = addr {
event_loop.poll(Duration::from_secs(duration_seconds))?; // println!("found cast device at {}", addr);
// } else {
// println!("cast device does not advertise address");
// }
}
let result = d2.read().unwrap().clone(); todo!()
Ok(result)
} }
async fn reconnect(pairing_id: &str) -> Result<TcpStream, HomekitError> { async fn reconnect(pairing_id: &str) -> Result<TcpStream, HomekitError> {
log::warn!("error connecting to device..."); log::warn!("error connecting to device...");
log::warn!("trying to find accessory ip/port via bonjour/mdns..."); log::warn!("trying to find {pairing_id}'s ip/port via bonjour/mdns...");
let discovered = discover(20).await?; let (hostname, port) = discover(20, pairing_id).await?;
if let Some((hostname, port)) = discovered log::info!("successfully found device at {hostname}:{port}");
.iter() let socket = TcpStream::connect(format!("{hostname}:{port}")).await?;
.find(|d| { log::info!(" ...and connected!");
d.txt() Ok(socket)
.as_ref()
.map(|t| t.get("id"))
.is_some_and(|txt| txt.is_some_and(|id| id == pairing_id))
})
.map(|d| (d.host_name(), d.port()))
{
let socket = TcpStream::connect(format!("{hostname}:{port}")).await?;
log::info!("successfully found device at {hostname}:{port}");
Ok(socket)
} else {
Err(HomekitError::DeviceNotFound)
}
} }
impl AccessorySocket { impl AccessorySocket {
@ -351,6 +372,6 @@ impl AccessorySocket {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum DiscoveryError { pub enum DiscoveryError {
#[error("zeroconf")] #[error("mdns")]
Zeroconf(#[from] zeroconf::error::Error), Mdns(#[from] mdns::Error),
} }

View file

@ -332,6 +332,8 @@ pub enum HomekitError {
Discovery(#[from] DiscoveryError), Discovery(#[from] DiscoveryError),
#[error("device not found")] #[error("device not found")]
DeviceNotFound, DeviceNotFound,
#[error("timeout")]
Timeout(#[from] tokio::time::error::Elapsed),
} }
impl From<TlvError> for HomekitError { impl From<TlvError> for HomekitError {

View file

@ -36,9 +36,12 @@ pub async fn metrics(state: &State<Mutex<HashMap<String, ConnectedDevice>>>) ->
} }
s.push_str( s.push_str(
format!( format!(
"{}{{accessory=\"{}.{}.{}\"}} {}\n", "{}{{hub=\"{}\",accessory=\"{}.{}\",service=\"{}.{}.{}\"}} {}\n",
characteristic.characteristic_type, characteristic.characteristic_type,
name, name,
name,
aid,
name,
aid, aid,
cid, cid,
match value { match value {