use std::collections::HashSet;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::time::{Duration, Instant};
use crate::protocol::dds::discovery::endpoint_data::{
PublicationBuiltinTopicData, SubscriptionBuiltinTopicData,
};
use crate::protocol::dds::discovery::qos_profile::QosProfile;
use crate::protocol::dds::discovery::sedp::SedpParticipant;
use crate::protocol::dds::discovery::spdp::SpdpParticipant;
use crate::protocol::dds::discovery::IncomingResult;
use crate::protocol::dds::transport::{
bind_multicast_reuse, metatraffic_multicast_port, TransportConfig, UdpTransport,
SPDP_MULTICAST_IPV4,
};
use crate::protocol::dds::types::guid::{Guid, GuidPrefix, ENTITYID_PARTICIPANT};
use super::dds_type::DdsType;
use super::entity_id::EntityIdAllocator;
use super::error::DdsApiError;
use super::publisher::{addr_to_locator, Publisher, WriterEntry};
use super::subscription::{
guid_to_bytes, pub_data_locators, sub_data_locator, ReaderEntry, Subscription,
};
const SPDP_BEACON_PERIOD: Duration = Duration::from_millis(1000);
pub struct Participant {
domain_id: u16,
guid_prefix: GuidPrefix,
spdp: Option<SpdpParticipant>,
spdp_multicast_locator: Option<crate::protocol::dds::types::locator::Locator>,
last_beacon_at: Option<Instant>,
auto_discovered_peers: HashSet<[u8; 12]>,
sedp: SedpParticipant,
peer_metatraffic_locators: Vec<crate::protocol::dds::types::locator::Locator>,
writers: Vec<WriterEntry>,
readers: Vec<ReaderEntry>,
matched_pubs: HashSet<[u8; 16]>,
matched_subs: HashSet<[u8; 16]>,
}
impl Participant {
pub fn new(
domain_id: u16,
guid_prefix: GuidPrefix,
qos: QosProfile,
) -> Result<Self, DdsApiError> {
let bind = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0));
let cfg = TransportConfig {
read_timeout: Some(Duration::from_millis(5)),
..TransportConfig::unicast(bind)
};
let sedp_transport = UdpTransport::new(cfg)?;
let sedp_local_addr = sedp_transport.local_addr()?;
let sedp = SedpParticipant::with_transport_and_qos(guid_prefix, sedp_transport, qos);
let mc_port = metatraffic_multicast_port(domain_id);
let unicast_locator = addr_to_locator(sedp_local_addr)?;
let mut unicast_locators: heapless::Vec<crate::protocol::dds::types::locator::Locator, 4> =
heapless::Vec::new();
let _ = unicast_locators.push(unicast_locator);
let spdp_multicast_locator: Option<crate::protocol::dds::types::locator::Locator>;
let spdp = match bind_multicast_reuse(mc_port, SPDP_MULTICAST_IPV4) {
Ok(mc_sock) => {
let mc_transport = UdpTransport::from_socket(mc_sock);
let _ = mc_transport.set_read_timeout(Some(Duration::from_millis(1)));
let mc_loc = crate::protocol::dds::types::locator::Locator::udp_v4(
mc_port as u32,
SPDP_MULTICAST_IPV4,
);
spdp_multicast_locator = Some(mc_loc);
match SpdpParticipant::with_multicast_transport(
domain_id,
guid_prefix,
mc_transport,
unicast_locators,
) {
Ok(s) => Some(s),
Err(e) => {
eprintln!(
"oxictl: SPDP multicast init failed ({e}); falling back to explicit add_peer"
);
None
}
}
}
Err(e) => {
eprintln!(
"oxictl: multicast socket bind failed ({e}); SPDP auto-discovery disabled"
);
spdp_multicast_locator = None;
None
}
};
Ok(Self {
domain_id,
guid_prefix,
spdp,
spdp_multicast_locator,
last_beacon_at: None,
auto_discovered_peers: HashSet::new(),
sedp,
peer_metatraffic_locators: Vec::new(),
writers: Vec::new(),
readers: Vec::new(),
matched_pubs: HashSet::new(),
matched_subs: HashSet::new(),
})
}
pub fn domain_id(&self) -> u16 {
self.domain_id
}
pub fn has_multicast(&self) -> bool {
self.spdp.is_some()
}
pub fn local_metatraffic_addr(&self) -> Result<SocketAddr, DdsApiError> {
Ok(self.sedp.local_addr()?)
}
pub fn add_peer(&mut self, addr: SocketAddr) -> Result<(), DdsApiError> {
let locator = addr_to_locator(addr)?;
self.peer_metatraffic_locators.push(locator);
Ok(())
}
pub fn create_publisher<T: DdsType>(
&mut self,
topic_name: &str,
qos: &QosProfile,
) -> Result<Publisher<T>, DdsApiError> {
let entity_id = EntityIdAllocator::next_writer();
let entry = WriterEntry::new(self.guid_prefix, entity_id, topic_name, T::TYPE_NAME, qos)?;
let idx = self.writers.len();
self.writers.push(entry);
Ok(Publisher::new(idx))
}
pub fn create_subscription<T: DdsType>(
&mut self,
topic_name: &str,
qos: &QosProfile,
) -> Result<Subscription<T>, DdsApiError> {
let entity_id = EntityIdAllocator::next_reader();
let entry = ReaderEntry::new(self.guid_prefix, entity_id, topic_name, T::TYPE_NAME, qos)?;
let idx = self.readers.len();
self.readers.push(entry);
Ok(Subscription::new(idx))
}
pub fn publish<T: DdsType>(
&mut self,
publisher: &Publisher<T>,
value: &T,
) -> Result<crate::protocol::dds::types::sequence::SequenceNumber, DdsApiError> {
let entry = self
.writers
.get_mut(publisher.entry_idx)
.ok_or(DdsApiError::Serialization("invalid publisher handle"))?;
publisher.publish(entry, value)
}
pub fn take<T: DdsType>(
&mut self,
subscription: &Subscription<T>,
) -> Vec<super::dds_type::Sample<T>> {
match self.readers.get_mut(subscription.entry_idx) {
Some(entry) => subscription.take(entry),
None => Vec::new(),
}
}
pub fn queue_depth<T: DdsType>(&self, subscription: &Subscription<T>) -> usize {
self.readers
.get(subscription.entry_idx)
.map(|e| e.raw_queue.len())
.unwrap_or(0)
}
pub fn spin_once(&mut self) -> Result<IncomingResult, DdsApiError> {
if let (Some(spdp), Some(mc_loc)) = (&mut self.spdp, &self.spdp_multicast_locator) {
let send_now = self
.last_beacon_at
.map(|t| t.elapsed() >= SPDP_BEACON_PERIOD)
.unwrap_or(true);
if send_now {
let _ = spdp.send_beacon_to(mc_loc);
self.last_beacon_at = Some(Instant::now());
}
}
let mut new_peers: Vec<([u8; 12], crate::protocol::dds::types::locator::Locator)> =
Vec::new();
if let Some(spdp) = self.spdp.as_mut() {
let _ = spdp.process_incoming();
for disc in spdp.discovered() {
let prefix_bytes = disc.data.participant_guid.prefix.0;
if let Some(loc) = disc.data.metatraffic_unicast_locators.first() {
new_peers.push((prefix_bytes, *loc));
}
}
}
for (prefix, loc) in new_peers {
if self.auto_discovered_peers.insert(prefix) {
self.peer_metatraffic_locators.push(loc);
}
}
self.announce_endpoints()?;
let result = self.sedp.process_incoming()?;
self.match_publications_to_readers()?;
self.match_subscriptions_to_writers()?;
for entry in self.writers.iter_mut() {
let _ = entry.writer.process_incoming();
let _ = entry.writer.send_heartbeat_if_due();
}
for entry in self.readers.iter_mut() {
loop {
match entry.reader.recv() {
Ok(Some(sample)) => {
let guid_bytes = guid_to_bytes(&sample.writer_guid);
if entry.raw_queue.is_full() {
if !entry.raw_queue.is_empty() {
entry.raw_queue.swap_remove(0);
}
}
let _ = entry.raw_queue.push((sample.data, guid_bytes));
}
Ok(None) => break,
Err(_) => break,
}
}
}
Ok(result)
}
fn announce_endpoints(&mut self) -> Result<(), DdsApiError> {
for locator in self.peer_metatraffic_locators.clone() {
for entry in self.writers.iter_mut() {
self.sedp.announce_publication(&entry.pub_data, &locator)?;
entry.announced = true;
}
for entry in self.readers.iter_mut() {
self.sedp.announce_subscription(&entry.sub_data, &locator)?;
entry.announced = true;
}
}
Ok(())
}
fn match_publications_to_readers(&mut self) -> Result<(), DdsApiError> {
let discovered: Vec<PublicationBuiltinTopicData> =
self.sedp.discovered_publications().to_vec();
for pub_data in &discovered {
let guid_bytes = guid_to_bytes(&pub_data.endpoint_guid);
if self.matched_pubs.contains(&guid_bytes) {
continue;
}
let writer_guid = pub_data.endpoint_guid;
let writer_locators: Vec<_> = pub_data_locators(pub_data);
for entry in self.readers.iter_mut() {
if entry.sub_data.topic_name == pub_data.topic_name
&& entry.sub_data.type_name == pub_data.type_name
{
entry
.reader
.add_matched_writer(writer_guid, writer_locators.clone());
}
}
self.matched_pubs.insert(guid_bytes);
}
Ok(())
}
fn match_subscriptions_to_writers(&mut self) -> Result<(), DdsApiError> {
let discovered: Vec<SubscriptionBuiltinTopicData> =
self.sedp.discovered_subscriptions().to_vec();
for sub_data in &discovered {
let guid_bytes = guid_to_bytes(&sub_data.endpoint_guid);
if self.matched_subs.contains(&guid_bytes) {
continue;
}
let reader_guid = sub_data.endpoint_guid;
let reader_locators: Vec<_> = match sub_data_locator(sub_data) {
Some(loc) => vec![loc],
None => continue,
};
for entry in self.writers.iter_mut() {
if entry.pub_data.topic_name == sub_data.topic_name
&& entry.pub_data.type_name == sub_data.type_name
{
entry
.writer
.add_matched_reader(reader_guid, reader_locators.clone());
}
}
self.matched_subs.insert(guid_bytes);
}
Ok(())
}
pub fn publisher_guid<T: DdsType>(&self, pubr: &Publisher<T>) -> Option<[u8; 16]> {
self.writers
.get(pubr.entry_idx)
.map(|e| guid_to_bytes(&e.pub_data.endpoint_guid))
}
pub fn guid(&self) -> Guid {
Guid::new(self.guid_prefix, ENTITYID_PARTICIPANT)
}
pub fn peer_count(&self) -> usize {
self.peer_metatraffic_locators.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn participant_new_with_domain_constructs() {
let guid = GuidPrefix([0xAA; 12]);
let p = Participant::new(
0,
guid,
crate::protocol::dds::discovery::qos_profile::QosProfile::ros2_default(),
)
.expect("participant construction failed");
assert_eq!(p.domain_id(), 0);
assert!(p.local_metatraffic_addr().is_ok());
}
#[test]
fn participant_peer_count_starts_zero() {
let p = Participant::new(
1,
GuidPrefix([0xBB; 12]),
crate::protocol::dds::discovery::qos_profile::QosProfile::ros2_default(),
)
.expect("participant construction failed");
assert_eq!(p.peer_count(), 0);
}
}