zigbee: Add basic APS layer support

This commit is contained in:
Alberto Gallegos Ramonet
2025-05-23 18:45:19 +09:00
parent 2ab68f0cb6
commit f315c12d8d
23 changed files with 3234 additions and 33 deletions

View File

@@ -3,6 +3,9 @@ build_lib(
SOURCE_FILES
helper/zigbee-helper.cc
helper/zigbee-stack-container.cc
model/zigbee-aps-header.cc
model/zigbee-aps-tables.cc
model/zigbee-aps.cc
model/zigbee-nwk-fields.cc
model/zigbee-nwk.cc
model/zigbee-stack.cc
@@ -12,6 +15,9 @@ build_lib(
HEADER_FILES
helper/zigbee-helper.h
helper/zigbee-stack-container.h
model/zigbee-aps-header.h
model/zigbee-aps-tables.h
model/zigbee-aps.h
model/zigbee-nwk-fields.h
model/zigbee-nwk.h
model/zigbee-stack.h
@@ -20,4 +26,5 @@ build_lib(
model/zigbee-nwk-tables.h
LIBRARIES_TO_LINK ${liblr-wpan}
TEST_SOURCES test/zigbee-rreq-test.cc
test/zigbee-aps-data-test.cc
)

View File

@@ -35,28 +35,42 @@ This is the case of ``NetDevices`` such as the ``LrWpanNetDevice``.
While other technologies like 6loWPAN can interact with the underlying MAC layer through general-purpose ``NetDevice`` interfaces, Zigbee has specific requirements that necessitate certain features from a lr-wpan MAC.
Consequently, the |ns3| Zigbee implementation directly accesses the aggregated ``LrWpanMacBase`` and interfaces with it accordingly.
The current scope of the project includes **only the NWK layer in the Zigbee stack**. However, the project can be later extended
to support higher layers like the Application Sublayer (APS) and the Zigbee Cluster Library (ZCL).
The current scope of the project includes **only the NWK layer and the APS layer in the Zigbee stack**. However, the project can be later extended
to support higher layers like the application framework (AF) or services like the Zigbee Cluster Library (ZCL).
Scope and Limitations
---------------------
- MacInterfaceTable is not supported (Multiple interface support).
The NWK has the following limitations:
- The NWK MacInterfaceTable NIB is not supported (Multiple MAC interfaces support).
- Security handling is not supported.
- Source routing and Tree routing are not implemented.
- Zigbee Stack profile 0x01 (used for tree routing and distributed address assignment) is not supported.
- A few NIB attributes setting and NWK constants are not supported by the |ns3| attribute system.
- Traces are not implemented yet.
- Trace sources are not implemented yet.
- Data broadcast do not support retries or passive acknowledgment.
- Data broadcast to low power routers is not supported as the underlying lr-wpan netdevice has no concept of energy consumption.
- Address duplication detection is not supported.
- Beacon mode is not througly tested.
- The following Zigbee layers are not supported yet:
- Zigbee Application Support Sub-Layer (APS)
- Zigbee Cluster Library (ZCL)
- Zigbee Device Object (ZDO)
- Application Framework (AF)
- 16-bit address resolution from a IEEE address at NWK layer is not supported.
The APS has the following limitations:
- No groupcasting support (Similar to multicast)
- No fragmentation support
- No duplicates detection
- No acknowledged transmissions
- No extenended address (64-bit) destinations supported
- No trace sources
The following Zigbee layers or services are not supported yet:
- Zigbee Cluster Library (ZCL)
- Zigbee Device Object (ZDO)
- Application Framework (AF)
To see a list of |ns3| Zigbee undergoing development efforts check issue `#1165 <https://gitlab.com/nsnam/ns-3-dev/-/issues/1165>`_
@@ -298,6 +312,31 @@ Alternatively, a Mesh route discovery can be performed along a data transmission
Important: The process described above assumes that devices have already joined the network.
A route discovery request issued before a device is part of the network (join process) will result in failure.
The application support sub-layer (APS)
---------------------------------------
As its name suggests, this intermediate layer exists between the Network (NWK) layer and the Application Framework (AF).
It provides services to both the Zigbee Device Object (ZDO) and the AF.
The APS layer introduces abstract concepts such as **EndPoints**, **Clusters**, and **Profiles**; however, it does not assign specific meanings to these concepts.
Instead, it simply adds this information to the transmitted frames.
Effective management of **EndPoints**, **Clusters**, and **Profile IDs** requires the use of the Zigbee Cluster Library (ZCL) and the ZDO, which are not currently supported by this implementation.
This implementation offers only the most basic functions of the APS, allowing data transmission using known 16-bit address destinations.
Features such as groupcasting, transmission to IEEE addresses (64-bit address destinations), fragmentation, duplication detection, and security are not currently supported but are under development.
By default, the APS layer is integrated within the Zigbee stack.
However, users can choose to work solely with the NWK layer.
To do this, they must disable the APS by using the `SetNwkLayerOnly()` function in the Zigbee helper.
The APS layer can transmit data that includes information regarding source and destination endpoints, but it cannot initiate network formation or network joining procedures.
Typically, the ZDO manages these processes; however, since we currently do not have the ZDO, users must directly interact with the NWK layers network formation and joining primitives to trigger these options.
The following is a list of APS primitives are included:
- APSDE-DATA (Request, Confirm, Indication) <------ Only Address mode 0x02 (16-bit address and destination endpoint present) supported.
- APSME-BIND (Request, Confirm) <----Included but inconsequential without group table support
- APSME-UNBIND (Request, Confirm) <----Included but inconsequential without group table support
Usage
-----
@@ -329,7 +368,10 @@ is used to establish a Zigbee stack on top of these devices::
// Install a zigbee stack on a set of devices using the helper
ZigbeeHelper zigbee;
zigbee.SetNwkLayerOnly(); // Activate if you wish to have only the NWK layer in the stack
ZigbeeStackContainer zigbeeStackContainer = zigbee.Install(lrwpanDevices);
// ... ...
// Call to NWK primitives contained in the NWK layer in the Zigbee stack.
Attributes
~~~~~~~~~~
@@ -375,11 +417,12 @@ All the examples listed here shows scenarios in which a quasi-layer implementati
* ``zigbee-association-join.cc``: An example showing the NWK layer join process of 3 devices in a zigbee network (MAC association).
* ``zigbee-nwk-routing.cc``: Shows a simple topology of 5 router devices sequentially joining a network. Data transmission and route discovery (MESH routing) are also shown in this example
* ``zigbee-nwk-routing-grid.cc``: Shows a complex grid topology of 50 router devices sequentially joining a network. Route discovery (MANY-TO-ONE routing) is also shown in this example.
* ``zigbee-aps-data.cc``: Demonstrates the usage of the APS layer to transmit data.
The following unit test have been developed to ensure the correct behavior of the module:
* ``zigbee-rreq-test``: Test some situations in which RREQ messages should be retried during a route discovery process.
* ``zigbee-aps-data-test``: Test the APS data transmission
Validation
----------

View File

@@ -3,6 +3,7 @@ set(base_examples
zigbee-nwk-association-join
zigbee-nwk-routing
zigbee-nwk-routing-grid
zigbee-aps-data
)
foreach(

View File

@@ -0,0 +1,442 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Authors:
*
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
/**
* Mesh routing example with APS layer data transmission in a simple topology.
*
* This example use a Zigbee stack formed by NWK and APS layers.
* The APS layer is used to transmit data, the creation of the necessary routes is
* handle automatically by the NWK layer. There is no ZDO, therefore we need to manually
* use the NWK to form the network and establish the router devices.
*
*
* Network Extended PAN id: 0X000000000000CA:FE (based on the PAN coordinator address)
*
* Devices Addresses:
*
* [Coordinator] ZC (dev0 | Node 0): [00:00:00:00:00:00:CA:FE] [00:00]
* [Router 1] ZR1 (dev1 | Node 1): [00:00:00:00:00:00:00:01] [short addr assigned by ZC]
* [Router 2] ZR2 (dev2 | Node 2): [00:00:00:00:00:00:00:02] [short addr assigned by ZR1]
* [Router 3] ZR3 (dev3 | Node 3): [00:00:00:00:00:00:00:03] [short addr assigned by ZR2]
* [Router 4] ZR4 (dev4 | Node 4): [00:00:00:00:00:00:00:04] [short addr assigned by ZR1]
*
* Topology:
*
* ZC--------ZR1------------ZR2----------ZR3
* |
* |
* ZR4
*/
#include "ns3/constant-position-mobility-model.h"
#include "ns3/core-module.h"
#include "ns3/log.h"
#include "ns3/lr-wpan-module.h"
#include "ns3/packet.h"
#include "ns3/propagation-delay-model.h"
#include "ns3/propagation-loss-model.h"
#include "ns3/simulator.h"
#include "ns3/single-model-spectrum-channel.h"
#include "ns3/zigbee-module.h"
#include <iostream>
using namespace ns3;
using namespace ns3::lrwpan;
using namespace ns3::zigbee;
NS_LOG_COMPONENT_DEFINE("ZigbeeRouting");
ZigbeeStackContainer zigbeeStacks;
static void
TraceRoute(Mac16Address src, Mac16Address dst)
{
std::cout << "\nTime " << Simulator::Now().As(Time::S) << " | "
<< "Traceroute to destination [" << dst << "]:\n";
Mac16Address target = src;
uint32_t count = 1;
while (target != Mac16Address("FF:FF") && target != dst)
{
Ptr<ZigbeeStack> zstack;
for (auto i = zigbeeStacks.Begin(); i != zigbeeStacks.End(); i++)
{
zstack = *i;
if (zstack->GetNwk()->GetNetworkAddress() == target)
{
break;
}
}
bool neighbor = false;
target = zstack->GetNwk()->FindRoute(dst, neighbor);
if (target == Mac16Address("FF:FF"))
{
std::cout << count << ". Node " << zstack->GetNode()->GetId() << " ["
<< zstack->GetNwk()->GetNetworkAddress() << " | "
<< zstack->GetNwk()->GetIeeeAddress() << "]: "
<< " Destination Unreachable\n";
}
else
{
std::cout << count << ". Node " << zstack->GetNode()->GetId() << " ["
<< zstack->GetNwk()->GetNetworkAddress() << " | "
<< zstack->GetNwk()->GetIeeeAddress() << "]: "
<< "NextHop [" << target << "] ";
if (neighbor)
{
std::cout << "(*Neighbor)\n";
}
else
{
std::cout << "\n";
}
count++;
}
}
std::cout << "\n";
}
static void
ApsDataIndication(Ptr<ZigbeeStack> stack, ApsdeDataIndicationParams params, Ptr<Packet> p)
{
std::cout << Simulator::Now().As(Time::S) << " Node " << stack->GetNode()->GetId() << " | "
<< "ApsdeDataIndication: Received packet of size " << p->GetSize()
<< " for destination EndPoint " << params.m_dstEndPoint << "\n";
}
static void
NwkNetworkFormationConfirm(Ptr<ZigbeeStack> stack, NlmeNetworkFormationConfirmParams params)
{
std::cout << "NlmeNetworkFormationConfirmStatus = " << params.m_status << "\n";
}
static void
NwkNetworkDiscoveryConfirm(Ptr<ZigbeeStack> stack, NlmeNetworkDiscoveryConfirmParams params)
{
// See Zigbee Specification r22.1.0, 3.6.1.4.1
// This method implements a simplistic version of the method implemented
// in a zigbee APL layer. In this layer a candidate Extended PAN Id must
// be selected and a NLME-JOIN.request must be issued.
if (params.m_status == NwkStatus::SUCCESS)
{
std::cout << " Network discovery confirm Received. Networks found ("
<< params.m_netDescList.size() << "):\n";
for (const auto& netDescriptor : params.m_netDescList)
{
std::cout << " ExtPanID: 0x" << std::hex << netDescriptor.m_extPanId << "\n"
<< std::dec << " CH: " << static_cast<uint32_t>(netDescriptor.m_logCh)
<< "\n"
<< std::hex << " Pan ID: 0x" << netDescriptor.m_panId << "\n"
<< " Stack profile: " << std::dec
<< static_cast<uint32_t>(netDescriptor.m_stackProfile) << "\n"
<< "--------------------\n";
}
NlmeJoinRequestParams joinParams;
zigbee::CapabilityInformation capaInfo;
capaInfo.SetDeviceType(ROUTER);
capaInfo.SetAllocateAddrOn(true);
joinParams.m_rejoinNetwork = zigbee::JoiningMethod::ASSOCIATION;
joinParams.m_capabilityInfo = capaInfo.GetCapability();
joinParams.m_extendedPanId = params.m_netDescList[0].m_extPanId;
Simulator::ScheduleNow(&ZigbeeNwk::NlmeJoinRequest, stack->GetNwk(), joinParams);
}
else
{
NS_ABORT_MSG("Unable to discover networks | status: " << params.m_status);
}
}
static void
NwkJoinConfirm(Ptr<ZigbeeStack> stack, NlmeJoinConfirmParams params)
{
if (params.m_status == NwkStatus::SUCCESS)
{
std::cout << Simulator::Now().As(Time::S) << " Node " << stack->GetNode()->GetId() << " | "
<< " The device joined the network SUCCESSFULLY with short address " << std::hex
<< params.m_networkAddress << " on the Extended PAN Id: " << std::hex
<< params.m_extendedPanId << "\n"
<< std::dec;
// 3 - After dev 1 is associated, it should be started as a router
// (i.e. it becomes able to accept request from other devices to join the network)
NlmeStartRouterRequestParams startRouterParams;
Simulator::ScheduleNow(&ZigbeeNwk::NlmeStartRouterRequest,
stack->GetNwk(),
startRouterParams);
}
else
{
std::cout << " The device FAILED to join the network with status " << params.m_status
<< "\n";
}
}
static void
NwkRouteDiscoveryConfirm(Ptr<ZigbeeStack> stack, NlmeRouteDiscoveryConfirmParams params)
{
std::cout << "NlmeRouteDiscoveryConfirmStatus = " << params.m_status << "\n";
}
static void
SendData(Ptr<ZigbeeStack> stackSrc, Ptr<ZigbeeStack> stackDst)
{
// Send data from a device with stackSrc to device with stackDst.
// We do not know what network address will be assigned after the JOIN procedure
// but we can request the network address from stackDst (the destination device) when
// we intend to send data. If a route do not exist, we will search for a route
// before transmitting data (Mesh routing).
Ptr<Packet> p = Create<Packet>(5);
// Src and Dst Endpoints must not be 0, because this is reserved for the ZDO.
// Other Endpoint numbers can help to differentiate between different applications
// running in the same node (similar to the concept of a port in TCP/IP).
// Likewise, because we currently do not have ZDO or ZCL or AF, clusterId
// and profileId numbers are non-sensical.
ApsdeDataRequestParams dataReqParams;
ZigbeeApsTxOptions txOptions;
dataReqParams.m_useAlias = false;
// Default, use 16 bit address destination (No option), equivalent to 0x00
dataReqParams.m_txOptions = txOptions.GetTxOptions();
dataReqParams.m_srcEndPoint = 3;
dataReqParams.m_clusterId = 5; // Arbitrary value
dataReqParams.m_profileId = 2; // Arbitrary value
dataReqParams.m_dstAddrMode = ApsDstAddressMode::DST_ADDR16_DST_ENDPOINT_PRESENT;
dataReqParams.m_dstAddr16 = stackDst->GetNwk()->GetNetworkAddress();
dataReqParams.m_dstEndPoint = 4;
Simulator::ScheduleNow(&ZigbeeAps::ApsdeDataRequest, stackSrc->GetAps(), dataReqParams, p);
// Give a few seconds to allow the creation of the route and
// then print the route trace and tables from the source
Simulator::Schedule(Seconds(3),
&TraceRoute,
stackSrc->GetNwk()->GetNetworkAddress(),
stackDst->GetNwk()->GetNetworkAddress());
Ptr<OutputStreamWrapper> stream = Create<OutputStreamWrapper>(&std::cout);
Simulator::Schedule(Seconds(4), &ZigbeeNwk::PrintNeighborTable, stackSrc->GetNwk(), stream);
Simulator::Schedule(Seconds(4), &ZigbeeNwk::PrintRoutingTable, stackSrc->GetNwk(), stream);
Simulator::Schedule(Seconds(4),
&ZigbeeNwk::PrintRouteDiscoveryTable,
stackSrc->GetNwk(),
stream);
}
int
main(int argc, char* argv[])
{
LogComponentEnableAll(LogLevel(LOG_PREFIX_TIME | LOG_PREFIX_FUNC | LOG_PREFIX_NODE));
// Enable logs for further details
// LogComponentEnable("ZigbeeNwk", LOG_LEVEL_DEBUG);
RngSeedManager::SetSeed(3);
RngSeedManager::SetRun(4);
NodeContainer nodes;
nodes.Create(5);
//// Configure MAC
LrWpanHelper lrWpanHelper;
NetDeviceContainer lrwpanDevices = lrWpanHelper.Install(nodes);
Ptr<LrWpanNetDevice> dev0 = lrwpanDevices.Get(0)->GetObject<LrWpanNetDevice>();
Ptr<LrWpanNetDevice> dev1 = lrwpanDevices.Get(1)->GetObject<LrWpanNetDevice>();
Ptr<LrWpanNetDevice> dev2 = lrwpanDevices.Get(2)->GetObject<LrWpanNetDevice>();
Ptr<LrWpanNetDevice> dev3 = lrwpanDevices.Get(3)->GetObject<LrWpanNetDevice>();
Ptr<LrWpanNetDevice> dev4 = lrwpanDevices.Get(4)->GetObject<LrWpanNetDevice>();
// Device must ALWAYS have IEEE Address (Extended address) assigned.
// Network address (short address) are assigned by the the JOIN mechanism
dev0->GetMac()->SetExtendedAddress("00:00:00:00:00:00:CA:FE");
dev1->GetMac()->SetExtendedAddress("00:00:00:00:00:00:00:01");
dev2->GetMac()->SetExtendedAddress("00:00:00:00:00:00:00:02");
dev3->GetMac()->SetExtendedAddress("00:00:00:00:00:00:00:03");
dev4->GetMac()->SetExtendedAddress("00:00:00:00:00:00:00:04");
Ptr<SingleModelSpectrumChannel> channel = CreateObject<SingleModelSpectrumChannel>();
Ptr<LogDistancePropagationLossModel> propModel =
CreateObject<LogDistancePropagationLossModel>();
Ptr<ConstantSpeedPropagationDelayModel> delayModel =
CreateObject<ConstantSpeedPropagationDelayModel>();
channel->AddPropagationLossModel(propModel);
channel->SetPropagationDelayModel(delayModel);
dev0->SetChannel(channel);
dev1->SetChannel(channel);
dev2->SetChannel(channel);
dev3->SetChannel(channel);
dev4->SetChannel(channel);
// Configure the Zigbee stack, by default both the NWK and the APS are present
ZigbeeHelper zigbeeHelper;
ZigbeeStackContainer zigbeeStackContainer = zigbeeHelper.Install(lrwpanDevices);
Ptr<ZigbeeStack> zstack0 = zigbeeStackContainer.Get(0)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack1 = zigbeeStackContainer.Get(1)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack2 = zigbeeStackContainer.Get(2)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack3 = zigbeeStackContainer.Get(3)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack4 = zigbeeStackContainer.Get(4)->GetObject<ZigbeeStack>();
// Add the stacks to a container to later on print routes.
zigbeeStacks.Add(zstack0);
zigbeeStacks.Add(zstack1);
zigbeeStacks.Add(zstack2);
zigbeeStacks.Add(zstack3);
zigbeeStacks.Add(zstack4);
// Assign streams to the zigbee stacks to obtain
// reprodusable results from random events occurring inside the stack.
zstack0->GetNwk()->AssignStreams(0);
zstack1->GetNwk()->AssignStreams(10);
zstack2->GetNwk()->AssignStreams(20);
zstack3->GetNwk()->AssignStreams(30);
zstack4->GetNwk()->AssignStreams(40);
//// Configure Nodes Mobility
Ptr<ConstantPositionMobilityModel> dev0Mobility = CreateObject<ConstantPositionMobilityModel>();
dev0Mobility->SetPosition(Vector(0, 0, 0));
dev0->GetPhy()->SetMobility(dev0Mobility);
Ptr<ConstantPositionMobilityModel> dev1Mobility = CreateObject<ConstantPositionMobilityModel>();
dev1Mobility->SetPosition(Vector(90, 0, 0));
dev1->GetPhy()->SetMobility(dev1Mobility);
Ptr<ConstantPositionMobilityModel> dev2Mobility = CreateObject<ConstantPositionMobilityModel>();
dev2Mobility->SetPosition(Vector(170, 0, 0));
dev2->GetPhy()->SetMobility(dev2Mobility);
Ptr<ConstantPositionMobilityModel> dev3Mobility = CreateObject<ConstantPositionMobilityModel>();
dev3Mobility->SetPosition(Vector(250, 0, 0));
dev3->GetPhy()->SetMobility(dev3Mobility);
Ptr<ConstantPositionMobilityModel> dev4Mobility = CreateObject<ConstantPositionMobilityModel>();
dev4Mobility->SetPosition(Vector(90, 50, 0));
dev4->GetPhy()->SetMobility(dev4Mobility);
// NWK callbacks hooks
// These hooks are usually directly connected to the ZDO.
// In this case, there is no ZDO, therefore, we connect the event outputs
// of all devices directly to our static functions in this example.
zstack0->GetNwk()->SetNlmeNetworkFormationConfirmCallback(
MakeBoundCallback(&NwkNetworkFormationConfirm, zstack0));
zstack0->GetNwk()->SetNlmeRouteDiscoveryConfirmCallback(
MakeBoundCallback(&NwkRouteDiscoveryConfirm, zstack0));
zstack1->GetNwk()->SetNlmeNetworkDiscoveryConfirmCallback(
MakeBoundCallback(&NwkNetworkDiscoveryConfirm, zstack1));
zstack2->GetNwk()->SetNlmeNetworkDiscoveryConfirmCallback(
MakeBoundCallback(&NwkNetworkDiscoveryConfirm, zstack2));
zstack3->GetNwk()->SetNlmeNetworkDiscoveryConfirmCallback(
MakeBoundCallback(&NwkNetworkDiscoveryConfirm, zstack3));
zstack4->GetNwk()->SetNlmeNetworkDiscoveryConfirmCallback(
MakeBoundCallback(&NwkNetworkDiscoveryConfirm, zstack4));
zstack1->GetNwk()->SetNlmeJoinConfirmCallback(MakeBoundCallback(&NwkJoinConfirm, zstack1));
zstack2->GetNwk()->SetNlmeJoinConfirmCallback(MakeBoundCallback(&NwkJoinConfirm, zstack2));
zstack3->GetNwk()->SetNlmeJoinConfirmCallback(MakeBoundCallback(&NwkJoinConfirm, zstack3));
zstack4->GetNwk()->SetNlmeJoinConfirmCallback(MakeBoundCallback(&NwkJoinConfirm, zstack4));
// APS callback hooks
zstack0->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, zstack0));
zstack1->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, zstack1));
zstack2->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, zstack2));
zstack3->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, zstack3));
zstack4->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, zstack4));
// 1 - Initiate the Zigbee coordinator, start the network
// ALL_CHANNELS = 0x07FFF800 (Channels 11~26)
NlmeNetworkFormationRequestParams netFormParams;
netFormParams.m_scanChannelList.channelPageCount = 1;
netFormParams.m_scanChannelList.channelsField[0] = ALL_CHANNELS;
netFormParams.m_scanDuration = 0;
netFormParams.m_superFrameOrder = 15;
netFormParams.m_beaconOrder = 15;
Simulator::ScheduleWithContext(zstack0->GetNode()->GetId(),
Seconds(1),
&ZigbeeNwk::NlmeNetworkFormationRequest,
zstack0->GetNwk(),
netFormParams);
// 2- Schedule devices sequentially find and join the network.
// After this procedure, each device make a NLME-START-ROUTER.request to become a router
NlmeNetworkDiscoveryRequestParams netDiscParams;
netDiscParams.m_scanChannelList.channelPageCount = 1;
netDiscParams.m_scanChannelList.channelsField[0] = 0x00007800; // BitMap: Channels 11~14
netDiscParams.m_scanDuration = 2;
Simulator::ScheduleWithContext(zstack1->GetNode()->GetId(),
Seconds(3),
&ZigbeeNwk::NlmeNetworkDiscoveryRequest,
zstack1->GetNwk(),
netDiscParams);
NlmeNetworkDiscoveryRequestParams netDiscParams2;
netDiscParams2.m_scanChannelList.channelPageCount = 1;
netDiscParams2.m_scanChannelList.channelsField[0] = 0x00007800; // BitMap: Channels 11~14
netDiscParams2.m_scanDuration = 2;
Simulator::ScheduleWithContext(zstack2->GetNode()->GetId(),
Seconds(4),
&ZigbeeNwk::NlmeNetworkDiscoveryRequest,
zstack2->GetNwk(),
netDiscParams2);
NlmeNetworkDiscoveryRequestParams netDiscParams3;
netDiscParams2.m_scanChannelList.channelPageCount = 1;
netDiscParams2.m_scanChannelList.channelsField[0] = 0x00007800; // BitMap: Channels 11~14
netDiscParams2.m_scanDuration = 2;
Simulator::ScheduleWithContext(zstack3->GetNode()->GetId(),
Seconds(5),
&ZigbeeNwk::NlmeNetworkDiscoveryRequest,
zstack3->GetNwk(),
netDiscParams3);
NlmeNetworkDiscoveryRequestParams netDiscParams4;
netDiscParams4.m_scanChannelList.channelPageCount = 1;
netDiscParams4.m_scanChannelList.channelsField[0] = 0x00007800; // BitMap: Channels 11~14
netDiscParams4.m_scanDuration = 2;
Simulator::ScheduleWithContext(zstack4->GetNode()->GetId(),
Seconds(6),
&ZigbeeNwk::NlmeNetworkDiscoveryRequest,
zstack4->GetNwk(),
netDiscParams4);
// 5- Find Route and Send data (Call to APS layer)
Simulator::Schedule(Seconds(8), &SendData, zstack0, zstack3);
Simulator::Stop(Seconds(20));
Simulator::Run();
Simulator::Destroy();
return 0;
}

