6.1. Transport API

The following diagram presents the classes defined on the transport API of eProsima Fast DDS. It shows the abstract API interfaces, and the classes required to implement a transport.

hide empty members
interface TransportDescriptorInterface
{
    +uint32_t maxMessageSize
    +uint32_t maxInitialPeersRange
}
interface TransportInterface
{
    #int32_t transport_kind_
}
class Locator
{
    +int32_t kind
    +uint32_t port
    +octet[16] address
}
TransportDescriptorInterface <|-- CustomTransportDescriptor
TransportInterface <|-- CustomTransport
CustomTransport <--right CustomTransportDescriptor : create
Locator <--right TransportInterface

Transport API diagram

6.1.1. TransportDescriptorInterface

Any class that implements the TransportDescriptorInterface is known as a TransportDescriptor. It acts as a builder for a given transport, meaning that is allows to configure the transport, and then a new Transport can be built according to this configuration using its create_transport() factory member function.

6.1.1.1. Data members

The TransportDescriptorInterface defines the following data members:

Member

Data type

Description

maxMessageSize

uint32_t

Maximum size of a single message in the transport.

maxInitialPeersRange

uint32_t

Number of channels opened with each initial remote peer

Any implementation of TransportDescriptorInterface should add as many data members as required to full configure the transport it describes.

6.1.2. TransportInterface

A Transport is any class that implements the TransportInterface. It is the object that actually performs the message distribution over a physical transport.

Each Transport class defines its own transport_kind, a unique identifier that is used to check the compatibility of a Locator with a Transport, i.e., determine whether a Locator refers to a Transport or not.

Applications do not create the Transport instance themselves. Instead, applications use a TransportDescriptor instance to configure the desired transport, and add this configured instance to the list of user-defined transports of the DomainParticipant. The DomainParticipant will use the factory function on the TransportDescriptor to create the Transport when required.

DomainParticipantQos qos;

// Create a descriptor for the new transport.
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
udp_transport->sendBufferSize = 9216;
udp_transport->receiveBufferSize = 9216;
udp_transport->non_blocking_send = true;

// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(udp_transport);

// Avoid using the default transport
qos.transport().use_builtin_transports = false;

6.1.2.1. Data members

The TransportInterface defines the following data members:

Member

Data type

Description

transport_kind_

int32_t

Unique identifier of the transport type.

Note

transport_kind_ is a protected data member for internal use. It cannot be accessed nor modified from the public API. However, users that are implementing a custom Transport need to fill it with a unique constant value in the new implementation.

Currently the following identifiers are used in Fast DDS:

Identifier

Value

Transport type

LOCATOR_KIND_RESERVED-api

0

None. Reserved value for internal use.

LOCATOR_KIND_UDPv4-api

1

UDP Transport over IPv4.

LOCATOR_KIND_UDPv6-api

2

UDP Transport over IPv6.

LOCATOR_KIND_TCPv4-api

4

TCP Transport over IPv4.

LOCATOR_KIND_TCPv6-api

8

TCP Transport over IPv6.

LOCATOR_KIND_SHM-api

16

Shared Memory Transport.

6.1.3. Locator

A Locator_t uniquely identifies a communication channel with a remote peer for a particular transport. For example, on UDP transports, the Locator will contain the information of the IP address and port of the remote peer.

The Locator class is not abstract, and no specializations are implemented for each transport type. Instead, transports should map the data members of the Locator class to their own channel identification concepts. For example, on Shared Memory Transport the address contains a unique ID for the local host, and the port represents the shared ring buffer used to communicate buffer descriptors.

Please refer to Listening Locators for more information about how to configure DomainParticipant to listen to incoming traffic.

6.1.3.1. Data members

The Locator defines the following data members:

Member

Data type

Description

kind

int32_t

Unique identifier of the transport type.

port

uint32_t

The channel port.

address

octet[16]

The channel address.

In TCP, the port of the locator is divided into a physical and a logical port.

  • The physical port is the port used by the network device, the real port that the operating system understands. It is stored in the two least significant bytes of the member port.

  • The logical port is the RTPS port. It is stored in the two most significant bytes of the member port.

In UDP there is only the physical port, which is also the RTPS port, and is stored in the two least significant bytes of the member port.

6.1.3.2. Configuring IP locators with IPLocator

IPLocator is an auxiliary static class that offers methods to manipulate IP based locators. It is convenient when setting up a new UDP Transport or TCP Transport, as it simplifies setting IPv4 and IPv6 addresses, or manipulating ports.

For example, normally users configure the physical port and do not need to worry about logical ports. However, IPLocator allows to manage them if needed.

// We will configure a TCP locator with IPLocator
Locator_t locator;

// Get & set the physical port
uint16_t physical_port = IPLocator::getPhysicalPort(locator);
IPLocator::setPhysicalPort(locator, 5555);

// On TCP locators, we can get & set the logical port
uint16_t logical_port = IPLocator::getLogicalPort(locator);
IPLocator::setLogicalPort(locator, 7400);

// Set WAN address
IPLocator::setWan(locator, "80.88.75.55");

