Skip to main content

Discovery

Discovery is the automatic process by which DDS participants and endpoints find each other on the network.

Overview

DDS uses two discovery protocols:

ProtocolPurposeTraffic
SPDPFind participantsMulticast
SEDPExchange endpointsUnicast
┌──────────────────────────────────────────────────────────────┐
│ Discovery Flow │
├──────────────────────────────────────────────────────────────┤
│ │
│ Participant A Participant B │
│ │ │ │
│ │ ──── SPDP (multicast) ────────> │ Phase 1: Find │
│ │ <─── SPDP (multicast) ───────── │ participants │
│ │ │ │
│ │ ──── SEDP (unicast) ──────────> │ Phase 2: Exchange │
│ │ <─── SEDP (unicast) ─────────── │ endpoints │
│ │ │ │
│ │ ──── User Data ─────────────────>│ Phase 3: Data │
│ │ │ flow begins │
│ │
└──────────────────────────────────────────────────────────────┘

SPDP (Simple Participant Discovery Protocol)

SPDP discovers other DomainParticipants in the same domain.

How It Works

  1. Each participant sends periodic announcements via multicast
  2. Announcements contain participant GUID, locators, lease duration
  3. Participants maintain a list of discovered participants

SPDP Messages

SPDP Announcement:
┌────────────────────────────────────────┐
│ GUID Prefix: [01, 0f, aa, ...] │
│ Vendor ID: HDDS (0x01aa) │
│ Unicast Locator: 192.168.1.100:7411 │
│ Multicast Locator: 239.255.0.1:7400 │
│ Lease Duration: 30s │
│ User Data: "app=sensor" │
└────────────────────────────────────────┘

Network Configuration

Default multicast settings:

SettingDefaultDomain N
Multicast Address239.255.0.1239.255.0.1
Discovery Port74007400 + N*10
Participant Offset+0, +1+0 to +9
// Customize discovery
let config = DomainParticipantConfig::default()
.discovery_multicast("239.255.0.1:7400")
.lease_duration(Duration::from_secs(30));

SPDP Timing

Initial announcements:  Quick burst (0.5s, 1s, 2s)
Steady state: Every lease_duration/3
Lease expiration: Remove after lease_duration

SEDP (Simple Endpoint Discovery Protocol)

SEDP exchanges information about DataWriters and DataReaders.

How It Works

  1. After SPDP discovers a participant, SEDP begins
  2. Participants exchange endpoint information via unicast
  3. Each side learns about remote writers and readers

SEDP Messages

SEDP Publication (Writer Info):
┌────────────────────────────────────────┐
│ Endpoint GUID: [01, 0f, aa, ..., c2] │
│ Topic Name: "SensorTopic" │
│ Type Name: "sensors::SensorData" │
│ Reliability: Reliable │
│ Durability: TransientLocal │
│ Ownership: Shared │
│ Type Object: (XTypes info) │
└────────────────────────────────────────┘

SEDP Subscription (Reader Info):
┌────────────────────────────────────────┐
│ Endpoint GUID: [02, 1a, bb, ..., c7] │
│ Topic Name: "SensorTopic" │
│ Type Name: "sensors::SensorData" │
│ Reliability: Reliable │
│ Durability: Volatile │
└────────────────────────────────────────┘

Built-in Endpoints

Each participant has built-in endpoints for discovery:

Entity IDPurpose
0x000100c2SPDP Writer
0x000100c7SPDP Reader
0x000003c2Publications Writer
0x000003c7Publications Reader
0x000004c2Subscriptions Writer
0x000004c7Subscriptions Reader

Matching Process

After SEDP exchange, endpoints are matched:

Match Criteria:
1. Same Topic Name (exact string match)
2. Compatible Type (type name + XTypes)
3. Compatible QoS (see QoS rules)
4. Same Domain (implicit from discovery)
5. Partition Overlap (if configured)

QoS Compatibility Check

PolicyRequirement
ReliabilityWriter ≥ Reader
DurabilityWriter ≥ Reader
DeadlineWriter ≤ Reader
LivelinessWriter kind ≥ Reader kind
OwnershipWriter = Reader

Discovery Events

Participant Events

