This article describes how Fast DDS 3.6.2 constructs and uses identities for RTPS participants and endpoints. The design distinguishes entities across both space and time:

  • Space: distinguish entities in different hosts, processes, and participants, and distinguish readers and writers within one participant.
  • Time: distinguish newly created entities from previous entities whose process or participant IDs have been reused.

The GUID prefix identifies the participant; the entity ID identifies an entity within it. Process randomness and participant-generation counters reduce identity reuse across restarts and recreation. These mechanisms reduce collision risk rather than guarantee uniqueness under every configuration.

1. GUID and instance handle

This section compares RTPS entity GUIDs with DDS entity instance handles.

Property GUID Entity instance handle
Layer RTPS DDS API
Purpose Identify participants, readers, and writers in protocol communication Refer to DDS entities through the API
Structure 12-byte prefix plus 4-byte entity ID Opaque value; applications should not interpret its bytes
Access RTPS entity GUID DDS entity get_instance_handle()

Fast DDS maps the GUIDs of RTPS-backed entities to their DDS entity handles. In that case, the two representations identify the same entity.

The GUID-to-handle reinterpret_cast is a Fast DDS implementation shortcut. DDS does not require these C++ types to share a memory layout.

2. GUID layout and generation

A GUID contains a 12-byte prefix and a 4-byte entity ID. Entities within one participant share its prefix. Entity IDs distinguish them within that participant.

GUID (16 bytes)

┌──────────────────────────────────────┬────────────────────────────┐
│ Bytes 0–11: GUID prefix (12 bytes)   │ Bytes 12–15: Entity ID     │
│                                      │ (4 bytes)                  │
└──────────────────────────────────────┴────────────────────────────┘

Fast DDS normally generates the prefix as follows:

GUID prefix (12 bytes)

┌──────────────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────────────────────────┐
│ Bytes 0–1        │ Bytes 2–3        │ Bytes 4–5        │ Bytes 6–7        │ Bytes 8–11                                 │
│ Vendor (2 bytes) │ Host hash        │ PID low bits     │ Random value     │ Participant ID + reuse counter             │
│                  │ (2 bytes)        │ (2 bytes)        │ (2 bytes)        │ (4-byte generation-qualified ID)           │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────────────────────────┘
Prefix bytes Fast DDS value
0–1 eProsima vendor ID: 01 0f
2–3 IPv4-derived 16-bit host ID
4–5 Lowest 16 bits of the OS PID
6–7 Random 16-bit value generated by the process-local GuidUtils singleton
8–11 Reusable participant ID combined with a reuse counter; distinguishes successive uses before counter wraparound

RTPS specifies the vendor bytes. The remaining encoding shown here is a Fast DDS convention. Generated multi-byte values use least-significant byte first.

The reuse counter belongs to each reusable participant-ID slot in m_RTPSParticipantIDs, not to the participant object or a global generation counter. Each slot stores its own reserved, used, and counter state. Removing a participant releases the slot but retains its counter; the next participant using that slot receives the next generation value.

Creation sequence

  1. DomainParticipantImpl asks RTPSDomainImpl::create_participant_guid() to reserve the configured participant ID and generate a preliminary GUID. A negative configured value requests automatic allocation of the smallest ID whose reserved and used flags are both false.
  2. Enabling the DDS participant creates its RTPS participant and generates the actual prefix.
  3. RTPSParticipantImpl combines the selected prefix with participant entity ID 00 00 01 c1.
  4. The DDS implementation adopts the actual RTPS GUID and updates its entity handle.
  5. Removing the participant releases its ID but retains the prefix-generation counter. The ID can be reused without immediately reusing the prefix.
RTPSDomainImpl method When called Purpose Advances prefix counter?
create_participant_guid() DDS participant construction Reserve an ID and initialize the DDS object’s preliminary GUID and entity handle No
create_participant() DDS participant enable, or direct RTPS creation Create the RTPS participant with its actual GUID Yes, for IDs below 0x10000

create_participant_guid() calls guid_prefix_create(participant_id, ...) directly. It also assigns participant entity ID 00 00 01 c1. No RTPS participant object exists yet.

The preliminary GUID initializes guid_ and handle_, supports local DDS identity queries, and lets the factory check initialization before enable. It is not yet advertised through RTPS discovery.

