use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use crate::error::XrceError;
use crate::submessages::Message;
pub const XRCE_DISCOVERY_GROUP: Ipv4Addr = Ipv4Addr::new(239, 255, 0, 2);
pub const XRCE_DISCOVERY_PORT: u16 = 7400;
#[derive(Debug)]
pub struct MulticastDiscovery {
pub socket: UdpSocket,
pub group_addr: SocketAddrV4,
}
impl MulticastDiscovery {
pub fn start(port: u16) -> std::io::Result<Self> {
let socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port))?;
let _ = socket.join_multicast_v4(&XRCE_DISCOVERY_GROUP, &Ipv4Addr::UNSPECIFIED);
Ok(Self {
socket,
group_addr: SocketAddrV4::new(XRCE_DISCOVERY_GROUP, XRCE_DISCOVERY_PORT),
})
}
pub fn start_on(local: SocketAddr) -> std::io::Result<Self> {
let socket = UdpSocket::bind(local)?;
let _ = socket.join_multicast_v4(&XRCE_DISCOVERY_GROUP, &Ipv4Addr::UNSPECIFIED);
Ok(Self {
socket,
group_addr: SocketAddrV4::new(XRCE_DISCOVERY_GROUP, XRCE_DISCOVERY_PORT),
})
}
pub fn send_to(&self, msg: &Message, target: SocketAddr) -> Result<(), XrceError> {
let bytes = msg.encode()?;
self.socket
.send_to(&bytes, target)
.map_err(|_| XrceError::ValueOutOfRange {
message: "discovery send_to failed",
})?;
Ok(())
}
pub fn send_multicast(&self, msg: &Message) -> Result<(), XrceError> {
self.send_to(msg, SocketAddr::V4(self.group_addr))
}
pub fn recv(&self) -> Result<(SocketAddr, Message), XrceError> {
let mut buf = [0u8; 65_507];
let (n, peer) =
self.socket
.recv_from(&mut buf)
.map_err(|_| XrceError::ValueOutOfRange {
message: "discovery recv_from failed",
})?;
let msg = Message::decode(&buf[..n])?;
Ok((peer, msg))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
extern crate alloc;
use super::*;
use crate::header::{ClientKey, MessageHeader, SessionId, StreamId};
use crate::serial_number::SerialNumber16;
use crate::submessages::{GetInfoPayload, Submessage};
fn build_get_info_msg() -> Message {
let header = MessageHeader::with_client_key(
SessionId(0x00),
StreamId::NONE,
SerialNumber16::new(1),
ClientKey([0xCA, 0xFE, 0xBA, 0xBE]),
)
.unwrap();
let sm: Submessage = GetInfoPayload {
representation: alloc::vec![0xAA, 0xBB, 0xCC, 0xDD, 0, 0, 0, 0],
}
.into_submessage()
.unwrap();
Message::new(header, alloc::vec![sm]).unwrap()
}
#[test]
fn discovery_constants_match_spec() {
assert_eq!(XRCE_DISCOVERY_GROUP, Ipv4Addr::new(239, 255, 0, 2));
assert_eq!(XRCE_DISCOVERY_PORT, 7400);
}
#[test]
fn loopback_send_recv_roundtrip() {
let listener = MulticastDiscovery::start_on("127.0.0.1:0".parse().unwrap()).unwrap();
let listener_addr = listener.socket.local_addr().unwrap();
let sender = MulticastDiscovery::start_on("127.0.0.1:0".parse().unwrap()).unwrap();
sender
.socket
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
.unwrap();
listener
.socket
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
.unwrap();
let msg = build_get_info_msg();
sender.send_to(&msg, listener_addr).expect("send");
let (_peer, received) = listener.recv().expect("recv");
assert_eq!(received, msg);
}
#[test]
fn start_with_ephemeral_port_succeeds() {
let d = MulticastDiscovery::start(0).unwrap();
let addr = d.socket.local_addr().unwrap();
assert_ne!(addr.port(), 0);
}
#[test]
fn multicast_send_via_xrce_discovery_group_does_not_error() {
let d = MulticastDiscovery::start(0).expect("bind");
let msg = build_get_info_msg();
let res = d.send_multicast(&msg);
match res {
Ok(()) => {}
Err(XrceError::ValueOutOfRange { .. }) => {}
Err(other) => panic!("unerwarteter XrceError-Typ: {other:?}"),
}
}
#[test]
fn discovery_group_addr_constructed_correctly() {
let d = MulticastDiscovery::start(0).expect("bind");
assert_eq!(d.group_addr.ip(), &Ipv4Addr::new(239, 255, 0, 2));
assert_eq!(d.group_addr.port(), XRCE_DISCOVERY_PORT);
}
#[test]
fn tcp_discovery_uses_same_port_scheme_as_udp() {
use crate::transport_udp::agent_default_port;
for domain in 0u16..=10 {
let p = agent_default_port(domain);
assert_eq!(p, 7400 + 4 * domain);
}
}
}