impl DomainParticipantListener for MyListener {
fn on_participant_discovered(&mut self, participant: &DomainParticipant, info: DiscoveredParticipantInfo) {
println!("Found participant: {:?}", info.guid);
println!(" Vendor: {:?}", info.vendor_id);
println!(" User data: {:?}", info.user_data);
}

fn on_participant_lost(&mut self, participant: &DomainParticipant, guid: GUID) {
println!("Participant left: {:?}", guid);
}
}

Endpoint Events

impl DataWriterListener for MyListener {
fn on_publication_matched(&mut self, writer: &DataWriter<T>, status: PublicationMatchedStatus) {
println!("Writer matched {} readers (total: {})",
status.current_count_change,
status.current_count);
}
}

impl DataReaderListener for MyListener {
fn on_subscription_matched(&mut self, reader: &DataReader<T>, status: SubscriptionMatchedStatus) {
println!("Reader matched {} writers (total: {})",
status.current_count_change,
status.current_count);
}
}

Discovery Queries

List Discovered Participants

for info in participant.discovered_participants() {
println!("Participant: {:?}", info.guid);
println!(" Unicast: {:?}", info.unicast_locators);
}

List Discovered Topics

for info in participant.discovered_topics() {
println!("Topic: {} (type: {})", info.name, info.type_name);
}

Check Matched Endpoints

// Writer side
let matched_readers = writer.matched_subscriptions();
for reader_guid in matched_readers {
println!("Matched reader: {:?}", reader_guid);
}

// Reader side
let matched_writers = reader.matched_publications();
for writer_guid in matched_writers {
println!("Matched writer: {:?}", writer_guid);
}

Static Discovery

For networks without multicast:

let config = DomainParticipantConfig::default()
// Disable multicast discovery
.enable_multicast_discovery(false)

// Manually specify peer addresses
.initial_peers(vec![
"192.168.1.100:7400".parse()?,
"192.168.1.101:7400".parse()?,
]);

Discovery Timing

Typical Discovery Times

ScenarioTime
Same host50-100 ms
Same LAN100-500 ms
Cross-subnet (static)100-200 ms

Speeding Up Discovery

let config = DomainParticipantConfig::default()
// Faster initial announcements
.initial_announcement_period(Duration::from_millis(100))

// Shorter lease (faster detection of failures)
.lease_duration(Duration::from_secs(10));

Troubleshooting Discovery

No Participants Found

# Check multicast connectivity
ping -c 3 239.255.0.1

# Verify SPDP traffic
tcpdump -i any -n udp port 7400

# Enable discovery debug
export RUST_LOG=hdds::discovery=debug

Endpoints Not Matching

Common causes:

IssueSolution
Different domain IDUse same domain
Topic name mismatchCheck spelling, case
Type name mismatchUse same IDL
QoS incompatibleCheck reliability, durability
Partition mismatchCheck partition strings

Firewall Configuration

Required ports:

UDP 7400-7500    (discovery + data, per domain)
UDP 239.255.0.0/16 (multicast, if enabled)

Vendor Interoperability

Discovery works across DDS vendors:

VendorSPDPSEDPNotes
FastDDSYesYesDefault settings work
RTI ConnextYesYesMay need interop mode
CycloneDDSYesYesExcellent compliance
OpenDDSYesYesWorks with RTPS

Vendor Detection

for info in participant.discovered_participants() {
match info.vendor_id {
VendorId::HDDS => println!("HDDS participant"),
VendorId::RTI => println!("RTI Connext participant"),
VendorId::EPROSIMA => println!("FastDDS participant"),
VendorId::CYCLONE => println!("CycloneDDS participant"),
_ => println!("Unknown vendor: {:?}", info.vendor_id),
}
}

Discovery Architecture

┌─────────────────────────────────────────────────────────────┐
│ Discovery Subsystem │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SPDP Writer │ │ SEDP Pubs │ │ SEDP Subs │ │
│ │ (builtin) │ │ Writer │ │ Writer │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Discovery Database │ │
│ │ - Discovered Participants │ │
│ │ - Discovered Endpoints │ │
│ │ - Matching State │ │
│ └──────────────────────────────────────────────────────┘ │
│ ▲ ▲ ▲ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SPDP Reader │ │ SEDP Pubs │ │ SEDP Subs │ │
│ │ (builtin) │ │ Reader │ │ Reader │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Next Steps