1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
time::Duration,
};
use crossbeam::thread;
use if_addrs::{IfAddr, Interface};
use crate::error::Error;
use crate::{datatypes::DeviceData, error::Result, protocol};
const QUERY: &str = r#"{
"system": {"get_sysinfo": null},
"emeter": {"get_realtime": null},
"smartlife.iot.dimmer": {"get_dimmer_parameters": null},
"smartlife.iot.common.emeter": {"get_realtime": null},
"smartlife.iot.smartbulb.lightingservice": {"get_light_state": null}
}"#;
fn can_interface_broadcast(iface: Interface) -> Option<(Ipv4Addr, Ipv4Addr)> {
match iface.addr {
IfAddr::V4(addr) => match (addr.ip.is_loopback(), addr.broadcast) {
(false, Some(broadcast)) => Some((addr.ip, broadcast)),
_ => None,
},
IfAddr::V6(_) => None,
}
}
fn discover_on_interface(
timeout: Option<Duration>,
ip: Ipv4Addr,
broadcast: Ipv4Addr,
request: &[u8],
) -> Result<HashMap<SocketAddr, DeviceData>> {
let socket_addr = SocketAddr::new(IpAddr::V4(ip), 0);
let udp_socket = UdpSocket::bind(socket_addr)?;
udp_socket.set_broadcast(true)?;
udp_socket.set_read_timeout(timeout)?;
let dest_socket_addr = SocketAddr::new(IpAddr::V4(broadcast), 9999);
for _ in 0..3 {
let _send_res = udp_socket.send_to(&request[4..request.len()], dest_socket_addr);
}
let mut buf = [0_u8; 4096];
let mut devices = HashMap::new();
while let Ok((size, addr)) = udp_socket.recv_from(&mut buf) {
let data = protocol::decrypt(&mut buf[0..size]);
if let Ok(device_data) = serde_json::from_str::<DeviceData>(&data) {
devices.insert(addr, device_data);
}
}
Ok(devices)
}
#[allow(clippy::needless_collect)]
pub fn with_timeout(timeout: Option<Duration>) -> Result<Vec<(SocketAddr, DeviceData)>> {
let request = protocol::encrypt(QUERY).unwrap();
let addrs = if_addrs::get_if_addrs()?;
thread::scope(|s| {
let handles = addrs
.into_iter()
.filter_map(can_interface_broadcast)
.map(|(ip, broadcast)| {
let request = &request;
s.spawn(move |_| discover_on_interface(timeout, ip, broadcast, request))
})
.collect::<Vec<_>>();
handles
.into_iter()
.filter_map(|join_handle| join_handle.join().ok().and_then(Result::ok))
.flat_map(|addresses| addresses)
.collect::<Vec<_>>()
})
.map_err(|_e| Error::Other("cannot discover devices".to_string()))
}
pub fn discover() -> Result<Vec<(SocketAddr, DeviceData)>> {
with_timeout(Some(Duration::from_secs(3)))
}