7.8. Discovery

This section discusses the implementation of discovery plugins in RTI Connext Cert. For a general overview of discovery in RTI Connext Cert, see What is Discovery?.

Connext Cert discovery traffic is conducted through transports. Please see the Transports section for more information about registering and configuring transports.

7.8.1. What is Discovery?

Discovery is the behind-the-scenes way in which RTI Connext Cert objects (DomainParticipants, DataWriters, and DataReaders) on different nodes find out about each other. Each DomainParticipant maintains a database of information about all the active DataReaders and DataWriters that are in the same DDS domain. This database is what makes it possible for DataWriters and DataReaders to communicate. To create and refresh the database, each application follows a common discovery process.

This section describes the default discovery mechanism known as the Simple Discovery Protocol, which includes two phases: Simple Participant Discovery and Simple Endpoint Discovery.

The goal of these two phases is to build, for each DomainParticipant, a complete picture of all the entities that belong to the remote participants that are in its peers list. The peers list is the list of nodes with which a participant may communicate. It starts out the same as the initial_peers list that you configure in the DISCOVERY QosPolicy. If the accept_unknown_peers flag in that same QosPolicy is TRUE, then other nodes may also be added as they are discovered; if it is FALSE, then the peers list will match the initial_peers list, plus any peers added using the DomainParticipant’s add_peer() operation.

The following section discusses how Connext Cert objects on different nodes find out about each other using the default Simple Discovery Protocol (SDP). It describes the sequence of messages that are passed between Connext Cert on the sending and receiving sides.

Note that this chapter is shared between Connext Micro and Connext Cert. However Connext Cert only supports static endpoint discovery described in Static Discovery Plugin.

The discovery process occurs automatically, so you do not have to implement any special code. For more information about advanced topics related to Discovery, please refer to the Discovery chapter in the RTI Connext DDS Core Libraries User’s Manual (available here if you have Internet access).

7.8.1.1. Simple Participant Discovery

This phase of the Simple Discovery Protocol is performed by the Simple Participant Discovery Protocol (SPDP) and is common for both dynamic and static endpoint discovery.

During the Participant Discovery phase, DomainParticipants learn about each other. The DomainParticipant’s details are communicated to all other DomainParticipants in the same DDS domain by sending participant declaration messages, also known as participant DATA submessages. The details include the DomainParticipant’s unique identifying key (GUID or Globally Unique ID described below), transport locators (addresses and port numbers), and QoS. These messages are sent on a periodic basis using best-effort communication.

Participant DATAs are sent periodically to maintain the liveliness of the DomainParticipant. They are also used to communicate changes in the DomainParticipant’s QoS. Only changes to QosPolicies that are part of the DomainParticipant’s built-in data need to be propagated.

When receiving remote participant discovery information, Connext Cert determines if the local participant matches the remote one. A ‘match’ between the local and remote participant occurs only if the local and remote participant have the same Domain ID. This matching process occurs as soon as the local participant receives discovery information from the remote one. If there is no match, the discovery DATA is ignored, resulting in the remote participant (and all its associated entities) not being discovered.

When a DomainParticipant is deleted, a participant DATA (delete) submessage with the DomainParticipant’s identifying GUID is sent.

The GUID is a unique reference to an entity. It is composed of a GUID prefix and an Entity ID. By default, the GUID prefix is calculated. The entityID is set by Connext Cert (you may be able to change it in a future version).

7.8.2. Configuring Participant Discovery Peers

An RTI Connext Cert DomainParticipant must be able to send participant discovery announcement messages for other DomainParticipants to discover itself, and it must receive announcements from other DomainParticipants to discover them.

To do so, each DomainParticipant will send its discovery announcements to a set of locators known as its peer list, where a peer is the transport locator of one or more potential other DomainParticipants to discover.

7.8.2.1. The Peer Address

A peer descriptor string of the initial_peers sequence defines the interface and address of the locator to which to send, as well as the indices of participants to which to send. The peer descriptor format is:

< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
    which means a literal.

ADDRESS = <PREFIX://><ADDRESS> |
          @<PREFIX://><ADDRESS> |
          INDEX@<PREFIX>://<ADDRESS>

INDEX = INTEGER | '[' INTEGER ']' | '[' INTEGER-INTEGER ']' | '[' -INTEGER ']'

PREFIX = [a-zA-Z_][0-9a-zA-Z_]+

INTEGER = DEC_INTEGER | HEX_INTEGER

DEC_INTEGER = [0-9]+

HEX_INTEGER = [0x|0X][0-9a-fA-F]+

ADDRESS = 0 or more 8bit characters

Note that peer descriptors use zero-based indexing; the first peer index is 0, not 1. Also note that while the PREFIX is marked optional, it should always be used.

Remember that every DomainParticipant has a participant index that is unique within a DDS domain. The participant index (also referred to as the participant ID), together with the DDS domain ID, is used to calculate the network ports on which DataReaders of that participant will receive messages. Thus, by specifying the participant index, or a range of indices, for a peer locator, that locator becomes one or more ports to which messages will be sent only if addressed to the entities of a particular DomainParticipant. Specifying indices restricts the number of participant announcements sent to a locator where other DomainParticipants exist and, thus, should be considered to minimize network bandwidth usage.

For example:

DDS_StringSeq_set_maximum(&dp_qos.discovery.initial_peers, 5);
DDS_StringSeq_set_length(&dp_qos.discovery.initial_peers, 5);

/* If the index is not specified, it defaults to 5, thus sending to
 * the first 6 participant IDs on at IP address 192.168.1.1 using
 * the transport registered as _udp.
 */
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 0) =
     DDS_String_dup("_udp://192.168.1.1");

/* Only send participant annoucements to multicast address 239.255.0.1
 * using the transport registered as _udp. Note that for multicast
 * addresses the index is not relevant since it is a shared address.
 */
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 1) =
     DDS_String_dup("_udp://239.255.0.1");

/* Send annoucements to participant ID 1,2,3, and 4 on 10.10.30.101
 * using the transport registered as _udp.
 */
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 2) =
     DDS_String_dup("[0-4]@_udp://10.10.30.101");

/* Send annoucements to participant ID 2 on address 10.10.30.102
 * using the transport registered as _udp.
 */
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 3) =
     DDS_String_dup("[2]@_udp://10.10.30.102");

/* Send annoucements to participant ID 0-8 on address 10.10.30.102
 * using the transport registered as _udp.
 */
*DDS_StringSeq_get_reference(&dp_qos.discovery.initial_peers, 4) =
     DDS_String_dup("8@_udp://10.10.30.102");

7.8.3. Configuring Initial Peers and Adding Peers

DiscoveryQosPolicy_initial_peers is the list of peers a DomainParticipant sends its participant announcement messages, when it is enabled, as part of the discovery process.

DiscoveryQosPolicy_initial_peers is an empty sequence by default.

Peers can also be added to the list, before and after a DomainParticipant has been enabled, by using DomainParticipant_add_peer.

The DomainParticipant will start sending participant announcement messages to the new peer as soon as it is enabled.

7.8.4. Configuring Discovery Data Reception

In order to receive discovery and user data, it is necessary to configure the DomainParticipantQos.discovery.enabled_transports sequence. This is a sequence of transport addresses to listen for discovery data on, and is sent as part of the participant annoucements. Other DomainParticipants will send to these addresses.

The address format for configuring data reception uses the following format:

< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
    which means a literal.

ADDRESS = <PREFIX://><ADDRESS>

PREFIX = [a-zA-Z_][0-9a-zA-Z_]+

ADDRESS = 0 or more 8bit characters

Note that while the PREFIX is marked optional, it should always be used.

For example, to receive on a single unicast address:

DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 1);