create_participant() calls guid_prefix_create(get_id_for_prefix(ID), ...). This uses and advances the generation counter. After creation, DDS replaces its preliminary GUID and handle with the actual RTPS GUID.

The two paths reuse the same allocated participant ID. The counter is consumed during actual RTPS prefix generation, not preliminary GUID generation. A pre-enable GUID or handle can therefore differ from its post-enable value.

[1] DDS construction
    Reserve the configured participant ID or the smallest free ID.
    Pre-calculate the preliminary GUID.
                              │
                              ▼
[2] DDS enable
    Call RTPSDomainImpl::create_participant().
                              │
                              ▼
[3] Generate a qualified participant-prefix value
    Combine the participant ID with its reuse counter.
    Advance the counter for the next creation.
                              │
                              ▼
[4] guid_prefix_create()
    Copy the process prefix into bytes 0–7.
    Write the qualified participant-prefix value into bytes 8–11.
                              │
                              ▼
[5] Build the participant GUID
    Select the communication prefix.
    Append participant entity ID 00 00 01 c1.
                              │
                              ▼
[6] DDS adopts the RTPS GUID
    Update the DDS participant GUID and entity handle.
                              │
                              ▼
[7] Remove the participant
    Clear its reserved and used ID entries, but retain the reuse counter.
                              │
                              └──── ID can be selected again ────► [1]

The diagram shows the normal path with small participant IDs, whether configured explicitly or allocated automatically. For IDs below 65,536, the prefix-generation value contains the reuse counter in bits 31–16 and the participant ID in bits 15–0.

Reusable participant ID, different GUID prefix

The participant ID is a reusable allocation slot and default-port input. The GUID identifies a particular participant creation. Reusing a slot should not make peers mistake the new participant for the old one.

reserve_participant_id() prevents simultaneous reservations of the same ID in this process. It does not prevent a later participant from using an ID released after deletion.

Create A → reserve ID 3
While A exists → another reservation of ID 3 fails
Delete A → release ID 3
Create B → reserve ID 3 again

Without the counter, A and B could have the same generated prefix: the vendor, host, process, and participant-ID components are unchanged. A peer could still retain A’s discovery state, for example until its lease expires, and mistake B for A.

Reservation prevents simultaneous allocation conflicts. The generation counter distinguishes successive creations using the same ID.

ret |= m_RTPSParticipantIDs[participant_id].counter;
m_RTPSParticipantIDs[participant_id].counter += 0x10000;

For participant IDs below 0x10000, the ID occupies the lower 16 bits and the counter occupies the upper 16 bits. Bitwise OR combines them without overlap. Adding 0x10000 advances the upper 16 bits for the next creation.

participant ID:  0x00000003
reuse counter:  0x00020000
bitwise OR:      0x00020003  → GUID-prefix bytes 8–11
next counter:    0x00030000

If ID 3 starts with counter zero, successive uses produce 0x00000003, 0x00010003, and 0x00020003. The actual ID stays 3, so its calculated default ports stay unchanged. The generated prefix changes with the counter.

Removing a participant clears its allocation flags but retains this counter. The counter is separate from the per-process random value and wraps after 65,536 increments. It protects against immediate identity reuse, not all possible collisions.

Why keep participant IDs small?

Default UDP ports depend on the domain ID and participant ID:

Traffic Default UDP port
Discovery multicast 7400 + 250 × domain_id
Discovery unicast 7410 + 250 × domain_id + 2 × participant_id
User-data multicast 7401 + 250 × domain_id
User-data unicast 7411 + 250 × domain_id + 2 × participant_id

For domain 0, IDs 0, 1, and 2 select discovery ports 7410, 7412, and 7414. Their default user-data unicast ports are 7411, 7413, and 7415.

A unicast initial-peer locator with port zero expands into candidate discovery ports for IDs 0 through maxInitialPeersRange - 1. Small IDs keep participants within that probe range. Reusing free slots avoids port numbers growing with every creation.

The generation counter changes the GUID prefix, not the port calculation. Reusing ID 0 can reuse ports 7410/7411 while giving the new participant a different prefix.

These formulas describe default UDP settings. Explicit locator ports and configurable port parameters can change them.

