rbroadlink/network/
util.rs

1//! Set of utility methods useful when working with network requests.
2
3use std::{
4    net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
5    slice::ChunksExact,
6    time::Duration,
7};
8
9/// Computes the checksum of a slice of bytes.
10///
11/// The checksum is computed by summing all of the bytes with 0xBEAF and masking
12/// with 0xFFFF.
13pub fn checksum(data: &[u8]) -> u16 {
14    // Get the checksum
15    let mut sum = 0xBEAFu32;
16    for &d in data {
17        sum += u32::from(d);
18    }
19
20    return sum as u16;
21}
22
23/// Computes the generic checksum of a bytes array.
24///
25/// This iterates all 16-bit array elements, summing
26/// the values into a 32-bit variable. This functions
27/// paddies with zero an octet at the end (if necessary)
28/// to turn into a 16-bit element.
29pub fn compute_generic_checksum(buf: &[u8]) -> u16 {
30    let mut state: u32 = 0xFFFF;
31
32    let mut chunks_iter: ChunksExact<u8> = buf.chunks_exact(2);
33    while let Some(chunk) = chunks_iter.next() {
34        state += u16::from_le_bytes([chunk[0], chunk[1]]) as u32;
35    }
36
37    if let Some(&b) = chunks_iter.remainder().get(0) {
38        state += u16::from_le_bytes([b, 0]) as u32;
39    }
40
41    state = (state >> 16) + (state & 0xffff);
42    state = !state & 0xffff;
43
44    state as u16
45}
46
47/// Returns the first available non-local address or the passed IP, if present.
48pub fn local_ip_or(ip: Option<Ipv4Addr>) -> Result<IpAddr, String> {
49    Ok(match ip {
50        Some(ip) => IpAddr::V4(ip),
51        None => get_if_addrs::get_if_addrs()
52            .map_err(|e| {
53                format!(
54                    "Could not automatically determine machine IP address. {}",
55                    e
56                )
57            })?
58            .iter()
59            .find(|x| x.ip().is_ipv4() && !x.ip().is_loopback())
60            .ok_or("Could not find a local IPv4 address!")?
61            .ip(),
62    })
63}
64
65/// Sends a message and returns the received response.
66fn send_and_receive_impl(
67    msg: &[u8],
68    addr: Ipv4Addr,
69    port: Option<u16>,
70) -> Result<UdpSocket, String> {
71    // Set up the socket addresses
72    let unspecified_addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port.unwrap_or(0)));
73    let destination_addr = SocketAddr::from((addr, 80));
74
75    // Set up the communication socket
76    // Note: We need to enable support for broadcast
77    let socket = UdpSocket::bind(unspecified_addr)
78        .map_err(|e| format!("Could not bind to any port. {}", e))?;
79    socket
80        .set_broadcast(true)
81        .map_err(|e| format!("Could not enable broadcast. {}", e))?;
82
83    // Send the message
84    socket
85        .set_read_timeout(Some(Duration::new(10, 0)))
86        .map_err(|e| format!("Could not set read timeout! {}", e))?;
87    socket
88        .send_to(&msg, destination_addr)
89        .map_err(|e| format!("Could not broadcast message! {}", e))?;
90
91    return Ok(socket);
92}
93
94/// Sends a message and returns the as many received responses as possible (within a timeout).
95pub fn send_and_receive_many<I, T>(
96    msg: &[u8],
97    addr: Ipv4Addr,
98    port: Option<u16>,
99    cb: T,
100) -> Result<Vec<I>, String>
101where
102    T: Fn(usize, &[u8], SocketAddr) -> Result<I, String>,
103{
104    // Get the socket
105    let socket = send_and_receive_impl(msg, addr, port)
106        .map_err(|e| format!("Could not create socket for message sending! {}", e))?;
107
108    // Transform the results
109    let mut results: Vec<I> = vec![];
110    let mut recv_buffer = [0u8; 8092];
111    while let Ok((bytes_received, addr)) = socket.recv_from(&mut recv_buffer) {
112        results.push(cb(bytes_received, &recv_buffer[0..bytes_received], addr)?);
113    }
114
115    return Ok(results);
116}
117
118/// Sends a message and returns the first received response.
119pub fn send_and_receive_one<I, T>(
120    msg: &[u8],
121    addr: Ipv4Addr,
122    port: Option<u16>,
123    cb: T,
124) -> Result<I, String>
125where
126    T: Fn(usize, &[u8], SocketAddr) -> Result<I, String>,
127{
128    // Get the socket
129    let socket = send_and_receive_impl(msg, addr, port)
130        .map_err(|e| format!("Could not create socket for message sending! {}", e))?;
131
132    // Transform the result
133    let mut recv_buffer = [0u8; 8092];
134    if let Ok((bytes_received, addr)) = socket.recv_from(&mut recv_buffer) {
135        return Ok(cb(bytes_received, &recv_buffer[0..bytes_received], addr)?);
136    }
137
138    return Err("No response within timeout!".into());
139}
140
141/// Reverses a MAC address. Used to fix the backwards response from the broadlink device.
142pub fn reverse_mac(mac_flipped: [u8; 6]) -> [u8; 6] {
143    // Fix the mac address by reversing it.
144    let mut mac = [0u8; 6];
145    for i in 0..6 {
146        mac[i] = mac_flipped[6 - i - 1];
147    }
148
149    return mac;
150}