/* Receive on the unicast address 192.168.1.1 using the transport registered
 * as _udp.
 */
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
                             DDS_String_dup("_udp://192.168.1.1");

To receive on all unicast addresses allowed by the transport:

DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 1);

/* Receive on all unicast addresses allowed by the transport registered
 * as _udp. This is not recommended if more than 4 network interfaces are
 * allowed as it is non-deterministic which interfaces will be used.
 */
*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
                             DDS_String_dup("_udp://");

To receive on one unicast address and one multicast address:

DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 2);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 2);

*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
                             DDS_String_dup("_udp://192.168.1.1");

*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 1) =
                             DDS_String_dup("_udp://239.255.0.1");

To receive on one multicast address:

DDS_StringSeq_set_maximum(&DomainParticipantQos.discovery.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.discovery.enabled_transports, 1);

*DDS_StringSeq_get_reference(&DomainParticipantQos.discovery.enabled_transports, 0) =
                             DDS_String_dup("_udp://239.255.0.1");

7.8.5. Configuring User Data Reception

In order to receive discovery and user data, it is necessary to configure the DomainParticipantQos.user_traffic.enabled_transports sequence. This is a sequence of default transport addresses to listen for user data on (unless a DataReader or DataWriter specifies its own address; see Configuring Enabled Transports per DataReader or DataWriter below) and is sent as part of the participant annoucements. Other DomainParticipants will send to these addresses.

The address format for configuring data reception uses the following format:

< > denotes optional
[ ] denotes range or discreet values, unless enclosed in ''
    which means a literal.

ADDRESS = <PREFIX://><ADDRESS>

PREFIX = [a-zA-Z_][0-9a-zA-Z_]+

ADDRESS = 0 or more 8bit characters

Note that while the PREFIX is marked optional, it should always be used.

For example, to receive on a single unicast address:

DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 1);

/* Receive on the unicast address 192.168.1.1 using the transport registered
 * as _udp.
 */
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
                             DDS_String_dup("_udp://192.168.1.1");

To receive on all unicast addresses allowed by the transport:

DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 1);

/* Receive on all unicast addresses allowed by the transport registered
 * as _udp. This is not recommended if more than 4 network interfaces are
 * allowed as it is non-deterministic which interfaces will be used.
 */
*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
                             DDS_String_dup("_udp://");

To receive on one unicast address and one multicast address:

DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 2);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 2);

*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
                             DDS_String_dup("_udp://192.168.1.1");

*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 1) =
                             DDS_String_dup("_udp://239.255.0.1");

Note

When both multicast and unicast is specified, the following rules are used:

  • New data is sent over multicast.

  • Retransmissions are sent over unicast.

To receive on one multicast address:

DDS_StringSeq_set_maximum(&DomainParticipantQos.user_traffic.enabled_transports, 1);
DDS_StringSeq_set_length(&DomainParticipantQos.user_traffic.enabled_transports, 1);

*DDS_StringSeq_get_reference(&DomainParticipantQos.user_traffic.enabled_transports, 0) =
                             DDS_String_dup("_udp://239.255.0.1");

7.8.6. Configuring Enabled Transports per DataReader or DataWriter

A DataReader or DataWriter can specify its own addresses in the DataReaderQos.transport.enabled_transports and DataWriterQos.transport.enabled_transports policies. The address format is exactly the same as for DomainParticipantQos.user_traffic.enabled_transports, with the restriction that a DataWriter can only specify its own unicast addresses.

7.8.7. Discovery Plugins

When a DomainParticipant receives a participant discovery message from another DomainParticipant, it will engage in the process of exchanging information of user-created DataWriter and DataReader endpoints.

RTI Connext Cert provides two ways of determinig endpoint information of other DomainParticipants: Dynamic Discovery Plugin and Static Discovery Plugin.

7.8.7.1. Dynamic Discovery Plugin

Note

The Dynamic Discovery Plugin is not available in Connext Cert. The description is only included for completeness and comparison with the Static Discovery Plugin.

