/* * 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) interface. */ #include "trel_interface.hpp" #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE #include #include "common/array.hpp" #include "common/as_core_type.hpp" #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/instance.hpp" #include "common/locator_getters.hpp" #include "common/log.hpp" #include "common/string.hpp" #include "net/dns_types.hpp" namespace ot { namespace Trel { RegisterLogModule("TrelInterface"); const char Interface::kTxtRecordExtAddressKey[] = "xa"; const char Interface::kTxtRecordExtPanIdKey[] = "xp"; Interface::Interface(Instance &aInstance) : InstanceLocator(aInstance) , mInitialized(false) , mEnabled(false) , mFiltered(false) , mRegisterServiceTask(aInstance, HandleRegisterServiceTask) { } void Interface::Init(void) { OT_ASSERT(!mInitialized); mInitialized = true; if (mEnabled) { mEnabled = false; Enable(); } } void Interface::Enable(void) { VerifyOrExit(!mEnabled); mEnabled = true; VerifyOrExit(mInitialized); otPlatTrelEnable(&GetInstance(), &mUdpPort); LogInfo("Enabled interface, local port:%u", mUdpPort); mRegisterServiceTask.Post(); exit: return; } void Interface::Disable(void) { VerifyOrExit(mEnabled); mEnabled = false; VerifyOrExit(mInitialized); otPlatTrelDisable(&GetInstance()); mPeerTable.Clear(); LogDebg("Disabled interface"); exit: return; } void Interface::HandleExtAddressChange(void) { VerifyOrExit(mInitialized && mEnabled); LogDebg("Extended Address changed, re-registering DNS-SD service"); mRegisterServiceTask.Post(); exit: return; } void Interface::HandleExtPanIdChange(void) { VerifyOrExit(mInitialized && mEnabled); LogDebg("Extended PAN ID changed, re-registering DNS-SD service"); mRegisterServiceTask.Post(); exit: return; } void Interface::HandleRegisterServiceTask(Tasklet &aTasklet) { aTasklet.Get().RegisterService(); } void Interface::RegisterService(void) { // TXT data consists of two entries: the length fields, the // "key" string, "=" char, and binary representation of the MAC // or Extended PAN ID values. static constexpr uint8_t kTxtDataSize = /* ExtAddr */ sizeof(uint8_t) + sizeof(kTxtRecordExtAddressKey) - 1 + sizeof(char) + sizeof(Mac::ExtAddress) + /* ExtPanId */ sizeof(uint8_t) + sizeof(kTxtRecordExtPanIdKey) - 1 + sizeof(char) + sizeof(MeshCoP::ExtendedPanId); uint8_t txtDataBuffer[kTxtDataSize]; MutableData txtData; Dns::TxtEntry txtEntries[2]; VerifyOrExit(mInitialized && mEnabled); txtEntries[0].Init(kTxtRecordExtAddressKey, Get().GetExtAddress().m8, sizeof(Mac::ExtAddress)); txtEntries[1].Init(kTxtRecordExtPanIdKey, Get().GetExtPanId().m8, sizeof(MeshCoP::ExtendedPanId)); txtData.Init(txtDataBuffer, sizeof(txtDataBuffer)); SuccessOrAssert(Dns::TxtEntry::AppendEntries(txtEntries, GetArrayLength(txtEntries), txtData)); LogInfo("Registering DNS-SD service: port:%u, txt:\"%s=%s, %s=%s\"", mUdpPort, kTxtRecordExtAddressKey, Get().GetExtAddress().ToString().AsCString(), kTxtRecordExtPanIdKey, Get().GetExtPanId().ToString().AsCString()); otPlatTrelRegisterService(&GetInstance(), mUdpPort, txtData.GetBytes(), static_cast(txtData.GetLength())); exit: return; } extern "C" void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo) { Instance &instance = AsCoreType(aInstance); VerifyOrExit(instance.IsInitialized()); instance.Get().HandleDiscoveredPeerInfo(*static_cast(aInfo)); exit: return; } void Interface::HandleDiscoveredPeerInfo(const Peer::Info &aInfo) { Peer * entry; Mac::ExtAddress extAddress; MeshCoP::ExtendedPanId extPanId; bool isNew = false; VerifyOrExit(mInitialized && mEnabled); SuccessOrExit(ParsePeerInfoTxtData(aInfo, extAddress, extPanId)); VerifyOrExit(extAddress != Get().GetExtAddress()); if (aInfo.IsRemoved()) { entry = mPeerTable.FindMatching(extAddress); VerifyOrExit(entry != nullptr); RemovePeerEntry(*entry); ExitNow(); } // It is a new entry or an update to an existing entry. First // check whether we have an existing entry that matches the same // socket address, and remove it if it is associated with a // different Extended MAC address. This ensures that we do not // keep stale entries in the peer table. entry = mPeerTable.FindMatching(aInfo.GetSockAddr()); if ((entry != nullptr) && !entry->Matches(extAddress)) { RemovePeerEntry(*entry); entry = nullptr; } if (entry == nullptr) { entry = mPeerTable.FindMatching(extAddress); } if (entry == nullptr) { entry = GetNewPeerEntry(); VerifyOrExit(entry != nullptr); entry->SetExtAddress(extAddress); isNew = true; } if (!isNew) { VerifyOrExit((entry->GetExtPanId() != extPanId) || (entry->GetSockAddr() != aInfo.GetSockAddr())); } entry->SetExtPanId(extPanId); entry->SetSockAddr(aInfo.GetSockAddr()); entry->Log(isNew ? "Added" : "Updated"); exit: return; } Error Interface::ParsePeerInfoTxtData(const Peer::Info & aInfo, Mac::ExtAddress & aExtAddress, MeshCoP::ExtendedPanId &aExtPanId) const { Error error; Dns::TxtEntry entry; Dns::TxtEntry::Iterator iterator; bool parsedExtAddress = false; bool parsedExtPanId = false; aExtPanId.Clear(); iterator.Init(aInfo.GetTxtData(), aInfo.GetTxtLength()); while ((error = iterator.GetNextEntry(entry)) == kErrorNone) { if (strcmp(entry.mKey, kTxtRecordExtAddressKey) == 0) { VerifyOrExit(!parsedExtAddress, error = kErrorParse); VerifyOrExit(entry.mValueLength == sizeof(Mac::ExtAddress), error = kErrorParse); aExtAddress.Set(entry.mValue); parsedExtAddress = true; } else if (strcmp(entry.mKey, kTxtRecordExtPanIdKey) == 0) { VerifyOrExit(!parsedExtPanId, error = kErrorParse); VerifyOrExit(entry.mValueLength == sizeof(MeshCoP::ExtendedPanId), error = kErrorParse); memcpy(aExtPanId.m8, entry.mValue, sizeof(MeshCoP::ExtendedPanId)); parsedExtPanId = true; } // Skip over and ignore any unknown keys. } VerifyOrExit(error == kErrorNotFound); error = kErrorNone; VerifyOrExit(parsedExtAddress && parsedExtPanId, error = kErrorParse); exit: return error; } Interface::Peer *Interface::GetNewPeerEntry(void) { Peer *peerEntry; peerEntry = mPeerTable.PushBack(); VerifyOrExit(peerEntry == nullptr); for (Peer &entry : mPeerTable) { if (entry.GetExtPanId() != Get().GetExtPanId()) { ExitNow(peerEntry = &entry); } } for (Peer &entry : mPeerTable) { // We skip over any existing entry in neighbor table (even if the // entry is in invalid state). if (Get().FindNeighbor(entry.GetExtAddress(), Neighbor::kInStateAny) != nullptr) { continue; } #if OPENTHREAD_FTD { Mac::Address macAddress; macAddress.SetExtended(entry.GetExtAddress()); if (Get().FindRxOnlyNeighborRouter(macAddress) != nullptr) { continue; } } #endif ExitNow(peerEntry = &entry); } exit: return peerEntry; } void Interface::RemovePeerEntry(Peer &aEntry) { aEntry.Log("Removing"); // Replace the entry being removed with the last entry (if not the // last one already) and then pop the last entry from array. if (&aEntry != mPeerTable.Back()) { aEntry = *mPeerTable.Back(); } mPeerTable.PopBack(); } Error Interface::Send(const Packet &aPacket, bool aIsDiscovery) { Error error = kErrorNone; Peer *peerEntry; VerifyOrExit(mInitialized && mEnabled, error = kErrorAbort); VerifyOrExit(!mFiltered); switch (aPacket.GetHeader().GetType()) { case Header::kTypeBroadcast: for (Peer &entry : mPeerTable) { if (!aIsDiscovery && (entry.GetExtPanId() != Get().GetExtPanId())) { continue; } otPlatTrelSend(&GetInstance(), aPacket.GetBuffer(), aPacket.GetLength(), &entry.mSockAddr); } break; case Header::kTypeUnicast: case Header::kTypeAck: peerEntry = mPeerTable.FindMatching(aPacket.GetHeader().GetDestination()); VerifyOrExit(peerEntry != nullptr, error = kErrorAbort); otPlatTrelSend(&GetInstance(), aPacket.GetBuffer(), aPacket.GetLength(), &peerEntry->mSockAddr); break; } exit: return error; } extern "C" void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength) { Instance &instance = AsCoreType(aInstance); VerifyOrExit(instance.IsInitialized()); instance.Get().HandleReceived(aBuffer, aLength); exit: return; } void Interface::HandleReceived(uint8_t *aBuffer, uint16_t aLength) { LogDebg("HandleReceived(aLength:%u)", aLength); VerifyOrExit(mInitialized && mEnabled && !mFiltered); mRxPacket.Init(aBuffer, aLength); Get().ProcessReceivedPacket(mRxPacket); exit: return; } const Interface::Peer *Interface::GetNextPeer(PeerIterator &aIterator) const { const Peer *entry = mPeerTable.At(aIterator); if (entry != nullptr) { aIterator++; } return entry; } void Interface::Peer::Log(const char *aAction) const { OT_UNUSED_VARIABLE(aAction); LogInfo("%s peer mac:%s, xpan:%s, %s", aAction, GetExtAddress().ToString().AsCString(), GetExtPanId().ToString().AsCString(), GetSockAddr().ToString().AsCString()); } } // namespace Trel } // namespace ot #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE