From a4368909ff52e0d001a5410ad2c6553d2751cca5 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Mon, 12 Oct 2020 01:46:20 +0800 Subject: [PATCH] [low-power] add cli command macsend for certification (#5573) This command allows an Rx-Off-When-Idle device to send an empty mac data frame or mac datarequest to its parent. This command is only used for certification. --- include/openthread/instance.h | 2 +- include/openthread/link.h | 15 ++++ src/cli/README.md | 14 ++++ src/cli/cli.cpp | 27 ++++++++ src/cli/cli.hpp | 3 + src/core/api/link_api.cpp | 9 +++ src/core/common/message.hpp | 11 +-- src/core/thread/indirect_sender.cpp | 32 +-------- src/core/thread/mesh_forwarder.cpp | 79 ++++++++++++++++++++++ src/core/thread/mesh_forwarder.hpp | 13 ++++ tests/scripts/expect/v1_2-sed_mac_send.exp | 41 +++++++++++ tests/scripts/thread-cert/node.py | 8 +++ 12 files changed, 218 insertions(+), 36 deletions(-) create mode 100755 tests/scripts/expect/v1_2-sed_mac_send.exp diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 77285558c..bee273a16 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (30) +#define OPENTHREAD_API_VERSION (31) /** * @addtogroup api-instance diff --git a/include/openthread/link.h b/include/openthread/link.h index 8b4f565af..a833f3a70 100644 --- a/include/openthread/link.h +++ b/include/openthread/link.h @@ -1109,6 +1109,21 @@ otError otLinkSetEnabled(otInstance *aInstance, bool aEnable); */ bool otLinkIsEnabled(otInstance *aInstance); +/** + * This function instructs the device to send an empty IEEE 802.15.4 data frame. + * + * This function is only supported on an Rx-Off-When-Idle device to send an empty data frame to its parent. + * Note: available only when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval OT_ERROR_NONE Successfully enqueued an empty message. + * @retval OT_ERROR_INVALID_STATE Device is not in Rx-Off-When-Idle mode. + * @retval OT_ERROR_NO_BUFS Insufficient message buffers available. + * + */ +otError otLinkSendEmptyData(otInstance *aInstance); + /** * @} * diff --git a/src/cli/README.md b/src/cli/README.md index 5a2893bac..eef19b4a9 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -2047,6 +2047,20 @@ Set the number of indirect TX retries on the MAC layer. Done ``` +### mac send \ + +Instruct an Rx-Off-When-Idle device to send a mac frame to its parent. The mac frame could be either a mac data request or an empty mac data frame. Use `datarequest` to send a mac data request and `data` to send an empty mac data. This feature is for certification, it can only be used when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + +```bash +> mac send datarequest +Done +``` + +```bash +> mac send emptydata +Done +``` + ### macfilter List the macfilter status, including address and received signal strength filter settings. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 6ce76df3b..eb5a29acf 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -4028,6 +4028,12 @@ otError Interpreter::ProcessMac(uint8_t aArgsLength, char *aArgs[]) { error = ProcessMacRetries(aArgsLength - 1, aArgs + 1); } +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + else if (strcmp(aArgs[0], "send") == 0) + { + error = ProcessMacSend(aArgsLength - 1, aArgs + 1); + } +#endif else { error = OT_ERROR_INVALID_COMMAND; @@ -4082,6 +4088,27 @@ exit: return error; } +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +otError Interpreter::ProcessMacSend(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + + VerifyOrExit(aArgsLength == 1, OT_NOOP); + + if (strcmp(aArgs[0], "datarequest") == 0) + { + error = otLinkSendDataRequest(mInstance); + } + else if (strcmp(aArgs[0], "emptydata") == 0) + { + error = otLinkSendEmptyData(mInstance); + } + +exit: + return error; +} +#endif + #if OPENTHREAD_CONFIG_DIAG_ENABLE otError Interpreter::ProcessDiag(uint8_t aArgsLength, char *aArgs[]) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 060af4ff1..b8896529a 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -471,6 +471,9 @@ private: #endif otError ProcessMac(uint8_t aArgsLength, char *aArgs[]); otError ProcessMacRetries(uint8_t aArgsLength, char *aArgs[]); +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + otError ProcessMacSend(uint8_t aArgsLength, char *aArgs[]); +#endif static void HandleIcmpReceive(void * aContext, otMessage * aMessage, diff --git a/src/core/api/link_api.cpp b/src/core/api/link_api.cpp index fb0ebecb6..8c9f43a16 100644 --- a/src/core/api/link_api.cpp +++ b/src/core/api/link_api.cpp @@ -536,3 +536,12 @@ exit: } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +otError otLinkSendEmptyData(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SendEmptyMessage(); +} +#endif diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 12c749c4e..254a94e76 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -164,7 +164,7 @@ struct MessageMetadata uint8_t mChannel; ///< Used for MLE Announce. } mPanIdChannel; ///< Used for MLE Discover Request, Response, and Announce messages. - uint8_t mType : 2; ///< Identifies the type of message. + uint8_t mType : 3; ///< Identifies the type of message. uint8_t mSubType : 4; ///< Identifies the message sub type. bool mDirectTx : 1; ///< Used to indicate whether a direct transmission is required. bool mLinkSecurity : 1; ///< Indicates whether or not link security is enabled. @@ -297,10 +297,11 @@ public: */ enum Type { - kTypeIp6 = 0, ///< A full uncompressed IPv6 packet - kType6lowpan = 1, ///< A 6lowpan frame - kTypeSupervision = 2, ///< A child supervision frame. - kTypeOther = 3, ///< Other (data) message. + kTypeIp6 = 0, ///< A full uncompressed IPv6 packet + kType6lowpan = 1, ///< A 6lowpan frame + kTypeSupervision = 2, ///< A child supervision frame. + kTypeMacEmptyData = 3, ///< An empty MAC data frame. + kTypeOther = 4, ///< Other (data) message. }; /** diff --git a/src/core/thread/indirect_sender.cpp b/src/core/thread/indirect_sender.cpp index c0c2cd092..6bf15b3e6 100644 --- a/src/core/thread/indirect_sender.cpp +++ b/src/core/thread/indirect_sender.cpp @@ -418,37 +418,9 @@ uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, M void IndirectSender::PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool aAckRequest) { - uint16_t fcf; - Mac::Address macSource, macDest; - + Mac::Address macDest; aChild.GetMacAddress(macDest); - - macSource.SetShort(Get().GetShortAddress()); - - if (macSource.IsShortAddrInvalid() || macDest.IsExtended()) - { - macSource.SetExtended(Get().GetExtAddress()); - } - - fcf = Mac::Frame::kFcfFrameData | Mac::Frame::kFcfFrameVersion2006 | Mac::Frame::kFcfPanidCompression | - Mac::Frame::kFcfSecurityEnabled; - - if (aAckRequest) - { - fcf |= Mac::Frame::kFcfAckRequest; - } - - fcf |= (macDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt; - fcf |= (macSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt; - - aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32); - - aFrame.SetDstPanId(Get().GetPanId()); - IgnoreError(aFrame.SetSrcPanId(Get().GetPanId())); - aFrame.SetDstAddr(macDest); - aFrame.SetSrcAddr(macSource); - aFrame.SetPayloadLength(0); - aFrame.SetFramePending(false); + Get().PrepareEmptyFrame(aFrame, macDest, aAckRequest); } void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index 89f6a1673..7226a85ca 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -151,6 +151,46 @@ exit: return; } +void MeshForwarder::PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest) +{ + uint16_t fcf = 0; + + Mac::Address macSource; + macSource.SetShort(Get().GetShortAddress()); + + if (macSource.IsShortAddrInvalid() || aMacDest.IsExtended()) + { + macSource.SetExtended(Get().GetExtAddress()); + } + + fcf = Mac::Frame::kFcfFrameData | Mac::Frame::kFcfPanidCompression | Mac::Frame::kFcfSecurityEnabled; + + if (aAckRequest) + { + fcf |= Mac::Frame::kFcfAckRequest; + } + + fcf |= (aMacDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt; + fcf |= (macSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt; + Get().UpdateFrameControlField(nullptr, 0, fcf); + + aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32); + + if (aFrame.IsDstPanIdPresent()) + { + aFrame.SetDstPanId(Get().GetPanId()); + } + IgnoreError(aFrame.SetSrcPanId(Get().GetPanId())); + + aFrame.SetDstAddr(aMacDest); + aFrame.SetSrcAddr(macSource); + aFrame.SetFramePending(false); +#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT + IgnoreError(Get().AppendHeaderIe(false, aFrame)); +#endif + aFrame.SetPayloadLength(0); +} + void MeshForwarder::RemoveMessage(Message &aMessage) { PriorityQueue *queue = aMessage.GetPriorityQueue(); @@ -238,6 +278,12 @@ Message *MeshForwarder::GetDirectTransmission(void) #endif +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + case Message::kTypeMacEmptyData: + error = OT_ERROR_NONE; + break; +#endif + default: error = OT_ERROR_DROP; break; @@ -454,6 +500,17 @@ otError MeshForwarder::HandleFrameRequest(Mac::TxFrame &aFrame) OT_ASSERT(aFrame.GetLength() != 7); break; +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + case Message::kTypeMacEmptyData: + { + Mac::Address macDestAddr; + + macDestAddr.SetShort(Get().GetParent().GetRloc16()); + PrepareEmptyFrame(aFrame, macDestAddr, /* aAckRequest */ true); + } + break; +#endif + #if OPENTHREAD_FTD case Message::kType6lowpan: @@ -1281,6 +1338,28 @@ exit: return error; } +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +otError MeshForwarder::SendEmptyMessage(void) +{ + otError error = OT_ERROR_NONE; + Message *message = nullptr; + + VerifyOrExit(mEnabled && !Get().GetRxOnWhenIdle() && + Get().GetParent().IsStateValidOrRestoring(), + error = OT_ERROR_INVALID_STATE); + + message = Get().New(Message::kTypeMacEmptyData, 0); + VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = SendMessage(*message)); + +exit: + FreeMessageOnError(message, error); + otLogDebgMac("Send empty message, error:%s", otThreadErrorToString(error)); + return error; +} +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + // LCOV_EXCL_START #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1) diff --git a/src/core/thread/mesh_forwarder.hpp b/src/core/thread/mesh_forwarder.hpp index 9888434e5..ae4b48ca2 100644 --- a/src/core/thread/mesh_forwarder.hpp +++ b/src/core/thread/mesh_forwarder.hpp @@ -186,6 +186,18 @@ public: */ otError SendMessage(Message &aMessage); +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + /** + * This method sends an empty data frame to the parent. + * + * @retval OT_ERROR_NONE Successfully enqueued an empty message. + * @retval OT_ERROR_INVALID_STATE Device is not in Rx-Off-When-Idle mode or it has no parent. + * @retval OT_ERROR_NO_BUFS Insufficient message buffers available. + * + */ + otError SendEmptyMessage(void); +#endif + /** * This method is called by the address resolver when an EID-to-RLOC mapping has been resolved. * @@ -403,6 +415,7 @@ private: bool aAddMeshHeader = false, uint16_t aMeshSource = 0xffff, uint16_t aMeshDest = 0xffff); + void PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aDstAddr, bool aAckRequest); void SendMesh(Message &aMessage, Mac::TxFrame &aFrame); void SendDestinationUnreachable(uint16_t aMeshSource, const Message &aMessage); diff --git a/tests/scripts/expect/v1_2-sed_mac_send.exp b/tests/scripts/expect/v1_2-sed_mac_send.exp new file mode 100755 index 000000000..f099023be --- /dev/null +++ b/tests/scripts/expect/v1_2-sed_mac_send.exp @@ -0,0 +1,41 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +setup_nodes "-" + +set spawn_id $spawn_2 +send "mac send emptydata\n" +expect "Done" +send "mac send datarequest\n" +expect "Done" + +dispose_nodes diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index a2ea0a91a..b3c7d5f91 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -956,6 +956,14 @@ class NodeImpl: self.send_command('csl timeout %d' % csl_timeout) self._expect('Done') + def send_mac_emptydata(self): + self.send_command('mac send emptydata') + self._expect('Done') + + def send_mac_datarequest(self): + self.send_command('mac send datarequest') + self._expect('Done') + def set_router_upgrade_threshold(self, threshold): cmd = 'routerupgradethreshold %d' % threshold self.send_command(cmd)