From 473af53155ec336d843e083cd6b70a7046560caa Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 28 Nov 2024 08:58:19 -0800 Subject: [PATCH] [udp] add `kNetifThreadInternal` which disallows platform UDP use (#10965) This commit introduces `kNetifThreadInternal` as a network interface option for UDP sockets. Unlike other options, this disallows the use of platform UDP for the socket, indicating that the socket should use the OpenThread internal Thread network interface only. This model replaces the previous approach where `ShouldUsePlatformUdp()` would check the socket port against a set of port numbers used by different modules (such as MLE, TMF, Joiner Router, etc) to determine whether platform UDP APIs should be used. With the new model, each module decides whether to associate its socket with the `kNetifThreadInternal`. This is a more flexible and extensible model, ensuring that sockets that should not use the platform do not waste resources (they will not be created or opened/closed on the platform). This also help avoid edge cases where platform UDP operations may unintentionally fail when the platform socket is not actually needed. --- include/openthread/instance.h | 2 +- include/openthread/udp.h | 3 +- src/cli/cli_udp.cpp | 2 +- src/core/api/udp_api.cpp | 2 +- src/core/meshcop/joiner_router.cpp | 2 +- src/core/net/dhcp6_client.cpp | 2 +- src/core/net/dhcp6_server.cpp | 2 +- src/core/net/dnssd_server.cpp | 2 +- src/core/net/srp_client.cpp | 2 +- src/core/net/srp_server.cpp | 2 +- src/core/net/udp6.cpp | 117 +++++++++++++++-------------- src/core/net/udp6.hpp | 33 ++++++-- src/core/thread/mle.cpp | 2 +- src/core/thread/tmf.cpp | 2 +- src/posix/platform/udp.cpp | 14 +++- tests/unit/test_srp_server.cpp | 2 +- 16 files changed, 113 insertions(+), 78 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 6608d3ae9..d277ec8d9 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (464) +#define OPENTHREAD_API_VERSION (465) /** * @addtogroup api-instance diff --git a/include/openthread/udp.h b/include/openthread/udp.h index 903b803cf..7c36bf049 100644 --- a/include/openthread/udp.h +++ b/include/openthread/udp.h @@ -115,7 +115,8 @@ typedef void (*otUdpReceive)(void *aContext, otMessage *aMessage, const otMessag typedef enum otNetifIdentifier { OT_NETIF_UNSPECIFIED = 0, ///< Unspecified network interface. - OT_NETIF_THREAD, ///< The Thread interface. + OT_NETIF_THREAD_HOST, ///< The host Thread interface - allow use of platform UDP. + OT_NETIF_THREAD_INTERNAL, ///< The internal Thread interface (within OpenThread) - do not use platform UDP. OT_NETIF_BACKBONE, ///< The Backbone interface. } otNetifIdentifier; diff --git a/src/cli/cli_udp.cpp b/src/cli/cli_udp.cpp index 257b188ab..aba4b2db6 100644 --- a/src/cli/cli_udp.cpp +++ b/src/cli/cli_udp.cpp @@ -83,7 +83,7 @@ template <> otError UdpExample::Process(Arg aArgs[]) { otError error; otSockAddr sockaddr; - otNetifIdentifier netif = OT_NETIF_THREAD; + otNetifIdentifier netif = OT_NETIF_THREAD_HOST; if (aArgs[0] == "-u") { diff --git a/src/core/api/udp_api.cpp b/src/core/api/udp_api.cpp index 008a402f1..d6096ad11 100644 --- a/src/core/api/udp_api.cpp +++ b/src/core/api/udp_api.cpp @@ -44,7 +44,7 @@ otMessage *otUdpNewMessage(otInstance *aInstance, const otMessageSettings *aSett otError otUdpOpen(otInstance *aInstance, otUdpSocket *aSocket, otUdpReceive aCallback, void *aContext) { - return AsCoreType(aInstance).Get().Open(AsCoreType(aSocket), Ip6::kNetifThread, aCallback, aContext); + return AsCoreType(aInstance).Get().Open(AsCoreType(aSocket), Ip6::kNetifThreadHost, aCallback, aContext); } bool otUdpIsOpen(otInstance *aInstance, const otUdpSocket *aSocket) diff --git a/src/core/meshcop/joiner_router.cpp b/src/core/meshcop/joiner_router.cpp index 7c793d5be..3c29e8e42 100644 --- a/src/core/meshcop/joiner_router.cpp +++ b/src/core/meshcop/joiner_router.cpp @@ -69,7 +69,7 @@ void JoinerRouter::Start(void) VerifyOrExit(!mSocket.IsBound()); - IgnoreError(mSocket.Open(Ip6::kNetifThread)); + IgnoreError(mSocket.Open(Ip6::kNetifThreadInternal)); IgnoreError(mSocket.Bind(port)); IgnoreError(Get().AddUnsecurePort(port)); LogInfo("Joiner Router: start"); diff --git a/src/core/net/dhcp6_client.cpp b/src/core/net/dhcp6_client.cpp index d6d71afbf..b8b99e6e5 100644 --- a/src/core/net/dhcp6_client.cpp +++ b/src/core/net/dhcp6_client.cpp @@ -162,7 +162,7 @@ void Client::Start(void) { VerifyOrExit(!mSocket.IsBound()); - IgnoreError(mSocket.Open(Ip6::kNetifThread)); + IgnoreError(mSocket.Open(Ip6::kNetifThreadInternal)); IgnoreError(mSocket.Bind(kDhcpClientPort)); ProcessNextIdentityAssociation(); diff --git a/src/core/net/dhcp6_server.cpp b/src/core/net/dhcp6_server.cpp index c240e2730..7e9c07e47 100644 --- a/src/core/net/dhcp6_server.cpp +++ b/src/core/net/dhcp6_server.cpp @@ -130,7 +130,7 @@ void Server::Start(void) { VerifyOrExit(!mSocket.IsOpen()); - IgnoreError(mSocket.Open(Ip6::kNetifThread)); + IgnoreError(mSocket.Open(Ip6::kNetifThreadInternal)); IgnoreError(mSocket.Bind(kDhcpServerPort)); exit: diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index d22976cb1..ff3176aa5 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -71,7 +71,7 @@ Error Server::Start(void) VerifyOrExit(!IsRunning()); - SuccessOrExit(error = mSocket.Open(kBindUnspecifiedNetif ? Ip6::kNetifUnspecified : Ip6::kNetifThread)); + SuccessOrExit(error = mSocket.Open(kBindUnspecifiedNetif ? Ip6::kNetifUnspecified : Ip6::kNetifThreadInternal)); SuccessOrExit(error = mSocket.Bind(kPort)); #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index 105905a3e..c6fe993da 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -400,7 +400,7 @@ Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester) VerifyOrExit(GetState() == kStateStopped, error = (aServerSockAddr == GetServerAddress()) ? kErrorNone : kErrorBusy); - SuccessOrExit(error = mSocket.Open(Ip6::kNetifThread)); + SuccessOrExit(error = mSocket.Open(Ip6::kNetifThreadInternal)); error = mSocket.Connect(aServerSockAddr); diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index 5a329834d..f34fc963c 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -658,7 +658,7 @@ Error Server::PrepareSocket(void) #endif VerifyOrExit(!mSocket.IsOpen()); - SuccessOrExit(error = mSocket.Open(Ip6::kNetifThread)); + SuccessOrExit(error = mSocket.Open(Ip6::kNetifThreadHost)); error = mSocket.Bind(mPort); exit: diff --git a/src/core/net/udp6.cpp b/src/core/net/udp6.cpp index 9d8bd0c56..4295217ea 100644 --- a/src/core/net/udp6.cpp +++ b/src/core/net/udp6.cpp @@ -119,7 +119,7 @@ Error Udp::Socket::JoinNetifMulticastGroup(NetifIdentifier aNetifIdentifier, con VerifyOrExit(aAddress.IsMulticast(), error = kErrorInvalidArgs); #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - error = otPlatUdpJoinMulticastGroup(this, MapEnum(aNetifIdentifier), &aAddress); + error = Plat::JoinMulticastGroup(*this, aNetifIdentifier, aAddress); #endif exit: @@ -136,7 +136,7 @@ Error Udp::Socket::LeaveNetifMulticastGroup(NetifIdentifier aNetifIdentifier, co VerifyOrExit(aAddress.IsMulticast(), error = kErrorInvalidArgs); #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - error = otPlatUdpLeaveMulticastGroup(this, MapEnum(aNetifIdentifier), &aAddress); + error = Plat::LeaveMulticastGroup(*this, aNetifIdentifier, aAddress); #endif exit: @@ -144,6 +144,57 @@ exit: } #endif +//--------------------------------------------------------------------------------------------------------------------- +// Udp::Plat + +#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE + +Error Udp::Plat::Open(SocketHandle &aSocket) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpSocket(&aSocket) : kErrorNone; +} + +Error Udp::Plat::Close(SocketHandle &aSocket) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpClose(&aSocket) : kErrorNone; +} + +Error Udp::Plat::Bind(SocketHandle &aSocket) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpBind(&aSocket) : kErrorNone; +} + +Error Udp::Plat::BindToNetif(SocketHandle &aSocket) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpBindToNetif(&aSocket, MapEnum(aSocket.GetNetifId())) : kErrorNone; +} + +Error Udp::Plat::Connect(SocketHandle &aSocket) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpConnect(&aSocket) : kErrorNone; +} + +Error Udp::Plat::Send(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo) +{ + OT_ASSERT(aSocket.ShouldUsePlatformUdp()); + + return otPlatUdpSend(&aSocket, &aMessage, &aMessageInfo); +} + +Error Udp::Plat::JoinMulticastGroup(SocketHandle &aSocket, NetifIdentifier aNetifId, const Address &aAddress) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpJoinMulticastGroup(&aSocket, MapEnum(aNetifId), &aAddress) + : kErrorNone; +} + +Error Udp::Plat::LeaveMulticastGroup(SocketHandle &aSocket, NetifIdentifier aNetifId, const Address &aAddress) +{ + return aSocket.ShouldUsePlatformUdp() ? otPlatUdpLeaveMulticastGroup(&aSocket, MapEnum(aNetifId), &aAddress) + : kErrorNone; +} + +#endif // OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // Udp @@ -178,7 +229,7 @@ Error Udp::Open(SocketHandle &aSocket, NetifIdentifier aNetifId, ReceiveHandler aSocket.mContext = aContext; #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - error = otPlatUdpSocket(&aSocket); + error = Plat::Open(aSocket); #endif SuccessOrExit(error); @@ -193,7 +244,7 @@ Error Udp::Bind(SocketHandle &aSocket, const SockAddr &aSockAddr) Error error = kErrorNone; #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - SuccessOrExit(error = otPlatUdpBindToNetif(&aSocket, MapEnum(aSocket.GetNetifId()))); + SuccessOrExit(error = Plat::BindToNetif(aSocket)); #endif VerifyOrExit(aSockAddr.GetAddress().IsUnspecified() || Get().HasUnicastAddress(aSockAddr.GetAddress()), @@ -207,14 +258,14 @@ Error Udp::Bind(SocketHandle &aSocket, const SockAddr &aSockAddr) { aSocket.mSockName.mPort = GetEphemeralPort(); #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - error = otPlatUdpBind(&aSocket); + error = Plat::Bind(aSocket); #endif } while (error != kErrorNone); } #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - else if (ShouldUsePlatformUdp(aSocket)) + else { - error = otPlatUdpBind(&aSocket); + error = Plat::Bind(aSocket); } #endif @@ -234,10 +285,7 @@ Error Udp::Connect(SocketHandle &aSocket, const SockAddr &aSockAddr) } #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - if (ShouldUsePlatformUdp(aSocket)) - { - error = otPlatUdpConnect(&aSocket); - } + error = Plat::Connect(aSocket); #endif exit: @@ -251,9 +299,8 @@ Error Udp::Close(SocketHandle &aSocket) VerifyOrExit(IsOpen(aSocket)); #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - error = otPlatUdpClose(&aSocket); + SuccessOrExit(error = Plat::Close(aSocket)); #endif - SuccessOrExit(error); RemoveSocket(aSocket); aSocket.GetSockName().Clear(); @@ -299,9 +346,9 @@ Error Udp::SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &a messageInfoLocal.SetSockPort(aSocket.GetSockName().mPort); #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - if (ShouldUsePlatformUdp(aSocket)) + if (aSocket.ShouldUsePlatformUdp()) { - SuccessOrExit(error = otPlatUdpSend(&aSocket, &aMessage, &messageInfoLocal)); + SuccessOrExit(error = Plat::Send(aSocket, aMessage, messageInfoLocal)); } else #endif @@ -447,45 +494,5 @@ bool Udp::IsPortInUse(uint16_t aPort) const return found; } -#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - -bool Udp::ShouldUsePlatformUdp(uint16_t aPort) const -{ - bool shouldUse = false; - - VerifyOrExit(aPort != Mle::kUdpPort); - VerifyOrExit(aPort != Tmf::kUdpPort); -#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE && !OPENTHREAD_CONFIG_DNSSD_SERVER_BIND_UNSPECIFIED_NETIF - VerifyOrExit(aPort != Dns::ServiceDiscovery::Server::kPort); -#endif -#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE - VerifyOrExit(aPort != Get().GetUdpProxyPort()); -#endif -#if OPENTHREAD_FTD - VerifyOrExit(aPort != Get().GetJoinerUdpPort()); -#endif -#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE - VerifyOrExit(aPort != Dhcp6::kDhcpServerPort); -#endif -#if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE - VerifyOrExit(aPort != Dhcp6::kDhcpClientPort); -#endif - - shouldUse = true; - -exit: - return shouldUse; -} - -bool Udp::ShouldUsePlatformUdp(const Udp::SocketHandle &aSocket) const -{ - return (ShouldUsePlatformUdp(aSocket.mSockName.mPort) -#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE - || aSocket.IsBackbone() -#endif - ); -} -#endif // OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - } // namespace Ip6 } // namespace ot diff --git a/src/core/net/udp6.hpp b/src/core/net/udp6.hpp index c9563196a..0a5d8ecc2 100644 --- a/src/core/net/udp6.hpp +++ b/src/core/net/udp6.hpp @@ -70,9 +70,10 @@ class Udp; */ enum NetifIdentifier : uint8_t { - kNetifUnspecified = OT_NETIF_UNSPECIFIED, ///< Unspecified network interface. - kNetifThread = OT_NETIF_THREAD, ///< The Thread interface. - kNetifBackbone = OT_NETIF_BACKBONE, ///< The Backbone interface. + kNetifUnspecified = OT_NETIF_UNSPECIFIED, ///< Unspecified network interface. + kNetifThreadHost = OT_NETIF_THREAD_HOST, ///< The host Thread interface - allow use of platform UDP. + kNetifThreadInternal = OT_NETIF_THREAD_INTERNAL, ///< The internal Thread interface - do not use platform UDP. + kNetifBackbone = OT_NETIF_BACKBONE, ///< The Backbone interface. }; /** @@ -142,6 +143,14 @@ public: */ void SetNetifId(NetifIdentifier aNetifId) { mNetifId = static_cast(aNetifId); } + /** + * Indicates whether or not the socket can use platform UDP. + * + * @retval TRUE This socket should use platform UDP. + * @retval FALSE This socket is associated with the internal Thread interface and should not use platform UDP. + */ + bool ShouldUsePlatformUdp(void) const { return GetNetifId() != kNetifThreadInternal; } + #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE /** * Indicate whether or not the socket is bound to the backbone network interface. @@ -649,14 +658,24 @@ private: static constexpr uint16_t kSrpServerPortMin = OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT_MIN; static constexpr uint16_t kSrpServerPortMax = OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT_MAX; +#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE + struct Plat + { + static Error Open(SocketHandle &aSocket); + static Error Close(SocketHandle &aSocket); + static Error Bind(SocketHandle &aSocket); + static Error BindToNetif(SocketHandle &aSocket); + static Error Connect(SocketHandle &aSocket); + static Error Send(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo); + static Error JoinMulticastGroup(SocketHandle &aSocket, NetifIdentifier aNetifId, const Address &aAddress); + static Error LeaveMulticastGroup(SocketHandle &aSocket, NetifIdentifier aNetifId, const Address &aAddress); + }; +#endif + static bool IsPortReserved(uint16_t aPort); void AddSocket(SocketHandle &aSocket); void RemoveSocket(SocketHandle &aSocket); -#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE - bool ShouldUsePlatformUdp(uint16_t aPort) const; - bool ShouldUsePlatformUdp(const SocketHandle &aSocket) const; -#endif uint16_t mEphemeralPort; LinkedList mReceivers; diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index e3d184f04..7285587c7 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -127,7 +127,7 @@ Error Mle::Enable(void) Error error = kErrorNone; UpdateLinkLocalAddress(); - SuccessOrExit(error = mSocket.Open(Ip6::kNetifThread)); + SuccessOrExit(error = mSocket.Open(Ip6::kNetifThreadInternal)); SuccessOrExit(error = mSocket.Bind(kUdpPort)); #if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE diff --git a/src/core/thread/tmf.cpp b/src/core/thread/tmf.cpp index 2f470a3db..e8662fba6 100644 --- a/src/core/thread/tmf.cpp +++ b/src/core/thread/tmf.cpp @@ -83,7 +83,7 @@ Agent::Agent(Instance &aInstance) SetResourceHandler(&HandleResource); } -Error Agent::Start(void) { return Coap::Start(kUdpPort, Ip6::kNetifThread); } +Error Agent::Start(void) { return Coap::Start(kUdpPort, Ip6::kNetifThreadInternal); } template <> void Agent::HandleTmf(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { diff --git a/src/posix/platform/udp.cpp b/src/posix/platform/udp.cpp index 89c6d3d8e..ebd2009d5 100644 --- a/src/posix/platform/udp.cpp +++ b/src/posix/platform/udp.cpp @@ -305,7 +305,7 @@ otError otPlatUdpBindToNetif(otUdpSocket *aUdpSocket, otNetifIdentifier aNetifId #endif // __linux__ break; } - case OT_NETIF_THREAD: + case OT_NETIF_THREAD_HOST: { #ifdef __linux__ VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &gNetifName, strlen(gNetifName)) == 0, @@ -341,6 +341,9 @@ otError otPlatUdpBindToNetif(otUdpSocket *aUdpSocket, otNetifIdentifier aNetifId break; } + + case OT_NETIF_THREAD_INTERNAL: + assert(false); } VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)) == 0, error = OT_ERROR_FAILED); @@ -471,7 +474,7 @@ otError otPlatUdpJoinMulticastGroup(otUdpSocket *aUdpSocket, { case OT_NETIF_UNSPECIFIED: break; - case OT_NETIF_THREAD: + case OT_NETIF_THREAD_HOST: mreq.ipv6mr_interface = gNetifIndex; break; case OT_NETIF_BACKBONE: @@ -481,6 +484,8 @@ otError otPlatUdpJoinMulticastGroup(otUdpSocket *aUdpSocket, ExitNow(error = OT_ERROR_NOT_IMPLEMENTED); #endif break; + case OT_NETIF_THREAD_INTERNAL: + assert(false); } VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == 0 || errno == EADDRINUSE, @@ -512,7 +517,7 @@ otError otPlatUdpLeaveMulticastGroup(otUdpSocket *aUdpSocket, { case OT_NETIF_UNSPECIFIED: break; - case OT_NETIF_THREAD: + case OT_NETIF_THREAD_HOST: mreq.ipv6mr_interface = gNetifIndex; break; case OT_NETIF_BACKBONE: @@ -522,6 +527,9 @@ otError otPlatUdpLeaveMulticastGroup(otUdpSocket *aUdpSocket, ExitNow(error = OT_ERROR_NOT_IMPLEMENTED); #endif break; + + case OT_NETIF_THREAD_INTERNAL: + assert(false); } VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq, sizeof(mreq)) == 0 || errno == EADDRINUSE, diff --git a/tests/unit/test_srp_server.cpp b/tests/unit/test_srp_server.cpp index ec7fa9abf..333da4a1a 100644 --- a/tests/unit/test_srp_server.cpp +++ b/tests/unit/test_srp_server.cpp @@ -1076,7 +1076,7 @@ void TestSrpClientDelayedResponse(void) sServerRxCount = 0; - SuccessOrQuit(udpSocket.Open(Ip6::kNetifThread)); + SuccessOrQuit(udpSocket.Open(Ip6::kNetifThreadHost)); SuccessOrQuit(udpSocket.Bind(kServerPort)); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -