| /* |
| * Copyright (c) 2016, 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. |
| */ |
| |
| /** |
| * @file |
| * This file implements IEEE 802.15.4 header generation and processing. |
| */ |
| |
| #include "mac_frame.hpp" |
| |
| #include <stdio.h> |
| |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/log.hpp" |
| #include "radio/trel_link.hpp" |
| #if !OPENTHREAD_RADIO || OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE |
| #include "crypto/aes_ccm.hpp" |
| #endif |
| |
| namespace ot { |
| namespace Mac { |
| |
| using ot::Encoding::LittleEndian::ReadUint16; |
| using ot::Encoding::LittleEndian::ReadUint32; |
| using ot::Encoding::LittleEndian::WriteUint16; |
| using ot::Encoding::LittleEndian::WriteUint32; |
| |
| void HeaderIe::Init(uint16_t aId, uint8_t aLen) |
| { |
| Init(); |
| SetId(aId); |
| SetLength(aLen); |
| } |
| |
| void Frame::InitMacHeader(uint16_t aFcf, uint8_t aSecurityControl) |
| { |
| mLength = CalculateAddrFieldSize(aFcf); |
| |
| OT_ASSERT(mLength != kInvalidSize); |
| |
| WriteUint16(aFcf, mPsdu); |
| |
| if (aFcf & kFcfSecurityEnabled) |
| { |
| mPsdu[mLength] = aSecurityControl; |
| |
| mLength += CalculateSecurityHeaderSize(aSecurityControl); |
| mLength += CalculateMicSize(aSecurityControl); |
| } |
| |
| if ((aFcf & kFcfFrameTypeMask) == kFcfFrameMacCmd) |
| { |
| mLength += kCommandIdSize; |
| } |
| |
| mLength += GetFcsSize(); |
| } |
| |
| uint16_t Frame::GetFrameControlField(void) const |
| { |
| return ReadUint16(mPsdu); |
| } |
| |
| Error Frame::ValidatePsdu(void) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindPayloadIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| VerifyOrExit((index + GetFooterLength()) <= mLength, error = kErrorParse); |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetAckRequest(bool aAckRequest) |
| { |
| if (aAckRequest) |
| { |
| mPsdu[0] |= kFcfAckRequest; |
| } |
| else |
| { |
| mPsdu[0] &= ~kFcfAckRequest; |
| } |
| } |
| |
| void Frame::SetFramePending(bool aFramePending) |
| { |
| if (aFramePending) |
| { |
| mPsdu[0] |= kFcfFramePending; |
| } |
| else |
| { |
| mPsdu[0] &= ~kFcfFramePending; |
| } |
| } |
| |
| uint8_t Frame::FindDstPanIdIndex(void) const |
| { |
| uint8_t index; |
| |
| VerifyOrExit(IsDstPanIdPresent(), index = kInvalidIndex); |
| |
| index = kFcfSize + kDsnSize; |
| |
| exit: |
| return index; |
| } |
| |
| bool Frame::IsDstPanIdPresent(uint16_t aFcf) |
| { |
| bool present = true; |
| |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| if (IsVersion2015(aFcf)) |
| { |
| switch (aFcf & (kFcfDstAddrMask | kFcfSrcAddrMask | kFcfPanidCompression)) |
| { |
| case (kFcfDstAddrNone | kFcfSrcAddrNone): |
| case (kFcfDstAddrExt | kFcfSrcAddrNone | kFcfPanidCompression): |
| case (kFcfDstAddrShort | kFcfSrcAddrNone | kFcfPanidCompression): |
| case (kFcfDstAddrNone | kFcfSrcAddrExt): |
| case (kFcfDstAddrNone | kFcfSrcAddrShort): |
| case (kFcfDstAddrNone | kFcfSrcAddrExt | kFcfPanidCompression): |
| case (kFcfDstAddrNone | kFcfSrcAddrShort | kFcfPanidCompression): |
| case (kFcfDstAddrExt | kFcfSrcAddrExt | kFcfPanidCompression): |
| present = false; |
| break; |
| default: |
| break; |
| } |
| } |
| else |
| #endif |
| { |
| present = IsDstAddrPresent(aFcf); |
| } |
| |
| return present; |
| } |
| |
| Error Frame::GetDstPanId(PanId &aPanId) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindDstPanIdIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| aPanId = ReadUint16(&mPsdu[index]); |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetDstPanId(PanId aPanId) |
| { |
| uint8_t index = FindDstPanIdIndex(); |
| |
| OT_ASSERT(index != kInvalidIndex); |
| WriteUint16(aPanId, &mPsdu[index]); |
| } |
| |
| uint8_t Frame::FindDstAddrIndex(void) const |
| { |
| return kFcfSize + kDsnSize + (IsDstPanIdPresent() ? sizeof(PanId) : 0); |
| } |
| |
| Error Frame::GetDstAddr(Address &aAddress) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindDstAddrIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| switch (GetFrameControlField() & kFcfDstAddrMask) |
| { |
| case kFcfDstAddrShort: |
| aAddress.SetShort(ReadUint16(&mPsdu[index])); |
| break; |
| |
| case kFcfDstAddrExt: |
| aAddress.SetExtended(&mPsdu[index], ExtAddress::kReverseByteOrder); |
| break; |
| |
| default: |
| aAddress.SetNone(); |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetDstAddr(ShortAddress aShortAddress) |
| { |
| OT_ASSERT((GetFrameControlField() & kFcfDstAddrMask) == kFcfDstAddrShort); |
| WriteUint16(aShortAddress, &mPsdu[FindDstAddrIndex()]); |
| } |
| |
| void Frame::SetDstAddr(const ExtAddress &aExtAddress) |
| { |
| uint8_t index = FindDstAddrIndex(); |
| |
| OT_ASSERT((GetFrameControlField() & kFcfDstAddrMask) == kFcfDstAddrExt); |
| OT_ASSERT(index != kInvalidIndex); |
| |
| aExtAddress.CopyTo(&mPsdu[index], ExtAddress::kReverseByteOrder); |
| } |
| |
| void Frame::SetDstAddr(const Address &aAddress) |
| { |
| switch (aAddress.GetType()) |
| { |
| case Address::kTypeShort: |
| SetDstAddr(aAddress.GetShort()); |
| break; |
| |
| case Address::kTypeExtended: |
| SetDstAddr(aAddress.GetExtended()); |
| break; |
| |
| default: |
| OT_ASSERT(false); |
| OT_UNREACHABLE_CODE(break); |
| } |
| } |
| |
| uint8_t Frame::FindSrcPanIdIndex(void) const |
| { |
| uint8_t index = 0; |
| uint16_t fcf = GetFrameControlField(); |
| |
| VerifyOrExit(IsSrcPanIdPresent(), index = kInvalidIndex); |
| |
| index += kFcfSize + kDsnSize; |
| |
| if (IsDstPanIdPresent(fcf)) |
| { |
| index += sizeof(PanId); |
| } |
| |
| switch (fcf & kFcfDstAddrMask) |
| { |
| case kFcfDstAddrShort: |
| index += sizeof(ShortAddress); |
| break; |
| |
| case kFcfDstAddrExt: |
| index += sizeof(ExtAddress); |
| break; |
| } |
| |
| exit: |
| return index; |
| } |
| |
| bool Frame::IsSrcPanIdPresent(uint16_t aFcf) |
| { |
| bool srcPanIdPresent = false; |
| |
| if ((aFcf & kFcfSrcAddrMask) != kFcfSrcAddrNone && (aFcf & kFcfPanidCompression) == 0) |
| { |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| // Handle a special case in IEEE 802.15.4-2015, when Pan ID Compression is 0, but Src Pan ID is not present: |
| // Dest Address: Extended |
| // Source Address: Extended |
| // Dest Pan ID: Present |
| // Src Pan ID: Not Present |
| // Pan ID Compression: 0 |
| if (!IsVersion2015(aFcf) || (aFcf & kFcfDstAddrMask) != kFcfDstAddrExt || |
| (aFcf & kFcfSrcAddrMask) != kFcfSrcAddrExt) |
| #endif |
| { |
| srcPanIdPresent = true; |
| } |
| } |
| |
| return srcPanIdPresent; |
| } |
| |
| Error Frame::GetSrcPanId(PanId &aPanId) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSrcPanIdIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| aPanId = ReadUint16(&mPsdu[index]); |
| |
| exit: |
| return error; |
| } |
| |
| Error Frame::SetSrcPanId(PanId aPanId) |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSrcPanIdIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| WriteUint16(aPanId, &mPsdu[index]); |
| |
| exit: |
| return error; |
| } |
| |
| uint8_t Frame::FindSrcAddrIndex(void) const |
| { |
| uint8_t index = 0; |
| uint16_t fcf = GetFrameControlField(); |
| |
| index += kFcfSize + kDsnSize; |
| |
| if (IsDstPanIdPresent(fcf)) |
| { |
| index += sizeof(PanId); |
| } |
| |
| switch (fcf & kFcfDstAddrMask) |
| { |
| case kFcfDstAddrShort: |
| index += sizeof(ShortAddress); |
| break; |
| |
| case kFcfDstAddrExt: |
| index += sizeof(ExtAddress); |
| break; |
| } |
| |
| if (IsSrcPanIdPresent(fcf)) |
| { |
| index += sizeof(PanId); |
| } |
| |
| return index; |
| } |
| |
| Error Frame::GetSrcAddr(Address &aAddress) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSrcAddrIndex(); |
| uint16_t fcf = GetFrameControlField(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| switch (fcf & kFcfSrcAddrMask) |
| { |
| case kFcfSrcAddrShort: |
| aAddress.SetShort(ReadUint16(&mPsdu[index])); |
| break; |
| |
| case kFcfSrcAddrExt: |
| aAddress.SetExtended(&mPsdu[index], ExtAddress::kReverseByteOrder); |
| break; |
| |
| default: |
| aAddress.SetNone(); |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetSrcAddr(ShortAddress aShortAddress) |
| { |
| uint8_t index = FindSrcAddrIndex(); |
| |
| OT_ASSERT((GetFrameControlField() & kFcfSrcAddrMask) == kFcfSrcAddrShort); |
| OT_ASSERT(index != kInvalidIndex); |
| |
| WriteUint16(aShortAddress, &mPsdu[index]); |
| } |
| |
| void Frame::SetSrcAddr(const ExtAddress &aExtAddress) |
| { |
| uint8_t index = FindSrcAddrIndex(); |
| |
| OT_ASSERT((GetFrameControlField() & kFcfSrcAddrMask) == kFcfSrcAddrExt); |
| OT_ASSERT(index != kInvalidIndex); |
| |
| aExtAddress.CopyTo(&mPsdu[index], ExtAddress::kReverseByteOrder); |
| } |
| |
| void Frame::SetSrcAddr(const Address &aAddress) |
| { |
| switch (aAddress.GetType()) |
| { |
| case Address::kTypeShort: |
| SetSrcAddr(aAddress.GetShort()); |
| break; |
| |
| case Address::kTypeExtended: |
| SetSrcAddr(aAddress.GetExtended()); |
| break; |
| |
| default: |
| OT_ASSERT(false); |
| } |
| } |
| |
| Error Frame::GetSecurityControlField(uint8_t &aSecurityControlField) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| aSecurityControlField = mPsdu[index]; |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetSecurityControlField(uint8_t aSecurityControlField) |
| { |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| OT_ASSERT(index != kInvalidIndex); |
| |
| mPsdu[index] = aSecurityControlField; |
| } |
| |
| uint8_t Frame::FindSecurityHeaderIndex(void) const |
| { |
| uint8_t index; |
| |
| VerifyOrExit(kFcfSize < mLength, index = kInvalidIndex); |
| VerifyOrExit(GetSecurityEnabled(), index = kInvalidIndex); |
| index = SkipAddrFieldIndex(); |
| |
| exit: |
| return index; |
| } |
| |
| Error Frame::GetSecurityLevel(uint8_t &aSecurityLevel) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| aSecurityLevel = mPsdu[index] & kSecLevelMask; |
| |
| exit: |
| return error; |
| } |
| |
| Error Frame::GetKeyIdMode(uint8_t &aKeyIdMode) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| aKeyIdMode = mPsdu[index] & kKeyIdModeMask; |
| |
| exit: |
| return error; |
| } |
| |
| Error Frame::GetFrameCounter(uint32_t &aFrameCounter) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| // Security Control |
| index += kSecurityControlSize; |
| |
| aFrameCounter = ReadUint32(&mPsdu[index]); |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetFrameCounter(uint32_t aFrameCounter) |
| { |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| OT_ASSERT(index != kInvalidIndex); |
| |
| // Security Control |
| index += kSecurityControlSize; |
| |
| WriteUint32(aFrameCounter, &mPsdu[index]); |
| |
| static_cast<Mac::TxFrame *>(this)->SetIsHeaderUpdated(true); |
| } |
| |
| const uint8_t *Frame::GetKeySource(void) const |
| { |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| OT_ASSERT(index != kInvalidIndex); |
| |
| return &mPsdu[index + kSecurityControlSize + kFrameCounterSize]; |
| } |
| |
| uint8_t Frame::GetKeySourceLength(uint8_t aKeyIdMode) |
| { |
| uint8_t len = 0; |
| |
| switch (aKeyIdMode) |
| { |
| case kKeyIdMode0: |
| len = kKeySourceSizeMode0; |
| break; |
| |
| case kKeyIdMode1: |
| len = kKeySourceSizeMode1; |
| break; |
| |
| case kKeyIdMode2: |
| len = kKeySourceSizeMode2; |
| break; |
| |
| case kKeyIdMode3: |
| len = kKeySourceSizeMode3; |
| break; |
| } |
| |
| return len; |
| } |
| |
| void Frame::SetKeySource(const uint8_t *aKeySource) |
| { |
| uint8_t keySourceLength; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| OT_ASSERT(index != kInvalidIndex); |
| |
| keySourceLength = GetKeySourceLength(mPsdu[index] & kKeyIdModeMask); |
| |
| memcpy(&mPsdu[index + kSecurityControlSize + kFrameCounterSize], aKeySource, keySourceLength); |
| } |
| |
| Error Frame::GetKeyId(uint8_t &aKeyId) const |
| { |
| Error error = kErrorNone; |
| uint8_t keySourceLength; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| keySourceLength = GetKeySourceLength(mPsdu[index] & kKeyIdModeMask); |
| |
| aKeyId = mPsdu[index + kSecurityControlSize + kFrameCounterSize + keySourceLength]; |
| |
| exit: |
| return error; |
| } |
| |
| void Frame::SetKeyId(uint8_t aKeyId) |
| { |
| uint8_t keySourceLength; |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| OT_ASSERT(index != kInvalidIndex); |
| |
| keySourceLength = GetKeySourceLength(mPsdu[index] & kKeyIdModeMask); |
| |
| mPsdu[index + kSecurityControlSize + kFrameCounterSize + keySourceLength] = aKeyId; |
| } |
| |
| Error Frame::GetCommandId(uint8_t &aCommandId) const |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindPayloadIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| aCommandId = mPsdu[IsVersion2015() ? index : (index - 1)]; |
| |
| exit: |
| return error; |
| } |
| |
| Error Frame::SetCommandId(uint8_t aCommandId) |
| { |
| Error error = kErrorNone; |
| uint8_t index = FindPayloadIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex, error = kErrorParse); |
| |
| mPsdu[IsVersion2015() ? index : (index - 1)] = aCommandId; |
| |
| exit: |
| return error; |
| } |
| |
| bool Frame::IsDataRequestCommand(void) const |
| { |
| bool isDataRequest = false; |
| uint8_t commandId; |
| |
| VerifyOrExit(GetType() == kFcfFrameMacCmd); |
| SuccessOrExit(GetCommandId(commandId)); |
| isDataRequest = (commandId == kMacCmdDataRequest); |
| |
| exit: |
| return isDataRequest; |
| } |
| |
| uint8_t Frame::GetHeaderLength(void) const |
| { |
| return static_cast<uint8_t>(GetPayload() - mPsdu); |
| } |
| |
| uint8_t Frame::GetFooterLength(void) const |
| { |
| uint8_t footerLength = static_cast<uint8_t>(GetFcsSize()); |
| uint8_t index = FindSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex); |
| footerLength += CalculateMicSize(mPsdu[index]); |
| |
| exit: |
| return footerLength; |
| } |
| |
| uint8_t Frame::CalculateMicSize(uint8_t aSecurityControl) |
| { |
| uint8_t micSize = 0; |
| |
| switch (aSecurityControl & kSecLevelMask) |
| { |
| case kSecNone: |
| case kSecEnc: |
| micSize = kMic0Size; |
| break; |
| |
| case kSecMic32: |
| case kSecEncMic32: |
| micSize = kMic32Size; |
| break; |
| |
| case kSecMic64: |
| case kSecEncMic64: |
| micSize = kMic64Size; |
| break; |
| |
| case kSecMic128: |
| case kSecEncMic128: |
| micSize = kMic128Size; |
| break; |
| } |
| |
| return micSize; |
| } |
| |
| uint16_t Frame::GetMaxPayloadLength(void) const |
| { |
| return GetMtu() - (GetHeaderLength() + GetFooterLength()); |
| } |
| |
| uint16_t Frame::GetPayloadLength(void) const |
| { |
| return mLength - (GetHeaderLength() + GetFooterLength()); |
| } |
| |
| void Frame::SetPayloadLength(uint16_t aLength) |
| { |
| mLength = GetHeaderLength() + GetFooterLength() + aLength; |
| } |
| |
| uint8_t Frame::SkipSecurityHeaderIndex(void) const |
| { |
| uint8_t index = SkipAddrFieldIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex); |
| |
| if (GetSecurityEnabled()) |
| { |
| uint8_t securityControl; |
| uint8_t headerSize; |
| |
| VerifyOrExit(index < mLength, index = kInvalidIndex); |
| securityControl = mPsdu[index]; |
| |
| headerSize = CalculateSecurityHeaderSize(securityControl); |
| VerifyOrExit(headerSize != kInvalidSize, index = kInvalidIndex); |
| |
| index += headerSize; |
| |
| VerifyOrExit(index <= mLength, index = kInvalidIndex); |
| } |
| |
| exit: |
| return index; |
| } |
| |
| uint8_t Frame::CalculateSecurityHeaderSize(uint8_t aSecurityControl) |
| { |
| uint8_t size = kSecurityControlSize + kFrameCounterSize; |
| |
| VerifyOrExit((aSecurityControl & kSecLevelMask) != kSecNone, size = kInvalidSize); |
| |
| switch (aSecurityControl & kKeyIdModeMask) |
| { |
| case kKeyIdMode0: |
| size += kKeySourceSizeMode0; |
| break; |
| |
| case kKeyIdMode1: |
| size += kKeySourceSizeMode1 + kKeyIndexSize; |
| break; |
| |
| case kKeyIdMode2: |
| size += kKeySourceSizeMode2 + kKeyIndexSize; |
| break; |
| |
| case kKeyIdMode3: |
| size += kKeySourceSizeMode3 + kKeyIndexSize; |
| break; |
| } |
| |
| exit: |
| return size; |
| } |
| |
| uint8_t Frame::SkipAddrFieldIndex(void) const |
| { |
| uint8_t index; |
| |
| VerifyOrExit(kFcfSize + kDsnSize + GetFcsSize() <= mLength, index = kInvalidIndex); |
| |
| index = CalculateAddrFieldSize(GetFrameControlField()); |
| |
| exit: |
| return index; |
| } |
| |
| uint8_t Frame::CalculateAddrFieldSize(uint16_t aFcf) |
| { |
| uint8_t size = kFcfSize + kDsnSize; |
| |
| // This static method calculates the size (number of bytes) of |
| // Address header field for a given Frame Control `aFcf` value. |
| // The size includes the Frame Control and Sequence Number fields |
| // along with Destination and Source PAN ID and Short/Extended |
| // Addresses. If the `aFcf` is not valid, this method returns |
| // `kInvalidSize`. |
| |
| if (IsDstPanIdPresent(aFcf)) |
| { |
| size += sizeof(PanId); |
| } |
| |
| switch (aFcf & kFcfDstAddrMask) |
| { |
| case kFcfDstAddrNone: |
| break; |
| |
| case kFcfDstAddrShort: |
| size += sizeof(ShortAddress); |
| break; |
| |
| case kFcfDstAddrExt: |
| size += sizeof(ExtAddress); |
| break; |
| |
| default: |
| ExitNow(size = kInvalidSize); |
| } |
| |
| if (IsSrcPanIdPresent(aFcf)) |
| { |
| size += sizeof(PanId); |
| } |
| |
| switch (aFcf & kFcfSrcAddrMask) |
| { |
| case kFcfSrcAddrNone: |
| break; |
| |
| case kFcfSrcAddrShort: |
| size += sizeof(ShortAddress); |
| break; |
| |
| case kFcfSrcAddrExt: |
| size += sizeof(ExtAddress); |
| break; |
| |
| default: |
| ExitNow(size = kInvalidSize); |
| } |
| |
| exit: |
| return size; |
| } |
| |
| uint8_t Frame::FindPayloadIndex(void) const |
| { |
| // We use `uint16_t` for `index` to handle its potential roll-over |
| // while parsing and verifying Header IE(s). |
| |
| uint16_t index = SkipSecurityHeaderIndex(); |
| |
| VerifyOrExit(index != kInvalidIndex); |
| |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| if (IsIePresent()) |
| { |
| uint8_t footerLength = GetFooterLength(); |
| |
| do |
| { |
| const HeaderIe *ie = reinterpret_cast<const HeaderIe *>(&mPsdu[index]); |
| |
| index += sizeof(HeaderIe); |
| VerifyOrExit(index + footerLength <= mLength, index = kInvalidIndex); |
| |
| index += ie->GetLength(); |
| VerifyOrExit(index + footerLength <= mLength, index = kInvalidIndex); |
| |
| if (ie->GetId() == Termination2Ie::kHeaderIeId) |
| { |
| break; |
| } |
| |
| // If the `index + footerLength == mLength`, we exit the `while()` |
| // loop. This covers the case where frame contains one or more |
| // Header IEs but no data payload. In this case, spec does not |
| // require Header IE termination to be included (it is optional) |
| // since the end of frame can be determined from frame length and |
| // footer length. |
| |
| } while (index + footerLength < mLength); |
| |
| // Assume no Payload IE in current implementation |
| } |
| #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| |
| if (!IsVersion2015() && (GetFrameControlField() & kFcfFrameTypeMask) == kFcfFrameMacCmd) |
| { |
| index += kCommandIdSize; |
| } |
| |
| exit: |
| return static_cast<uint8_t>(index); |
| } |
| |
| const uint8_t *Frame::GetPayload(void) const |
| { |
| uint8_t index = FindPayloadIndex(); |
| const uint8_t *payload; |
| |
| VerifyOrExit(index != kInvalidIndex, payload = nullptr); |
| payload = &mPsdu[index]; |
| |
| exit: |
| return payload; |
| } |
| |
| const uint8_t *Frame::GetFooter(void) const |
| { |
| return mPsdu + mLength - GetFooterLength(); |
| } |
| |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| uint8_t Frame::FindHeaderIeIndex(void) const |
| { |
| uint8_t index; |
| |
| VerifyOrExit(IsIePresent(), index = kInvalidIndex); |
| |
| index = SkipSecurityHeaderIndex(); |
| |
| exit: |
| return index; |
| } |
| |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| template <typename IeType> Error Frame::AppendHeaderIeAt(uint8_t &aIndex) |
| { |
| Error error = kErrorNone; |
| |
| SuccessOrExit(error = InitIeHeaderAt(aIndex, IeType::kHeaderIeId, IeType::kIeContentSize)); |
| |
| InitIeContentAt<IeType>(aIndex); |
| |
| exit: |
| return error; |
| } |
| |
| Error Frame::InitIeHeaderAt(uint8_t &aIndex, uint8_t ieId, uint8_t ieContentSize) |
| { |
| Error error = kErrorNone; |
| |
| if (aIndex == 0) |
| { |
| aIndex = FindHeaderIeIndex(); |
| } |
| |
| VerifyOrExit(aIndex != kInvalidIndex, error = kErrorNotFound); |
| |
| reinterpret_cast<HeaderIe *>(mPsdu + aIndex)->Init(ieId, ieContentSize); |
| aIndex += sizeof(HeaderIe); |
| |
| mLength += sizeof(HeaderIe) + ieContentSize; |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| template <> void Frame::InitIeContentAt<TimeIe>(uint8_t &aIndex) |
| { |
| reinterpret_cast<TimeIe *>(mPsdu + aIndex)->Init(); |
| aIndex += sizeof(TimeIe); |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE |
| template <> void Frame::InitIeContentAt<CslIe>(uint8_t &aIndex) |
| { |
| aIndex += sizeof(CslIe); |
| } |
| #endif |
| |
| template <> void Frame::InitIeContentAt<Termination2Ie>(uint8_t &aIndex) |
| { |
| OT_UNUSED_VARIABLE(aIndex); |
| } |
| #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| |
| const uint8_t *Frame::GetHeaderIe(uint8_t aIeId) const |
| { |
| uint8_t index = FindHeaderIeIndex(); |
| uint8_t payloadIndex = FindPayloadIndex(); |
| const uint8_t *header = nullptr; |
| |
| // `FindPayloadIndex()` verifies that Header IE(s) in frame (if present) |
| // are well-formed. |
| |
| VerifyOrExit((index != kInvalidIndex) && (payloadIndex != kInvalidIndex)); |
| |
| while (index <= payloadIndex) |
| { |
| const HeaderIe *ie = reinterpret_cast<const HeaderIe *>(&mPsdu[index]); |
| |
| if (ie->GetId() == aIeId) |
| { |
| header = &mPsdu[index]; |
| ExitNow(); |
| } |
| |
| index += sizeof(HeaderIe) + ie->GetLength(); |
| } |
| |
| exit: |
| return header; |
| } |
| |
| #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| const uint8_t *Frame::GetThreadIe(uint8_t aSubType) const |
| { |
| uint8_t index = FindHeaderIeIndex(); |
| uint8_t payloadIndex = FindPayloadIndex(); |
| const uint8_t *header = nullptr; |
| |
| // `FindPayloadIndex()` verifies that Header IE(s) in frame (if present) |
| // are well-formed. |
| VerifyOrExit((index != kInvalidIndex) && (payloadIndex != kInvalidIndex)); |
| |
| while (index <= payloadIndex) |
| { |
| const HeaderIe *ie = reinterpret_cast<const HeaderIe *>(&mPsdu[index]); |
| |
| if (ie->GetId() == VendorIeHeader::kHeaderIeId) |
| { |
| const VendorIeHeader *vendorIe = |
| reinterpret_cast<const VendorIeHeader *>(reinterpret_cast<const uint8_t *>(ie) + sizeof(HeaderIe)); |
| if (vendorIe->GetVendorOui() == ThreadIe::kVendorOuiThreadCompanyId && vendorIe->GetSubType() == aSubType) |
| { |
| header = &mPsdu[index]; |
| ExitNow(); |
| } |
| } |
| |
| index += sizeof(HeaderIe) + ie->GetLength(); |
| } |
| |
| exit: |
| return header; |
| } |
| #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| |
| #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| |
| #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE |
| void Frame::SetCslIe(uint16_t aCslPeriod, uint16_t aCslPhase) |
| { |
| uint8_t *cur = GetHeaderIe(CslIe::kHeaderIeId); |
| CslIe * csl; |
| |
| VerifyOrExit(cur != nullptr); |
| |
| csl = reinterpret_cast<CslIe *>(cur + sizeof(HeaderIe)); |
| csl->SetPeriod(aCslPeriod); |
| csl->SetPhase(aCslPhase); |
| exit: |
| return; |
| } |
| #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE |
| |
| #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| void Frame::SetEnhAckProbingIe(const uint8_t *aValue, uint8_t aLen) |
| { |
| uint8_t *cur = GetThreadIe(ThreadIe::kEnhAckProbingIe); |
| |
| OT_ASSERT(cur != nullptr); |
| |
| memcpy(cur + sizeof(HeaderIe) + sizeof(VendorIeHeader), aValue, aLen); |
| } |
| #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| |
| #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| const TimeIe *Frame::GetTimeIe(void) const |
| { |
| const TimeIe * timeIe = nullptr; |
| const uint8_t *cur = nullptr; |
| |
| cur = GetHeaderIe(VendorIeHeader::kHeaderIeId); |
| VerifyOrExit(cur != nullptr); |
| |
| cur += sizeof(HeaderIe); |
| |
| timeIe = reinterpret_cast<const TimeIe *>(cur); |
| VerifyOrExit(timeIe->GetVendorOui() == TimeIe::kVendorOuiNest, timeIe = nullptr); |
| VerifyOrExit(timeIe->GetSubType() == TimeIe::kVendorIeTime, timeIe = nullptr); |
| |
| exit: |
| return timeIe; |
| } |
| #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| uint16_t Frame::GetMtu(void) const |
| { |
| uint16_t mtu = 0; |
| |
| switch (GetRadioType()) |
| { |
| #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| case kRadioTypeIeee802154: |
| mtu = OT_RADIO_FRAME_MAX_SIZE; |
| break; |
| #endif |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| case kRadioTypeTrel: |
| mtu = Trel::Link::kMtuSize; |
| break; |
| #endif |
| } |
| |
| return mtu; |
| } |
| |
| uint8_t Frame::GetFcsSize(void) const |
| { |
| uint8_t fcsSize = 0; |
| |
| switch (GetRadioType()) |
| { |
| #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| case kRadioTypeIeee802154: |
| fcsSize = k154FcsSize; |
| break; |
| #endif |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| case kRadioTypeTrel: |
| fcsSize = Trel::Link::kFcsSize; |
| break; |
| #endif |
| } |
| |
| return fcsSize; |
| } |
| |
| #elif OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| uint16_t Frame::GetMtu(void) const |
| { |
| return Trel::Link::kMtuSize; |
| } |
| |
| uint8_t Frame::GetFcsSize(void) const |
| { |
| return Trel::Link::kFcsSize; |
| } |
| #endif |
| |
| // Explicit instantiation |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT |
| #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| template Error Frame::AppendHeaderIeAt<TimeIe>(uint8_t &aIndex); |
| #endif |
| #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE |
| template Error Frame::AppendHeaderIeAt<CslIe>(uint8_t &aIndex); |
| #endif |
| template Error Frame::AppendHeaderIeAt<Termination2Ie>(uint8_t &aIndex); |
| #endif |
| |
| void TxFrame::CopyFrom(const TxFrame &aFromFrame) |
| { |
| uint8_t * psduBuffer = mPsdu; |
| otRadioIeInfo *ieInfoBuffer = mInfo.mTxInfo.mIeInfo; |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| uint8_t radioType = mRadioType; |
| #endif |
| |
| memcpy(this, &aFromFrame, sizeof(Frame)); |
| |
| // Set the original buffer pointers (and link type) back on |
| // the frame (which were overwritten by above `memcpy()`). |
| |
| mPsdu = psduBuffer; |
| mInfo.mTxInfo.mIeInfo = ieInfoBuffer; |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| mRadioType = radioType; |
| #endif |
| |
| memcpy(mPsdu, aFromFrame.mPsdu, aFromFrame.mLength); |
| |
| // mIeInfo may be null when TIME_SYNC is not enabled. |
| #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| memcpy(mInfo.mTxInfo.mIeInfo, aFromFrame.mInfo.mTxInfo.mIeInfo, sizeof(otRadioIeInfo)); |
| #endif |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| if (mRadioType != aFromFrame.GetRadioType()) |
| { |
| // Frames associated with different radio link types can have |
| // different FCS size. We adjust the PSDU length after the |
| // copy to account for this. |
| |
| SetLength(aFromFrame.GetPsduLength() - aFromFrame.GetFcsSize() + GetFcsSize()); |
| } |
| #endif |
| } |
| |
| void TxFrame::ProcessTransmitAesCcm(const ExtAddress &aExtAddress) |
| { |
| #if OPENTHREAD_RADIO && !OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE |
| OT_UNUSED_VARIABLE(aExtAddress); |
| #else |
| uint32_t frameCounter = 0; |
| uint8_t securityLevel; |
| uint8_t nonce[Crypto::AesCcm::kNonceSize]; |
| uint8_t tagLength; |
| Crypto::AesCcm aesCcm; |
| |
| VerifyOrExit(GetSecurityEnabled()); |
| |
| SuccessOrExit(GetSecurityLevel(securityLevel)); |
| SuccessOrExit(GetFrameCounter(frameCounter)); |
| |
| Crypto::AesCcm::GenerateNonce(aExtAddress, frameCounter, securityLevel, nonce); |
| |
| aesCcm.SetKey(GetAesKey()); |
| tagLength = GetFooterLength() - GetFcsSize(); |
| |
| aesCcm.Init(GetHeaderLength(), GetPayloadLength(), tagLength, nonce, sizeof(nonce)); |
| aesCcm.Header(GetHeader(), GetHeaderLength()); |
| aesCcm.Payload(GetPayload(), GetPayload(), GetPayloadLength(), Crypto::AesCcm::kEncrypt); |
| aesCcm.Finalize(GetFooter()); |
| |
| SetIsSecurityProcessed(true); |
| |
| exit: |
| return; |
| #endif // OPENTHREAD_RADIO && !OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE |
| } |
| |
| void TxFrame::GenerateImmAck(const RxFrame &aFrame, bool aIsFramePending) |
| { |
| uint16_t fcf = kFcfFrameAck | aFrame.GetVersion(); |
| |
| mChannel = aFrame.mChannel; |
| memset(&mInfo.mTxInfo, 0, sizeof(mInfo.mTxInfo)); |
| |
| if (aIsFramePending) |
| { |
| fcf |= kFcfFramePending; |
| } |
| WriteUint16(fcf, mPsdu); |
| |
| mPsdu[kSequenceIndex] = aFrame.GetSequence(); |
| |
| mLength = kImmAckLength; |
| } |
| |
| #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 |
| Error TxFrame::GenerateEnhAck(const RxFrame &aFrame, bool aIsFramePending, const uint8_t *aIeData, uint8_t aIeLength) |
| { |
| Error error = kErrorNone; |
| |
| uint16_t fcf = kFcfFrameAck | kFcfFrameVersion2015 | kFcfSrcAddrNone; |
| Address address; |
| PanId panId; |
| uint8_t footerLength; |
| uint8_t securityControlField; |
| uint8_t keyId; |
| |
| mChannel = aFrame.mChannel; |
| memset(&mInfo.mTxInfo, 0, sizeof(mInfo.mTxInfo)); |
| |
| // Set frame control field |
| if (aIsFramePending) |
| { |
| fcf |= kFcfFramePending; |
| } |
| |
| if (aFrame.GetSecurityEnabled()) |
| { |
| fcf |= kFcfSecurityEnabled; |
| } |
| |
| if (aFrame.IsPanIdCompressed()) |
| { |
| fcf |= kFcfPanidCompression; |
| } |
| |
| // Destination address mode |
| if ((aFrame.GetFrameControlField() & kFcfSrcAddrMask) == kFcfSrcAddrExt) |
| { |
| fcf |= kFcfDstAddrExt; |
| } |
| else if ((aFrame.GetFrameControlField() & kFcfSrcAddrMask) == kFcfSrcAddrShort) |
| { |
| fcf |= kFcfDstAddrShort; |
| } |
| else |
| { |
| fcf |= kFcfDstAddrNone; |
| } |
| |
| if (aIeLength > 0) |
| { |
| fcf |= kFcfIePresent; |
| } |
| |
| WriteUint16(fcf, mPsdu); |
| |
| // Set sequence number |
| mPsdu[kSequenceIndex] = aFrame.GetSequence(); |
| |
| if (IsDstPanIdPresent()) |
| { |
| // Set address field |
| if (aFrame.IsSrcPanIdPresent()) |
| { |
| SuccessOrExit(error = aFrame.GetSrcPanId(panId)); |
| } |
| else if (aFrame.IsDstPanIdPresent()) |
| { |
| SuccessOrExit(error = aFrame.GetDstPanId(panId)); |
| } |
| else |
| { |
| ExitNow(error = kErrorParse); |
| } |
| |
| SetDstPanId(panId); |
| } |
| |
| if (aFrame.IsSrcAddrPresent()) |
| { |
| SuccessOrExit(error = aFrame.GetSrcAddr(address)); |
| SetDstAddr(address); |
| } |
| |
| // At this time the length of ACK hasn't been determined, set it to |
| // `kMaxPsduSize` to call methods that check frame length |
| mLength = kMaxPsduSize; |
| |
| // Set security header |
| if (aFrame.GetSecurityEnabled()) |
| { |
| SuccessOrExit(error = aFrame.GetSecurityControlField(securityControlField)); |
| SuccessOrExit(error = aFrame.GetKeyId(keyId)); |
| |
| SetSecurityControlField(securityControlField); |
| SetKeyId(keyId); |
| } |
| |
| // Set header IE |
| if (aIeLength > 0) |
| { |
| OT_ASSERT(aIeData != nullptr); |
| memcpy(&mPsdu[FindHeaderIeIndex()], aIeData, aIeLength); |
| } |
| |
| // Set frame length |
| footerLength = GetFooterLength(); |
| OT_ASSERT(footerLength != kInvalidIndex); |
| mLength = SkipSecurityHeaderIndex() + aIeLength + footerLength; |
| |
| exit: |
| return error; |
| } |
| #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 |
| |
| Error RxFrame::ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const KeyMaterial &aMacKey) |
| { |
| #if OPENTHREAD_RADIO |
| OT_UNUSED_VARIABLE(aExtAddress); |
| OT_UNUSED_VARIABLE(aMacKey); |
| |
| return kErrorNone; |
| #else |
| Error error = kErrorSecurity; |
| uint32_t frameCounter = 0; |
| uint8_t securityLevel; |
| uint8_t nonce[Crypto::AesCcm::kNonceSize]; |
| uint8_t tag[kMaxMicSize]; |
| uint8_t tagLength; |
| Crypto::AesCcm aesCcm; |
| |
| VerifyOrExit(GetSecurityEnabled(), error = kErrorNone); |
| |
| SuccessOrExit(GetSecurityLevel(securityLevel)); |
| SuccessOrExit(GetFrameCounter(frameCounter)); |
| |
| Crypto::AesCcm::GenerateNonce(aExtAddress, frameCounter, securityLevel, nonce); |
| |
| aesCcm.SetKey(aMacKey); |
| tagLength = GetFooterLength() - GetFcsSize(); |
| |
| aesCcm.Init(GetHeaderLength(), GetPayloadLength(), tagLength, nonce, sizeof(nonce)); |
| aesCcm.Header(GetHeader(), GetHeaderLength()); |
| #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| aesCcm.Payload(GetPayload(), GetPayload(), GetPayloadLength(), Crypto::AesCcm::kDecrypt); |
| #else |
| // For fuzz tests, execute AES but do not alter the payload |
| uint8_t fuzz[OT_RADIO_FRAME_MAX_SIZE]; |
| aesCcm.Payload(fuzz, GetPayload(), GetPayloadLength(), Crypto::AesCcm::kDecrypt); |
| #endif |
| aesCcm.Finalize(tag); |
| |
| #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| VerifyOrExit(memcmp(tag, GetFooter(), tagLength) == 0); |
| #endif |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| #endif // OPENTHREAD_RADIO |
| } |
| |
| // LCOV_EXCL_START |
| |
| #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE) |
| |
| Frame::InfoString Frame::ToInfoString(void) const |
| { |
| InfoString string; |
| uint8_t commandId, type; |
| Address src, dst; |
| |
| string.Append("len:%d, seqnum:%d, type:", mLength, GetSequence()); |
| |
| type = GetType(); |
| |
| switch (type) |
| { |
| case kFcfFrameBeacon: |
| string.Append("Beacon"); |
| break; |
| |
| case kFcfFrameData: |
| string.Append("Data"); |
| break; |
| |
| case kFcfFrameAck: |
| string.Append("Ack"); |
| break; |
| |
| case kFcfFrameMacCmd: |
| if (GetCommandId(commandId) != kErrorNone) |
| { |
| commandId = 0xff; |
| } |
| |
| switch (commandId) |
| { |
| case kMacCmdDataRequest: |
| string.Append("Cmd(DataReq)"); |
| break; |
| |
| case kMacCmdBeaconRequest: |
| string.Append("Cmd(BeaconReq)"); |
| break; |
| |
| default: |
| string.Append("Cmd(%d)", commandId); |
| break; |
| } |
| |
| break; |
| |
| default: |
| string.Append("%d", type); |
| break; |
| } |
| |
| IgnoreError(GetSrcAddr(src)); |
| IgnoreError(GetDstAddr(dst)); |
| |
| string.Append(", src:%s, dst:%s, sec:%s, ackreq:%s", src.ToString().AsCString(), dst.ToString().AsCString(), |
| ToYesNo(GetSecurityEnabled()), ToYesNo(GetAckRequest())); |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| string.Append(", radio:%s", RadioTypeToString(GetRadioType())); |
| #endif |
| |
| return string; |
| } |
| |
| #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE) |
| |
| // LCOV_EXCL_STOP |
| |
| } // namespace Mac |
| } // namespace ot |