Dynamic endpoint discovery uses builtin discovery DataWriters and DataReader to exchange messages about user created DataWriter and DataReaders. A DomainParticipant using dynamic participant, dynamic endpoint (DPDE) discovery will have a pair of builtin DataWriters for sending messages about its own user created DataWriters and DataReaders, and a pair of builtin DataReaders for receiving messages from other DomainParticipants about their user created DataWriters and DataReaders.

Given a DomainParticipant with a user DataWriter, receiving an endpoint discovery message for a user DataReader allows the DomainParticipant to get the type, topic, and QoS of the DataReader that determine whether the DataReader is a match. When a matching DataReader is discovered, the DataWriter will include that DataReader and its locators as destinations for its subsequent writes.

Note

RTI Connext uses the acronyms SPDP and SEDP to distinguish between the two phases of Simple Discovery: participant and endpoint phases (see Discovery in the Core Libraries User’s Manual). RTI Connext Cert uses the acronyms DPSE and DPDE to distinguish between the static and dynamic endpoint discovery plugins available in RTI Connext Cert. The DPSE plugin implements the SPDP protocol and DPDE implements the SPDP and SEDP protocol.

7.8.7.2. Static Discovery Plugin

Static endpoint discovery uses function calls to statically assert information about remote endpoints belonging to remote DomainParticipants. An application with a DomainParticipant using dynamic participant, static endpoint (DPSE) discovery has control over which endpoints belonging to particular remote DomainParticipants are discoverable.

Whereas dynamic endpoint-discovery can establish matches for all endpoint-discovery messages it receives, static endpoint-discovery establishes matches only for the endpoint that have been asserted programmatically. When a DomainParticipant receives a participant discovery message from another DomainParticipant, it will engage in the process of matching previously asserted user-created DataWriter and DataReader endpoints.

With DPSE, a user needs to know a priori the configuration of the entities that will need to be discovered by its application. The user must know the names of all DomainParticipants within the DDS domain and the exact QoS of the remote DataWriters and DataReaders.

Note

RTI Connext uses the acronyms SPDP and SEDP to distinguish between the two phases of Simple Discovery: participant and endpoint phases (see Discovery in the Core Libraries User’s Manual). RTI Connext Cert uses the acronyms DPSE and DPDE to distinguish between the static and dynamic endpoint discovery plugins available in RTI Connext Cert. The DPSE plugin implements the SPDP protocol and DPDE implements the SPDP and SEDP protocol.

Please refer to the C API Reference the following remote entity assertion APIs:

7.8.7.2.1. Remote Participant Assertion

Given a local DomainParticipant, static discovery requires first the names of remote DomainParticipants to be asserted, in order for endpoints on them to match. This is done by calling DPSE_RemoteParticipant_assert with the name of a remote DomainParticipant. The name must match the name contained in the participant discovery announcement produced by that DomainParticipant. This has to be done reciprocally between two DomainParticipants so that they may discover one another.

For example, a DomainParticipant has entity name “participant_1”, while another DomainParticipant has name “participant_2.” participant_1 should call DPSE_RemoteParticipant_assert(“participant_2”) in order to discover participant_2. Similarly, participant_2 must also assert participant_1 for discovery between the two to succeed.

/* participant_1 is asserting (remote) participant_2 */
retcode = DPSE_RemoteParticipant_assert(participant_1,
                                        "participant_2");
if (retcode != DDS_RETCODE_OK) {
    printf("participant_1 failed to assert participant_2\n");
    goto done;
}

7.8.7.2.2. Remote Publication and Subscription Assertion

Next, a DomainParticipant needs to assert the remote endpoints it wants to match that belong to an already asserted remote DomainParticipant. The endpoint assertion function is used, specifying an argument which contains all the QoS and configuration of the remote endpoint. Where DPDE gets remote endpoint QoS information from received endpoint-discovery messages, in DPSE, the remote endpoint’s QoS must be configured locally. With remote endpoints asserted, the DomainParticipant then waits until it receives a participant discovery announcement from an asserted remote DomainParticipant. Once received that, all endpoints that have been asserted for that remote DomainParticipant are considered discovered and ready to be matched with local endpoints.

