/*
 *  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
 * @brief
 *   This file includes the platform UDP driver.
 */

#ifdef __APPLE__
#define __APPLE_USE_RFC_3542
#endif

#include "openthread-posix-config.h"
#include "platform-posix.h"

#include <arpa/inet.h>
#include <assert.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>

#include <openthread/udp.h>
#include <openthread/platform/udp.h>

#include "common/code_utils.hpp"

#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE

#include "posix/platform/ip6_utils.hpp"
#include "posix/platform/mainloop.hpp"
#include "posix/platform/udp.hpp"

using namespace ot::Posix::Ip6Utils;

namespace {

constexpr size_t kMaxUdpSize = 1280;

void *FdToHandle(int aFd) { return reinterpret_cast<void *>(aFd); }

int FdFromHandle(void *aHandle) { return static_cast<int>(reinterpret_cast<long>(aHandle)); }

otError transmitPacket(int aFd, uint8_t *aPayload, uint16_t aLength, const otMessageInfo &aMessageInfo)
{
#ifdef __APPLE__
    // use fixed value for CMSG_SPACE is not a constant expression on macOS
    constexpr size_t kBufferSize = 128;
#else
    constexpr size_t kBufferSize = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int));
#endif
    struct sockaddr_in6 peerAddr;
    uint8_t             control[kBufferSize];
    size_t              controlLength = 0;
    struct iovec        iov;
    struct msghdr       msg;
    struct cmsghdr     *cmsg;
    ssize_t             rval;
    otError             error = OT_ERROR_NONE;

    memset(&peerAddr, 0, sizeof(peerAddr));
    peerAddr.sin6_port   = htons(aMessageInfo.mPeerPort);
    peerAddr.sin6_family = AF_INET6;
    CopyIp6AddressTo(aMessageInfo.mPeerAddr, &peerAddr.sin6_addr);

    if (IsIp6AddressLinkLocal(aMessageInfo.mPeerAddr) && !aMessageInfo.mIsHostInterface)
    {
        // sin6_scope_id only works for link local destinations
        peerAddr.sin6_scope_id = gNetifIndex;
    }

    memset(control, 0, sizeof(control));

    iov.iov_base = aPayload;
    iov.iov_len  = aLength;

    msg.msg_name       = &peerAddr;
    msg.msg_namelen    = sizeof(peerAddr);
    msg.msg_control    = control;
    msg.msg_controllen = static_cast<decltype(msg.msg_controllen)>(sizeof(control));
    msg.msg_iov        = &iov;
    msg.msg_iovlen     = 1;
    msg.msg_flags      = 0;

    {
        int hopLimit = (aMessageInfo.mHopLimit ? aMessageInfo.mHopLimit : OPENTHREAD_CONFIG_IP6_HOP_LIMIT_DEFAULT);

        cmsg             = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_level = IPPROTO_IPV6;
        cmsg->cmsg_type  = IPV6_HOPLIMIT;
        cmsg->cmsg_len   = CMSG_LEN(sizeof(int));

        memcpy(CMSG_DATA(cmsg), &hopLimit, sizeof(int));

        controlLength += CMSG_SPACE(sizeof(int));
    }

    if (!IsIp6AddressMulticast(aMessageInfo.mSockAddr) && !IsIp6AddressUnspecified(aMessageInfo.mSockAddr))
    {
        struct in6_pktinfo pktinfo;

        cmsg             = CMSG_NXTHDR(&msg, cmsg);
        cmsg->cmsg_level = IPPROTO_IPV6;
        cmsg->cmsg_type  = IPV6_PKTINFO;
        cmsg->cmsg_len   = CMSG_LEN(sizeof(pktinfo));

        pktinfo.ipi6_ifindex = aMessageInfo.mIsHostInterface ? 0 : gNetifIndex;

        CopyIp6AddressTo(aMessageInfo.mSockAddr, &pktinfo.ipi6_addr);
        memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));

        controlLength += CMSG_SPACE(sizeof(pktinfo));
    }

#ifdef __APPLE__
    msg.msg_controllen = static_cast<socklen_t>(controlLength);
#else
    msg.msg_controllen           = controlLength;
