[nexus] enable PCAP output (#12350)

This commit enables PCAP output in the Nexus platform by adding a
Nexus::Pcap utility class and integrating it into the Nexus::Core.
PCAP logging can be enabled by setting the OT_NEXUS_PCAP_FILE
environment variable.
This commit is contained in:
Jonathan Hui
2026-01-30 12:52:06 -08:00
committed by GitHub
parent 3318ae688e
commit 5051fd1808
6 changed files with 276 additions and 6 deletions
+1
View File
@@ -46,6 +46,7 @@ add_library(ot-nexus-platform
platform/nexus_mdns.cpp
platform/nexus_misc.cpp
platform/nexus_node.cpp
platform/nexus_pcap.cpp
platform/nexus_radio.cpp
platform/nexus_settings.cpp
platform/nexus_trel.cpp
+18 -5
View File
@@ -27,6 +27,9 @@
*/
#include "nexus_core.hpp"
#include <cstdlib>
#include "nexus_node.hpp"
namespace ot {
@@ -40,11 +43,20 @@ Core::Core(void)
, mCurNodeId(0)
, mPendingAction(false)
{
const char *pcapFile;
VerifyOrQuit(!sInUse);
sCore = this;
sInUse = true;
mNextAlarmTime = mNow.GetDistantFuture();
pcapFile = getenv("OT_NEXUS_PCAP_FILE");
if ((pcapFile != nullptr) && (pcapFile[0] != '\0'))
{
mPcap.Open(pcapFile);
}
}
Core::~Core(void) { sInUse = false; }
@@ -134,6 +146,8 @@ void Core::ProcessRadio(Node &aNode)
ackRequested = aNode.mRadio.mTxFrame.GetAckRequest();
mPcap.WriteFrame(aNode.mRadio.mTxFrame, mNow.GetValue() * 1000ull);
otPlatRadioTxStarted(&aNode.GetInstance(), &aNode.mRadio.mTxFrame);
for (Node &rxNode : mNodes)
@@ -187,16 +201,15 @@ void Core::ProcessRadio(Node &aNode)
if (ackMode != kNoAck)
{
Mac::TxFrame ackFrame;
uint8_t ackPsdu[Mac::Frame::kImmAckLength];
ClearAllBytes(ackFrame);
ackFrame.mPsdu = ackPsdu;
Radio::Frame ackFrame;
ackFrame.GenerateImmAck(
static_cast<const Mac::RxFrame &>(static_cast<const Mac::Frame &>(aNode.mRadio.mTxFrame)),
(ackMode == kSendAckFramePending));
ackFrame.UpdateFcs();
mPcap.WriteFrame(ackFrame, mNow.GetValue() * 1000ull);
otPlatRadioTxDone(&aNode.GetInstance(), &aNode.mRadio.mTxFrame, &ackFrame, kErrorNone);
}
else
+2
View File
@@ -33,6 +33,7 @@
#include "instance/instance.hpp"
#include "nexus_alarm.hpp"
#include "nexus_pcap.hpp"
#include "nexus_radio.hpp"
#include "nexus_utils.hpp"
@@ -85,6 +86,7 @@ private:
static bool sInUse;
OwningList<Node> mNodes;
Pcap mPcap;
uint16_t mCurNodeId;
bool mPendingAction;
TimeMilli mNow;
+165
View File
@@ -0,0 +1,165 @@
/*
* 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 "common/clearable.hpp"
#include "common/code_utils.hpp"
#include "common/encoding.hpp"
namespace ot {
namespace Nexus {
Pcap::Pcap(void)
: mFile(nullptr)
{
}
Pcap::~Pcap(void) { Close(); }
void Pcap::Open(const char *aFilename)
{
OT_TOOL_PACKED_BEGIN
struct PcapHeader
{
uint32_t mMagicNumber;
uint16_t mVersionMajor;
uint16_t mVersionMinor;
int32_t mThisZone;
uint32_t mSigFigs;
uint32_t mSnapLen;
uint32_t mNetwork;
} OT_TOOL_PACKED_END header;
Close();
mFile = fopen(aFilename, "wb");
VerifyOrExit(mFile != nullptr);
ClearAllBytes(header);
header.mMagicNumber = LittleEndian::HostSwap(kPcapMagicNumber);
header.mVersionMajor = LittleEndian::HostSwap(kPcapVersionMajor);
header.mVersionMinor = LittleEndian::HostSwap(kPcapVersionMinor);
header.mSnapLen = LittleEndian::HostSwap(kPcapSnapLen);
header.mNetwork = LittleEndian::HostSwap(kPcapDlt154Tap);
VerifyOrExit(fwrite(&header, sizeof(header), 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)
{
OT_TOOL_PACKED_BEGIN
struct PcapRecordHeader
{
uint32_t mTsSec;
uint32_t mTsUsec;
uint32_t mInclLen;
uint32_t mOrigLen;
} OT_TOOL_PACKED_END recordHeader;
OT_TOOL_PACKED_BEGIN
struct TapHeader
{
uint8_t mVersion;
uint8_t mReserved;
uint16_t mLength;
} OT_TOOL_PACKED_END tapHeader;
OT_TOOL_PACKED_BEGIN
struct TapFcsTlv
{
uint16_t mType;
uint16_t mLength;
uint8_t mValue;
uint8_t mPadding[3];
} OT_TOOL_PACKED_END fcsTlv;
OT_TOOL_PACKED_BEGIN
struct TapChannelTlv
{
uint16_t mType;
uint16_t mLength;
uint8_t mPage;
uint16_t mChannel;
uint8_t mPadding[1];
} OT_TOOL_PACKED_END channelTlv;
uint32_t tapLength;
VerifyOrExit(mFile != nullptr);
tapLength = sizeof(tapHeader) + sizeof(fcsTlv) + sizeof(channelTlv);
ClearAllBytes(recordHeader);
recordHeader.mTsSec = LittleEndian::HostSwap(static_cast<uint32_t>(aTimeUs / 1000000));
recordHeader.mTsUsec = LittleEndian::HostSwap(static_cast<uint32_t>(aTimeUs % 1000000));
recordHeader.mInclLen = LittleEndian::HostSwap(tapLength + aFrame.mLength);
recordHeader.mOrigLen = recordHeader.mInclLen;
VerifyOrExit(fwrite(&recordHeader, sizeof(recordHeader), 1, mFile) == 1, Close());
ClearAllBytes(tapHeader);
tapHeader.mVersion = LittleEndian::HostSwap(kTapVersion);
tapHeader.mLength = LittleEndian::HostSwap(static_cast<uint16_t>(tapLength));
VerifyOrExit(fwrite(&tapHeader, sizeof(tapHeader), 1, mFile) == 1, Close());
ClearAllBytes(fcsTlv);
fcsTlv.mType = LittleEndian::HostSwap(kTapFcsType);
fcsTlv.mLength = LittleEndian::HostSwap(kTapFcsLength);
fcsTlv.mValue = LittleEndian::HostSwap(kTapFcsValue);
VerifyOrExit(fwrite(&fcsTlv, sizeof(fcsTlv), 1, mFile) == 1, Close());
ClearAllBytes(channelTlv);
channelTlv.mType = LittleEndian::HostSwap(kTapChannelType);
channelTlv.mLength = LittleEndian::HostSwap(kTapChannelLength);
channelTlv.mPage = LittleEndian::HostSwap(kTapChannelPage);
channelTlv.mChannel = LittleEndian::HostSwap(aFrame.mChannel);
VerifyOrExit(fwrite(&channelTlv, sizeof(channelTlv), 1, mFile) == 1, Close());
VerifyOrExit(fwrite(aFrame.mPsdu, aFrame.mLength, 1, mFile) == 1, Close());
VerifyOrExit(fflush(mFile) == 0, Close());
exit:
return;
}
} // namespace Nexus
} // namespace ot
+89
View File
@@ -0,0 +1,89 @@
/*
* 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.
*/
#ifndef OT_NEXUS_PLATFORM_NEXUS_PCAP_HPP_
#define OT_NEXUS_PLATFORM_NEXUS_PCAP_HPP_
#include <cstdint>
#include <cstdio>
#include <openthread/platform/radio.h>
namespace ot {
namespace Nexus {
class Pcap
{
public:
Pcap(void);
~Pcap(void);
/**
* Opens a pcap file.
*
* @param[in] aFilename The filename to open.
*/
void Open(const char *aFilename);
/**
* Closes the pcap file.
*/
void Close(void);
/**
* Writes a frame to the pcap file.
*
* @param[in] aFrame The frame to write.
* @param[in] aTimeUs The timestamp in microseconds.
*/
void WriteFrame(const otRadioFrame &aFrame, uint64_t aTimeUs);
private:
static constexpr uint32_t kPcapMagicNumber = 0xa1b2c3d4;
static constexpr uint16_t kPcapVersionMajor = 2;
static constexpr uint16_t kPcapVersionMinor = 4;
static constexpr uint32_t kPcapSnapLen = 65535;
static constexpr uint32_t kPcapDlt154Tap = 283;
static constexpr uint8_t kTapVersion = 0;
static constexpr uint16_t kTapFcsType = 0;
static constexpr uint16_t kTapFcsLength = 1;
static constexpr uint8_t kTapFcsValue = 1; // 16-bit CRC
static constexpr uint16_t kTapChannelType = 3;
static constexpr uint16_t kTapChannelLength = 3;
static constexpr uint8_t kTapChannelPage = 0;
FILE *mFile;
};
} // namespace Nexus
} // namespace ot
#endif // OT_NEXUS_PLATFORM_NEXUS_PCAP_HPP_
+1 -1
View File
@@ -50,7 +50,7 @@ struct Radio
static constexpr State kStateReceive = OT_RADIO_STATE_RECEIVE;
static constexpr State kStateTransmit = OT_RADIO_STATE_TRANSMIT;
struct Frame : public Mac::Frame
struct Frame : public Mac::TxFrame
{
Frame(void);
explicit Frame(const Frame &aFrame);