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/node_impl.h" 25 : : 26 : : #include <atomic> 27 : : #include <charconv> 28 : : #include <memory> 29 : : #include <mutex> 30 : : #include <shared_mutex> 31 : : #include <utility> 32 : : 33 : : #include "./base/bytes.h" 34 : : #include "./base/logger.h" 35 : : #include "./base/message_loop.h" 36 : : #include "./extension/bag_writer.h" 37 : : #include "./extension/discovery_reporter.h" 38 : : #include "./impl/client_impl.h" 39 : : #include "./impl/server_impl.h" 40 : : #include "./private/license_check.h" 41 : : #include "./version.h" 42 : : 43 : : #ifdef VLINK_ENABLE_SECURITY 44 : : #include <openssl/crypto.h> 45 : : #endif 46 : : 47 : : namespace vlink { 48 : : 49 : : static constexpr bool kIgnoreIntraUrl{false}; 50 : : 51 : : // GlobalDiscoveryReporter 52 : : struct GlobalDiscoveryReporter final { 53 : : public: 54 : 2418 : static DiscoveryReporter* get(bool create_if_missing = true) { 55 [ + + + - ]: 2418 : static GlobalDiscoveryReporter global; 56 : 2418 : return global.get_instance(create_if_missing); 57 : : } 58 : : 59 : : private: 60 : : GlobalDiscoveryReporter() = default; 61 : : 62 : 2417 : DiscoveryReporter* get_instance(bool create_if_missing) { 63 [ + - ]: 2417 : std::lock_guard lock(mtx_); 64 : : 65 [ + - ]: 2418 : check_disable_env(); 66 : : 67 [ + + + - : 2418 : if (!create_if_missing || disabled_ || instance_) { + + + + ] 68 : 2379 : return instance_.get(); 69 : : } 70 : : 71 [ + - ]: 39 : instance_ = std::make_unique<DiscoveryReporter>(); 72 [ + - ]: 39 : instance_->async_run(); 73 : : 74 : 39 : return instance_.get(); 75 : 2418 : } 76 : : 77 : 2418 : void check_disable_env() { 78 [ + + ]: 2418 : if (disable_checked_) { 79 : 2364 : return; 80 : : } 81 : : 82 : 54 : disable_checked_ = true; 83 : : 84 [ + - + - : 54 : static std::string discovery = Utils::get_env("VLINK_DISCOVER_DISABLE"); + - + - - - ] 85 : : 86 [ - + ]: 54 : if (!discovery.empty()) { 87 : 0 : int value = 0; 88 [ # # ]: 0 : auto [p, error] = std::from_chars(discovery.data(), discovery.data() + discovery.size(), value); 89 : : 90 [ # # # # ]: 0 : if (error == std::errc() && value == 1) { 91 [ # # # # : 0 : VLOG_I("DiscoveryReporter: Global discovery reporter is disabled."); # # ] 92 : 0 : disabled_ = true; 93 : 0 : return; 94 : : } 95 : : } 96 : : } 97 : : 98 : 54 : ~GlobalDiscoveryReporter() = default; 99 : : 100 : : std::mutex mtx_; 101 : : bool disable_checked_{false}; 102 : : bool disabled_{false}; 103 : : std::unique_ptr<DiscoveryReporter> instance_; 104 : : 105 : : VLINK_DISALLOW_COPY_AND_ASSIGN(GlobalDiscoveryReporter) 106 : : }; 107 : : 108 : 2320 : static bool should_report_discovery(const NodeImpl& node) { 109 [ + + + + : 2320 : return node.is_discovery_enabled && !node.url.empty() && !node.is_security_type && + + ] 110 : 2320 : (!kIgnoreIntraUrl || node.transport_type != TransportType::kIntra); 111 : : } 112 : : 113 : : // AbstractNode 114 [ + - ]: 1 : std::any AbstractNode::get_native_handle() const { return nullptr; } 115 : : 116 : 960 : AbstractNode::AbstractNode() = default; 117 : : 118 : 960 : AbstractNode::~AbstractNode() = default; 119 : : 120 : : // NodeImpl 121 : : struct NodeImplHelper final { 122 : : std::atomic_bool is_interrupted{false}; 123 : : 124 : : Conf::PropertiesMap property_map; 125 : : std::shared_mutex mtx; 126 : : std::shared_mutex status_mtx; 127 : : std::mutex post_mtx; 128 : : NodeImpl::StatusCallback status_callback; 129 : : std::atomic<MessageLoop*> message_loop{nullptr}; 130 : : 131 : : std::shared_ptr<BagWriter> data_recorder; 132 : : std::atomic_bool data_recorder_enabled{false}; 133 : : }; 134 : : 135 : 1 : bool NodeImpl::suspend() { 136 : : // has_suspend = true; 137 : : 138 [ + - + - : 2 : VLOG_W("Function [suspend] is not supported."); + - ] 139 : : 140 : 1 : return false; 141 : : } 142 : : 143 : 1 : bool NodeImpl::resume() { 144 : : // has_suspend = false; 145 : : 146 [ + - + - : 2 : VLOG_W("Function [resume] is not supported."); + - ] 147 : : 148 : 1 : return false; 149 : : } 150 : : 151 : 1 : bool NodeImpl::is_suspend() const { 152 [ + - + - : 2 : VLOG_W("Function [is_suspend] is not supported."); + - ] 153 : : 154 : 1 : return false; 155 : : } 156 : : 157 : 1166 : void NodeImpl::interrupt() { helper_->is_interrupted.store(true, std::memory_order_release); } 158 : : 159 : 955 : bool NodeImpl::is_support_loan() const { return false; } 160 : : 161 : 2 : Bytes NodeImpl::loan(int64_t size) { 162 : : (void)size; 163 : : 164 : 2 : return Bytes(); 165 : : } 166 : : 167 : 7 : bool NodeImpl::return_loan(const Bytes& bytes) { 168 : : (void)bytes; 169 : : 170 : 7 : return false; 171 : : } 172 : : 173 : 2 : const struct Conf* NodeImpl::get_conf() const { return nullptr; } 174 : : 175 : 1 : const AbstractNode* NodeImpl::get_abstract_node() const { return nullptr; } 176 : : 177 : 1 : Status::BasePtr NodeImpl::get_status(Status::Type type) const { 178 : : (void)type; 179 : : 180 [ + - + - : 2 : VLOG_W("Function [get_status] is not supported."); + - ] 181 : : 182 : 1 : return std::make_shared<Status::Unknown>(); 183 : : } 184 : : 185 : 1152 : bool NodeImpl::check_version(const Version& version) { 186 : 1152 : Version runtime_version{VLINK_VERSION_MAJOR, VLINK_VERSION_MINOR, VLINK_VERSION_PATCH}; 187 : : 188 [ + + ]: 1152 : if VUNLIKELY (version != runtime_version) { 189 : : static std::atomic_bool print_warn{false}; 190 : : 191 [ + + ]: 2 : if VUNLIKELY (!print_warn.exchange(true, std::memory_order_relaxed)) { 192 [ + - + - : 2 : VLOG_W("The version may be incompatible. [Compiled]: ", version.to_string(), + - + - + - - - - - ] 193 : : " [Runtime]: ", runtime_version.to_string(), "."); 194 : : } 195 : : 196 : 2 : return false; 197 : : } 198 : : 199 : 1150 : return true; 200 : : } 201 : : 202 : 61 : bool NodeImpl::attach(class MessageLoop* message_loop) { 203 [ + + ]: 61 : if VUNLIKELY (!message_loop) { 204 : 6 : return false; 205 : : } 206 : : 207 : 55 : MessageLoop* expected = nullptr; 208 : 55 : return helper_->message_loop.compare_exchange_strong(expected, message_loop, std::memory_order_release, 209 : 55 : std::memory_order_relaxed); 210 : : } 211 : : 212 : 772 : bool NodeImpl::detach() { 213 : 772 : MessageLoop* message_loop = nullptr; 214 : : 215 : : { 216 [ + - ]: 772 : std::lock_guard lock(helper_->post_mtx); 217 : : 218 : 772 : message_loop = helper_->message_loop.exchange(nullptr, std::memory_order_acq_rel); 219 : 772 : } 220 : : 221 [ + + ]: 772 : if (!message_loop) { 222 : 721 : return false; 223 : : } 224 : : 225 [ + - ]: 51 : if (!message_loop->is_in_same_thread()) { 226 : 51 : message_loop->wait_for_idle(); 227 : : } 228 : : 229 : 51 : return true; 230 : : } 231 : : 232 : 9723 : class MessageLoop* NodeImpl::get_message_loop() const { return helper_->message_loop.load(std::memory_order_acquire); } 233 : : 234 : 29 : void NodeImpl::register_status_handler(StatusCallback&& callback) { 235 [ + + + + : 29 : if VUNLIKELY (transport_type != TransportType::kDds && transport_type != TransportType::kDdsc && + + + + + + ] 236 : : transport_type != TransportType::kDdsr) { 237 [ + - + - : 10 : VLOG_W("Function [register_status_handler] is not supported."); + - ] 238 : 5 : return; 239 : : } 240 : : 241 [ + - ]: 24 : std::lock_guard lock(helper_->status_mtx); 242 : 24 : helper_->status_callback = std::move(callback); 243 : 24 : } 244 : : 245 : 964 : bool NodeImpl::has_register_status() const { 246 [ + + + + : 964 : if VUNLIKELY (transport_type != TransportType::kDds && transport_type != TransportType::kDdsc && + + + + + + ] 247 : : transport_type != TransportType::kDdsr) { 248 [ + - + - : 12 : VLOG_W("Function [has_register_status] is not supported."); + - ] 249 : 6 : return false; 250 : : } 251 : : 252 [ + - ]: 958 : std::shared_lock lock(helper_->status_mtx); 253 : : 254 : 958 : return helper_->status_callback != nullptr; 255 : 958 : } 256 : : 257 : 29 : void NodeImpl::call_status(Status::BasePtr ptr) { 258 [ + + - + : 29 : if VUNLIKELY (transport_type != TransportType::kDds && transport_type != TransportType::kDdsc && - + - - - + ] 259 : : transport_type != TransportType::kDdsr) { 260 [ # # # # : 0 : VLOG_W("Function [call_status] is not supported."); # # ] 261 : 1 : return; 262 : : } 263 : : 264 : : { 265 [ + - ]: 29 : std::lock_guard post_lock(helper_->post_mtx); 266 : : 267 : 29 : auto* message_loop = helper_->message_loop.load(std::memory_order_acquire); 268 : : 269 [ + + ]: 29 : if VLIKELY (message_loop) { 270 [ + - + - ]: 1 : message_loop->post_task([this, ptr]() mutable { 271 [ + - ]: 1 : std::shared_lock lock(helper_->status_mtx); 272 [ + - ]: 1 : if VLIKELY (helper_->status_callback) { 273 [ + - ]: 1 : helper_->status_callback(std::move(ptr)); 274 : : } 275 : 1 : }); 276 : 1 : return; 277 : : } 278 [ + + ]: 29 : } 279 : : 280 [ + - ]: 28 : std::shared_lock lock(helper_->status_mtx); 281 : : 282 [ + - ]: 28 : if VLIKELY (helper_->status_callback) { 283 [ + - ]: 28 : helper_->status_callback(std::move(ptr)); 284 : : } 285 : 28 : } 286 : : 287 : 347 : void NodeImpl::set_property(const std::string& prop, const std::string& value) { 288 [ + - ]: 347 : std::lock_guard lock(helper_->mtx); 289 [ + - + - ]: 347 : helper_->property_map[prop] = value; 290 : 347 : } 291 : : 292 : 346 : std::string NodeImpl::get_property(const std::string& prop) const { 293 [ + - ]: 346 : std::shared_lock lock(helper_->mtx); 294 : : 295 [ + - ]: 346 : auto iter = helper_->property_map.find(prop); 296 : : 297 [ + + ]: 346 : if VLIKELY (iter != helper_->property_map.end()) { 298 [ + - ]: 237 : return iter->second; 299 : : } 300 : : 301 : 109 : return {}; 302 : 346 : } 303 : : 304 : 476 : Conf::PropertiesMap NodeImpl::get_all_properties() const { 305 [ + - ]: 476 : std::shared_lock lock(helper_->mtx); 306 [ + - ]: 952 : return helper_->property_map; 307 : 476 : } 308 : : 309 : 34 : void NodeImpl::set_discovery_enabled(bool enable) { is_discovery_enabled = enable; } 310 : : 311 : 5 : bool NodeImpl::get_discovery_enabled() const { return is_discovery_enabled; } 312 : : 313 : 1 : void NodeImpl::set_record_path(const std::string& path) { 314 : 1 : std::shared_ptr<BagWriter> new_recorder; 315 : : 316 [ - + ]: 1 : if (!path.empty()) { 317 [ # # ]: 0 : new_recorder = BagWriter::filter_get(path); 318 : : } 319 : : 320 : 1 : std::shared_ptr<BagWriter> old_recorder; 321 : : 322 : : { 323 [ + - ]: 1 : std::lock_guard lock(helper_->mtx); 324 : 1 : old_recorder = std::move(helper_->data_recorder); 325 : 1 : helper_->data_recorder = std::move(new_recorder); 326 : 1 : helper_->data_recorder_enabled.store(helper_->data_recorder != nullptr, std::memory_order_release); 327 : 1 : } 328 : : 329 : 1 : old_recorder.reset(); 330 : 1 : } 331 : : 332 : 0 : bool NodeImpl::enable_security(const Security::Config& cfg) { 333 [ # # ]: 0 : auto sec_cfg = cfg; 334 : : 335 [ # # ]: 0 : return enable_security(std::move(sec_cfg)); 336 : 0 : } 337 : : 338 : 49 : bool NodeImpl::enable_security(Security::Config&& cfg) { 339 [ + + + + : 49 : if VUNLIKELY (transport_type == TransportType::kIntra || (transport_type == TransportType::kDds && is_cdr_type)) { - + - + + + ] 340 [ + - + - : 6 : VLOG_W("Security::Config will ignore intra/dds(cdr) transport."); + - ] 341 : 3 : return false; 342 : : } 343 : : 344 [ + - ]: 46 : if VLIKELY (cfg.advanced.aad_context.empty()) { 345 [ + - ]: 46 : cfg.advanced.aad_context = url; 346 [ + - ]: 46 : cfg.advanced.aad_context += "|"; 347 [ + - ]: 46 : cfg.advanced.aad_context += ser_type; 348 [ + - ]: 46 : cfg.advanced.aad_context += "|"; 349 [ + - + - ]: 46 : cfg.advanced.aad_context += std::to_string(static_cast<uint32_t>(schema_type)); 350 : : } 351 : : 352 [ + - ]: 46 : auto candidate = std::make_unique<Security>(std::move(cfg)); 353 : : 354 [ - + ]: 46 : if VUNLIKELY (!candidate->is_configured()) { 355 : : VLOG_W("Security::Config has no usable slot."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 356 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 357 : : } 358 : : 359 [ + + + + ]: 46 : bool needs_encrypt = (impl_type == kPublisher || impl_type == kSetter); 360 [ + + + + ]: 46 : bool needs_decrypt = (impl_type == kSubscriber || impl_type == kGetter); 361 : : 362 : : #if defined(NDEBUG) || defined(__ANDROID__) 363 : : if (impl_type == kClient) { 364 : : needs_encrypt = true; 365 : : const auto* client_impl = static_cast<const ClientImpl*>(this); 366 : : needs_decrypt = client_impl != nullptr && client_impl->is_resp_type; 367 : : } else if (impl_type == kServer) { 368 : : needs_decrypt = true; 369 : : const auto* server_impl = static_cast<const ServerImpl*>(this); 370 : : needs_encrypt = server_impl != nullptr && server_impl->is_resp_type; 371 : : } 372 : : #else 373 [ + + ]: 46 : if (impl_type == kClient) { 374 : 7 : needs_encrypt = true; 375 [ + - ]: 7 : const auto* client_impl = dynamic_cast<const ClientImpl*>(this); 376 [ + - + - ]: 7 : needs_decrypt = client_impl != nullptr && client_impl->is_resp_type; 377 [ + + ]: 39 : } else if (impl_type == kServer) { 378 : 8 : needs_decrypt = true; 379 [ + - ]: 8 : const auto* server_impl = dynamic_cast<const ServerImpl*>(this); 380 [ + - + - ]: 8 : needs_encrypt = server_impl != nullptr && server_impl->is_resp_type; 381 : : } 382 : : #endif 383 : : 384 [ + + - + : 46 : if VUNLIKELY (needs_encrypt && !candidate->can_encrypt()) { - + ] 385 : : VLOG_W("Security::Config cannot encrypt for this sender role."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 386 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 387 : : } 388 : : 389 [ + + - + : 46 : if VUNLIKELY (needs_decrypt && !candidate->can_decrypt()) { - + ] 390 : : VLOG_W("Security::Config cannot decrypt for this receiver role."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 391 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 392 : : } 393 : : 394 : 46 : security = std::move(candidate); 395 : : 396 : 46 : return true; 397 : 46 : } 398 : : 399 : 1 : void NodeImpl::set_ssl_options(const SslOptions& options) { 400 [ + - ]: 1 : std::lock_guard lock(helper_->mtx); 401 : 1 : options.parse_to(helper_->property_map); 402 : 1 : } 403 : : 404 : 11018 : void NodeImpl::try_record(ActionType action_type, const Bytes& data) { 405 [ + - ]: 11018 : auto* global_recorder = BagWriter::global_get(); 406 : 11011 : const bool data_recorder_enabled = helper_->data_recorder_enabled.load(std::memory_order_acquire); 407 : : 408 [ + + + # : 11013 : if VLIKELY (!global_recorder && !data_recorder_enabled) { + # ] 409 : 11015 : return; 410 : : } 411 : : 412 : 0 : std::shared_ptr<BagWriter> data_recorder; 413 : : 414 [ - # ]: 0 : if (data_recorder_enabled) { 415 [ # # ]: 0 : std::shared_lock lock(helper_->mtx); 416 : 0 : data_recorder = helper_->data_recorder; 417 : 0 : } 418 : : 419 [ # # # # : 0 : if VLIKELY (!global_recorder && !data_recorder) { # # ] 420 : 0 : return; 421 : : } 422 : : 423 : : if constexpr (kIgnoreIntraUrl) { 424 : : if (transport_type == TransportType::kIntra) { 425 : : return; 426 : : } 427 : : } 428 : : 429 : 0 : Frame frame; 430 : 0 : frame.timestamp = -1; 431 [ # # ]: 0 : frame.url = url; 432 [ # # ]: 0 : frame.ser_type = ser_type; 433 : 0 : frame.schema_type = schema_type; 434 : : 435 [ # # # # ]: 0 : if (action_type == ActionType::kPublish && init_impl_type == kSetter) { 436 : 0 : frame.action_type = ActionType::kSet; 437 [ # # # # ]: 0 : } else if (action_type == ActionType::kSubscribe && init_impl_type == kGetter) { 438 : 0 : frame.action_type = ActionType::kGet; 439 : : } else { 440 : 0 : frame.action_type = action_type; 441 : : } 442 : : 443 : 0 : frame.data = Bytes::shallow_copy(data.data(), data.size()); 444 : : 445 [ # # ]: 0 : if VUNLIKELY (global_recorder) { 446 [ # # ]: 0 : global_recorder->push(frame); 447 : : } 448 : : 449 [ # # ]: 0 : if VUNLIKELY (data_recorder) { 450 [ # # ]: 0 : data_recorder->push(frame); 451 : : } 452 [ # # ]: 0 : } 453 : : 454 : 174 : void NodeImpl::reset_interrupted() { helper_->is_interrupted.store(false, std::memory_order_release); } 455 : : 456 : 337 : bool NodeImpl::is_interrupted() const { return helper_->is_interrupted.load(std::memory_order_acquire); } 457 : : 458 : 1160 : void NodeImpl::init_ext() { 459 [ + + ]: 1160 : if (should_report_discovery(*this)) { 460 : 507 : auto* global_reporter = GlobalDiscoveryReporter::get(); 461 : : 462 [ + - ]: 507 : if (global_reporter) { 463 [ - + - - : 507 : if (CpuProfiler::is_global_enabled() && !profiler) { - + ] 464 : : profiler = std::make_unique<CpuProfiler>(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 465 : : } 466 : : 467 : 507 : global_reporter->add(this); 468 : : } 469 : : } 470 : : 471 : : #ifdef VLINK_ENABLE_CHECK_LICENSE 472 : : [[maybe_unused]] static LicenseCheck license; 473 : : #endif 474 : 1160 : } 475 : : 476 : 1160 : void NodeImpl::deinit_ext() { 477 [ + + ]: 1160 : if (!should_report_discovery(*this)) { 478 : 653 : return; 479 : : } 480 : : 481 : 507 : auto* global_reporter = GlobalDiscoveryReporter::get(false); 482 : : 483 [ + - ]: 507 : if (global_reporter) { 484 : 507 : global_reporter->remove(this); 485 : : 486 [ - + - - : 507 : if (CpuProfiler::is_global_enabled() && profiler) { - + ] 487 : : profiler->restart(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 488 : : } 489 : : } 490 : : } 491 : : 492 : 1404 : void NodeImpl::global_init() { 493 : : #ifdef VLINK_ENABLE_SECURITY 494 : : [[maybe_unused]] static const int kOpenSslInitialized = 495 [ + + + - : 1404 : OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr); + - - - ] 496 : : #endif 497 : : 498 : 1404 : Logger::get(); 499 : : 500 : 1403 : Bytes::init_memory_pool(); 501 : : 502 : 1404 : BagWriter::global_get(); 503 : : 504 : 1404 : GlobalDiscoveryReporter::get(false); 505 : 1404 : } 506 : : 507 [ + - ]: 1401 : NodeImpl::NodeImpl(ImplType type) : impl_type(type), init_impl_type(type), helper_(std::make_unique<NodeImplHelper>()) { 508 [ + - ]: 1401 : global_init(); 509 : 1401 : } 510 : : 511 : 1401 : NodeImpl::~NodeImpl() = default; 512 : : 513 : : } // namespace vlink