Users can set qos.wire_protocol().participant_id to a nonnegative value instead of using automatic allocation. This selects the corresponding default unicast ports: for domain 0, ID 3 selects discovery port 7416 and user-data port 7417. Multicast ports remain independent of the participant ID. Explicit locator ports can override these calculated defaults.

3. Host ID, machine ID, and process identity

Host ID versus machine ID

Property Host ID Machine ID
Representation 16-bit value OS-provided string
Source IPv4 locator address list Platform identity store
In generated GUID prefix Yes, bytes 2–3 No
Host detection GUID-only comparison Participant discovery-data comparison

Host hashes all returned IPv4 locators’ 16-byte address fields with MD5, in list order. It XORs the digest’s eight 16-bit words into one 16-bit host ID. An empty list uses fallback bytes 127, 1.

The hash covers the address list, not one selected interface. List order and address changes can change it. Network namespaces can expose different lists. A 16-bit hash can collide.

The Host singleton caches its result. Existing processes do not automatically recompute it after network changes.

Fast DDS reads the machine ID directly; it does not generate or hash it here.

Platform Machine-ID source
Linux/POSIX First 32 characters of /etc/machine-id
Windows Registry SOFTWARE\Microsoft\Cryptography\MachineGuid
macOS IOPlatformUUID

On systemd-based Linux, the ID is normally initialized at installation or first boot. It may be provisioned, taken from an existing platform ID, or randomly generated. It normally persists across boots.

Retrieval failure produces an empty string. Cloned images can share an ID if provisioning preserves it. Containers may expose their own ID or the host’s ID.

Locality checks

Method Comparison
GuidPrefix_t::is_on_same_host_as() First 4 prefix bytes
GuidPrefix_t::is_on_same_process_as() First 8 prefix bytes
GuidPrefix_t::is_from_this_host() First 4 bytes against the local singleton prefix
GuidPrefix_t::is_from_this_process() First 8 bytes against the local singleton prefix
ParticipantProxyData::is_from_this_host() Machine IDs when the remote ID is present; otherwise GUID-based fallback

Prefix checks assume Fast DDS-generated encoding. They do not query the remote OS. Custom prefixes can invalidate that assumption. Machine-ID comparison requires discovery data; a GUID alone does not contain that string.

Why PID alone is insufficient

Separate container PID namespaces can assign PID 1 to several processes. After a crash or reboot, a process can regain its previous PID while peers still retain the old participant’s unexpired discovery lease.

Fast DDS combines the PID’s low 16 bits with a random 16-bit value. The GuidUtils singleton generates that value once, so participants in the process share it. Fresh process initialization normally produces a different value.

For example, two containers can use PID 1 but different random values. Their process portions then differ. This reduces collision and identity-reuse risk; it does not guarantee uniqueness.

4. Entity ID

EntityId_t identifies an entity within its participant. It has a three-byte entity key and a one-byte entity kind.

Entity ID (4 bytes)

┌────────────────────────────────┬────────────────────────────┐
│ Bytes 0–2: Entity key          │ Byte 3: Entity kind        │
│ (3 bytes)                      │ (1 byte)                   │
└────────────────────────────────┴────────────────────────────┘

The kind distinguishes categories such as participants, keyed/unkeyed readers and writers, and built-in/user entities. Built-in IDs are predefined. User endpoints receive IDs unique within their participant.

The GUID prefix distinguishes participants. The entity ID distinguishes entities within one participant.

Every RTPS participant entity uses the predefined ID 00 00 01 c1. Their different prefixes distinguish their complete GUIDs:

Participant A = prefix A + 00 00 01 c1
Participant B = prefix B + 00 00 01 c1

Readers and writers belonging to a participant share its prefix but have different entity IDs.

Built-in endpoint roles also have predefined IDs. Examples include SPDP participant discovery, SEDP publication/subscription discovery, liveliness, security, and type lookup. Each participant can reuse those role IDs because its prefix differs.

These IDs are predefined because the protocols assign well-known identities to built-in roles, not merely because each role has at most one endpoint per participant. Endpoint creation depends on the enabled features and discovery mode.

User readers and writers receive allocated entity IDs, even when a participant contains only one user endpoint. Not all RTPS entity IDs are predefined constants.