#![deny(missing_docs)]
#![warn(clippy::unwrap_used)]
use std::net::UdpSocket;
use std::time::Duration;
use log::{info, trace};
use std::fmt::Formatter;
use std::io;
const BROADCAST_ADDRESS : &str = "255.255.255.255";
const LISTENING_ADDRESS : &str = "0.0.0.0";
const MAX_INCOMING_BEACON_SIZE : usize = 1024;
const MAGIC_NUMBER: u16 = 0xbeef;
pub struct BeaconSender {
socket: UdpSocket,
beacon_payload: Vec<u8>,
broadcast_address: String,
}
fn u16_to_array_of_u8(x:u16) -> [u8;2] {
let b1 : u8 = ((x >> 8) & 0xff) as u8;
let b2 : u8 = (x & 0xff) as u8;
[b1, b2]
}
fn array_of_u8_to_u16(array: &[u8]) -> u16 {
let upper : u16 = (array[0] as u16) << 8;
let lower : u16 = array[1] as u16;
upper + lower
}
impl BeaconSender {
pub fn new(service_port: u16, service_name: &[u8], broadcast_port: u16) -> io::Result<Self> {
let bind_address = format!("{LISTENING_ADDRESS}:0");
let socket:UdpSocket = UdpSocket::bind(&bind_address)
.map_err(|e|
io::Error::new(io::ErrorKind::AddrInUse,
format!("SimpDiscover::BeaconSender could not bind to UdpSocket {bind_address} ({e})")))?;
info!("Socket bound to: {}", bind_address);
socket.set_broadcast(true)?;
info!("Broadcast mode set to ON");
let mut beacon_payload: Vec<u8> = u16_to_array_of_u8(MAGIC_NUMBER).to_vec();
beacon_payload.append(&mut u16_to_array_of_u8(service_port).to_vec());
beacon_payload.append(&mut service_name.to_vec());
let broadcast_address = format!("{BROADCAST_ADDRESS}:{broadcast_port}");
Ok(Self {
socket,
beacon_payload,
broadcast_address,
})
}
pub fn send_loop(&self, period: Duration) -> io::Result<()> {
loop {
self.send_one_beacon()?;
std::thread::sleep(period);
}
}
pub fn send_one_beacon(&self) -> io::Result<usize> {
trace!("Sending Beacon '{}' to: '{}'", String::from_utf8_lossy(&self.beacon_payload[4..]),
self.broadcast_address);
self.socket.send_to(&self.beacon_payload, &self.broadcast_address)
}
}
pub struct Beacon {
pub service_ip: String,
pub service_port: u16,
pub service_name: Vec<u8>
}
impl std::fmt::Display for Beacon {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let service_name = String::from_utf8(self.service_name.clone()).unwrap_or_else(|_| "Invalid UTF-8 String".into());
write!(f, "ServiceName: '{}', Service IP: {}, Service Port: {}", service_name, self.service_ip, self.service_port)
}
}
pub struct BeaconListener {
socket: UdpSocket,
service_name: Vec<u8>,
}
impl BeaconListener {
pub fn new(service_name: &[u8], listening_port: u16) -> io::Result<Self> {
let listening_address = format!("{}:{}", LISTENING_ADDRESS, listening_port);
let socket = UdpSocket::bind(&listening_address)
.map_err(|e|
io::Error::new(io::ErrorKind::AddrInUse,
format!("SimpDiscover::BeaconListener could not bind to UdpSocket at {listening_address} ({e})")))?;
trace!("Socket bound to: {}", listening_address);
socket.set_broadcast(true)?;
Ok(Self {
socket,
service_name: service_name.to_vec(),
})
}
pub fn wait(&self, timeout: Option<Duration>) -> io::Result<Beacon> {
self.socket.set_read_timeout(timeout)?;
info!("Read timeout set to: {:?}", timeout);
info!("Waiting for beacon matching '{}'", String::from_utf8_lossy(&self.service_name));
loop {
let beacon = self.receive_one_beacon()?;
if beacon.service_name == self.service_name {
trace!("Beacon '{}' matches filter '{}': returning beacon",
String::from_utf8_lossy(&beacon.service_name), String::from_utf8_lossy(&self.service_name));
return Ok(beacon);
} else {
trace!("Beacon '{}' does not match filter '{}': ignoring",
String::from_utf8_lossy(&beacon.service_name), String::from_utf8_lossy(&self.service_name));
}
}
}
fn receive_one_beacon(&self) -> io::Result<Beacon> {
let mut buffer = [0; MAX_INCOMING_BEACON_SIZE];
loop {
let (number_of_bytes, source_address) = self.socket.recv_from(&mut buffer)?;
let magic_number = array_of_u8_to_u16(&buffer[0..2]);
if magic_number == MAGIC_NUMBER {
let service_port = array_of_u8_to_u16(&buffer[2..4]);
let service_name = buffer[4..number_of_bytes].to_vec();
return Ok(Beacon {
service_ip: source_address.ip().to_string(),
service_port,
service_name
});
}
}
}
}