View File

@@ -160,10 +160,11 @@ main(int argc, char* argv[])
dev1->SetChannel(channel);
dev2->SetChannel(channel);
//// Configure NWK
//// Configure the Zigbee Stack and use only the NWK layer
ZigbeeHelper zigbee;
ZigbeeStackContainer zigbeeStackContainer = zigbee.Install(lrwpanDevices);
ZigbeeHelper zigbeeHelper;
zigbeeHelper.SetNwkLayerOnly();
ZigbeeStackContainer zigbeeStackContainer = zigbeeHelper.Install(lrwpanDevices);
Ptr<ZigbeeStack> zstack0 = zigbeeStackContainer.Get(0)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack1 = zigbeeStackContainer.Get(1)->GetObject<ZigbeeStack>();

View File

@@ -120,10 +120,11 @@ main(int argc, char* argv[])
dev1->SetChannel(channel);
dev2->SetChannel(channel);
//// Configure NWK
//// Configure the Zigbee Stack and use only the NWK layer
ZigbeeHelper zigbee;
ZigbeeStackContainer zigbeeStackContainer = zigbee.Install(lrwpanDevices);
ZigbeeHelper zigbeeHelper;
zigbeeHelper.SetNwkLayerOnly();
ZigbeeStackContainer zigbeeStackContainer = zigbeeHelper.Install(lrwpanDevices);
Ptr<ZigbeeStack> zstack0 = zigbeeStackContainer.Get(0)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack1 = zigbeeStackContainer.Get(1)->GetObject<ZigbeeStack>();

View File

@@ -279,6 +279,7 @@ main(int argc, char* argv[])
lrWpanHelper.SetExtendedAddresses(lrwpanDevices);
ZigbeeHelper zigbeeHelper;
zigbeeHelper.SetNwkLayerOnly();
zigbeeStacks = zigbeeHelper.Install(lrwpanDevices);
// NWK callbacks hooks

View File

@@ -275,10 +275,11 @@ main(int argc, char* argv[])
dev3->SetChannel(channel);
dev4->SetChannel(channel);
//// Configure NWK
// Configure the Zigbee stack and use only the NWK layer.
ZigbeeHelper zigbee;
ZigbeeStackContainer zigbeeStackContainer = zigbee.Install(lrwpanDevices);
ZigbeeHelper zigbeeHelper;
zigbeeHelper.SetNwkLayerOnly();
ZigbeeStackContainer zigbeeStackContainer = zigbeeHelper.Install(lrwpanDevices);
Ptr<ZigbeeStack> zstack0 = zigbeeStackContainer.Get(0)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack1 = zigbeeStackContainer.Get(1)->GetObject<ZigbeeStack>();

View File

