LCOV - code coverage report
Current view: top level - asiolibs/plugins - SocketReaderModule.cpp (source / functions) Coverage Total Hit
Test: code.result Lines: 0.0 % 139 0
Test Date: 2026-10-11 15:05:20 Functions: 0.0 % 31 0

            Line data    Source code
       1              : /**
       2              :  * @file SocketReaderModule.cpp Boost.Asio-based socket reader plugin for low-bandwidth devices
       3              :  *
       4              :  * This is part of the DUNE DAQ , copyright 2020.
       5              :  * Licensing/copyright details are in the COPYING file that you should have
       6              :  * received with this code.
       7              :  */
       8              : 
       9              : #include "SocketReaderModule.hpp"
      10              : 
      11              : #include "CreateSource.hpp"
      12              : 
      13              : #include "appmodel/DataReaderModule.hpp"
      14              : #include "appmodel/NWDetDataReceiver.hpp"
      15              : #include "appmodel/SocketDataSender.hpp"
      16              : #include "appmodel/SocketDetectorToDaqConnection.hpp"
      17              : #include "confmodel/DetectorStream.hpp"
      18              : #include "confmodel/GeoId.hpp"
      19              : #include "confmodel/NetworkInterface.hpp"
      20              : #include "confmodel/QueueWithSourceId.hpp"
      21              : 
      22              : #include "datahandlinglibs/DataHandlingIssues.hpp"
      23              : 
      24              : #include "asiolibs/opmon/SocketReaderModule.pb.h"
      25              : 
      26              : #include <memory>
      27              : #include <string>
      28              : #include <utility>
      29              : #include <vector>
      30              : 
      31              : namespace dunedaq::asiolibs {
      32              : 
      33            0 : SocketReaderModule::SocketReaderModule(const std::string& name)
      34              :   : DAQModule(name)
      35            0 :   , m_work_guard(boost::asio::make_work_guard(m_io_context))
      36              : {
      37            0 :   register_command("conf", &SocketReaderModule::do_configure);
      38            0 :   register_command("start", &SocketReaderModule::do_start);
      39            0 :   register_command("stop_trigger_sources", &SocketReaderModule::do_stop);
      40            0 : }
      41              : 
      42              : void
      43            0 : SocketReaderModule::init(const std::shared_ptr<appfwk::ConfigurationManager> mcfg)
      44              : {
      45            0 :   auto* mdal = mcfg->get_dal<appmodel::DataReaderModule>(get_name());
      46              : 
      47            0 :   if (mdal->get_raw_data_callbacks().empty()) {
      48            0 :     auto err =
      49            0 :       datahandlinglibs::InitializationError(ERS_HERE, "No outputs defined for socket reader in configuration.");
      50            0 :     ers::fatal(err);
      51            0 :     throw err;
      52            0 :   }
      53              : 
      54              :   // Loop over output queues, extract source ids and create source model objects
      55            0 :   for (auto con : mdal->get_raw_data_callbacks()) {
      56              :     // TODO: add nullpointer check against misconfiguration
      57            0 :     auto ptr = m_sources[con->get_source_id()] = createSourceModel(con);
      58            0 :     register_node(con->UID(), ptr);
      59            0 :   }
      60              : 
      61            0 :   auto* d2d_conn = mdal->get_connections()[0]; // there's only 1 connection
      62            0 :   auto* socket_d2d_conn = d2d_conn->cast<appmodel::SocketDetectorToDaqConnection>();
      63            0 :   if (socket_d2d_conn == nullptr) {
      64            0 :     auto err =
      65            0 :       datahandlinglibs::InitializationError(ERS_HERE, "Connection is not of type SocketDetectorToDaqConnection.");
      66            0 :     ers::fatal(err);
      67            0 :     throw err;
      68            0 :   }
      69              : 
      70            0 :   auto nw_receiver = socket_d2d_conn->get_net_receiver();
      71            0 :   auto local_ip = nw_receiver->get_uses()->get_ip_address()[0];
      72              : 
      73            0 :   for (auto nw_sender : socket_d2d_conn->get_net_senders()) {
      74            0 :     if (nw_sender->is_excluded(*(mcfg->get_session()))) {
      75            0 :       continue;
      76              :     }
      77              : 
      78            0 :     auto* socket_sender = nw_sender->cast<appmodel::SocketDataSender>();
      79            0 :     if (socket_sender == nullptr) {
      80            0 :       auto err = datahandlinglibs::InitializationError(ERS_HERE, "Sender is not of type SocketDataSender.");
      81            0 :       ers::fatal(err);
      82            0 :       throw err;
      83            0 :     }
      84              : 
      85            0 :     auto local_port = socket_sender->get_remote_port();
      86              : 
      87            0 :     m_reader_infos.emplace_back(std::make_shared<ReaderInfo>(local_ip, local_port, std::make_shared<SocketStats>()));
      88              : 
      89            0 :     if (string_to_socket_type(socket_sender->get_socket_type()) == SocketType::TCP) {
      90            0 :       m_readers.push_back(std::make_shared<std::variant<TCPReader, UDPReader>>(TCPReader{}));
      91              :     } else {
      92            0 :       m_readers.push_back(std::make_shared<std::variant<TCPReader, UDPReader>>(UDPReader{}));
      93              :     }
      94              : 
      95            0 :     auto remote_ip = socket_sender->get_uses()->get_ip_address()[0];
      96            0 :     auto remote_port = socket_sender->get_local_port();
      97              : 
      98              :     // Loop over streams
      99            0 :     for (auto det_stream : socket_sender->get_streams()) {
     100            0 :       if (det_stream->is_excluded(*(mcfg->get_session()))) {
     101            0 :         continue;
     102              :       }
     103              : 
     104            0 :       remote_stream_pair_t sender_stream_pair = { { remote_ip, remote_port },
     105            0 :                                                   det_stream->get_geo_id()->get_stream_id() };
     106            0 :       m_remote_to_source[sender_stream_pair] = m_sources[det_stream->get_source_id()];
     107            0 :     }
     108            0 :   }
     109            0 : }
     110              : 
     111              : SocketReaderModule::SocketType
     112            0 : SocketReaderModule::string_to_socket_type(const std::string& socket_type) const
     113              : {
     114            0 :   if (socket_type == "TCP") {
     115              :     return SocketReaderModule::SocketType::TCP;
     116            0 :   } else if (socket_type == "UDP") {
     117            0 :     return SocketReaderModule::SocketType::UDP;
     118              :   }
     119              :   return SocketReaderModule::SocketType::INVALID;
     120              : }
     121              : 
     122              : void
     123            0 : SocketReaderModule::do_configure(const CommandData_t&)
     124              : {
     125            0 :   for (std::size_t i = 0; i < m_readers.size(); ++i) {
     126            0 :     auto reader_info = m_reader_infos[i];
     127            0 :     std::visit([this, reader_info](auto& reader) { reader.configure(m_io_context, reader_info); }, *m_readers[i]);
     128            0 :   }
     129            0 : }
     130              : 
     131              : void
     132            0 : SocketReaderModule::do_start(const CommandData_t&)
     133              : {
     134            0 :   for (const auto& reader_info : m_reader_infos) {
     135              :     // Reset opmon variables
     136            0 :     reader_info->socket_stats->packets_received = 0;
     137            0 :     reader_info->socket_stats->bytes_received = 0;
     138            0 :     reader_info->socket_stats->stats_packet_count = 0;
     139              :   }
     140              : 
     141              :   // Setup callbacks on all sourcemodels
     142            0 :   for (auto& [_, source] : m_sources) {
     143            0 :     source->acquire_callback();
     144              :   }
     145              : 
     146            0 :   m_io_thread = std::jthread([this] { m_io_context.run(); });
     147              : 
     148            0 :   for (auto& reader : m_readers) {
     149            0 :     boost::asio::co_spawn(m_io_context,
     150            0 :                           std::visit([this](auto& reader) { return reader.start(m_remote_to_source); }, *reader),
     151              :                           boost::asio::detached);
     152              :   }
     153            0 : }
     154              : 
     155              : void
     156            0 : SocketReaderModule::do_stop(const CommandData_t&)
     157              : {
     158            0 :   for (auto& reader : m_readers) {
     159            0 :     std::visit([](auto& reader) { reader.stop(); }, *reader);
     160              :   }
     161              : 
     162            0 :   m_work_guard.reset();
     163            0 : }
     164              : 
     165              : void
     166            0 : SocketReaderModule::generate_opmon_data()
     167              : {
     168            0 :   for (const auto& reader_info : m_reader_infos) {
     169            0 :     opmon::SocketReaderStats stats;
     170            0 :     stats.set_packets_received(reader_info->socket_stats->packets_received.load());
     171            0 :     stats.set_bytes_received(reader_info->socket_stats->bytes_received.load());
     172            0 :     publish(std::move(stats),
     173            0 :             { { "socket-reader", reader_info->local_ip + ":" + std::to_string(reader_info->local_port) } });
     174            0 :   }
     175            0 : }
     176              : 
     177              : void
     178            0 : SocketReaderModule::TCPReader::configure(boost::asio::io_context& io_context, std::shared_ptr<ReaderInfo> reader_info)
     179              : {
     180            0 :   m_socket_stats = reader_info->socket_stats;
     181              : 
     182            0 :   m_socket = std::make_unique<boost::asio::ip::tcp::socket>(io_context);
     183              : 
     184            0 :   boost::asio::ip::address local_address;
     185              : 
     186            0 :   try {
     187            0 :     local_address = boost::asio::ip::make_address(reader_info->local_ip);
     188            0 :   } catch (const boost::system::system_error& e) {
     189            0 :     TLOG() << "Failed to configure TCP socket: " << e.what() << " (Local IP: " << reader_info->local_ip << ")";
     190            0 :     throw;
     191            0 :   }
     192              : 
     193            0 :   boost::asio::ip::tcp::acceptor acceptor(io_context,
     194            0 :                                           boost::asio::ip::tcp::endpoint(local_address, reader_info->local_port));
     195              : 
     196            0 :   TLOG() << "Waiting for TCP connection at " << reader_info->local_ip << ":" << reader_info->local_port;
     197              : 
     198            0 :   acceptor.accept(*m_socket);
     199              : 
     200            0 :   m_remote = { m_socket->remote_endpoint().address().to_string(), m_socket->remote_endpoint().port() };
     201              : 
     202            0 :   TLOG() << "Established TCP connection from " << m_socket->local_endpoint().address().to_string() << ":"
     203            0 :          << m_socket->local_endpoint().port() << " to " << m_remote.first << ":" << m_remote.second;
     204            0 : }
     205              : 
     206              : boost::asio::awaitable<void>
     207            0 : SocketReaderModule::TCPReader::start(const remote_source_map_t& remote_to_source)
     208              : {
     209              :   const auto buffer_size = remote_to_source.begin()->second->get_expected_frame_size();
     210              :   std::vector<char> buffer(buffer_size);
     211              : 
     212              :   while (m_socket->is_open()) {
     213              :     const auto bytes_received =
     214              :       co_await boost::asio::async_read(*m_socket, boost::asio::buffer(buffer), boost::asio::use_awaitable);
     215              : 
     216              :     const auto* daq_header = reinterpret_cast<const dunedaq::detdataformats::DAQEthHeader*>(buffer.data());
     217              : 
     218              :     auto stream_id = static_cast<unsigned>(daq_header->stream_id);
     219              :     remote_stream_pair_t sender_stream_pair = { m_remote, stream_id };
     220              : 
     221              :     auto src_it = remote_to_source.find(sender_stream_pair);
     222              :     if (src_it == remote_to_source.end()) {
     223            0 :       TLOG() << "Unexpected sender-stream combination! (" << m_remote.first << ":" << m_remote.second << ", "
     224              :              << stream_id << ")";
     225              :       continue;
     226              :     }
     227              : 
     228              :     src_it->second->handle_daq_frame(buffer.data());
     229              : 
     230              :     ++m_socket_stats->packets_received;
     231              :     m_socket_stats->bytes_received.fetch_add(bytes_received);
     232              :     ++m_socket_stats->stats_packet_count;
     233              :   }
     234            0 : }
     235              : 
     236              : void
     237            0 : SocketReaderModule::TCPReader::stop()
     238              : {
     239            0 :   if (m_socket && m_socket->is_open()) {
     240            0 :     m_socket->close();
     241              :   }
     242            0 :   TLOG() << "Closed TCP connection";
     243            0 : }
     244              : 
     245              : void
     246            0 : SocketReaderModule::UDPReader::configure(boost::asio::io_context& io_context, std::shared_ptr<ReaderInfo> reader_info)
     247              : {
     248            0 :   m_socket_stats = reader_info->socket_stats;
     249              : 
     250            0 :   try {
     251            0 :     boost::asio::ip::udp::endpoint writer_endpoint(boost::asio::ip::address::from_string(reader_info->local_ip),
     252            0 :                                                    reader_info->local_port);
     253              : 
     254            0 :     m_socket = std::make_unique<boost::asio::ip::udp::socket>(io_context, writer_endpoint);
     255              : 
     256            0 :     TLOG() << "Created UDP socket on " << m_socket->local_endpoint().address() << ":"
     257            0 :            << m_socket->local_endpoint().port();
     258              : 
     259            0 :   } catch (const boost::system::system_error& e) {
     260            0 :     TLOG() << "Failed to configure UDP socket: " << e.what() << " (Local IP: " << reader_info->local_ip
     261            0 :            << ", Local port: " << reader_info->local_port << ")";
     262            0 :     throw;
     263            0 :   }
     264            0 : }
     265              : 
     266              : boost::asio::awaitable<void>
     267            0 : SocketReaderModule::UDPReader::start(const remote_source_map_t& remote_to_source)
     268              : {
     269              :   const auto buffer_size = remote_to_source.begin()->second->get_expected_frame_size();
     270              :   std::vector<char> buffer(buffer_size);
     271              :   boost::asio::ip::udp::endpoint sender_endpoint;
     272              : 
     273              :   while (m_socket->is_open()) {
     274              :     std::size_t bytes_received =
     275              :       co_await m_socket->async_receive_from(boost::asio::buffer(buffer), sender_endpoint, boost::asio::use_awaitable);
     276              : 
     277              :     const auto* daq_header = reinterpret_cast<const dunedaq::detdataformats::DAQEthHeader*>(buffer.data());
     278              : 
     279              :     auto stream_id = static_cast<unsigned>(daq_header->stream_id);
     280              : 
     281              :     remote_t remote = { sender_endpoint.address().to_string(), sender_endpoint.port() };
     282              :     remote_stream_pair_t sender_stream_pair = { remote, stream_id };
     283              : 
     284              :     auto src_it = remote_to_source.find(sender_stream_pair);
     285              : 
     286              :     if (src_it == remote_to_source.end()) {
     287            0 :       TLOG() << "Unexpected sender-stream combination! (" << remote.first << ":" << remote.second << ", " << stream_id
     288              :              << ")";
     289              :       continue;
     290              :     }
     291              : 
     292              :     if (bytes_received == buffer_size) [[likely]] {
     293              :       src_it->second->handle_daq_frame(buffer.data());
     294              :     } else {
     295            0 :       TLOG() << "Payload is smaller than " << buffer_size << " (" << bytes_received << ")";
     296              :     }
     297              : 
     298              :     ++m_socket_stats->packets_received;
     299              :     m_socket_stats->bytes_received.fetch_add(bytes_received);
     300              :     ++m_socket_stats->stats_packet_count;
     301              :   }
     302            0 : }
     303              : 
     304              : void
     305            0 : SocketReaderModule::UDPReader::stop()
     306              : {
     307            0 :   if (m_socket && m_socket->is_open()) {
     308            0 :     m_socket->close();
     309              :   }
     310            0 :   TLOG() << "Closed UDP socket";
     311            0 : }
     312              : 
     313              : } // namespace dunedaq::asiolibs
     314              : 
     315            0 : DEFINE_DUNE_DAQ_MODULE(dunedaq::asiolibs::SocketReaderModule)
        

Generated by: LCOV version 2.0-1