Branch data Line data Source code
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 : : #include "./impl/url.h"
25 : :
26 : : #include <atomic>
27 : : #include <map>
28 : : #include <memory>
29 : : #include <mutex>
30 : : #include <shared_mutex>
31 : : #include <string>
32 : : #include <unordered_map>
33 : : #include <utility>
34 : :
35 : : #include "./base/helpers.h"
36 : : #include "./base/plugin.h"
37 : : #include "./base/utils.h"
38 : : #include "./extension/url_remap.h"
39 : : #include "./impl/conf_plugin_interface.h"
40 : : #include "./impl/url_parser.h"
41 : :
42 : : #define VLINK_URL_USE_REMAP 1
43 : : #define VLINK_URL_USE_PLUGIN 1
44 : :
45 : : namespace vlink {
46 : :
47 : : [[maybe_unused]] static constexpr uint16_t kMaxUrlLength = 120U;
48 : :
49 : 572 : [[maybe_unused]] inline static TransportType get_transport_for_str(const std::string& str) noexcept {
50 [ + + ]: 572 : if (str == "intra") {
51 : 213 : return TransportType::kIntra;
52 : : }
53 : :
54 : : #if !defined(__ANDROID__)
55 : :
56 [ + + ]: 359 : if (str == "shm") {
57 : 96 : return TransportType::kShm;
58 : : }
59 : :
60 [ + + ]: 263 : if (str == "shm2") {
61 : 1 : return TransportType::kShm2;
62 : : }
63 : : #endif
64 : :
65 [ + + ]: 262 : if (str == "zenoh") {
66 : 1 : return TransportType::kZenoh;
67 : : }
68 : :
69 [ + + - + : 261 : if (str == "dds" || str == "ddsf") {
+ + ]
70 : 136 : return TransportType::kDds;
71 : : }
72 : :
73 [ + + ]: 125 : if (str == "ddsc") {
74 : 91 : return TransportType::kDdsc;
75 : : }
76 : :
77 [ + + ]: 34 : if (str == "ddsr") {
78 : 1 : return TransportType::kDdsr;
79 : : }
80 : :
81 [ + + ]: 33 : if (str == "ddst") {
82 : 1 : return TransportType::kDdst;
83 : : }
84 : :
85 [ + + ]: 32 : if (str == "someip") {
86 : 1 : return TransportType::kSomeip;
87 : : }
88 : :
89 [ + + ]: 31 : if (str == "mqtt") {
90 : 1 : return TransportType::kMqtt;
91 : : }
92 : :
93 [ + + ]: 30 : if (str == "fdbus") {
94 : 1 : return TransportType::kFdbus;
95 : : }
96 : :
97 : : #if defined(__QNX__)
98 : :
99 : : if (str == "qnx") {
100 : : return TransportType::kQnx;
101 : : }
102 : : #endif
103 : :
104 : 29 : return TransportType::kUnknown;
105 : : }
106 : :
107 : 1 : [[maybe_unused]] inline static TransportType get_dds_transport_for_str(const std::string& str) noexcept {
108 [ + - - + : 1 : if (str == "dds" || str == "ddsf") {
- + ]
109 : 0 : return TransportType::kDds;
110 : : }
111 : :
112 [ + - ]: 1 : if (str == "ddsc") {
113 : 1 : return TransportType::kDdsc;
114 : : }
115 : :
116 [ # # ]: 0 : if (str == "ddsr") {
117 : 0 : return TransportType::kDdsr;
118 : : }
119 : :
120 [ # # ]: 0 : if (str == "ddst") {
121 : 0 : return TransportType::kDdst;
122 : : }
123 : :
124 : 0 : return TransportType::kUnknown;
125 : : }
126 : :
127 : 571 : [[maybe_unused]] inline static bool is_dds_type(const TransportType& transport) noexcept {
128 [ + + + + : 915 : return transport == TransportType::kDds || transport == TransportType::kDdsc || transport == TransportType::kDdsr ||
+ + ]
129 [ + + ]: 915 : transport == TransportType::kDdst;
130 : : }
131 : :
132 : 3 : [[maybe_unused]] inline static Url::TransportEnableFlag get_transport_enable_for_str(const std::string& str) noexcept {
133 [ + + ]: 3 : if (str == "intra") {
134 : 1 : return Url::kEnableIntra;
135 : : }
136 : :
137 : : #if !defined(__ANDROID__)
138 : :
139 [ - + ]: 2 : if (str == "shm") {
140 : 0 : return Url::kEnableShm;
141 : : }
142 : :
143 [ - + ]: 2 : if (str == "shm2") {
144 : 0 : return Url::kEnableShm2;
145 : : }
146 : : #endif
147 : :
148 [ - + ]: 2 : if (str == "zenoh") {
149 : 0 : return Url::kEnableZenoh;
150 : : }
151 : :
152 [ + - - + : 2 : if (str == "dds" || str == "ddsf") {
- + ]
153 : 0 : return Url::kEnableDds;
154 : : }
155 : :
156 [ + + ]: 2 : if (str == "ddsc") {
157 : 1 : return Url::kEnableDdsc;
158 : : }
159 : :
160 [ - + ]: 1 : if (str == "ddsr") {
161 : 0 : return Url::kEnableDdsr;
162 : : }
163 : :
164 [ - + ]: 1 : if (str == "ddst") {
165 : 0 : return Url::kEnableDdst;
166 : : }
167 : :
168 [ - + ]: 1 : if (str == "someip") {
169 : 0 : return Url::kEnableSomeip;
170 : : }
171 : :
172 [ - + ]: 1 : if (str == "mqtt") {
173 : 0 : return Url::kEnableMqtt;
174 : : }
175 : :
176 [ - + ]: 1 : if (str == "fdbus") {
177 : 0 : return Url::kEnableFdbus;
178 : : }
179 : :
180 : : #if defined(__QNX__)
181 : :
182 : : if (str == "qnx") {
183 : : return Url::kEnableQnx;
184 : : }
185 : : #endif
186 : :
187 : 1 : return Url::kEnableEmpty;
188 : : }
189 : :
190 : 15 : [[maybe_unused]] inline static bool is_intra_url(std::string_view url) noexcept {
191 : 15 : return Helpers::has_startwith(url, "intra://");
192 : : }
193 : :
194 : 15 : [[maybe_unused]] inline static bool is_shm_url(std::string_view url) noexcept {
195 [ + + + + ]: 15 : return Helpers::has_startwith(url, "shm://") || Helpers::has_startwith(url, "shm2://");
196 : : }
197 : :
198 : 539 : [[maybe_unused]] inline static int get_sort_index_for_url(std::string_view url) {
199 [ + + ]: 539 : if (url.empty()) {
200 : 3 : return -1;
201 : : }
202 : :
203 [ + + ]: 536 : if (Helpers::has_startwith(url, "intra://")) {
204 : 30 : return static_cast<int>(TransportType::kIntra);
205 : : }
206 : :
207 : : #if !defined(__ANDROID__)
208 : :
209 [ + + ]: 506 : if (Helpers::has_startwith(url, "shm://")) {
210 : 42 : return static_cast<int>(TransportType::kShm);
211 : : }
212 : :
213 [ + + ]: 464 : if (Helpers::has_startwith(url, "shm2://")) {
214 : 2 : return static_cast<int>(TransportType::kShm2);
215 : : }
216 : : #endif
217 : :
218 [ + + ]: 462 : if (Helpers::has_startwith(url, "zenoh://")) {
219 : 3 : return static_cast<int>(TransportType::kZenoh);
220 : : }
221 : :
222 [ + + + + : 459 : if (Helpers::has_startwith(url, "dds://") || Helpers::has_startwith(url, "ddsf://")) {
+ + ]
223 : 423 : return static_cast<int>(TransportType::kDds);
224 : : }
225 : :
226 [ + + ]: 36 : if (Helpers::has_startwith(url, "ddsc://")) {
227 : 27 : return static_cast<int>(TransportType::kDdsc);
228 : : }
229 : :
230 [ + + ]: 9 : if (Helpers::has_startwith(url, "ddsr://")) {
231 : 1 : return static_cast<int>(TransportType::kDdsr);
232 : : }
233 : :
234 [ + + ]: 8 : if (Helpers::has_startwith(url, "ddst://")) {
235 : 1 : return static_cast<int>(TransportType::kDdst);
236 : : }
237 : :
238 [ + + ]: 7 : if (Helpers::has_startwith(url, "someip://")) {
239 : 2 : return static_cast<int>(TransportType::kSomeip);
240 : : }
241 : :
242 [ + + ]: 5 : if (Helpers::has_startwith(url, "mqtt://")) {
243 : 1 : return static_cast<int>(TransportType::kMqtt);
244 : : }
245 : :
246 [ + + ]: 4 : if (Helpers::has_startwith(url, "fdbus://")) {
247 : 2 : return static_cast<int>(TransportType::kFdbus);
248 : : }
249 : :
250 : : #if defined(__QNX__)
251 : :
252 : : if (Helpers::has_startwith(url, "qnx://")) {
253 : : return static_cast<int>(TransportType::kQnx);
254 : : }
255 : : #endif
256 : :
257 : 2 : return 0;
258 : : }
259 : :
260 : : // GlobalModulesManager
261 : : class GlobalModulesManager final {
262 : : public:
263 : 58 : static GlobalModulesManager& get() {
264 [ + + + - : 58 : static GlobalModulesManager manager;
+ - - - ]
265 : 58 : return manager;
266 : : }
267 : :
268 : 7 : std::shared_ptr<ConfPluginInterface> get_interface(TransportType type) const {
269 [ + - ]: 7 : std::shared_lock lock(mtx_);
270 : :
271 [ + - ]: 7 : auto iter = ptr_map_.find(type);
272 : :
273 [ + - ]: 7 : if VUNLIKELY (iter == ptr_map_.end()) {
274 : 7 : return nullptr;
275 : : }
276 : :
277 : : return iter->second; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
278 : 7 : }
279 : :
280 : 51 : void init(uint16_t transport_enable_flags) {
281 : 51 : transport_enable_flags_ = transport_enable_flags;
282 : :
283 [ + - + - ]: 51 : const auto& url_plugins_env = Utils::get_env("VLINK_URL_PLUGINS", "");
284 : :
285 [ + + ]: 51 : if (url_plugins_env.empty()) {
286 : 50 : return;
287 : : }
288 : :
289 : 1 : auto plugins_list = Helpers::split_any(url_plugins_env);
290 : :
291 [ + - ]: 1 : std::lock_guard lock(mtx_);
292 : :
293 : : // plugin_.set_log_level(Logger::kWarn);
294 : :
295 [ + - + + ]: 4 : for (auto libname : plugins_list) {
296 [ + - + - ]: 3 : Helpers::replace_string(libname, "vlink-", "");
297 : :
298 : 3 : auto transport_enable = get_transport_enable_for_str(libname);
299 : :
300 [ + + ]: 3 : if VUNLIKELY (transport_enable == Url::TransportEnableFlag::kEnableEmpty) {
301 [ + - + - ]: 2 : VLOG_E("Unsupported plugin module, libname: ", libname, ".");
302 : 1 : continue;
303 : 1 : }
304 : :
305 [ + + ]: 2 : if (transport_enable_flags_ & transport_enable) {
306 [ + - + - ]: 2 : VLOG_T("Ignore linked modules, libname: ", libname, ".");
307 : 1 : continue;
308 : 1 : }
309 : :
310 [ + - + - : 2 : auto ptr = plugin_.load<ConfPluginInterface>("vlink-" + libname, 1, 0);
+ - + - +
- ]
311 : :
312 [ + - ]: 1 : if VLIKELY (ptr) {
313 [ + - + - ]: 1 : ptr_map_.emplace(ptr->get_transport_type(), std::move(ptr));
314 : : }
315 [ + + ]: 3 : }
316 [ + + ]: 51 : }
317 : :
318 : : private:
319 : 51 : GlobalModulesManager() = default;
320 : :
321 : 51 : ~GlobalModulesManager() {
322 : 51 : std::lock_guard lock(mtx_);
323 : 51 : ptr_map_.clear();
324 : 51 : plugin_.clear();
325 : 51 : }
326 : :
327 : : uint16_t transport_enable_flags_{0};
328 : : Plugin plugin_;
329 : : std::unordered_map<TransportType, std::shared_ptr<ConfPluginInterface>> ptr_map_;
330 : : mutable std::shared_mutex mtx_;
331 : :
332 : : VLINK_DISALLOW_COPY_AND_ASSIGN(GlobalModulesManager)
333 : : };
334 : :
335 : : // GlobalUrlRemap
336 : : class GlobalUrlRemap final : public UrlRemap {
337 : : public:
338 : 574 : static GlobalUrlRemap& get() {
339 [ + + + - : 574 : static GlobalUrlRemap remap;
+ - - - ]
340 : 574 : return remap;
341 : : }
342 : :
343 : 574 : std::string convert_thread_safe(const std::string& url) noexcept {
344 : 574 : std::lock_guard lock(mtx_);
345 : 574 : return convert(url);
346 : 574 : }
347 : :
348 : : private:
349 : 50 : GlobalUrlRemap() {
350 [ + - + - ]: 50 : const auto& url_remap_env = Utils::get_env("VLINK_URL_REMAP", "");
351 : :
352 [ + - ]: 50 : if (url_remap_env.empty()) {
353 : 50 : return;
354 : : }
355 : :
356 : 0 : set_enable_log(true);
357 : :
358 : 0 : load(url_remap_env);
359 [ - + ]: 50 : }
360 : :
361 : 50 : ~GlobalUrlRemap() = default;
362 : :
363 : : std::mutex mtx_;
364 : :
365 : : VLINK_DISALLOW_COPY_AND_ASSIGN(GlobalUrlRemap)
366 : : };
367 : :
368 : : // Protocol
369 : 574 : Protocol::Protocol(const std::string& address) {
370 [ - + ]: 574 : if VUNLIKELY (address.length() > kMaxUrlLength) {
371 [ # # # # ]: 0 : CLOG_F("The URL length exceeds the character limit (Target length: %zu, Max length: %u).", address.length(),
372 : : kMaxUrlLength);
373 : 0 : return;
374 : : }
375 : :
376 : : #if VLINK_URL_USE_REMAP
377 [ + - ]: 574 : std::string real_address = GlobalUrlRemap::get().convert_thread_safe(address);
378 : :
379 [ + + ]: 574 : UrlParser parser(real_address);
380 : 571 : str = std::move(real_address);
381 : : #else
382 : : UrlParser parser(address);
383 : : str = address;
384 : : #endif
385 : :
386 [ + - ]: 571 : transport = get_transport_for_str(parser.get_transport());
387 : :
388 [ + + ]: 571 : if (is_dds_type(transport)) {
389 [ + - + - ]: 456 : const auto dds_bind_transport_str = Utils::get_env("VLINK_DDS_BIND", "");
390 : :
391 [ + + ]: 228 : if VUNLIKELY (!dds_bind_transport_str.empty()) {
392 : 1 : TransportType dds_bind_transport = get_dds_transport_for_str(dds_bind_transport_str);
393 : :
394 [ + - ]: 1 : if VLIKELY (dds_bind_transport != TransportType::kUnknown) {
395 : : static std::atomic_bool dds_bind_print{false};
396 : :
397 [ + - ]: 1 : if (!dds_bind_print.exchange(true, std::memory_order_relaxed)) {
398 [ + - + - ]: 2 : CLOG_I("Bind [dds] to [%s].", dds_bind_transport_str.c_str());
399 : : }
400 : :
401 : 1 : transport = dds_bind_transport;
402 : : }
403 : : }
404 [ + + ]: 571 : } else if (transport == TransportType::kIntra) {
405 [ + - + - ]: 426 : const auto intra_bind_transport_str = Utils::get_env("VLINK_INTRA_BIND", "");
406 : :
407 [ + + ]: 213 : if (!intra_bind_transport_str.empty()) {
408 : 1 : TransportType intra_bind_transport = get_transport_for_str(intra_bind_transport_str);
409 : :
410 [ + - + - : 1 : if VLIKELY (intra_bind_transport != TransportType::kIntra && intra_bind_transport != TransportType::kUnknown) {
+ - ]
411 : : static std::atomic_bool intra_bind_print{false};
412 : :
413 [ + - ]: 1 : if (!intra_bind_print.exchange(true, std::memory_order_relaxed)) {
414 [ + - + - ]: 2 : CLOG_I("Bind [intra] to [%s].", intra_bind_transport_str.c_str());
415 : : }
416 : :
417 : 1 : transport = intra_bind_transport;
418 : : }
419 : : }
420 : 213 : }
421 : :
422 [ + - ]: 571 : host = std::move(const_cast<std::string&>(parser.get_host()));
423 [ + - ]: 571 : path = std::move(const_cast<std::string&>(parser.get_path()));
424 [ + - ]: 571 : dictionary = std::move(const_cast<std::map<std::string, std::string>&>(parser.get_query_dictionary()));
425 [ + - ]: 571 : fragment = std::move(const_cast<std::string&>(parser.get_fragment()));
426 : 589 : }
427 : :
428 : : // Url
429 : 574 : void Url::init_plugins(uint16_t transport_enable_flags) {
430 : : #if VLINK_URL_USE_PLUGIN
431 [ + + + - : 574 : static auto& manager_instance = GlobalModulesManager::get();
+ - - - ]
432 : :
433 : : static std::once_flag flag;
434 : :
435 [ + - ]: 625 : std::call_once(flag, [transport_enable_flags]() { manager_instance.init(transport_enable_flags); });
436 : : #else
437 : : (void)transport_enable_flags;
438 : : #endif
439 : 574 : }
440 : :
441 : 38 : std::unique_ptr<Conf> Url::load_for_plugin(TransportType type) {
442 : : #if VLINK_URL_USE_PLUGIN
443 : :
444 [ + + ]: 38 : if VUNLIKELY (type == TransportType::kUnknown) {
445 : 31 : return nullptr;
446 : : }
447 : :
448 [ + - + - ]: 7 : auto ptr = GlobalModulesManager::get().get_interface(type);
449 : :
450 [ + - ]: 7 : if VUNLIKELY (!ptr) {
451 : 7 : return nullptr;
452 : : }
453 : :
454 : : return ptr->create(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
455 : : #else
456 : : (void)type;
457 : :
458 : : return nullptr;
459 : : #endif
460 : 7 : }
461 : :
462 : 539 : int Url::get_sort_index(std::string_view url) { return get_sort_index_for_url(url); }
463 : :
464 [ + + + + ]: 10 : bool Url::is_local_type(std::string_view url) { return is_intra_url(url) || is_shm_url(url); }
465 : :
466 : 5 : bool Url::is_intra_type(std::string_view url) { return is_intra_url(url); }
467 : :
468 : 6 : bool Url::is_shm_type(std::string_view url) { return is_shm_url(url); }
469 : :
470 : 2 : std::ostream& operator<<(std::ostream& ostream, const Url& conf) noexcept {
471 : : ostream << "Url:"
472 : 2 : << "[type]" << +conf.get_impl_type() << "[str]" << conf.protocol_.str;
473 : :
474 : 2 : return ostream;
475 : : }
476 : :
477 : : } // namespace vlink
|