@@ -24,6 +24,7 @@ ZigbeeHelper::ZigbeeHelper()
{
NS_LOG_FUNCTION(this);
m_stackFactory.SetTypeId("ns3::zigbee::ZigbeeStack");
m_nwkLayerOnly = false;
}
void
@@ -49,6 +50,12 @@ ZigbeeHelper::Install(const NetDeviceContainer c)
NS_LOG_LOGIC("Installing Zigbee on node " << device->GetNode()->GetId());
Ptr<zigbee::ZigbeeStack> zigbeeStack = m_stackFactory.Create<zigbee::ZigbeeStack>();
if (m_nwkLayerOnly)
{
zigbeeStack->SetOnlyNwkLayer();
}
zigbeeStackContainer.Add(zigbeeStack);
device->GetNode()->AggregateObject(zigbeeStack);
zigbeeStack->SetNetDevice(device);
@@ -56,4 +63,10 @@ ZigbeeHelper::Install(const NetDeviceContainer c)
return zigbeeStackContainer;
}
void
ZigbeeHelper::SetNwkLayerOnly()
{
m_nwkLayerOnly = true;
}
} // namespace ns3

View File

@@ -15,6 +15,7 @@
#include "ns3/net-device-container.h"
#include "ns3/object-factory.h"
#include <optional>
#include <string>
namespace ns3
@@ -63,6 +64,12 @@ class ZigbeeHelper
*/
zigbee::ZigbeeStackContainer Install(NetDeviceContainer c);
/**
* If this is set, the helper will only create Zigbee stacks that contain
* only the NWK layer
*/
void SetNwkLayerOnly();
/**
* Assign a fixed random variable stream number to the random variables
* used by this model. Return the number of streams (possibly zero) that
@@ -78,6 +85,7 @@ class ZigbeeHelper
private:
ObjectFactory m_stackFactory; //!< Zigbee stack object factory.
bool m_nwkLayerOnly; //!< Flag indicating that only the NWK layer is present
};
} // namespace ns3

View File

@@ -0,0 +1,366 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Authors:
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#include "zigbee-aps-header.h"
#include "ns3/address-utils.h"
namespace ns3
{
namespace zigbee
{
ZigbeeApsHeader::ZigbeeApsHeader()
{
m_frameType = ApsFrameType::APS_DATA;
m_deliveryMode = ApsDeliveryMode::APS_UCST;
m_ackFormat = false;
m_security = false;
m_ackRequest = false;
m_extHeaderPresent = false;
}
ZigbeeApsHeader::~ZigbeeApsHeader()
{
}
void
ZigbeeApsHeader::SetFrameType(enum ApsFrameType type)
{
m_frameType = type;
}
ApsFrameType
ZigbeeApsHeader::GetFrameType() const
{
return m_frameType;
}
void
ZigbeeApsHeader::SetDeliveryMode(enum ApsDeliveryMode mode)
{
m_deliveryMode = mode;
}
ApsDeliveryMode
ZigbeeApsHeader::GetDeliveryMode() const
{
return m_deliveryMode;
}
void
ZigbeeApsHeader::SetSecurity(bool enabled)
{
m_security = enabled;
}
bool
ZigbeeApsHeader::IsSecurityEnabled() const
{
return m_security;
}
void
ZigbeeApsHeader::SetAckRequest(bool ack)
{
m_ackRequest = ack;
}
bool
ZigbeeApsHeader::IsAckRequested() const
{
return m_ackRequest;
}
void
ZigbeeApsHeader::SetExtHeaderPresent(bool present)
{
m_extHeaderPresent = present;
}
bool
ZigbeeApsHeader::IsExtHeaderPresent() const
{
return m_extHeaderPresent;
}
void
ZigbeeApsHeader::SetDstEndpoint(uint8_t dst)
{
m_dstEndpoint = dst;
}
uint8_t
ZigbeeApsHeader::GetDstEndpoint() const
{
return m_dstEndpoint;
}
void
ZigbeeApsHeader::SetClusterId(uint16_t clusterId)
{
m_clusterId = clusterId;
}
uint16_t
ZigbeeApsHeader::GetClusterId() const
{
return m_clusterId;
}
void
ZigbeeApsHeader::SetProfileId(uint16_t profileId)
{
m_profileId = profileId;
}
uint16_t
ZigbeeApsHeader::GetProfileId() const
{
return m_profileId;
}
void
ZigbeeApsHeader::SetSrcEndpoint(uint8_t src)
{
m_srcEndpoint = src;
}
uint8_t
ZigbeeApsHeader::GetSrcEndpoint() const
{
return m_srcEndpoint;
}
void
ZigbeeApsHeader::SetApsCounter(uint8_t counter)
{
m_apsCounter = counter;
}
uint8_t
ZigbeeApsHeader::GetApsCounter() const
{
return m_apsCounter;
}
void
ZigbeeApsHeader::Serialize(Buffer::Iterator start) const
{
Buffer::Iterator i = start;
// Frame control field
uint8_t frameControl = (m_frameType & 0x03) | ((m_deliveryMode & 0x03) << 2) |
((m_ackFormat ? 1 : 0) << 4) | ((m_security ? 1 : 0) << 5) |
((m_ackRequest ? 1 : 0) << 6) | ((m_extHeaderPresent ? 1 : 0) << 7);
i.WriteU8(frameControl);
// Addressing fields
if (m_deliveryMode == ApsDeliveryMode::APS_UCST || m_deliveryMode == ApsDeliveryMode::APS_BCST)
{
i.WriteU8(m_dstEndpoint);
}
if (m_deliveryMode == ApsDeliveryMode::APS_GROUP_ADDRESSING)
{
i.WriteHtolsbU16(m_groupAddress);
}
if (m_frameType == ApsFrameType::APS_DATA || m_frameType == ApsFrameType::APS_ACK)
{
i.WriteHtolsbU16(m_clusterId);
i.WriteHtolsbU16(m_profileId);
}
if (m_frameType == ApsFrameType::APS_DATA)
{
i.WriteU8(m_srcEndpoint);
}
i.WriteU8(m_apsCounter);
// Extended Header
if (m_extHeaderPresent)
{
// Extended Frame control field
uint8_t extFrameControl = (m_fragmentation & 0x03);
i.WriteU8(extFrameControl);
// Block control
if (m_fragmentation != ApsFragmentation::NOT_FRAGMENTED)
{
i.WriteU8(m_blockNumber);
}
// ACK Bitfield
if (m_frameType == ApsFrameType::APS_ACK)
{
i.WriteU8(m_ackBitfield);
}
}
}
uint32_t
ZigbeeApsHeader::Deserialize(Buffer::Iterator start)
{
Buffer::Iterator i = start;
uint8_t frameControl = i.ReadU8();
m_frameType = static_cast<ApsFrameType>(frameControl & 0x03);
m_deliveryMode = static_cast<ApsDeliveryMode>((frameControl >> 2) & 0x03);
m_ackFormat = (frameControl >> 4) & 0x01;
m_security = (frameControl >> 5) & 0x01;
m_ackRequest = (frameControl >> 6) & 0x01;
m_extHeaderPresent = (frameControl >> 7) & 0x01;
// Addressing fields
if (m_deliveryMode == ApsDeliveryMode::APS_UCST || m_deliveryMode == ApsDeliveryMode::APS_BCST)
{
m_dstEndpoint = i.ReadU8();
}
if (m_deliveryMode == ApsDeliveryMode::APS_GROUP_ADDRESSING)
{
m_groupAddress = i.ReadLsbtohU16();
}
if (m_frameType == ApsFrameType::APS_DATA || m_frameType == ApsFrameType::APS_ACK)
{
m_clusterId = i.ReadLsbtohU16();
m_profileId = i.ReadLsbtohU16();
}
if (m_frameType == ApsFrameType::APS_DATA)
{
m_srcEndpoint = i.ReadU8();
}
m_apsCounter = i.ReadU8();
// Extended Header
if (m_extHeaderPresent)
{
// Extended Frame control field
uint8_t extFrameControl = i.ReadU8();
m_fragmentation = static_cast<ApsFragmentation>(extFrameControl & 0x03);
// Block control
if (m_fragmentation != ApsFragmentation::NOT_FRAGMENTED)
{
m_blockNumber = i.ReadU8();
}
// ACK Bitfield
if (m_frameType == ApsFrameType::APS_ACK)
{
m_ackBitfield = i.ReadU8();
}
}
return i.GetDistanceFrom(start);
}
uint32_t
ZigbeeApsHeader::GetSerializedSize() const
{
uint8_t totalSize;
// See Zigbee Specification r22.1.0
// Fixed field:
// Frame Control (1) + APS Counter (1)
totalSize = 2;
// Variable Fields:
// Destination EndPoint field (1) (Section 2.2.5.1.2)
if (m_deliveryMode == ApsDeliveryMode::APS_UCST || m_deliveryMode == ApsDeliveryMode::APS_BCST)
{
totalSize += 1;
}
// Group Address field (2) (Section 2.2.5.1.3)
if (m_deliveryMode == ApsDeliveryMode::APS_GROUP_ADDRESSING)
{
totalSize += 2;
}
// Cluster identifier field and Profile identifier field (4)
// (Sections 2.2.5.1.4 and 2.2.5.15)
if (m_frameType == ApsFrameType::APS_DATA || m_frameType == ApsFrameType::APS_ACK)
{
totalSize += 4;
}
// Source Endpoint field (1) (Section 2.2.5.1.6)
if (m_frameType == ApsFrameType::APS_DATA)
{
totalSize += 1;
}
// Extended header sub-frame (variable)
if (m_extHeaderPresent)
{
// Extended Frame control field (1)
totalSize += 1;
// Block control (1)
if (m_fragmentation != ApsFragmentation::NOT_FRAGMENTED)
{
totalSize += 1;
}
// ACK Bitfield
if (m_frameType == ApsFrameType::APS_ACK)
{
totalSize += 1;
}
}
return totalSize;
}
TypeId
ZigbeeApsHeader::GetTypeId()
{
static TypeId tid = TypeId("ns3::zigbee::ZigbeeApsHeader")
.SetParent<Header>()
.SetGroupName("Zigbee")
.AddConstructor<ZigbeeApsHeader>();
return tid;
}
TypeId
ZigbeeApsHeader::GetInstanceTypeId() const
{
return GetTypeId();
}
void
ZigbeeApsHeader::Print(std::ostream& os) const
{
// TODO:
/* os << "\nAPS Frame Control = " << std::hex << std::showbase << static_cast<uint32_t>(
(m_frameType & 0x03) |
((m_deliveryMode & 0x03) << 2) |
((m_ackFormat ? 1 : 0) << 4) |
((m_security ? 1 : 0) << 5) |
((m_ackRequest ? 1 : 0) << 6) |
((m_extHeader ? 1 : 0) << 7));
os << " | Dst EP = " << static_cast<uint32_t>(m_dstEndpoint)
<< " | Src EP = " << static_cast<uint32_t>(m_srcEndpoint)
<< " | Cluster ID = " << m_clusterId
<< " | Profile ID = " << m_profileId
<< " | Counter = " << static_cast<uint32_t>(m_counter);
os << "\n";*/
}
} // namespace zigbee
} // namespace ns3

View File

@@ -0,0 +1,264 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Authors:
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#ifndef ZIGBEE_APS_HEADER_H
#define ZIGBEE_APS_HEADER_H
#include "ns3/header.h"
#include "ns3/mac16-address.h"
#include "ns3/mac64-address.h"
namespace ns3
{
namespace zigbee
{
/**
* @ingroup zigbee
*
* Values of the Frame Type Sub-Field.
* Zigbee Specification r22.1.0, Table 2-20
*/
enum ApsFrameType : uint8_t
{
APS_DATA = 0x00,
APS_COMMAND = 0x01,
APS_ACK = 0x02,
APS_INTERPAN_APS = 0x03
};
/**
* @ingroup zigbee
*
* Values of the Delivery Mode Sub-Field.
* Zigbee Specification r22.1.0, Table 2-21
*/
enum ApsDeliveryMode : uint8_t
{
APS_UCST = 0x00,
APS_BCST = 0x02,
APS_GROUP_ADDRESSING = 0x03
};
/**
* @ingroup zigbee
*
* Table 2-22 Values of the Fragmentation Sub-Field
* Zigbee Specification r22.1.0, Table 2-22
*/
enum ApsFragmentation : uint8_t
{
NOT_FRAGMENTED = 0x00,
FIRST_FRAGMENT = 0x01,
OTHER_FRAGMENT = 0x02
};
/**
* @ingroup zigbee
*
* Defines the APS header use by data transfer and commands issued from
* the APS layer. Zigbee Specification r22.1.0, Section 2.2.5.1.
*
*/
class ZigbeeApsHeader : public Header
{
public:
ZigbeeApsHeader();
~ZigbeeApsHeader() override;
/**
* Set the Frame type defined in the APS layer
*
* @param frameType The frame type to set on the header
*/
void SetFrameType(enum ApsFrameType frameType);
/**
* Get the frame time present in the header
*
* @return ApsFrameType
*/
ApsFrameType GetFrameType() const;
/**
* Defines the mode in which this frame should be transmitted.
*
* @param mode The delivery mode to set on the APS header
*/
void SetDeliveryMode(enum ApsDeliveryMode mode);
/**
* Get the transmission mode set on the header.
*
* @return ApsDeliveryMode The delivery mode of the header
*/
ApsDeliveryMode GetDeliveryMode() const;
/**
* Set whether or not security should be used to transmit
* the frame.
*
* @param enabled True if security should be used
*/
void SetSecurity(bool enabled);
/**
* Returns whether or not security is enabled for the present frame.alignas
*
* @return True if security is enabled for the frame.
*/
bool IsSecurityEnabled() const;
/**
* Set the acknowledment flag in the APS header.
*
* @param request True if the frame should be acknowledged
*/
void SetAckRequest(bool request);
/**
* Indicates if the present frame requires acknowledgment.
*
* @return True if the frame requires acknowledgment
*/
bool IsAckRequested() const;
/**
* Enables or disables the usage of the extended header.
* Only used when fragmentation is used.
*
* @param present True if the extended header (fragmentation) is present.
*/
void SetExtHeaderPresent(bool present);
/**
* Indicates whether or not the extended header is present in the frame.
* @return True if the extended header is present.
*/
bool IsExtHeaderPresent() const;
/**
* Set the Bitmap representing the framecontrol portion of the APS header.
*
* @param frameControl The bitmap representing the framecontrol portion.
*/
void SetFrameControl(uint8_t frameControl);
/**
* Get the frame control portion (bitmap) of the APS header.
*
* @return The frame control portion of the APS header.
*/
uint8_t GetFrameControl() const;
/**
* Set the destination endpoint in the APS header.
*
* @param endpoint The destination endpoint.
*/
void SetDstEndpoint(uint8_t endpoint);
/**
* Get the destination endpoint in the APS header.
*
* @return The destination endpoint.
*/
uint8_t GetDstEndpoint() const;
/**
* Set the cluster id in the APS header.
*
* @param clusterId The cluster id
*/
void SetClusterId(uint16_t clusterId);
/**
* Get the cluster id in the APS header.
* @return The cluster id.
*/
uint16_t GetClusterId() const;
/**
* Set the profile ID in the APS header.
*
* @param profileId The profile ID.
*/
void SetProfileId(uint16_t profileId);
/**
* Get the profile ID in the APS header.
* @return The profile ID.
*/
uint16_t GetProfileId() const;
/**
* Set the source endpoint in the APS header.
*
* @param endpoint The source endpoint.
*/
void SetSrcEndpoint(uint8_t endpoint);
/**
* Get the source endpoint in the APS header.
* @return The source endpoint.
*/
uint8_t GetSrcEndpoint() const;
/**
* Set the value of the APS counter in the APS header.
* @param counter The APS counter value.
*/
void SetApsCounter(uint8_t counter);
/**
* Get the APS counter value present in the APS header.
* @return The APS counter value.
*/
uint8_t GetApsCounter() const;
/**
* @brief Get the type ID.
* @return the object TypeId
*/
static TypeId GetTypeId();
TypeId GetInstanceTypeId() const override;
void Serialize(Buffer::Iterator start) const override;
uint32_t Deserialize(Buffer::Iterator start) override;
uint32_t GetSerializedSize() const override;
void Print(std::ostream& os) const override;
private:
// Frame Control field bits
ApsFrameType m_frameType; //!< Frame control field: Frame type
ApsDeliveryMode m_deliveryMode; //!< Frame control field: Delivery mode
bool m_ackFormat; //!< Frame control field: Acknowledgment format
bool m_security; //!< Frame control field: Security
bool m_ackRequest; //!< Frame control field: Acknowledge requested
bool m_extHeaderPresent; //!< Frame control field: Extended header present
// Addressing fields
uint8_t m_dstEndpoint; //!< Addressing field: Destination endpoint.
uint16_t m_groupAddress; //!< Addressing field: Group or 16-bit address.
uint16_t m_clusterId; //!< Addressing field: Cluster ID.
uint16_t m_profileId; //!< Addressing field: Profile ID.
uint8_t m_srcEndpoint; //!< Addressing field: Source endpoint.
uint8_t m_apsCounter; //!< APS counter field
// Extended header fields
ApsFragmentation m_fragmentation; //!< Extended header field: Fragmentation block type
uint8_t m_blockNumber; //!< Extended header field: Block number
uint8_t m_ackBitfield; //!< Extended header field: Acknowledgement bit field
};
} // namespace zigbee
} // namespace ns3
#endif // ZIGBEE_APS_HEADER_H