Fast DDS also allows to specify locator addresses using names. When an address is specified by a name, Fast DDS will query the known hosts and available DNS servers to try to resolve the IP address. This address will in turn be used to create the listening locator in the case of server, or as the address of the remote server in the case of clients (and servers that connect to other servers).

C++

Locator_t locator;
auto response = eprosima::fastrtps::rtps::IPLocator::resolveNameDNS("localhost");
// Get the first returned IPv4
if (response.first.size() > 0)
{
    IPLocator::setIPv4(locator, response.first.begin()->data());
    locator.port = 11811;
}
// Use the locator to create server or client

XML

<locator>
    <udpv4>
        <port>11811</port>
        <address>localhost</address>
    </udpv4>
</locator>

Warning

Currently, XML only supports loading IP addresses by name for UDP transport.

6.1.4. Chaining of transports

There are use cases where the user needs to pre-process out-coming information before being sent to network and also the incoming information after being received. Transport API offers two interfaces for implementing this kind of functionality: ChainingTransportDescriptor and ChainingTransport.

hide empty members
interface TransportDescriptorInterface
{
    +uint32_t maxMessageSize
    +uint32_t maxInitialPeersRange
}
interface TransportInterface
{
    #int32_t transport_kind_
}
interface ChainingTransportDescriptor
{
    +std::shared_ptr<TransportDescriptorInterface> low_level_descriptor
}
interface ChainingTransport
{
    #std::unique_ptr<TransportInterface> low_level_transport_
    {abstract} bool send(...)
    {abstract} void receive(...)
}
TransportDescriptorInterface <|-- ChainingTransportDescriptor
TransportInterface <|-- ChainingTransport
ChainingTransportDescriptor <|-- CustomChainingTransportDescriptor
ChainingTransport <|-- CustomChainingTransport
CustomChainingTransport <-- CustomChainingTransportDescriptor : create
CustomChainingTransportDescriptor "1" *-- "1" TransportDescriptorInterface : contains
CustomChainingTransport "1" *-- "1" TransportInterface : contains
CustomChainingTransportDescriptor --- UDPv4TransportDescriptor : example
CustomChainingTransport ---  UDPv4Transport : example

These extensions allow to implement a new Transport which depends on another one (called here as low level transport). The user can override the send() function, pre-processing the out-coming buffer before calling the associated low level transport. Also, when a incoming buffer arrives to the low level transport, this one calls the overridden receive() function to allow to pre-process the buffer.

6.1.4.1. ChainingTransportDescriptor

Implementing ChainingTransportDescriptor allows to configure the new Transport and set the low level transport on which it depends. The associated low level transport can be any transport which inherits from TransportInterface (including another ChainingTransport).

The ChainingTransportDescriptor defines the following data members:

Member

Data type

Description

low_level_descriptor

std::shared_ptr<TransportDescriptorInterface>

Transport descriptor of the low level transport

User has to specify the low level tranport in the definition of its new custom transport.

        DomainParticipantQos qos;

        auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();

        // Create a descriptor for the new transport.
        // The low level transport will be a UDPv4Transport.
        auto custom_transport = std::make_shared<CustomChainingTransportDescriptor>(udp_transport);

        // Link the Transport Layer to the Participant.
        qos.transport().user_transports.push_back(custom_transport);

        // Avoid using the default transport
        qos.transport().use_builtin_transports = false;

6.1.4.2. ChainingTransport

This interface forces the user to implement send() and receive() functions. The idea is to pre-process the buffer and after, call to the next level.

class CustomChainingTransport : public eprosima::fastdds::rtps::ChainingTransport
{

public:

    CustomChainingTransport(
            const CustomChainingTransportDescriptor& descriptor)
        : ChainingTransport(descriptor)
        , descriptor_(descriptor)
    {
    }

    eprosima::fastdds::rtps::TransportDescriptorInterface* get_configuration()
    {
        return &descriptor_;
    }

    bool send(
            eprosima::fastrtps::rtps::SenderResource* low_sender_resource,
            const eprosima::fastrtps::rtps::octet* send_buffer,
            uint32_t send_buffer_size,
            eprosima::fastrtps::rtps::LocatorsIterator* destination_locators_begin,
            eprosima::fastrtps::rtps::LocatorsIterator* destination_locators_end,
            const std::chrono::steady_clock::time_point& timeout) override
    {
        //
        // Preprocess outcoming buffer.
        //

        // Call low level transport
        return low_sender_resource->send(send_buffer, send_buffer_size, destination_locators_begin,
                       destination_locators_end, timeout);
    }

    void receive(
            eprosima::fastdds::rtps::TransportReceiverInterface* next_receiver,
            const eprosima::fastrtps::rtps::octet* receive_buffer,
            uint32_t receive_buffer_size,
            const eprosima::fastrtps::rtps::Locator_t& local_locator,
            const eprosima::fastrtps::rtps::Locator_t& remote_locator) override
    {
        //
        // Preprocess incoming buffer.
        //

        // Call upper level
        next_receiver->OnDataReceived(receive_buffer, receive_buffer_size, local_locator, remote_locator);
    }

private:

    CustomChainingTransportDescriptor descriptor_;
};