#endif

    rval = sendmsg(aFd, &msg, 0);
    VerifyOrExit(rval > 0, perror("sendmsg"));

exit:
    // EINVAL happens when we shift from child to router and the
    // interface address changes. Ask callers to try again later.
    if (rval == -1)
    {
        error = (errno == EINVAL) ? OT_ERROR_INVALID_STATE : OT_ERROR_FAILED;
    }

    return error;
}

otError receivePacket(int aFd, uint8_t *aPayload, uint16_t &aLength, otMessageInfo &aMessageInfo)
{
    struct sockaddr_in6 peerAddr;
    uint8_t             control[kMaxUdpSize];
    struct iovec        iov;
    struct msghdr       msg;
    ssize_t             rval;

    iov.iov_base = aPayload;
    iov.iov_len  = aLength;

    msg.msg_name       = &peerAddr;
    msg.msg_namelen    = sizeof(peerAddr);
    msg.msg_control    = control;
    msg.msg_controllen = sizeof(control);
    msg.msg_iov        = &iov;
    msg.msg_iovlen     = 1;
    msg.msg_flags      = 0;

    rval = recvmsg(aFd, &msg, 0);
    VerifyOrExit(rval > 0, perror("recvmsg"));
    aLength = static_cast<uint16_t>(rval);

    for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg))
    {
        if (cmsg->cmsg_level == IPPROTO_IPV6)
        {
            if (cmsg->cmsg_type == IPV6_HOPLIMIT)
            {
                int hoplimit;

                memcpy(&hoplimit, CMSG_DATA(cmsg), sizeof(hoplimit));
                aMessageInfo.mHopLimit = static_cast<uint8_t>(hoplimit);
            }
            else if (cmsg->cmsg_type == IPV6_PKTINFO)
            {
                struct in6_pktinfo pktinfo;

                memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));

                aMessageInfo.mIsHostInterface = (pktinfo.ipi6_ifindex != gNetifIndex);
                ReadIp6AddressFrom(&pktinfo.ipi6_addr, aMessageInfo.mSockAddr);
            }
        }
    }

    aMessageInfo.mPeerPort = ntohs(peerAddr.sin6_port);
    ReadIp6AddressFrom(&peerAddr.sin6_addr, aMessageInfo.mPeerAddr);

exit:
    return rval > 0 ? OT_ERROR_NONE : OT_ERROR_FAILED;
}

} // namespace

otError otPlatUdpSocket(otUdpSocket *aUdpSocket)
{
    otError error = OT_ERROR_NONE;
    int     fd;

    assert(aUdpSocket->mHandle == nullptr);

    fd = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, kSocketNonBlock);
    VerifyOrExit(fd >= 0, error = OT_ERROR_FAILED);

    aUdpSocket->mHandle = FdToHandle(fd);

exit:
    return error;
}

otError otPlatUdpClose(otUdpSocket *aUdpSocket)
{
    otError error = OT_ERROR_NONE;
    int     fd;

    // Only call `close()` on platform UDP sockets.
    // Platform UDP sockets always have valid `mHandle` upon creation.
    VerifyOrExit(aUdpSocket->mHandle != nullptr);

    fd = FdFromHandle(aUdpSocket->mHandle);
    VerifyOrExit(0 == close(fd), error = OT_ERROR_FAILED);

    aUdpSocket->mHandle = nullptr;

exit:
    return error;
}

otError otPlatUdpBind(otUdpSocket *aUdpSocket)
{
    otError error = OT_ERROR_NONE;
    int     fd;

    assert(gNetifIndex != 0);
    assert(aUdpSocket->mHandle != nullptr);
    VerifyOrExit(aUdpSocket->mSockName.mPort != 0, error = OT_ERROR_INVALID_ARGS);
    fd = FdFromHandle(aUdpSocket->mHandle);

    {
        struct sockaddr_in6 sin6;

        memset(&sin6, 0, sizeof(struct sockaddr_in6));
        sin6.sin6_port   = htons(aUdpSocket->mSockName.mPort);
        sin6.sin6_family = AF_INET6;
        CopyIp6AddressTo(aUdpSocket->mSockName.mAddress, &sin6.sin6_addr);

        VerifyOrExit(0 == bind(fd, reinterpret_cast<struct sockaddr *>(&sin6), sizeof(sin6)), error = OT_ERROR_FAILED);
    }

    {
        int on = 1;
        VerifyOrExit(0 == setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)), error = OT_ERROR_FAILED);
        VerifyOrExit(0 == setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)), error = OT_ERROR_FAILED);
    }

exit:
    if (error == OT_ERROR_FAILED)
    {
        ot::Posix::Udp::LogCrit("Failed to bind UDP socket: %s", strerror(errno));
    }

    return error;
}

otError otPlatUdpBindToNetif(otUdpSocket *aUdpSocket, otNetifIdentifier aNetifIdentifier)
{
    otError error = OT_ERROR_NONE;
    int     fd    = FdFromHandle(aUdpSocket->mHandle);
    int     one   = 1;
    int     zero  = 0;

    switch (aNetifIdentifier)
    {
    case OT_NETIF_UNSPECIFIED:
    {
#ifdef __linux__
        VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, nullptr, 0) == 0, error = OT_ERROR_FAILED);
#else  // __NetBSD__ || __FreeBSD__ || __APPLE__
        unsigned int netifIndex = 0;
        VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &netifIndex, sizeof(netifIndex)) == 0,
                               error = OT_ERROR_FAILED);
#endif // __linux__
        break;
    }
    case OT_NETIF_THREAD:
    {
#ifdef __linux__
        VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &gNetifName, strlen(gNetifName)) == 0,
                     error = OT_ERROR_FAILED);
#else  // __NetBSD__ || __FreeBSD__ || __APPLE__
        VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &gNetifIndex, sizeof(gNetifIndex)) == 0,
                               error = OT_ERROR_FAILED);
#endif // __linux__
        break;
    }
    case OT_NETIF_BACKBONE:
    {
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
        if (otSysGetInfraNetifName() == nullptr || otSysGetInfraNetifName()[0] == '\0')
        {
            ot::Posix::Udp::LogWarn("No backbone interface given, %s fails.", __func__);
            ExitNow(error = OT_ERROR_INVALID_ARGS);
        }
#ifdef __linux__
        VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, otSysGetInfraNetifName(),
                                strlen(otSysGetInfraNetifName())) == 0,
                     error = OT_ERROR_FAILED);
#else  // __NetBSD__ || __FreeBSD__ || __APPLE__
        uint32_t backboneNetifIndex = otSysGetInfraNetifIndex();
        VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &backboneNetifIndex, sizeof(backboneNetifIndex)) == 0,
                     error = OT_ERROR_FAILED);
#endif // __linux__
#else
        ExitNow(error = OT_ERROR_NOT_IMPLEMENTED);
#endif
        VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&one, sizeof(one)) == 0,
                     error = OT_ERROR_FAILED);

        break;
    }
    }

    VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)) == 0, error = OT_ERROR_FAILED);

exit:
    return error;
}

otError otPlatUdpConnect(otUdpSocket *aUdpSocket)
{
    otError             error = OT_ERROR_NONE;
    struct sockaddr_in6 sin6;
    int                 fd;
    bool isDisconnect = IsIp6AddressUnspecified(aUdpSocket->mPeerName.mAddress) && (aUdpSocket->mPeerName.mPort == 0);

    VerifyOrExit(aUdpSocket->mHandle != nullptr, error = OT_ERROR_INVALID_ARGS);

    fd = FdFromHandle(aUdpSocket->mHandle);

    memset(&sin6, 0, sizeof(struct sockaddr_in6));
    sin6.sin6_port = htons(aUdpSocket->mPeerName.mPort);

    if (!isDisconnect)
    {
        sin6.sin6_family = AF_INET6;
        CopyIp6AddressTo(aUdpSocket->mPeerName.mAddress, &sin6.sin6_addr);
    }
    else
    {
#ifdef __APPLE__
        sin6.sin6_family = AF_UNSPEC;
#else
        char      netifName[IFNAMSIZ];
        socklen_t len = sizeof(netifName);

        if (getsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &netifName, &len) != 0)
        {
                      ot::Posix::Udp::LogWarn("Failed to read socket bound device: %s", strerror(errno));
                      len = 0;
        }

        // There is a bug in linux that connecting to AF_UNSPEC does not disconnect.
        // We create new socket to disconnect.
        SuccessOrExit(error = otPlatUdpClose(aUdpSocket));
        SuccessOrExit(error = otPlatUdpSocket(aUdpSocket));
        SuccessOrExit(error = otPlatUdpBind(aUdpSocket));

        if (len > 0 && netifName[0] != '\0')
        {
                      fd = FdFromHandle(aUdpSocket->mHandle);
                      VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &netifName, len) == 0, {
                          ot::Posix::Udp::LogWarn("Failed to bind to device: %s", strerror(errno));
                          error = OT_ERROR_FAILED;
                      });
        }

        ExitNow();
#endif
    }

    if (connect(fd, reinterpret_cast<struct sockaddr *>(&sin6), sizeof(sin6)) != 0)
    {
#ifdef __APPLE__
        VerifyOrExit(errno == EAFNOSUPPORT && isDisconnect);
#endif
        ot::Posix::Udp::LogWarn("Failed to connect to [%s]:%u: %s",
                                Ip6AddressString(&aUdpSocket->mPeerName.mAddress).AsCString(),
                                aUdpSocket->mPeerName.mPort, strerror(errno));
        error = OT_ERROR_FAILED;
    }

exit:
    return error;
}

otError otPlatUdpSend(otUdpSocket *aUdpSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
    otError  error = OT_ERROR_NONE;
    int      fd;
    uint16_t len;
    uint8_t  payload[kMaxUdpSize];

    VerifyOrExit(aUdpSocket->mHandle != nullptr, error = OT_ERROR_INVALID_ARGS);
    fd = FdFromHandle(aUdpSocket->mHandle);

    len = otMessageGetLength(aMessage);
    VerifyOrExit(len == otMessageRead(aMessage, 0, payload, len), error = OT_ERROR_INVALID_ARGS);

    if (aMessageInfo->mMulticastLoop)
    {
        int value = 1;

        VerifyOrDie(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &value, sizeof(value)) == 0, OT_EXIT_ERROR_ERRNO);
    }

    error = transmitPacket(fd, payload, len, *aMessageInfo);

    if (aMessageInfo->mMulticastLoop)
    {
        int value = 0;

        VerifyOrDie(setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &value, sizeof(value)) == 0, OT_EXIT_ERROR_ERRNO);
    }

exit:
    if (error == OT_ERROR_NONE)
    {
        otMessageFree(aMessage);
    }

    return error;
}

otError otPlatUdpJoinMulticastGroup(otUdpSocket        *aUdpSocket,
                                    otNetifIdentifier   aNetifIdentifier,
                                    const otIp6Address *aAddress)
{
    otError          error = OT_ERROR_NONE;
    struct ipv6_mreq mreq;
    int              fd;

    VerifyOrExit(aUdpSocket->mHandle != nullptr, error = OT_ERROR_INVALID_ARGS);
    fd = FdFromHandle(aUdpSocket->mHandle);

    CopyIp6AddressTo(*aAddress, &mreq.ipv6mr_multiaddr);

    switch (aNetifIdentifier)
    {
    case OT_NETIF_UNSPECIFIED:
        break;
    case OT_NETIF_THREAD:
        mreq.ipv6mr_interface = gNetifIndex;
        break;
    case OT_NETIF_BACKBONE:
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
        mreq.ipv6mr_interface = otSysGetInfraNetifIndex();
#else
        ExitNow(error = OT_ERROR_NOT_IMPLEMENTED);
#endif
        break;
    }

    VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == 0 || errno == EADDRINUSE,
                 error = OT_ERROR_FAILED);

exit:
    if (error != OT_ERROR_NONE)
    {
        ot::Posix::Udp::LogCrit("IPV6_JOIN_GROUP failed: %s", strerror(errno));
    }

    return error;
}