View File

@@ -0,0 +1,278 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Authors:
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#include "zigbee-aps-tables.h"
#include "ns3/log.h"
#include "ns3/pointer.h"
#include "ns3/simulator.h"
#include <algorithm>
#include <iomanip>
namespace ns3
{
namespace zigbee
{
NS_LOG_COMPONENT_DEFINE("ZigbeeApsTables");
/***********************************************************
* Source Binding Entry
***********************************************************/
SrcBindingEntry::SrcBindingEntry()
{
}
SrcBindingEntry::SrcBindingEntry(Mac64Address address, uint8_t endPoint, uint16_t clusterId)
{
m_srcAddr = address;
m_srcEndPoint = endPoint;
m_clusterId = clusterId;
}
SrcBindingEntry::~SrcBindingEntry()
{
}
void
SrcBindingEntry::SetSrcAddress(Mac64Address address)
{
m_srcAddr = address;
}
void
SrcBindingEntry::SetSrcEndPoint(uint8_t endPoint)
{
m_srcEndPoint = endPoint;
}
void
SrcBindingEntry::SetClusterId(uint16_t clusterId)
{
m_clusterId = clusterId;
}
Mac64Address
SrcBindingEntry::GetSrcAddress() const
{
return m_srcAddr;
}
uint8_t
SrcBindingEntry::GetSrcEndPoint() const
{
return m_srcEndPoint;
}
uint16_t
SrcBindingEntry::GetClusterId() const
{
return m_clusterId;
}
/***********************************************************
* Destination Binding Entry
***********************************************************/
DstBindingEntry::DstBindingEntry()
{
}
DstBindingEntry::~DstBindingEntry()
{
}
void
DstBindingEntry::SetDstAddrMode(ApsDstAddressModeBind mode)
{
m_dstAddrMode = mode;
}
void
DstBindingEntry::SetDstAddr16(Mac16Address address)
{
m_dstAddr16 = address;
}
void
DstBindingEntry::SetDstAddr64(Mac64Address address)
{
m_dstAddr64 = address;
}
void
DstBindingEntry::SetDstEndPoint(uint8_t endPoint)
{
m_dstEndPoint = endPoint;
}
ApsDstAddressModeBind
DstBindingEntry::GetDstAddrMode() const
{
return m_dstAddrMode;
}
Mac16Address
DstBindingEntry::GetDstAddr16() const
{
return m_dstAddr16;
}
Mac64Address
DstBindingEntry::GetDstAddr64() const
{
return m_dstAddr64;
}
uint8_t
DstBindingEntry::GetDstEndPoint() const
{
return m_dstEndPoint;
}
/***********************************************************
* Binding Table
***********************************************************/
BindingTable::BindingTable()
{
m_maxSrcEntries = 10;
m_maxDstEntries = 10;
}
bool
BindingTable::CompareDestinations(const DstBindingEntry& first, const DstBindingEntry& second)
{
if (first.GetDstAddrMode() == ApsDstAddressModeBind::GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT)
{
// Group Addressing
if ((first.GetDstAddr16() == second.GetDstAddr16()) &&
(first.GetDstEndPoint() == second.GetDstEndPoint()))
{
return true;
}
}
else if (first.GetDstAddrMode() == ApsDstAddressModeBind::DST_ADDR64_DST_ENDPOINT_PRESENT)
{
// IEEE Addressing
if ((first.GetDstAddr64() == second.GetDstAddr64()) &&
(first.GetDstEndPoint() == second.GetDstEndPoint()))
{
return true;
}
}
return false;
}
bool
BindingTable::CompareSources(const SrcBindingEntry& first, const SrcBindingEntry& second)
{
return ((first.GetSrcAddress() == second.GetSrcAddress()) &&
(first.GetSrcEndPoint() == second.GetSrcEndPoint()) &&
(first.GetClusterId() == second.GetClusterId()));
}
BindingTableStatus
BindingTable::Bind(const SrcBindingEntry& src, const DstBindingEntry& dst)
{
for (auto& entry : m_bindingTable)
{
if (CompareSources(src, entry.first))
{
// The source exist, now check if the destination exist
for (const auto& destination : entry.second)
{
if (CompareDestinations(dst, destination))
{
NS_LOG_WARN("Entry already exist in binding table");
return BindingTableStatus::ENTRY_EXISTS;
}
}
// Add the new destination bound to the source
if (entry.second.size() >= m_maxDstEntries)
{
NS_LOG_WARN("Binding Table full, max destination entries (" << m_maxDstEntries
<< ") reached");
return BindingTableStatus::TABLE_FULL;
}
else
{
entry.second.emplace_back(dst);
return BindingTableStatus::BOUND;
}
}
}
if (m_bindingTable.size() >= m_maxSrcEntries)
{
NS_LOG_WARN("Binding Table full, max source entries (" << m_maxSrcEntries << ") reached");
return BindingTableStatus::TABLE_FULL;
}
else
{
// New source with its first destination
m_bindingTable.emplace_back(src, std::vector<DstBindingEntry>{dst});
return BindingTableStatus::BOUND;
}
}
BindingTableStatus
BindingTable::Unbind(const SrcBindingEntry& src, const DstBindingEntry& dst)
{
for (auto it = m_bindingTable.begin(); it != m_bindingTable.end(); ++it)
{
if (CompareSources(src, it->first))
{
// The source exists, now check if the destination exists
auto& destinations = it->second;
for (auto destIt = destinations.begin(); destIt != destinations.end(); ++destIt)
{
if (CompareDestinations(dst, *destIt))
{
// Destination found, remove it
destinations.erase(destIt);
// If no destinations left, remove the source entry
if (destinations.empty())
{
m_bindingTable.erase(it);
}
// Successfully unbound
return BindingTableStatus::UNBOUND;
}
}
// Destination not found
NS_LOG_WARN("Cannot unbind, destination entry do not exist");
return BindingTableStatus::ENTRY_NOT_FOUND;
}
}
// Source not found
NS_LOG_WARN("Cannot unbind, source entry do not exist");
return BindingTableStatus::ENTRY_NOT_FOUND;
}
bool
BindingTable::LookUpEntries(const SrcBindingEntry& src, std::vector<DstBindingEntry>& dstEntries)
{
for (auto& entry : m_bindingTable)
{
if (CompareSources(src, entry.first))
{
// The source entry exist, return all the dst entries.
dstEntries = entry.second;
return true;
}
}
return false;
}
} // namespace zigbee
} // namespace ns3

View File

@@ -0,0 +1,272 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author:
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#ifndef ZIGBEE_APS_TABLES_H
#define ZIGBEE_APS_TABLES_H
#include "ns3/mac16-address.h"
#include "ns3/mac64-address.h"
#include "ns3/output-stream-wrapper.h"
#include "ns3/timer.h"
#include <cassert>
#include <deque>
#include <stdint.h>
#include <sys/types.h>
namespace ns3
{
namespace zigbee
{
/**
* @ingroup zigbee
*
* APS Destination Address Mode for Binding
* Zigbee Specification r22.1.0, Table 2-6
* APSME-BIND.request Parameters
*/
enum class ApsDstAddressModeBind : std::uint8_t
{
GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT = 0x01,
DST_ADDR64_DST_ENDPOINT_PRESENT = 0x03
};
/**
* @ingroup zigbee
*
* The status resulting of interactions with the binding table.
*/
enum class BindingTableStatus : std::uint8_t
{
BOUND = 0,
UNBOUND = 1,
TABLE_FULL = 2,
ENTRY_EXISTS = 3,
ENTRY_NOT_FOUND = 4
};
/**
* @ingroup zigbee
*
* Binding Table entry: Source portion of the table.
* As described in Zigbee Specification r22.1.0, Table 2-134
*/
class SrcBindingEntry : public SimpleRefCount<SrcBindingEntry>
{
public:
/**
* The default constructor of the source binding entry.
*/
SrcBindingEntry();
/**
* Constructor of the source binding entry.
*
* @param address The IEEE address to register, (typically the IEEE address of the source
* device).
* @param endPoint The source endpoint to register to the entry.
* @param clusterId The cluster ID to register to the entry.
*/
SrcBindingEntry(Mac64Address address, uint8_t endPoint, uint16_t clusterId);
~SrcBindingEntry();
/**
* Set the source IEEE address to the entry.
*
* @param address The IEEE address (64-bit address) of the entry
*/
void SetSrcAddress(Mac64Address address);
/**
* Set the source endpoint of the source binding entry.
* @param endPoint The source endpoint to set in the entry.
*/
void SetSrcEndPoint(uint8_t endPoint);
/**
* Set the cluster ID of the source binding entry.
* @param clusterId The cluster ID to set in the entry.
*/
void SetClusterId(uint16_t clusterId);
/**
* Get the IEEE address from the source binding entry.
* @return The IEEE address in the source binding entry.
*/
Mac64Address GetSrcAddress() const;
/**
* Get the source endpoint from the source binding entry.
*
* @return The source endpoint.
*/
uint8_t GetSrcEndPoint() const;
/**
* Get the cluster ID from the source binding entry.
* @return The cluster ID.
*/
uint16_t GetClusterId() const;
private:
Mac64Address m_srcAddr; //!< The source IEEE address in the source entry.
uint8_t m_srcEndPoint{0}; //!< The source endpoint in the source entry.
uint16_t m_clusterId{0}; //!< The cluster ID in the source entry.
};
/**
* Binding Table entry: Destination portion of the table.
* As described in Zigbee Specification r22.1.0, Table 2-134
*/
class DstBindingEntry : public SimpleRefCount<DstBindingEntry>
{
public:
/**
* The default constructor of the destination binding entry.
*/
DstBindingEntry();
~DstBindingEntry();
/**
* Set the destination address mode of the destination binding entry.
* @param mode The destination address mode to set.
*/
void SetDstAddrMode(ApsDstAddressModeBind mode);
/**
* Set the destination 16-bit address of the destination binding entry.
* @param address The 16-bit address of the destination binding entry.
*/
void SetDstAddr16(Mac16Address address);
/**
* Set the destination IEEE Address (64-bit address) of the destination binding entry.
* @param address The destination IEEE address (64-bit address) to set
*/
void SetDstAddr64(Mac64Address address);
/**
* Set the destination endppoint to the destination binding entry.
* @param endPoint The destination endpoint to set.
*/
void SetDstEndPoint(uint8_t endPoint);
/**
* Get the destination address mode used by the destination entry.
* @return The destination address mode used by the entry.
*/
ApsDstAddressModeBind GetDstAddrMode() const;
/**
* Get the 16-bit address destination of the destination entry.
* @return The 16-bit address of the destination entry.
*/
Mac16Address GetDstAddr16() const;
/**
* Get the 64-bit address destination of the destination entry.
* @return The IEEE address (64-bit address) destination
*/
Mac64Address GetDstAddr64() const;
/**
* Get the destination endpoint of the destination entry.
* @return The destination endpoint.
*/
uint8_t GetDstEndPoint() const;
private:
ApsDstAddressModeBind m_dstAddrMode{
ApsDstAddressModeBind::DST_ADDR64_DST_ENDPOINT_PRESENT}; //!< The destination address mode
//!< used by the entry.
Mac16Address m_dstAddr16; //!< The destination 16-bit address in the destination entry.
Mac64Address
m_dstAddr64; //!< The destination IEEE address (64-bit address) in the destination entry.
uint8_t m_dstEndPoint{0xF0}; //!< The destination endpoint in the destination entry.
};
/**
* APS Binding Table
* See Zigbee specification r22.1.0, Table 2-134
* Similar to the z-boss implementation, the binding table is divided in two portions:
* The source part and the destination part. A single source can have multiple destination
* entries as described by the Zigbee specification. This creates a relationship one to many
* (a source entry with multiple destination entries) which is both useful for 64 bit Address UCST
* or 16-bit groupcast destination bindings.
*/
class BindingTable
{
public:
/**
* The constructor of the binding table.
*/
BindingTable();
/**
* Add an entry to the binding table.
* In essence it binds the source entry portion to the table to one or more
* destination portion entries (one to many).
*
* @param src The source entry portion of the table.
* @param dst The destination entry portion of the table.
* @return The resulting status of the binding attempt.
*/
BindingTableStatus Bind(const SrcBindingEntry& src, const DstBindingEntry& dst);
/**
* Unbinds a destination entry portion of a binding table from a source entry portion.
*
* @param src The source entry portion of the table.
* @param dst The destination entry portion of the table.
* @return The resulting status of the unbinding attempt.
*/
BindingTableStatus Unbind(const SrcBindingEntry& src, const DstBindingEntry& dst);
/**
* Look for destination entries binded to an specific source entry portion in the binding
* table.
*
* @param src The source entry portion of the table to search.
* @param dstEntries The resulting destination entries binded to the provided source entry
* portion
* @return True if at least one destination entry portion was retrieved.
*/
bool LookUpEntries(const SrcBindingEntry& src, std::vector<DstBindingEntry>& dstEntries);
private:
/**
* Compare the equality of 2 source entries
*
* @param first The first source entry to compare.
* @param second The second source entry to compare.
* @return True if the destinations entries are identical
*/
bool CompareSources(const SrcBindingEntry& first, const SrcBindingEntry& second);
/**
* Compare the equality of 2 destination entries
*
* @param first The first destination entry to compare.
* @param second The second destination entry to compare.
* @return True if the destinations entries are identical
*/
bool CompareDestinations(const DstBindingEntry& first, const DstBindingEntry& second);
std::vector<std::pair<SrcBindingEntry,
std::vector<DstBindingEntry>>>
m_bindingTable; //!< The binding table object
uint8_t m_maxSrcEntries; //!< The maximum amount of source entries allowed in the table
uint8_t m_maxDstEntries; //!< The maximum amount of destination entries allowed in the table
};
} // namespace zigbee
} // namespace ns3
#endif /* ZIGBEE_APS_TABLES_H */

