blob: 871c8b892c01bfd5dab53a5f8293cc298e2837d2 [file]
/*
* 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 {
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<uint32_t>(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<uint32_t>(sizeof(idb)));
idb.mLinkType = LittleEndian::HostSwap(static_cast<uint16_t>(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<uint32_t>(sizeof(idb)));
idb.mLinkType = LittleEndian::HostSwap(static_cast<uint16_t>(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<uint32_t>(aTimeUs >> 32));
epb.mTimestampLow = LittleEndian::HostSwap(static_cast<uint32_t>(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<uint16_t>(kTapVersion));
tapHeader.mLength = LittleEndian::HostSwap(static_cast<uint16_t>(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 otPlatInfraIfLinkLayerAddress &aSrcAddr,
const otPlatInfraIfLinkLayerAddress &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.mLength >= sizeof(ethHeader.mDst), "Destination MAC address length is less than expected");
memcpy(ethHeader.mDst, aDstAddr.mAddress, sizeof(ethHeader.mDst));
VerifyOrQuit(aSrcAddr.mLength >= sizeof(ethHeader.mSrc), "Source MAC address length is less than expected");
memcpy(ethHeader.mSrc, aSrcAddr.mAddress, 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<uint32_t>(aTimeUs >> 32));
epb.mTimestampLow = LittleEndian::HostSwap(static_cast<uint32_t>(aTimeUs & 0xffffffff));
epb.mCapturedLen = LittleEndian::HostSwap(packetLen);
epb.mOriginalLen = LittleEndian::HostSwap(packetLen);
VerifyOrExit(fwrite(&epb, sizeof(epb), 1, mFile) == 1, Close());
VerifyOrExit(fwrite(&ethHeader, 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;
}
} // namespace Nexus
} // namespace ot