otError otPlatUdpLeaveMulticastGroup(otUdpSocket        *aUdpSocket,
                                     otNetifIdentifier   aNetifIdentifier,
                                     const otIp6Address *aAddress)
{
    otError          error = OT_ERROR_NONE;
    struct ipv6_mreq mreq;
    int              fd;

    VerifyOrExit(aUdpSocket->mHandle != nullptr, error = OT_ERROR_INVALID_ARGS);
    fd = FdFromHandle(aUdpSocket->mHandle);

    CopyIp6AddressTo(*aAddress, &mreq.ipv6mr_multiaddr);

    switch (aNetifIdentifier)
    {
    case OT_NETIF_UNSPECIFIED:
        break;
    case OT_NETIF_THREAD:
        mreq.ipv6mr_interface = gNetifIndex;
        break;
    case OT_NETIF_BACKBONE:
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
        mreq.ipv6mr_interface = otSysGetInfraNetifIndex();
#else
        ExitNow(error = OT_ERROR_NOT_IMPLEMENTED);
#endif
        break;
    }

    VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq, sizeof(mreq)) == 0 || errno == EADDRINUSE,
                 error = OT_ERROR_FAILED);

exit:
    if (error != OT_ERROR_NONE)
    {
        ot::Posix::Udp::LogCrit("IPV6_LEAVE_GROUP failed: %s", strerror(errno));
    }

    return error;
}

namespace ot {
namespace Posix {

const char Udp::kLogModuleName[] = "Udp";

void Udp::Update(otSysMainloopContext &aContext)
{
    VerifyOrExit(gNetifIndex != 0);

    for (otUdpSocket *socket = otUdpGetSockets(gInstance); socket != nullptr; socket = socket->mNext)
    {
        int fd;

        if (socket->mHandle == nullptr)
        {
            continue;
        }

        fd = FdFromHandle(socket->mHandle);
        FD_SET(fd, &aContext.mReadFdSet);

        if (aContext.mMaxFd < fd)
        {
            aContext.mMaxFd = fd;
        }
    }

exit:
    return;
}

void Udp::Init(const char *aIfName)
{
    if (aIfName == nullptr)
    {
        DieNow(OT_EXIT_INVALID_ARGUMENTS);
    }

    if (aIfName != gNetifName)
    {
        VerifyOrDie(strlen(aIfName) < sizeof(gNetifName) - 1, OT_EXIT_INVALID_ARGUMENTS);
        assert(gNetifIndex == 0);
        strcpy(gNetifName, aIfName);
        gNetifIndex = if_nametoindex(gNetifName);
        VerifyOrDie(gNetifIndex != 0, OT_EXIT_ERROR_ERRNO);
    }

    assert(gNetifIndex != 0);
}

void Udp::SetUp(void) { Mainloop::Manager::Get().Add(*this); }

void Udp::TearDown(void) { Mainloop::Manager::Get().Remove(*this); }

void Udp::Deinit(void)
{
    // TODO All platform sockets should be closed
}

Udp &Udp::Get(void)
{
    static Udp sInstance;

    return sInstance;
}

void Udp::Process(const otSysMainloopContext &aContext)
{
    otMessageSettings msgSettings = {false, OT_MESSAGE_PRIORITY_NORMAL};

    for (otUdpSocket *socket = otUdpGetSockets(gInstance); socket != nullptr; socket = socket->mNext)
    {
        int fd = FdFromHandle(socket->mHandle);

        if (fd > 0 && FD_ISSET(fd, &aContext.mReadFdSet))
        {
            otMessageInfo messageInfo;
            otMessage    *message = nullptr;
            uint8_t       payload[kMaxUdpSize];
            uint16_t      length = sizeof(payload);

            memset(&messageInfo, 0, sizeof(messageInfo));
            messageInfo.mSockPort = socket->mSockName.mPort;

            if (OT_ERROR_NONE != receivePacket(fd, payload, length, messageInfo))
            {
                continue;
            }

            message = otUdpNewMessage(gInstance, &msgSettings);

            if (message == nullptr)
            {
                continue;
            }

            if (otMessageAppend(message, payload, length) != OT_ERROR_NONE)
            {
                otMessageFree(message);
                continue;
            }

            socket->mHandler(socket->mContext, message, &messageInfo);
            otMessageFree(message);
            // only process one socket a time
            break;
        }
    }

    return;
}

} // namespace Posix
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
