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