LCOV - code coverage report
Current view: top level - src/extension - vcap_reader.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 913 917 99.6 %
Date: 2026-09-30 18:30:16 Functions: 83 85 97.6 %
Branches: 902 1462 61.7 %

           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 "./extension/vcap_reader.h"
      25                 :            : 
      26                 :            : #include <algorithm>
      27                 :            : #include <atomic>
      28                 :            : #include <cinttypes>
      29                 :            : #include <cmath>
      30                 :            : #include <filesystem>
      31                 :            : #include <fstream>
      32                 :            : #include <limits>
      33                 :            : #include <memory>
      34                 :            : #include <mutex>
      35                 :            : #include <shared_mutex>
      36                 :            : #include <string>
      37                 :            : #include <string_view>
      38                 :            : #include <unordered_map>
      39                 :            : #include <utility>
      40                 :            : #include <vector>
      41                 :            : 
      42                 :            : #include "./base/condition_variable.h"
      43                 :            : #include "./base/elapsed_timer.h"
      44                 :            : #include "./base/helpers.h"
      45                 :            : #include "./base/logger.h"
      46                 :            : #include "./version.h"
      47                 :            : 
      48                 :            : // json
      49                 :            : #include <nlohmann/json.hpp>
      50                 :            : 
      51                 :            : // mcap
      52                 :            : #include "./private/mcap_import.h"
      53                 :            : 
      54                 :            : namespace vlink {
      55                 :            : 
      56                 :            : [[maybe_unused]] static constexpr size_t kMaxTaskSize = 50000U;
      57                 :            : 
      58                 :        466 : static mcap::Timestamp get_read_end_time(int64_t start_timestamp_ns, int64_t end_time_ms) {
      59   [ +  +  +  -  :        498 :   if (end_time_ms <= 0 || start_timestamp_ns < 0 ||
                   +  + ]
      60         [ +  + ]:         32 :       end_time_ms > (std::numeric_limits<int64_t>::max() - start_timestamp_ns - 1000) / 1000'000) {
      61                 :        436 :     return mcap::MaxTime;
      62                 :            :   }
      63                 :            : 
      64                 :         30 :   return start_timestamp_ns + end_time_ms * 1000'000 + 1000;
      65                 :            : }
      66                 :            : 
      67                 :            : // VCAPReader::Impl
      68                 :            : struct VCAPReader::Impl final {  // NOLINT(clang-analyzer-optin.performance.Padding)
      69                 :            :   std::atomic<BagReader::Status> status{VCAPReader::kStopped};
      70                 :            :   std::atomic_bool stop_flag{false};
      71                 :            :   std::atomic_bool pause_flag{false};
      72                 :            :   std::atomic_bool pause_next_flag{false};
      73                 :            :   std::atomic_bool jump_flag{false};
      74                 :            :   std::atomic<int64_t> pause_elapsed{0};
      75                 :            :   std::atomic<int64_t> offset_elapsed{0};
      76                 :            :   std::atomic<int64_t> real_elapsed{0};
      77                 :            :   std::atomic<int64_t> extra_elapsed{0};
      78                 :            :   std::atomic<int64_t> begin_time{0};
      79                 :            :   std::atomic<double> rate{1.0};
      80                 :            :   std::atomic<int> times{1};
      81                 :            :   std::atomic_bool is_pending{false};
      82                 :            :   std::atomic<int> split_index{0};
      83                 :            : 
      84                 :            :   bool read_only{false};
      85                 :            :   bool try_to_fix{false};
      86                 :            :   bool enable_compress{false};
      87                 :            : 
      88                 :            :   std::string path;
      89                 :            :   BagReader::Info info;
      90                 :            :   std::vector<BagReader::Info::UrlMeta> raw_url_metas;
      91                 :            :   std::mutex mtx;
      92                 :            :   ConditionVariable cv;
      93                 :            : 
      94                 :            :   BagReader::Config config;
      95                 :            :   std::mutex config_mtx;
      96                 :            :   std::shared_mutex time_mtx;
      97                 :            : 
      98                 :            :   ElapsedTimer elapsed_timer{ElapsedTimer::kMicro};
      99                 :            :   ElapsedTimer pause_elapsed_timer{ElapsedTimer::kMicro};
     100                 :            :   ElapsedTimer offset_timer{ElapsedTimer::kMicro};
     101                 :            :   ElapsedTimer real_timer{ElapsedTimer::kMicro};
     102                 :            : 
     103                 :            :   BagReader::StatusCallback status_callback;
     104                 :            :   BagReader::ReadyCallback ready_callback;
     105                 :            :   BagReader::FinishCallback finish_callback;
     106                 :            : 
     107                 :            :   int64_t total_start_timestamp_ns{-1};
     108                 :            :   bool total_has_completed{false};
     109                 :            : 
     110                 :            :   // mcap
     111                 :            : 
     112                 :            :   // WrapperFile
     113                 :            :   struct WrapperFile final {
     114                 :            :     std::string path;
     115                 :            :     std::unique_ptr<mcap::McapReader> reader;
     116                 :            :     std::unique_ptr<mcap::LinearMessageView> msg_view;
     117                 :            :     std::optional<mcap::LinearMessageView::Iterator> msg_view_begin;
     118                 :            :     std::optional<mcap::LinearMessageView::Iterator> msg_view_end;
     119                 :            :     int index{0};
     120                 :            :     int64_t start_timestamp_ns{0};
     121                 :            :     int64_t begin{0};
     122                 :            :     int64_t end{0};
     123                 :            :     std::unordered_map<std::string, int> url_to_id_map;
     124                 :            :     std::unordered_map<int, std::string> id_to_url_map;
     125                 :            :     std::unordered_map<int, ActionType> channel_action_map;
     126                 :            :     bool has_idx_elapsed{false};
     127                 :            :     bool has_idx_url{false};
     128                 :            :     bool has_schema{false};
     129                 :            :     bool has_completed{false};
     130                 :            :     bool is_channel_broken{false};
     131                 :            : 
     132                 :        805 :     WrapperFile() {
     133         [ +  - ]:        805 :       url_to_id_map.reserve(128);
     134         [ +  - ]:        805 :       id_to_url_map.reserve(128);
     135         [ +  - ]:        805 :       channel_action_map.reserve(128);
     136                 :        805 :     }
     137                 :            :   };
     138                 :            : 
     139                 :            :   std::vector<WrapperFile> file_list;
     140                 :            : 
     141                 :            :   std::unique_ptr<mcap::LinearMessageView> cursor_msg_view;
     142                 :            :   std::optional<mcap::LinearMessageView::Iterator> cursor_iter;
     143                 :            :   std::optional<mcap::LinearMessageView::Iterator> cursor_iter_end;
     144                 :            :   int cursor_file_index{0};
     145                 :            :   int64_t cursor_begin_us{0};
     146                 :            :   int64_t cursor_end_us{0};
     147                 :            :   bool cursor_need_advance{false};
     148                 :            :   bool cursor_read_error{false};
     149                 :            :   BagReader::Config cursor_config;
     150                 :            : };
     151                 :            : 
     152                 :            : // VCAPReader
     153                 :        185 : VCAPReader::VCAPReader(const std::string& path, bool read_only, bool try_to_fix)
     154         [ +  - ]:        185 :     : BagReader(path, read_only, try_to_fix), impl_{std::make_unique<Impl>()} {
     155   [ +  -  +  - ]:        185 :   set_name("VCAPReader");
     156                 :            : 
     157   [ +  -  +  - ]:        185 :   url_ser_map().reserve(128);
     158   [ +  -  +  - ]:        185 :   url_schema_type_map().reserve(128);
     159                 :            : 
     160                 :        185 :   impl_->read_only = read_only;
     161                 :        185 :   impl_->try_to_fix = try_to_fix;
     162                 :            : 
     163         [ +  + ]:        185 :   open(path);
     164                 :        192 : }
     165                 :            : 
     166                 :        178 : VCAPReader::~VCAPReader() {
     167         [ +  + ]:        178 :   if (!impl_->stop_flag.load(std::memory_order_relaxed)) {
     168                 :        177 :     do_stop();
     169                 :            :   }
     170                 :            : 
     171                 :        178 :   quit(true);
     172                 :            : 
     173                 :        178 :   impl_->cv.notify_one();
     174                 :            : 
     175                 :        178 :   wait_for_quit();
     176                 :            : 
     177                 :        178 :   detach_plugin();
     178                 :            : 
     179                 :        178 :   close();
     180                 :        178 : }
     181                 :            : 
     182                 :          8 : void VCAPReader::bind_bag_interface(const std::shared_ptr<BagPluginInterface>& bag_interface) {
     183                 :          8 :   BagReader::bind_bag_interface(bag_interface);
     184                 :          8 :   impl_->info.url_metas = impl_->raw_url_metas;
     185                 :          8 :   process_url_metas(impl_->info.url_metas);
     186                 :          8 :   rebuild_url_meta_lookup(impl_->info.url_metas);
     187                 :          8 : }
     188                 :            : 
     189                 :          2 : void VCAPReader::register_status_callback(StatusCallback&& status_callback) {
     190                 :          2 :   impl_->status_callback = std::move(status_callback);
     191                 :          2 : }
     192                 :            : 
     193                 :          4 : void VCAPReader::register_ready_callback(ReadyCallback&& ready_callback) {
     194                 :          4 :   impl_->ready_callback = std::move(ready_callback);
     195                 :          4 : }
     196                 :            : 
     197                 :         15 : void VCAPReader::register_finish_callback(FinishCallback&& finish_callback) {
     198                 :         15 :   impl_->finish_callback = std::move(finish_callback);
     199                 :         15 : }
     200                 :            : 
     201                 :         16 : void VCAPReader::register_output_callback(OutputCallback&& output_callback) {
     202                 :         16 :   BagReader::register_output_callback(std::move(output_callback));
     203                 :         16 : }
     204                 :            : 
     205                 :         30 : void VCAPReader::play(const Config& config) {
     206   [ +  -  +  + ]:         30 :   if VUNLIKELY (is_busy()) {
     207   [ +  -  +  -  :          2 :     VLOG_W("VCAPReader: Is busy.");
                   +  - ]
     208                 :            :   }
     209                 :            : 
     210         [ +  + ]:         30 :   if (config.skip_blank) {
     211                 :          2 :     impl_->begin_time.store(std::max(config.begin_time, impl_->info.blank_duration), std::memory_order_relaxed);
     212                 :            :   } else {
     213                 :         28 :     impl_->begin_time.store(config.begin_time, std::memory_order_relaxed);
     214                 :            :   }
     215                 :            : 
     216         [ +  + ]:         30 :   if (config.rate <= 0) {
     217                 :          1 :     impl_->rate.store(1, std::memory_order_relaxed);
     218                 :            :   } else {
     219                 :         29 :     impl_->rate.store(config.rate, std::memory_order_relaxed);
     220                 :            :   }
     221                 :            : 
     222                 :         30 :   impl_->times.store(config.times, std::memory_order_relaxed);
     223                 :            : 
     224                 :         60 :   impl_->real_elapsed.store(impl_->begin_time.load(std::memory_order_relaxed) * 1000U, std::memory_order_relaxed);
     225                 :         30 :   impl_->is_pending.store(true, std::memory_order_relaxed);
     226                 :            : 
     227                 :            :   {
     228         [ +  - ]:         30 :     std::unique_lock lock(impl_->mtx);
     229                 :         30 :     impl_->stop_flag.store(false, std::memory_order_relaxed);
     230                 :         30 :     impl_->pause_flag.store(false, std::memory_order_relaxed);
     231                 :         30 :     impl_->pause_next_flag.store(false, std::memory_order_relaxed);
     232                 :         30 :     impl_->jump_flag.store(false, std::memory_order_relaxed);
     233                 :         30 :   }
     234                 :            : 
     235                 :         30 :   Config config_snapshot;
     236                 :            : 
     237                 :            :   {
     238         [ +  - ]:         30 :     std::unique_lock lock(impl_->config_mtx);
     239         [ +  - ]:         30 :     impl_->config = config;
     240         [ +  - ]:         30 :     config_snapshot = impl_->config;
     241                 :         30 :   }
     242                 :            : 
     243   [ +  -  +  -  :         60 :   post_task([this, config_snapshot]() { read(config_snapshot); });
                   +  - ]
     244                 :         30 : }
     245                 :            : 
     246                 :          4 : void VCAPReader::stop() { do_stop(); }
     247                 :            : 
     248                 :          2 : void VCAPReader::pause() {
     249                 :            :   {
     250         [ +  - ]:          2 :     std::unique_lock lock(impl_->mtx);
     251                 :          2 :     impl_->pause_flag.store(true, std::memory_order_relaxed);
     252                 :          2 :   }
     253                 :            : 
     254                 :          2 :   impl_->cv.notify_one();
     255                 :          2 : }
     256                 :            : 
     257                 :          2 : void VCAPReader::resume() {
     258                 :            :   {
     259         [ +  - ]:          2 :     std::unique_lock lock(impl_->mtx);
     260                 :          2 :     impl_->pause_flag.store(false, std::memory_order_relaxed);
     261                 :          2 :   }
     262                 :            : 
     263                 :          2 :   impl_->cv.notify_one();
     264                 :          2 : }
     265                 :            : 
     266                 :         10 : void VCAPReader::pause_to_next() {
     267                 :            :   {
     268         [ +  - ]:         10 :     std::unique_lock lock(impl_->mtx);
     269                 :            : 
     270         [ +  + ]:         10 :     if (!impl_->pause_flag.load(std::memory_order_relaxed)) {
     271                 :          1 :       return;
     272                 :            :     }
     273                 :            : 
     274                 :          9 :     impl_->pause_next_flag.store(true, std::memory_order_relaxed);
     275         [ +  + ]:         10 :   }
     276                 :            : 
     277                 :          9 :   impl_->cv.notify_one();
     278                 :            : }
     279                 :            : 
     280                 :          7 : void VCAPReader::jump(int64_t begin_time, double rate, int times, bool force_to_play) {
     281         [ +  + ]:          7 :   if (begin_time < 0) {
     282                 :          1 :     begin_time = 0;
     283         [ +  + ]:          6 :   } else if (begin_time > impl_->info.total_duration) {
     284                 :          1 :     begin_time = std::max<int64_t>(0, impl_->info.total_duration - 100);
     285                 :            :   }
     286                 :            : 
     287                 :          7 :   impl_->real_elapsed.store(begin_time * 1000U, std::memory_order_relaxed);
     288                 :          7 :   impl_->is_pending.store(true, std::memory_order_relaxed);
     289                 :            : 
     290                 :          7 :   bool last_pause_flag = impl_->pause_flag.load(std::memory_order_relaxed);
     291                 :            : 
     292                 :            :   {
     293         [ +  - ]:          7 :     std::unique_lock lock(impl_->mtx);
     294                 :          7 :     impl_->stop_flag.store(false, std::memory_order_relaxed);
     295                 :          7 :     impl_->pause_flag.store(false, std::memory_order_relaxed);
     296                 :          7 :     impl_->pause_next_flag.store(false, std::memory_order_relaxed);
     297                 :          7 :     impl_->jump_flag.store(true, std::memory_order_relaxed);
     298                 :          7 :   }
     299                 :            : 
     300                 :          7 :   impl_->cv.notify_one();
     301                 :            : 
     302         [ +  - ]:          7 :   wait_for_idle();
     303                 :            : 
     304                 :          7 :   impl_->begin_time.store(begin_time, std::memory_order_relaxed);
     305                 :            : 
     306         [ +  + ]:          7 :   if (rate <= 0) {
     307                 :          3 :     impl_->rate.store(1, std::memory_order_relaxed);
     308                 :            :   } else {
     309                 :          4 :     impl_->rate.store(rate, std::memory_order_relaxed);
     310                 :            :   }
     311                 :            : 
     312                 :          7 :   impl_->times.store(times, std::memory_order_relaxed);
     313                 :            : 
     314                 :            :   {
     315         [ +  - ]:          7 :     std::unique_lock lock(impl_->mtx);
     316                 :          7 :     impl_->stop_flag.store(false, std::memory_order_relaxed);
     317         [ +  + ]:          8 :     impl_->pause_flag.store(force_to_play ? false : last_pause_flag, std::memory_order_relaxed);
     318                 :          7 :     impl_->pause_next_flag.store(false, std::memory_order_relaxed);
     319                 :          7 :     impl_->jump_flag.store(false, std::memory_order_relaxed);
     320                 :          7 :   }
     321                 :            : 
     322                 :          7 :   Config config_snapshot;
     323                 :            : 
     324                 :            :   {
     325         [ +  - ]:          7 :     std::unique_lock lock(impl_->config_mtx);
     326         [ +  - ]:          7 :     config_snapshot = impl_->config;
     327                 :          7 :   }
     328                 :            : 
     329                 :          7 :   config_snapshot.auto_pause = false;
     330   [ +  -  +  -  :         14 :   post_task([this, config_snapshot]() { read(config_snapshot); });
                   +  - ]
     331                 :          8 : }
     332                 :            : 
     333                 :         33 : std::future<bool> VCAPReader::check() {
     334         [ +  + ]:         33 :   if VUNLIKELY (is_busy()) {
     335   [ +  -  +  -  :          2 :     VLOG_W("VCAPReader: Is busy.");
                   +  - ]
     336                 :            :   }
     337                 :            : 
     338                 :        390 :   return invoke_task([this]() {
     339         [ +  + ]:         33 :     if (!impl_->total_has_completed) {
     340   [ +  -  +  -  :          2 :       VLOG_W("VCAPReader: Incomplete data detected.");
                   +  - ]
     341                 :          1 :       return false;
     342                 :            :     }
     343                 :            : 
     344                 :         32 :     mcap::Status status;
     345                 :            : 
     346                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     347                 :            :     auto status_function = [](const mcap::Status& status) {
     348                 :            :       if (!status.ok()) {
     349                 :            :         CLOG_W("VCAPReader: Failed to check summary, error = %s.", status.message.c_str());
     350                 :            :       }
     351                 :            :     };
     352                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     353                 :            : 
     354         [ +  + ]:         83 :     for (auto& wrapper_file : impl_->file_list) {
     355         [ -  + ]:         51 :       if VUNLIKELY (!wrapper_file.reader) {
     356                 :            :         continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     357                 :            :       }
     358                 :            : 
     359         [ +  - ]:         51 :       status = wrapper_file.reader->readSummary(mcap::ReadSummaryMethod::ForceScan, status_function);
     360                 :            : 
     361         [ -  + ]:         51 :       if VUNLIKELY (!status.ok()) {
     362                 :            :         CLOG_W("VCAPReader: Failed to check whole summary, error = %s.",  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     363                 :            :                status.message.c_str());                                   // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     364                 :            :         return false;                                                     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     365                 :            :       }
     366                 :            :     }
     367                 :            : 
     368                 :         32 :     bool is_ok = true;
     369                 :            : 
     370         [ +  + ]:         32 :     if VUNLIKELY (impl_->info.total_duration < impl_->info.blank_duration) {
     371   [ +  -  +  -  :          2 :       CLOG_W("VCAPReader: Invalid duration, blank=%" PRId64 " total=%" PRId64 ".",
                   +  - ]
     372                 :            :              static_cast<int64_t>(impl_->info.blank_duration), static_cast<int64_t>(impl_->info.total_duration));
     373                 :          1 :       is_ok = false;
     374                 :            :     }
     375                 :            : 
     376   [ +  +  +  +  :         32 :     if VUNLIKELY (impl_->info.message_count > 0 && impl_->info.url_metas.empty()) {
                   +  + ]
     377   [ +  -  +  -  :          2 :       CLOG_W("VCAPReader: Message count is %" PRId64 " but url meta list is empty.",
                   +  - ]
     378                 :            :              static_cast<int64_t>(impl_->info.message_count));
     379                 :          1 :       is_ok = false;
     380                 :            :     }
     381                 :            : 
     382                 :         32 :     size_t total_count = 0;
     383                 :         32 :     size_t total_raw_size = 0;
     384                 :            : 
     385         [ +  + ]:         80 :     for (const auto& url_meta : impl_->info.url_metas) {
     386                 :         48 :       total_count += url_meta.count;
     387                 :         48 :       total_raw_size += url_meta.size;
     388                 :            : 
     389         [ -  + ]:         48 :       if VUNLIKELY (!url_meta.valid) {
     390                 :            :         CLOG_W("VCAPReader: Invalid url meta detected at index=%d.", url_meta.index);  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     391                 :            :         is_ok = false;                                                                 // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     392                 :            :       }
     393                 :            : 
     394         [ +  + ]:         48 :       if VUNLIKELY (url_meta.url.empty()) {
     395   [ +  -  +  -  :          2 :         CLOG_W("VCAPReader: Empty url detected at index=%d.", url_meta.index);
                   +  - ]
     396                 :          1 :         is_ok = false;
     397                 :            :       }
     398                 :            : 
     399         [ +  + ]:         48 :       if VUNLIKELY (url_meta.url_type.empty()) {
     400   [ +  -  +  -  :          2 :         CLOG_W("VCAPReader: Empty url_type detected for url=%s.", url_meta.url.c_str());
                   +  - ]
     401                 :          1 :         is_ok = false;
     402                 :            :       }
     403                 :            : 
     404   [ +  -  +  +  :         48 :       if VUNLIKELY (url_meta.count > 0 && url_meta.ser_type.empty()) {
                   +  + ]
     405   [ +  -  +  -  :          4 :         CLOG_W("VCAPReader: Empty ser_type detected for url=%s.", url_meta.url.c_str());
                   +  - ]
     406                 :          2 :         is_ok = false;
     407                 :            :       }
     408                 :            : 
     409         [ -  + ]:         48 :       if VUNLIKELY (!SchemaData::is_valid_type(url_meta.schema_type)) {
     410                 :            :         CLOG_W("VCAPReader: Invalid schema_type=%d detected for url=%s.",  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     411                 :            :                static_cast<int>(url_meta.schema_type),                     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     412                 :            :                url_meta.url.c_str());
     413                 :            :         is_ok = false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     414                 :            :       }
     415                 :            : 
     416                 :         48 :       auto inferred_schema_type = SchemaData::infer_ser_type(url_meta.ser_type);
     417                 :            : 
     418   [ +  +  +  +  :         48 :       if VUNLIKELY (url_meta.schema_type == SchemaType::kUnknown && inferred_schema_type != SchemaType::kUnknown) {
                   +  + ]
     419                 :          1 :         const auto schema_label = SchemaData::convert_type(inferred_schema_type);
     420   [ +  -  +  -  :          2 :         CLOG_W("VCAPReader: Missing schema_type for url=%s, inferred=%.*s.", url_meta.url.c_str(),
                   +  - ]
     421                 :            :                static_cast<int>(schema_label.size()), schema_label.data());
     422                 :          1 :         is_ok = false;
     423                 :            :       }
     424                 :            : 
     425   [ +  -  +  -  :         48 :       if VUNLIKELY (!std::isfinite(url_meta.loss) ||
          +  +  +  +  +  
             +  +  +  +  
                      + ]
     426                 :            :                     (url_meta.loss != -1.0 && (url_meta.loss < 0.0 || url_meta.loss > 1.0))) {
     427   [ +  -  +  -  :          6 :         CLOG_W("VCAPReader: Invalid loss=%f detected for url=%s.", url_meta.loss, url_meta.url.c_str());
                   +  - ]
     428                 :          3 :         is_ok = false;
     429                 :            :       }
     430                 :            : 
     431         [ +  + ]:         48 :       if VUNLIKELY (url_meta.freq < 0.0) {
     432   [ +  -  +  -  :          6 :         CLOG_W("VCAPReader: Invalid freq=%f detected for url=%s.", url_meta.freq, url_meta.url.c_str());
                   +  - ]
     433                 :          3 :         is_ok = false;
     434                 :            :       }
     435                 :            :     }
     436                 :            : 
     437   [ +  +  +  +  :         63 :     if ((!impl_->info.url_metas.empty() || impl_->info.message_count != 0) &&
                   +  + ]
     438         [ +  + ]:         31 :         total_count != static_cast<size_t>(impl_->info.message_count)) {
     439   [ +  -  +  -  :          4 :       CLOG_W("VCAPReader: Message count mismatch, header=%" PRId64 " metas=%zu.",
                   +  - ]
     440                 :            :              static_cast<int64_t>(impl_->info.message_count), total_count);
     441                 :          2 :       is_ok = false;
     442                 :            :     }
     443                 :            : 
     444   [ +  +  -  +  :         62 :     if ((!impl_->info.url_metas.empty() || impl_->info.total_raw_size != 0) &&
                   -  + ]
     445         [ -  + ]:         30 :         total_raw_size != static_cast<size_t>(impl_->info.total_raw_size)) {
     446                 :            :       CLOG_W("VCAPReader: Raw size mismatch, header=%" PRId64 " metas=%zu.",  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     447                 :            :              static_cast<int64_t>(impl_->info.total_raw_size), total_raw_size);
     448                 :            :       is_ok = false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     449                 :            :     }
     450                 :            : 
     451   [ +  -  +  + ]:         39 :     for (const auto& schema_data : detect_schema()) {
     452         [ -  + ]:          7 :       if VUNLIKELY (schema_data.name.empty()) {
     453                 :            :         CLOG_W("VCAPReader: Empty schema name detected.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     454                 :            :         is_ok = false;                                      // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     455                 :            :       }
     456                 :            : 
     457         [ -  + ]:          7 :       if VUNLIKELY (schema_data.encoding.empty()) {
     458                 :            :         CLOG_W("VCAPReader: Empty schema encoding detected for name=%s.",  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     459                 :            :                schema_data.name.c_str());                                  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     460                 :            :         is_ok = false;                                                     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     461                 :            :       }
     462                 :            : 
     463   [ +  -  +  +  :          7 :       if VUNLIKELY (!SchemaData::is_valid_type(schema_data.schema_type) ||
                   +  + ]
     464                 :            :                     schema_data.schema_type == SchemaType::kUnknown) {
     465   [ +  -  +  -  :          2 :         CLOG_W("VCAPReader: Invalid schema_type=%d detected for schema=%s.", static_cast<int>(schema_data.schema_type),
                   +  - ]
     466                 :            :                schema_data.name.c_str());
     467                 :          1 :         is_ok = false;
     468                 :            :       }
     469                 :         32 :     }
     470                 :            : 
     471                 :         32 :     return is_ok;
     472         [ +  - ]:         65 :   });
     473                 :            : }
     474                 :            : 
     475                 :          3 : std::future<bool> VCAPReader::reindex() {
     476         [ +  + ]:          3 :   if VUNLIKELY (is_busy()) {
     477   [ +  -  +  -  :          2 :     VLOG_W("VCAPReader: Is busy.");
                   +  - ]
     478                 :            :   }
     479                 :            : 
     480                 :          3 :   return invoke_task([]() {
     481   [ +  -  +  -  :          6 :     VLOG_W("VCAPReader: Reindex is not supported for vcap.");
                   +  - ]
     482                 :            : 
     483                 :          3 :     return false;
     484         [ +  - ]:          3 :   });
     485                 :            : }
     486                 :            : 
     487                 :          3 : std::future<bool> VCAPReader::fix(bool rebuild) {
     488         [ +  + ]:          3 :   if VUNLIKELY (is_busy()) {
     489   [ +  -  +  -  :          2 :     VLOG_W("VCAPReader: Is busy.");
                   +  - ]
     490                 :            :   }
     491                 :            : 
     492                 :          3 :   return invoke_task([rebuild]() {
     493                 :            :     (void)rebuild;
     494                 :            : 
     495   [ +  -  +  -  :          6 :     VLOG_W("VCAPReader: Fix is not supported for vcap.");
                   +  - ]
     496                 :            : 
     497                 :          3 :     return false;
     498         [ +  - ]:          3 :   });
     499                 :            : }
     500                 :            : 
     501                 :          8 : void VCAPReader::tag(const std::string& tag_name) {
     502         [ +  + ]:          8 :   if VUNLIKELY (is_busy()) {
     503   [ +  -  +  -  :          2 :     VLOG_W("VCAPReader: Is busy.");
                   +  - ]
     504                 :            :   }
     505                 :            : 
     506   [ +  -  +  - ]:          8 :   post_task([this, tag_name]() {
     507                 :            :     try {
     508                 :            : #ifdef _WIN32
     509                 :            :       std::filesystem::path file_path(Helpers::string_to_wstring(impl_->path));
     510                 :            :       std::string suffix = Helpers::path_to_string(file_path.extension());
     511                 :            : #else
     512         [ +  - ]:          8 :       std::filesystem::path file_path(impl_->path);
     513   [ +  -  +  - ]:          8 :       std::string suffix = file_path.extension().string();
     514                 :            : #endif
     515                 :            : 
     516                 :         52 :       std::transform(suffix.begin(), suffix.end(), suffix.begin(), [](unsigned char c) { return std::tolower(c); });
     517                 :            : 
     518         [ +  + ]:          8 :       if (suffix == ".vcapx") {
     519                 :            :         try {
     520                 :          4 :           nlohmann::ordered_json root_json;
     521                 :          4 :           nlohmann::ordered_json header_json;
     522                 :            : 
     523                 :            :           {
     524         [ +  - ]:          4 :             std::ifstream file(file_path);
     525                 :            : 
     526         [ +  + ]:          4 :             file >> root_json;
     527                 :            : 
     528         [ +  - ]:          3 :             file.close();
     529                 :          4 :           }
     530                 :            : 
     531   [ +  -  +  - ]:          3 :           header_json = root_json["VLinkHeader"];
     532                 :            : 
     533   [ +  -  +  - ]:          3 :           header_json["tag"] = tag_name;
     534                 :            : 
     535         [ +  - ]:          3 :           root_json["VLinkHeader"] = std::move(header_json);
     536                 :            : 
     537                 :            :           {
     538         [ +  - ]:          3 :             std::ofstream filex(impl_->path, std::ios::out | std::ios::trunc);
     539                 :            : 
     540   [ +  -  +  - ]:          3 :             if VLIKELY (filex.is_open()) {
     541   [ +  -  +  - ]:          3 :               filex << root_json.dump(4);
     542         [ +  - ]:          3 :               filex.close();
     543                 :            :             }
     544                 :          3 :           }
     545         [ -  + ]:          6 :         } catch (nlohmann::json::exception& e) {
     546   [ +  -  +  -  :          2 :           VLOG_W("VCAPReader: JSON parse error, ", e.what(), ".");
                   +  - ]
     547                 :          1 :         }
     548                 :            :       } else {
     549   [ +  -  +  -  :          8 :         VLOG_W("VCAPReader: Tag is not supported for single vcap.");
                   +  - ]
     550                 :            :       }
     551         [ -  - ]:          8 :     } catch (std::filesystem::filesystem_error& e) {
     552                 :            :       VLOG_F("VCAPReader: Filesystem error, ", e.what(), ".");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     553                 :            :       return;                                                   // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     554                 :            :     }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     555                 :            :   });
     556                 :          8 : }
     557                 :            : 
     558                 :          5 : int64_t VCAPReader::get_timestamp() const {
     559         [ +  - ]:          5 :   std::shared_lock time_lock(impl_->time_mtx);
     560                 :            : 
     561         [ +  + ]:          5 :   if (impl_->status.load(std::memory_order_relaxed) == kPlaying) {
     562         [ -  + ]:          1 :     if (impl_->is_pending.load(std::memory_order_relaxed)) {
     563                 :            :       return impl_->real_elapsed.load(std::memory_order_relaxed) / 1000U;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     564                 :            :     } else {
     565                 :          1 :       return (impl_->real_elapsed.load(std::memory_order_relaxed) +
     566                 :          1 :               (impl_->real_timer.get() * impl_->rate.load(std::memory_order_relaxed))) /
     567                 :          1 :              1000U;
     568                 :            :     }
     569         [ +  + ]:          4 :   } else if (impl_->status.load(std::memory_order_relaxed) == kPaused) {
     570                 :          2 :     return (impl_->real_elapsed.load(std::memory_order_relaxed) +
     571                 :          2 :             ((impl_->real_timer.get() - impl_->pause_elapsed_timer.get() -
     572                 :          2 :               impl_->extra_elapsed.load(std::memory_order_relaxed)) *
     573                 :          2 :              impl_->rate.load(std::memory_order_relaxed))) /
     574                 :          2 :            1000U;
     575                 :            :   } else {
     576                 :          2 :     return 0;
     577                 :            :   }
     578                 :          5 : }
     579                 :            : 
     580                 :          5 : int64_t VCAPReader::get_real_timestamp() const {
     581   [ +  +  +  +  :          9 :   if (impl_->status.load(std::memory_order_relaxed) == kPlaying ||
                   +  + ]
     582                 :          4 :       impl_->status.load(std::memory_order_relaxed) == kPaused) {
     583                 :          6 :     return impl_->real_elapsed.load(std::memory_order_relaxed) / 1000U;
     584                 :            :   } else {
     585                 :          2 :     return 0;
     586                 :            :   }
     587                 :            : }
     588                 :            : 
     589                 :         20 : BagReader::Status VCAPReader::get_status() const { return impl_->status.load(std::memory_order_relaxed); }
     590                 :            : 
     591                 :        233 : const BagReader::Info& VCAPReader::get_info() const { return impl_->info; }
     592                 :            : 
     593                 :         36 : std::vector<SchemaData> VCAPReader::detect_schema() {
     594                 :         36 :   std::vector<SchemaData> schema_list;
     595                 :         36 :   std::unordered_map<std::string, size_t> schema_index_map;
     596                 :            : 
     597         [ +  + ]:         36 :   if (!impl_->info.has_schema) {
     598                 :         25 :     return schema_list;
     599                 :            :   }
     600                 :            : 
     601         [ +  - ]:         11 :   schema_index_map.reserve(impl_->info.url_metas.size());
     602                 :            : 
     603         [ +  + ]:         24 :   for (auto& wrapper_file : impl_->file_list) {
     604   [ +  -  +  + ]:         25 :     for (const auto& [schema_id, schema_ptr] : wrapper_file.reader->schemas()) {
     605                 :            :       (void)schema_id;
     606                 :            : 
     607                 :         12 :       SchemaData schema;
     608         [ +  - ]:         12 :       schema.name = schema_ptr->name;
     609         [ +  - ]:         12 :       schema.encoding = schema_ptr->encoding;
     610                 :         12 :       schema.schema_type = SchemaData::resolve_type(SchemaType::kUnknown, schema.name, schema.encoding);
     611                 :            : 
     612   [ +  -  +  -  :         12 :       if (!schema.name.empty() && !schema_ptr->data.empty()) {
                   +  - ]
     613         [ +  - ]:         12 :         std::string schema_key = schema.name;
     614         [ +  - ]:         12 :         schema_key.push_back('\x1F');
     615         [ +  - ]:         12 :         schema_key.append(SchemaData::convert_type(schema.schema_type));
     616         [ +  - ]:         12 :         auto schema_index_iter = schema_index_map.find(schema_key);
     617                 :            : 
     618         [ +  + ]:         12 :         if (schema_index_iter == schema_index_map.end()) {
     619                 :            :           schema.data =
     620                 :         10 :               Bytes::deep_copy(reinterpret_cast<const uint8_t*>(schema_ptr->data.data()), schema_ptr->data.size());
     621         [ +  - ]:         10 :           schema_index_map.emplace(schema_key, schema_list.size());
     622         [ +  - ]:         10 :           schema_list.emplace_back(std::move(schema));
     623                 :            :         } else {
     624                 :          2 :           auto& current_schema = schema_list[schema_index_iter->second];
     625                 :            : 
     626   [ -  +  -  -  :          2 :           if (current_schema.encoding.empty() && !schema.encoding.empty()) {
                   -  + ]
     627                 :            :             current_schema.encoding = schema.encoding;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     628                 :            :           }
     629                 :            : 
     630         [ -  + ]:          2 :           if (current_schema.data.empty()) {
     631                 :          0 :             current_schema.data = Bytes::deep_copy(
     632                 :            :                 reinterpret_cast<const uint8_t*>(schema_ptr->data.data()),  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     633                 :            :                 schema_ptr->data.size());                                   // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     634                 :            :           }
     635                 :            :         }
     636                 :         12 :       }
     637                 :         25 :     }
     638                 :            :   }
     639                 :            : 
     640                 :         11 :   return schema_list;
     641                 :         36 : }
     642                 :            : 
     643                 :          9 : bool VCAPReader::is_split_mode() const { return impl_->info.split_count > 0; }
     644                 :            : 
     645                 :          4 : int VCAPReader::get_split_index() const { return impl_->split_index.load(std::memory_order_relaxed); }
     646                 :            : 
     647                 :          6 : bool VCAPReader::is_jumping() const { return impl_->jump_flag.load(std::memory_order_relaxed); }
     648                 :            : 
     649                 :         84 : size_t VCAPReader::get_max_task_count() const { return kMaxTaskSize; }
     650                 :            : 
     651                 :        125 : void VCAPReader::on_begin() { MessageLoop::on_begin(); }
     652                 :            : 
     653                 :        125 : void VCAPReader::on_end() { MessageLoop::on_end(); }
     654                 :            : 
     655                 :        110 : bool VCAPReader::do_open_cursor(const Config& config) {
     656                 :        110 :   impl_->cursor_config = config;
     657         [ +  + ]:        110 :   impl_->cursor_begin_us = config.begin_time > 0 ? config.begin_time * 1000 : 0;
     658         [ +  + ]:        110 :   impl_->cursor_end_us = config.end_time > 0 ? config.end_time * 1000 : 0;
     659                 :        110 :   impl_->cursor_file_index = 0;
     660                 :        110 :   impl_->cursor_read_error = false;
     661                 :            : 
     662         [ -  + ]:        110 :   if VUNLIKELY (impl_->file_list.empty()) {
     663                 :            :     VLOG_W("VCAPReader: Cursor cannot find any data.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     664                 :            :     return false;                                        // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     665                 :            :   }
     666                 :            : 
     667                 :        110 :   return prepare_cursor_view(0);
     668                 :            : }
     669                 :            : 
     670                 :        562 : bool VCAPReader::do_read_next(Frame& out, bool& is_error) {
     671                 :        562 :   is_error = false;
     672                 :            : 
     673                 :            :   while (true) {
     674   [ +  -  -  +  :        892 :     if VUNLIKELY (!impl_->cursor_iter.has_value() || !impl_->cursor_iter_end.has_value()) {
                   -  + ]
     675                 :        562 :       return false;
     676                 :            :     }
     677                 :            : 
     678         [ +  - ]:        892 :     auto& iter = impl_->cursor_iter.value();
     679         [ +  - ]:        892 :     auto& iter_end = impl_->cursor_iter_end.value();
     680                 :            : 
     681         [ +  + ]:        892 :     if (impl_->cursor_need_advance) {
     682   [ +  -  +  - ]:        452 :       if (iter != iter_end) {
     683         [ +  - ]:        452 :         iter++;
     684                 :            :       }
     685                 :            : 
     686                 :        452 :       impl_->cursor_need_advance = false;
     687                 :            :     }
     688                 :            : 
     689   [ +  -  +  + ]:        892 :     if (iter == iter_end) {
     690         [ -  + ]:        427 :       if VUNLIKELY (impl_->cursor_read_error) {
     691                 :            :         is_error = true;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     692                 :            :         return false;     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     693                 :            :       }
     694                 :            : 
     695         [ +  + ]:        427 :       if (impl_->cursor_file_index + 1 < static_cast<int>(impl_->file_list.size())) {
     696   [ +  -  -  + ]:        317 :         if VUNLIKELY (!prepare_cursor_view(impl_->cursor_file_index + 1)) {
     697                 :            :           is_error = true;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     698                 :            :           return false;     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     699                 :            :         }
     700                 :            : 
     701                 :        317 :         continue;
     702                 :            :       }
     703                 :            : 
     704                 :        110 :       return false;
     705                 :            :     }
     706                 :            : 
     707         [ +  - ]:        465 :     const int64_t timestamp = (static_cast<int64_t>(iter->message.logTime) - impl_->total_start_timestamp_ns) / 1000;
     708                 :            : 
     709   [ +  +  -  +  :        465 :     if (impl_->cursor_begin_us > 0 && timestamp < impl_->cursor_begin_us) {
                   -  + ]
     710                 :            :       iter++;    // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     711                 :            :       continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     712                 :            :     }
     713                 :            : 
     714   [ +  +  +  +  :        465 :     if (impl_->cursor_end_us > 0 && timestamp > impl_->cursor_end_us) {
                   +  + ]
     715         [ +  - ]:         13 :       iter++;
     716                 :         13 :       continue;
     717                 :            :     }
     718                 :            : 
     719   [ +  -  -  + ]:        452 :     if VUNLIKELY (iter->message.dataSize > static_cast<uint64_t>(std::numeric_limits<size_t>::max())) {
     720                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
     721                 :            :       CLOG_W("VCAPReader: Cursor message data size is too large to address, size = %" PRIu64 ".",
     722                 :            :              static_cast<uint64_t>(iter->message.dataSize));
     723                 :            :       iter++;
     724                 :            :       continue;
     725                 :            :     }
     726                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     727                 :            : 
     728                 :        452 :     std::string output_url;
     729                 :            : 
     730   [ +  -  +  -  :        452 :     if VUNLIKELY (!convert_playback_url(iter->channel->topic, output_url)) {
                   -  + ]
     731                 :            :       iter++;    // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     732                 :            :       continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     733                 :            :     }
     734                 :            : 
     735                 :        452 :     ActionType action_type = ActionType::kUnknownAction;
     736         [ +  - ]:        452 :     auto& wrapper_file = impl_->file_list.at(impl_->cursor_file_index);
     737                 :            : 
     738   [ +  -  +  - ]:        452 :     if (auto action_iter = wrapper_file.channel_action_map.find(iter->message.channelId);
     739         [ +  - ]:        452 :         action_iter != wrapper_file.channel_action_map.end()) {
     740                 :        452 :       action_type = action_iter->second;
     741                 :            :     }
     742                 :            : 
     743         [ +  - ]:        452 :     const auto* data = reinterpret_cast<const uint8_t*>(iter->message.data);
     744         [ +  - ]:        452 :     const auto size = static_cast<size_t>(iter->message.dataSize);
     745                 :            : 
     746                 :        452 :     out.timestamp = timestamp;
     747                 :        452 :     out.url = std::move(output_url);
     748                 :        452 :     out.ser_type.clear();
     749                 :        452 :     out.schema_type = SchemaType::kUnknown;
     750                 :        452 :     out.action_type = action_type;
     751                 :        452 :     out.data = Bytes::shallow_copy(data, size);
     752                 :            : 
     753         [ +  - ]:        452 :     fill_frame_meta(out);
     754                 :            : 
     755                 :        452 :     impl_->cursor_need_advance = true;
     756                 :            : 
     757                 :        452 :     return true;
     758         [ +  - ]:        782 :   }
     759                 :            : }
     760                 :            : 
     761                 :        427 : bool VCAPReader::prepare_cursor_view(int file_index) {
     762                 :        427 :   impl_->cursor_iter.reset();
     763                 :        427 :   impl_->cursor_iter_end.reset();
     764                 :        427 :   impl_->cursor_msg_view.reset();
     765                 :        427 :   impl_->cursor_need_advance = false;
     766                 :            : 
     767   [ +  -  -  +  :        427 :   if VUNLIKELY (file_index < 0 || file_index >= static_cast<int>(impl_->file_list.size())) {
                   -  + ]
     768                 :            :     return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     769                 :            :   }
     770                 :            : 
     771         [ +  - ]:        427 :   auto& wrapper_file = impl_->file_list.at(file_index);
     772                 :            : 
     773         [ -  + ]:        427 :   if VUNLIKELY (!wrapper_file.reader) {
     774                 :            :     VLOG_W("VCAPReader: Cursor target vcap reader is empty.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     775                 :            :     return false;                                               // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     776                 :            :   }
     777                 :            : 
     778                 :          0 :   auto status_function = [this](const mcap::Status& status) {
     779                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     780                 :            :     if (!status.ok()) {
     781                 :            :       CLOG_W("VCAPReader: Failed to read cursor message, error = %s.", status.message.c_str());
     782                 :            :       impl_->cursor_read_error = true;
     783                 :            :     }
     784                 :            :   };
     785                 :            :   // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     786                 :            : 
     787                 :         20 :   auto filter_function = [this](std::string_view url) -> bool {
     788                 :         20 :     return match_playback_url_filter(url, impl_->cursor_config.filter_urls);
     789                 :        427 :   };
     790                 :            : 
     791                 :        427 :   mcap::ReadMessageOptions read_options;
     792                 :        427 :   read_options.startTime =
     793         [ +  + ]:        427 :       impl_->cursor_begin_us > 0 ? impl_->cursor_begin_us * 1000 + impl_->total_start_timestamp_ns : 0;
     794                 :        427 :   read_options.endTime = mcap::MaxTime;
     795   [ +  +  +  +  :        427 :   if (!impl_->cursor_config.filter_urls.empty() || has_playback_url_rules()) {
                   +  + ]
     796                 :         12 :     read_options.topicFilter = filter_function;
     797                 :            :   }
     798                 :        427 :   read_options.readOrder = mcap::ReadMessageOptions::ReadOrder::FileOrder;
     799                 :            : 
     800                 :        427 :   const auto end_time = get_read_end_time(impl_->total_start_timestamp_ns, impl_->cursor_config.end_time);
     801         [ +  - ]:        427 :   const auto [start_offset, end_offset] = wrapper_file.reader->byteRange(read_options.startTime, end_time);
     802                 :            : 
     803                 :            :   // NOLINTNEXTLINE(readability-redundant-smartptr-get)
     804         [ +  - ]:        854 :   impl_->cursor_msg_view = std::make_unique<mcap::LinearMessageView>(*wrapper_file.reader.get(), read_options,
     805                 :        427 :                                                                      start_offset, end_offset, status_function);
     806                 :            : 
     807         [ +  + ]:        427 :   if (start_offset == end_offset) {
     808         [ +  - ]:          3 :     impl_->cursor_iter.emplace(impl_->cursor_msg_view->end());
     809                 :            :   } else {
     810         [ +  - ]:        424 :     impl_->cursor_iter.emplace(impl_->cursor_msg_view->begin());
     811                 :            :   }
     812                 :            : 
     813         [ +  - ]:        427 :   impl_->cursor_iter_end.emplace(impl_->cursor_msg_view->end());
     814                 :        427 :   impl_->cursor_file_index = file_index;
     815                 :            : 
     816                 :        427 :   return true;
     817                 :        427 : }
     818                 :            : 
     819                 :         98 : void VCAPReader::update_status(Status status) {
     820                 :         98 :   bool has_changed = false;
     821                 :            : 
     822         [ +  + ]:         98 :   if (status == kStopped) {
     823         [ +  + ]:         34 :     if (impl_->status.load(std::memory_order_relaxed) != kStopped) {
     824                 :         33 :       impl_->status.store(kStopped, std::memory_order_relaxed);
     825                 :         33 :       has_changed = true;
     826                 :            :     }
     827         [ +  + ]:         64 :   } else if (status == kPaused) {
     828         [ +  + ]:         19 :     if (impl_->status.load(std::memory_order_relaxed) != kPaused) {
     829                 :         14 :       impl_->status.store(kPaused, std::memory_order_relaxed);
     830                 :         14 :       has_changed = true;
     831                 :            :     }
     832         [ +  - ]:         45 :   } else if (status == kPlaying) {
     833         [ +  + ]:         45 :     if (impl_->status.load(std::memory_order_relaxed) != kPlaying) {
     834                 :         42 :       impl_->status.store(kPlaying, std::memory_order_relaxed);
     835                 :         42 :       has_changed = true;
     836                 :            :     }
     837                 :            :   }
     838                 :            : 
     839         [ +  + ]:         98 :   if (has_changed) {
     840         [ +  + ]:         89 :     if VLIKELY (impl_->status_callback) {
     841                 :         19 :       impl_->status_callback(impl_->status.load(std::memory_order_relaxed));
     842                 :            :     }
     843                 :            :   }
     844                 :         98 : }
     845                 :            : 
     846                 :        181 : void VCAPReader::do_stop() {
     847                 :            :   {
     848         [ +  - ]:        181 :     std::unique_lock lock(impl_->mtx);
     849                 :        181 :     impl_->stop_flag.store(true, std::memory_order_relaxed);
     850                 :        181 :     impl_->pause_flag.store(false, std::memory_order_relaxed);
     851                 :        181 :     impl_->pause_next_flag.store(false, std::memory_order_relaxed);
     852                 :        181 :     impl_->jump_flag.store(false, std::memory_order_relaxed);
     853                 :        181 :   }
     854                 :            : 
     855                 :        181 :   impl_->cv.notify_one();
     856                 :        181 : }
     857                 :            : 
     858                 :         14 : void VCAPReader::do_pause() {
     859         [ +  - ]:         14 :   std::unique_lock lock(impl_->mtx);
     860                 :            : 
     861         [ +  - ]:         14 :   while (impl_->pause_flag.load(std::memory_order_relaxed)) {
     862                 :         14 :     impl_->pause_elapsed_timer.restart();
     863         [ +  - ]:         14 :     update_status(kPaused);
     864                 :            : 
     865         [ +  - ]:         14 :     impl_->cv.wait(lock, [this]() -> bool {
     866   [ +  +  +  + ]:         67 :       return impl_->stop_flag.load(std::memory_order_relaxed) || !impl_->pause_flag.load(std::memory_order_relaxed) ||
     867         [ +  - ]:         30 :              impl_->pause_next_flag.load(std::memory_order_relaxed) ||
     868   [ +  -  -  + ]:         55 :              impl_->jump_flag.load(std::memory_order_relaxed) || is_ready_to_quit();
     869                 :            :     });
     870                 :            : 
     871                 :         14 :     impl_->pause_elapsed.fetch_add(impl_->pause_elapsed_timer.get(), std::memory_order_relaxed);
     872                 :            : 
     873                 :            :     {
     874         [ +  - ]:         14 :       std::lock_guard time_lock(impl_->time_mtx);
     875                 :         14 :       impl_->real_timer.restart();
     876                 :            : 
     877         [ -  + ]:         28 :       if (impl_->offset_elapsed.load(std::memory_order_relaxed) > 0) {
     878                 :            :         impl_->real_elapsed.fetch_add(                                        // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     879                 :            :             (impl_->offset_timer.get() - impl_->pause_elapsed_timer.get()) *  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     880                 :            :                 impl_->rate.load(std::memory_order_relaxed),                  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     881                 :            :             std::memory_order_relaxed);
     882                 :            :       }
     883                 :         14 :     }
     884                 :            : 
     885         [ +  - ]:         14 :     update_status(kPlaying);
     886                 :            : 
     887         [ +  - ]:         14 :     if (impl_->pause_next_flag.load(std::memory_order_relaxed)) {
     888                 :         28 :       impl_->pause_elapsed.fetch_sub(impl_->offset_elapsed.load(std::memory_order_relaxed), std::memory_order_relaxed);
     889                 :         14 :       break;
     890                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
     891                 :            :     } else if (impl_->offset_elapsed.load(std::memory_order_relaxed) > 0) {
     892                 :            :       impl_->offset_timer.restart();
     893                 :            : 
     894                 :            :       impl_->cv.wait_for(lock, std::chrono::microseconds(impl_->offset_elapsed.load(std::memory_order_relaxed)),
     895                 :            :                          [this]() -> bool {
     896                 :            :                            return impl_->stop_flag.load(std::memory_order_relaxed) ||
     897                 :            :                                   impl_->pause_flag.load(std::memory_order_relaxed) ||
     898                 :            :                                   impl_->pause_next_flag.load(std::memory_order_relaxed) ||
     899                 :            :                                   impl_->jump_flag.load(std::memory_order_relaxed) || is_ready_to_quit();
     900                 :            :                          });
     901                 :            : 
     902                 :            :       if VUNLIKELY (impl_->pause_flag.load(std::memory_order_relaxed)) {
     903                 :            :         impl_->offset_elapsed.fetch_sub(impl_->offset_timer.get(), std::memory_order_relaxed);
     904                 :            :         // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     905                 :            :       } else {
     906                 :            :         impl_->offset_elapsed.store(0, std::memory_order_relaxed);  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     907                 :            :       }
     908                 :            :     }
     909                 :            :   }
     910                 :         14 : }
     911                 :            : 
     912                 :        804 : bool VCAPReader::prepare_file(void* file) {
     913                 :        804 :   auto* wrapper_file = static_cast<Impl::WrapperFile*>(file);
     914                 :            : 
     915                 :        804 :   wrapper_file->has_completed = true;
     916                 :            : 
     917                 :        804 :   auto& reader = wrapper_file->reader;
     918                 :            : 
     919         [ -  + ]:        804 :   if VUNLIKELY (!reader) {
     920                 :            :     wrapper_file->has_completed = false;                                             // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     921                 :            :     CLOG_F("VCAPReader: Mcap [%s] reader is nullptr.", wrapper_file->path.c_str());  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     922                 :            :     return false;                                                                    // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     923                 :            :   }
     924                 :            : 
     925   [ +  -  -  + ]:        804 :   if VUNLIKELY (!reader->header()) {
     926                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     927                 :            :     wrapper_file->has_completed = false;
     928                 :            :     CLOG_F("VCAPReader: Mcap [%s] reader header is nullptr.", wrapper_file->path.c_str());
     929                 :            :     return false;
     930                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     931                 :            :   }
     932                 :            : 
     933   [ +  -  -  + ]:        804 :   if VUNLIKELY (reader->header()->profile != "vlink") {
     934                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     935                 :            :     wrapper_file->has_completed = false;
     936                 :            :     CLOG_F("VCAPReader: Mcap [%s] profile is %s, not valid.", wrapper_file->path.c_str(),
     937                 :            :            reader->header()->profile.c_str());
     938                 :            :     return false;
     939                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     940                 :            :   }
     941                 :            : 
     942                 :        804 :   mcap::Status status;
     943                 :            : 
     944         [ +  - ]:        804 :   status = reader->readSummary(mcap::ReadSummaryMethod::NoFallbackScan);
     945                 :            : 
     946         [ -  + ]:        804 :   if VUNLIKELY (!status.ok()) {
     947                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     948                 :            :     wrapper_file->has_completed = false;
     949                 :            : 
     950                 :            :     if (impl_->try_to_fix) {
     951                 :            :       CLOG_E("VCAPReader: Failed to read summary, error = %s. Trying to fix.", status.message.c_str());
     952                 :            :       status = reader->readSummary(mcap::ReadSummaryMethod::AllowFallbackScan);
     953                 :            : 
     954                 :            :       if VUNLIKELY (!status.ok()) {
     955                 :            :         CLOG_F("VCAPReader: Failed to read summary, error = %s.", status.message.c_str());
     956                 :            :         return false;
     957                 :            :         // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     958                 :            :       }
     959                 :            : 
     960                 :            :     } else {
     961                 :            :       CLOG_F("VCAPReader: Failed to read summary, error = %s.",  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     962                 :            :              status.message.c_str());                            // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     963                 :            :       return false;                                              // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     964                 :            :     }
     965                 :            :   }
     966                 :            : 
     967         [ +  - ]:        804 :   const auto& meta_index = reader->metadataIndexes();
     968         [ +  - ]:        804 :   const auto& statistics = reader->statistics();
     969                 :            : 
     970         [ -  + ]:        804 :   if (!statistics.has_value()) {
     971                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     972                 :            :     wrapper_file->has_completed = false;
     973                 :            :     CLOG_F("VCAPReader: Mcap [%s] cannot find statistics.", wrapper_file->path.c_str());
     974                 :            :     return false;
     975                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     976                 :            :   }
     977                 :            : 
     978   [ +  -  +  + ]:        804 :   for (const auto& [schema_id, schema_ptr] : reader->schemas()) {
     979                 :            :     (void)schema_id;
     980                 :            : 
     981   [ +  -  +  -  :         19 :     if (schema_ptr && !schema_ptr->data.empty()) {
                   +  - ]
     982                 :         19 :       wrapper_file->has_schema = true;
     983                 :         19 :       impl_->info.has_schema = true;
     984                 :         19 :       break;
     985                 :            :     }
     986                 :        804 :   }
     987                 :            : 
     988                 :            :   // read header
     989                 :            :   {
     990   [ +  -  +  - ]:        804 :     auto header_iter = meta_index.find("VLinkHeader");
     991                 :            : 
     992         [ -  + ]:        804 :     if VUNLIKELY (header_iter == meta_index.end()) {
     993                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
     994                 :            :       wrapper_file->has_completed = false;
     995                 :            :       CLOG_F("VCAPReader: Mcap [%s] cannot find header.", wrapper_file->path.c_str());
     996                 :            :       return false;
     997                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     998                 :            :     }
     999                 :            : 
    1000                 :        804 :     const auto& header_index = header_iter->second;
    1001                 :            : 
    1002                 :            :     mcap::Record header_record;
    1003                 :            : 
    1004   [ +  -  +  - ]:        804 :     status = mcap::McapReader::ReadRecord(*reader->dataSource(), header_index.offset, &header_record);
    1005                 :            : 
    1006         [ -  + ]:        804 :     if VUNLIKELY (!status.ok()) {
    1007                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1008                 :            :       wrapper_file->has_completed = false;
    1009                 :            :       CLOG_F("VCAPReader: Failed to read header record for index, error = %s.", status.message.c_str());
    1010                 :            :       return false;
    1011                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1012                 :            :     }
    1013                 :            : 
    1014                 :        804 :     mcap::Metadata header_meta_data;
    1015                 :            : 
    1016         [ +  - ]:        804 :     status = mcap::McapReader::ParseMetadata(header_record, &header_meta_data);
    1017                 :            : 
    1018         [ -  + ]:        804 :     if VUNLIKELY (!status.ok()) {
    1019                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1020                 :            :       wrapper_file->has_completed = false;
    1021                 :            :       CLOG_F("VCAPReader: Failed to parse header meta data, error = %s.", status.message.c_str());
    1022                 :            :       return false;
    1023                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1024                 :            :     }
    1025                 :            : 
    1026   [ +  -  +  - ]:        804 :     auto& tag_str = header_meta_data.metadata["tag"];
    1027   [ +  -  +  - ]:        804 :     auto& version_str = header_meta_data.metadata["version"];
    1028   [ +  -  +  - ]:        804 :     auto& compress_str = header_meta_data.metadata["compress"];
    1029   [ +  -  +  - ]:        804 :     auto& process_str = header_meta_data.metadata["process"];
    1030   [ +  -  +  - ]:        804 :     auto& date_str = header_meta_data.metadata["date"];
    1031   [ +  -  +  - ]:        804 :     auto& start_timestamp_str = header_meta_data.metadata["start_timestamp"];
    1032   [ +  -  +  - ]:        804 :     auto& timezone_str = header_meta_data.metadata["timezone"];
    1033                 :            : 
    1034         [ -  + ]:        804 :     if VUNLIKELY (version_str.empty()) {
    1035                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1036                 :            :       wrapper_file->has_completed = false;
    1037                 :            : 
    1038                 :            :       if (impl_->read_only) {
    1039                 :            :         CLOG_E("VCAPReader: Mcap [%s] cannot find version in header.", wrapper_file->path.c_str());
    1040                 :            :       }
    1041                 :            : 
    1042                 :            :       version_str = "0.0.0";
    1043                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1044                 :            :     } else {
    1045                 :        804 :       auto version = Version::from_string(version_str);
    1046                 :            : 
    1047         [ +  + ]:        804 :       if VUNLIKELY (!version.is_valid()) {
    1048                 :          1 :         wrapper_file->has_completed = false;
    1049                 :            : 
    1050         [ -  + ]:          1 :         if (impl_->read_only) {
    1051                 :            :           CLOG_E("VCAPReader: Mcap [%s] header version is invalid.",  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1052                 :            :                  wrapper_file->path.c_str());                         // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1053                 :            :         }
    1054                 :            :       } else {
    1055         [ +  + ]:        803 :         if VUNLIKELY (version.major != VLINK_VERSION_MAJOR) {
    1056                 :          1 :           wrapper_file->has_completed = false;
    1057   [ +  -  +  -  :          2 :           VLOG_F("VCAPReader: Mcap version is incompatible.");
                   -  + ]
    1058                 :            :           return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1059                 :            :         }
    1060                 :            :       }
    1061                 :            :     }
    1062                 :            : 
    1063         [ -  + ]:        803 :     if VUNLIKELY (compress_str.empty()) {
    1064                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1065                 :            :       wrapper_file->has_completed = false;
    1066                 :            : 
    1067                 :            :       if (impl_->read_only) {
    1068                 :            :         CLOG_E("VCAPReader: Mcap [%s] cannot find compress in header.", wrapper_file->path.c_str());
    1069                 :            :       }
    1070                 :            : 
    1071                 :            :       compress_str = "Unknown";
    1072                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1073                 :            :     }
    1074                 :            : 
    1075         [ -  + ]:        803 :     if VUNLIKELY (process_str.empty()) {
    1076                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1077                 :            :       wrapper_file->has_completed = false;
    1078                 :            : 
    1079                 :            :       if (impl_->read_only) {
    1080                 :            :         CLOG_E("VCAPReader: Mcap [%s] cannot find process in header.", wrapper_file->path.c_str());
    1081                 :            :       }
    1082                 :            : 
    1083                 :            :       process_str = "Unknown";
    1084                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1085                 :            :     }
    1086                 :            : 
    1087         [ -  + ]:        803 :     if VUNLIKELY (date_str.empty()) {
    1088                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1089                 :            :       wrapper_file->has_completed = false;
    1090                 :            : 
    1091                 :            :       if (impl_->read_only) {
    1092                 :            :         CLOG_E("VCAPReader: Mcap [%s] cannot find date in header.", wrapper_file->path.c_str());
    1093                 :            :       }
    1094                 :            : 
    1095                 :            :       date_str = "Unknown";
    1096                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1097                 :            :     }
    1098                 :            : 
    1099         [ -  + ]:        803 :     if VUNLIKELY (timezone_str.empty()) {
    1100                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1101                 :            :       wrapper_file->has_completed = false;
    1102                 :            : 
    1103                 :            :       if (impl_->read_only) {
    1104                 :            :         CLOG_E("VCAPReader: Mcap [%s] cannot find timezone in header.", wrapper_file->path.c_str());
    1105                 :            :         // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1106                 :            :       }
    1107                 :            :     }
    1108                 :            : 
    1109         [ -  + ]:        803 :     if (tag_str.empty()) {
    1110                 :            :       tag_str = "Empty";  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1111                 :            :     }
    1112                 :            : 
    1113         [ +  - ]:        803 :     impl_->info.tag_name = tag_str;
    1114         [ +  - ]:        803 :     impl_->info.version = version_str;
    1115         [ +  - ]:        803 :     impl_->info.storage_type = "vcap";
    1116                 :        803 :     impl_->info.message_count = statistics->messageCount;
    1117         [ +  - ]:        803 :     impl_->info.time_accuracy = "MicroSecond";
    1118         [ +  - ]:        803 :     impl_->info.compression_type = compress_str;
    1119         [ +  - ]:        803 :     impl_->info.process_name = process_str;
    1120         [ +  - ]:        803 :     impl_->info.date_time = date_str;
    1121                 :            : 
    1122                 :            :     try {
    1123         [ +  + ]:        803 :       impl_->info.start_timestamp = std::stoll(start_timestamp_str);
    1124         [ -  + ]:          1 :     } catch (std::exception&) {
    1125                 :          1 :       impl_->info.start_timestamp = Helpers::convert_date_to_timestamp(impl_->info.date_time) / 1000'000;
    1126                 :          1 :     }
    1127                 :            : 
    1128         [ +  + ]:        803 :     if VUNLIKELY (impl_->info.start_timestamp < 0) {
    1129                 :          1 :       impl_->info.start_timestamp = 0;
    1130                 :            : 
    1131         [ -  + ]:          1 :       if (impl_->read_only) {
    1132                 :            :         VLOG_E("VCAPReader: Invalid start_timestamp_ns.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1133                 :            :       }
    1134                 :            :     }
    1135                 :            : 
    1136                 :        803 :     int64_t timestamp_diff = 0;
    1137                 :            : 
    1138                 :        803 :     timestamp_diff = static_cast<int64_t>(statistics->messageStartTime / 1000'000) - impl_->info.start_timestamp;
    1139                 :            : 
    1140         [ +  + ]:        803 :     if (timestamp_diff < 0) {
    1141                 :          3 :       timestamp_diff = 0;
    1142                 :          3 :       impl_->info.start_timestamp = static_cast<int64_t>(statistics->messageStartTime / 1000'000);
    1143                 :            :     }
    1144                 :            : 
    1145                 :        803 :     impl_->info.blank_duration = timestamp_diff;
    1146                 :            : 
    1147                 :        803 :     timestamp_diff = static_cast<int64_t>(statistics->messageEndTime / 1000'000) - impl_->info.start_timestamp;
    1148                 :            : 
    1149         [ -  + ]:        803 :     if (timestamp_diff < 0) {
    1150                 :            :       timestamp_diff = 0;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1151                 :            :     }
    1152                 :            : 
    1153                 :        803 :     impl_->info.total_duration = timestamp_diff;
    1154                 :            : 
    1155                 :            :     try {
    1156         [ +  - ]:        803 :       impl_->info.timezone = std::stoi(timezone_str);
    1157                 :            :     } catch (std::exception&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1158                 :            :       impl_->info.timezone = 0;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1159                 :            :     }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1160         [ +  - ]:        804 :   }
    1161                 :            : 
    1162                 :            :   // read channel
    1163                 :            :   {
    1164                 :        803 :     int channel = 0;
    1165                 :        803 :     std::string channel_str;
    1166                 :            :     mcap::Record channel_record;
    1167                 :            : 
    1168         [ +  - ]:        803 :     url_ser_map().clear();
    1169         [ +  - ]:        803 :     url_schema_type_map().clear();
    1170                 :        803 :     impl_->info.url_metas.clear();
    1171                 :        803 :     impl_->raw_url_metas.clear();
    1172                 :            : 
    1173                 :        803 :     impl_->info.total_raw_size = 0;
    1174                 :            : 
    1175         [ +  + ]:       2446 :     for (const auto& [name, index] : meta_index) {
    1176         [ +  + ]:       1643 :       if (!Helpers::has_startwith(name, "VLinkChannel_")) {
    1177                 :        803 :         continue;
    1178                 :            :       }
    1179                 :            : 
    1180         [ +  - ]:        840 :       channel_str = name;
    1181   [ +  -  +  - ]:        840 :       Helpers::replace_string(channel_str, "VLinkChannel_", "");
    1182                 :            : 
    1183                 :            :       try {
    1184         [ +  - ]:        840 :         channel = std::stoi(channel_str);
    1185                 :            :       } catch (std::exception&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1186                 :            :         continue;                  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1187                 :            :       }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1188                 :            : 
    1189         [ +  - ]:        840 :       auto channel_count_iter = statistics->channelMessageCounts.find(channel);
    1190                 :            : 
    1191         [ -  + ]:        840 :       if VUNLIKELY (channel_count_iter == statistics->channelMessageCounts.end()) {
    1192                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1193                 :            :         wrapper_file->has_completed = false;
    1194                 :            : 
    1195                 :            :         if (impl_->read_only) {
    1196                 :            :           CLOG_E("VCAPReader: Mcap [%s] cannot read statistics in channel.", wrapper_file->path.c_str());
    1197                 :            :         }
    1198                 :            : 
    1199                 :            :         continue;
    1200                 :            :       }
    1201                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1202                 :            : 
    1203                 :        840 :       auto channel_msg_count = channel_count_iter->second;
    1204                 :            : 
    1205         [ +  - ]:        840 :       const auto& channel_ptr = reader->channel(channel);
    1206                 :            : 
    1207         [ -  + ]:        840 :       if VUNLIKELY (!channel_ptr) {
    1208                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1209                 :            :         wrapper_file->has_completed = false;
    1210                 :            : 
    1211                 :            :         if (impl_->read_only) {
    1212                 :            :           CLOG_E("VCAPReader: Mcap [%s] cannot find ptr in channel.", wrapper_file->path.c_str());
    1213                 :            :         }
    1214                 :            : 
    1215                 :            :         continue;
    1216                 :            :       }
    1217                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1218                 :            : 
    1219   [ +  -  +  - ]:        840 :       status = mcap::McapReader::ReadRecord(*reader->dataSource(), index.offset, &channel_record);
    1220                 :            : 
    1221         [ -  + ]:        840 :       if VUNLIKELY (!status.ok()) {
    1222                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1223                 :            :         wrapper_file->has_completed = false;
    1224                 :            : 
    1225                 :            :         if (impl_->read_only) {
    1226                 :            :           CLOG_E("VCAPReader: Failed to read channel record for index, error = %s.", status.message.c_str());
    1227                 :            :         }
    1228                 :            : 
    1229                 :            :         continue;
    1230                 :            :       }
    1231                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1232                 :            : 
    1233                 :        840 :       mcap::Metadata channel_meta_data;
    1234                 :            : 
    1235         [ +  - ]:        840 :       status = mcap::McapReader::ParseMetadata(channel_record, &channel_meta_data);
    1236                 :            : 
    1237         [ -  + ]:        840 :       if VUNLIKELY (!status.ok()) {
    1238                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1239                 :            :         wrapper_file->has_completed = false;
    1240                 :            : 
    1241                 :            :         if (impl_->read_only) {
    1242                 :            :           CLOG_E("VCAPReader: Failed to parse channel meta data, error = %s.", status.message.c_str());
    1243                 :            :         }
    1244                 :            : 
    1245                 :            :         continue;
    1246                 :            :       }
    1247                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1248                 :            : 
    1249                 :        840 :       Info::UrlMeta url_meta;
    1250                 :            : 
    1251   [ +  -  +  - ]:        840 :       auto& index_str = channel_meta_data.metadata["index"];
    1252   [ +  -  +  - ]:        840 :       auto& type_str = channel_meta_data.metadata["type"];
    1253   [ +  -  +  - ]:        840 :       auto& count_str = channel_meta_data.metadata["count"];
    1254   [ +  -  +  - ]:        840 :       auto& size_str = channel_meta_data.metadata["size"];
    1255   [ +  -  +  - ]:        840 :       auto& loss_str = channel_meta_data.metadata["loss"];
    1256   [ +  -  +  - ]:        840 :       auto& freq_str = channel_meta_data.metadata["freq"];
    1257   [ +  -  +  - ]:        840 :       auto ser_iter = channel_meta_data.metadata.find("ser");
    1258   [ +  -  +  - ]:        840 :       auto encoding_iter = channel_meta_data.metadata.find("encoding");
    1259   [ +  -  +  - ]:        840 :       auto action_iter = channel_meta_data.metadata.find("action");
    1260         [ +  - ]:        840 :       const auto& schema_ptr = reader->schema(channel_ptr->schemaId);
    1261                 :            : 
    1262                 :        840 :       int pindex = -1;
    1263                 :            : 
    1264                 :            :       try {
    1265         [ +  - ]:        840 :         pindex = std::stoi(index_str);
    1266                 :            :       } catch (std::exception&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1267                 :            :         pindex = -1;               // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1268                 :            :       }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1269                 :            : 
    1270         [ -  + ]:        840 :       if VUNLIKELY (pindex != channel - 1) {
    1271                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1272                 :            :         wrapper_file->has_completed = false;
    1273                 :            : 
    1274                 :            :         if (impl_->read_only) {
    1275                 :            :           CLOG_E("VCAPReader: Mcap [%s] channel index error.", wrapper_file->path.c_str());
    1276                 :            :         }
    1277                 :            : 
    1278                 :            :         continue;
    1279                 :            :       }
    1280                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1281                 :            : 
    1282                 :        840 :       url_meta.valid = true;
    1283                 :        840 :       url_meta.index = pindex;
    1284         [ +  - ]:        840 :       url_meta.url = channel_ptr->topic;
    1285         [ +  - ]:        840 :       url_meta.url_type = type_str;
    1286                 :        840 :       url_meta.schema_type = SchemaType::kUnknown;
    1287                 :            : 
    1288         [ +  - ]:        840 :       if (ser_iter != channel_meta_data.metadata.end()) {
    1289         [ +  - ]:        840 :         url_meta.ser_type = ser_iter->second;
    1290                 :            :       } else if (schema_ptr) {                 // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1291                 :            :         url_meta.ser_type = schema_ptr->name;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1292                 :            :       }
    1293                 :            : 
    1294         [ +  - ]:        840 :       if (encoding_iter != channel_meta_data.metadata.end()) {
    1295                 :        840 :         url_meta.schema_type =
    1296         [ +  + ]:        858 :             SchemaData::resolve_type(SchemaData::convert_encoding(encoding_iter->second), url_meta.ser_type,
    1297                 :        858 :                                      schema_ptr ? std::string_view(schema_ptr->encoding) : std::string_view{});
    1298                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1299                 :            :       } else if (schema_ptr) {
    1300                 :            :         url_meta.schema_type =
    1301                 :            :             SchemaData::resolve_type(SchemaData::convert_encoding(schema_ptr->encoding), url_meta.ser_type);
    1302                 :            :         // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1303                 :            :       }
    1304                 :            : 
    1305   [ +  +  +  -  :        840 :       if (schema_ptr && !schema_ptr->data.empty()) {
                   +  + ]
    1306                 :         18 :         wrapper_file->has_schema = true;
    1307                 :         18 :         impl_->info.has_schema = true;
    1308                 :            :       }
    1309                 :            : 
    1310                 :            :       try {
    1311         [ +  - ]:        840 :         url_meta.count = static_cast<size_t>(std::stoull(count_str));
    1312                 :            :       } catch (std::exception&) {            // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1313                 :            :         url_meta.count = channel_msg_count;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1314                 :            :       }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1315                 :            : 
    1316                 :            :       try {
    1317         [ +  - ]:        840 :         url_meta.loss = std::stod(loss_str);
    1318                 :            :       } catch (std::exception&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1319                 :            :         url_meta.loss = 0;         // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1320                 :            :       }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1321                 :            : 
    1322                 :            :       try {
    1323         [ +  - ]:        840 :         url_meta.size = static_cast<size_t>(std::stoull(size_str));
    1324                 :            :       } catch (std::exception&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1325                 :            :         url_meta.size = 0;         // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1326                 :            :       }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1327                 :            : 
    1328                 :            :       try {
    1329         [ +  - ]:        840 :         url_meta.freq = std::stod(freq_str);
    1330                 :            :       } catch (std::exception&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1331                 :            :         url_meta.freq = 0;         // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1332                 :            :       }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1333                 :            : 
    1334                 :        840 :       impl_->info.total_raw_size += url_meta.size;
    1335                 :            : 
    1336         [ +  - ]:        840 :       wrapper_file->id_to_url_map.emplace(url_meta.index, url_meta.url);
    1337         [ +  - ]:        840 :       wrapper_file->url_to_id_map.emplace(url_meta.url, url_meta.index);
    1338                 :            : 
    1339   [ +  -  +  - ]:        840 :       url_ser_map().emplace(url_meta.url, url_meta.ser_type);
    1340   [ +  -  +  - ]:        840 :       url_schema_type_map().emplace(url_meta.url, url_meta.schema_type);
    1341                 :            : 
    1342         [ +  - ]:        840 :       if (action_iter != channel_meta_data.metadata.end()) {
    1343         [ +  - ]:        840 :         url_meta.action_type = convert_action(action_iter->second);
    1344         [ +  - ]:        840 :         wrapper_file->channel_action_map.emplace(channel, url_meta.action_type);
    1345                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1346                 :            :       } else if (auto channel_action_iter = channel_ptr->metadata.find("action");
    1347                 :            :                  channel_action_iter != channel_ptr->metadata.end()) {
    1348                 :            :         url_meta.action_type = convert_action(channel_action_iter->second);
    1349                 :            :         wrapper_file->channel_action_map.emplace(channel, url_meta.action_type);
    1350                 :            :         // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1351                 :            :       } else {
    1352                 :            :         wrapper_file->channel_action_map.emplace(channel,                      // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1353                 :            :                                                  ActionType::kUnknownAction);  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1354                 :            :       }
    1355                 :            : 
    1356         [ +  - ]:        840 :       impl_->info.url_metas.emplace_back(std::move(url_meta));
    1357   [ +  -  +  -  :        840 :     }
             +  -  +  - ]
    1358                 :            : 
    1359                 :        803 :     wrapper_file->is_channel_broken = false;
    1360                 :            : 
    1361         [ +  + ]:        803 :     if (impl_->info.url_metas.empty()) {
    1362   [ +  -  -  + ]:          2 :       for (const auto& [id, pchannel] : reader->channels()) {
    1363                 :            :         // LCOV_EXCL_START GCOVR_EXCL_START
    1364                 :            :         wrapper_file->is_channel_broken = true;
    1365                 :            : 
    1366                 :            :         auto pschema_ptr = reader->schema(pchannel->schemaId);
    1367                 :            :         auto channel_count_iter = statistics->channelMessageCounts.find(id);
    1368                 :            : 
    1369                 :            :         Info::UrlMeta url_meta;
    1370                 :            : 
    1371                 :            :         url_meta.valid = true;
    1372                 :            :         url_meta.index = id - 1;
    1373                 :            :         url_meta.url = pchannel->topic;
    1374                 :            :         url_meta.url_type = "Event";
    1375                 :            : 
    1376                 :            :         if (pschema_ptr) {
    1377                 :            :           url_meta.ser_type = pschema_ptr->name;
    1378                 :            :           url_meta.schema_type =
    1379                 :            :               SchemaData::resolve_type(SchemaData::convert_encoding(pschema_ptr->encoding), url_meta.ser_type);
    1380                 :            : 
    1381                 :            :           if (!pschema_ptr->data.empty()) {
    1382                 :            :             wrapper_file->has_schema = true;
    1383                 :            :             impl_->info.has_schema = true;
    1384                 :            :           }
    1385                 :            :         }
    1386                 :            : 
    1387                 :            :         if (channel_count_iter != statistics->channelMessageCounts.end()) {
    1388                 :            :           url_meta.count = channel_count_iter->second;
    1389                 :            :         }
    1390                 :            : 
    1391                 :            :         wrapper_file->id_to_url_map.emplace(url_meta.index, url_meta.url);
    1392                 :            :         wrapper_file->url_to_id_map.emplace(url_meta.url, url_meta.index);
    1393                 :            : 
    1394                 :            :         url_ser_map().emplace(url_meta.url, url_meta.ser_type);
    1395                 :            :         url_schema_type_map().emplace(url_meta.url, url_meta.schema_type);
    1396                 :            : 
    1397                 :            :         if (auto action_iter = pchannel->metadata.find("action"); action_iter != pchannel->metadata.end()) {
    1398                 :            :           url_meta.action_type = convert_action(action_iter->second);
    1399                 :            :           wrapper_file->channel_action_map.emplace(id, url_meta.action_type);
    1400                 :            :           // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1401                 :            :         } else {
    1402                 :            :           wrapper_file->channel_action_map.emplace(id, ActionType::kUnknownAction);  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1403                 :            :         }
    1404                 :            : 
    1405                 :            :         impl_->info.url_metas.emplace_back(std::move(url_meta));  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1406                 :          2 :       }
    1407                 :            :     }
    1408                 :            : 
    1409         [ +  - ]:        803 :     std::sort(impl_->info.url_metas.begin(), impl_->info.url_metas.end());
    1410         [ +  - ]:        803 :     impl_->raw_url_metas = impl_->info.url_metas;
    1411         [ +  - ]:        803 :     rebuild_url_meta_lookup(impl_->info.url_metas);
    1412                 :        803 :   }
    1413                 :            : 
    1414                 :        803 :   impl_->info.has_idx_elapsed = false;
    1415                 :        803 :   impl_->info.has_idx_url = false;
    1416                 :            : 
    1417                 :        803 :   impl_->info.has_completed = wrapper_file->has_completed;
    1418                 :            : 
    1419                 :        803 :   return true;
    1420                 :        804 : }
    1421                 :            : 
    1422                 :        185 : void VCAPReader::open(const std::string& path) {
    1423         [ +  - ]:        185 :   close();
    1424                 :            : 
    1425                 :       1609 :   auto to_open = [this](Impl::WrapperFile& wrapper_file) -> bool {
    1426                 :        805 :     mcap::Status status;
    1427                 :            : 
    1428         [ +  - ]:        805 :     wrapper_file.reader = std::make_unique<mcap::McapReader>();
    1429                 :            : 
    1430         [ +  - ]:        805 :     status = wrapper_file.reader->open(wrapper_file.path);
    1431                 :            : 
    1432         [ +  + ]:        805 :     if VUNLIKELY (!status.ok()) {
    1433   [ +  -  +  -  :          2 :       CLOG_F("VCAPReader: Failed to open vcap, error = %s.", status.message.c_str());
                   -  + ]
    1434                 :            :       wrapper_file.reader.reset();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1435                 :            :       return false;                 // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1436                 :            :     }
    1437                 :            : 
    1438   [ +  +  -  + ]:        804 :     if VUNLIKELY (!prepare_file(&wrapper_file)) {
    1439                 :            :       wrapper_file.reader->close();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1440                 :            :       wrapper_file.reader.reset();   // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1441                 :            :       return false;                  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1442                 :            :     }
    1443                 :            : 
    1444                 :        803 :     return true;
    1445                 :        805 :   };
    1446                 :            : 
    1447         [ +  - ]:        185 :   impl_->path = path;
    1448                 :            : 
    1449                 :        185 :   impl_->total_start_timestamp_ns = -1;
    1450                 :            : 
    1451                 :        185 :   impl_->total_has_completed = true;
    1452                 :            : 
    1453                 :            :   try {
    1454                 :            : #ifdef _WIN32
    1455                 :            :     std::filesystem::path file_path(Helpers::string_to_wstring(path));
    1456                 :            : 
    1457                 :            :     impl_->info.file_name = Helpers::path_to_string(file_path.filename());
    1458                 :            : 
    1459                 :            :     impl_->info.file_size = 0;
    1460                 :            : 
    1461                 :            :     std::string suffix = Helpers::path_to_string(file_path.extension());
    1462                 :            : #else
    1463         [ +  - ]:        185 :     std::filesystem::path file_path(path);
    1464                 :            : 
    1465   [ +  -  +  - ]:        185 :     impl_->info.file_name = file_path.filename().string();
    1466                 :            : 
    1467                 :        185 :     impl_->info.file_size = 0;
    1468                 :            : 
    1469   [ +  -  +  - ]:        185 :     std::string suffix = file_path.extension().string();
    1470                 :            : #endif
    1471                 :            : 
    1472                 :        185 :     std::error_code exists_ec;
    1473                 :            : 
    1474         [ +  + ]:        185 :     if VUNLIKELY (!std::filesystem::exists(file_path, exists_ec)) {
    1475   [ +  -  +  -  :          2 :       CLOG_F("VCAPReader: Mcap [%s] does not exist.", path.c_str());
                   -  + ]
    1476                 :            :       return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1477                 :            :     }
    1478                 :            : 
    1479                 :        184 :     std::filesystem::path parent_path;
    1480                 :            : 
    1481                 :            :     try {
    1482         [ +  - ]:        184 :       parent_path = file_path.parent_path();
    1483                 :            :     } catch (std::filesystem::filesystem_error&) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1484                 :            :     }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1485                 :            : 
    1486                 :       1231 :     std::transform(suffix.begin(), suffix.end(), suffix.begin(), [](unsigned char c) { return std::tolower(c); });
    1487                 :            : 
    1488         [ +  + ]:        184 :     if (suffix == ".vcapx") {
    1489                 :            :       try {
    1490                 :        127 :         int64_t blank_duration = -1;
    1491                 :            : 
    1492                 :        127 :         nlohmann::json root_json;
    1493                 :            : 
    1494                 :            :         {
    1495         [ +  - ]:        127 :           std::ifstream filex(file_path);
    1496                 :            : 
    1497         [ +  + ]:        127 :           filex >> root_json;
    1498                 :            : 
    1499         [ +  - ]:        126 :           filex.close();
    1500                 :        127 :         }
    1501                 :            : 
    1502   [ +  -  +  - ]:        126 :         nlohmann::json header_json = root_json["VLinkHeader"];
    1503   [ +  -  +  - ]:        126 :         nlohmann::json urls_json = root_json["VLinkUrls"];
    1504   [ +  -  +  - ]:        126 :         nlohmann::json files_json = root_json["VLinkFiles"];
    1505                 :            : 
    1506                 :        126 :         impl_->info.file_size = 0;
    1507                 :            : 
    1508                 :        126 :         int file_index = 0;
    1509                 :            : 
    1510         [ +  + ]:        126 :         if (!files_json.empty()) {
    1511         [ +  - ]:        125 :           impl_->file_list.reserve(files_json.size());
    1512                 :            : 
    1513                 :        125 :           impl_->info.has_idx_elapsed = true;
    1514                 :        125 :           impl_->info.has_idx_url = true;
    1515                 :        125 :           impl_->info.has_schema = false;
    1516                 :            :         } else {
    1517                 :          1 :           impl_->info.has_idx_elapsed = false;
    1518                 :          1 :           impl_->info.has_idx_url = false;
    1519                 :          1 :           impl_->info.has_schema = false;
    1520                 :            :         }
    1521                 :            : 
    1522                 :        126 :         std::filesystem::path file_db;
    1523                 :        126 :         std::string file_db_str;
    1524                 :            : 
    1525   [ +  -  +  -  :        874 :         for (const auto& file_info : files_json) {
             +  -  +  + ]
    1526                 :            : #ifdef _WIN32
    1527                 :            : 
    1528                 :            :           if (parent_path.empty()) {
    1529                 :            :             file_db = std::filesystem::path(Helpers::string_to_wstring(file_info));
    1530                 :            :           } else {
    1531                 :            :             file_db = parent_path / std::filesystem::path(Helpers::string_to_wstring(file_info));
    1532                 :            :           }
    1533                 :            : #else
    1534                 :            : 
    1535         [ +  + ]:        749 :           if (parent_path.empty()) {
    1536         [ +  - ]:          6 :             file_db = std::filesystem::path(file_info);
    1537                 :            :           } else {
    1538   [ +  -  +  - ]:        743 :             file_db = parent_path / std::filesystem::path(file_info);
    1539                 :            :           }
    1540                 :            : #endif
    1541                 :            : 
    1542         [ +  - ]:        749 :           file_db_str = file_db.string();
    1543                 :            : 
    1544                 :        749 :           std::error_code db_exists_ec;
    1545                 :            : 
    1546         [ +  + ]:        749 :           if VUNLIKELY (!std::filesystem::exists(file_db, db_exists_ec)) {
    1547   [ +  -  +  -  :          2 :             CLOG_F("VCAPReader: Mcap [%s] does not exist.", file_db_str.c_str());
                   -  + ]
    1548                 :            :             return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1549                 :            :           }
    1550                 :            : 
    1551         [ +  - ]:        748 :           Impl::WrapperFile wrapper_file;
    1552         [ +  - ]:        748 :           wrapper_file.path = file_db_str;
    1553                 :        748 :           wrapper_file.index = file_index;
    1554                 :            : 
    1555   [ +  -  -  + ]:        748 :           if VUNLIKELY (!to_open(wrapper_file)) {
    1556                 :            :             CLOG_W("VCAPReader: Skipping invalid vcap [%s].", file_db_str.c_str());  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1557                 :            :             continue;                                                                // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1558                 :            :           }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1559                 :            : 
    1560         [ +  - ]:        748 :           if (!wrapper_file.has_idx_elapsed) {
    1561                 :        748 :             impl_->info.has_idx_elapsed = false;
    1562                 :            :           }
    1563                 :            : 
    1564         [ +  - ]:        748 :           if (!wrapper_file.has_idx_url) {
    1565                 :        748 :             impl_->info.has_idx_url = false;
    1566                 :            :           }
    1567                 :            : 
    1568         [ +  + ]:        748 :           if (wrapper_file.has_schema) {
    1569                 :          2 :             impl_->info.has_schema = true;
    1570                 :            :           }
    1571                 :            : 
    1572                 :        748 :           std::error_code db_size_ec;
    1573                 :        748 :           std::uintmax_t file_size = std::filesystem::file_size(file_db, db_size_ec);
    1574                 :            : 
    1575         [ -  + ]:        748 :           if VUNLIKELY (db_size_ec) {
    1576                 :            :             CLOG_W("VCAPReader: file_size failed for [%s]: %s.", file_db_str.c_str(),  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1577                 :            :                    db_size_ec.message().c_str());                                      // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1578                 :            :             file_size = 0;                                                             // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1579                 :            :           }
    1580                 :            : 
    1581                 :        748 :           impl_->info.file_size += file_size;
    1582                 :            : 
    1583                 :        748 :           wrapper_file.start_timestamp_ns = impl_->info.start_timestamp * 1000'000;
    1584                 :        748 :           wrapper_file.begin = impl_->info.blank_duration;
    1585                 :        748 :           wrapper_file.end = impl_->info.total_duration;
    1586                 :            : 
    1587         [ +  + ]:        748 :           if (impl_->total_start_timestamp_ns < 0) {
    1588                 :        124 :             impl_->total_start_timestamp_ns = wrapper_file.start_timestamp_ns;
    1589                 :            :           }
    1590                 :            : 
    1591         [ -  + ]:        748 :           if (!wrapper_file.has_completed) {
    1592                 :            :             impl_->total_has_completed = false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1593                 :            :           }
    1594                 :            : 
    1595   [ +  +  +  +  :        748 :           if (blank_duration < 0 || impl_->info.blank_duration < blank_duration) {
                   +  + ]
    1596                 :        125 :             blank_duration = impl_->info.blank_duration;
    1597                 :            :           }
    1598                 :            : 
    1599         [ +  - ]:        748 :           impl_->file_list.emplace_back(std::move(wrapper_file));
    1600                 :        748 :           ++file_index;
    1601         [ +  - ]:        748 :         }
    1602                 :            : 
    1603                 :        125 :         impl_->info.split_count = impl_->file_list.size();
    1604                 :            : 
    1605         [ +  + ]:        125 :         if VUNLIKELY (impl_->file_list.empty()) {
    1606   [ +  -  +  -  :          2 :           VLOG_F("VCAPReader: DB list is empty.");
                   -  + ]
    1607                 :            :           return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1608                 :            :         }
    1609                 :            : 
    1610   [ +  -  +  - ]:        124 :         int version_major = header_json["major"];
    1611   [ +  -  +  - ]:        124 :         int version_minor = header_json["minor"];
    1612   [ +  -  +  - ]:        124 :         int version_patch = header_json["patch"];
    1613                 :            : 
    1614                 :        124 :         impl_->info.version =
    1615   [ +  -  +  -  :        248 :             std::to_string(version_major) + "." + std::to_string(version_minor) + "." + std::to_string(version_patch);
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    1616         [ +  - ]:        124 :         impl_->info.storage_type = "vcap";
    1617   [ +  -  +  - ]:        124 :         impl_->info.message_count = header_json["count"];
    1618   [ +  -  +  - ]:        124 :         impl_->info.total_duration = header_json["duration"];
    1619                 :        124 :         impl_->info.total_duration /= 1000U;
    1620   [ +  -  +  - ]:        124 :         impl_->info.time_accuracy = header_json["accuracy"];
    1621   [ +  -  +  - ]:        124 :         impl_->info.compression_type = header_json["compress"];
    1622   [ +  -  +  - ]:        124 :         impl_->info.process_name = header_json["process"];
    1623   [ +  -  +  - ]:        124 :         impl_->info.date_time = header_json["date"];
    1624                 :            : 
    1625   [ +  -  +  + ]:        124 :         if (header_json.contains("start_timestamp")) {
    1626   [ +  -  +  - ]:        123 :           impl_->info.start_timestamp = header_json["start_timestamp"];
    1627                 :            :         } else {
    1628                 :          1 :           impl_->info.start_timestamp = Helpers::convert_date_to_timestamp(impl_->info.date_time) / 1000'000;
    1629                 :            :         }
    1630                 :            : 
    1631         [ +  + ]:        124 :         if VUNLIKELY (impl_->info.start_timestamp < 0) {
    1632                 :          1 :           impl_->info.start_timestamp = 0;
    1633                 :            : 
    1634         [ +  - ]:          1 :           if (impl_->read_only) {
    1635   [ +  -  +  -  :          2 :             VLOG_E("VCAPReader: Invalid start_timestamp.");
                   +  - ]
    1636                 :            :           }
    1637                 :            :         }
    1638                 :            : 
    1639   [ +  -  +  + ]:        124 :         if (header_json.contains("tag")) {
    1640   [ +  -  +  - ]:        123 :           impl_->info.tag_name = header_json["tag"];
    1641                 :            :         } else {
    1642         [ +  - ]:          1 :           impl_->info.tag_name = "Empty";
    1643                 :            :         }
    1644                 :            : 
    1645   [ +  -  +  + ]:        124 :         if (header_json.contains("complete")) {
    1646   [ +  -  +  - ]:        123 :           impl_->info.has_completed = header_json["complete"];
    1647                 :            :         } else {
    1648                 :          1 :           impl_->info.has_completed = true;
    1649                 :            :         }
    1650                 :            : 
    1651   [ +  -  +  + ]:        124 :         if (header_json.contains("timezone")) {
    1652   [ +  -  +  - ]:        123 :           impl_->info.timezone = header_json["timezone"];
    1653                 :            :         } else {
    1654                 :          1 :           impl_->info.timezone = 480;
    1655                 :            :         }
    1656                 :            : 
    1657   [ +  -  +  + ]:        124 :         if (header_json.contains("split_by_size")) {
    1658   [ +  -  +  - ]:        123 :           impl_->info.split_by_size = header_json["split_by_size"];
    1659                 :            :         }
    1660                 :            : 
    1661   [ +  -  +  + ]:        124 :         if (header_json.contains("split_by_time")) {
    1662   [ +  -  +  - ]:        123 :           impl_->info.split_by_time = header_json["split_by_time"];
    1663                 :            :         }
    1664                 :            : 
    1665                 :        124 :         impl_->info.blank_duration = blank_duration;
    1666                 :            : 
    1667   [ +  +  +  + ]:        251 :         if (impl_->info.compression_type.empty() || impl_->info.compression_type == "None" ||
    1668   [ +  -  +  +  :        251 :             impl_->info.compression_type == "NONE" || impl_->info.compression_type == "none") {
                   +  + ]
    1669                 :        123 :           impl_->enable_compress = false;
    1670                 :            :         } else {
    1671                 :          1 :           impl_->enable_compress = true;
    1672                 :            :         }
    1673                 :            : 
    1674         [ +  + ]:        124 :         if VUNLIKELY (impl_->info.time_accuracy != "MicroSecond") {
    1675   [ +  -  +  -  :          2 :           VLOG_F("VCAPReader: MCAP accuracy is not supported.");
                   -  + ]
    1676                 :            :           return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1677                 :            :         }
    1678                 :            : 
    1679         [ +  - ]:        123 :         url_ser_map().clear();
    1680         [ +  - ]:        123 :         url_schema_type_map().clear();
    1681                 :        123 :         impl_->info.url_metas.clear();
    1682                 :        123 :         impl_->raw_url_metas.clear();
    1683                 :            : 
    1684         [ +  - ]:        123 :         impl_->info.url_metas.reserve(urls_json.size());
    1685                 :            : 
    1686                 :        123 :         impl_->info.total_raw_size = 0;
    1687                 :            : 
    1688   [ +  -  +  -  :        246 :         for (const auto& url_info : urls_json) {
             +  -  +  + ]
    1689                 :        123 :           Info::UrlMeta url_meta;
    1690                 :            : 
    1691                 :        123 :           url_meta.valid = true;
    1692   [ +  -  +  - ]:        123 :           url_meta.index = url_info["index"];
    1693   [ +  -  +  - ]:        123 :           url_meta.url = url_info["url"];
    1694   [ +  -  +  - ]:        123 :           url_meta.url_type = url_info["type"];
    1695                 :            : 
    1696   [ +  -  +  + ]:        123 :           if (url_info.contains("action")) {
    1697   [ +  -  +  -  :        122 :             url_meta.action_type = convert_action(url_info["action"].get<std::string>());
                   +  - ]
    1698                 :            :           }
    1699                 :            : 
    1700   [ +  -  +  - ]:        123 :           url_meta.ser_type = url_info["ser"];
    1701                 :            : 
    1702   [ +  -  +  + ]:        123 :           if (url_info.contains("encoding")) {
    1703                 :        244 :             url_meta.schema_type = SchemaData::resolve_type(
    1704   [ +  -  +  - ]:        244 :                 SchemaData::convert_encoding(url_info["encoding"].get<std::string>()), url_meta.ser_type);
    1705                 :            :           } else {
    1706                 :          1 :             url_meta.schema_type = SchemaType::kUnknown;
    1707                 :            :           }
    1708                 :            : 
    1709   [ +  -  +  - ]:        123 :           url_meta.count = url_info["count"];
    1710   [ +  -  +  - ]:        123 :           url_meta.loss = url_info["loss"];
    1711                 :            : 
    1712   [ +  -  +  + ]:        123 :           if (url_info.contains("size")) {
    1713   [ +  -  +  - ]:        122 :             url_meta.size = url_info["size"];
    1714                 :            :           }
    1715                 :            : 
    1716   [ +  -  +  + ]:        123 :           if (url_info.contains("freq")) {
    1717   [ +  -  +  - ]:        122 :             url_meta.freq = url_info["freq"];
    1718                 :            :           }
    1719                 :            : 
    1720                 :        123 :           impl_->info.total_raw_size += url_meta.size;
    1721                 :            : 
    1722   [ +  -  +  - ]:        123 :           url_ser_map().emplace(url_meta.url, url_meta.ser_type);
    1723   [ +  -  +  - ]:        123 :           url_schema_type_map().emplace(url_meta.url, url_meta.schema_type);
    1724         [ +  - ]:        123 :           impl_->info.url_metas.emplace_back(std::move(url_meta));
    1725                 :        123 :         }
    1726                 :            : 
    1727         [ +  - ]:        123 :         std::sort(impl_->info.url_metas.begin(), impl_->info.url_metas.end());
    1728         [ +  - ]:        123 :         impl_->raw_url_metas = impl_->info.url_metas;
    1729         [ +  - ]:        123 :         rebuild_url_meta_lookup(impl_->info.url_metas);
    1730   [ +  -  +  -  :        146 :       } catch (nlohmann::json::exception& e) {
          +  -  +  -  +  
             -  +  -  +  
                      + ]
    1731   [ +  -  +  -  :          2 :         VLOG_F("VCAPReader: JSON parse error, ", e.what(), ".");
                   -  + ]
    1732                 :            :         return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1733                 :          1 :       }
    1734                 :            :     } else {
    1735         [ +  - ]:         57 :       Impl::WrapperFile wrapper_file;
    1736         [ +  - ]:         57 :       wrapper_file.path = impl_->path;
    1737                 :            : 
    1738   [ +  +  -  + ]:         57 :       if VUNLIKELY (!to_open(wrapper_file)) {
    1739                 :            :         CLOG_F("VCAPReader: Failed to prepare vcap [%s].", path.c_str());  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1740                 :            :         return;                                                            // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1741                 :            :       }
    1742                 :            : 
    1743                 :         55 :       impl_->info.file_size = 0;
    1744                 :            : 
    1745                 :         55 :       std::error_code single_size_ec;
    1746                 :         55 :       std::uintmax_t file_size = std::filesystem::file_size(file_path, single_size_ec);
    1747                 :            : 
    1748         [ -  + ]:         55 :       if VUNLIKELY (single_size_ec) {
    1749                 :            :         CLOG_W("VCAPReader: file_size failed for [%s]: %s.", path.c_str(),  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1750                 :            :                single_size_ec.message().c_str());                           // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1751                 :            :         file_size = 0;                                                      // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1752                 :            :       }
    1753                 :            : 
    1754                 :         55 :       impl_->info.file_size += file_size;
    1755                 :            : 
    1756                 :         55 :       wrapper_file.start_timestamp_ns = impl_->info.start_timestamp * 1000'000;
    1757                 :         55 :       wrapper_file.begin = impl_->info.blank_duration;
    1758                 :         55 :       wrapper_file.end = impl_->info.total_duration;
    1759                 :            : 
    1760         [ +  - ]:         55 :       if (impl_->total_start_timestamp_ns < 0) {
    1761                 :         55 :         impl_->total_start_timestamp_ns = wrapper_file.start_timestamp_ns;
    1762                 :            :       }
    1763                 :            : 
    1764         [ +  + ]:         55 :       if (!wrapper_file.has_completed) {
    1765                 :          1 :         impl_->total_has_completed = false;
    1766                 :            :       }
    1767                 :            : 
    1768         [ +  - ]:         55 :       impl_->file_list.emplace_back(std::move(wrapper_file));
    1769         [ +  - ]:         57 :     }
    1770   [ +  -  +  -  :        205 :   } catch (std::filesystem::filesystem_error& e) {
             +  -  +  - ]
    1771                 :            :     VLOG_F("VCAPReader: Filesystem error, ", e.what(), ".");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1772                 :            :     return;                                                   // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1773                 :            :   }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1774                 :            : }
    1775                 :            : 
    1776                 :        363 : void VCAPReader::close() {
    1777                 :        363 :   impl_->cursor_iter.reset();
    1778                 :        363 :   impl_->cursor_iter_end.reset();
    1779                 :        363 :   impl_->cursor_msg_view.reset();
    1780                 :            : 
    1781         [ +  + ]:       1164 :   for (auto& wrapper_file : impl_->file_list) {
    1782         [ +  - ]:        801 :     if (wrapper_file.reader) {
    1783         [ +  - ]:        801 :       wrapper_file.reader->close();
    1784                 :        801 :       wrapper_file.reader.reset();
    1785                 :            :     }
    1786                 :            :   }
    1787                 :            : 
    1788                 :        363 :   impl_->file_list.clear();
    1789                 :        363 : }
    1790                 :            : 
    1791                 :         39 : int VCAPReader::get_reset_index(const Config& config) {
    1792                 :         39 :   impl_->is_pending.store(true, std::memory_order_relaxed);
    1793                 :            : 
    1794                 :            :   // LCOV_EXCL_START GCOVR_EXCL_START
    1795                 :            :   auto status_function = [](const mcap::Status& status) {
    1796                 :            :     if (!status.ok()) {
    1797                 :            :       CLOG_W("VCAPReader: Failed to read message, error = %s.", status.message.c_str());
    1798                 :            :     }
    1799                 :            :   };
    1800                 :            :   // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1801                 :            : 
    1802                 :         12 :   auto filter_function = [this, &config](std::string_view url) -> bool {
    1803                 :         12 :     return match_playback_url_filter(url, config.filter_urls);
    1804                 :         39 :   };
    1805                 :            : 
    1806                 :         39 :   int start_index = -1;
    1807                 :            : 
    1808                 :         39 :   int64_t last_time = impl_->begin_time.load(std::memory_order_relaxed);
    1809                 :            : 
    1810                 :         39 :   mcap::ReadMessageOptions read_options;
    1811                 :         39 :   const auto end_time = get_read_end_time(impl_->total_start_timestamp_ns, config.end_time);
    1812                 :            : 
    1813         [ +  + ]:         91 :   for (auto& wrapper_file : impl_->file_list) {
    1814   [ +  +  +  -  :        134 :     if (start_index < 0 && impl_->begin_time.load(std::memory_order_relaxed) >= last_time &&
                   +  + ]
    1815         [ +  + ]:         82 :         impl_->begin_time.load(std::memory_order_relaxed) <= wrapper_file.end) {
    1816         [ +  + ]:         78 :       if (impl_->begin_time.load(std::memory_order_relaxed) > 0) {
    1817                 :          7 :         read_options.startTime =
    1818                 :         14 :             impl_->begin_time.load(std::memory_order_relaxed) * 1000'000 + impl_->total_start_timestamp_ns;
    1819                 :          7 :         read_options.endTime = mcap::MaxTime;
    1820   [ +  -  -  +  :          7 :         if (!config.filter_urls.empty() || has_playback_url_rules()) {
                   -  + ]
    1821                 :          0 :           read_options.topicFilter = filter_function;
    1822                 :            :         }
    1823                 :          7 :         read_options.readOrder = mcap::ReadMessageOptions::ReadOrder::FileOrder;
    1824                 :            :       } else {
    1825                 :         32 :         read_options.startTime = 0;
    1826                 :         32 :         read_options.endTime = mcap::MaxTime;
    1827   [ +  +  -  +  :         32 :         if (!config.filter_urls.empty() || has_playback_url_rules()) {
                   +  + ]
    1828                 :          5 :           read_options.topicFilter = filter_function;
    1829                 :            :         }
    1830                 :         32 :         read_options.readOrder = mcap::ReadMessageOptions::ReadOrder::FileOrder;
    1831                 :            :       }
    1832                 :            : 
    1833                 :         39 :       start_index = wrapper_file.index;
    1834                 :            :     } else {
    1835                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1836                 :            :       read_options.startTime = 0;
    1837                 :            :       read_options.endTime = mcap::MaxTime;
    1838                 :            :       if (!config.filter_urls.empty() || has_playback_url_rules()) {
    1839                 :            :         read_options.topicFilter = filter_function;
    1840                 :            :       }
    1841                 :            :       read_options.readOrder = mcap::ReadMessageOptions::ReadOrder::FileOrder;
    1842                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1843                 :            :     }
    1844                 :            : 
    1845         [ +  - ]:         52 :     const auto [start_offset, end_offset] = wrapper_file.reader->byteRange(read_options.startTime, end_time);
    1846                 :            : 
    1847                 :            :     // NOLINTNEXTLINE(readability-redundant-smartptr-get)
    1848         [ +  - ]:        104 :     wrapper_file.msg_view = std::make_unique<mcap::LinearMessageView>(*wrapper_file.reader.get(), read_options,
    1849                 :         52 :                                                                       start_offset, end_offset, status_function);
    1850                 :            : 
    1851         [ +  + ]:         52 :     if (start_offset == end_offset) {
    1852                 :            :       wrapper_file.msg_view_begin.emplace(wrapper_file.msg_view->end());  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1853                 :            :       wrapper_file.msg_view_end.emplace(wrapper_file.msg_view->end());    // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1854                 :            :     } else {
    1855         [ +  - ]:         50 :       wrapper_file.msg_view_begin.emplace(wrapper_file.msg_view->begin());
    1856         [ +  - ]:         50 :       wrapper_file.msg_view_end.emplace(wrapper_file.msg_view->end());
    1857                 :            :     }
    1858                 :            : 
    1859                 :         52 :     last_time = wrapper_file.end;
    1860                 :            :   }
    1861                 :            : 
    1862                 :         39 :   impl_->is_pending.store(false, std::memory_order_relaxed);
    1863                 :            : 
    1864                 :         39 :   return start_index;
    1865                 :         39 : }
    1866                 :            : 
    1867                 :         37 : void VCAPReader::read(const Config& config) {
    1868                 :         37 :   int loop_times = 0;
    1869                 :            : 
    1870                 :         37 :   reset_plugin();
    1871                 :            : 
    1872         [ +  + ]:         37 :   if (config.auto_pause) {
    1873                 :          5 :     impl_->pause_flag.store(true, std::memory_order_relaxed);
    1874                 :            :   }
    1875                 :            : 
    1876                 :         37 :   bool is_interrupted = false;
    1877                 :            : 
    1878                 :            :   do {
    1879                 :            :     // prepare
    1880         [ +  - ]:         39 :     int start_index = get_reset_index(config);
    1881                 :            : 
    1882         [ +  + ]:         39 :     if (impl_->ready_callback) {
    1883         [ +  - ]:         13 :       impl_->ready_callback();
    1884                 :            :     }
    1885                 :            : 
    1886   [ +  -  -  +  :         39 :     if VUNLIKELY (start_index < 0 || start_index > static_cast<int>(impl_->file_list.size()) - 1) {
                   -  + ]
    1887                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1888                 :            :       VLOG_W("VCAPReader: Cannot find any data for play.");
    1889                 :            : 
    1890                 :            :       update_status(kStopped);
    1891                 :            : 
    1892                 :            :       if (config.auto_quit) {
    1893                 :            :         quit();
    1894                 :            :       }
    1895                 :            : 
    1896                 :            :       return;
    1897                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1898                 :            :     }
    1899                 :            : 
    1900                 :            :     {
    1901         [ +  - ]:         39 :       std::lock_guard time_lock(impl_->time_mtx);
    1902                 :         39 :       impl_->pause_elapsed.store(0, std::memory_order_relaxed);
    1903                 :         39 :       impl_->offset_elapsed.store(0, std::memory_order_relaxed);
    1904                 :         78 :       impl_->real_elapsed.store(impl_->begin_time.load(std::memory_order_relaxed) * 1000U, std::memory_order_relaxed);
    1905                 :            : 
    1906                 :         39 :       impl_->elapsed_timer.restart();
    1907                 :         39 :       impl_->pause_elapsed_timer.restart();
    1908                 :         39 :       impl_->offset_timer.restart();
    1909                 :         39 :       impl_->real_timer.restart();
    1910                 :         39 :     }
    1911                 :            : 
    1912         [ +  + ]:         39 :     if (impl_->stop_flag.load(std::memory_order_relaxed)) {
    1913                 :          1 :       is_interrupted = true;
    1914         [ +  - ]:          1 :       update_status(kStopped);
    1915                 :          9 :       break;
    1916         [ +  + ]:         38 :     } else if (impl_->jump_flag.load(std::memory_order_relaxed)) {
    1917                 :          2 :       break;
    1918         [ +  + ]:         36 :     } else if (impl_->pause_flag.load(std::memory_order_relaxed)) {
    1919                 :          5 :       impl_->pause_elapsed_timer.restart();
    1920         [ +  - ]:          5 :       update_status(kPaused);
    1921         [ +  - ]:          5 :       do_pause();
    1922                 :            :       {
    1923         [ +  - ]:          5 :         std::lock_guard time_lock(impl_->time_mtx);
    1924                 :          5 :         impl_->pause_elapsed.store(0, std::memory_order_relaxed);
    1925                 :          5 :         impl_->offset_elapsed.store(0, std::memory_order_relaxed);
    1926                 :         10 :         impl_->real_elapsed.store(impl_->begin_time.load(std::memory_order_relaxed) * 1000U, std::memory_order_relaxed);
    1927                 :            : 
    1928                 :          5 :         impl_->elapsed_timer.restart();
    1929                 :          5 :         impl_->pause_elapsed_timer.restart();
    1930                 :          5 :         impl_->offset_timer.restart();
    1931                 :          5 :         impl_->real_timer.restart();
    1932                 :          5 :       }
    1933                 :            : 
    1934                 :            :     } else {
    1935         [ +  - ]:         31 :       update_status(kPlaying);
    1936                 :            :     }
    1937                 :            : 
    1938                 :         36 :     int64_t elapsed = 0;
    1939                 :         36 :     int64_t timestamp = 0;
    1940                 :         36 :     int64_t last_timestamp = 0;
    1941                 :         36 :     const uint8_t* data = nullptr;
    1942                 :         36 :     size_t size = 0;
    1943                 :            : 
    1944                 :            :     // process files
    1945         [ +  + ]:         79 :     for (int index = start_index; index < static_cast<int>(impl_->file_list.size()); ++index) {
    1946                 :         47 :       impl_->split_index.store(index, std::memory_order_relaxed);
    1947                 :            : 
    1948         [ +  - ]:         94 :       auto& wrapper_file = impl_->file_list.at(impl_->split_index.load(std::memory_order_relaxed));
    1949                 :            : 
    1950         [ -  + ]:         47 :       if VUNLIKELY (!wrapper_file.reader) {
    1951                 :            :         VLOG_W("VCAPReader: Target vcap reader is empty.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1952                 :            :         return;                                              // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1953                 :            :       }
    1954                 :            : 
    1955                 :            :       // process datas
    1956         [ +  - ]:         47 :       auto iter = std::move(wrapper_file.msg_view_begin).value();    // NOLINT
    1957         [ +  - ]:         47 :       auto iter_end = std::move(wrapper_file.msg_view_end).value();  // NOLINT
    1958                 :            : 
    1959   [ +  -  +  -  :        153 :       for (; iter != iter_end; iter++) {
                   +  + ]
    1960         [ +  - ]:        110 :         timestamp = (static_cast<int64_t>(iter->message.logTime) - impl_->total_start_timestamp_ns) / 1000;
    1961                 :            : 
    1962         [ -  + ]:        110 :         if VUNLIKELY (last_timestamp > timestamp + 10'000U) {
    1963                 :            :           VLOG_W("VCAPReader: The vcap timestamp is incorrect.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1964                 :            :         }
    1965                 :            : 
    1966                 :        110 :         last_timestamp = timestamp;
    1967                 :            : 
    1968         [ -  + ]:        220 :         if (timestamp < impl_->begin_time.load(std::memory_order_relaxed) * 1000U) {
    1969                 :            :           continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1970                 :            :         }
    1971                 :            : 
    1972   [ +  +  +  + ]:        110 :         if (config.end_time > 0 && timestamp > config.end_time * 1000U) {
    1973   [ +  -  +  -  :         24 :           if (impl_->stop_flag.load(std::memory_order_relaxed) || impl_->jump_flag.load(std::memory_order_relaxed) ||
                   -  + ]
    1974   [ +  -  -  + ]:         12 :               is_ready_to_quit()) {
    1975                 :          0 :             is_interrupted = true;
    1976                 :          4 :             break;
    1977                 :            :           }
    1978                 :            : 
    1979                 :         12 :           continue;
    1980                 :            :         }
    1981                 :            : 
    1982         [ +  - ]:         98 :         data = reinterpret_cast<const uint8_t*>(iter->message.data);
    1983                 :            : 
    1984   [ +  -  -  + ]:         98 :         if VUNLIKELY (iter->message.dataSize > static_cast<uint64_t>(std::numeric_limits<size_t>::max())) {
    1985                 :            :           // LCOV_EXCL_START GCOVR_EXCL_START
    1986                 :            :           CLOG_W("VCAPReader: Message data size is too large to address, size = %" PRIu64 ".",
    1987                 :            :                  static_cast<uint64_t>(iter->message.dataSize));
    1988                 :            :           continue;
    1989                 :            :         }
    1990                 :            :         // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1991                 :            : 
    1992         [ +  - ]:         98 :         size = static_cast<size_t>(iter->message.dataSize);
    1993                 :            : 
    1994                 :         98 :         elapsed =
    1995                 :         98 :             (timestamp / impl_->rate.load(std::memory_order_relaxed)) -
    1996                 :         98 :             (impl_->elapsed_timer.get() - impl_->pause_elapsed.load(std::memory_order_relaxed)) -
    1997                 :        196 :             (impl_->begin_time.load(std::memory_order_relaxed) * 1000U / impl_->rate.load(std::memory_order_relaxed));
    1998                 :            : 
    1999                 :            :         {
    2000         [ +  - ]:         98 :           std::unique_lock lock(impl_->mtx);
    2001                 :            : 
    2002         [ +  + ]:         98 :           if (config.force_delay > 0) {
    2003         [ +  - ]:         35 :             impl_->cv.wait_for(lock, std::chrono::milliseconds(config.force_delay), [this]() -> bool {
    2004         [ +  - ]:        140 :               return impl_->stop_flag.load(std::memory_order_relaxed) ||
    2005         [ +  + ]:        138 :                      impl_->pause_next_flag.load(std::memory_order_relaxed) ||
    2006   [ +  +  -  + ]:        209 :                      impl_->jump_flag.load(std::memory_order_relaxed) || is_ready_to_quit();
    2007                 :            :             });
    2008   [ +  +  +  - ]:         63 :           } else if (config.force_delay < 0 && elapsed > 0) {
    2009                 :          1 :             impl_->offset_timer.restart();
    2010                 :            : 
    2011         [ +  - ]:          1 :             impl_->cv.wait_for(lock, std::chrono::microseconds(elapsed), [this]() -> bool {
    2012         [ +  - ]:          4 :               return impl_->stop_flag.load(std::memory_order_relaxed) ||
    2013         [ +  - ]:          4 :                      impl_->pause_next_flag.load(std::memory_order_relaxed) ||
    2014         [ +  - ]:          4 :                      impl_->jump_flag.load(std::memory_order_relaxed) ||
    2015   [ +  -  -  + ]:          6 :                      impl_->pause_flag.load(std::memory_order_relaxed) || is_ready_to_quit();
    2016                 :            :             });
    2017                 :            : 
    2018         [ -  + ]:          1 :             if VUNLIKELY (impl_->pause_flag.load(std::memory_order_relaxed)) {
    2019                 :            :               impl_->offset_elapsed.store(elapsed - impl_->offset_timer.get(),  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    2020                 :            :                                           std::memory_order_relaxed);           // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    2021                 :            :             }
    2022                 :            :           }
    2023                 :         98 :         }
    2024                 :            : 
    2025   [ +  +  +  +  :        192 :         if (impl_->stop_flag.load(std::memory_order_relaxed) || impl_->jump_flag.load(std::memory_order_relaxed) ||
                   +  + ]
    2026   [ +  -  -  + ]:         94 :             is_ready_to_quit()) {
    2027                 :          4 :           is_interrupted = true;
    2028                 :          4 :           break;
    2029         [ +  + ]:         94 :         } else if (impl_->pause_flag.load(std::memory_order_relaxed)) {
    2030         [ +  - ]:          9 :           do_pause();
    2031                 :          9 :           impl_->pause_next_flag.store(false, std::memory_order_relaxed);
    2032                 :            : 
    2033   [ +  -  +  -  :         18 :           if (impl_->stop_flag.load(std::memory_order_relaxed) || impl_->jump_flag.load(std::memory_order_relaxed) ||
                   -  + ]
    2034   [ +  -  -  + ]:          9 :               is_ready_to_quit()) {
    2035                 :            :             is_interrupted = true;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    2036                 :            :             break;                  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    2037                 :            :           }
    2038                 :            :         }
    2039                 :            : 
    2040                 :            :         {
    2041         [ +  - ]:         94 :           std::lock_guard time_lock(impl_->time_mtx);
    2042                 :         94 :           impl_->real_timer.restart();
    2043                 :         94 :           impl_->real_elapsed.store(timestamp, std::memory_order_relaxed);
    2044                 :         94 :         }
    2045                 :            : 
    2046                 :         94 :         ActionType action_type = ActionType::kUnknownAction;
    2047                 :            : 
    2048   [ +  -  +  - ]:         94 :         if (auto action_iter = wrapper_file.channel_action_map.find(iter->message.channelId);
    2049         [ +  - ]:         94 :             action_iter != wrapper_file.channel_action_map.end()) {
    2050                 :         94 :           action_type = action_iter->second;
    2051                 :            :         }
    2052                 :            : 
    2053                 :         94 :         Frame frame;
    2054                 :         94 :         frame.timestamp = timestamp;
    2055   [ +  -  +  - ]:         94 :         frame.url = iter->channel->topic;
    2056                 :         94 :         frame.action_type = action_type;
    2057                 :         94 :         frame.data = Bytes::shallow_copy(data, size);
    2058                 :            : 
    2059         [ +  - ]:         94 :         BagReader::process_output(frame);
    2060                 :         94 :       }
    2061                 :            : 
    2062         [ +  + ]:         47 :       if (is_interrupted) {
    2063                 :          4 :         break;
    2064                 :            :       }
    2065   [ +  +  +  + ]:         51 :     }
    2066                 :            : 
    2067         [ +  + ]:         36 :     if (is_interrupted) {
    2068         [ +  + ]:          4 :       if (impl_->stop_flag.load(std::memory_order_relaxed)) {
    2069         [ +  - ]:          2 :         update_status(kStopped);
    2070                 :            :       }
    2071                 :            : 
    2072                 :          4 :       break;
    2073                 :            :     }
    2074                 :            : 
    2075         [ +  + ]:         32 :     if (!impl_->jump_flag.load(std::memory_order_relaxed)) {
    2076         [ +  - ]:         31 :       update_status(kStopped);
    2077                 :            : 
    2078         [ +  + ]:         31 :       if (config.skip_blank) {
    2079                 :          3 :         impl_->begin_time.store(std::max(config.begin_time, impl_->info.blank_duration), std::memory_order_relaxed);
    2080                 :            :       } else {
    2081                 :         28 :         impl_->begin_time.store(config.begin_time, std::memory_order_relaxed);
    2082                 :            :       }
    2083                 :            :     }
    2084                 :            : 
    2085   [ +  +  +  +  :         62 :     if (impl_->stop_flag.load(std::memory_order_relaxed) || impl_->jump_flag.load(std::memory_order_relaxed) ||
                   +  + ]
    2086   [ +  -  -  + ]:         30 :         is_ready_to_quit()) {
    2087                 :          2 :       is_interrupted = true;
    2088                 :          2 :       break;
    2089                 :            :     }
    2090                 :            : 
    2091         [ +  - ]:         30 :     flush_plugin();
    2092   [ +  -  +  + ]:         90 :   } while (impl_->times.load(std::memory_order_relaxed) <= 0 ||
    2093         [ +  - ]:         60 :            (impl_->times.load(std::memory_order_relaxed) > 0 &&
    2094         [ +  + ]:         60 :             ++loop_times < impl_->times.load(std::memory_order_relaxed)));
    2095                 :            : 
    2096         [ +  + ]:         37 :   if (impl_->stop_flag.load(std::memory_order_relaxed)) {
    2097                 :          4 :     is_interrupted = true;
    2098                 :            :   }
    2099                 :            : 
    2100   [ +  +  +  +  :         37 :   if (!impl_->jump_flag.load(std::memory_order_relaxed) && impl_->finish_callback) {
                   +  + ]
    2101                 :         31 :     impl_->finish_callback(is_interrupted);
    2102                 :            :   }
    2103                 :            : 
    2104   [ +  +  +  +  :         37 :   if (!impl_->jump_flag.load(std::memory_order_relaxed) && config.auto_quit) {
                   +  + ]
    2105                 :          1 :     quit();
    2106                 :            :   }
    2107                 :            : 
    2108                 :            :   // clean msg_view
    2109         [ +  + ]:         87 :   for (auto& wrapper_file : impl_->file_list) {
    2110                 :         50 :     wrapper_file.msg_view.reset();
    2111                 :            :   }
    2112                 :            : }
    2113                 :            : 
    2114                 :            : }  // namespace vlink

Generated by: LCOV version 1.14