blob: 1fb3984fe0d6e146b1236658d3f577c71b134f4d [file] [log] [blame]
/*
* Copyright (c) 2018, 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 the BorderAgent service.
*/
#include "border_agent.hpp"
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
#include "coap/coap_message.hpp"
#include "common/as_core_type.hpp"
#include "common/heap.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "meshcop/meshcop.hpp"
#include "meshcop/meshcop_tlvs.hpp"
#include "thread/thread_netif.hpp"
#include "thread/thread_tlvs.hpp"
#include "thread/uri_paths.hpp"
namespace ot {
namespace MeshCoP {
RegisterLogModule("BorderAgent");
namespace {
constexpr uint16_t kBorderAgentUdpPort = OPENTHREAD_CONFIG_BORDER_AGENT_UDP_PORT; ///< UDP port of border agent service.
}
void BorderAgent::ForwardContext::Init(Instance & aInstance,
const Coap::Message &aMessage,
bool aPetition,
bool aSeparate)
{
InstanceLocatorInit::Init(aInstance);
mMessageId = aMessage.GetMessageId();
mPetition = aPetition;
mSeparate = aSeparate;
mType = aMessage.GetType();
mTokenLength = aMessage.GetTokenLength();
memcpy(mToken, aMessage.GetToken(), mTokenLength);
}
Error BorderAgent::ForwardContext::ToHeader(Coap::Message &aMessage, uint8_t aCode)
{
if ((mType == Coap::kTypeNonConfirmable) || mSeparate)
{
aMessage.Init(Coap::kTypeNonConfirmable, static_cast<Coap::Code>(aCode));
}
else
{
aMessage.Init(Coap::kTypeAck, static_cast<Coap::Code>(aCode));
}
if (!mSeparate)
{
aMessage.SetMessageId(mMessageId);
}
return aMessage.SetToken(mToken, mTokenLength);
}
Coap::Message::Code BorderAgent::CoapCodeFromError(Error aError)
{
Coap::Message::Code code;
switch (aError)
{
case kErrorNone:
code = Coap::kCodeChanged;
break;
case kErrorParse:
code = Coap::kCodeBadRequest;
break;
default:
code = Coap::kCodeInternalError;
break;
}
return code;
}
void BorderAgent::SendErrorMessage(ForwardContext &aForwardContext, Error aError)
{
Error error = kErrorNone;
Coap::CoapSecure &coaps = Get<Coap::CoapSecure>();
Coap::Message * message = nullptr;
VerifyOrExit((message = coaps.NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = aForwardContext.ToHeader(*message, CoapCodeFromError(aError)));
SuccessOrExit(error = coaps.SendMessage(*message, coaps.GetMessageInfo()));
exit:
FreeMessageOnError(message, error);
LogError("send error CoAP message", error);
}
void BorderAgent::SendErrorMessage(const Coap::Message &aRequest, bool aSeparate, Error aError)
{
Error error = kErrorNone;
Coap::CoapSecure &coaps = Get<Coap::CoapSecure>();
Coap::Message * message = nullptr;
VerifyOrExit((message = coaps.NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
if (aRequest.IsNonConfirmable() || aSeparate)
{
message->Init(Coap::kTypeNonConfirmable, CoapCodeFromError(aError));
}
else
{
message->Init(Coap::kTypeAck, CoapCodeFromError(aError));
}
if (!aSeparate)
{
message->SetMessageId(aRequest.GetMessageId());
}
SuccessOrExit(error = message->SetTokenFromMessage(aRequest));
SuccessOrExit(error = coaps.SendMessage(*message, coaps.GetMessageInfo()));
exit:
FreeMessageOnError(message, error);
LogError("send error CoAP message", error);
}
void BorderAgent::HandleCoapResponse(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
Error aResult)
{
OT_UNUSED_VARIABLE(aMessageInfo);
ForwardContext &forwardContext = *static_cast<ForwardContext *>(aContext);
forwardContext.Get<BorderAgent>().HandleCoapResponse(forwardContext, AsCoapMessagePtr(aMessage), aResult);
}
void BorderAgent::HandleCoapResponse(ForwardContext &aForwardContext, const Coap::Message *aResponse, Error aResult)
{
Coap::Message *message = nullptr;
Error error;
SuccessOrExit(error = aResult);
VerifyOrExit((message = Get<Coap::CoapSecure>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
if (aForwardContext.IsPetition() && aResponse->GetCode() == Coap::kCodeChanged)
{
uint8_t state;
SuccessOrExit(error = Tlv::Find<StateTlv>(*aResponse, state));
if (state == StateTlv::kAccept)
{
uint16_t sessionId;
SuccessOrExit(error = Tlv::Find<CommissionerSessionIdTlv>(*aResponse, sessionId));
IgnoreError(Get<Mle::MleRouter>().GetCommissionerAloc(mCommissionerAloc.GetAddress(), sessionId));
Get<ThreadNetif>().AddUnicastAddress(mCommissionerAloc);
IgnoreError(Get<Ip6::Udp>().AddReceiver(mUdpReceiver));
LogInfo("commissioner accepted: session ID=%d, ALOC=%s", sessionId,
mCommissionerAloc.GetAddress().ToString().AsCString());
}
}
SuccessOrExit(error = aForwardContext.ToHeader(*message, aResponse->GetCode()));
if (aResponse->GetLength() > aResponse->GetOffset())
{
SuccessOrExit(error = message->SetPayloadMarker());
}
SuccessOrExit(error = ForwardToCommissioner(*message, *aResponse));
exit:
if (error != kErrorNone)
{
FreeMessage(message);
LogWarn("Commissioner request[%hu] failed: %s", aForwardContext.GetMessageId(), ErrorToString(error));
SendErrorMessage(aForwardContext, error);
}
Heap::Free(&aForwardContext);
}
template <Coap::Resource BorderAgent::*aResource>
void BorderAgent::HandleRequest(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
IgnoreError(static_cast<BorderAgent *>(aContext)->ForwardToLeader(
AsCoapMessage(aMessage), AsCoreType(aMessageInfo),
(static_cast<BorderAgent *>(aContext)->*aResource).GetUriPath(), false, false));
}
template <>
void BorderAgent::HandleRequest<&BorderAgent::mCommissionerPetition>(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo)
{
IgnoreError(static_cast<BorderAgent *>(aContext)->ForwardToLeader(AsCoapMessage(aMessage), AsCoreType(aMessageInfo),
UriPath::kLeaderPetition, true, true));
}
template <>
void BorderAgent::HandleRequest<&BorderAgent::mCommissionerKeepAlive>(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo)
{
static_cast<BorderAgent *>(aContext)->HandleKeepAlive(AsCoapMessage(aMessage), AsCoreType(aMessageInfo));
}
template <>
void BorderAgent::HandleRequest<&BorderAgent::mRelayTransmit>(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo)
{
OT_UNUSED_VARIABLE(aMessageInfo);
static_cast<BorderAgent *>(aContext)->HandleRelayTransmit(AsCoapMessage(aMessage));
}
template <>
void BorderAgent::HandleRequest<&BorderAgent::mRelayReceive>(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo)
{
OT_UNUSED_VARIABLE(aMessageInfo);
static_cast<BorderAgent *>(aContext)->HandleRelayReceive(AsCoapMessage(aMessage));
}
template <>
void BorderAgent::HandleRequest<&BorderAgent::mProxyTransmit>(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo)
{
OT_UNUSED_VARIABLE(aMessageInfo);
static_cast<BorderAgent *>(aContext)->HandleProxyTransmit(AsCoapMessage(aMessage));
}
BorderAgent::BorderAgent(Instance &aInstance)
: InstanceLocator(aInstance)
, mCommissionerPetition(UriPath::kCommissionerPetition,
BorderAgent::HandleRequest<&BorderAgent::mCommissionerPetition>,
this)
, mCommissionerKeepAlive(UriPath::kCommissionerKeepAlive,
BorderAgent::HandleRequest<&BorderAgent::mCommissionerKeepAlive>,
this)
, mRelayTransmit(UriPath::kRelayTx, BorderAgent::HandleRequest<&BorderAgent::mRelayTransmit>, this)
, mRelayReceive(UriPath::kRelayRx, BorderAgent::HandleRequest<&BorderAgent::mRelayReceive>, this)
, mCommissionerGet(UriPath::kCommissionerGet, BorderAgent::HandleRequest<&BorderAgent::mCommissionerGet>, this)
, mCommissionerSet(UriPath::kCommissionerSet, BorderAgent::HandleRequest<&BorderAgent::mCommissionerSet>, this)
, mActiveGet(UriPath::kActiveGet, BorderAgent::HandleRequest<&BorderAgent::mActiveGet>, this)
, mActiveSet(UriPath::kActiveSet, BorderAgent::HandleRequest<&BorderAgent::mActiveSet>, this)
, mPendingGet(UriPath::kPendingGet, BorderAgent::HandleRequest<&BorderAgent::mPendingGet>, this)
, mPendingSet(UriPath::kPendingSet, BorderAgent::HandleRequest<&BorderAgent::mPendingSet>, this)
, mProxyTransmit(UriPath::kProxyTx, BorderAgent::HandleRequest<&BorderAgent::mProxyTransmit>, this)
, mUdpReceiver(BorderAgent::HandleUdpReceive, this)
, mTimer(aInstance, HandleTimeout)
, mState(kStateStopped)
, mUdpProxyPort(0)
{
mCommissionerAloc.InitAsThreadOriginRealmLocalScope();
}
void BorderAgent::HandleNotifierEvents(Events aEvents)
{
VerifyOrExit(aEvents.ContainsAny(kEventThreadRoleChanged | kEventCommissionerStateChanged));
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
VerifyOrExit(Get<Commissioner>().IsDisabled());
#endif
if (Get<Mle::MleRouter>().IsAttached())
{
Start();
}
else
{
Stop();
}
exit:
return;
}
void BorderAgent::HandleProxyTransmit(const Coap::Message &aMessage)
{
Message * message = nullptr;
Ip6::MessageInfo messageInfo;
uint16_t offset;
Error error;
UdpEncapsulationTlv tlv;
SuccessOrExit(error = Tlv::FindTlvOffset(aMessage, Tlv::kUdpEncapsulation, offset));
SuccessOrExit(error = aMessage.Read(offset, tlv));
VerifyOrExit((message = Get<Ip6::Udp>().NewMessage(0)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->SetLength(tlv.GetUdpLength()));
aMessage.CopyTo(offset + sizeof(tlv), 0, tlv.GetUdpLength(), *message);
VerifyOrExit(tlv.GetSourcePort() > 0 && tlv.GetDestinationPort() > 0, error = kErrorDrop);
messageInfo.SetSockPort(tlv.GetSourcePort());
messageInfo.SetSockAddr(mCommissionerAloc.GetAddress());
messageInfo.SetPeerPort(tlv.GetDestinationPort());
SuccessOrExit(error = Tlv::Find<Ip6AddressTlv>(aMessage, messageInfo.GetPeerAddr()));
SuccessOrExit(error = Get<Ip6::Udp>().SendDatagram(*message, messageInfo, Ip6::kProtoUdp));
mUdpProxyPort = tlv.GetSourcePort();
LogInfo("Proxy transmit sent to %s", messageInfo.GetPeerAddr().ToString().AsCString());
exit:
FreeMessageOnError(message, error);
LogError("send proxy stream", error);
}
bool BorderAgent::HandleUdpReceive(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error;
Coap::Message *message = nullptr;
VerifyOrExit(aMessageInfo.GetSockAddr() == mCommissionerAloc.GetAddress(),
error = kErrorDestinationAddressFiltered);
VerifyOrExit(aMessage.GetLength() > 0, error = kErrorNone);
message = Get<Coap::CoapSecure>().NewPriorityNonConfirmablePostMessage(UriPath::kProxyRx);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
{
UdpEncapsulationTlv tlv;
uint16_t offset;
uint16_t udpLength = aMessage.GetLength() - aMessage.GetOffset();
tlv.Init();
tlv.SetSourcePort(aMessageInfo.GetPeerPort());
tlv.SetDestinationPort(aMessageInfo.GetSockPort());
tlv.SetUdpLength(udpLength);
SuccessOrExit(error = message->Append(tlv));
offset = message->GetLength();
SuccessOrExit(error = message->SetLength(offset + udpLength));
aMessage.CopyTo(aMessage.GetOffset(), offset, udpLength, *message);
}
SuccessOrExit(error = Tlv::Append<Ip6AddressTlv>(*message, aMessageInfo.GetPeerAddr()));
SuccessOrExit(error = Get<Coap::CoapSecure>().SendMessage(*message, Get<Coap::CoapSecure>().GetMessageInfo()));
LogInfo("Sent to commissioner on %s", UriPath::kProxyRx);
exit:
FreeMessageOnError(message, error);
LogError("notify commissioner on ProxyRx (c/ur)", error);
return error != kErrorDestinationAddressFiltered;
}
void BorderAgent::HandleRelayReceive(const Coap::Message &aMessage)
{
Coap::Message *message = nullptr;
Error error;
VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = kErrorDrop);
message = Get<Coap::CoapSecure>().NewPriorityNonConfirmablePostMessage(UriPath::kRelayRx);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = ForwardToCommissioner(*message, aMessage));
LogInfo("Sent to commissioner on %s", UriPath::kRelayRx);
exit:
FreeMessageOnError(message, error);
}
Error BorderAgent::ForwardToCommissioner(Coap::Message &aForwardMessage, const Message &aMessage)
{
Error error = kErrorNone;
uint16_t offset = 0;
offset = aForwardMessage.GetLength();
SuccessOrExit(error = aForwardMessage.SetLength(offset + aMessage.GetLength() - aMessage.GetOffset()));
aMessage.CopyTo(aMessage.GetOffset(), offset, aMessage.GetLength() - aMessage.GetOffset(), aForwardMessage);
SuccessOrExit(error =
Get<Coap::CoapSecure>().SendMessage(aForwardMessage, Get<Coap::CoapSecure>().GetMessageInfo()));
LogInfo("Sent to commissioner");
exit:
LogError("send to commissioner", error);
return error;
}
void BorderAgent::HandleKeepAlive(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error;
error = ForwardToLeader(aMessage, aMessageInfo, UriPath::kLeaderKeepAlive, false, true);
if (error == kErrorNone)
{
mTimer.Start(kKeepAliveTimeout);
}
}
void BorderAgent::HandleRelayTransmit(const Coap::Message &aMessage)
{
Error error = kErrorNone;
uint16_t joinerRouterRloc;
Coap::Message * message = nullptr;
Tmf::MessageInfo messageInfo(GetInstance());
uint16_t offset = 0;
VerifyOrExit(aMessage.IsNonConfirmablePostRequest());
SuccessOrExit(error = Tlv::Find<JoinerRouterLocatorTlv>(aMessage, joinerRouterRloc));
message = Get<Tmf::Agent>().NewPriorityNonConfirmablePostMessage(UriPath::kRelayTx);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
offset = message->GetLength();
SuccessOrExit(error = message->SetLength(offset + aMessage.GetLength() - aMessage.GetOffset()));
aMessage.CopyTo(aMessage.GetOffset(), offset, aMessage.GetLength() - aMessage.GetOffset(), *message);
messageInfo.SetSockAddrToRlocPeerAddrTo(joinerRouterRloc);
messageInfo.SetSockPortToTmf();
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo));
LogInfo("Sent to joiner router request on %s", UriPath::kRelayTx);
exit:
FreeMessageOnError(message, error);
LogError("send to joiner router request RelayTx (c/tx)", error);
}
Error BorderAgent::ForwardToLeader(const Coap::Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const char * aPath,
bool aPetition,
bool aSeparate)
{
Error error = kErrorNone;
ForwardContext * forwardContext = nullptr;
Tmf::MessageInfo messageInfo(GetInstance());
Coap::Message * message = nullptr;
uint16_t offset = 0;
if (aSeparate)
{
SuccessOrExit(error = Get<Coap::CoapSecure>().SendAck(aMessage, aMessageInfo));
}
forwardContext = static_cast<ForwardContext *>(Heap::CAlloc(1, sizeof(ForwardContext)));
VerifyOrExit(forwardContext != nullptr, error = kErrorNoBufs);
forwardContext->Init(GetInstance(), aMessage, aPetition, aSeparate);
message = Get<Tmf::Agent>().NewPriorityConfirmablePostMessage(aPath);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
offset = message->GetLength();
SuccessOrExit(error = message->SetLength(offset + aMessage.GetLength() - aMessage.GetOffset()));
aMessage.CopyTo(aMessage.GetOffset(), offset, aMessage.GetLength() - aMessage.GetOffset(), *message);
SuccessOrExit(error = messageInfo.SetSockAddrToRlocPeerAddrToLeaderAloc());
messageInfo.SetSockPortToTmf();
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo, HandleCoapResponse, forwardContext));
// HandleCoapResponse is responsible to free this forward context.
forwardContext = nullptr;
LogInfo("Forwarded request to leader on %s", aPath);
exit:
LogError("forward to leader", error);
if (error != kErrorNone)
{
if (forwardContext != nullptr)
{
Heap::Free(forwardContext);
}
FreeMessage(message);
SendErrorMessage(aMessage, aSeparate, error);
}
return error;
}
void BorderAgent::HandleConnected(bool aConnected, void *aContext)
{
static_cast<BorderAgent *>(aContext)->HandleConnected(aConnected);
}
void BorderAgent::HandleConnected(bool aConnected)
{
if (aConnected)
{
LogInfo("Commissioner connected");
mState = kStateActive;
mTimer.Start(kKeepAliveTimeout);
}
else
{
LogInfo("Commissioner disconnected");
IgnoreError(Get<Ip6::Udp>().RemoveReceiver(mUdpReceiver));
Get<ThreadNetif>().RemoveUnicastAddress(mCommissionerAloc);
mState = kStateStarted;
mUdpProxyPort = 0;
}
}
uint16_t BorderAgent::GetUdpPort(void) const
{
return Get<Coap::CoapSecure>().GetUdpPort();
}
void BorderAgent::Start(void)
{
Error error;
Coap::CoapSecure &coaps = Get<Coap::CoapSecure>();
Pskc pskc;
VerifyOrExit(mState == kStateStopped, error = kErrorNone);
Get<KeyManager>().GetPskc(pskc);
SuccessOrExit(error = coaps.Start(kBorderAgentUdpPort));
SuccessOrExit(error = coaps.SetPsk(pskc.m8, Pskc::kSize));
pskc.Clear();
coaps.SetConnectedCallback(HandleConnected, this);
coaps.AddResource(mActiveGet);
coaps.AddResource(mActiveSet);
coaps.AddResource(mPendingGet);
coaps.AddResource(mPendingSet);
coaps.AddResource(mCommissionerPetition);
coaps.AddResource(mCommissionerKeepAlive);
coaps.AddResource(mCommissionerSet);
coaps.AddResource(mCommissionerGet);
coaps.AddResource(mProxyTransmit);
coaps.AddResource(mRelayTransmit);
Get<Tmf::Agent>().AddResource(mRelayReceive);
mState = kStateStarted;
mUdpProxyPort = 0;
LogInfo("Border Agent start listening on port %d", kBorderAgentUdpPort);
exit:
if (error != kErrorNone)
{
LogWarn("failed to start Border Agent on port %d: %s", kBorderAgentUdpPort, ErrorToString(error));
}
}
void BorderAgent::HandleTimeout(Timer &aTimer)
{
aTimer.Get<BorderAgent>().HandleTimeout();
}
void BorderAgent::HandleTimeout(void)
{
if (Get<Coap::CoapSecure>().IsConnected())
{
Get<Coap::CoapSecure>().Disconnect();
LogWarn("Reset commissioner session");
}
}
void BorderAgent::Stop(void)
{
Coap::CoapSecure &coaps = Get<Coap::CoapSecure>();
VerifyOrExit(mState != kStateStopped);
mTimer.Stop();
coaps.RemoveResource(mCommissionerPetition);
coaps.RemoveResource(mCommissionerKeepAlive);
coaps.RemoveResource(mCommissionerSet);
coaps.RemoveResource(mCommissionerGet);
coaps.RemoveResource(mActiveGet);
coaps.RemoveResource(mActiveSet);
coaps.RemoveResource(mPendingGet);
coaps.RemoveResource(mPendingSet);
coaps.RemoveResource(mProxyTransmit);
coaps.RemoveResource(mRelayTransmit);
Get<Tmf::Agent>().RemoveResource(mRelayReceive);
coaps.Stop();
mState = kStateStopped;
mUdpProxyPort = 0;
LogInfo("Border Agent stopped");
exit:
return;
}
void BorderAgent::ApplyMeshLocalPrefix(void)
{
VerifyOrExit(mState == kStateActive);
if (Get<ThreadNetif>().HasUnicastAddress(mCommissionerAloc))
{
Get<ThreadNetif>().RemoveUnicastAddress(mCommissionerAloc);
mCommissionerAloc.GetAddress().SetPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix());
Get<ThreadNetif>().AddUnicastAddress(mCommissionerAloc);
}
exit:
return;
}
} // namespace MeshCoP
} // namespace ot
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE