use std::time::{Duration, Instant};
use heapless::String as HString;
use oxictl::protocol::dds::api::{
builtin_impls::LogOwned, DdsType, Participant, Publisher, Sample, Subscription,
};
use oxictl::protocol::dds::discovery::qos_profile::QosProfile;
use oxictl::protocol::dds::ros2::log::LogSeverity;
use oxictl::protocol::dds::transport::{probe_multicast_loopback, SPDP_MULTICAST_IPV4};
use oxictl::protocol::dds::types::guid::GuidPrefix;
fn guid_prefix(seed: u8) -> GuidPrefix {
GuidPrefix([seed; 12])
}
fn wait_until<F>(
p1: &mut Participant,
p2: &mut Participant,
timeout: Duration,
mut condition: F,
) -> bool
where
F: FnMut(&Participant, &Participant) -> bool,
{
let deadline = Instant::now() + timeout;
loop {
let _ = p1.spin_once();
let _ = p2.spin_once();
if condition(p1, p2) {
return true;
}
if Instant::now() >= deadline {
return false;
}
}
}
#[test]
fn participant_create_and_peer_registration() {
let mut p1 = Participant::new(0, guid_prefix(0x01), QosProfile::ros2_default())
.expect("p1 creation failed");
let mut p2 = Participant::new(0, guid_prefix(0x02), QosProfile::ros2_default())
.expect("p2 creation failed");
let addr1 = p1.local_metatraffic_addr().expect("p1 metatraffic addr");
let addr2 = p2.local_metatraffic_addr().expect("p2 metatraffic addr");
p1.add_peer(addr2).expect("p1 add_peer");
p2.add_peer(addr1).expect("p2 add_peer");
assert!(p1.peer_count() >= 1);
assert!(p2.peer_count() >= 1);
}
#[test]
fn cdr_string_dds_type_roundtrip() {
let original: HString<256> = HString::try_from("hello DDS!").expect("string creation");
let mut buf = [0u8; 512];
let len = original.serialize(&mut buf).expect("serialize");
let decoded = HString::<256>::deserialize(&buf[..len]).expect("deserialize");
assert_eq!(original, decoded);
}
#[test]
fn log_owned_roundtrip() {
let log = LogOwned {
severity: LogSeverity::Warn,
stamp_sec: 1234,
stamp_nsec: 567_000_000,
name: HString::<256>::try_from("/my_node").expect("name"),
msg: HString::<256>::try_from("something went wrong").expect("msg"),
file: HString::<256>::try_from("src/main.rs").expect("file"),
function: HString::<256>::try_from("my_function").expect("function"),
line: 42,
};
let mut buf = [0u8; 1024];
let len = log.serialize(&mut buf).expect("serialize");
let decoded = LogOwned::deserialize(&buf[..len]).expect("deserialize");
assert_eq!(decoded.stamp_sec, 1234);
assert_eq!(decoded.stamp_nsec, 567_000_000);
assert_eq!(decoded.severity as u8, LogSeverity::Warn as u8);
assert_eq!(decoded.name.as_str(), "/my_node");
assert_eq!(decoded.msg.as_str(), "something went wrong");
assert_eq!(decoded.line, 42);
}
#[test]
fn publisher_subscription_create() {
let mut p = Participant::new(0, guid_prefix(0x10), QosProfile::ros2_default())
.expect("participant creation failed");
let _pub1: Publisher<HString<256>> = p
.create_publisher::<HString<256>>("test_topic", &QosProfile::ros2_default())
.expect("create_publisher");
let _sub1: Subscription<HString<256>> = p
.create_subscription::<HString<256>>("test_topic", &QosProfile::ros2_default())
.expect("create_subscription");
}
#[test]
fn end_to_end_publish_subscribe() {
let mut pub_p = Participant::new(5, guid_prefix(0x20), QosProfile::ros2_default())
.expect("publisher participant");
let mut sub_p = Participant::new(5, guid_prefix(0x21), QosProfile::ros2_default())
.expect("subscriber participant");
let pub_addr = pub_p.local_metatraffic_addr().expect("pub addr");
let sub_addr = sub_p.local_metatraffic_addr().expect("sub addr");
pub_p.add_peer(sub_addr).expect("pub add peer");
sub_p.add_peer(pub_addr).expect("sub add peer");
let pub1 = pub_p
.create_publisher::<HString<256>>("chatter", &QosProfile::ros2_default())
.expect("create publisher");
let sub1 = sub_p
.create_subscription::<HString<256>>("chatter", &QosProfile::ros2_default())
.expect("create subscription");
let timeout = Duration::from_secs(3);
let _ = pub_p.spin_once();
let _ = sub_p.spin_once();
let matched = wait_until(
&mut pub_p,
&mut sub_p,
timeout,
|_pp, _sp| true, );
assert!(matched, "wait_until timed out");
let value: HString<256> = HString::try_from("Hello, DDS world!").expect("string");
pub_p.publish(&pub1, &value).expect("publish");
let deadline = Instant::now() + Duration::from_secs(3);
let mut received: Vec<Sample<HString<256>>> = Vec::new();
while received.is_empty() && Instant::now() < deadline {
let _ = pub_p.spin_once();
let _ = sub_p.spin_once();
received = sub_p.take(&sub1);
}
assert!(!received.is_empty(), "no samples received within timeout");
assert_eq!(received[0].data.as_str(), "Hello, DDS world!");
}
#[test]
#[cfg_attr(not(any(target_os = "linux", target_os = "macos")), ignore)]
fn multicast_auto_discovery() {
if !probe_multicast_loopback(SPDP_MULTICAST_IPV4, Duration::from_millis(500)) {
eprintln!("oxictl test: multicast loopback not functional on this host (probe timed out); skipping multicast_auto_discovery.");
return;
}
let mut p1 = Participant::new(99, GuidPrefix([0x01; 12]), QosProfile::ros2_default())
.expect("participant 1 construction failed");
let mut p2 = Participant::new(99, GuidPrefix([0x02; 12]), QosProfile::ros2_default())
.expect("participant 2 construction failed");
if !p1.has_multicast() || !p2.has_multicast() {
eprintln!("oxictl test: SPDP multicast socket setup failed on this host; skipping test.");
return;
}
let deadline = Instant::now() + Duration::from_secs(3);
loop {
let _ = p1.spin_once();
let _ = p2.spin_once();
if p1.peer_count() >= 1 && p2.peer_count() >= 1 {
break;
}
if Instant::now() > deadline {
panic!("Participants did not discover each other within 3 seconds");
}
std::thread::sleep(Duration::from_millis(50));
}
assert!(p1.peer_count() >= 1);
assert!(p2.peer_count() >= 1);
}