diff --git a/CHANGES.md b/CHANGES.md index 93cdb2745..0a8bc1fdd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ Changes from ns-3.41 to ns-3-dev -------------------------------- ### New API +* (wifi) New trace helper `WifiPhyRxTraceHelper` for detailed tracing of Wi-Fi Phy reception events * (wifi) New trace sources `SpectrumWifiPhy::SignalTransmission`, `YansWifiPhy::SignalArrival`, and `YansWifiPhy::SignalTransmission` * (wifi) New trace sources `WifiPhyStateHelper::RxOutcome` and`WifiPhy::PhyRxPpduDrop`, to support additional tracing. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d2823e3b8..0358e357d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -24,7 +24,7 @@ Python API requires [Cppyy](https://cppyy.readthedocs.io/en/latest/installation. been tested on Linux. The latest known version to work with ns-3 is cppyy==3.1.2. ### New user-visible features - +- (wifi) 1901! - Added WifiPhyRxTraceHelper for Wi-Fi Phy tracing - (network) !1828 - Added a common helper to create and install applications - (lr-wpan) !1915 - Use MAC and PHY standard attribute ids - (lr-wpan) !1924 - Adds MAC attribute identifiers diff --git a/src/wifi/CMakeLists.txt b/src/wifi/CMakeLists.txt index 2412074bd..3ae57c4da 100644 --- a/src/wifi/CMakeLists.txt +++ b/src/wifi/CMakeLists.txt @@ -12,6 +12,7 @@ set(source_files helper/wifi-mac-helper.cc helper/wifi-radio-energy-model-helper.cc helper/yans-wifi-helper.cc + helper/wifi-phy-rx-trace-helper.cc model/addba-extension.cc model/adhoc-wifi-mac.cc model/ampdu-subframe-header.cc @@ -170,6 +171,7 @@ set(header_files helper/wifi-mac-helper.h helper/wifi-radio-energy-model-helper.h helper/yans-wifi-helper.h + helper/wifi-phy-rx-trace-helper.h model/addba-extension.h model/adhoc-wifi-mac.h model/ampdu-subframe-header.h diff --git a/src/wifi/helper/wifi-phy-rx-trace-helper.cc b/src/wifi/helper/wifi-phy-rx-trace-helper.cc new file mode 100644 index 000000000..975ef2da8 --- /dev/null +++ b/src/wifi/helper/wifi-phy-rx-trace-helper.cc @@ -0,0 +1,1094 @@ +/* + * Copyright (c) 2023 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "wifi-phy-rx-trace-helper.h" + +#include "ns3/boolean.h" +#include "ns3/config.h" +#include "ns3/fatal-error.h" +#include "ns3/frame-exchange-manager.h" +#include "ns3/log.h" +#include "ns3/multi-model-spectrum-channel.h" +#include "ns3/net-device-container.h" +#include "ns3/node-container.h" +#include "ns3/node-list.h" +#include "ns3/pointer.h" +#include "ns3/simulator.h" +#include "ns3/spectrum-wifi-phy.h" +#include "ns3/wifi-mac-header.h" +#include "ns3/wifi-mac-queue.h" +#include "ns3/wifi-mac.h" +#include "ns3/wifi-mode.h" +#include "ns3/wifi-net-device.h" +#include "ns3/wifi-psdu.h" +#include "ns3/wifi-spectrum-signal-parameters.h" +#include "ns3/yans-wifi-phy.h" + +#include +#include +#include +#include +#include + +namespace ns3 +{ + +NS_LOG_COMPONENT_DEFINE("WifiPhyRxTraceHelper"); + +/** + * Number of places to shift WifiPpdu UID values, when generating unique IDs + */ +const uint32_t SHIFT = 16; + +WifiPhyRxTraceHelper::WifiPhyRxTraceHelper() +{ + NS_LOG_FUNCTION(this); +} + +void +WifiPhyRxTraceHelper::Enable(NodeContainer nodes) +{ + NS_LOG_FUNCTION(this << nodes.GetN()); + Enable(nodes, MapMacAddressesToNodeIds(nodes)); +} + +void +WifiPhyRxTraceHelper::Enable(NetDeviceContainer netDevices) +{ + NS_LOG_FUNCTION(this << netDevices.GetN()); + NodeContainer nodes; + // filter netDevice entries through a set to prevent duplicate entries in the NodeContainer + std::set> nodesSeen; + for (uint32_t i = 0; i < netDevices.GetN(); i++) + { + auto nodePtr = netDevices.Get(i)->GetNode(); + if (nodesSeen.count(nodePtr) == 0) + { + nodesSeen.insert(nodePtr); + nodes.Add(nodePtr); + } + } + Enable(nodes, MapMacAddressesToNodeIds(nodes)); +} + +void +WifiPhyRxTraceHelper::Enable(NodeContainer nodes, + const std::map& macToNodeMap) +{ + NS_LOG_FUNCTION(this << nodes.GetN() << macToNodeMap.size()); + NS_ABORT_MSG_IF(m_traceSink, "A trace sink is already configured for this helper"); + m_traceSink = CreateObject(); + m_traceSink->SetMapMacAddressToNodeId(macToNodeMap); + for (uint32_t nodeId = 0; nodeId < nodes.GetN(); nodeId++) + { + for (uint32_t deviceId = 0; deviceId < nodes.Get(nodeId)->GetNDevices(); deviceId++) + { + auto wifiDevice = DynamicCast(nodes.Get(nodeId)->GetDevice(deviceId)); + if (wifiDevice) + { + for (uint8_t i = 0; i < wifiDevice->GetNPhys(); i++) + { + auto phy = wifiDevice->GetPhy(i); + auto yansPhy = DynamicCast(phy); + auto spectrumPhy = DynamicCast(phy); + NS_ASSERT_MSG(yansPhy || spectrumPhy, "Phy type not found"); + const std::string context = + "/NodeList/" + std::to_string(wifiDevice->GetNode()->GetId()) + + "/DeviceList/" + std::to_string(wifiDevice->GetIfIndex()) + "/Phys/" + + std::to_string(i); + auto connected = false; + if (yansPhy) + { + connected = yansPhy->TraceConnect( + "SignalArrival", + context, + MakeCallback(&WifiPhyRxTraceSink::PhySignalArrival, m_traceSink)); + } + else if (spectrumPhy) + { + connected = spectrumPhy->TraceConnect( + "SignalArrival", + context, + MakeCallback(&WifiPhyRxTraceSink::SpectrumPhySignalArrival, + m_traceSink)); + } + else + { + NS_FATAL_ERROR("Phy type not found"); + } + NS_ASSERT_MSG(connected, "Trace source not connected"); + connected = phy->TraceConnect( + "SignalTransmission", + context, + MakeCallback(&WifiPhyRxTraceSink::PhySignalTransmission, m_traceSink)); + NS_ASSERT_MSG(connected, "Trace source not connected"); + // Log packet drops + connected = phy->TraceConnect( + "PhyRxPpduDrop", + context, + MakeCallback(&WifiPhyRxTraceSink::PhyPpduDrop, m_traceSink)); + NS_ASSERT_MSG(connected, "Trace source not connected"); + // Trace PHY Outcome events + connected = phy->GetState()->TraceConnect( + "RxOutcome", + context, + MakeCallback(&WifiPhyRxTraceSink::PpduOutcome, m_traceSink)); + NS_ASSERT_MSG(connected, "Trace source not connected"); + } + } + } + } + + // If a map is provided use map if not populate it yourself + if (!macToNodeMap.empty()) + { + m_traceSink->SetMapMacAddressToNodeId(macToNodeMap); + } + // A linkID in one device might not be the same link on another device, need to create a mapping + // from node to link + m_traceSink->MapNodeToLinkToChannel(nodes); + + if (!m_traceSink->IsCollectionPeriodActive()) + { + NS_LOG_DEBUG("Connected traces but collection period is not active"); + } +} + +void +WifiPhyRxTraceHelper::PrintStatistics() const +{ + m_traceSink->PrintStatistics(); +} + +void +WifiPhyRxTraceHelper::PrintStatistics(Ptr node, uint32_t deviceId, uint8_t linkId) const +{ + m_traceSink->PrintStatistics(node->GetId(), deviceId, linkId); +} + +void +WifiPhyRxTraceHelper::PrintStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + m_traceSink->PrintStatistics(nodeId, deviceId, linkId); +} + +void +WifiPhyRxTraceHelper::Start(Time startTime) +{ + NS_LOG_FUNCTION(this << startTime.As(Time::S)); + Simulator::Schedule(startTime, &WifiPhyRxTraceSink::Start, m_traceSink); +} + +void +WifiPhyRxTraceHelper::Stop(Time stopTime) +{ + NS_LOG_FUNCTION(this << stopTime.As(Time::S)); + Simulator::Schedule(stopTime, &WifiPhyRxTraceSink::Stop, m_traceSink); +} + +const std::vector& +WifiPhyRxTraceHelper::GetPpduRecords() const +{ + m_traceSink->CreateVectorFromRecords(); + return m_traceSink->GetPpduRecords(); +} + +std::optional>> +WifiPhyRxTraceHelper::GetPpduRecords(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + return m_traceSink->GetPpduRecords(nodeId, deviceId, linkId); +} + +void +WifiPhyRxTraceHelper::Reset() +{ + NS_LOG_FUNCTION(this); + NS_LOG_INFO("Reset WifiPhyRxTraceHelper"); + m_traceSink->Reset(); +} + +WifiPhyTraceStatistics +WifiPhyRxTraceHelper::GetStatistics() const +{ + return m_traceSink->GetStatistics(); +} + +WifiPhyTraceStatistics +WifiPhyRxTraceHelper::GetStatistics(Ptr node, uint32_t deviceId, uint8_t linkId) const +{ + return m_traceSink->GetStatistics(node->GetId(), deviceId, linkId); +} + +WifiPhyTraceStatistics +WifiPhyRxTraceHelper::GetStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + return m_traceSink->GetStatistics(nodeId, deviceId, linkId); +} + +std::map +WifiPhyRxTraceHelper::MapMacAddressesToNodeIds(NodeContainer nodes) const +{ + std::map macAddressToNodeId; + for (uint32_t i = 0; i < nodes.GetN(); i++) + { + for (uint32_t device = 0; device < nodes.Get(i)->GetNDevices(); device++) + { + if (auto wdev = DynamicCast(nodes.Get(i)->GetDevice(device))) + { + for (uint32_t link = 0; link < wdev->GetNPhys(); link++) + { + macAddressToNodeId[Mac48Address::ConvertFrom( + wdev->GetMac()->GetFrameExchangeManager(link)->GetAddress())] = + nodes.Get(i)->GetId(); + NS_LOG_DEBUG("NodeID: " + << nodes.Get(i)->GetId() << " DeviceID: " << device + << " LinkId: " << link << " MAC: " + << wdev->GetMac()->GetFrameExchangeManager(link)->GetAddress()); + } + } + } + } + return macAddressToNodeId; +} + +NS_OBJECT_ENSURE_REGISTERED(WifiPhyRxTraceSink); + +TypeId +WifiPhyRxTraceSink::GetTypeId() +{ + static TypeId tid = TypeId("ns3::WifiPhyRxTraceSink") + .SetParent() + .SetGroupName("Wifi") + .AddConstructor(); + return tid; +} + +WifiPhyRxTraceSink::WifiPhyRxTraceSink() +{ + NS_LOG_FUNCTION(this); +} + +void +WifiPhyRxTraceSink::SetMapMacAddressToNodeId( + const std::map& MacAddressToNodeIdMap) +{ + m_macAddressToNodeId = MacAddressToNodeIdMap; +} + +uint32_t +WifiPhyRxTraceSink::ContextToNodeId(std::string context) const +{ + const auto sub = context.substr(10); + const auto pos = sub.find("/Device"); + return std::stoi(sub.substr(0, pos)); +} + +int +WifiPhyRxTraceSink::ContextToLinkId(std::string context) const +{ + const auto pos = context.find("/Phys/"); + const auto sub = context.substr(pos + 6); + const auto nextSlashPos = sub.find('/'); + if (nextSlashPos == std::string::npos) + { + return std::stoi(sub); + } + const auto physNumberStr = sub.substr(0, nextSlashPos); + return std::stoi(physNumberStr); +} + +int +WifiPhyRxTraceSink::ContextToDeviceId(std::string context) const +{ + auto pos = context.find("/DeviceList/"); + pos += 12; + const auto sub = context.substr(pos); + const auto nextSlashPos = sub.find('/'); + const auto deviceIdStr = sub.substr(0, nextSlashPos); + return std::stoi(deviceIdStr); +} + +std::string +WifiPhyRxTraceSink::ContextToTuple(std::string context) const +{ + const auto str = std::to_string(ContextToNodeId(context)) + ":" + + std::to_string(ContextToDeviceId(context)) + ":" + + std::to_string(ContextToLinkId(context)); + return str; +} + +// Each WifiPpdu has a UID, but more than one trace record can exist for a unique WifiPpdu +// This generator shifts the WifiPpdu UID leftward and combines it with a member variable +// counter to generate more than one unique ID for each WifiPpdu UID +uint64_t +WifiPhyRxTraceSink::UniqueTagGenerator::GenerateUniqueTag(uint64_t ppduUid) +{ + uint64_t tag; + do + { + tag = (ppduUid << SHIFT) | counter++; + } while (usedTags.find(tag) != usedTags.end()); + usedTags.insert(tag); + NS_LOG_DEBUG("Generating tag " << tag << " from ppdu UID " << ppduUid); + return tag; +} + +bool +operator==(const WifiPpduRxRecord& lhs, const WifiPpduRxRecord& rhs) +{ + return lhs.m_rxTag == rhs.m_rxTag; +} + +bool +operator!=(const WifiPpduRxRecord& lhs, const WifiPpduRxRecord& rhs) +{ + return !(lhs == rhs); +} + +bool +operator<(const WifiPpduRxRecord& lhs, const WifiPpduRxRecord& rhs) +{ + return lhs.m_rxTag < rhs.m_rxTag; +} + +WifiPhyTraceStatistics +operator+(const WifiPhyTraceStatistics& lhs, const WifiPhyTraceStatistics& rhs) +{ + WifiPhyTraceStatistics result; + result.m_overlappingPpdus = lhs.m_overlappingPpdus + rhs.m_overlappingPpdus; + result.m_nonOverlappingPpdus = lhs.m_nonOverlappingPpdus + rhs.m_nonOverlappingPpdus; + result.m_failedPpdus = lhs.m_failedPpdus + rhs.m_failedPpdus; + result.m_receivedPpdus = lhs.m_receivedPpdus + rhs.m_receivedPpdus; + result.m_receivedMpdus = lhs.m_receivedMpdus + rhs.m_receivedMpdus; + result.m_failedMpdus = lhs.m_failedMpdus + rhs.m_failedMpdus; + + // Merge map fields + for (const auto& pair : lhs.m_ppduDropReasons) + { + result.m_ppduDropReasons[pair.first] += pair.second; + } + for (const auto& pair : rhs.m_ppduDropReasons) + { + result.m_ppduDropReasons[pair.first] += pair.second; + } + return result; +} + +bool +operator==(const WifiPhyTraceStatistics& lhs, const WifiPhyTraceStatistics& rhs) +{ + return lhs.m_overlappingPpdus == rhs.m_overlappingPpdus && + lhs.m_nonOverlappingPpdus == rhs.m_nonOverlappingPpdus && + lhs.m_failedPpdus == rhs.m_failedPpdus && lhs.m_receivedPpdus == rhs.m_receivedPpdus && + lhs.m_receivedMpdus == rhs.m_receivedMpdus && lhs.m_failedMpdus == rhs.m_failedMpdus && + lhs.m_ppduDropReasons == rhs.m_ppduDropReasons; +} + +bool +operator!=(const WifiPhyTraceStatistics& lhs, const WifiPhyTraceStatistics& rhs) +{ + return !(lhs == rhs); +} + +void +WifiPhyRxTraceSink::MapNodeToLinkToChannel(NodeContainer nodes) +{ + for (uint32_t i = 0; i < nodes.GetN(); i++) + { + for (uint32_t device = 0; device < nodes.Get(i)->GetNDevices(); device++) + { + auto wdev = DynamicCast(nodes.Get(i)->GetDevice(device)); + if (wdev) + { + for (uint32_t link = 0; link < wdev->GetNPhys(); link++) + { + m_nodeToDeviceToLinkToChannelInfo[nodes.Get(i)->GetId()][wdev->GetIfIndex()] + [link] = std::make_pair( + wdev->GetPhy(link)->GetChannelNumber(), + wdev->GetPhy(link)->GetFrequency()); + + NS_LOG_DEBUG("NodeId: " + << nodes.Get(i)->GetId() << " DeviceID: " << wdev->GetIfIndex() + << " LinkId: " << link + << " freq: " << wdev->GetPhy(link)->GetFrequency() + << " ch#: " << int(wdev->GetPhy(link)->GetChannelNumber())); + } + } + } + } +} + +// channel number, frequency +std::optional> +WifiPhyRxTraceSink::GetChannelInfo(uint32_t nodeId, uint32_t deviceId, int link) const +{ + auto nodeIter = m_nodeToDeviceToLinkToChannelInfo.find(nodeId); + if (nodeIter != m_nodeToDeviceToLinkToChannelInfo.end()) + { + auto& deviceMap = nodeIter->second; + auto deviceIter = deviceMap.find(deviceId); + if (deviceIter != deviceMap.end()) + { + auto& linkMap = deviceIter->second; + auto linkIter = linkMap.find(link); + if (linkIter != linkMap.end()) + { + return std::optional>(linkIter->second); + } + } + } + return std::nullopt; +} + +// TODO: Profile this and possibly refactor to avoid duplicates +void +WifiPhyRxTraceSink::UpdateCurrentlyReceivedSignal(uint32_t nodeId, + uint32_t deviceId, + uint8_t linkId) +{ + NS_LOG_FUNCTION(this << nodeId << deviceId << linkId); + // Add all overlapping packets to the list of overlapping packets for this frame arrival + for (const auto& it : m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(linkId)) + { + for (const auto& iter : m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(linkId)) + { + if (it != iter) + { + m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].emplace_back(iter); + } + } + } + + // Remove duplicates + for (const auto& it : m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(linkId)) + { + // First, sort the vector to bring duplicates together + std::sort(m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].begin(), + m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].end()); + // Use std::unique to remove duplicates (adjacent identical elements) + m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].erase( + std::unique(m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].begin(), + m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].end()), + m_rxTagToListOfOverlappingPpduRecords[it.m_rxTag].end()); + } + NS_LOG_INFO( + "Map of overlapping PPDU records size: " << m_rxTagToListOfOverlappingPpduRecords.size()); +} + +void +WifiPhyRxTraceSink::EndTx(uint32_t nodeId, uint32_t deviceId, WifiPpduRxRecord ppduRecord) +{ + NS_LOG_FUNCTION(this << nodeId << deviceId); + // remove from currently received (in this case transmitted) packets + NS_LOG_INFO("Remove transmit record at " << nodeId << ":" << deviceId << ":" + << +ppduRecord.m_linkId << " UID " + << ppduRecord.m_rxTag); + m_nodeDeviceLinkRxRecords.at(nodeId) + .at(deviceId) + .at(ppduRecord.m_linkId) + .erase( + std::remove( + m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(ppduRecord.m_linkId).begin(), + m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(ppduRecord.m_linkId).end(), + ppduRecord), + m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(ppduRecord.m_linkId).end()); + NS_LOG_INFO("Size of m_nodeDeviceLinkRxRecords: " << m_nodeDeviceLinkRxRecords.size()); + + // Erase the item from rxTagToPpduRecord + m_rxTagToPpduRecord.erase(ppduRecord.m_rxTag); + NS_LOG_INFO("Size of m_rxTagToPpduRecord: " << m_rxTagToPpduRecord.size()); +} + +void +WifiPhyRxTraceSink::PhyRxEnd(uint32_t nodeId, uint32_t deviceId, uint64_t rxTag, uint64_t ppduUid) +{ + NS_LOG_FUNCTION(this << nodeId << deviceId << rxTag << ppduUid); + NS_ASSERT_MSG(m_rxTagToPpduRecord.contains(rxTag), "Missing PPDU at PhyRxEnd"); + // Update the end time on the record, and reinsert to the map + auto ppduRecord = m_rxTagToPpduRecord[rxTag]; + ppduRecord.m_endTime = Simulator::Now(); + auto [it, inserted] = m_rxTagToPpduRecord.insert_or_assign(rxTag, ppduRecord); + NS_ASSERT_MSG(!inserted, "Did not assign successfully"); + + // Update the lists of overlapping PPDUs + UpdateCurrentlyReceivedSignal(nodeId, deviceId, ppduRecord.m_linkId); + for (const auto& it : m_rxTagToListOfOverlappingPpduRecords[rxTag]) + { + ppduRecord.m_overlappingPpdu.emplace_back(it); + } + + NS_LOG_INFO("Remove reception record at " << nodeId << ":" << deviceId << ":" + << +ppduRecord.m_linkId << " UID " << rxTag); + // Remove from map of active records + m_nodeDeviceLinkRxRecords.at(nodeId) + .at(deviceId) + .at(ppduRecord.m_linkId) + .erase( + std::remove( + m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(ppduRecord.m_linkId).begin(), + m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(ppduRecord.m_linkId).end(), + ppduRecord), + m_nodeDeviceLinkRxRecords.at(nodeId).at(deviceId).at(ppduRecord.m_linkId).end()); + NS_LOG_INFO("Size of m_nodeDeviceLinkRxRecords: " << m_nodeDeviceLinkRxRecords.size()); + + // Erase the item from rxTagToPpduRecord + m_rxTagToPpduRecord.erase(rxTag); + NS_LOG_INFO("Size of m_rxTagToPpduRecord: " << m_rxTagToPpduRecord.size()); + + // checks if statistics period has been started before adding to the list of completed records + if (m_statisticsCollectionPeriodStarted) + { + NS_LOG_INFO("Adding PPDU record for " << ppduRecord.m_receiverId << " " << deviceId << " " + << +ppduRecord.m_linkId); + m_completedRecords[ppduRecord.m_receiverId][deviceId][ppduRecord.m_linkId].emplace_back( + ppduRecord); + } + else + { + NS_LOG_DEBUG("Not adding PPDU record (statistics not started) for " + << ppduRecord.m_receiverId << " " << deviceId << " " << ppduRecord.m_linkId); + } +} + +bool +WifiPhyRxTraceSink::IsCollectionPeriodActive() const +{ + return m_statisticsCollectionPeriodStarted; +} + +void +WifiPhyRxTraceSink::Start() +{ + m_statisticsCollectionPeriodStarted = true; +} + +void +WifiPhyRxTraceSink::Stop() +{ + m_statisticsCollectionPeriodStarted = false; +} + +void +WifiPhyRxTraceSink::PhySignalTransmission(std::string context, + Ptr ppdu, + const WifiTxVector& txVector) +{ + NS_LOG_FUNCTION(context << ppdu << txVector); + uint32_t nodeId = ContextToNodeId(context); + uint32_t deviceId = ContextToDeviceId(context); + uint8_t linkId = ContextToLinkId(context); + + WifiPpduRxRecord ppduRecord; + ppduRecord.m_startTime = Simulator::Now(); + ppduRecord.m_endTime = Simulator::Now() + ppdu->GetTxDuration(); + ppduRecord.m_ppdu = ppdu; + ppduRecord.m_senderId = nodeId; + ppduRecord.m_linkId = linkId; + ppduRecord.m_senderDeviceId = deviceId; + + // Tag used to determine which packet needs to be removed from currently received packets + ppduRecord.m_rxTag = m_tagGenerator.GenerateUniqueTag(ppdu->GetUid()); + + NS_LOG_INFO("Transmit at " << ContextToTuple(context) << " insert to tagToRecord map for UID " + << ppduRecord.m_rxTag); + auto [it, inserted] = m_rxTagToPpduRecord.insert_or_assign(ppduRecord.m_rxTag, ppduRecord); + NS_ASSERT_MSG(inserted, "Did not insert successfully"); + NS_LOG_INFO("Size of m_rxTagToPpduRecord: " << m_rxTagToPpduRecord.size()); + + /* + m_rxTagToPpduRecord[ppduRecord.m_rxTag] = ppduRecord; + */ + + // Use this to find who is the sender in the reception side + m_ppduUidToTxTag[ppduRecord.m_ppdu->GetUid()] = ppduRecord.m_rxTag; + + // store record in a map, indexed by nodeId, deviceId, linkId + NS_LOG_INFO("Transmit at " << ContextToTuple(context) + << " insert to active records map for UID " << ppduRecord.m_rxTag); + m_nodeDeviceLinkRxRecords[nodeId][deviceId][linkId].emplace_back(ppduRecord); + NS_LOG_INFO("Size of active records: " << m_nodeDeviceLinkRxRecords.size()); + + Simulator::Schedule(ppduRecord.m_endTime - ppduRecord.m_startTime, + &WifiPhyRxTraceSink::EndTx, + this, + nodeId, + deviceId, + ppduRecord); +} + +void +WifiPhyRxTraceSink::SpectrumPhySignalArrival(std::string context, + Ptr signal, + uint32_t senderNodeId, + double rxPower, + Time duration) +{ + NS_LOG_FUNCTION(context << signal << senderNodeId << rxPower << duration); + const auto wifiSignal = DynamicCast(signal); + const auto ppdu = wifiSignal->ppdu; + + if (!wifiSignal) + { + NS_LOG_DEBUG("Non-WiFi Signal Received"); + return; + } + PhySignalArrival(context, ppdu, rxPower, duration); +} + +void +WifiPhyRxTraceSink::PhySignalArrival(std::string context, + Ptr ppdu, + double rxPower, + Time duration) +{ + NS_LOG_FUNCTION(context << ppdu << rxPower << duration); + uint32_t nodeId = ContextToNodeId(context); + uint32_t deviceId = ContextToDeviceId(context); + uint8_t linkId = ContextToLinkId(context); + + // Associate this received PPDU with a record previously stored on the transmit side, if present + bool hasTxTag = false; + WifiPpduRxRecord txPpduRecord; + if (m_ppduUidToTxTag.contains(ppdu->GetUid())) + { + hasTxTag = true; + txPpduRecord = m_rxTagToPpduRecord[m_ppduUidToTxTag[ppdu->GetUid()]]; + NS_LOG_DEBUG("Arrival RxNodeID: " << nodeId << " SenderID: " << txPpduRecord.m_senderId + << " Received on LinkID: " << +linkId + << " Frame Sent on LinkId: " << +txPpduRecord.m_linkId); + } + else + { + NS_LOG_DEBUG("Arrival RxNodeID: " << nodeId << " Received on LinkID: " << +linkId + << "; no sender info"); + } + if (hasTxTag) + { + auto rxInfo = GetChannelInfo(nodeId, deviceId, linkId); + auto txInfo = GetChannelInfo(txPpduRecord.m_senderId, + txPpduRecord.m_senderDeviceId, + txPpduRecord.m_linkId); + if (!txInfo.has_value()) + { + NS_LOG_DEBUG( + "Didn't find TX record for transmission; possibly from an untraced sender"); + } + else + { + if ((int(txInfo->second) != int(rxInfo->second)) || + (int(txInfo->first) != int(rxInfo->first))) + { + NS_LOG_DEBUG( + "Received Signal on a different frequency or channel number than what was " + "configured for this PHY or link"); + return; + } + } + } + + WifiPpduRxRecord ppduRecord; + ppduRecord.m_startTime = Simulator::Now(); + ppduRecord.m_endTime = Simulator::Now() + duration; + ppduRecord.m_ppdu = ppdu; + ppduRecord.m_rssi = rxPower; + ppduRecord.m_rxTag = m_tagGenerator.GenerateUniqueTag(ppdu->GetUid()); + ppduRecord.m_receiverId = nodeId; + ppduRecord.m_linkId = linkId; + if (hasTxTag) + { + ppduRecord.m_senderId = txPpduRecord.m_senderId; + ppduRecord.m_senderDeviceId = txPpduRecord.m_senderDeviceId; + NS_LOG_INFO( + "Receive at " << ContextToTuple(context) << " from node " << txPpduRecord.m_senderId + << "; insert to active records map for UID " << ppduRecord.m_rxTag); + } + else + { + NS_LOG_INFO("Receive at " << ContextToTuple(context) + << "; insert to active records map for UID " + << ppduRecord.m_rxTag); + } + + // Add to list of currently received frames on this node, device and specific link + m_nodeDeviceLinkRxRecords[nodeId][deviceId][linkId].emplace_back(ppduRecord); + NS_LOG_INFO("Size of active records " << m_nodeDeviceLinkRxRecords.size()); + + UpdateCurrentlyReceivedSignal(nodeId, deviceId, linkId); + + NS_LOG_INFO("Receive at " << ContextToTuple(context) << " insert to tagToRecord map for UID " + << ppduRecord.m_rxTag); + auto [it, inserted] = m_rxTagToPpduRecord.insert_or_assign(ppduRecord.m_rxTag, ppduRecord); + NS_ASSERT_MSG(inserted, "Did not insert successfully"); + NS_LOG_INFO("Size of m_rxTagToPpduRecord " << m_rxTagToPpduRecord.size()); + + NS_LOG_INFO("Receive at " << ContextToTuple(context) << " insert to highly nested map for UID " + << ppduRecord.m_rxTag); + m_nodeDeviceLinkPidToRxId[nodeId][deviceId][linkId][ppduRecord.m_ppdu->GetUid()] = + ppduRecord.m_rxTag; + NS_LOG_INFO("Size of highly nested map " << m_nodeDeviceLinkPidToRxId.size()); +} + +void +WifiPhyRxTraceSink::PhyPpduDrop(std::string context, + Ptr ppdu, + WifiPhyRxfailureReason reason) +{ + NS_LOG_FUNCTION(this << context << ppdu << reason); + uint32_t nodeId = ContextToNodeId(context); + uint32_t deviceId = ContextToDeviceId(context); + uint8_t linkId = ContextToLinkId(context); + WifiPpduRxRecord ppduRecord = + m_rxTagToPpduRecord[m_nodeDeviceLinkPidToRxId[nodeId][deviceId][linkId][ppdu->GetUid()]]; + + if (ppduRecord.m_ppdu == nullptr) + { + NS_LOG_DEBUG("Frame being dropped was not observed on SignalArrival trace. Means it was " + "received on a wrong link configuration"); + return; + } + ppduRecord.m_reason = reason; + + std::vector outcome; + for (const auto& mpdu : *PeekPointer(ppdu->GetPsdu())) + { + mpdu->GetPacket(); + outcome.emplace_back(false); + } + ppduRecord.m_statusPerMpdu = outcome; + m_rxTagToPpduRecord[ppduRecord.m_rxTag] = ppduRecord; + + Simulator::Schedule(ppduRecord.m_endTime - Simulator::Now(), + &WifiPhyRxTraceSink::PhyRxEnd, + this, + nodeId, + deviceId, + ppduRecord.m_rxTag, + ppdu->GetUid()); +} + +void +WifiPhyRxTraceSink::PpduOutcome(std::string context, + Ptr ppdu, + RxSignalInfo signal, + const WifiTxVector& txVector, + const std::vector& statusMpdu) +{ + NS_LOG_FUNCTION(context << ppdu << signal << txVector); + uint32_t nodeId = ContextToNodeId(context); + uint32_t deviceId = ContextToDeviceId(context); + uint8_t linkId = ContextToLinkId(context); + + WifiPpduRxRecord ppduRecord = + m_rxTagToPpduRecord[m_nodeDeviceLinkPidToRxId[nodeId][deviceId][linkId][ppdu->GetUid()]]; + + if (ppduRecord.m_ppdu) + { + NS_LOG_DEBUG("Found an expected frame in the outcome"); + } + else + { + NS_LOG_DEBUG("Frame to be processed was not observed on SignalArrival trace"); + return; + } + // Save the reception status per MPDU in the PPDU record + ppduRecord.m_statusPerMpdu = statusMpdu; + + auto [it, inserted] = m_rxTagToPpduRecord.insert_or_assign(ppduRecord.m_rxTag, ppduRecord); + NS_ASSERT_MSG(!inserted, "Did not assign successfully"); + PhyRxEnd(nodeId, deviceId, ppduRecord.m_rxTag, ppdu->GetUid()); +} + +void +WifiPhyRxTraceSink::CountStatisticsForRecord(WifiPhyTraceStatistics& statistics, + const WifiPpduRxRecord& record) const +{ + // Check if PPDU was dropped and if any MPDU was addressed to the receiver + if (record.m_reason) + { + bool mpduToReceiver = false; + bool shouldCount = true; + Ptr p; + auto it = record.m_statusPerMpdu.begin(); + for (const auto& mpdu : *PeekPointer(record.m_ppdu->GetPsdu())) + { + p = mpdu->GetProtocolDataUnit(); + WifiMacHeader hdr; + p->PeekHeader(hdr); + if (!(hdr.IsData() && + (hdr.GetType() == WIFI_MAC_DATA || hdr.GetType() == WIFI_MAC_QOSDATA))) + { + shouldCount = false; + break; + } + if (!(*it) && (record.m_receiverId == m_macAddressToNodeId.at(hdr.GetAddr1()))) + { + // Failed MPDU + statistics.m_failedMpdus++; + mpduToReceiver = true; + } + ++it; + } + // At least one MPDU addressed to the receiver was dropped, mark PPDU as + // failed + if (mpduToReceiver && shouldCount) + { + statistics.m_failedPpdus++; + // Check if PPDU Overlap + if (!record.m_overlappingPpdu.empty()) + { + // Yes Overlap + statistics.m_overlappingPpdus++; + } + else + { + // No Overlap + statistics.m_nonOverlappingPpdus++; + } + // It is due to a drop, clarify and count the reason + statistics.m_ppduDropReasons[record.m_reason]++; + } + } + else + { + // Payload Decode Attempt (No Drop but still not certain of MPDU outcome) + // MPDU Level + + Ptr p; + auto it = record.m_statusPerMpdu.begin(); + bool mpduFail = false; + bool shouldCount = true; + bool mpduToReceiver = false; + for (const auto& mpdu : *PeekPointer(record.m_ppdu->GetPsdu())) + { + p = mpdu->GetProtocolDataUnit(); + WifiMacHeader hdr; + p->PeekHeader(hdr); + if (!(hdr.IsData() && + (hdr.GetType() == WIFI_MAC_DATA || hdr.GetType() == WIFI_MAC_QOSDATA))) + { + shouldCount = false; + break; + } + if (*it && (m_macAddressToNodeId.contains(hdr.GetAddr1())) && + (record.m_receiverId == m_macAddressToNodeId.at(hdr.GetAddr1()))) + { + // Successful MPDU + statistics.m_receivedMpdus++; + mpduToReceiver = true; + } + else if (!(*it) && (m_macAddressToNodeId.contains(hdr.GetAddr1())) && + (record.m_receiverId == m_macAddressToNodeId.at(hdr.GetAddr1()))) + { + // Failed MPDU + statistics.m_failedMpdus++; + mpduFail = true; + mpduToReceiver = true; + } + ++it; + } + if (!mpduFail && shouldCount && mpduToReceiver) + { + // No drops or payload decode errors for all MPDUs addressed to the + // receiver + statistics.m_receivedPpdus++; + + // Check if PPDU Overlap + if (!record.m_overlappingPpdu.empty()) + { + // Yes Overlap + statistics.m_overlappingPpdus++; + } + else + { + // No Overlap + statistics.m_nonOverlappingPpdus++; + } + } + else if (shouldCount && mpduToReceiver) + { + // At least one MPDU addressed to the receiver failed. + statistics.m_failedPpdus++; + // Check if PPDU Overlap + if (!record.m_overlappingPpdu.empty()) + { + // Yes Overlap + statistics.m_overlappingPpdus++; + } + else + { + // No Overlap + statistics.m_nonOverlappingPpdus++; + } + } + } +} + +WifiPhyTraceStatistics +WifiPhyRxTraceSink::CountStatistics() const +{ + NS_LOG_FUNCTION(this); + WifiPhyTraceStatistics statistics; + for (const auto& nodeMap : m_completedRecords) + { + for (const auto& deviceMap : nodeMap.second) + { + for (const auto& linkMap : deviceMap.second) + { + for (const auto& record : linkMap.second) + { + CountStatisticsForRecord(statistics, record); + } + } + } + } + return statistics; +} + +WifiPhyTraceStatistics +WifiPhyRxTraceSink::CountStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + NS_LOG_FUNCTION(this << nodeId << deviceId << linkId); + WifiPhyTraceStatistics statistics; + if (m_completedRecords.contains(nodeId)) + { + auto mapOfDevices = m_completedRecords.at(nodeId); + if (mapOfDevices.contains(deviceId)) + { + auto mapOfLinks = mapOfDevices.at(deviceId); + if (mapOfLinks.contains(linkId)) + { + auto vectorOfRecords = mapOfLinks.at(linkId); + for (const auto& record : vectorOfRecords) + { + CountStatisticsForRecord(statistics, record); + } + } + } + } + return statistics; +} + +WifiPhyTraceStatistics +WifiPhyRxTraceSink::GetStatistics() const +{ + WifiPhyTraceStatistics stats = CountStatistics(); + return stats; +} + +WifiPhyTraceStatistics +WifiPhyRxTraceSink::GetStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + WifiPhyTraceStatistics stats = CountStatistics(nodeId, deviceId, linkId); + return stats; +} + +void +WifiPhyRxTraceSink::PrintStatistics() const +{ + WifiPhyTraceStatistics statistics = CountStatistics(); + + std::cout << "Total PPDUs Received: " << statistics.m_receivedPpdus + statistics.m_failedPpdus + << std::endl; + std::cout << "Total Non-Overlapping PPDUs Received: " << statistics.m_nonOverlappingPpdus + << std::endl; + std::cout << "Total Overlapping PPDUs Received: " << statistics.m_overlappingPpdus << std::endl; + + std::cout << "\nSuccessful PPDUs: " << statistics.m_receivedPpdus << std::endl; + std::cout << "Failed PPDUs: " << statistics.m_failedPpdus << std::endl; + for (auto reason : statistics.m_ppduDropReasons) + { + std::cout << "PPDU Dropped due to " << reason.first << ": " << reason.second << std::endl; + } + std::cout << "\nTotal MPDUs: " << statistics.m_failedMpdus + statistics.m_receivedMpdus + << std::endl; + std::cout << "Total Successful MPDUs: " << statistics.m_receivedMpdus << std::endl; + std::cout << "Total Failed MPDUs: " << statistics.m_failedMpdus << std::endl; +} + +void +WifiPhyRxTraceSink::PrintStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + WifiPhyTraceStatistics statistics = CountStatistics(nodeId, deviceId, linkId); + std::cout << "Total PPDUs Received: " << statistics.m_receivedPpdus + statistics.m_failedPpdus + << std::endl; + std::cout << "Total Non-Overlapping PPDUs Received: " << statistics.m_nonOverlappingPpdus + << std::endl; + std::cout << "Total Overlapping PPDUs Received: " << statistics.m_overlappingPpdus << std::endl; + + std::cout << "\nSuccessful PPDUs: " << statistics.m_receivedPpdus << std::endl; + std::cout << "Failed PPDUs: " << statistics.m_failedPpdus << std::endl; + for (auto reason : statistics.m_ppduDropReasons) + { + std::cout << "PPDU Dropped due to " << reason.first << ": " << reason.second << std::endl; + } + std::cout << "\nTotal MPDUs: " << statistics.m_failedMpdus + statistics.m_receivedMpdus + << std::endl; + std::cout << "Total Successful MPDUs: " << statistics.m_receivedMpdus << std::endl; + std::cout << "Total Failed MPDUs: " << statistics.m_failedMpdus << std::endl; +} + +void +WifiPhyRxTraceSink::Reset() +{ + m_completedRecords.clear(); + m_records.clear(); +} + +// Return a single vector with all completed records +void +WifiPhyRxTraceSink::CreateVectorFromRecords() +{ + m_records.clear(); + for (const auto& nodeMap : m_completedRecords) + { + for (const auto& deviceMap : nodeMap.second) + { + for (const auto& linkMap : deviceMap.second) + { + for (const auto& wifiRecord : linkMap.second) + { + m_records.emplace_back(wifiRecord); + } + } + } + } +} + +const std::vector& +WifiPhyRxTraceSink::GetPpduRecords() const +{ + return m_records; +} + +std::optional>> +WifiPhyRxTraceSink::GetPpduRecords(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const +{ + if (m_completedRecords.contains(nodeId)) + { + if (m_completedRecords.at(nodeId).contains(deviceId)) + { + if (m_completedRecords.at(nodeId).at(deviceId).contains(linkId)) + { + return std::optional>>( + m_completedRecords.at(nodeId).at(deviceId).at(linkId)); + } + } + } + return std::nullopt; +} + +} // namespace ns3 diff --git a/src/wifi/helper/wifi-phy-rx-trace-helper.h b/src/wifi/helper/wifi-phy-rx-trace-helper.h new file mode 100644 index 000000000..162996a9a --- /dev/null +++ b/src/wifi/helper/wifi-phy-rx-trace-helper.h @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2023 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIFI_PHY_RX_TRACE_HELPER_H +#define WIFI_PHY_RX_TRACE_HELPER_H + +#include "ns3/nstime.h" +#include "ns3/object.h" +#include "ns3/phy-entity.h" +#include "ns3/ptr.h" +#include "ns3/wifi-phy-common.h" +#include "ns3/wifi-phy-state.h" +#include "ns3/wifi-ppdu.h" + +#include +#include +#include +#include +#include +#include + +// Forward declare some friend test classes that access private methods +class TestWifiPhyRxTraceHelper; +class TestWifiPhyRxTraceHelperMloStr; +class TestWifiPhyRxTraceHelperYans; + +namespace ns3 +{ + +class Mac48Address; +class NetDeviceContainer; +class Node; +class NodeContainer; +struct SpectrumSignalParameters; +class WifiPhyRxTraceSink; +class WifiTxVector; + +/** + * @struct WifiPhyTraceStatistics + * @brief Keeps track of PHY layer trace statistics. + * + * This structure stores various statistics related to the Physical Layer (PHY) of the Wi-Fi + * communication, including the number of successful and failed PPDUs containing unicast data, + * and unicast data MPDU receptions. + */ +struct WifiPhyTraceStatistics +{ + uint64_t m_overlappingPpdus{ + 0}; ///< Number of PPDUs that overlapped in time with at least one other PPDU. + uint64_t m_nonOverlappingPpdus{ + 0}; ///< Number of PPDUs that did not overlap in time with any other PPDU. + uint64_t m_receivedPpdus{0}; ///< Number of successfully received PPDUs (with unicast data). + uint64_t m_failedPpdus{0}; ///< Number of failed PPDU receptions (with unicast data). + uint64_t m_receivedMpdus{0}; ///< Number of successfully received unicast data MPDUs. + uint64_t m_failedMpdus{0}; ///< Number of failed unicast data MPDU receptions. + std::map m_ppduDropReasons; ///< Counts of the drop reasons +}; + +/** + * @struct WifiPpduRxRecord + * @brief Structure recording a received PPDU (Physical Protocol Data Unit) in a Wi-Fi network. + * + * This structure contains various details about the received PPDU, such as signal strength, + * identifiers for the sender and receiver, timing information, and reception status. + */ +struct WifiPpduRxRecord +{ + Ptr m_ppdu{nullptr}; ///< Pointer to the received PPDU. + double m_rssi{0}; ///< Received Signal Strength Indicator (RSSI) in dBm. + uint64_t m_rxTag{ + std::numeric_limits::max()}; ///< Unique tag for the reception of this PPDU. + uint32_t m_receiverId{std::numeric_limits::max()}; ///< Node ID of the receiver. + Time m_startTime; ///< Start time of the PPDU reception. + Time m_endTime; ///< End time of the PPDU reception. + WifiPhyRxfailureReason m_reason{ + WifiPhyRxfailureReason::UNKNOWN}; ///< Reason for reception failure, if any. + std::vector + m_overlappingPpdu; ///< List of PPDUs that overlapped in time with this reception. + std::vector m_statusPerMpdu; ///< Reception status for each MPDU within the PPDU. + uint8_t m_linkId{std::numeric_limits::max()}; ///< The link ID belonging to this record + uint32_t m_senderId{std::numeric_limits::max()}; ///< Node ID of the sender. + uint32_t m_senderDeviceId{std::numeric_limits::max()}; ///< Device ID of Sender +}; + +/** + * @class WifiPhyRxTraceHelper + * @brief Assists in tracing and analyzing Wi-Fi Physical Layer (PHY) receptions. + * + * The WifiPhyRxTraceHelper class can be used to instrument Wi-Fi nodes (or devices, or + * links) to keep track of the reception of Wi-Fi signals, and in particular, whether they overlap + * (collide) with one another. The implementation maintains reception records within internal + * data structures, and statistics or full reception records can be queried. + * + * The class provides functionality to connect traces to all nodes and WifiNetDevices within scope, + * enabling the capture of all Physical Protocol Data Units (PPDUs) received. It also + * allows for the collection and retrieval of statistics related to successful and failed receptions + * of PPDUs containing unicast data, and their corresponding MAC Protocol Data Units (MPDUs). + * + * Key features include: + * - Enabling trace connections to capture reception data. + * - Starting and stopping the collection of statistics at specified times. + * - Resetting the collected data for fresh starts in data collection. + * - Accessing detailed reception records for further analysis. + * + * Usage involves connecting to desired nodes or devices, (optionally) managing the + * collection period with start, stop, and reset methods, and finally, accessing the collected + * statistics or reception records. + * + * Statistics are only compiled for unicast data (WIFI_MAC_DATA and WIFI_MAC_QOSDATA), although + * PPDU records are kept for all frame types because it is possible for non-data frames to + * collide with data frames. + */ +class WifiPhyRxTraceHelper +{ + friend class ::TestWifiPhyRxTraceHelper; + friend class ::TestWifiPhyRxTraceHelperMloStr; + friend class ::TestWifiPhyRxTraceHelperYans; + + public: + /** + * Constructor. + */ + WifiPhyRxTraceHelper(); + + /** + * Enables trace collection for all nodes and WifiNetDevices in the specified NodeContainer. + * @param nodes The NodeContainer to which traces are to be connected. + */ + void Enable(NodeContainer nodes); + + /** + * Enables trace collection for all nodes corresponding to the devices in the specified + * NetDeviceContainer. + * @param devices The NetDeviceContainer containing nodes to which traces are to be connected. + */ + void Enable(NetDeviceContainer devices); + + /** + * Retrieves current statistics of successful and failed data PPDUs and MPDUs receptions, + * for all nodes, devices, and links that have been enabled. + * + * Statistics are compiled for the current observation window, which extends to the + * trace helper start time or last reset time (whichever is later) until the last + * stop time or the current time (whichever is earlier). + * + * @return A WifiPhyTraceStatistics object containing current reception statistics. + */ + WifiPhyTraceStatistics GetStatistics() const; + + /** + * Retrieves reception statistics for a given node, device, and link. + * @param node Ptr to Node. + * @param deviceId Device identifier (optional, defaults to 0). + * @param linkId Link identifier (optional, defaults to 0). + * @return WifiPhyTraceStatistics object with current reception stats. + */ + WifiPhyTraceStatistics GetStatistics(Ptr node, + uint32_t deviceId = 0, + uint8_t linkId = 0) const; + + /** + * Retrieves reception statistics for a given node, device, and link. + * @param nodeId Node identifier. + * @param deviceId Device identifier (optional, defaults to 0). + * @param linkId Link identifier (optional, defaults to 0). + * @return WifiPhyTraceStatistics object with current reception stats. + */ + WifiPhyTraceStatistics GetStatistics(uint32_t nodeId, + uint32_t deviceId = 0, + uint8_t linkId = 0) const; + + /** + * Starts the collection of statistics from a specified start time. + * @param startTime The time to start collecting statistics. + */ + void Start(Time startTime); + + /** + * Stops the collection of statistics at a specified time. + * @param stopTime The time to stop collecting statistics. + */ + void Stop(Time stopTime); + + /** + * Resets the current statistics, clearing all counts and PPDU records. + */ + void Reset(); + + /** + * Accesses a vector of saved and completed PPDU reception records. + * @return A constant reference to a vector of WifiPpduRxRecord instances. + */ + const std::vector& GetPpduRecords() const; + + /** + * Accesses PPDU reception records for a specific node, device, and link. + * + * Because the return value could be null if the specified node/device/link do not have any + * records, this method returns a std::optional object, and because we are passing the + * underlying vector by const reference to avoid a copy, it must be wrapped by + * std::reference_wrapper. See the example program wifi-phy-reception-trace-example.cc for an + * example of how to use the value returned. + * + * @param nodeId The ID of the node. + * @param deviceId The ID of the WifiNetDevice (optional, defaults to 0). + * @param linkId The ID of the link (optional, defaults to 0). + * @return A reference to a vector of WifiPpduRxRecord instances. + */ + std::optional>> GetPpduRecords( + uint32_t nodeId, + uint32_t deviceId = 0, + uint8_t linkId = 0) const; + + /** + * Print statistics for all nodes, devices, and links during the collection period. + */ + void PrintStatistics() const; + + /** + * Prints statistics collected in the period for a specific node, device, and link. + * @param node Ptr to Node. + * @param deviceId Device identifier (optional, defaults to 0). + * @param linkId Link identifier (optional, defaults to 0). + */ + void PrintStatistics(Ptr node, uint32_t deviceId = 0, uint8_t linkId = 0) const; + + /** + * Prints statistics collected in the period for a specific node, device, and link. + * @param nodeId Node identifier. + * @param deviceId Device identifier (optional, defaults to 0). + * @param linkId Link identifier (optional, defaults to 0). + */ + void PrintStatistics(uint32_t nodeId, uint32_t deviceId = 0, uint8_t linkId = 0) const; + + private: + /** + * Enables trace collection for all nodes and WifiNetDevices in the specified NodeContainer. + * @param nodes The NodeContainer to which traces are to be connected. + * @param MacToNodeMap A mapping from MAC address to node ID. + */ + void Enable(NodeContainer nodes, const std::map& MacToNodeMap); + + /** + * Populates the mapping of MAC addresses to node IDs for a given set of nodes. + * @param nodes The container of nodes for which to populate the MAC address to node ID mapping. + * @return a copy of the std::map container + */ + std::map MapMacAddressesToNodeIds(NodeContainer nodes) const; + + Ptr m_traceSink; ///< Pointer to the current trace sink object. +}; + +/** + * @class WifiPhyRxTraceSink + * @brief Sink class for capturing and analyzing PHY layer reception events in Wi-Fi networks. + * + * The WifiPhyRxTraceSink class acts as a comprehensive sink for events related to the + * reception of signals at the Physical Layer (PHY) of Wi-Fi networks. It is designed to facilitate + * the detailed analysis and tracing of reception activities, and the management of reception data + * across nodes, devices, and links. This class supports the capture and analysis of detailed + * reception records, including successful and failed receptions, and offers capabilities to + * manage the collection of reception statistics. + * + * Features include: + * - Unique tag generation for tracking individual reception events. + * - Access to detailed PPDU reception records for analysis. + * - Management of reception statistics collection through start, stop, and reset operations. + * - Mapping of MAC addresses to node IDs for network element identification. + * - Handling of PHY signal transmission and reception events for detailed outcome logging. + * - Tracking and analysis of overlapping reception events for interference assessment. + */ +class WifiPhyRxTraceSink : public Object +{ + public: + /** + * Constructor. + */ + WifiPhyRxTraceSink(); + + /** + * Retrieves the TypeId of the class. + * @return The TypeId of the WifiPhyRxTraceSink class. + */ + static TypeId GetTypeId(); + + /** + * @brief Generating unique tags for more than one instance of a WifiPpdu object + * + * This class is responsible for generating unique identifiers for each received + * WifiPpdu. The WifiPpdu UID is not sufficient because there can be more than one + * record per WifiPpdu. + */ + class UniqueTagGenerator + { + public: + /** + * Generates a unique tag for a WifiPpdu. Uses an internal counter and a set of + * previously used tags to ensure that each generated tag is unique, allowing accurate + * tracking of received frames. + * @param ppduUid The PPDU UID to generate a unique tag for. + * @return uint64_t The generated unique tag. + */ + uint64_t GenerateUniqueTag(uint64_t ppduUid); + + private: + uint64_t counter{0}; ///< Counter to help generate unique tags. + std::set usedTags; ///< Set of already used tags. + }; + + UniqueTagGenerator m_tagGenerator; ///< Instance of UniqueTagGenerator used for generating + ///< unique reception tags. + + /** + * Provides access to all saved and completed PPDU reception records across all nodes, + * WifiNetDevices, and links. + * @return A constant reference to a vector of WifiPpduRxRecord instances. + */ + const std::vector& GetPpduRecords() const; + + /** + * Provides access to PPDU reception records for a specific node, device, and link. + * @param nodeId The ID of the node. + * @param deviceId The ID of the WifiNetDevice (optional, defaults to 0). + * @param linkId The ID of the link (optional, defaults to 0). + * @return A constant reference to a vector of WifiPpduRxRecord instances. + */ + std::optional>> GetPpduRecords( + uint32_t nodeId, + uint32_t deviceId = 0, + uint8_t linkId = 0) const; + + /** + * Creates a vector with all completed WiFi reception records to be returned by the + * GetPpduRecords(). This is only called if no specific node, device or link is + * provided. + */ + void CreateVectorFromRecords(); + + /** + * Provides a custom mapping of MAC addresses to intended receiver node IDs. This mapping is + * crucial for identifying the nodes involved in reception events based on their MAC addresses, + * facilitating the analysis of network dynamics at the PHY layer. + * @param MacAddressToNodeIdMap A mapping from MAC addresses to node IDs. + */ + void SetMapMacAddressToNodeId(const std::map& MacAddressToNodeIdMap); + + /** + * Retrieves the frequency and channel number used by a specific link. + * @param node The node ID. + * @param deviceId The device ID of the WifiNetDevice. + * @param link The link ID. + * @return A pair containing the channel number (uint8_t) and frequency (uint16_t). + */ + std::optional> GetChannelInfo(uint32_t node, + uint32_t deviceId, + int link) const; + + /** + * Starts the statistics collection period from a specified start time. Ongoing PPDUs when this + * method is called will be counted towards this statistics collection period. + */ + void Start(); + + /** + * Stops the statistics collection period at a specified time. This method ensures that + * incomplete PPDUs at the end of the collection period are not included in the current + * statistics count. + */ + void Stop(); + + /** + * Resets the statistics collection, clearing all counts and discarding all fully completed PPDU + * records. This allows for a fresh start in data collection and analysis. + */ + void Reset(); + + /** + * Translates a context string to a link ID, facilitating the association of events with + * specific links in the network. + * @param context The context string. + * @return The link ID associated with the context. + */ + int ContextToLinkId(std::string context) const; + + /** + * Translates a context string to a device ID, enabling the identification of devices involved + * in reception events based on context information. + * @param context The context string. + * @return The device ID associated with the context. + */ + int ContextToDeviceId(std::string context) const; + + /** + * Translates a context string to a node ID. + * @param context The context string to translate. + * @return The corresponding node ID, facilitating the identification of the node involved in a + * reception event. + */ + uint32_t ContextToNodeId(std::string context) const; + + /** + * Translate a context string to a colon-delimited tuple "0:0:0" where the first element + * is a node ID, second is a device ID, third is a link ID + * @param context The context string to translate. + * @return The tuple of node, device, and link IDs + */ + std::string ContextToTuple(std::string context) const; + + /** + * Handles the event of a PHY signal transmission. + * @param context The context string, providing situational information about the transmission. + * @param ppdu The Wi-Fi PPDU being transmitted. + * @param txVector The transmission vector, detailing the parameters of the transmission. + */ + void PhySignalTransmission(std::string context, + Ptr ppdu, + const WifiTxVector& txVector); + + /** + * @brief Handles the event of a Wi-Fi PPDU arrival. + * + * @param context The context string, offering contextual information about the signal + * reception. + * @param ppdu The received WifiPpdu + * @param rxPower The power level at which the signal was received, indicating the signal's + * strength. + * @param duration The duration of the signal, offering temporal information about the reception + * event. + */ + void PhySignalArrival(std::string context, + Ptr ppdu, + double rxPower, + Time duration); + + /** + * @brief Handles the event of a PHY signal arrival from a SpectrumChannel. + * + * @param context The context string, offering contextual information about the signal + * reception. + * @param signal The received signal parameters, detailing the characteristics of the arriving + * signal. + * @param senderNodeId The ID of the node sending the signal, providing insight into the source + * of the signal. + * @param rxPower The power level at which the signal was received, indicating the signal's + * strength. + * @param duration The duration of the signal, offering temporal information about the reception + * event. + */ + void SpectrumPhySignalArrival(std::string context, + Ptr signal, + uint32_t senderNodeId, + double rxPower, + Time duration); + + /** + * Maps nodes to links and channels, creating a structured association between network + * elements and their communication channels. This mapping is essential for understanding + * what frequency and channel number is used for a given link. + * @param nodes NodeContainer of all nodes whose MAC address is going to be link to their + * NodeID. + */ + void MapNodeToLinkToChannel(NodeContainer nodes); + + /** + * Returns statistics on the count of successful and failed PPDUs with unicast data, and their + * MPDUs, across all nodes, devices, and links. + * @return A WifiPhyTraceStatistics object containing the current statistics. This method does + * not reset the collected statistics, allowing for continuous accumulation of data over time. + */ + WifiPhyTraceStatistics GetStatistics() const; + + /** + * Returns statistics on the count of successful and failed PPDUs with unicast data, and their + * MPDUs, for a specific node, device, and link. + * @param nodeId The node ID from which to get the statistics. + * @param deviceId The device ID from which to get the statistics. + * @param linkId The link ID from which to get the statistics. + * @return A WifiPhyTraceStatistics object containing the current statistics. This method does + * not reset the collected statistics, allowing for continuous accumulation of data over time. + */ + WifiPhyTraceStatistics GetStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const; + + /** + * Updates the information for signals currently being received by a node. + * @param nodeId The node ID for which to update the reception information. + * @param deviceId The device ID on the node for which to update the information. + * @param linkId The link ID associated with the ongoing reception. + */ + void UpdateCurrentlyReceivedSignal(uint32_t nodeId, uint32_t deviceId, uint8_t linkId); + + /** + * Records the outcome of a PPDU transmission, including the signal info, and the success or + * failure status of each MPDU within the PPDU. + * @param context Descriptive context for the PPDU outcome. + * @param ppdu Pointer to the PPDU whose outcome is being recorded. + * @param signal Information about the received signal. + * @param txVector The transmission vector used for the PPDU. + * @param statusPerMpdu A vector indicating the success (true) or failure (false) of each MPDU. + */ + void PpduOutcome(std::string context, + Ptr ppdu, + RxSignalInfo signal, + const WifiTxVector& txVector, + const std::vector& statusPerMpdu); + + /** + * Logs the drop of a PPDU at the PHY layer, detailing the context and reason for the drop. + * @param context Descriptive context for the PPDU drop. + * @param ppdu Pointer to the PPDU that was dropped. + * @param reason The reason for the PPDU drop, represented as a WifiPhyRxfailureReason. + */ + void PhyPpduDrop(std::string context, Ptr ppdu, WifiPhyRxfailureReason reason); + + /** + * Handles the end of a PHY reception event, logging the conclusion of a reception and its + * associated details. + * @param nodeId The node ID where the reception ended. + * @param deviceId The device ID on the node where the reception took place. + * @param rxTag The unique reception tag associated with the reception event. + * @param ppduUid The PPDU UID of the received WifiPpdu + */ + void PhyRxEnd(uint32_t nodeId, uint32_t deviceId, uint64_t rxTag, uint64_t ppduUid); + + /** + * Handles the conclusion of a transmission event, facilitating the logging of transmission + * outcomes. + * @param nodeId The node ID from which the transmission originated. + * @param deviceId The device ID on the node that performed the transmission. + * @param ppduRecord The WifiPpduRxRecord representing the concluded transmission. + */ + void EndTx(uint32_t nodeId, uint32_t deviceId, WifiPpduRxRecord ppduRecord); + + /** + * Counts and aggregates PHY layer statistics including receptions, transmissions, and + * performance metrics (Success, Failure, Overlap) of unicast data PPDUs and MPDUs across all + * nodes. + * @return WifiPhyTraceStatistics object containing aggregated statistics for all nodes. + */ + WifiPhyTraceStatistics CountStatistics() const; + + /** + * Counts statistics related to PHY layer receptions, transmissions, and performance metrics + * such as Success, Failure or Overlap of a PPDU containing unicast data, and the MPDUs within, + * for the specific node, + * device and link. + * @param nodeId Node identifier. + * @param deviceId Device identifier. + * @param linkId Link identifier. + * @return WifiPhyTraceStatistics object with current reception stats. + */ + WifiPhyTraceStatistics CountStatistics(uint32_t nodeId, + uint32_t deviceId, + uint8_t linkId) const; + + /** + * Prints a summary of the statistics collected, offering a concise overview of PHY layer + * performance and reception outcomes for all nodes, devices and links. + */ + void PrintStatistics() const; + + /** + * Prints statistics collected in the period for a node, device, and link. + * @param nodeId Node identifier. + * @param deviceId Device identifier. + * @param linkId Link identifier. + */ + void PrintStatistics(uint32_t nodeId, uint32_t deviceId, uint8_t linkId) const; + + /** + * @brief Returns whether the collection period is active + * @return Whether the collection period is active + */ + bool IsCollectionPeriodActive() const; + + /** + * @brief Maps a reception tag to the corresponding WifiPpduRxRecord. + * + * This map links each unique reception tag generated upon receiving a + * frame to the corresponding detailed reception record (WifiPpduRxRecord). It is + * refreshed every time a new reception record is created. + */ + std::map m_rxTagToPpduRecord; + + /** + * @brief Stores records of PPDUs that have completed reception, organized by node, device, and + * link. + * + * This nested map structure is updated each time a WifiPpduRxRecord is finalized, + * keeping a history of all completed packet receptions across different nodes, + * devices, and links. + */ + std::map>>> + m_completedRecords; + + /** + * @brief Stores a flat vector of all records of PPDUs that have completed reception. + * + * This vector is updated upon the request of GetPpduRecords(), + * maintaining all packet receptions. + */ + std::vector m_records; + + /** + * @brief Tracks ongoing frames being transmitted or received per node, device, and link. + * + * This map associates each node (by its ID) with nested maps keyed by device and link IDs, each + * containing vectors of WifiPpduRxRecords. It enables the tracking of current packet + * transmissions and receptions. + */ + std::map>>> + m_nodeDeviceLinkRxRecords; + + /** + * @brief Maps each reception tag to a list of overlapping WifiPpduRxRecords. + * + * For each unique reception tag, this map stores a vector of WifiPpduRxRecords that + * overlap with the record corresponding to the tag. + */ + std::map> m_rxTagToListOfOverlappingPpduRecords; + + /** + * @brief Aids in correlating PHY reception drops and outcomes with specific reception tags. + * + * This complex map structure is used to navigate from node and device IDs, and link and packet + * IDs (PIDs), to the corresponding reception tag. + */ + std::map>>> + m_nodeDeviceLinkPidToRxId; + + /** + * @brief Maps WifiPpdu UIDs to WifiPpduRxRecord tags stored by transmitter. + * + * This map allows a transmit-side PPDU record to be fetched based on the WifiPpdu UID. + */ + std::map m_ppduUidToTxTag; + + /** + * @brief Maps MAC addresses to node IDs. + * + * This mapping facilitates the identification of nodes within the network based on the MAC + * addresses observed in reception events. By translating MAC addresses to node IDs, this map + * enables the association of reception data with specific network nodes. + */ + std::map m_macAddressToNodeId; + + /** + * Maps node IDs to device IDs and further to link IDs, associating each with a pair consisting + * of the channel number and frequency. This structure is vital for understanding the channel + * and frequency utilization across different links. + */ + std::map>>> + m_nodeToDeviceToLinkToChannelInfo; + + private: + /** + * Flag indicating whether to keep a record of certain statistics or events for analysis. This + * boolean flag can be toggled to control the granularity and scope of data collection, enabling + * customization of the analysis to focus on specific times of interest. + */ + bool m_statisticsCollectionPeriodStarted; + + /** + * Update the passed-in statistics object with statistics from the passed-in record. + * \param statistics The WifiPhyTraceStatistics object to modify + * \param record The WifiPpduRxRecord to examine + */ + void CountStatisticsForRecord(WifiPhyTraceStatistics& statistics, + const WifiPpduRxRecord& record) const; + +}; // class WifiPhyRxTraceSink + +// Non-member function declarations + +/** + * @brief Checks if two WifiPpduRxRecord objects are equal. + * + * Compares two WifiPpduRxRecord objects to determine if they are equal. Two objects + * are considered equal if all their corresponding counts have the same values. + * + * @param lhs The left-hand side WifiPpduRxRecord object in the comparison. + * @param rhs The right-hand side WifiPpduRxRecord object in the comparison. + * @return true if the objects are equal, false otherwise. + */ +bool operator==(const WifiPpduRxRecord& lhs, const WifiPpduRxRecord& rhs); + +/** + * @brief Checks if two WifiPpduRxRecord objects are not equal. + * + * Compares two WifiPpduRxRecord objects to determine if they are not equal. Two objects + * are considered not equal if at least one of their corresponding counts have different values. + * + * @param lhs The left-hand side WifiPpduRxRecord object in the comparison. + * @param rhs The right-hand side WifiPpduRxRecord object in the comparison. + * @return true if the objects are not equal, false otherwise. + */ +bool operator!=(const WifiPpduRxRecord& lhs, const WifiPpduRxRecord& rhs); + +/** + * @brief Determines if one WifiPpduRxRecord object is less than another. + * + * Compares two WifiPpduRxRecord objects to determine if the left-hand side object is + * considered less than the right-hand side object based on a specific criteria of comparison, + * such as a key property value. + * + * @note The specific criteria for comparison should be defined and consistent. + * + * @param lhs The left-hand side WifiPpduRxRecord object in the comparison. + * @param rhs The right-hand side WifiPpduRxRecord object in the comparison. + * @return true if lhs is considered less than rhs, false otherwise. + */ +bool operator<(const WifiPpduRxRecord& lhs, const WifiPpduRxRecord& rhs); + +/** + * @brief Checks if two WifiPhyTraceStatistics objects are equal. + * + * Determines if two WifiPhyTraceStatistics objects are equal by comparing their counts. + * Equality is based on the values of all relevant statistical counts being identical. + * + * @param lhs The left-hand side WifiPhyTraceStatistics object in the comparison. + * @param rhs The right-hand side WifiPhyTraceStatistics object in the comparison. + * @return true if all counts are equal, false otherwise. + */ +bool operator==(const WifiPhyTraceStatistics& lhs, const WifiPhyTraceStatistics& rhs); + +/** + * @brief Checks if two WifiPhyTraceStatistics objects are not equal. + * + * Determines if two WifiPhyTraceStatistics objects are not equal by comparing their counts. + * Non-equality is based on any of the relevant statistical counts having different values. + * + * @param lhs The left-hand side WifiPhyTraceStatistics object in the comparison. + * @param rhs The right-hand side WifiPhyTraceStatistics object in the comparison. + * @return true if any property is different, false otherwise. + */ +bool operator!=(const WifiPhyTraceStatistics& lhs, const WifiPhyTraceStatistics& rhs); + +/** + * @brief Adds two WifiPhyTraceStatistics objects. + * + * Combines two WifiPhyTraceStatistics objects by summing their counts. + * This can be useful for aggregating statistics from multiple sources or time periods. + * + * @param lhs The left-hand side WifiPhyTraceStatistics object to be added. + * @param rhs The right-hand side WifiPhyTraceStatistics object to be added. + * @return A new WifiPhyTraceStatistics object that is the result of the addition. + */ +WifiPhyTraceStatistics operator+(const WifiPhyTraceStatistics& lhs, + const WifiPhyTraceStatistics& rhs); + +} // namespace ns3 + +#endif /* WIFI_PHY_RX_TRACE_HELPER_H */