/* * Copyright (c) 2026, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "nexus_pcap.hpp" #include "nexus_utils.hpp" #include "common/clearable.hpp" #include "common/code_utils.hpp" #include "common/encoding.hpp" namespace ot { namespace Nexus { #ifndef __EMSCRIPTEN__ OT_TOOL_PACKED_BEGIN struct Epb { uint32_t mBlockType; uint32_t mBlockTotalLength; uint32_t mInterfaceId; uint32_t mTimestampHigh; uint32_t mTimestampLow; uint32_t mCapturedLen; uint32_t mOriginalLen; } OT_TOOL_PACKED_END; Pcap::Pcap(void) : mFile(nullptr) { } Pcap::~Pcap(void) { Close(); } void Pcap::Open(const char *aFilename) { OT_TOOL_PACKED_BEGIN struct Shb { uint32_t mBlockType; uint32_t mBlockTotalLength; uint32_t mByteOrderMagic; uint16_t mVersionMajor; uint16_t mVersionMinor; uint64_t mSectionLength; uint32_t mBlockTotalLength2; } OT_TOOL_PACKED_END shb; OT_TOOL_PACKED_BEGIN struct Idb { uint32_t mBlockType; uint32_t mBlockTotalLength; uint16_t mLinkType; uint16_t mReserved; uint32_t mSnapLen; uint32_t mBlockTotalLength2; } OT_TOOL_PACKED_END idb; Close(); mFile = fopen(aFilename, "wb"); VerifyOrExit(mFile != nullptr); ClearAllBytes(shb); shb.mBlockType = LittleEndian::HostSwap(kPcapngShbType); shb.mBlockTotalLength = LittleEndian::HostSwap(static_cast(sizeof(shb))); shb.mByteOrderMagic = LittleEndian::HostSwap(kPcapngByteOrderMagic); shb.mVersionMajor = LittleEndian::HostSwap(kPcapngVersionMajor); shb.mVersionMinor = LittleEndian::HostSwap(kPcapngVersionMinor); shb.mSectionLength = 0xffffffffffffffffULL; shb.mBlockTotalLength2 = shb.mBlockTotalLength; VerifyOrExit(fwrite(&shb, sizeof(shb), 1, mFile) == 1, Close()); ClearAllBytes(idb); idb.mBlockType = LittleEndian::HostSwap(kPcapngIdbType); idb.mBlockTotalLength = LittleEndian::HostSwap(static_cast(sizeof(idb))); idb.mLinkType = LittleEndian::HostSwap(static_cast(kPcapngLinkTypeIeee802154)); idb.mSnapLen = LittleEndian::HostSwap(kPcapngSnapLen); idb.mBlockTotalLength2 = idb.mBlockTotalLength; VerifyOrExit(fwrite(&idb, sizeof(idb), 1, mFile) == 1, Close()); ClearAllBytes(idb); idb.mBlockType = LittleEndian::HostSwap(kPcapngIdbType); idb.mBlockTotalLength = LittleEndian::HostSwap(static_cast(sizeof(idb))); idb.mLinkType = LittleEndian::HostSwap(static_cast(kPcapngLinkTypeEthernet)); idb.mSnapLen = LittleEndian::HostSwap(kPcapngSnapLen); idb.mBlockTotalLength2 = idb.mBlockTotalLength; VerifyOrExit(fwrite(&idb, sizeof(idb), 1, mFile) == 1, Close()); VerifyOrExit(fflush(mFile) == 0, Close()); exit: return; } void Pcap::Close(void) { VerifyOrExit(mFile != nullptr); fclose(mFile); mFile = nullptr; exit: return; } void Pcap::WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs) { Epb epb; OT_TOOL_PACKED_BEGIN struct TapHeader { uint16_t mVersion; uint16_t mLength; } OT_TOOL_PACKED_END tapHeader; uint32_t tapLength; uint32_t packetLen; uint32_t paddedLen; uint32_t blockTotalLength; uint32_t padding = 0; VerifyOrExit(mFile != nullptr); tapLength = sizeof(tapHeader) + 8 + 8; // Note: tshark expects specific lengths for TLVs. // FCS TLV: Type(2), Length(2), Value(1), Padding(3) -> Total 8 bytes. Value length = 1. // Channel TLV: Type(2), Length(2), Channel(2), Page(1), Padding(1) -> Total 8 bytes. Value length = 3. packetLen = tapLength + aFrame.mLength; paddedLen = (packetLen + 3) & ~3u; blockTotalLength = sizeof(epb) + paddedLen + sizeof(uint32_t); ClearAllBytes(epb); epb.mBlockType = LittleEndian::HostSwap(kPcapngEpbType); epb.mBlockTotalLength = LittleEndian::HostSwap(blockTotalLength); epb.mInterfaceId = LittleEndian::HostSwap(0u); epb.mTimestampHigh = LittleEndian::HostSwap(static_cast(aTimeUs >> 32)); epb.mTimestampLow = LittleEndian::HostSwap(static_cast(aTimeUs & 0xffffffff)); epb.mCapturedLen = LittleEndian::HostSwap(packetLen); epb.mOriginalLen = LittleEndian::HostSwap(packetLen); VerifyOrExit(fwrite(&epb, sizeof(epb), 1, mFile) == 1, Close()); ClearAllBytes(tapHeader); tapHeader.mVersion = LittleEndian::HostSwap(static_cast(kTapVersion)); tapHeader.mLength = LittleEndian::HostSwap(static_cast(tapLength)); VerifyOrExit(fwrite(&tapHeader, sizeof(tapHeader), 1, mFile) == 1, Close()); // FCS TLV uint8_t fcsTlv[8]; ClearAllBytes(fcsTlv); LittleEndian::WriteUint16(kTapFcsType, &fcsTlv[0]); LittleEndian::WriteUint16(kTapFcsLength, &fcsTlv[2]); fcsTlv[4] = kTapFcsValue; VerifyOrExit(fwrite(fcsTlv, sizeof(fcsTlv), 1, mFile) == 1, Close()); // Channel TLV uint8_t channelTlv[8]; ClearAllBytes(channelTlv); LittleEndian::WriteUint16(kTapChannelType, &channelTlv[0]); LittleEndian::WriteUint16(kTapChannelLength, &channelTlv[2]); LittleEndian::WriteUint16(aFrame.mChannel, &channelTlv[4]); channelTlv[6] = kTapChannelPage; VerifyOrExit(fwrite(channelTlv, sizeof(channelTlv), 1, mFile) == 1, Close()); VerifyOrExit(fwrite(aFrame.mPsdu, aFrame.mLength, 1, mFile) == 1, Close()); if (paddedLen > packetLen) { VerifyOrExit(fwrite(&padding, paddedLen - packetLen, 1, mFile) == 1, Close()); } VerifyOrExit(fwrite(&epb.mBlockTotalLength, sizeof(epb.mBlockTotalLength), 1, mFile) == 1, Close()); VerifyOrExit(fflush(mFile) == 0, Close()); exit: return; } void Pcap::WritePacket(const InfraIf::LinkLayerAddress &aSrcAddr, const InfraIf::LinkLayerAddress &aDstAddr, const uint8_t *aBuffer, uint16_t aLength, uint64_t aTimeUs) { Epb epb; OT_TOOL_PACKED_BEGIN struct EthernetHeader { uint8_t mDst[6]; uint8_t mSrc[6]; uint16_t mType; } OT_TOOL_PACKED_END; EthernetHeader ethHeader; uint32_t packetLen; uint32_t paddedLen; uint32_t blockTotalLength; uint32_t padding = 0; VerifyOrExit(mFile != nullptr); ClearAllBytes(ethHeader); VerifyOrQuit(aDstAddr.GetLength() >= sizeof(ethHeader.mDst), "Destination MAC address length is less than expected"); memcpy(ethHeader.mDst, aDstAddr.GetBytes(), sizeof(ethHeader.mDst)); VerifyOrQuit(aSrcAddr.GetLength() >= sizeof(ethHeader.mSrc), "Source MAC address length is less than expected"); memcpy(ethHeader.mSrc, aSrcAddr.GetBytes(), sizeof(ethHeader.mSrc)); ethHeader.mType = BigEndian::HostSwap16(kEtherTypeIPv6); packetLen = sizeof(ethHeader) + aLength; paddedLen = (packetLen + 3) & ~3u; blockTotalLength = sizeof(epb) + paddedLen + sizeof(uint32_t); ClearAllBytes(epb); epb.mBlockType = LittleEndian::HostSwap(kPcapngEpbType); epb.mBlockTotalLength = LittleEndian::HostSwap(blockTotalLength); epb.mInterfaceId = LittleEndian::HostSwap(1u); // Use Interface ID 1 for Ethernet epb.mTimestampHigh = LittleEndian::HostSwap(static_cast(aTimeUs >> 32)); epb.mTimestampLow = LittleEndian::HostSwap(static_cast(aTimeUs & 0xffffffff)); epb.mCapturedLen = LittleEndian::HostSwap(packetLen); epb.mOriginalLen = LittleEndian::HostSwap(packetLen); VerifyOrExit(fwrite(&epb, sizeof(epb), 1, mFile) == 1, Close()); VerifyOrExit(fwrite(ðHeader, sizeof(ethHeader), 1, mFile) == 1, Close()); VerifyOrExit(fwrite(aBuffer, aLength, 1, mFile) == 1, Close()); if (paddedLen > packetLen) { VerifyOrExit(fwrite(&padding, paddedLen - packetLen, 1, mFile) == 1, Close()); } VerifyOrExit(fwrite(&epb.mBlockTotalLength, sizeof(epb.mBlockTotalLength), 1, mFile) == 1, Close()); VerifyOrExit(fflush(mFile) == 0, Close()); exit: return; } #else // __EMSCRIPTEN__ Pcap::Pcap(void) {} Pcap::~Pcap(void) {} void Pcap::Open(const char *) {} void Pcap::Close(void) {} void Pcap::WriteFrame(const otRadioFrame &, uint64_t) {} void Pcap::WritePacket(const InfraIf::LinkLayerAddress &, const InfraIf::LinkLayerAddress &, const uint8_t *, uint16_t, uint64_t) { } #endif // __EMSCRIPTEN__ } // namespace Nexus } // namespace ot