Assume participant_1 contains a DataWriter, and participant_2 has a DataReader, both communicating on topic HelloWorld. participant_1 needs to assert the DataReader in participant_2 as a remote subscription. The remote subscription data passed to the operation must match exactly the QoS actually used by the remote DataReader:

/* Set participant_2's reader's QoS in remote subscription data  */
rem_subscription_data.key.value[DDS_BUILTIN_TOPIC_KEY_OBJECT_ID] = 200;
rem_subscription_data.topic_name = DDS_String_dup("Example HelloWorld");
rem_subscription_data.type_name = DDS_String_dup("HelloWorld");
rem_subscription_data.reliability.kind = DDS_RELIABLE_RELIABILITY_QOS;

/* Assert reader as a remote subscription belonging to (remote) participant_2 */
retcode = DPSE_RemoteSubscription_assert(participant_1,
                                         "participant_2",
                                         &rem_subscription_data,
                                         HelloWorld_get_key_kind(HelloWorldTypePlugin_get(), NULL));
if (retcode != DDS_RETCODE_OK)
{
    printf("failed to assert remote subscription\n");
    goto done;
}

Reciprocally, participant_2 must assert participant_1’s DataWriter as a remote publication, also specifying matching QoS parameters:

/* Set participant_1's writer's QoS in remote publication data  */
rem_publication_data.key.value[DDS_BUILTIN_TOPIC_KEY_OBJECT_ID] = 100;
rem_publication_data.key.value.topic_name = DDS_String_dup("Example HelloWorld");
rem_publication_data.key.value.type_name = DDS_String_dup("HelloWorld");
rem_publication_data.key.value.reliability.kind = DDS_RELIABLE_RELIABILITY_QOS;

/* Assert writer as a remote publication belonging to (remote) participant_1 */
retcode = DPSE_RemotePublication_assert(participant_2,
                                        "participant_1",
                                        &rem_publication_data,
                                        HelloWorld_get_key_kind(HelloWorldTypePlugin_get(), NULL));
if (retcode != DDS_RETCODE_OK)
{
    printf("failed to assert remote publication\n");
    goto done;
}

When participant_1 receives a participant discovery message from participant_2, it is aware of participant_2, based on its previous assertion, and it knows participant_2 has a matching DataReader, also based on the previous assertion of the remote endpoint. It therefore establishes a match between its DataWriter and participant_2’s DataReader. Likewise, participant_2 will match participant_1’s DataWriter with its local DataRead, upon receiving one of participant_1’s participant discovery messages.

Note, with DPSE, there is no runtime check of QoS consistency between DataWriters and DataReaders, because no endpoint discovery messages are exchanged. This makes it extremely important that users of DPSE ensure that the QoS set for a local DataWriter and DataReader is the same QoS being used by another DomainParticipant to assert it as a remote DataWriter or DataReader.

7.8.8. Asymmetric Matching and Lost Samples

The DDS discovery process is necessary to establish communication between a DataWriter and a DataReader. However, it is important to understand that DDS applications do not connect to each other; there is no handshake protocol to ensure that a DataReader is ready to receive data from a DataWriter. Thus, it is possible that a DataWriter matches a DataReader before the DataReader matches the DataWriter (and vice versa). For this reason, it is possible that data published by a DataWriter is not received by the DataReader, even on a local network.

The reason for this asymmetric behavior can be for any number of reasons, such as, but not limited to:

  • Network delays

  • Packets taking different paths through the network

  • Address resolution delays

  • OS scheduling

DDS offers some solutions to mitigate this problem, e.g., the DURABILITY QoS policy, but in other cases it may be necessary for applications to implement their own synchronization protocols.