pub mod backend;
pub mod dac;
pub mod error;
pub mod protocol;
use dac::{RelayInfo, ServerInfo, ServiceInfo};
use log::debug;
use protocol::{
PacketHeader, ReadBytes, ScanResponse, ServiceMapEntry, ServiceMapResponseHeader, SizeBytes,
WriteBytes, IDNCMD_SCAN_REQUEST, IDNCMD_SCAN_RESPONSE, IDNCMD_SERVICEMAP_REQUEST,
IDNCMD_SERVICEMAP_RESPONSE, IDNMSK_PKTFLAGS_GROUP, IDN_PORT,
};
use socket2::{Domain, Protocol, Socket, Type};
use std::collections::HashMap;
use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::time::{Duration, Instant};
pub use backend::IdnBackend;
pub use dac::{stream, Addressed, ServiceType};
pub use error::{CommunicationError, ProtocolError, ResponseError, Result};
pub use protocol::{AcknowledgeResponse, Point, PointExtended, PointXyrgbHighRes, PointXyrgbi};
use crate::types::{DacCapabilities, OutputModel};
pub fn default_capabilities() -> DacCapabilities {
DacCapabilities {
pps_min: 1,
pps_max: 100_000,
max_points_per_chunk: 179,
output_model: OutputModel::UdpTimed,
}
}
pub const DEFAULT_CLIENT_GROUP: u8 = 0;
pub fn scan_for_servers(timeout: Duration) -> io::Result<Vec<ServerInfo>> {
scan_for_servers_with_group(timeout, DEFAULT_CLIENT_GROUP)
}
pub fn scan_for_servers_with_group(
timeout: Duration,
client_group: u8,
) -> io::Result<Vec<ServerInfo>> {
let mut scanner = ServerScanner::new(client_group)?;
scanner.scan(timeout)
}
struct BroadcastEndpoint {
socket: UdpSocket,
broadcast_addr: SocketAddrV4,
}
pub struct ServerScanner {
endpoints: Vec<BroadcastEndpoint>,
unicast_socket: UdpSocket,
client_group: u8,
sequence: u16,
buffer: [u8; 1500],
}
impl ServerScanner {
pub fn new(client_group: u8) -> io::Result<Self> {
let client_group = client_group & IDNMSK_PKTFLAGS_GROUP;
let mut endpoints = Vec::new();
if let Ok(interfaces) = crate::net_utils::get_local_interfaces() {
for iface in &interfaces {
match Self::create_broadcast_socket(iface.ip) {
Ok(socket) => {
endpoints.push(BroadcastEndpoint {
socket,
broadcast_addr: SocketAddrV4::new(iface.broadcast_address(), IDN_PORT),
});
}
Err(e) => {
debug!(
"IDN: failed to create broadcast socket for {}: {}",
iface.ip, e
);
}
}
}
}
let unicast_socket = Self::create_unicast_socket()?;
Ok(Self {
endpoints,
unicast_socket,
client_group,
sequence: 0,
buffer: [0u8; 1500],
})
}
fn create_broadcast_socket(bind_ip: Ipv4Addr) -> io::Result<UdpSocket> {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
socket.set_broadcast(true)?;
socket.set_nonblocking(true)?;
let bind_addr = SocketAddrV4::new(bind_ip, 0);
socket.bind(&socket2::SockAddr::from(bind_addr))?;
Ok(UdpSocket::from(socket))
}
fn create_unicast_socket() -> io::Result<UdpSocket> {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
socket.set_broadcast(true)?;
let bind_addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
socket.bind(&socket2::SockAddr::from(bind_addr))?;
Ok(UdpSocket::from(socket))
}
pub fn scan(&mut self, timeout: Duration) -> io::Result<Vec<ServerInfo>> {
let start = Instant::now();
let mut servers: HashMap<[u8; 16], ServerInfo> = HashMap::new();
let mut addr_to_unit: HashMap<SocketAddr, [u8; 16]> = HashMap::new();
self.send_broadcast_scan()?;
self.unicast_socket.set_nonblocking(true)?;
while start.elapsed() < timeout {
let mut received_any = false;
for i in 0..self.endpoints.len() {
let mut buf = [0u8; 1500];
match self.endpoints[i].socket.recv_from(&mut buf) {
Ok((len, src_addr)) => {
if let Some((response, src_addr)) =
Self::process_scan_response(&buf[..len], &src_addr)
{
Self::record_server(
&mut servers,
&mut addr_to_unit,
response,
src_addr,
);
}
received_any = true;
}
Err(e)
if e.kind() == io::ErrorKind::WouldBlock
|| e.kind() == io::ErrorKind::TimedOut =>
{
}
Err(_) => {}
}
}
match self.unicast_socket.recv_from(&mut self.buffer) {
Ok((len, src_addr)) => {
if let Some((response, src_addr)) =
Self::process_scan_response(&self.buffer[..len], &src_addr)
{
Self::record_server(&mut servers, &mut addr_to_unit, response, src_addr);
}
received_any = true;
}
Err(e)
if e.kind() == io::ErrorKind::WouldBlock
|| e.kind() == io::ErrorKind::TimedOut =>
{
}
Err(_) => {}
}
if !received_any {
std::thread::sleep(Duration::from_millis(5));
}
}
self.unicast_socket.set_nonblocking(false)?;
for server in servers.values_mut() {
if let Some(&addr) = server.addresses.first() {
let _ = self.query_service_map(server, addr);
}
}
debug!("IDN: scan complete, found {} servers", servers.len());
Ok(servers.into_values().collect())
}
fn record_server(
servers: &mut HashMap<[u8; 16], ServerInfo>,
addr_to_unit: &mut HashMap<SocketAddr, [u8; 16]>,
response: ScanResponse,
src_addr: SocketAddr,
) {
if let Some(unit_id) = addr_to_unit.get(&src_addr) {
if let Some(server) = servers.get_mut(unit_id) {
if !server.addresses.contains(&src_addr) {
server.addresses.push(src_addr);
}
}
} else {
let entry = servers.entry(response.unit_id).or_insert_with(|| {
ServerInfo::new(
response.unit_id,
response.hostname_str().to_string(),
(
response.protocol_version >> 4,
response.protocol_version & 0x0F,
),
response.status,
)
});
if !entry.addresses.contains(&src_addr) {
entry.addresses.push(src_addr);
}
addr_to_unit.insert(src_addr, response.unit_id);
}
}
fn process_scan_response(
data: &[u8],
src_addr: &SocketAddr,
) -> Option<(ScanResponse, SocketAddr)> {
if data.len() < PacketHeader::SIZE_BYTES + ScanResponse::SIZE_BYTES {
return None;
}
let mut cursor = data;
let header: PacketHeader = cursor.read_bytes().ok()?;
if header.command != IDNCMD_SCAN_RESPONSE {
return None;
}
let response: ScanResponse = cursor.read_bytes().ok()?;
if !src_addr.is_ipv4() {
return None;
}
Some((response, *src_addr))
}
fn send_broadcast_scan(&mut self) -> io::Result<()> {
let seq = self.next_sequence();
let header = PacketHeader {
command: IDNCMD_SCAN_REQUEST,
flags: self.client_group,
sequence: seq,
};
let mut packet = Vec::with_capacity(PacketHeader::SIZE_BYTES);
packet.write_bytes(header)?;
for endpoint in &self.endpoints {
let _ = endpoint.socket.send_to(&packet, endpoint.broadcast_addr);
}
let broadcast_addr = SocketAddrV4::new(Ipv4Addr::BROADCAST, IDN_PORT);
let _ = self.unicast_socket.send_to(&packet, broadcast_addr);
Ok(())
}
fn query_service_map(&mut self, server: &mut ServerInfo, addr: SocketAddr) -> io::Result<()> {
let seq = self.next_sequence();
let header = PacketHeader {
command: IDNCMD_SERVICEMAP_REQUEST,
flags: self.client_group,
sequence: seq,
};
let mut packet = Vec::with_capacity(PacketHeader::SIZE_BYTES);
packet.write_bytes(header)?;
self.unicast_socket.send_to(&packet, addr)?;
self.unicast_socket
.set_read_timeout(Some(Duration::from_millis(500)))?;
let (len, _) = self.unicast_socket.recv_from(&mut self.buffer)?;
if len < PacketHeader::SIZE_BYTES + ServiceMapResponseHeader::SIZE_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"service map response too small",
));
}
let mut cursor = &self.buffer[..len];
let header: PacketHeader = cursor.read_bytes()?;
if header.command != IDNCMD_SERVICEMAP_RESPONSE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unexpected command: 0x{:02x}", header.command),
));
}
let map_header: ServiceMapResponseHeader = cursor.read_bytes()?;
let header_extra =
(map_header.struct_size as usize).saturating_sub(ServiceMapResponseHeader::SIZE_BYTES);
if header_extra > 0 && cursor.len() >= header_extra {
cursor = &cursor[header_extra..];
}
let entry_size = map_header.entry_size as usize;
server.relays = parse_service_map_entries(
&mut cursor,
map_header.relay_entry_count,
entry_size,
RelayInfo::from_entry,
)?;
server.services = parse_service_map_entries(
&mut cursor,
map_header.service_entry_count,
entry_size,
ServiceInfo::from_entry,
)?;
Ok(())
}
fn next_sequence(&mut self) -> u16 {
let seq = self.sequence;
self.sequence = self.sequence.wrapping_add(1);
seq
}
pub fn scan_address(
&mut self,
addr: SocketAddr,
timeout: Duration,
) -> io::Result<Vec<ServerInfo>> {
let start = Instant::now();
let mut servers: HashMap<[u8; 16], ServerInfo> = HashMap::new();
let mut addr_to_unit: HashMap<SocketAddr, [u8; 16]> = HashMap::new();
self.send_scan_to(addr)?;
let recv_timeout = Duration::from_millis(100);
self.unicast_socket.set_read_timeout(Some(recv_timeout))?;
while start.elapsed() < timeout {
match self.recv_scan_response_with_port() {
Ok((response, src_addr)) => {
Self::record_server(&mut servers, &mut addr_to_unit, response, src_addr);
}
Err(e)
if e.kind() == io::ErrorKind::WouldBlock
|| e.kind() == io::ErrorKind::TimedOut =>
{
continue;
}
Err(_) => {}
}
}
for server in servers.values_mut() {
if let Some(&addr) = server.addresses.first() {
let _ = self.query_service_map(server, addr);
}
}
debug!(
"IDN: scan_address complete, found {} servers",
servers.len()
);
Ok(servers.into_values().collect())
}
fn recv_scan_response_with_port(&mut self) -> io::Result<(ScanResponse, SocketAddr)> {
let (len, src_addr) = self.unicast_socket.recv_from(&mut self.buffer)?;
if len < PacketHeader::SIZE_BYTES + ScanResponse::SIZE_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("packet too small: {} bytes", len),
));
}
let mut cursor = &self.buffer[..len];
let header: PacketHeader = cursor.read_bytes()?;
if header.command != IDNCMD_SCAN_RESPONSE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unexpected command: 0x{:02x}", header.command),
));
}
let response: ScanResponse = cursor.read_bytes()?;
let _extra = (response.struct_size as usize).saturating_sub(ScanResponse::SIZE_BYTES);
Ok((response, src_addr))
}
fn send_scan_to(&mut self, addr: SocketAddr) -> io::Result<()> {
let seq = self.next_sequence();
let header = PacketHeader {
command: IDNCMD_SCAN_REQUEST,
flags: self.client_group,
sequence: seq,
};
let mut packet = Vec::with_capacity(PacketHeader::SIZE_BYTES);
packet.write_bytes(header)?;
self.unicast_socket.send_to(&packet, addr)?;
Ok(())
}
}
fn parse_service_map_entries<T>(
cursor: &mut &[u8],
count: u8,
entry_size: usize,
convert: fn(&ServiceMapEntry) -> T,
) -> io::Result<Vec<T>> {
let mut result = Vec::with_capacity(count as usize);
for _ in 0..count {
if cursor.len() < entry_size {
break;
}
let read_bytes = entry_size.min(ServiceMapEntry::SIZE_BYTES);
let mut entry_buf = [0u8; ServiceMapEntry::SIZE_BYTES];
entry_buf[..read_bytes].copy_from_slice(&cursor[..read_bytes]);
let entry: ServiceMapEntry = (&entry_buf[..]).read_bytes()?;
result.push(convert(&entry));
*cursor = &cursor[entry_size..];
}
Ok(result)
}