VLink  2.0.0
A high-performance communication middleware
dds_conf.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file dds_conf.h
26  * @brief Transport configuration for the @c dds:// eProsima Fast-DDS / Fast-RTPS transport.
27  *
28  * @details
29  * @c DdsConf is the default cross-machine transport in VLink and binds the @c dds://
30  * URL scheme to the eProsima Fast-DDS implementation of the OMG DDS specification.
31  * It supports the full RTPS wire protocol over UDP/TCP and SHM, scales from a single
32  * LAN segment to wide-area deployments, and interoperates with any other compliant
33  * DDS vendor on the same Domain. Pub/sub, RPC (request/response over DDS topics)
34  * and field-style state synchronisation are all routed through Fast-DDS DataReaders
35  * and DataWriters under the hood.
36  *
37  * @par Supported Node Types
38  *
39  * | Publisher | Subscriber | Server | Client | Getter | Setter |
40  * | :-------: | :--------: | :----: | :----: | :----: | :----: |
41  * | yes | yes | yes | yes | yes | yes |
42  *
43  * @par URL Format
44  * @code
45  * dds://<topic>[?domain=<N>&depth=<N>&qos=<profile>]
46  * dds://<topic>[?domain=<N>&part=<v>&topic=<v>&pub=<v>&sub=<v>&writer=<v>&reader=<v>]
47  * @endcode
48  *
49  * | Component | Description |
50  * | ---------- | ---------------------------------------------------------------------------- |
51  * | @c topic | DDS topic name; URL host concatenated with the URL path |
52  * | @c domain | DDS Domain ID (@c ?domain=); defaults from the @c VLINK_DDS_DOMAIN env var |
53  * | @c depth | Optional history-depth override; @c 0 keeps the QoS-selected depth |
54  * | @c qos | Named QoS profile registered with @c register_qos() (@c ?qos=) |
55  * | @c qos_ext | Remaining query keys after @c domain, @c depth, @c qos have been removed |
56  *
57  * @par Environment Variables
58  *
59  * | Variable | Description | Default |
60  * | ------------------ | ------------------------------------------------------------ | ------- |
61  * | VLINK_DDS_DOMAIN | Default DDS Domain ID when @c ?domain= is not present in URL | 0 |
62  *
63  * @par QoS Registration
64  * Named profiles must be registered before any endpoint that references them is created.
65  * A typical reliable + transient-local profile for late-joining subscribers looks like:
66  * @code
67  * vlink::Qos late_joiner;
68  * late_joiner.reliability.kind = vlink::Qos::Reliability::kReliable;
69  * late_joiner.durability.kind = vlink::Qos::Durability::kTransientLocal;
70  * late_joiner.history.kind = vlink::Qos::History::kKeepLast;
71  * late_joiner.history.depth = 16;
72  * vlink::DdsConf::register_qos("late_joiner", late_joiner);
73  * @endcode
74  *
75  * @par Type-Support Registration
76  * For CDR-serialised messages the Fast-DDS @c TopicDataType factory must be wired
77  * to the topic name before any endpoint is opened on that topic. An optional
78  * response type can be registered at the same time for RPC topics; the helper
79  * automatically appends the @c "___resp" suffix.
80  * @code
81  * vlink::DdsConf::register_topic<MyMsgPubSubType>("my_topic");
82  * vlink::DdsConf::register_topic<MyReqPubSubType, MyRespPubSubType>("my_rpc");
83  * vlink::DdsConf::register_url<MyMsgPubSubType>("dds://my_topic?domain=1");
84  * @endcode
85  *
86  * @par Example
87  * @code
88  * vlink::DdsConf::load_global_qos_file("/etc/vlink/dds_profile.xml");
89  *
90  * vlink::Qos qos;
91  * qos.reliability.kind = vlink::Qos::Reliability::kReliable;
92  * qos.durability.kind = vlink::Qos::Durability::kTransientLocal;
93  * vlink::DdsConf::register_qos("reliable_tl", qos);
94  *
95  * auto pub = vlink::Publisher<MyMsg>::create_unique("dds://state?domain=42&qos=reliable_tl");
96  * auto sub = vlink::Subscriber<MyMsg>::create_unique("dds://state?domain=42&qos=reliable_tl");
97  * @endcode
98  *
99  * @note Compiled only when @c VLINK_SUPPORT_DDS is defined.
100  * @note @c qos and @c qos_ext are mutually exclusive; setting both forces @c is_valid() to @c false.
101  * @note RPC reply topics are derived by appending @c "___resp" to the topic name.
102  */
103 
104 #pragma once
105 
106 #ifdef VLINK_SUPPORT_DDS
107 
108 #include <cstdint>
109 #include <map>
110 #include <shared_mutex>
111 #include <string>
112 #include <tuple>
113 #include <vector>
114 
115 #include "../base/functional.h"
116 #include "../extension/qos.h"
117 #include "../impl/conf.h"
118 
119 namespace eprosima::fastdds::dds {
120 class TopicDataType;
121 }
122 
123 namespace vlink {
124 
125 /**
126  * @struct DdsConf
127  * @brief Concrete @c Conf describing a Fast-DDS endpoint addressed by a @c dds:// URL.
128  *
129  * @details
130  * Captures the topic, Domain ID, history-depth override, and either a named QoS
131  * profile or a per-entity QoS property map. Both constructors share the same
132  * @c topic and @c domain fields; the second constructor populates @c qos_ext
133  * instead of @c qos.
134  */
135 struct VLINK_EXPORT DdsConf final : public Conf {
136  std::string topic; ///< Fast-DDS topic name (URL host concatenated with path).
137  int32_t domain{0}; ///< DDS Domain ID joined by the underlying DomainParticipant.
138  int32_t depth{0}; ///< History-depth override; @c 0 keeps the QoS-selected depth.
139  std::string qos; ///< Named QoS profile key registered via @c register_qos().
140  PropertiesMap qos_ext; ///< Per-entity property map; populated from query keys outside @c domain / @c depth / @c qos.
141 
142  /**
143  * @brief Builds a @c DdsConf from topic, Domain, depth, and an optional named QoS profile.
144  *
145  * @param _topic Fast-DDS topic name.
146  * @param _domain Domain ID; defaults to @c 0.
147  * @param _depth History-depth override; defaults to @c 0 (use QoS depth).
148  * @param _qos Named QoS profile key; empty by default.
149  */
150  explicit DdsConf(const std::string& _topic, int32_t _domain = 0, int32_t _depth = 0, const std::string& _qos = "");
151 
152  /**
153  * @brief Builds a @c DdsConf from topic, Domain, and an explicit per-entity QoS map.
154  *
155  * @details
156  * Use this overload when QoS must be assembled at runtime instead of being
157  * registered as a named profile. Mutually exclusive with the @c qos field;
158  * @c is_valid() returns @c false if both are non-empty on the same instance.
159  *
160  * @param _topic Fast-DDS topic name.
161  * @param _domain DDS Domain ID.
162  * @param _qos_ext Property map carrying per-entity QoS overrides.
163  */
164  explicit DdsConf(const std::string& _topic, int32_t _domain, const PropertiesMap& _qos_ext);
165 
166  /**
167  * @brief Component-wise equality on all configuration fields.
168  *
169  * @param conf Configuration to compare with.
170  * @return @c true when @c topic, @c domain, @c depth, @c qos and @c qos_ext all match.
171  */
172  [[nodiscard]] bool operator==(const DdsConf& conf) const noexcept;
173 
174  /**
175  * @brief Logical negation of @c operator==.
176  *
177  * @param conf Configuration to compare with.
178  * @return @c true when any field differs from @p conf.
179  */
180  [[nodiscard]] bool operator!=(const DdsConf& conf) const noexcept;
181 
182  /**
183  * @brief Reports this object's transport tag.
184  *
185  * @return @c TransportType::kDds.
186  */
187  [[nodiscard]] TransportType get_transport_type() const override;
188 
189  /**
190  * @brief Returns the topics currently discovered on the given DDS Domain.
191  *
192  * @details
193  * Each entry is a @c (topic_name, type_name) pair captured from the
194  * @c DdsFactory discovery cache. The result is a point-in-time snapshot and may
195  * be empty when discovery has not yet completed.
196  *
197  * @param _domain DDS Domain ID to query.
198  * @return Vector of @c (topic_name, type_name) tuples; may be empty.
199  */
200  [[nodiscard]] static std::vector<std::tuple<std::string, std::string>> get_discovered_topics(int32_t _domain);
201 
202  /**
203  * @brief Loads a Fast-DDS XML QoS profile file as the process-wide default.
204  *
205  * @details
206  * Must be invoked before any @c dds:// participant is created; profile names
207  * declared in the file become available to all Fast-DDS endpoints in the process.
208  *
209  * @param filepath Absolute or relative path to a Fast-DDS XML profile file.
210  * @return @c true when the file was loaded successfully, @c false otherwise.
211  */
212  static bool load_global_qos_file(const std::string& filepath);
213 
214  /**
215  * @brief Registers a Fast-DDS @c TopicDataType factory for a topic name.
216  *
217  * @details
218  * Required for any CDR-serialised (non-Protobuf) message type. Call once per
219  * topic before any endpoint is opened on it. When @c TypeSupportRespT is not
220  * @c void, the response type is also registered under the topic name with a
221  * trailing @c "___resp" suffix.
222  *
223  * @tparam TypeSupportT Fast-DDS @c TopicDataType subclass for the request/message type.
224  * @tparam TypeSupportRespT Fast-DDS @c TopicDataType subclass for the response type; @c void to skip.
225  * @param name DDS topic name the factory is bound to.
226  */
227  template <typename TypeSupportT, typename TypeSupportRespT = void>
228  static void register_topic(const std::string& name);
229 
230  /**
231  * @brief Convenience wrapper that derives the topic name from a @c dds:// URL.
232  *
233  * @details
234  * Parses @p name with @c UrlParser, extracts the topic part, and forwards to
235  * @c register_topic<TypeSupportT, TypeSupportRespT>().
236  *
237  * @tparam TypeSupportT Fast-DDS @c TopicDataType subclass for the request type.
238  * @tparam TypeSupportRespT Fast-DDS @c TopicDataType subclass for the response type; @c void to skip.
239  * @param name Full URL string, for example @c "dds://my_topic?domain=1".
240  */
241  template <typename TypeSupportT, typename TypeSupportRespT = void>
242  static void register_url(const std::string& name);
243 
244  /**
245  * @brief Registers a named QoS profile that endpoints may reference via @c ?qos=.
246  *
247  * @details
248  * Profile names share a global namespace. Collisions with DDS-reserved tokens
249  * (@c part, @c topic, @c pub, @c sub, @c writer, @c reader, @c depth) or with an
250  * already registered profile abort with a fatal log entry.
251  *
252  * @param name Unique profile key; must not collide with any reserved token.
253  * @param qos @c Qos value associated with the key.
254  */
255  static void register_qos(const std::string& name, const Qos& qos);
256 
257  private:
258  template <typename TypeSupportT>
259  static void register_topic_internal(const std::string& name);
260 
261  static void register_qos_internal(const std::string& name, const Qos& qos);
262 
263  static Function<void*()> find_type_support(const std::string& name);
264 
265  static const Qos& find_qos(const std::string& name);
266 
267  static std::string get_topic_for_url(const std::string& url);
268 
269  friend class DdsFactory;
270  static std::map<std::string, Function<void*()>> type_support_map_;
271  static std::map<std::string, Qos> qos_map_;
272  static std::shared_mutex mtx_;
273  static constexpr const char* kRespSuffix{"___resp"};
274 #ifndef VLINK_ENABLE_C_INTERFACE
276 #endif
278  VLINK_CONF_IMPL(DdsConf)
279 };
280 
281 ////////////////////////////////////////////////////////////////
282 /// Details
283 ////////////////////////////////////////////////////////////////
284 
285 inline DdsConf::DdsConf(const std::string& _topic, int32_t _domain, int32_t _depth, const std::string& _qos)
286  : topic(_topic), domain(_domain), depth(_depth), qos(_qos) {}
287 
288 inline DdsConf::DdsConf(const std::string& _topic, int32_t _domain, const PropertiesMap& _qos_ext)
289  : topic(_topic), domain(_domain), qos_ext(_qos_ext) {}
290 
291 inline bool DdsConf::operator==(const DdsConf& conf) const noexcept {
292  return topic == conf.topic && domain == conf.domain && depth == conf.depth && qos == conf.qos &&
293  qos_ext == conf.qos_ext;
294 }
295 
296 inline bool DdsConf::operator!=(const DdsConf& conf) const noexcept { return !(*this == conf); }
297 
298 inline TransportType DdsConf::get_transport_type() const { return TransportType::kDds; }
299 
300 template <typename TypeSupportT, typename TypeSupportRespT>
301 inline void DdsConf::register_topic(const std::string& name) {
302  std::lock_guard lock(mtx_);
303  register_topic_internal<TypeSupportT>(name);
304  if constexpr (!std::is_same_v<TypeSupportRespT, void>) {
305  register_topic_internal<TypeSupportRespT>(name + kRespSuffix);
306  }
307 }
308 
309 template <typename TypeSupportT, typename TypeSupportRespT>
310 inline void DdsConf::register_url(const std::string& name) {
311  register_topic<TypeSupportT, TypeSupportRespT>(get_topic_for_url(name));
312 }
313 
314 template <typename TypeSupportT>
315 inline void DdsConf::register_topic_internal(const std::string& name) {
316  static_assert(std::is_base_of_v<eprosima::fastdds::dds::TopicDataType, TypeSupportT>, "Must be dds type.");
317 
318  type_support_map_[name] = [] { return new TypeSupportT(); };
319 }
320 
321 } // namespace vlink
322 
323 #endif
#define VLINK_CONF_IMPL(classname)
Convenience macro that emits the standard concrete conf boilerplate.
Definition: conf.h:251
#define VLINK_DECLARE_GLOBAL_PROPERTY()
Declares per-transport static configuration storage and access helpers.
Definition: conf.h:309
#define VLINK_ALLOW_IMPL_TYPE(type)
Records the bitmask of ImplType values supported by a conf.
Definition: conf.h:292
#define VLINK_EXPORT
Definition: macros.h:81