diff --git a/SilKit/source/tracing/Pcap.hpp b/SilKit/source/tracing/Pcap.hpp index 8e4a30f57..dd9f7ff0d 100644 --- a/SilKit/source/tracing/Pcap.hpp +++ b/SilKit/source/tracing/Pcap.hpp @@ -5,16 +5,27 @@ #pragma once #include +#include "silkit/participant/exception.hpp" + namespace SilKit { namespace Tracing { namespace Pcap { -const uint32_t NativeMagic = 0xa1b23c4d; +// The magic number defines the unit of PacketHeader::ts_fraction. SIL Kit writes nanosecond files. +const uint32_t NativeMagic = 0xa1b23c4d; // nanosecond resolution +const uint32_t MicrosecondMagic = 0xa1b2c3d4; // microsecond resolution, e.g., written by tcpdump / Wireshark const size_t GlobalHeaderSize = 24; const size_t PacketHeaderSize = 16; const uint16_t MajorVersion = 2; const uint16_t MinorVersion = 4; +// The unit of PacketHeader::ts_fraction +enum class TimestampMode +{ + Nanoseconds, + Microseconds, +}; + struct GlobalHeader { uint32_t magic_number = NativeMagic; /* magic number */ @@ -24,15 +35,28 @@ struct GlobalHeader uint32_t sigfigs = 0; /* accuracy of timestamps */ uint32_t snaplen = 65535; /* max length of captured packets, in octets */ uint32_t network = 1; /* data link type */ + + auto GetTimestampMode() const -> TimestampMode + { + switch (magic_number) + { + case NativeMagic: + return TimestampMode::Nanoseconds; + case MicrosecondMagic: + return TimestampMode::Microseconds; + default: + throw SilKitError("PCAP global header: invalid magic number"); + } + } }; static_assert(sizeof(GlobalHeader) == GlobalHeaderSize, "GlobalHeader size must be equal to 24 bytes"); struct PacketHeader { - uint32_t ts_sec; /* timestamp seconds */ - uint32_t ts_usec; /* timestamp microseconds */ - uint32_t incl_len; /* number of octets of packet saved in file */ - uint32_t orig_len; /* actual length of packet */ + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_fraction; /* timestamp nanoseconds or microseconds, depending on the magic number */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ }; static_assert(sizeof(PacketHeader) == PacketHeaderSize, "PacketHeader size must be equal to 16 bytes"); diff --git a/SilKit/source/tracing/PcapReader.cpp b/SilKit/source/tracing/PcapReader.cpp index b9541a09d..942a73034 100755 --- a/SilKit/source/tracing/PcapReader.cpp +++ b/SilKit/source/tracing/PcapReader.cpp @@ -137,9 +137,14 @@ void PcapReader::ReadGlobalHeader() throw SilKitError("PCAP file cannot be opened: global header short read"); } auto* hdr = reinterpret_cast(buf.data()); - if (hdr->magic_number != Pcap::NativeMagic) + switch (hdr->GetTimestampMode()) { - throw SilKitError("PCAP file cannot be opened: invalid PCAP valid magic number"); + case Pcap::TimestampMode::Nanoseconds: + _nsPerTimestampFraction = 1; + break; + case Pcap::TimestampMode::Microseconds: + _nsPerTimestampFraction = 1000; + break; } if ((hdr->version_major != Pcap::MajorVersion) && (hdr->version_minor != Pcap::MinorVersion)) { @@ -181,7 +186,8 @@ bool PcapReader::Seek(size_t messageNumber) } auto msg = std::make_shared(); auto* hdr = reinterpret_cast(buf.data()); - std::chrono::nanoseconds timeStamp{((uint64_t)hdr->ts_sec * 1000000000u) + ((uint64_t)hdr->ts_usec * 1000u)}; + std::chrono::nanoseconds timeStamp{((uint64_t)hdr->ts_sec * 1000000000u) + + ((uint64_t)hdr->ts_fraction * _nsPerTimestampFraction)}; std::vector msgBuf{}; msgBuf.resize(hdr->incl_len); diff --git a/SilKit/source/tracing/PcapReader.hpp b/SilKit/source/tracing/PcapReader.hpp index f481eb8fb..2b956d0dd 100755 --- a/SilKit/source/tracing/PcapReader.hpp +++ b/SilKit/source/tracing/PcapReader.hpp @@ -49,6 +49,7 @@ class PcapReader final : public SilKit::IReplayChannelReader SilKit::Services::Logging::ILogger* _log{nullptr}; std::chrono::nanoseconds _startTime{0}; std::chrono::nanoseconds _endTime{0}; + uint64_t _nsPerTimestampFraction{1}; //!< 1 for nanosecond files, 1000 for microsecond files }; } // namespace Tracing diff --git a/SilKit/source/tracing/PcapSink.cpp b/SilKit/source/tracing/PcapSink.cpp index 98bd2fef3..54dd46781 100644 --- a/SilKit/source/tracing/PcapSink.cpp +++ b/SilKit/source/tracing/PcapSink.cpp @@ -109,14 +109,15 @@ void PcapSink::Trace(SilKit::Services::TransmitDirection /*unused*/, std::unique_lock lock; - const auto tosec = 1000'000ull; - const auto usec = std::chrono::duration_cast(timestamp); + // The global header announces nanosecond resolution (Pcap::NativeMagic). + const auto nsPerSecond = 1'000'000'000ull; + const auto nsec = static_cast(timestamp.count()); Pcap::PacketHeader pcapPacketHeader; pcapPacketHeader.orig_len = static_cast(message.raw.size()); pcapPacketHeader.incl_len = pcapPacketHeader.orig_len; - pcapPacketHeader.ts_sec = static_cast(usec.count() / tosec); - pcapPacketHeader.ts_usec = static_cast(usec.count() % tosec); + pcapPacketHeader.ts_sec = static_cast(nsec / nsPerSecond); + pcapPacketHeader.ts_fraction = static_cast(nsec % nsPerSecond); bool ok = true; if (_file.is_open()) diff --git a/SilKit/source/tracing/Test_Pcap.cpp b/SilKit/source/tracing/Test_Pcap.cpp index b8f3edb08..7d425f8eb 100755 --- a/SilKit/source/tracing/Test_Pcap.cpp +++ b/SilKit/source/tracing/Test_Pcap.cpp @@ -21,13 +21,16 @@ using namespace SilKit::Tracing; using namespace SilKit::Services::Ethernet; using namespace SilKit::Core::Tests; -std::vector MakePcapTestData(WireEthernetFrame& wireFrame, size_t numMessages) +// Packet i has the timestamp i seconds + i fraction units (nanoseconds or microseconds, depending on the magic). +std::vector MakePcapTestData(WireEthernetFrame& wireFrame, size_t numMessages, + uint32_t magic = Pcap::NativeMagic) { std::vector data; std::string payloadData{"Testing Trace message Pcap, padding to a minimal size of > 64 bytes" "... and so on 0123456789ABCDEF"}; Pcap::GlobalHeader ghdr{}; + ghdr.magic_number = magic; Pcap::PacketHeader phdr{}; EthernetMac destinationMac{1, 2, 3, 4, 5, 6}; @@ -45,7 +48,7 @@ std::vector MakePcapTestData(WireEthernetFrame& wireFrame, size_t numMe for (auto i = 0u; i < numMessages; i++) { phdr.ts_sec = i; - phdr.ts_usec = i; + phdr.ts_fraction = i; phdr.incl_len = static_cast(frame.size()); phdr.orig_len = phdr.incl_len; memcpy(raw, &phdr, Pcap::PacketHeaderSize); @@ -90,4 +93,43 @@ TEST(Test_Pcap, read_from_pcap) EXPECT_EQ((int)numMessages, 10); } +auto ReadTimestamps(uint32_t magic) -> std::vector +{ + MockLogger log; + std::stringstream ss; + WireEthernetFrame testInput; + auto raw = MakePcapTestData(testInput, 3, magic); + ss.write(reinterpret_cast(raw.data()), raw.size()); + + PcapReader reader{&ss, log.AsILogger()}; + std::vector timestamps; + do + { + auto msg = reader.Read(); + if (!msg) + { + break; + } + timestamps.push_back(msg->Timestamp()); + } while (reader.Seek(1)); + return timestamps; +} + +TEST(Test_Pcap, read_nanosecond_timestamps) +{ + using namespace std::chrono_literals; + EXPECT_THAT(ReadTimestamps(Pcap::NativeMagic), testing::ElementsAre(0ns, 1s + 1ns, 2s + 2ns)); +} + +TEST(Test_Pcap, read_microsecond_timestamps) +{ + using namespace std::chrono_literals; + EXPECT_THAT(ReadTimestamps(Pcap::MicrosecondMagic), testing::ElementsAre(0ns, 1s + 1us, 2s + 2us)); +} + +TEST(Test_Pcap, invalid_magic_number_throws) +{ + EXPECT_THROW(ReadTimestamps(0x12345678), SilKit::SilKitError); +} + } // namespace