View File

@@ -0,0 +1,567 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Authors:
*
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#include "zigbee-aps.h"
#include "ns3/log.h"
#include "ns3/packet.h"
#include "ns3/simulator.h"
namespace ns3
{
namespace zigbee
{
NS_LOG_COMPONENT_DEFINE("ZigbeeAps");
NS_OBJECT_ENSURE_REGISTERED(ZigbeeAps);
TypeId
ZigbeeAps::GetTypeId()
{
static TypeId tid = TypeId("ns3::zigbee::ZigbeeAps")
.SetParent<Object>()
.SetGroupName("Zigbee")
.AddConstructor<ZigbeeAps>();
return tid;
}
ZigbeeAps::ZigbeeAps()
{
NS_LOG_FUNCTION(this);
}
void
ZigbeeAps::NotifyConstructionCompleted()
{
NS_LOG_FUNCTION(this);
}
ZigbeeAps::~ZigbeeAps()
{
NS_LOG_FUNCTION(this);
}
void
ZigbeeAps::DoInitialize()
{
NS_LOG_FUNCTION(this);
Object::DoInitialize();
}
void
ZigbeeAps::DoDispose()
{
m_nwk = nullptr;
// m_apsBindingTable.Dispose();
m_apsdeDataConfirmCallback = MakeNullCallback<void, ApsdeDataConfirmParams>();
Object::DoDispose();
}
void
ZigbeeAps::SetNwk(Ptr<ZigbeeNwk> nwk)
{
m_nwk = nwk;
}
Ptr<ZigbeeNwk>
ZigbeeAps::GetNwk() const
{
return m_nwk;
}
void
ZigbeeAps::ApsdeDataRequest(ApsdeDataRequestParams params, Ptr<Packet> asdu)
{
NS_LOG_FUNCTION(this);
// Fill APSDE-data.confirm parameters in case we need to return an error
ApsdeDataConfirmParams confirmParams;
confirmParams.m_dstAddrMode = params.m_dstAddrMode;
confirmParams.m_dstAddr16 = params.m_dstAddr16;
confirmParams.m_dstAddr64 = params.m_dstAddr64;
confirmParams.m_dstEndPoint = params.m_dstEndPoint;
confirmParams.m_srcEndPoint = params.m_srcEndPoint;
ZigbeeApsTxOptions txOptions(params.m_txOptions);
if (txOptions.IsSecurityEnabled())
{
// TODO: Add support for security options
if (!m_apsdeDataConfirmCallback.IsNull())
{
confirmParams.m_status = ApsStatus::SECURITY_FAIL;
confirmParams.m_txTime = Simulator::Now();
m_apsdeDataConfirmCallback(confirmParams);
}
NS_LOG_WARN("Security is not currently supported");
return;
}
// TODO: Fragmentation
// TODO: Add ACK support
if (txOptions.IsAckRequired())
{
NS_ABORT_MSG("Transmission with ACK not supported");
return;
}
// See See 2.2.4.1.1
switch (params.m_dstAddrMode)
{
case ApsDstAddressMode::DST_ADDR_AND_DST_ENDPOINT_NOT_PRESENT: {
// Use BINDING TABLE to send data to one or more destinations
// (Groupcast or IEEE address destination transmission)
NS_ABORT_MSG("Binded destination groupcast not supported");
// SendDataWithBindingTable(params, asdu);
break;
}
case ApsDstAddressMode::GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT: {
// TODO: Add Groupcast (multicast) support
NS_ABORT_MSG("GROUP ADDRESS (MCST) not supported");
break;
}
case ApsDstAddressMode::DST_ADDR16_DST_ENDPOINT_PRESENT: {
// Regular UCST or BCST transmission with the 16 bit destination address
SendDataUcstBcst(params, asdu);
break;
}
case ApsDstAddressMode::DST_ADDR64_DST_ENDPOINT_PRESENT: {
// TODO: Add Extended address transmission support.
// The NWK do not accept direct extended address transmissions,
// therefore, the APS must translate the extended address
// to a short address using the nwkAddressMap NIB.
if (!m_apsdeDataConfirmCallback.IsNull())
{
confirmParams.m_status = ApsStatus::NO_SHORT_ADDRESS;
confirmParams.m_txTime = Simulator::Now();
m_apsdeDataConfirmCallback(confirmParams);
}
NS_LOG_WARN("Extended address mode not supported");
break;
}
default:
NS_ABORT_MSG("Invalid Option");
break;
}
}
void
ZigbeeAps::SendDataWithBindingTable(ApsdeDataRequestParams params, Ptr<Packet> asdu)
{
NS_LOG_FUNCTION(this);
// Fill APSDE-data.confirm parameters in case we need to return an error
ApsdeDataConfirmParams confirmParams;
confirmParams.m_dstAddrMode = params.m_dstAddrMode;
confirmParams.m_dstAddr16 = params.m_dstAddr16;
confirmParams.m_dstAddr64 = params.m_dstAddr64;
confirmParams.m_dstEndPoint = params.m_dstEndPoint;
confirmParams.m_srcEndPoint = params.m_srcEndPoint;
// APS Header
ZigbeeApsTxOptions txOptions(params.m_txOptions);
ZigbeeApsHeader apsHeader;
apsHeader.SetFrameType(ApsFrameType::APS_DATA);
apsHeader.SetSrcEndpoint(params.m_srcEndPoint);
apsHeader.SetProfileId(params.m_profileId);
apsHeader.SetClusterId(params.m_clusterId);
apsHeader.SetExtHeaderPresent(false);
apsHeader.SetDeliveryMode(ApsDeliveryMode::APS_UCST);
// NLDE-data.request params
NldeDataRequestParams nwkParams;
nwkParams.m_radius = params.m_radius;
nwkParams.m_discoverRoute = DiscoverRouteType::ENABLE_ROUTE_DISCOVERY;
nwkParams.m_securityEnable = txOptions.IsSecurityEnabled();
nwkParams.m_dstAddrMode = AddressMode::UCST_BCST;
SrcBindingEntry srcEntry;
std::vector<DstBindingEntry> dstEntries;
if (m_apsBindingTable.LookUpEntries(srcEntry, dstEntries))
{
for (const auto& dst : dstEntries)
{
if (dst.GetDstAddrMode() == ApsDstAddressModeBind::DST_ADDR64_DST_ENDPOINT_PRESENT)
{
// We must look into the nwkAddressMap to transform the
// 64 bit address destination to a 16 bit destination
NS_LOG_WARN("Bound destination found but 64bit destination not supported");
// TODO: drop trace here
}
else
{
// Send a UCST message to each destination
nwkParams.m_dstAddr = dst.GetDstAddr16();
apsHeader.SetApsCounter(m_apsCounter.GetValue());
m_apsCounter++;
Ptr<Packet> p = asdu->Copy();
p->AddHeader(apsHeader);
Simulator::ScheduleNow(&ZigbeeNwk::NldeDataRequest, m_nwk, nwkParams, p);
}
}
if (!m_apsdeDataConfirmCallback.IsNull())
{
confirmParams.m_status = ApsStatus::SUCCESS;
confirmParams.m_txTime = Simulator::Now();
m_apsdeDataConfirmCallback(confirmParams);
}
}
else
{
if (!m_apsdeDataConfirmCallback.IsNull())
{
confirmParams.m_status = ApsStatus::NO_BOUND_DEVICE;
confirmParams.m_txTime = Simulator::Now();
m_apsdeDataConfirmCallback(confirmParams);
}
}
}
void
ZigbeeAps::SendDataUcstBcst(ApsdeDataRequestParams params, Ptr<Packet> asdu)
{
NS_LOG_FUNCTION(this);
// Fill APSDE-data.confirm parameters in case we need to return an error
ApsdeDataConfirmParams confirmParams;
confirmParams.m_dstAddrMode = params.m_dstAddrMode;
confirmParams.m_dstAddr16 = params.m_dstAddr16;
confirmParams.m_dstAddr64 = params.m_dstAddr64;
confirmParams.m_dstEndPoint = params.m_dstEndPoint;
confirmParams.m_srcEndPoint = params.m_srcEndPoint;
// APS Header
ZigbeeApsTxOptions txOptions(params.m_txOptions);
ZigbeeApsHeader apsHeader;
apsHeader.SetFrameType(ApsFrameType::APS_DATA);
apsHeader.SetSrcEndpoint(params.m_srcEndPoint);
apsHeader.SetProfileId(params.m_profileId);
apsHeader.SetClusterId(params.m_clusterId);
apsHeader.SetExtHeaderPresent(false);
apsHeader.SetDeliveryMode(ApsDeliveryMode::APS_UCST);
// NLDE-data.request params
NldeDataRequestParams nwkParams;
nwkParams.m_radius = params.m_radius;
nwkParams.m_discoverRoute = DiscoverRouteType::ENABLE_ROUTE_DISCOVERY;
nwkParams.m_securityEnable = txOptions.IsSecurityEnabled();
nwkParams.m_dstAddrMode = AddressMode::UCST_BCST;
if (params.m_dstAddr16 == "FF:FF" || params.m_dstAddr16 == "FF:FD" ||
params.m_dstAddr16 == "FF:FC" || params.m_dstAddr16 == "FF:FB")
{
// Destination is a broadcast address
apsHeader.SetDeliveryMode(ApsDeliveryMode::APS_BCST);
}
else
{
// Destination is a unicast address
apsHeader.SetDeliveryMode(ApsDeliveryMode::APS_UCST);
}
if (params.m_useAlias)
{
if (txOptions.IsAckRequired()) // 0b1 = 00000001 = 1 dec
{
if (!m_apsdeDataConfirmCallback.IsNull())
{
confirmParams.m_status = ApsStatus::NOT_SUPPORTED;
confirmParams.m_txTime = Simulator::Now();
m_apsdeDataConfirmCallback(confirmParams);
}
return;
}
nwkParams.m_useAlias = params.m_useAlias;
nwkParams.m_aliasSeqNumber = params.m_aliasSeqNumb;
nwkParams.m_aliasSrcAddr = params.m_aliasSrcAddr;
apsHeader.SetApsCounter(params.m_aliasSeqNumb);
}
else
{
apsHeader.SetApsCounter(m_apsCounter.GetValue());
m_apsCounter++;
}
nwkParams.m_dstAddrMode = AddressMode::UCST_BCST;
nwkParams.m_dstAddr = params.m_dstAddr16;
apsHeader.SetDstEndpoint(params.m_dstEndPoint);
asdu->AddHeader(apsHeader);
Simulator::ScheduleNow(&ZigbeeNwk::NldeDataRequest, m_nwk, nwkParams, asdu);
}
void
ZigbeeAps::ApsmeBindRequest(ApsmeBindRequestParams params)
{
ApsmeBindConfirmParams confirmParams;
confirmParams.m_srcAddr = params.m_srcAddr;
confirmParams.m_srcEndPoint = params.m_srcEndPoint;
confirmParams.m_clusterId = params.m_clusterId;
confirmParams.m_dstAddr16 = params.m_dstAddr16;
confirmParams.m_dstAddr64 = params.m_dstAddr64;
confirmParams.m_dstAddrMode = params.m_dstAddrMode;
confirmParams.m_dstEndPoint = params.m_dstEndPoint;
// TODO: confirm the device has joined the network
// How? APS have no access to join information.
// Verify params are in valid range (2.2.4.3.1)
if ((params.m_srcEndPoint < 0x01 || params.m_srcEndPoint > 0xfe) ||
(params.m_dstEndPoint < 0x01))
{
if (!m_apsmeBindConfirmCallback.IsNull())
{
confirmParams.m_status = ApsStatus::ILLEGAL_REQUEST;
m_apsmeBindConfirmCallback(confirmParams);
}
return;
}
SrcBindingEntry srcEntry(params.m_srcAddr, params.m_srcEndPoint, params.m_clusterId);
DstBindingEntry dstEntry;
if (params.m_dstAddrMode == ApsDstAddressModeBind::GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT)
{
// Group Addressing binding
dstEntry.SetDstAddrMode(ApsDstAddressModeBind::GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT);
dstEntry.SetDstAddr16(params.m_dstAddr16);
}
else
{
// Unicast binding
dstEntry.SetDstAddrMode(ApsDstAddressModeBind::DST_ADDR64_DST_ENDPOINT_PRESENT);
dstEntry.SetDstEndPoint(params.m_dstEndPoint);
}
switch (m_apsBindingTable.Bind(srcEntry, dstEntry))
{
case BindingTableStatus::BOUND:
confirmParams.m_status = ApsStatus::SUCCESS;
break;
case BindingTableStatus::ENTRY_EXISTS:
confirmParams.m_status = ApsStatus::INVALID_BINDING;
break;
case BindingTableStatus::TABLE_FULL:
confirmParams.m_status = ApsStatus::TABLE_FULL;
break;
default:
NS_LOG_ERROR("Invalid binding option");
}
if (!m_apsmeBindConfirmCallback.IsNull())
{
m_apsmeBindConfirmCallback(confirmParams);
}
}
void
ZigbeeAps::ApsmeUnbindRequest(ApsmeBindRequestParams params)
{
}
void
ZigbeeAps::NldeDataConfirm(NldeDataConfirmParams params)
{
}
void
ZigbeeAps::NldeDataIndication(NldeDataIndicationParams params, Ptr<Packet> nsdu)
{
NS_LOG_FUNCTION(this);
ZigbeeApsHeader apsHeader;
nsdu->RemoveHeader(apsHeader);
ApsdeDataIndicationParams indicationParams;
indicationParams.m_status = ApsStatus::SUCCESS;
// TODO:
// See section 2.2.4.1.3.
// - Handle Security
// - Handle grouping(MCST) (Note group table shared by NWK and APS)
// - Handle binding
// - Handle fragmentation
// - Detect Duplicates
// - Handle ACK
// Check if packet is fragmented
if (apsHeader.IsExtHeaderPresent())
{
indicationParams.m_status = ApsStatus::DEFRAG_UNSUPPORTED;
if (!m_apsdeDataIndicationCallback.IsNull())
{
m_apsdeDataIndicationCallback(indicationParams, nsdu);
}
NS_LOG_WARN("Extended Header (Fragmentation) not supported");
return;
}
if (apsHeader.GetFrameType() == ApsFrameType::APS_DATA)
{
if (apsHeader.GetDeliveryMode() == ApsDeliveryMode::APS_UCST ||
apsHeader.GetDeliveryMode() == ApsDeliveryMode::APS_BCST)
{
indicationParams.m_dstAddrMode = ApsDstAddressMode::DST_ADDR16_DST_ENDPOINT_PRESENT;
// Note: Extracting the Address directly from the NWK, creates a dependency on this NWK
// implementation. This is not a very good design, but in practice, it is unavoidable
// due to the descriptions in the specification.
indicationParams.m_dstAddr16 = m_nwk->GetNetworkAddress();
indicationParams.m_dstEndPoint = apsHeader.GetDstEndpoint();
indicationParams.m_srcAddrMode = ApsSrcAddressMode::SRC_ADDR16_SRC_ENDPOINT_PRESENT;
indicationParams.m_srcAddress16 = params.m_srcAddr;
indicationParams.m_srcEndpoint = apsHeader.GetSrcEndpoint();
indicationParams.m_profileId = apsHeader.GetProfileId();
indicationParams.m_clusterId = apsHeader.GetClusterId();
indicationParams.asduLength = nsdu->GetSize();
indicationParams.m_securityStatus = ApsSecurityStatus::UNSECURED;
indicationParams.m_linkQuality = params.m_linkQuality;
indicationParams.m_rxTime = Simulator::Now();
if (!m_apsdeDataIndicationCallback.IsNull())
{
m_apsdeDataIndicationCallback(indicationParams, nsdu);
}
}
else
{
// TODO: Group deliveryMode == (MCST)
NS_LOG_WARN("Group delivery not supported");
}
}
}
void
ZigbeeAps::SetApsdeDataConfirmCallback(ApsdeDataConfirmCallback c)
{
m_apsdeDataConfirmCallback = c;
}
void
ZigbeeAps::SetApsdeDataIndicationCallback(ApsdeDataIndicationCallback c)
{
m_apsdeDataIndicationCallback = c;
}
void
ZigbeeAps::SetApsmeBindConfirmCallback(ApsmeBindConfirmCallback c)
{
m_apsmeBindConfirmCallback = c;
}
void
ZigbeeAps::SetApsmeUnbindConfirmCallback(ApsmeUnbindConfirmCallback c)
{
m_apsmeUnbindConfirmCallback = c;
}
//////////////////////////
// ZigbeeApsTxOptions //
//////////////////////////
ZigbeeApsTxOptions::ZigbeeApsTxOptions(uint8_t value)
: m_txOptions(value)
{
}
void
ZigbeeApsTxOptions::SetSecurityEnabled(bool enable)
{
SetBit(0, enable);
}
void
ZigbeeApsTxOptions::SetUseNwkKey(bool enable)
{
SetBit(1, enable);
}
void
ZigbeeApsTxOptions::SetAckRequired(bool enable)
{
SetBit(2, enable);
}
void
ZigbeeApsTxOptions::SetFragmentationPermitted(bool enable)
{
SetBit(3, enable);
}
void
ZigbeeApsTxOptions::SetIncludeExtendedNonce(bool enable)
{
SetBit(4, enable);
}
bool
ZigbeeApsTxOptions::IsSecurityEnabled() const
{
return GetBit(0);
}
bool
ZigbeeApsTxOptions::IsUseNwkKey() const
{
return GetBit(1);
}
bool
ZigbeeApsTxOptions::IsAckRequired() const
{
return GetBit(2);
}
bool
ZigbeeApsTxOptions::IsFragmentationPermitted() const
{
return GetBit(3);
}
bool
ZigbeeApsTxOptions::IsIncludeExtendedNonce() const
{
return GetBit(4);
}
uint8_t
ZigbeeApsTxOptions::GetTxOptions() const
{
return m_txOptions;
}
void
ZigbeeApsTxOptions::SetBit(int pos, bool value)
{
if (value)
{
m_txOptions |= (1 << pos);
}
else
{
m_txOptions &= ~(1 << pos);
}
}
bool
ZigbeeApsTxOptions::GetBit(int pos) const
{
return (m_txOptions >> pos) & 1;
}
} // namespace zigbee
} // namespace ns3

