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

Generated by: LCOV version 2.0-1