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