| /* |
| * Copyright (c) 2019, 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 Thread Radio Encapsulation Link (TREL). |
| */ |
| |
| #include "trel_link.hpp" |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/instance.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| |
| namespace ot { |
| namespace Trel { |
| |
| RegisterLogModule("TrelLink"); |
| |
| Link::Link(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mState(kStateDisabled) |
| , mRxChannel(0) |
| , mPanId(Mac::kPanIdBroadcast) |
| , mTxPacketNumber(0) |
| , mTxTasklet(aInstance, HandleTxTasklet) |
| , mTimer(aInstance, HandleTimer) |
| , mInterface(aInstance) |
| { |
| memset(&mTxFrame, 0, sizeof(mTxFrame)); |
| memset(&mRxFrame, 0, sizeof(mRxFrame)); |
| memset(mAckFrameBuffer, 0, sizeof(mAckFrameBuffer)); |
| |
| mTxFrame.mPsdu = &mTxPacketBuffer[kMaxHeaderSize]; |
| mTxFrame.SetLength(0); |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| mTxFrame.SetRadioType(Mac::kRadioTypeTrel); |
| mRxFrame.SetRadioType(Mac::kRadioTypeTrel); |
| #endif |
| |
| mTimer.Start(kAckWaitWindow); |
| } |
| |
| void Link::AfterInit(void) |
| { |
| mInterface.Init(); |
| } |
| |
| void Link::Enable(void) |
| { |
| mInterface.Enable(); |
| |
| if (mState == kStateDisabled) |
| { |
| SetState(kStateSleep); |
| } |
| } |
| |
| void Link::Disable(void) |
| { |
| mInterface.Disable(); |
| |
| if (mState != kStateDisabled) |
| { |
| SetState(kStateDisabled); |
| } |
| } |
| |
| void Link::Sleep(void) |
| { |
| OT_ASSERT(mState != kStateDisabled); |
| SetState(kStateSleep); |
| } |
| |
| void Link::Receive(uint8_t aChannel) |
| { |
| OT_ASSERT(mState != kStateDisabled); |
| mRxChannel = aChannel; |
| SetState(kStateReceive); |
| } |
| |
| void Link::Send(void) |
| { |
| OT_ASSERT(mState != kStateDisabled); |
| |
| SetState(kStateTransmit); |
| mTxTasklet.Post(); |
| } |
| |
| void Link::HandleTxTasklet(Tasklet &aTasklet) |
| { |
| aTasklet.Get<Link>().HandleTxTasklet(); |
| } |
| |
| void Link::HandleTxTasklet(void) |
| { |
| BeginTransmit(); |
| } |
| |
| void Link::BeginTransmit(void) |
| { |
| Mac::Address destAddr; |
| Mac::PanId destPanId; |
| Header::Type type; |
| Packet txPacket; |
| Neighbor * neighbor = nullptr; |
| Mac::RxFrame *ackFrame = nullptr; |
| bool isDisovery = false; |
| |
| VerifyOrExit(mState == kStateTransmit); |
| |
| // After sending a frame on a given channel we should |
| // continue to rx on same channel |
| mRxChannel = mTxFrame.GetChannel(); |
| |
| VerifyOrExit(!mTxFrame.IsEmpty(), InvokeSendDone(kErrorAbort)); |
| |
| IgnoreError(mTxFrame.GetDstAddr(destAddr)); |
| |
| if (destAddr.IsNone() || destAddr.IsBroadcast()) |
| { |
| type = Header::kTypeBroadcast; |
| } |
| else |
| { |
| type = Header::kTypeUnicast; |
| neighbor = Get<NeighborTable>().FindNeighbor(destAddr, Neighbor::kInStateAnyExceptInvalid); |
| |
| if (destAddr.IsShort()) |
| { |
| if (neighbor != nullptr) |
| { |
| destAddr.SetExtended(neighbor->GetExtAddress()); |
| } |
| else |
| { |
| // Send as a broadcast since we don't know the dest |
| // ext address to include in the packet header. |
| type = Header::kTypeBroadcast; |
| } |
| } |
| } |
| |
| if (type == Header::kTypeBroadcast) |
| { |
| // Thread utilizes broadcast transmissions to discover |
| // neighboring devices. We determine whether this broadcast |
| // frame tx is a discovery or normal data. All messages |
| // used for discovery either disable MAC security or utilize |
| // MAC Key ID mode 2. All data communication uses MAC Key ID |
| // Mode 1. |
| |
| if (!mTxFrame.GetSecurityEnabled()) |
| { |
| isDisovery = true; |
| } |
| else |
| { |
| uint8_t keyIdMode; |
| |
| IgnoreError(mTxFrame.GetKeyIdMode(keyIdMode)); |
| isDisovery = (keyIdMode == Mac::Frame::kKeyIdMode2); |
| } |
| } |
| |
| if (mTxFrame.GetDstPanId(destPanId) != kErrorNone) |
| { |
| destPanId = Mac::kPanIdBroadcast; |
| } |
| |
| txPacket.Init(type, mTxFrame.GetPsdu(), mTxFrame.GetLength()); |
| |
| if (neighbor == nullptr) |
| { |
| txPacket.GetHeader().SetAckMode(Header::kNoAck); |
| txPacket.GetHeader().SetPacketNumber(mTxPacketNumber++); |
| } |
| else |
| { |
| txPacket.GetHeader().SetAckMode(Header::kAckRequested); |
| txPacket.GetHeader().SetPacketNumber(neighbor->mTrelTxPacketNumber++); |
| neighbor->mTrelCurrentPendingAcks++; |
| } |
| |
| txPacket.GetHeader().SetChannel(mTxFrame.GetChannel()); |
| txPacket.GetHeader().SetPanId(destPanId); |
| txPacket.GetHeader().SetSource(Get<Mac::Mac>().GetExtAddress()); |
| |
| if (type == Header::kTypeUnicast) |
| { |
| OT_ASSERT(destAddr.IsExtended()); |
| txPacket.GetHeader().SetDestination(destAddr.GetExtended()); |
| } |
| |
| LogDebg("BeginTransmit() [%s] plen:%d", txPacket.GetHeader().ToString().AsCString(), txPacket.GetPayloadLength()); |
| |
| VerifyOrExit(mInterface.Send(txPacket, isDisovery) == kErrorNone, InvokeSendDone(kErrorAbort)); |
| |
| if (mTxFrame.GetAckRequest()) |
| { |
| uint16_t fcf = Mac::Frame::kFcfFrameAck; |
| |
| if (!Get<Mle::MleRouter>().IsRxOnWhenIdle()) |
| { |
| fcf |= Mac::Frame::kFcfFramePending; |
| } |
| |
| // Prepare the ack frame (FCF followed by sequence number) |
| Encoding::LittleEndian::WriteUint16(fcf, mAckFrameBuffer); |
| mAckFrameBuffer[sizeof(fcf)] = mTxFrame.GetSequence(); |
| |
| mRxFrame.mPsdu = mAckFrameBuffer; |
| mRxFrame.mLength = k154AckFrameSize; |
| mRxFrame.mChannel = mTxFrame.GetChannel(); |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| mRxFrame.mRadioType = Mac::kRadioTypeTrel; |
| #endif |
| mRxFrame.mInfo.mRxInfo.mTimestamp = 0; |
| mRxFrame.mInfo.mRxInfo.mRssi = OT_RADIO_RSSI_INVALID; |
| mRxFrame.mInfo.mRxInfo.mLqi = OT_RADIO_LQI_NONE; |
| mRxFrame.mInfo.mRxInfo.mAckedWithFramePending = false; |
| |
| ackFrame = &mRxFrame; |
| } |
| |
| InvokeSendDone(kErrorNone, ackFrame); |
| |
| exit: |
| return; |
| } |
| |
| void Link::InvokeSendDone(Error aError, Mac::RxFrame *aAckFrame) |
| { |
| SetState(kStateReceive); |
| |
| Get<Mac::Mac>().RecordFrameTransmitStatus(mTxFrame, aAckFrame, aError, /* aRetryCount */ 0, /* aWillRetx */ false); |
| Get<Mac::Mac>().HandleTransmitDone(mTxFrame, aAckFrame, aError); |
| } |
| |
| void Link::HandleTimer(Timer &aTimer) |
| { |
| aTimer.Get<Link>().HandleTimer(); |
| } |
| |
| void Link::HandleTimer(void) |
| { |
| mTimer.Start(kAckWaitWindow); |
| |
| #if OPENTHREAD_FTD |
| for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid)) |
| { |
| HandleTimer(child); |
| } |
| |
| for (Router &router : Get<RouterTable>().Iterate()) |
| { |
| HandleTimer(router); |
| } |
| #endif |
| |
| // Parent and ParentCandidate should also be updated as neighbors. |
| // Parent is considered only when the device is detached or child. |
| // When device becomes a router/leader the parent entry is copied |
| // into the router table but the MLE parent may still stay in |
| // valid state. Note that "Parent Candidate" may be in use on a |
| // router/leader during a partition merge, so it is always treated |
| // as a neighbor. |
| |
| switch (Get<Mle::MleRouter>().GetRole()) |
| { |
| case Mle::kRoleDisabled: |
| break; |
| |
| case Mle::kRoleDetached: |
| case Mle::kRoleChild: |
| HandleTimer(Get<Mle::MleRouter>().GetParent()); |
| |
| OT_FALL_THROUGH; |
| |
| case Mle::kRoleRouter: |
| case Mle::kRoleLeader: |
| HandleTimer(Get<Mle::MleRouter>().GetParentCandidate()); |
| break; |
| } |
| } |
| |
| void Link::HandleTimer(Neighbor &aNeighbor) |
| { |
| VerifyOrExit(!aNeighbor.IsStateInvalid()); |
| |
| // Any remaining previous pending ack has timed out. |
| |
| while (aNeighbor.mTrelPreviousPendingAcks > 0) |
| { |
| aNeighbor.mTrelPreviousPendingAcks--; |
| |
| ReportDeferredAckStatus(aNeighbor, kErrorNoAck); |
| VerifyOrExit(!aNeighbor.IsStateInvalid()); |
| } |
| |
| aNeighbor.mTrelPreviousPendingAcks = aNeighbor.mTrelCurrentPendingAcks; |
| aNeighbor.mTrelCurrentPendingAcks = 0; |
| |
| exit: |
| return; |
| } |
| |
| void Link::ProcessReceivedPacket(Packet &aPacket) |
| { |
| Header::Type type; |
| |
| VerifyOrExit(aPacket.IsHeaderValid()); |
| |
| type = aPacket.GetHeader().GetType(); |
| |
| if (type != Header::kTypeAck) |
| { |
| // No need to check state or channel for a TREL ack packet. |
| // Note that TREL ack may be received much later than the tx |
| // and device can be on a different rx channel. |
| |
| VerifyOrExit((mState == kStateReceive) || (mState == kStateTransmit)); |
| VerifyOrExit(aPacket.GetHeader().GetChannel() == mRxChannel); |
| } |
| |
| if (mPanId != Mac::kPanIdBroadcast) |
| { |
| Mac::PanId rxPanId = aPacket.GetHeader().GetPanId(); |
| |
| VerifyOrExit((rxPanId == mPanId) || (rxPanId == Mac::kPanIdBroadcast)); |
| } |
| |
| // Drop packets originating from same device. |
| VerifyOrExit(aPacket.GetHeader().GetSource() != Get<Mac::Mac>().GetExtAddress()); |
| |
| if (type != Header::kTypeBroadcast) |
| { |
| VerifyOrExit(aPacket.GetHeader().GetDestination() == Get<Mac::Mac>().GetExtAddress()); |
| |
| if (type == Header::kTypeAck) |
| { |
| HandleAck(aPacket); |
| ExitNow(); |
| } |
| } |
| |
| LogDebg("ReceivedPacket() [%s] plen:%d", aPacket.GetHeader().ToString().AsCString(), aPacket.GetPayloadLength()); |
| |
| if (aPacket.GetHeader().GetAckMode() == Header::kAckRequested) |
| { |
| SendAck(aPacket); |
| } |
| |
| mRxFrame.mPsdu = aPacket.GetPayload(); |
| mRxFrame.mLength = aPacket.GetPayloadLength(); |
| mRxFrame.mChannel = aPacket.GetHeader().GetChannel(); |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| mRxFrame.mRadioType = Mac::kRadioTypeTrel; |
| #endif |
| mRxFrame.mInfo.mRxInfo.mTimestamp = 0; |
| mRxFrame.mInfo.mRxInfo.mRssi = kRxRssi; |
| mRxFrame.mInfo.mRxInfo.mLqi = OT_RADIO_LQI_NONE; |
| mRxFrame.mInfo.mRxInfo.mAckedWithFramePending = true; |
| |
| Get<Mac::Mac>().HandleReceivedFrame(&mRxFrame, kErrorNone); |
| |
| exit: |
| return; |
| } |
| |
| void Link::HandleAck(Packet &aAckPacket) |
| { |
| Error ackError; |
| Mac::Address srcAddress; |
| Neighbor * neighbor; |
| uint32_t ackNumber; |
| |
| LogDebg("HandleAck() [%s]", aAckPacket.GetHeader().ToString().AsCString()); |
| |
| srcAddress.SetExtended(aAckPacket.GetHeader().GetSource()); |
| neighbor = Get<NeighborTable>().FindNeighbor(srcAddress, Neighbor::kInStateAnyExceptInvalid); |
| VerifyOrExit(neighbor != nullptr); |
| |
| ackNumber = aAckPacket.GetHeader().GetPacketNumber(); |
| |
| // Verify that neighbor is waiting for acks and the received ack |
| // number is within the range of expected ack numbers. |
| |
| VerifyOrExit(neighbor->IsRxAckNumberValid(ackNumber)); |
| |
| do |
| { |
| // Check whether the received ack number matches the next |
| // expected one. If it does not, it indicates that some of |
| // packets missed their acks. |
| |
| ackError = (ackNumber == neighbor->GetExpectedTrelAckNumber()) ? kErrorNone : kErrorNoAck; |
| |
| neighbor->DecrementPendingTrelAckCount(); |
| |
| ReportDeferredAckStatus(*neighbor, ackError); |
| VerifyOrExit(!neighbor->IsStateInvalid()); |
| |
| } while (ackError == kErrorNoAck); |
| |
| exit: |
| return; |
| } |
| |
| void Link::SendAck(Packet &aRxPacket) |
| { |
| Packet ackPacket; |
| |
| ackPacket.Init(mAckPacketBuffer, sizeof(mAckPacketBuffer)); |
| |
| ackPacket.GetHeader().Init(Header::kTypeAck); |
| ackPacket.GetHeader().SetAckMode(Header::kNoAck); |
| ackPacket.GetHeader().SetChannel(aRxPacket.GetHeader().GetChannel()); |
| ackPacket.GetHeader().SetPanId(aRxPacket.GetHeader().GetPanId()); |
| ackPacket.GetHeader().SetPacketNumber(aRxPacket.GetHeader().GetPacketNumber()); |
| ackPacket.GetHeader().SetSource(Get<Mac::Mac>().GetExtAddress()); |
| ackPacket.GetHeader().SetDestination(aRxPacket.GetHeader().GetSource()); |
| |
| LogDebg("SendAck [%s]", ackPacket.GetHeader().ToString().AsCString()); |
| |
| IgnoreError(mInterface.Send(ackPacket)); |
| } |
| |
| void Link::ReportDeferredAckStatus(Neighbor &aNeighbor, Error aError) |
| { |
| LogDebg("ReportDeferredAckStatus(): %s for %s", aNeighbor.GetExtAddress().ToString().AsCString(), |
| ErrorToString(aError)); |
| |
| Get<MeshForwarder>().HandleDeferredAck(aNeighbor, aError); |
| } |
| |
| void Link::SetState(State aState) |
| { |
| if (mState != aState) |
| { |
| LogDebg("State: %s -> %s", StateToString(mState), StateToString(aState)); |
| mState = aState; |
| } |
| } |
| |
| void Link::HandleNotifierEvents(Events aEvents) |
| { |
| if (aEvents.Contains(kEventThreadExtPanIdChanged)) |
| { |
| mInterface.HandleExtPanIdChange(); |
| } |
| } |
| |
| // LCOV_EXCL_START |
| |
| const char *Link::StateToString(State aState) |
| { |
| static const char *const kStateStrings[] = { |
| "Disabled", // (0) kStateDisabled |
| "Sleep", // (1) kStateSleep |
| "Receive", // (2) kStateReceive |
| "Transmit", // (3) kStateTransmit |
| }; |
| |
| static_assert(0 == kStateDisabled, "kStateDisabled value is incorrect"); |
| static_assert(1 == kStateSleep, "kStateSleep value is incorrect"); |
| static_assert(2 == kStateReceive, "kStateReceive value is incorrect"); |
| static_assert(3 == kStateTransmit, "kStateTransmit value is incorrect"); |
| |
| return kStateStrings[aState]; |
| } |
| |
| // LCOV_EXCL_STOP |
| |
| } // namespace Trel |
| } // namespace ot |
| |
| #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |