mod bulb;
use self::bulb::*;
use crate::device::*;
use net2::unix::UnixUdpBuilderExt;
use std::collections;
use std::error;
use std::io::{self, Write};
use std::iter;
use std::net;
use std::str::FromStr;
use std::sync;
use std::thread;
use std::time;
const DISCOVERY_PORT: u16 = 48899;
const DISCOVERY_MAGIC: &[u8] = b"HF-A11ASSISTHREAD";
pub fn command<'a, 'b>() -> clap::App<'a, 'b> {
clap::SubCommand::with_name("fluxled")
.about("TODO")
.arg(
clap::Arg::with_name("target")
.short("t")
.long("target")
.takes_value(true)
.min_values(1)
.multiple(true)
.validator(|addr| match net::IpAddr::from_str(addr.as_str()) {
Ok(_) => Ok(()),
Err(err) => Err(format!("{} ({})", err, addr)),
})
.conflicts_with_all(&["discover"])
.help("One or more target IP addresses"),
)
.arg(
clap::Arg::with_name("discover")
.short("d")
.long("discover")
.conflicts_with_all(&["target"])
.help("Discover Flux-LED nodes"),
)
.arg(
clap::Arg::with_name("network")
.short("n")
.long("net")
.takes_value(true)
.requires_all(&["discover"])
.help("The network range of where to look for devices in CIDR format"),
)
}
pub fn from_command(args: &clap::ArgMatches, _gargs: &GlobalArgs) -> io::Result<FromCommand> {
if args.is_present("discover") {
let network_range_rs = args
.value_of("network")
.map(|cidr| {
cidr.parse()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
})
.unwrap_or_else(Cidr::default_interface);
let network_range = match network_range_rs {
Ok(cidr) => cidr,
Err(err) => {
eprintln!(
"Could not guess which interface to use for discovery: {}",
err
);
eprintln!("Please set one using --net <cidr>");
return Ok(FromCommand::SubcommandHandled);
}
};
if let Err(err) = tui_discover(network_range) {
eprintln!("{}", err);
}
return Ok(FromCommand::SubcommandHandled);
}
let bulbs: Vec<_> = args
.values_of("target")
.unwrap()
.map(|addr| Bulb::new(addr.parse().unwrap()))
.collect();
let dev = Box::new(generic::Generic {
format: generic::Format::RGB24,
});
let output = Display {
bulbs,
buf: Vec::new(),
};
Ok(FromCommand::Output(Box::new((dev, output))))
}
fn tui_discover(network_range: Cidr) -> io::Result<()> {
let discovery_stream = discover(network_range);
let mut discovered: collections::HashSet<net::SocketAddr> = collections::HashSet::new();
let (close_tx, close_rx) = sync::mpsc::sync_channel(0);
thread::spawn(move || {
let mut out = io::stderr();
for ch in ['|', '/', '-', '\\'].iter().cycle() {
if close_rx.try_recv().is_ok() {
break;
}
write!(&mut out, "\r{}", ch).unwrap();
out.flush().unwrap();
thread::sleep(time::Duration::from_millis(100));
}
});
for result in discovery_stream {
let node = match result {
Ok(node) => node,
Err(err) => {
close_tx.send(()).unwrap();
eprint!("\r");
return Err(err);
}
};
if !discovered.contains(&node.0) {
let ip_str = format!("{}", node.0.ip()); match node.1 {
Some(name) => eprintln!("\r{: <15} -> {}", ip_str, name),
None => eprintln!("\r{: <15}", ip_str),
};
}
discovered.insert(node.0);
}
Ok(())
}
fn discover(
network_range: Cidr,
) -> sync::mpsc::Receiver<io::Result<(net::SocketAddr, Option<String>)>> {
let (tx, rx) = sync::mpsc::channel();
thread::spawn(move || {
macro_rules! try_or_send {
($expression:expr) => {
match $expression {
Ok(val) => val,
Err(err) => {
tx.send(Err(err)).unwrap();
return;
}
}
};
}
let socket = {
let b = try_or_send!(net2::UdpBuilder::new_v4());
try_or_send!(b.reuse_address(true));
try_or_send!(b.reuse_port(true));
try_or_send!(b.bind(("0.0.0.0", DISCOVERY_PORT)))
};
try_or_send!(socket.set_broadcast(true));
try_or_send!(socket.set_read_timeout(Some(time::Duration::from_secs(1))));
loop {
for ip in network_range.addresses() {
let addr = net::SocketAddr::new(net::IpAddr::V4(ip), DISCOVERY_PORT);
try_or_send!(socket.send_to(DISCOVERY_MAGIC, addr));
}
loop {
let mut recv_buf = [0; 64];
let (_, sender_addr) = match socket.recv_from(&mut recv_buf) {
Err(_) => break,
Ok(rs) => rs,
};
if recv_buf.starts_with(DISCOVERY_MAGIC) {
continue;
}
let name = String::from_utf8_lossy(&recv_buf).into_owned();
tx.send(Ok((sender_addr, Some(name)))).unwrap();
}
}
});
rx
}
struct Cidr {
addr: net::IpAddr,
mask: net::IpAddr,
}
impl Cidr {
fn addresses(&self) -> impl iter::Iterator<Item = net::Ipv4Addr> {
match (self.addr, self.mask) {
(net::IpAddr::V4(network_ip), net::IpAddr::V4(mask_ip)) => {
let network: u32 = network_ip.into();
let mask: u32 = mask_ip.into();
let start = network & mask;
let end = start | !mask;
(start..end).map(net::Ipv4Addr::from)
}
(net::IpAddr::V6(_network), net::IpAddr::V6(_mask)) => unimplemented!(),
_ => unreachable!(),
}
}
#[cfg(target_os = "linux")]
fn default_interface() -> io::Result<Cidr> {
use nix::net::if_::InterfaceFlags;
use nix::sys::socket::{InetAddr, SockAddr};
nix::ifaddrs::getifaddrs()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?
.filter(|iface| iface.address.is_some() && iface.netmask.is_some())
.filter(|iface| !iface.flags.contains(InterfaceFlags::IFF_LOOPBACK))
.filter(|iface| iface.flags.contains(InterfaceFlags::IFF_LOWER_UP))
.filter(|iface| matches!(iface.address, Some(SockAddr::Inet(InetAddr::V4(_)))))
.filter_map(|iface| {
let addr = match iface.address.unwrap() {
SockAddr::Inet(a) => a.to_std().ip(),
_ => return None,
};
let mask = match iface.netmask.unwrap() {
SockAddr::Inet(a) => a.to_std().ip(),
_ => return None,
};
Some(Cidr { addr, mask })
})
.next()
.ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "Unable to determine default network")
})
}
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
))]
fn default_interface() -> io::Result<Cidr> {
Err(io::Error::new(
io::ErrorKind::Other,
"Platform is not supported",
))
}
}
impl FromStr for Cidr {
type Err = Box<dyn error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Cidr, Self::Err> {
let mut split = s.split('/');
let addr: net::IpAddr = split
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "missing the address of the CIDR"))?
.parse()?;
let mask_str = split
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "missing the mask of the CIDR"))?;
let mask: net::IpAddr =
mask_str
.parse()
.or_else(|_| -> Result<_, Box<dyn error::Error + Send + Sync>> {
let bits: u32 = mask_str.parse()?;
Ok(net::IpAddr::V4(net::Ipv4Addr::from(
!((0x8000_0000 >> (bits - 1)) - 1),
)))
})?;
Ok(Cidr { addr, mask })
}
}