LCOV - code coverage report
Current view: top level - dpdklibs/unittest - WIBEthPacketBuilder_test.cxx (source / functions) Coverage Total Hit
Test: code.result Lines: 100.0 % 177 177
Test Date: 2026-10-11 15:05:20 Functions: 100.0 % 23 23

            Line data    Source code
       1              : /**
       2              :  * @file WIBEthPacketBuilder_test.cxx
       3              :  *
       4              :  * Byte-exact verification of the WIBEth packet builder against a reference
       5              :  * header assembled from the protocol definitions rather than from the builder:
       6              :  * the full 42-byte Ethernet/IPv4/UDP layout including checksum, payload
       7              :  * equality outside the patched DAQ header, input immutability, patch
       8              :  * semantics, and construction into a misaligned destination.
       9              :  */
      10              : 
      11              : #include "dpdklibs/wibeth/WIBEthPacketBuilder.hpp"
      12              : #include "fdreadoutlibs/DUNEWIBEthTypeAdapter.hpp"
      13              : 
      14              : #define BOOST_TEST_MODULE WIBEthPacketBuilder_test // NOLINT
      15              : 
      16              : #include "boost/test/unit_test.hpp"
      17              : 
      18              : #include <array>
      19              : #include <cstddef>
      20              : #include <cstdint>
      21              : #include <cstring>
      22              : #include <vector>
      23              : 
      24              : using namespace dunedaq::dpdklibs;
      25              : 
      26              : namespace {
      27              : 
      28              : // Reference configuration, each field in the notation it is normally quoted
      29              : // in.  golden_config() hands these to the builder; expected_header() lays the
      30              : // same values out on the wire.  The two paths share no code.
      31              : constexpr std::array<std::uint8_t, 6> kDstMacBytes = { 0x6c, 0xb3, 0x11, 0x79, 0x81, 0x00 };
      32              : constexpr std::array<std::uint8_t, 6> kSrcMacBytes = { 0x6c, 0xb3, 0x11, 0x79, 0xb8, 0x6c };
      33              : constexpr std::array<std::uint8_t, 4> kSrcIpBytes = { 192, 168, 100, 1 };
      34              : constexpr std::array<std::uint8_t, 4> kDstIpBytes = { 192, 168, 100, 2 };
      35              : constexpr std::uint16_t kSrcPort = 55677;
      36              : constexpr std::uint16_t kDstPort = 55678;
      37              : constexpr std::uint16_t kPacketId = 42;
      38              : 
      39              : rte_ether_addr
      40            6 : make_mac(const std::array<std::uint8_t, 6>& bytes)
      41              : {
      42            6 :   rte_ether_addr addr{};
      43           42 :   for (std::size_t i = 0; i < bytes.size(); ++i) {
      44           36 :     addr.addr_bytes[i] = bytes[i];
      45              :   }
      46            6 :   return addr;
      47              : }
      48              : 
      49              : // The DAQ header is at Ethernet offset 42 and is not aligned for its 64-bit
      50              : // words.  Read it through an aligned copy.
      51              : dunedaq::detdataformats::DAQEthHeader
      52            6 : read_daq_header(const std::uint8_t* frame_start)
      53              : {
      54            6 :   dunedaq::detdataformats::DAQEthHeader header;
      55            6 :   std::memcpy(&header, frame_start, sizeof(header));
      56            6 :   return header;
      57              : }
      58              : 
      59              : // Ones'-complement sum of 16-bit big-endian words, written independently of
      60              : // rte_ipv4_cksum, which the builder uses.  Over a valid IPv4 header including
      61              : // its checksum field the sum is 0xffff; over the same header with that field
      62              : // zero, the complement of the sum is the checksum.
      63              : std::uint16_t
      64            2 : ones_complement_sum(const std::uint8_t* data, std::size_t bytes)
      65              : {
      66            2 :   std::uint32_t sum = 0;
      67           22 :   for (std::size_t i = 0; i + 1 < bytes; i += 2) {
      68           20 :     sum += static_cast<std::uint32_t>((data[i] << 8) | data[i + 1]);
      69              :   }
      70            4 :   while ((sum >> 16) != 0) {
      71            2 :     sum = (sum & 0xffffU) + (sum >> 16);
      72              :   }
      73            2 :   return static_cast<std::uint16_t>(sum);
      74              : }
      75              : 
      76              : wibeth::PacketConfig
      77            3 : golden_config()
      78              : {
      79            3 :   wibeth::PacketConfig cfg;
      80            3 :   cfg.dst_mac = make_mac(kDstMacBytes);
      81            3 :   cfg.src_mac = make_mac(kSrcMacBytes);
      82            3 :   cfg.src_ip = wibeth::parse_ipv4_addr("192.168.100.1");
      83            3 :   cfg.dst_ip = wibeth::parse_ipv4_addr("192.168.100.2");
      84            3 :   cfg.src_port = kSrcPort;
      85            3 :   cfg.dst_port = kDstPort;
      86            3 :   cfg.packet_id = kPacketId;
      87            3 :   return cfg;
      88              : }
      89              : 
      90              : // The 42-byte header golden_config() must produce, assembled field by field
      91              : // from the protocol definitions: Ethernet II, IPv4 (RFC 791), UDP (RFC 768).
      92              : // Sizes are literals, byte order is explicit, and the checksum comes from the
      93              : // routine above.  No builder constant, no DPDK helper and no function under
      94              : // test appears here, so this is an independent reference rather than a
      95              : // restatement of the implementation.
      96              : std::array<std::uint8_t, 42>
      97            1 : expected_header()
      98              : {
      99            1 :   constexpr std::uint16_t kIPv4HeaderLen = 20;
     100            1 :   constexpr std::uint16_t kUDPHeaderLen = 8;
     101            1 :   constexpr std::uint16_t kWIBEthFrameLen = 7200;
     102            1 :   constexpr std::size_t kIPv4Offset = 14;
     103            1 :   constexpr std::size_t kChecksumOffset = kIPv4Offset + 10;
     104              : 
     105            1 :   std::array<std::uint8_t, 42> header{};
     106            1 :   std::size_t at = 0;
     107              :   // at() rather than operator[]: a layout that writes past the end must throw,
     108              :   // not corrupt the reference.
     109           42 :   const auto put8 = [&](std::uint8_t value) { header.at(at++) = value; };
     110           10 :   const auto put16 = [&](std::uint16_t value) {
     111            9 :     put8(static_cast<std::uint8_t>(value >> 8));
     112            9 :     put8(static_cast<std::uint8_t>(value & 0xffU));
     113           10 :   };
     114            5 :   const auto put_bytes = [&](const auto& bytes) {
     115           24 :     for (auto byte : bytes) {
     116           20 :       put8(byte);
     117              :     }
     118            5 :   };
     119              : 
     120              :   // Ethernet II: destination MAC, source MAC, EtherType 0x0800 for IPv4.
     121            1 :   put_bytes(kDstMacBytes);
     122            1 :   put_bytes(kSrcMacBytes);
     123            1 :   put16(0x0800);
     124              : 
     125              :   // IPv4: version 4 with a 5-word header, no DSCP/ECN, total length,
     126              :   // identification, no flags or fragment offset, TTL 64, protocol 17 (UDP),
     127              :   // checksum zero for now, source address, destination address.
     128            1 :   put8(0x45);
     129            1 :   put8(0x00);
     130            1 :   put16(static_cast<std::uint16_t>(kIPv4HeaderLen + kUDPHeaderLen + kWIBEthFrameLen));
     131            1 :   put16(kPacketId);
     132            1 :   put16(0x0000);
     133            1 :   put8(64);
     134            1 :   put8(17);
     135            1 :   put16(0x0000);
     136            1 :   put_bytes(kSrcIpBytes);
     137            1 :   put_bytes(kDstIpBytes);
     138              : 
     139              :   // UDP: source port, destination port, datagram length, checksum unused.
     140            1 :   put16(kSrcPort);
     141            1 :   put16(kDstPort);
     142            1 :   put16(static_cast<std::uint16_t>(kUDPHeaderLen + kWIBEthFrameLen));
     143            1 :   put16(0x0000);
     144              : 
     145            1 :   BOOST_REQUIRE_EQUAL(at, header.size());
     146              : 
     147              :   // The checksum covers the IPv4 header with its own field zero, which is the
     148              :   // state written above.
     149            1 :   const std::uint16_t checksum = static_cast<std::uint16_t>(~ones_complement_sum(&header[kIPv4Offset], kIPv4HeaderLen));
     150            1 :   header.at(kChecksumOffset) = static_cast<std::uint8_t>(checksum >> 8);
     151            1 :   header.at(kChecksumOffset + 1) = static_cast<std::uint8_t>(checksum & 0xffU);
     152              : 
     153            1 :   return header;
     154              : }
     155              : 
     156              : } // namespace
     157              : 
     158              : BOOST_AUTO_TEST_SUITE(WIBEthPacketBuilder_test)
     159              : 
     160            2 : BOOST_AUTO_TEST_CASE(GoldenHeaderPayloadAndInputImmutability)
     161              : {
     162            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     163            1 :   std::memset(frame.data, 0xab, sizeof(frame.data));
     164              : 
     165            1 :   auto* wibeth_frame = reinterpret_cast<dunedaq::fddetdataformats::WIBEthFrame*>(frame.data);
     166            1 :   wibeth_frame->daq_header.det_id = 0;
     167            1 :   wibeth_frame->daq_header.crate_id = 1;
     168            1 :   wibeth_frame->daq_header.slot_id = 1;
     169            1 :   wibeth_frame->daq_header.stream_id = 0;
     170            1 :   wibeth_frame->daq_header.seq_id = 0;
     171            1 :   wibeth_frame->daq_header.block_length = 0;
     172            1 :   wibeth_frame->daq_header.timestamp = 123456789;
     173              : 
     174              :   // Copy the input so it can be compared after construction.
     175            1 :   std::vector<std::uint8_t> input_snapshot(frame.data, frame.data + wibeth::kWIBEthFrameBytes);
     176              : 
     177            1 :   wibeth::HeaderPatch patch;
     178            1 :   patch.set_det_id = true;
     179            1 :   patch.det_id = 3;
     180            1 :   patch.set_seq_id = true;
     181            1 :   patch.seq_id = 17;
     182            1 :   patch.force_block_length = true;
     183              : 
     184            1 :   std::vector<std::uint8_t> packet_bytes(wibeth::kEthernetPacketBytes, 0);
     185            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, golden_config(), patch);
     186              : 
     187              :   // Compare the 42-byte header element-wise so a mismatch reports its offset
     188              :   // and value.
     189            1 :   const auto expected = expected_header();
     190            1 :   BOOST_CHECK_EQUAL_COLLECTIONS(
     191              :     packet_bytes.begin(), packet_bytes.begin() + expected.size(), expected.begin(), expected.end());
     192              : 
     193              :   // Patched DAQ header fields.
     194            1 :   const auto out_header = read_daq_header(packet_bytes.data() + wibeth::kPacketHeaderBytes);
     195            1 :   BOOST_CHECK_EQUAL(out_header.det_id, 3);
     196            1 :   BOOST_CHECK_EQUAL(out_header.crate_id, 1);
     197            1 :   BOOST_CHECK_EQUAL(out_header.slot_id, 1);
     198            1 :   BOOST_CHECK_EQUAL(out_header.stream_id, 0);
     199            1 :   BOOST_CHECK_EQUAL(out_header.seq_id, 17);
     200            1 :   BOOST_CHECK_EQUAL(out_header.block_length, wibeth::kWIBEthBlockLength);
     201            1 :   BOOST_CHECK_EQUAL(out_header.timestamp, 123456789);
     202              : 
     203              :   // Complete payload equality outside the patched 16-byte DAQ header.
     204            1 :   BOOST_CHECK_EQUAL(std::memcmp(packet_bytes.data() + wibeth::kPacketHeaderBytes + wibeth::kDAQEthHeaderBytes,
     205              :                                 input_snapshot.data() + wibeth::kDAQEthHeaderBytes,
     206              :                                 wibeth::kWIBEthFrameBytes - wibeth::kDAQEthHeaderBytes),
     207              :                     0);
     208              : 
     209              :   // The input frame must not have been modified.
     210            1 :   BOOST_CHECK_EQUAL(std::memcmp(frame.data, input_snapshot.data(), wibeth::kWIBEthFrameBytes), 0);
     211            1 : }
     212              : 
     213            2 : BOOST_AUTO_TEST_CASE(IPv4ChecksumVerifiedIndependently)
     214              : {
     215              :   // A second address pair, to check checksum validity beyond the single
     216              :   // reference vector.
     217            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     218            1 :   std::memset(frame.data, 0x5a, sizeof(frame.data));
     219              : 
     220            1 :   wibeth::PacketConfig cfg;
     221            1 :   cfg.src_ip = wibeth::parse_ipv4_addr("10.73.139.16");
     222            1 :   cfg.dst_ip = wibeth::parse_ipv4_addr("10.73.139.17");
     223            1 :   cfg.packet_id = 7;
     224              : 
     225            1 :   std::vector<std::uint8_t> packet_bytes(wibeth::kEthernetPacketBytes, 0);
     226            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, cfg);
     227              : 
     228            1 :   const std::uint8_t* ipv4_header = packet_bytes.data() + wibeth::kEthernetHeaderBytes;
     229            1 :   BOOST_CHECK_EQUAL(ones_complement_sum(ipv4_header, 20), 0xffff);
     230            1 : }
     231              : 
     232            2 : BOOST_AUTO_TEST_CASE(PatchDisabledPreservesFrameVerbatim)
     233              : {
     234            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     235            1 :   std::memset(frame.data, 0x77, sizeof(frame.data));
     236              : 
     237            1 :   wibeth::HeaderPatch patch;
     238            1 :   patch.set_det_id = false;
     239            1 :   patch.set_seq_id = false;
     240            1 :   patch.force_block_length = false;
     241              : 
     242            1 :   std::vector<std::uint8_t> packet_bytes(wibeth::kEthernetPacketBytes, 0);
     243            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, wibeth::PacketConfig{}, patch);
     244              : 
     245            1 :   BOOST_CHECK_EQUAL(
     246              :     std::memcmp(packet_bytes.data() + wibeth::kPacketHeaderBytes, frame.data, wibeth::kWIBEthFrameBytes), 0);
     247            1 : }
     248              : 
     249            2 : BOOST_AUTO_TEST_CASE(SequenceIdWrapsModulo4096)
     250              : {
     251            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     252            1 :   std::memset(frame.data, 0, sizeof(frame.data));
     253            1 :   wibeth::PacketConfig cfg;
     254              : 
     255            1 :   wibeth::HeaderPatch patch;
     256            1 :   patch.set_seq_id = true;
     257              : 
     258            1 :   std::vector<std::uint8_t> packet_bytes(wibeth::kEthernetPacketBytes, 0);
     259              : 
     260            1 :   patch.seq_id = 4095;
     261            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, cfg, patch);
     262            1 :   BOOST_CHECK_EQUAL(read_daq_header(packet_bytes.data() + wibeth::kPacketHeaderBytes).seq_id, 4095);
     263              : 
     264            1 :   patch.seq_id = 4096;
     265            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, cfg, patch);
     266            1 :   BOOST_CHECK_EQUAL(read_daq_header(packet_bytes.data() + wibeth::kPacketHeaderBytes).seq_id, 0);
     267            1 : }
     268              : 
     269            2 : BOOST_AUTO_TEST_CASE(DetIdUpperBoundary63)
     270              : {
     271            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     272            1 :   std::memset(frame.data, 0, sizeof(frame.data));
     273            1 :   wibeth::PacketConfig cfg;
     274              : 
     275            1 :   wibeth::HeaderPatch patch;
     276            1 :   patch.set_det_id = true;
     277            1 :   patch.det_id = 63; // largest value the 6-bit field can carry
     278              : 
     279            1 :   std::vector<std::uint8_t> packet_bytes(wibeth::kEthernetPacketBytes, 0);
     280            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, cfg, patch);
     281            1 :   BOOST_CHECK_EQUAL(read_daq_header(packet_bytes.data() + wibeth::kPacketHeaderBytes).det_id, 63);
     282            1 : }
     283              : 
     284            2 : BOOST_AUTO_TEST_CASE(DetIdTruncatesToSixBits)
     285              : {
     286              :   // The patch stores det_id in the 6-bit DAQEthHeader field, so 64 wraps to 0.
     287              :   // The plugin rejects det_id > 63 at configuration.  Direct users of the
     288              :   // builder get this truncation.
     289            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     290            1 :   std::memset(frame.data, 0, sizeof(frame.data));
     291            1 :   wibeth::PacketConfig cfg;
     292              : 
     293            1 :   wibeth::HeaderPatch patch;
     294            1 :   patch.set_det_id = true;
     295            1 :   patch.det_id = 64;
     296              : 
     297            1 :   std::vector<std::uint8_t> packet_bytes(wibeth::kEthernetPacketBytes, 0);
     298            1 :   wibeth::construct_packet(packet_bytes.data(), frame.data, cfg, patch);
     299            1 :   BOOST_CHECK_EQUAL(read_daq_header(packet_bytes.data() + wibeth::kPacketHeaderBytes).det_id, 0);
     300            1 : }
     301              : 
     302            2 : BOOST_AUTO_TEST_CASE(ConstructsIntoMisalignedDestination)
     303              : {
     304            1 :   dunedaq::fdreadoutlibs::types::DUNEWIBEthTypeAdapter frame{};
     305            1 :   std::memset(frame.data, 0xcd, sizeof(frame.data));
     306              : 
     307            1 :   wibeth::HeaderPatch patch;
     308            1 :   patch.set_det_id = true;
     309            1 :   patch.det_id = 5;
     310            1 :   patch.set_seq_id = true;
     311            1 :   patch.seq_id = 9;
     312              : 
     313              :   // Offset the destination by one byte so no field is naturally aligned.
     314              :   // Construction reaches caller storage only through memcpy, so the result must
     315              :   // be byte-identical to an aligned construction.
     316            1 :   std::vector<std::uint8_t> aligned(wibeth::kEthernetPacketBytes, 0);
     317            1 :   wibeth::construct_packet(aligned.data(), frame.data, golden_config(), patch);
     318              : 
     319            1 :   std::vector<std::uint8_t> raw(wibeth::kEthernetPacketBytes + 1, 0);
     320            1 :   std::uint8_t* dst = raw.data() + 1;
     321            1 :   wibeth::construct_packet(dst, frame.data, golden_config(), patch);
     322              : 
     323            1 :   BOOST_CHECK_EQUAL(std::memcmp(dst, aligned.data(), wibeth::kEthernetPacketBytes), 0);
     324            1 :   BOOST_CHECK_EQUAL(raw.back(), 0xcd);
     325              : 
     326            1 :   const auto out_header = read_daq_header(dst + wibeth::kPacketHeaderBytes);
     327            1 :   BOOST_CHECK_EQUAL(out_header.det_id, 5);
     328            1 :   BOOST_CHECK_EQUAL(out_header.seq_id, 9);
     329            1 :   BOOST_CHECK_EQUAL(out_header.block_length, wibeth::kWIBEthBlockLength);
     330            1 : }
     331              : 
     332              : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1