View File

@@ -0,0 +1,551 @@
/*
* Copyright (c) 2025 Tokushima University, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Authors:
*
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#ifndef ZIGBEE_APS_H
#define ZIGBEE_APS_H
#include "zigbee-aps-header.h"
#include "zigbee-aps-tables.h"
#include "zigbee-nwk.h"
#include "ns3/event-id.h"
#include "ns3/mac16-address.h"
#include "ns3/mac64-address.h"
#include "ns3/object.h"
#include "ns3/random-variable-stream.h"
#include "ns3/sequence-number.h"
#include "ns3/traced-callback.h"
#include "ns3/traced-value.h"
#include <cstdint>
#include <iomanip>
#include <iterator>
namespace ns3
{
namespace zigbee
{
/**
* @ingroup zigbee
*
* APS Destination Address Mode,
* Zigbee Specification r22.1.0
* Table 2-2 APSDE-DATA.request Parameters
* See Table 2-4 APSDE-DATA.indication Parameters
*/
enum class ApsDstAddressMode : std::uint8_t
{
DST_ADDR_AND_DST_ENDPOINT_NOT_PRESENT = 0x00, //!< Destination address and destination endpoint
//!< not present.
GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT = 0x01, //!< Group address or 16-bit destination address
//!< present but destination endpoint not present.
DST_ADDR16_DST_ENDPOINT_PRESENT = 0x02, //!< 16-bit destination address and destination
//!< endpoint present.
DST_ADDR64_DST_ENDPOINT_PRESENT = 0x03, //!< 64-bit destination address and destination
//!< endpoint present.
DST_ADDR64_DST_ENDPOINT_NOT_PRESENT = 0x04 //!< 64-bit address present but destination
//!< endpoint not present.
};
/**
* @ingroup zigbee
*
* APS Source Address Mode,
* Zigbee Specification r22.1.0
* See Table 2-4 APSDE-DATA.indication Parameters
*/
enum class ApsSrcAddressMode : std::uint8_t
{
SRC_ADDR16_SRC_ENDPOINT_PRESENT = 0x02, //!< 16-bit source address and source endpoint present
SRC_ADDR64_SRC_ENDPOINT_PRESENT = 0x03, //!< 64-bit source address and source endpoint present
SRC_ADDR64_SRC_ENDPOINT_NOT_PRESENT = 0x04 //!< 64-bit source address present but source
//!< endpoint not present
};
/**
* @ingroup zigbee
*
* APS Security status
* See Zigbee Specification r22.1.0, Table 2-4
* APSDE-DATA.indication Parameters
*/
enum class ApsSecurityStatus : std::uint8_t
{
UNSECURED = 0x00, //!< Unsecured status
SECURED_NWK_KEY = 0x01, //!< Use NWK secure key
SECURED_LINK_KEY = 0x02 //!< Use link secure key
};
/**
* @ingroup zigbee
*
* APS Sub-layer Status Values
* See Zigbee Specification r22.1.0, Table 2-27
*/
enum class ApsStatus : std::uint8_t
{
SUCCESS = 0x00, //!< A request has been executed successfully.
ASDU_TOO_LONG = 0xa0, //!< A received fragmented
//!< frame could not be defragmented at the current time.
DEFRAG_DEFERRED = 0xa1, //!< Defragmentation deferred.
DEFRAG_UNSUPPORTED = 0xa2, //!< Defragmentation is not supported.
ILLEGAL_REQUEST = 0xa3, //!< Illegal request
INVALID_BINDING = 0xa4, //!< Invalid binding
INVALID_GROUP = 0xa5, //!< Invalid group
INVALID_PARAMETER = 0xa6, //!< A parameter value was invalid or out of range
NO_ACK = 0xa7, //!< No Acknowledgment
NO_BOUND_DEVICE = 0xa8, //!< No bound device
NO_SHORT_ADDRESS = 0xa9, //!< No short address present
NOT_SUPPORTED = 0xaa, //!< Not supported in APS
SECURED_LINK_KEY = 0xab, //!< Secured link key present
SECURED_NWK_KEY = 0xac, //!< Secured network key present
SECURITY_FAIL = 0xad, //!< Security failed
TABLE_FULL = 0xae, //!< Binding table or group table is full
UNSECURED = 0xaf, //!< Unsecured
UNSUPPORTED_ATTRIBUTE = 0xb0 //!< Unsupported attribute
};
/**
* @ingroup zigbee
*
* Zigbee Specification r22.1.0, Section 2.2.4.1.1
* APSDE-DATA.request params.
*/
struct ApsdeDataRequestParams
{
ApsDstAddressMode m_dstAddrMode{
ApsDstAddressMode::DST_ADDR_AND_DST_ENDPOINT_NOT_PRESENT}; //!< Destination address mode.
Mac16Address m_dstAddr16; //!< The destination 16-bit address
Mac64Address m_dstAddr64; //!< The destination 64-bit address
uint8_t m_dstEndPoint{0}; //!< The destination endpoint
uint16_t m_profileId{0}; //!< The application profile ID
uint16_t m_clusterId{0}; //!< The application cluster ID
uint8_t m_srcEndPoint{0}; //!< The source endpoint
uint32_t m_asduLength{0}; //!< The ASDU length
uint8_t m_txOptions{0}; //!< Transmission options
bool m_useAlias{false}; //!< Indicates if alias is used in this transmission
Mac16Address m_aliasSrcAddr; //!< Alias source address
uint8_t m_aliasSeqNumb{0}; //!< Alias sequence number
uint8_t m_radius{0}; //!< Radius (Number of hops this message travels)
};
/**
* @ingroup zigbee
*
* Zigbee Specification r22.1.0, Section 2.2.4.1.2
* APSDE-DATA.confirm params.
*/
struct ApsdeDataConfirmParams
{
ApsDstAddressMode m_dstAddrMode{
ApsDstAddressMode::DST_ADDR_AND_DST_ENDPOINT_NOT_PRESENT}; //!< Destination address mode.
Mac16Address m_dstAddr16; //!< The destination 16-bit address.
Mac64Address m_dstAddr64; //!< The destination IEEE address (64-bit address).
uint8_t m_dstEndPoint{0}; //!< The destination endpoint.
uint8_t m_srcEndPoint{0}; //!< The source endpoint.
ApsStatus m_status{ApsStatus::UNSUPPORTED_ATTRIBUTE}; //!< The confirmation status.
Time m_txTime; //!< The transmission timestamp.
};
/**
* @ingroup zigbee
*
* Zigbee Specification r22.1.0, Section 2.2.4.1.3
* APSDE-DATA.indications params.
*/
struct ApsdeDataIndicationParams
{
ApsDstAddressMode m_dstAddrMode{
ApsDstAddressMode::DST_ADDR_AND_DST_ENDPOINT_NOT_PRESENT}; //!< The destination
//!< address mode
Mac16Address m_dstAddr16; //!< The destination 16-bit address
Mac64Address m_dstAddr64; //!< The destination IEEE address (64-bit address)
uint8_t m_dstEndPoint{0xF0}; //!< The destination endpoint
ApsSrcAddressMode m_srcAddrMode{
ApsSrcAddressMode::SRC_ADDR16_SRC_ENDPOINT_PRESENT}; //!< The
//!< source address mode
Mac16Address m_srcAddress16; //!< The 16-bit address
Mac64Address m_srcAddress64; //!< The IEEE source address (64-bit address)
uint8_t m_srcEndpoint{0xF0}; //!< The application source endpoint
uint16_t m_profileId{0xC0DE}; //!< The application profile ID
uint16_t m_clusterId{0x0000}; //!< The application cluster ID
uint8_t asduLength{0}; //!< The size of the the ASDU packet
ApsStatus m_status{ApsStatus::SUCCESS}; //!< The data indication status
ApsSecurityStatus m_securityStatus{ApsSecurityStatus::UNSECURED}; //!< Security status
uint8_t m_linkQuality{0}; //!< The link quality indication value
Time m_rxTime; //!< The reception timestamp
};
/**
* @ingroup zigbee
*
* Zigbee Specification r22.1.0, Sections 2.2.4.3.1 and 2.2.4.3.3
* APSME-BIND.request and APSME-UNBIND.request params.
*/
struct ApsmeBindRequestParams
{
Mac64Address m_srcAddr; //!< The source IEEE address (64-bit address)
uint8_t m_srcEndPoint{0}; //!< The application source endpoint
uint16_t m_clusterId{0}; //!< The application cluster ID
ApsDstAddressModeBind m_dstAddrMode{
ApsDstAddressModeBind::GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT}; //!< Destination address mode.
Mac16Address m_dstAddr16; //!< The destination 16-bit address
Mac64Address m_dstAddr64; //!< The destination 64-bit address
uint8_t m_dstEndPoint{0xF0}; //!< The application destination endpoint
};
/**
* @ingroup zigbee
*
* Zigbee Specification r22.1.0, Sections 2.2.4.3.2 and 2.2.4.3.4
* APSME-BIND.confirm and APSME-UNBIND.confirm params
*/
struct ApsmeBindConfirmParams
{
ApsStatus m_status{ApsStatus::UNSUPPORTED_ATTRIBUTE}; //!< The status of the bind request
Mac64Address m_srcAddr; //!< The application source address
uint8_t m_srcEndPoint{0}; //!< The application source endpoint
uint16_t m_clusterId{0}; //!< The application cluster ID
ApsDstAddressModeBind m_dstAddrMode{
ApsDstAddressModeBind::GROUP_ADDR_DST_ENDPOINT_NOT_PRESENT}; //!< Destination address mode.
Mac16Address m_dstAddr16; //!< The destination 16-bit address
Mac64Address m_dstAddr64; //!< The destination 64-bit address
uint8_t m_dstEndPoint{0xF0}; //!< The application destination endpoint
};
//////////////////////
// Callbacks //
//////////////////////
/**
* @ingroup zigbee
*
* This callback is called to confirm a successfully transmission of an ASDU.
*/
typedef Callback<void, ApsdeDataConfirmParams> ApsdeDataConfirmCallback;
/**
* @ingroup zigbee
*
* This callback is called after a ASDU has successfully received and
* APS push it to deliver it to the next higher layer (typically the application framework).
*/
typedef Callback<void, ApsdeDataIndicationParams, Ptr<Packet>> ApsdeDataIndicationCallback;
/**
* @ingroup zigbee
*
* This callback is called to confirm a successfully addition of a destination
* into the binding table.
*/
typedef Callback<void, ApsmeBindConfirmParams> ApsmeBindConfirmCallback;
/**
* @ingroup zigbee
*
* This callback is called to confirm a successfully unbind request performed
* into the binding table.
*/
typedef Callback<void, ApsmeBindConfirmParams> ApsmeUnbindConfirmCallback;
/**
* @ingroup zigbee
*
* Zigbee Specification r22.1.0, Section 2.2.3
* Class that implements the Zigbee Specification Application Support Sub-layer (APS).
*/
class ZigbeeAps : public Object
{
public:
/**
* Get the type ID.
*
* @return the object TypeId
*/
static TypeId GetTypeId();
/**
* Default constructor.
*/
ZigbeeAps();
~ZigbeeAps() override;
/**
* Set the underlying NWK to use in this Zigbee APS
*
* @param nwk The pointer to the underlying Zigbee NWK to set to this Zigbee APS
*/
void SetNwk(Ptr<ZigbeeNwk> nwk);
/**
* Get the underlying NWK used by the current Zigbee APS.
*
* @return The pointer to the underlying NWK object currently connected to the Zigbee APS.
*/
Ptr<ZigbeeNwk> GetNwk() const;
/**
* Zigbee Specification r22.1.0, Section 2.2.4.1.1
* APSDE-DATA.request
* Request the transmission of data to one or more entities.
*
* @param params The APSDE data request params
* @param asdu The packet to transmit
*/
void ApsdeDataRequest(ApsdeDataRequestParams params, Ptr<Packet> asdu);
/**
* Zigbee Specification r22.1.0, Section 2.2.4.3.1
* APSME-BIND.request
* Bind a source entry to one or more destination entries in the binding table.
*
* @param params The APSDE bind request params
*/
void ApsmeBindRequest(ApsmeBindRequestParams params);
/**
* Zigbee Specification r22.1.0, Section 2.2.4.3.3
* APSME-BIND.request
* Unbind a destination entry from a source entry in the binding table.
*
* @param params The APSDE bind request params
*/
void ApsmeUnbindRequest(ApsmeBindRequestParams params);
/**
* Zigbee Specification r22.1.0, Section 3.2.1.2
* NLDE-DATA.confirm
* Used to report to the APS the transmission of data from the NWK.
*
* @param params The NLDE data confirm params
*/
void NldeDataConfirm(NldeDataConfirmParams params);
/**
* Zigbee Specification r22.1.0, Section 3.2.1.3
* NLDE-DATA.indication
* Used to report to the APS the reception of data from the NWK.
*
* @param params The NLDE data indication params
* @param nsdu The packet received
*/
void NldeDataIndication(NldeDataIndicationParams params, Ptr<Packet> nsdu);
/**
* Set the callback as part of the interconnections between the APS and
* the next layer or service (typically the application framework). The callback
* implements the callback used in a APSDE-DATA.confirm
*
* @param c the ApsdeDataConfirm callback
*/
void SetApsdeDataConfirmCallback(ApsdeDataConfirmCallback c);
/**
* Set the callback as part of the interconnections between the APS and
* the next layer or service (typically the application framework). The callback
* implements the callback used in a APSDE-DATA.indication
*
* @param c the ApsdeDataIndication callback
*/
void SetApsdeDataIndicationCallback(ApsdeDataIndicationCallback c);
/**
* Set the callback as part of the interconnections between the APS and
* the next layer or service (typically the application framework). The callback
* implements the callback used in a APSME-BIND.confirm
*
* @param c the ApsmeBindConfirm callback
*/
void SetApsmeBindConfirmCallback(ApsmeBindConfirmCallback c);
/**
* Set the callback as part of the interconnections between the APS and
* the next layer or service (typically the application framework). The callback
* implements the callback used in a APSDE-UNBIND.confirm
*
* @param c the ApsdeUnbindConfirm callback
*/
void SetApsmeUnbindConfirmCallback(ApsmeUnbindConfirmCallback c);
protected:
void DoInitialize() override;
void DoDispose() override;
void NotifyConstructionCompleted() override;
private:
/**
* Send a Groupcast or IEEE address destination from a list of destination in
* the binding table.
*
* @param params The APSDE data request params
* @param asdu The packet to transmit
*/
void SendDataWithBindingTable(ApsdeDataRequestParams params, Ptr<Packet> asdu);
/**
* Send a regular UCST or BCST data transmission to a known 16-bit address destination.
*
* @param params The APSDE data request params
* @param asdu The packet to transmit
*/
void SendDataUcstBcst(ApsdeDataRequestParams params, Ptr<Packet> asdu);
Ptr<ZigbeeNwk> m_nwk; //!< Pointer to the underlying NWK connected to this Zigbee APS
SequenceNumber8 m_apsCounter; //!< The sequence number used in packet Tx with APS headers.
BindingTable m_apsBindingTable; //!< The binding table associated to this APS layer
/**
* This callback is used to to notify the results of a data transmission
* request to the Application framework (AF) making the request.
* See Zigbee specification r22.1.0, Section 2.2.4.1.2
*/
ApsdeDataConfirmCallback m_apsdeDataConfirmCallback;
/**
* This callback is used to to notify the reception of data
* to the Application framework (AF).
* See Zigbee specification r22.1.0, Section 2.2.4.1.3
*/
ApsdeDataIndicationCallback m_apsdeDataIndicationCallback;
/**
* This callback is used to to notify the result of a binding
* request in the APS to the Application framework (AF).
* See Zigbee specification r22.1.0, Section 2.2.4.3.2
*/
ApsmeBindConfirmCallback m_apsmeBindConfirmCallback;
/**
* This callback is used to to notify the result of a unbinding
* request in the APS to the Application framework (AF).
* See Zigbee specification r22.1.0, Section 2.2.4.3.4
*/
ApsmeUnbindConfirmCallback m_apsmeUnbindConfirmCallback;
};
/**
* @ingroup zigbee
*
* Helper class used to craft the transmission options bitmap used by the
* APSDE-DATA.request.
*/
class ZigbeeApsTxOptions
{
public:
/**
* The constructor of the Tx options class.
*
* @param value The value to set in the Tx options.
*/
ZigbeeApsTxOptions(uint8_t value = 0);
/**
* Set the security enable bit of the TX options.
*
* @param enable True if security is enabled.
*/
void SetSecurityEnabled(bool enable);
/**
* Set the use network key bit of the TX options.
*
* @param enable True if Network key should be used.
*/
void SetUseNwkKey(bool enable);
/**
* Set the Acknowledgement required bit of the Tx options.
*
* @param enable True if ACK is required.
*/
void SetAckRequired(bool enable);
/**
* Set the fragmentation bit of the Tx options
*
* @param enable True if fragmentation is allowed in the transmission.
*/
void SetFragmentationPermitted(bool enable);
/**
* Set the include extended nonce bit of the Tx options
*
* @param enable True if the frame should include the extended nonce
*/
void SetIncludeExtendedNonce(bool enable);
/**
* Show if the security enable bit of the Tx options is present.
*
* @return True if the bit is active
*/
bool IsSecurityEnabled() const;
/**
* Show if the use network key bit of the Tx options is present.
*
* @return True if the bit is active
*/
bool IsUseNwkKey() const;
/**
* Show if the ACK bit of the Tx options is present.
*
* @return True if the bit is active
*/
bool IsAckRequired() const;
/**
* Show if the fragmentation permitted bit of the Tx options is present.
*
* @return True if the bit is active
*/
bool IsFragmentationPermitted() const;
/**
* Show if the include extended nonce bit of the Tx options is present.
*
* @return True if the bit is active
*/
bool IsIncludeExtendedNonce() const;
/**
* Get the complete bitmap containing the Tx options
*
* @return The Tx options bitmap.
*/
uint8_t GetTxOptions() const;
private:
/**
* Set a bit value into a position in the uint8_t representint the Tx options.
*
* @param pos Position to shift
* @param value Value to set
*/
void SetBit(int pos, bool value);
/**
* Get the value of the bit at the position indicated.
*
* @param pos The position in the uint8_t Tx options
* @return True if the bit value was obtained
*/
bool GetBit(int pos) const;
uint8_t m_txOptions; //!< the bitmap representing the Tx options
};
} // namespace zigbee
} // namespace ns3
#endif /* ZIGBEE_APS_H */

