From 5b80f271809760eb994c5987a01796970b207a9f Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 1 Oct 2026 15:13:39 +0200 Subject: [PATCH 1/2] tracing: write PCAP timestamps in the announced nanosecond resolution fixed: the PCAP sink announced nanosecond resolution (magic 0xa1b23c4d) but wrote microseconds, so Wireshark and other tools showed timestamps 1000 times too small; it now writes nanoseconds changed: the PCAP reader honors the resolution of the magic number and also accepts microsecond files (0xa1b2c3d4), e.g., captured by Wireshark or tcpdump note: files written by earlier SIL Kit versions contain microseconds under the nanosecond magic; the reader now reads their timestamps 1000 times too small, like every other PCAP tool already did added: Test_Pcap cases for both resolutions Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Konrad Breitsprecher --- SilKit/source/tracing/Pcap.hpp | 12 ++++---- SilKit/source/tracing/PcapReader.cpp | 13 +++++++-- SilKit/source/tracing/PcapReader.hpp | 1 + SilKit/source/tracing/PcapSink.cpp | 9 +++--- SilKit/source/tracing/Test_Pcap.cpp | 41 ++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 13 deletions(-) diff --git a/SilKit/source/tracing/Pcap.hpp b/SilKit/source/tracing/Pcap.hpp index 8e4a30f57..55c276d95 100644 --- a/SilKit/source/tracing/Pcap.hpp +++ b/SilKit/source/tracing/Pcap.hpp @@ -9,7 +9,9 @@ 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; @@ -29,10 +31,10 @@ static_assert(sizeof(GlobalHeader) == GlobalHeaderSize, "GlobalHeader size must 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..6f8271f5d 100755 --- a/SilKit/source/tracing/PcapReader.cpp +++ b/SilKit/source/tracing/PcapReader.cpp @@ -137,7 +137,15 @@ 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) + if (hdr->magic_number == Pcap::NativeMagic) + { + _nsPerTimestampFraction = 1; + } + else if (hdr->magic_number == Pcap::MicrosecondMagic) + { + _nsPerTimestampFraction = 1000; + } + else { throw SilKitError("PCAP file cannot be opened: invalid PCAP valid magic number"); } @@ -181,7 +189,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..0323b9380 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,38 @@ 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)); +} + } // namespace From cbbd033591092908baf1ce102e01d0804b4f828a Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Fri, 2 Oct 2026 15:40:03 +0200 Subject: [PATCH 2/2] tracing: derive the PCAP timestamp mode from the global header Add Pcap::TimestampMode and GlobalHeader::GetTimestampMode(), so code reading PCAP headers does not need to know the magic numbers. An unknown magic number throws a SilKitError. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Konrad Breitsprecher --- SilKit/source/tracing/Pcap.hpp | 22 ++++++++++++++++++++++ SilKit/source/tracing/PcapReader.cpp | 13 +++++-------- SilKit/source/tracing/Test_Pcap.cpp | 5 +++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/SilKit/source/tracing/Pcap.hpp b/SilKit/source/tracing/Pcap.hpp index 55c276d95..dd9f7ff0d 100644 --- a/SilKit/source/tracing/Pcap.hpp +++ b/SilKit/source/tracing/Pcap.hpp @@ -5,6 +5,8 @@ #pragma once #include +#include "silkit/participant/exception.hpp" + namespace SilKit { namespace Tracing { namespace Pcap { @@ -17,6 +19,13 @@ 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 */ @@ -26,6 +35,19 @@ 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"); diff --git a/SilKit/source/tracing/PcapReader.cpp b/SilKit/source/tracing/PcapReader.cpp index 6f8271f5d..942a73034 100755 --- a/SilKit/source/tracing/PcapReader.cpp +++ b/SilKit/source/tracing/PcapReader.cpp @@ -137,17 +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()) { + case Pcap::TimestampMode::Nanoseconds: _nsPerTimestampFraction = 1; - } - else if (hdr->magic_number == Pcap::MicrosecondMagic) - { + break; + case Pcap::TimestampMode::Microseconds: _nsPerTimestampFraction = 1000; - } - else - { - throw SilKitError("PCAP file cannot be opened: invalid PCAP valid magic number"); + break; } if ((hdr->version_major != Pcap::MajorVersion) && (hdr->version_minor != Pcap::MinorVersion)) { diff --git a/SilKit/source/tracing/Test_Pcap.cpp b/SilKit/source/tracing/Test_Pcap.cpp index 0323b9380..7d425f8eb 100755 --- a/SilKit/source/tracing/Test_Pcap.cpp +++ b/SilKit/source/tracing/Test_Pcap.cpp @@ -127,4 +127,9 @@ TEST(Test_Pcap, read_microsecond_timestamps) 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