use std::any::Any;
use std::net::IpAddr;
use std::time::{Duration, Instant};
use crate::backend::{BackendKind, EtherDreamBackend, Result};
use crate::device::DacType;
use crate::discovery::{downcast_connect_data, DiscoveredDevice, DiscoveredDeviceInfo, Discoverer};
use crate::protocols::ether_dream::protocol::DacBroadcast;
use crate::protocols::ether_dream::recv_dac_broadcasts;
const PREFIX: &str = "etherdream";
struct ConnectData {
broadcast: DacBroadcast,
ip: IpAddr,
}
pub struct EtherDreamDiscoverer {
timeout: Duration,
}
impl EtherDreamDiscoverer {
pub fn new() -> Self {
Self {
timeout: Duration::from_millis(1500),
}
}
}
impl Default for EtherDreamDiscoverer {
fn default() -> Self {
Self::new()
}
}
fn format_mac(mac: [u8; 6]) -> String {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
)
}
impl Discoverer for EtherDreamDiscoverer {
fn dac_type(&self) -> DacType {
DacType::EtherDream
}
fn prefix(&self) -> &str {
PREFIX
}
fn scan(&mut self) -> Vec<DiscoveredDevice> {
let mut rx = match recv_dac_broadcasts() {
Ok(rx) => rx,
Err(e) => {
log::warn!("Ether Dream discovery: failed to bind UDP broadcast socket: {e}");
return Vec::new();
}
};
if let Err(e) = rx.set_timeout(Some(Duration::from_millis(200))) {
log::warn!("Ether Dream discovery: failed to set socket timeout: {e}");
return Vec::new();
}
let mut discovered = Vec::new();
let mut seen_macs = std::collections::HashSet::new();
let deadline = Instant::now() + self.timeout;
while Instant::now() < deadline {
let (broadcast, source_addr) = match rx.next_broadcast() {
Ok(b) => b,
Err(e)
if matches!(
e.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) =>
{
continue;
}
Err(e) => {
log::warn!("Ether Dream discovery: recv error: {e}");
continue;
}
};
let ip = source_addr.ip();
let device_mac = broadcast.mac_address;
if !seen_macs.insert(device_mac) {
continue;
}
let stable_id = format!("{}:{}", PREFIX, format_mac(device_mac));
let info = DiscoveredDeviceInfo::new(DacType::EtherDream, stable_id, ip.to_string())
.with_ip(ip)
.with_mac(device_mac);
discovered.push(DiscoveredDevice::new(
info,
Box::new(ConnectData { broadcast, ip }),
));
}
discovered
}
fn connect(&mut self, opaque: Box<dyn Any + Send>) -> Result<BackendKind> {
let data = downcast_connect_data::<ConnectData>(opaque, "EtherDream")?;
Ok(BackendKind::Fifo(Box::new(EtherDreamBackend::new(
data.broadcast,
data.ip,
))))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn etherdream_stable_id_uses_mac() {
let mac = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab];
assert_eq!(format_mac(mac), "01:23:45:67:89:ab");
let info = DiscoveredDeviceInfo::new(
DacType::EtherDream,
format!("{}:{}", PREFIX, format_mac(mac)),
"192.168.1.100",
)
.with_ip("192.168.1.100".parse().unwrap())
.with_mac(mac);
assert_eq!(info.stable_id(), "etherdream:01:23:45:67:89:ab");
}
}