View File

@@ -8,8 +8,8 @@
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#ifndef ZIGBEE_TABLES_H
#define ZIGBEE_TABLES_H
#ifndef ZIGBEE_NWK_TABLES_H
#define ZIGBEE_NWK_TABLES_H
#include "zigbee-nwk-fields.h"
@@ -1324,4 +1324,4 @@ class BroadcastTransactionTable
} // namespace zigbee
} // namespace ns3
#endif /* ZIGBEE_TABLES_H */
#endif /* ZIGBEE_NWK_TABLES_H */

View File

@@ -297,7 +297,7 @@ struct NldeDataConfirmParams
};
/**
* @ingroup lr-wpan
* @ingroup zigbee
*
* NLDE-DATA.indication params. See Zigbee Specification 3.2.1.3.1
*/

View File

@@ -40,8 +40,9 @@ ZigbeeStack::ZigbeeStack()
NS_LOG_FUNCTION(this);
m_nwk = CreateObject<zigbee::ZigbeeNwk>();
// TODO: Create APS layer here.
// m_aps = CreateObject<zigbee::ZigbeeAps> ();
m_aps = CreateObject<zigbee::ZigbeeAps>();
m_nwkOnly = false;
}
ZigbeeStack::~ZigbeeStack()
@@ -56,6 +57,7 @@ ZigbeeStack::DoDispose()
m_netDevice = nullptr;
m_node = nullptr;
m_aps = nullptr;
m_nwk = nullptr;
m_mac = nullptr;
Object::DoDispose();
@@ -66,18 +68,21 @@ ZigbeeStack::DoInitialize()
{
NS_LOG_FUNCTION(this);
AggregateObject(m_nwk);
// AggregateObject(m_aps);
NS_ABORT_MSG_UNLESS(m_netDevice,
"Invalid NetDevice found when attempting to install ZigbeeStack");
// Make sure the NetDevice is previously initialized
// before using ZigbeeStack
// before using ZigbeeStack (PHY and MAC are initialized)
m_netDevice->Initialize();
m_mac = m_netDevice->GetObject<lrwpan::LrWpanMacBase>();
NS_ABORT_MSG_UNLESS(m_mac,
"No valid LrWpanMacBase found in this NetDevice, cannot use ZigbeeStack");
"Invalid LrWpanMacBase found in this NetDevice, cannot use ZigbeeStack");
m_nwk->Initialize();
AggregateObject(m_nwk);
// Set NWK callback hooks with the MAC
m_nwk->SetMac(m_mac);
@@ -97,14 +102,20 @@ ZigbeeStack::DoInitialize()
m_mac->SetMlmeAssociateConfirmCallback(MakeCallback(&ZigbeeNwk::MlmeAssociateConfirm, m_nwk));
// TODO: complete other callback hooks with the MAC
if (!m_nwkOnly)
{
// Set APS callback hooks with NWK (i.e., NLDE primitives only)
m_nwk->SetNldeDataConfirmCallback(MakeCallback(&ZigbeeAps::NldeDataConfirm, m_aps));
m_nwk->SetNldeDataIndicationCallback(MakeCallback(&ZigbeeAps::NldeDataIndication, m_aps));
m_aps->Initialize();
m_aps->SetNwk(m_nwk);
AggregateObject(m_aps);
}
// Obtain Extended address as soon as NWK is set to begin operations
m_mac->MlmeGetRequest(MacPibAttributeIdentifier::macExtendedAddress);
// TODO: Set APS callback hooks with NWK when support for APS layer is added.
// For example:
// m_aps->SetNwk (m_nwk);
// m_nwk->SetNldeDataIndicationCallback (MakeCallback (&ZigbeeAps::NldeDataIndication, m_aps));
Object::DoInitialize();
}
@@ -134,6 +145,12 @@ ZigbeeStack::SetNetDevice(Ptr<NetDevice> netDevice)
m_node = m_netDevice->GetNode();
}
void
ZigbeeStack::SetOnlyNwkLayer()
{
m_nwkOnly = true;
}
Ptr<zigbee::ZigbeeNwk>
ZigbeeStack::GetNwk() const
{
@@ -148,5 +165,19 @@ ZigbeeStack::SetNwk(Ptr<zigbee::ZigbeeNwk> nwk)
m_nwk = nwk;
}
Ptr<zigbee::ZigbeeAps>
ZigbeeStack::GetAps() const
{
return m_aps;
}
void
ZigbeeStack::SetAps(Ptr<zigbee::ZigbeeAps> aps)
{
NS_LOG_FUNCTION(this);
NS_ABORT_MSG_IF(ZigbeeStack::IsInitialized(), "APS layer cannot be set after initialization");
m_aps = aps;
}
} // namespace zigbee
} // namespace ns3

View File

@@ -10,6 +10,7 @@
#ifndef ZIGBEE_STACK_H
#define ZIGBEE_STACK_H
#include "zigbee-aps.h"
#include "zigbee-nwk.h"
#include "ns3/lr-wpan-mac-base.h"
@@ -28,6 +29,7 @@ namespace zigbee
{
class ZigbeeNwk;
class ZigbeeAps;
/**
* @ingroup zigbee
@@ -84,6 +86,20 @@ class ZigbeeStack : public Object
*/
void SetNwk(Ptr<ZigbeeNwk> nwk);
/**
* Get the APS layer used by this ZigbeeStack.
*
* @return the APS object
*/
Ptr<ZigbeeAps> GetAps() const;
/**
* Set the APS layer used by this ZigbeeStack.
*
* @param aps The APS layer object
*/
void SetAps(Ptr<ZigbeeAps> aps);
/**
* Returns a smart pointer to the underlying NetDevice.
*
@@ -100,6 +116,11 @@ class ZigbeeStack : public Object
*/
void SetNetDevice(Ptr<NetDevice> netDevice);
/**
* Inticates to the Zigbee stack that only the NWK layer should be present.
*/
void SetOnlyNwkLayer();
protected:
/**
* Dispose of the Objects used by the ZigbeeStack
@@ -114,8 +135,10 @@ class ZigbeeStack : public Object
private:
Ptr<lrwpan::LrWpanMacBase> m_mac; //!< The underlying LrWpan MAC connected to this Zigbee Stack.
Ptr<ZigbeeNwk> m_nwk; //!< The Zigbee Network layer.
Ptr<ZigbeeAps> m_aps; //!< The Zigbee Application Support Sub-layer
Ptr<Node> m_node; //!< The node associated with this NetDevice.
Ptr<NetDevice> m_netDevice; //!< Smart pointer to the underlying NetDevice.
bool m_nwkOnly; //!< Indicates that only the NWK layer is present in the Zigbee stack
};
} // namespace zigbee

View File

@@ -0,0 +1,330 @@
/*
* Copyright (c) 2025 Tokushima University, Tokushima, Japan
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author:
* Alberto Gallegos Ramonet <alramonet@is.tokushima-u.ac.jp>
*/
#include "ns3/constant-position-mobility-model.h"
#include "ns3/core-module.h"
#include "ns3/log.h"
#include "ns3/lr-wpan-module.h"
#include "ns3/packet.h"
#include "ns3/propagation-delay-model.h"
#include "ns3/propagation-loss-model.h"
#include "ns3/rng-seed-manager.h"
#include "ns3/simulator.h"
#include "ns3/single-model-spectrum-channel.h"
#include "ns3/zigbee-module.h"
#include <iomanip>
#include <iostream>
using namespace ns3;
using namespace ns3::lrwpan;
using namespace ns3::zigbee;
NS_LOG_COMPONENT_DEFINE("zigbee-aps-data-test");
/**
* @ingroup zigbee-test
* @ingroup tests
*
* Zigbee RREQ transmission retries test case
*/
class ZigbeeApsDataTestCase : public TestCase
{
public:
ZigbeeApsDataTestCase();
~ZigbeeApsDataTestCase() override;
private:
/**
* Callback for APSDE-DATA.indication
* This callback is called when a data packet is received by the APS layer.
*
* @param testcase The ZigbeeApsDataTestCase instance
* @param stack The Zigbee stack that received the data
* @param params The parameters of the APSDE-DATA.indication
* @param asdu The received packet
*/
static void ApsDataIndication(ZigbeeApsDataTestCase* testcase,
Ptr<ZigbeeStack> stack,
ApsdeDataIndicationParams params,
Ptr<Packet> asdu);
/**
* Callback for NLME-NETWORK-DISCOVERY.confirm
* This callback is called when a network discovery has been performed.
*
* @param testcase The ZigbeeApsDataTestCase instance
* @param stack The Zigbee stack that received the confirmation
* @param params The parameters of the NLME-NETWORK-DISCOVERY.confirm
*/
static void NwkNetworkDiscoveryConfirm(ZigbeeApsDataTestCase* testcase,
Ptr<ZigbeeStack> stack,
NlmeNetworkDiscoveryConfirmParams params);
/**
* Send data to a unicast destination.
* This function sends a data packet from stackSrc to stackDst.
*
* @param stackSrc The source Zigbee stack
* @param stackDst The destination Zigbee stack
*/
static void SendDataUcstDst(Ptr<ZigbeeStack> stackSrc, Ptr<ZigbeeStack> stackDst);
void DoRun() override;
uint16_t m_dstEndpoint; //!< The destination endpoint
};
ZigbeeApsDataTestCase::ZigbeeApsDataTestCase()
: TestCase("Zigbee: APS layer data test")
{
m_dstEndpoint = 0;
}
ZigbeeApsDataTestCase::~ZigbeeApsDataTestCase()
{
}
void
ZigbeeApsDataTestCase::ApsDataIndication(ZigbeeApsDataTestCase* testcase,
Ptr<ZigbeeStack> stack,
ApsdeDataIndicationParams params,
Ptr<Packet> asdu)
{
testcase->m_dstEndpoint = params.m_dstEndPoint;
}
void
ZigbeeApsDataTestCase::NwkNetworkDiscoveryConfirm(ZigbeeApsDataTestCase* testcase,
Ptr<ZigbeeStack> stack,
NlmeNetworkDiscoveryConfirmParams params)
{
if (params.m_status == NwkStatus::SUCCESS)
{
NlmeJoinRequestParams joinParams;
zigbee::CapabilityInformation capaInfo;
capaInfo.SetDeviceType(MacDeviceType::ENDDEVICE);
capaInfo.SetAllocateAddrOn(true);
joinParams.m_rejoinNetwork = JoiningMethod::ASSOCIATION;
joinParams.m_capabilityInfo = capaInfo.GetCapability();
joinParams.m_extendedPanId = params.m_netDescList[0].m_extPanId;
Simulator::ScheduleNow(&ZigbeeNwk::NlmeJoinRequest, stack->GetNwk(), joinParams);
}
else
{
NS_ABORT_MSG("Unable to discover networks | status: " << params.m_status);
}
}
void
ZigbeeApsDataTestCase::SendDataUcstDst(Ptr<ZigbeeStack> stackSrc, Ptr<ZigbeeStack> stackDst)
{
// UCST transmission to a single 16-bit address destination
// Data is transmitted from device stackSrc to device stackDst.
Ptr<Packet> p = Create<Packet>(5);
// Because we currently do not have ZDO or ZCL or AF, clusterId
// and profileId numbers are non-sensical.
ApsdeDataRequestParams dataReqParams;
// creates a BitMap with transmission options
// Default, use 16 bit address destination (No option), equivalent to bitmap 0x00
ZigbeeApsTxOptions txOptions;
dataReqParams.m_txOptions = txOptions.GetTxOptions();
dataReqParams.m_useAlias = false;
dataReqParams.m_srcEndPoint = 3;
dataReqParams.m_clusterId = 5; // Arbitrary value
dataReqParams.m_profileId = 2; // Arbitrary value
dataReqParams.m_dstAddrMode = ApsDstAddressMode::DST_ADDR16_DST_ENDPOINT_PRESENT;
dataReqParams.m_dstAddr16 = stackDst->GetNwk()->GetNetworkAddress();
dataReqParams.m_dstEndPoint = 4;
Simulator::ScheduleNow(&ZigbeeAps::ApsdeDataRequest, stackSrc->GetAps(), dataReqParams, p);
}
void
ZigbeeApsDataTestCase::DoRun()
{
// Transmit data using the APS layer.
// Zigbee Coordinator --------------> Zigbee EndDevice(destination endpoint:4)
// This test transmit a single packet to an enddevice with endpoint 4.
// The data transmission is done using the mode DST_ADDR16_DST_ENDPOINT_PRESENT (Mode 0x02).
// No fragmentations or Acknowledge is used
// Verification that the devices have join the network is performed
// (i.e., All devices have valid network addresses).
RngSeedManager::SetSeed(3);
RngSeedManager::SetRun(4);
NodeContainer nodes;
nodes.Create(3);
//// Add the PHY and MAC, configure the channel
LrWpanHelper lrWpanHelper;
NetDeviceContainer lrwpanDevices = lrWpanHelper.Install(nodes);
Ptr<LrWpanNetDevice> dev0 = lrwpanDevices.Get(0)->GetObject<LrWpanNetDevice>();
Ptr<LrWpanNetDevice> dev1 = lrwpanDevices.Get(1)->GetObject<LrWpanNetDevice>();
Ptr<LrWpanNetDevice> dev2 = lrwpanDevices.Get(2)->GetObject<LrWpanNetDevice>();
dev0->GetMac()->SetExtendedAddress("00:00:00:00:00:00:CA:FE");
dev1->GetMac()->SetExtendedAddress("00:00:00:00:00:00:00:01");
dev2->GetMac()->SetExtendedAddress("00:00:00:00:00:00:00:02");
Ptr<SingleModelSpectrumChannel> channel = CreateObject<SingleModelSpectrumChannel>();
Ptr<LogDistancePropagationLossModel> propModel =
CreateObject<LogDistancePropagationLossModel>();
Ptr<ConstantSpeedPropagationDelayModel> delayModel =
CreateObject<ConstantSpeedPropagationDelayModel>();
channel->AddPropagationLossModel(propModel);
channel->SetPropagationDelayModel(delayModel);
dev0->SetChannel(channel);
dev1->SetChannel(channel);
dev2->SetChannel(channel);
// Add Zigbee stack with NWK and APS
ZigbeeHelper zigbeeHelper;
ZigbeeStackContainer zigbeeStackContainer = zigbeeHelper.Install(lrwpanDevices);
Ptr<ZigbeeStack> zstack0 = zigbeeStackContainer.Get(0)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack1 = zigbeeStackContainer.Get(1)->GetObject<ZigbeeStack>();
Ptr<ZigbeeStack> zstack2 = zigbeeStackContainer.Get(2)->GetObject<ZigbeeStack>();
// reprodusable results from random events occurring inside the stack.
zstack0->GetNwk()->AssignStreams(0);
zstack1->GetNwk()->AssignStreams(10);
zstack2->GetNwk()->AssignStreams(20);
//// Configure Nodes Mobility
Ptr<ConstantPositionMobilityModel> dev0Mobility = CreateObject<ConstantPositionMobilityModel>();
dev0Mobility->SetPosition(Vector(0, 0, 0));
dev0->GetPhy()->SetMobility(dev0Mobility);
Ptr<ConstantPositionMobilityModel> dev1Mobility = CreateObject<ConstantPositionMobilityModel>();
dev1Mobility->SetPosition(Vector(50, 0, 0));
dev1->GetPhy()->SetMobility(dev1Mobility);
Ptr<ConstantPositionMobilityModel> dev2Mobility = CreateObject<ConstantPositionMobilityModel>();
dev2Mobility->SetPosition(Vector(0, 50, 0));
dev2->GetPhy()->SetMobility(dev2Mobility);
// Configure APS hooks
zstack1->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, this, zstack1));
zstack2->GetAps()->SetApsdeDataIndicationCallback(
MakeBoundCallback(&ApsDataIndication, this, zstack2));
// Configure NWK hooks
// We do not have ZDO, we are required to use the NWK
// directly to perform association.
zstack1->GetNwk()->SetNlmeNetworkDiscoveryConfirmCallback(
MakeBoundCallback(&NwkNetworkDiscoveryConfirm, this, zstack1));
zstack2->GetNwk()->SetNlmeNetworkDiscoveryConfirmCallback(
MakeBoundCallback(&NwkNetworkDiscoveryConfirm, this, zstack2));
// Configure NWK hooks (for managing Network Joining)
// 1 - Initiate the Zigbee coordinator on a channel
NlmeNetworkFormationRequestParams netFormParams;
netFormParams.m_scanChannelList.channelPageCount = 1;
netFormParams.m_scanChannelList.channelsField[0] = 0x00001800; // BitMap: channel 11 and 12
netFormParams.m_scanDuration = 0;
netFormParams.m_superFrameOrder = 15;
netFormParams.m_beaconOrder = 15;
Simulator::ScheduleWithContext(zstack0->GetNode()->GetId(),
Seconds(1),
&ZigbeeNwk::NlmeNetworkFormationRequest,
zstack0->GetNwk(),
netFormParams);
NlmeNetworkDiscoveryRequestParams netDiscParams;
netDiscParams.m_scanChannelList.channelPageCount = 1;
netDiscParams.m_scanChannelList.channelsField[0] = 0x00000800; // BitMap: Channels 11
netDiscParams.m_scanDuration = 2;
Simulator::ScheduleWithContext(zstack1->GetNode()->GetId(),
Seconds(2),
&ZigbeeNwk::NlmeNetworkDiscoveryRequest,
zstack1->GetNwk(),
netDiscParams);
NlmeNetworkDiscoveryRequestParams netDiscParams2;
netDiscParams.m_scanChannelList.channelPageCount = 1;
netDiscParams.m_scanChannelList.channelsField[0] = 0x00000800; // BitMap: Channels 11~14
netDiscParams.m_scanDuration = 2;
Simulator::ScheduleWithContext(zstack2->GetNode()->GetId(),
Seconds(3),
&ZigbeeNwk::NlmeNetworkDiscoveryRequest,
zstack2->GetNwk(),
netDiscParams2);
// Send data to a single UCST destination (16-bit address)
// The destination address is unknown until compilation, we extract
// it from the stack directly.
Simulator::Schedule(Seconds(4), &SendDataUcstDst, zstack0, zstack2);
Simulator::Run();
// Check that devices actually joined the network and have different 16-bit addresses.
NS_TEST_EXPECT_MSG_NE(zstack1->GetNwk()->GetNetworkAddress(),
Mac16Address("FF:FF"),
"The dev 1 was unable to join the network");
NS_TEST_EXPECT_MSG_NE(zstack2->GetNwk()->GetNetworkAddress(),
Mac16Address("FF:FF"),
"The dev 1 was unable to join the network");
NS_TEST_EXPECT_MSG_NE(zstack0->GetNwk()->GetNetworkAddress(),
zstack1->GetNwk()->GetNetworkAddress(),
"Error, devices 0 and 1 have the same 16 bit MAC address");
NS_TEST_EXPECT_MSG_NE(zstack1->GetNwk()->GetNetworkAddress(),
zstack2->GetNwk()->GetNetworkAddress(),
"Error, devices 1 and 2 have the same 16 bit MAC address");
// Check that the packet was received to the correct preconfigured destination endpoint.
NS_TEST_EXPECT_MSG_EQ(m_dstEndpoint,
4,
"Packet was not received in the correct destination endpoint");
Simulator::Destroy();
}
/**
* @ingroup zigbee-test
* @ingroup tests
*
* Zigbee APS Data TestSuite
*/
class ZigbeeApsDataTestSuite : public TestSuite
{
public:
ZigbeeApsDataTestSuite();
};
ZigbeeApsDataTestSuite::ZigbeeApsDataTestSuite()
: TestSuite("zigbee-aps-data-test", Type::UNIT)
{
AddTestCase(new ZigbeeApsDataTestCase, TestCase::Duration::QUICK);
}
static ZigbeeApsDataTestSuite zigbeeApsDataTestSuite; //!< Static variable for test initialization

View File

@@ -238,6 +238,7 @@ ZigbeeRreqRetryTestCase::DoRun()
//// Configure NWK
ZigbeeHelper zigbee;
zigbee.SetNwkLayerOnly();
ZigbeeStackContainer zigbeeStackContainer = zigbee.Install(lrwpanDevices);
Ptr<ZigbeeStack> zstack0 = zigbeeStackContainer.Get(0)->GetObject<ZigbeeStack>();