diff --git a/src/zigbee/CMakeLists.txt b/src/zigbee/CMakeLists.txt index 11069d6e1..860966ff1 100644 --- a/src/zigbee/CMakeLists.txt +++ b/src/zigbee/CMakeLists.txt @@ -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 ) diff --git a/src/zigbee/doc/figures/zigbeeStackArch.dia b/src/zigbee/doc/figures/zigbeeStackArch.dia index 9f5be4337..f46123817 100644 Binary files a/src/zigbee/doc/figures/zigbeeStackArch.dia and b/src/zigbee/doc/figures/zigbeeStackArch.dia differ diff --git a/src/zigbee/doc/zigbee.rst b/src/zigbee/doc/zigbee.rst index 67e3b834c..12b95c1ce 100644 --- a/src/zigbee/doc/zigbee.rst +++ b/src/zigbee/doc/zigbee.rst @@ -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 `_ @@ -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 layer’s 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 ---------- diff --git a/src/zigbee/examples/CMakeLists.txt b/src/zigbee/examples/CMakeLists.txt index fc0739618..1fa1abd32 100644 --- a/src/zigbee/examples/CMakeLists.txt +++ b/src/zigbee/examples/CMakeLists.txt @@ -3,6 +3,7 @@ set(base_examples zigbee-nwk-association-join zigbee-nwk-routing zigbee-nwk-routing-grid + zigbee-aps-data ) foreach( diff --git a/src/zigbee/examples/zigbee-aps-data.cc b/src/zigbee/examples/zigbee-aps-data.cc new file mode 100644 index 000000000..b6703b128 --- /dev/null +++ b/src/zigbee/examples/zigbee-aps-data.cc @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: + * + * Alberto Gallegos Ramonet + */ + +/** + * 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 + +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 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 stack, ApsdeDataIndicationParams params, Ptr 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 stack, NlmeNetworkFormationConfirmParams params) +{ + std::cout << "NlmeNetworkFormationConfirmStatus = " << params.m_status << "\n"; +} + +static void +NwkNetworkDiscoveryConfirm(Ptr 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(netDescriptor.m_logCh) + << "\n" + << std::hex << " Pan ID: 0x" << netDescriptor.m_panId << "\n" + << " Stack profile: " << std::dec + << static_cast(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 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 stack, NlmeRouteDiscoveryConfirmParams params) +{ + std::cout << "NlmeRouteDiscoveryConfirmStatus = " << params.m_status << "\n"; +} + +static void +SendData(Ptr stackSrc, Ptr 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 p = Create(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 stream = Create(&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 dev0 = lrwpanDevices.Get(0)->GetObject(); + Ptr dev1 = lrwpanDevices.Get(1)->GetObject(); + Ptr dev2 = lrwpanDevices.Get(2)->GetObject(); + Ptr dev3 = lrwpanDevices.Get(3)->GetObject(); + Ptr dev4 = lrwpanDevices.Get(4)->GetObject(); + + // 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 channel = CreateObject(); + Ptr propModel = + CreateObject(); + + Ptr delayModel = + CreateObject(); + + 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 zstack0 = zigbeeStackContainer.Get(0)->GetObject(); + Ptr zstack1 = zigbeeStackContainer.Get(1)->GetObject(); + Ptr zstack2 = zigbeeStackContainer.Get(2)->GetObject(); + Ptr zstack3 = zigbeeStackContainer.Get(3)->GetObject(); + Ptr zstack4 = zigbeeStackContainer.Get(4)->GetObject(); + + // 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 dev0Mobility = CreateObject(); + dev0Mobility->SetPosition(Vector(0, 0, 0)); + dev0->GetPhy()->SetMobility(dev0Mobility); + + Ptr dev1Mobility = CreateObject(); + dev1Mobility->SetPosition(Vector(90, 0, 0)); + dev1->GetPhy()->SetMobility(dev1Mobility); + + Ptr dev2Mobility = CreateObject(); + dev2Mobility->SetPosition(Vector(170, 0, 0)); + dev2->GetPhy()->SetMobility(dev2Mobility); + + Ptr dev3Mobility = CreateObject(); + dev3Mobility->SetPosition(Vector(250, 0, 0)); + dev3->GetPhy()->SetMobility(dev3Mobility); + + Ptr dev4Mobility = CreateObject(); + 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; +} diff --git a/src/zigbee/examples/zigbee-nwk-association-join.cc b/src/zigbee/examples/zigbee-nwk-association-join.cc index 5b4a36081..7b57bf368 100644 --- a/src/zigbee/examples/zigbee-nwk-association-join.cc +++ b/src/zigbee/examples/zigbee-nwk-association-join.cc @@ -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 zstack0 = zigbeeStackContainer.Get(0)->GetObject(); Ptr zstack1 = zigbeeStackContainer.Get(1)->GetObject(); diff --git a/src/zigbee/examples/zigbee-nwk-direct-join.cc b/src/zigbee/examples/zigbee-nwk-direct-join.cc index 4b7d95e06..cb96bf4ec 100644 --- a/src/zigbee/examples/zigbee-nwk-direct-join.cc +++ b/src/zigbee/examples/zigbee-nwk-direct-join.cc @@ -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 zstack0 = zigbeeStackContainer.Get(0)->GetObject(); Ptr zstack1 = zigbeeStackContainer.Get(1)->GetObject(); diff --git a/src/zigbee/examples/zigbee-nwk-routing-grid.cc b/src/zigbee/examples/zigbee-nwk-routing-grid.cc index a1bce12d5..3caf23505 100644 --- a/src/zigbee/examples/zigbee-nwk-routing-grid.cc +++ b/src/zigbee/examples/zigbee-nwk-routing-grid.cc @@ -279,6 +279,7 @@ main(int argc, char* argv[]) lrWpanHelper.SetExtendedAddresses(lrwpanDevices); ZigbeeHelper zigbeeHelper; + zigbeeHelper.SetNwkLayerOnly(); zigbeeStacks = zigbeeHelper.Install(lrwpanDevices); // NWK callbacks hooks diff --git a/src/zigbee/examples/zigbee-nwk-routing.cc b/src/zigbee/examples/zigbee-nwk-routing.cc index 3bccc8c5b..109bf12c3 100644 --- a/src/zigbee/examples/zigbee-nwk-routing.cc +++ b/src/zigbee/examples/zigbee-nwk-routing.cc @@ -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 zstack0 = zigbeeStackContainer.Get(0)->GetObject(); Ptr zstack1 = zigbeeStackContainer.Get(1)->GetObject(); diff --git a/src/zigbee/helper/zigbee-helper.cc b/src/zigbee/helper/zigbee-helper.cc index ad06770de..539e0d47f 100644 --- a/src/zigbee/helper/zigbee-helper.cc +++ b/src/zigbee/helper/zigbee-helper.cc @@ -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 zigbeeStack = m_stackFactory.Create(); + + 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 diff --git a/src/zigbee/helper/zigbee-helper.h b/src/zigbee/helper/zigbee-helper.h index 33d9d42fe..26323e6d4 100644 --- a/src/zigbee/helper/zigbee-helper.h +++ b/src/zigbee/helper/zigbee-helper.h @@ -15,6 +15,7 @@ #include "ns3/net-device-container.h" #include "ns3/object-factory.h" +#include #include 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 diff --git a/src/zigbee/model/zigbee-aps-header.cc b/src/zigbee/model/zigbee-aps-header.cc new file mode 100644 index 000000000..562af9aba --- /dev/null +++ b/src/zigbee/model/zigbee-aps-header.cc @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: + * Alberto Gallegos Ramonet + */ + +#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(frameControl & 0x03); + m_deliveryMode = static_cast((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(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
() + .SetGroupName("Zigbee") + .AddConstructor(); + 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( + (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(m_dstEndpoint) + << " | Src EP = " << static_cast(m_srcEndpoint) + << " | Cluster ID = " << m_clusterId + << " | Profile ID = " << m_profileId + << " | Counter = " << static_cast(m_counter); + os << "\n";*/ +} + +} // namespace zigbee +} // namespace ns3 diff --git a/src/zigbee/model/zigbee-aps-header.h b/src/zigbee/model/zigbee-aps-header.h new file mode 100644 index 000000000..b06ba0b42 --- /dev/null +++ b/src/zigbee/model/zigbee-aps-header.h @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: + * Alberto Gallegos Ramonet + */ + +#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 diff --git a/src/zigbee/model/zigbee-aps-tables.cc b/src/zigbee/model/zigbee-aps-tables.cc new file mode 100644 index 000000000..f7dd65247 --- /dev/null +++ b/src/zigbee/model/zigbee-aps-tables.cc @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: + * Alberto Gallegos Ramonet + */ + +#include "zigbee-aps-tables.h" + +#include "ns3/log.h" +#include "ns3/pointer.h" +#include "ns3/simulator.h" + +#include +#include + +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{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& 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 diff --git a/src/zigbee/model/zigbee-aps-tables.h b/src/zigbee/model/zigbee-aps-tables.h new file mode 100644 index 000000000..b14371031 --- /dev/null +++ b/src/zigbee/model/zigbee-aps-tables.h @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: + * Alberto Gallegos Ramonet + */ + +#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 +#include +#include +#include + +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 +{ + 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 +{ + 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& 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>> + 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 */ diff --git a/src/zigbee/model/zigbee-aps.cc b/src/zigbee/model/zigbee-aps.cc new file mode 100644 index 000000000..210e6a29d --- /dev/null +++ b/src/zigbee/model/zigbee-aps.cc @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: + * + * Alberto Gallegos Ramonet + */ + +#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() + .SetGroupName("Zigbee") + .AddConstructor(); + 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(); + + Object::DoDispose(); +} + +void +ZigbeeAps::SetNwk(Ptr nwk) +{ + m_nwk = nwk; +} + +Ptr +ZigbeeAps::GetNwk() const +{ + return m_nwk; +} + +void +ZigbeeAps::ApsdeDataRequest(ApsdeDataRequestParams params, Ptr 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 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 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 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 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 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 diff --git a/src/zigbee/model/zigbee-aps.h b/src/zigbee/model/zigbee-aps.h new file mode 100644 index 000000000..059645cd2 --- /dev/null +++ b/src/zigbee/model/zigbee-aps.h @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2025 Tokushima University, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: + * + * Alberto Gallegos Ramonet + */ + +#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 +#include +#include + +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 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> ApsdeDataIndicationCallback; + +/** + * @ingroup zigbee + * + * This callback is called to confirm a successfully addition of a destination + * into the binding table. + */ +typedef Callback ApsmeBindConfirmCallback; + +/** + * @ingroup zigbee + * + * This callback is called to confirm a successfully unbind request performed + * into the binding table. + */ +typedef Callback 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 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 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 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 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 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 asdu); + + Ptr 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 */ diff --git a/src/zigbee/model/zigbee-nwk-tables.h b/src/zigbee/model/zigbee-nwk-tables.h index 519ab8aca..e4f21e0da 100644 --- a/src/zigbee/model/zigbee-nwk-tables.h +++ b/src/zigbee/model/zigbee-nwk-tables.h @@ -8,8 +8,8 @@ * Alberto Gallegos Ramonet */ -#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 */ diff --git a/src/zigbee/model/zigbee-nwk.h b/src/zigbee/model/zigbee-nwk.h index 6f8d74357..7f1ef78c0 100644 --- a/src/zigbee/model/zigbee-nwk.h +++ b/src/zigbee/model/zigbee-nwk.h @@ -297,7 +297,7 @@ struct NldeDataConfirmParams }; /** - * @ingroup lr-wpan + * @ingroup zigbee * * NLDE-DATA.indication params. See Zigbee Specification 3.2.1.3.1 */ diff --git a/src/zigbee/model/zigbee-stack.cc b/src/zigbee/model/zigbee-stack.cc index ba15a0856..cda11bcda 100644 --- a/src/zigbee/model/zigbee-stack.cc +++ b/src/zigbee/model/zigbee-stack.cc @@ -40,8 +40,9 @@ ZigbeeStack::ZigbeeStack() NS_LOG_FUNCTION(this); m_nwk = CreateObject(); - // TODO: Create APS layer here. - // m_aps = CreateObject (); + m_aps = CreateObject(); + + 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(); 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) m_node = m_netDevice->GetNode(); } +void +ZigbeeStack::SetOnlyNwkLayer() +{ + m_nwkOnly = true; +} + Ptr ZigbeeStack::GetNwk() const { @@ -148,5 +165,19 @@ ZigbeeStack::SetNwk(Ptr nwk) m_nwk = nwk; } +Ptr +ZigbeeStack::GetAps() const +{ + return m_aps; +} + +void +ZigbeeStack::SetAps(Ptr 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 diff --git a/src/zigbee/model/zigbee-stack.h b/src/zigbee/model/zigbee-stack.h index ce9f39e84..19013f8af 100644 --- a/src/zigbee/model/zigbee-stack.h +++ b/src/zigbee/model/zigbee-stack.h @@ -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 nwk); + /** + * Get the APS layer used by this ZigbeeStack. + * + * @return the APS object + */ + Ptr GetAps() const; + + /** + * Set the APS layer used by this ZigbeeStack. + * + * @param aps The APS layer object + */ + void SetAps(Ptr aps); + /** * Returns a smart pointer to the underlying NetDevice. * @@ -100,6 +116,11 @@ class ZigbeeStack : public Object */ void SetNetDevice(Ptr 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 m_mac; //!< The underlying LrWpan MAC connected to this Zigbee Stack. Ptr m_nwk; //!< The Zigbee Network layer. + Ptr m_aps; //!< The Zigbee Application Support Sub-layer Ptr m_node; //!< The node associated with this NetDevice. Ptr 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 diff --git a/src/zigbee/test/zigbee-aps-data-test.cc b/src/zigbee/test/zigbee-aps-data-test.cc new file mode 100644 index 000000000..0dba7d09b --- /dev/null +++ b/src/zigbee/test/zigbee-aps-data-test.cc @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2025 Tokushima University, Tokushima, Japan + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: + * Alberto Gallegos Ramonet + */ + +#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 +#include + +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 stack, + ApsdeDataIndicationParams params, + Ptr 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 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 stackSrc, Ptr 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 stack, + ApsdeDataIndicationParams params, + Ptr asdu) +{ + testcase->m_dstEndpoint = params.m_dstEndPoint; +} + +void +ZigbeeApsDataTestCase::NwkNetworkDiscoveryConfirm(ZigbeeApsDataTestCase* testcase, + Ptr 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 stackSrc, Ptr stackDst) +{ + // UCST transmission to a single 16-bit address destination + // Data is transmitted from device stackSrc to device stackDst. + + Ptr p = Create(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 dev0 = lrwpanDevices.Get(0)->GetObject(); + Ptr dev1 = lrwpanDevices.Get(1)->GetObject(); + Ptr dev2 = lrwpanDevices.Get(2)->GetObject(); + + 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 channel = CreateObject(); + Ptr propModel = + CreateObject(); + + Ptr delayModel = + CreateObject(); + + 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 zstack0 = zigbeeStackContainer.Get(0)->GetObject(); + Ptr zstack1 = zigbeeStackContainer.Get(1)->GetObject(); + Ptr zstack2 = zigbeeStackContainer.Get(2)->GetObject(); + + // 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 dev0Mobility = CreateObject(); + dev0Mobility->SetPosition(Vector(0, 0, 0)); + dev0->GetPhy()->SetMobility(dev0Mobility); + + Ptr dev1Mobility = CreateObject(); + dev1Mobility->SetPosition(Vector(50, 0, 0)); + dev1->GetPhy()->SetMobility(dev1Mobility); + + Ptr dev2Mobility = CreateObject(); + 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 diff --git a/src/zigbee/test/zigbee-rreq-test.cc b/src/zigbee/test/zigbee-rreq-test.cc index 4ada6c6be..ba31fbd90 100644 --- a/src/zigbee/test/zigbee-rreq-test.cc +++ b/src/zigbee/test/zigbee-rreq-test.cc @@ -238,6 +238,7 @@ ZigbeeRreqRetryTestCase::DoRun() //// Configure NWK ZigbeeHelper zigbee; + zigbee.SetNwkLayerOnly(); ZigbeeStackContainer zigbeeStackContainer = zigbee.Install(lrwpanDevices); Ptr zstack0 = zigbeeStackContainer.Get(0)->GetObject();