blob: 68825e9c4607c7a4cf39565b86b304670c5db24b [file] [log] [blame]
/*
* 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 <string.h>
#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<Interface>().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<kWithUint16Length> txtData;
Dns::TxtEntry txtEntries[2];
VerifyOrExit(mInitialized && mEnabled);
txtEntries[0].Init(kTxtRecordExtAddressKey, Get<Mac::Mac>().GetExtAddress().m8, sizeof(Mac::ExtAddress));
txtEntries[1].Init(kTxtRecordExtPanIdKey, Get<MeshCoP::ExtendedPanIdManager>().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<Mac::Mac>().GetExtAddress().ToString().AsCString(), kTxtRecordExtPanIdKey,
Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId().ToString().AsCString());
otPlatTrelRegisterService(&GetInstance(), mUdpPort, txtData.GetBytes(), static_cast<uint8_t>(txtData.GetLength()));
exit:
return;
}
extern "C" void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo)
{
Instance &instance = AsCoreType(aInstance);
VerifyOrExit(instance.IsInitialized());
instance.Get<Interface>().HandleDiscoveredPeerInfo(*static_cast<const Interface::Peer::Info *>(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<Mac::Mac>().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<MeshCoP::ExtendedPanIdManager>().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<NeighborTable>().FindNeighbor(entry.GetExtAddress(), Neighbor::kInStateAny) != nullptr)
{
continue;
}
#if OPENTHREAD_FTD
{
Mac::Address macAddress;
macAddress.SetExtended(entry.GetExtAddress());
if (Get<NeighborTable>().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<MeshCoP::ExtendedPanIdManager>().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<Interface>().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<Link>().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