/*
 *  Copyright (c) 2024, 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.
 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"

namespace ot {
namespace Nexus {

typedef MeshCoP::Dtls Dtls;

static constexpr uint16_t kMaxNodes    = 3;
static constexpr uint16_t kUdpPort     = 1234;
static constexpr uint16_t kMessageSize = 100;
static constexpr uint16_t kMaxAttempts = 3;

static const uint8_t kPsk[] = {0x10, 0x20, 0x03, 0x15, 0x10, 0x00, 0x60, 0x16};

static Dtls::Session::ConnectEvent  sDtlsEvent[kMaxNodes];
static Array<uint8_t, kMessageSize> sDtlsLastReceive[kMaxNodes];
static bool                         sDtlsAutoClosed[kMaxNodes];
static uint32_t                     sHeapSessionsAllocated = 0;

const char *ConnectEventToString(Dtls::Session::ConnectEvent aEvent)
{
    const char *str = "";

    switch (aEvent)
    {
    case Dtls::Session::kConnected:
        str = "kConnected";
        break;
    case Dtls::Session::kDisconnectedPeerClosed:
        str = "kDisconnectedPeerClosed";
        break;
    case Dtls::Session::kDisconnectedLocalClosed:
        str = "kDisconnectedLocalClosed";
        break;
    case Dtls::Session::kDisconnectedMaxAttempts:
        str = "kDisconnectedMaxAttempts";
        break;
    case Dtls::Session::kDisconnectedError:
        str = "kDisconnectedError";
        break;
    case Dtls::Session::kDisconnectedTimeout:
        str = "kDisconnectedTimeout";
        break;
    }

    return str;
}

void HandleReceive(void *aContext, uint8_t *aBuf, uint16_t aLength)
{
    Node *node = static_cast<Node *>(aContext);

    VerifyOrQuit(node != nullptr);
    VerifyOrQuit(node->GetId() < kMaxNodes);

    Log("   node%u: HandleReceive(aLength:%u)", node->GetId(), aLength);

    sDtlsLastReceive[node->GetId()].Clear();

    for (; aLength > 0; aLength--, aBuf++)
    {
        SuccessOrQuit(sDtlsLastReceive[node->GetId()].PushBack(*aBuf));
    }
}

void HandleConnectEvent(Dtls::Session::ConnectEvent aEvent, void *aContext)
{
    Node *node = static_cast<Node *>(aContext);

    VerifyOrQuit(node != nullptr);
    VerifyOrQuit(node->GetId() < kMaxNodes);
    sDtlsEvent[node->GetId()] = aEvent;

    Log("   node%u: HandleConnectEvent(%s)", node->GetId(), ConnectEventToString(aEvent));
}

void HandleAutoClose(void *aContext)
{
    Node *node = static_cast<Node *>(aContext);

    VerifyOrQuit(node != nullptr);
    VerifyOrQuit(node->GetId() < kMaxNodes);
    sDtlsAutoClosed[node->GetId()] = true;

    Log("   node%u: HandleAutoClose()", node->GetId());
}

OwnedPtr<Message> PrepareMessage(Node &aNode)
{
    Message *message = aNode.Get<MessagePool>().Allocate(Message::kTypeOther);
    uint16_t length;

    VerifyOrQuit(message != nullptr);

    length = Random::NonCrypto::GenerateFromMinUpToExcluding<uint16_t>(1, kMessageSize);

    for (uint16_t i = 0; i < length; i++)
    {
        SuccessOrQuit(message->Append(Random::NonCrypto::Generate<uint8_t>()));
    }

    return OwnedPtr<Message>(message);
}

class DtlsTransportAndSingleSession : public InstanceLocator, public Dtls::Transport, public Dtls::Session
{
    // A DTLS transport and single session
public:
    explicit DtlsTransportAndSingleSession(Node &aNode)
        : InstanceLocator(aNode.GetInstance())
        , Dtls::Transport(aNode.GetInstance(), kWithLinkSecurity)
        , Dtls::Session(static_cast<Dtls::Transport &>(*this))
        , mNode(aNode)
    {
        SetAcceptCallback(HandleAccept, this);

        VerifyOrQuit(!IsSessionInUse());
    }

private:
    static MeshCoP::SecureSession *HandleAccept(void *aContext, const Ip6::MessageInfo &aMessageInfo)
    {
        OT_UNUSED_VARIABLE(aMessageInfo);

        return static_cast<DtlsTransportAndSingleSession *>(aContext)->HandleAccept();
    }

    Dtls::Session *HandleAccept(void)
    {
        Dtls::Session *session = IsSessionInUse() ? nullptr : static_cast<Dtls::Session *>(this);

        Log("   node%u: HandleAccept(), %s", mNode.GetId(), (session != nullptr) ? "accepted" : "rejected");
        return session;
    }

    Node &mNode;
};

class DtlsTransportAndHeapSession : public InstanceLocator, public Dtls::Transport
{
    // A DTLS session with heap allocated sessions.

public:
    explicit DtlsTransportAndHeapSession(Node &aNode)
        : InstanceLocator(aNode.GetInstance())
        , Dtls::Transport(aNode.GetInstance(), kWithLinkSecurity)
        , mNode(aNode)
    {
        SetAcceptCallback(HandleAccept, this);
        SetRemoveSessionCallback(HandleRemoveSession, this);
    }

private:
    class HeapDtlsSession : public Dtls::Session, public Heap::Allocatable<HeapDtlsSession>
    {
        friend Heap::Allocatable<HeapDtlsSession>;

    private:
        HeapDtlsSession(Dtls::Transport &aTransport)
            : Dtls::Session(aTransport)
        {
            sHeapSessionsAllocated++;
        }
    };

    static MeshCoP::SecureSession *HandleAccept(void *aContext, const Ip6::MessageInfo &aMessageInfo)
    {
        OT_UNUSED_VARIABLE(aMessageInfo);

        DtlsTransportAndHeapSession *transport;
        HeapDtlsSession             *session;

        VerifyOrQuit(aContext != nullptr);
        transport = static_cast<DtlsTransportAndHeapSession *>(aContext);

        Log("   node%u: HandleAccept()", transport->mNode.GetId());

        session = HeapDtlsSession::Allocate(*transport);
        VerifyOrQuit(session != nullptr);

        session->SetReceiveCallback(&ot::Nexus::HandleReceive, &transport->mNode);
        session->SetConnectCallback(&ot::Nexus::HandleConnectEvent, &transport->mNode);

        return session;
    }

    static void HandleRemoveSession(void *aContext, MeshCoP::SecureSession &aSesssion)
    {
        DtlsTransportAndHeapSession *transport;

        VerifyOrQuit(aContext != nullptr);
        transport = static_cast<DtlsTransportAndHeapSession *>(aContext);

        Log("   node%u: HandleRemoveSession()", transport->mNode.GetId());

        VerifyOrQuit(sHeapSessionsAllocated > 0);

        static_cast<HeapDtlsSession &>(aSesssion).Free();
        sHeapSessionsAllocated--;
    }

private:
    Node &mNode;
};

void TestDtlsSingleSession(void)
{
    Core  nexus;
    Node &node0 = nexus.CreateNode();
    Node &node1 = nexus.CreateNode();
    Node &node2 = nexus.CreateNode();

    Log("------------------------------------------------------------------------------------------------------");
    Log("TestDtlsSingleSession");

    nexus.AdvanceTime(0);

    // Form the topology: node0 leader, with node1 & node2 as its FTD children

    node0.Form();
    nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
    VerifyOrQuit(node0.Get<Mle::Mle>().IsLeader());

    node1.Join(node0, Node::kAsFed);
    nexus.AdvanceTime(20 * Time::kOneSecondInMsec);
    VerifyOrQuit(node1.Get<Mle::Mle>().IsChild());

    node2.Join(node0, Node::kAsFed);
    nexus.AdvanceTime(20 * Time::kOneSecondInMsec);
    VerifyOrQuit(node2.Get<Mle::Mle>().IsChild());

    {
        DtlsTransportAndSingleSession dtls0(node0);
        DtlsTransportAndSingleSession dtls1(node1);
        DtlsTransportAndSingleSession dtls2(node2);
        Ip6::SockAddr                 sockAddr;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Start DTLS (server) on node0 bound to port %u", kUdpPort);

        SuccessOrQuit(dtls0.SetPsk(kPsk, sizeof(kPsk)));
        dtls0.SetReceiveCallback(HandleReceive, &node0);
        dtls0.SetConnectCallback(HandleConnectEvent, &node0);
        SuccessOrQuit(dtls0.Open(kUdpPort));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls0.GetUdpPort() == kUdpPort);
        VerifyOrQuit(!dtls0.IsConnectionActive());

        sockAddr.SetAddress(node0.Get<Mle::Mle>().GetMeshLocalRloc());
        sockAddr.SetPort(kUdpPort);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Try to establish a DTLS connection from node 1 using a wrong PSK multiple times");

        SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk) - 1));
        dtls1.SetReceiveCallback(HandleReceive, &node1);
        dtls1.SetConnectCallback(HandleConnectEvent, &node1);
        SuccessOrQuit(dtls1.Open(0));

        for (uint16_t iter = 0; iter <= kMaxAttempts + 1; iter++)
        {
            memset(sDtlsEvent, Dtls::Session::kConnected, sizeof(sDtlsEvent));

            SuccessOrQuit(dtls1.Connect(sockAddr));
            nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

            VerifyOrQuit(!dtls0.IsConnected());
            VerifyOrQuit(!dtls1.IsConnected());

            VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedError);
            VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kDisconnectedError);
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Establish a DTLS connection from node1 with node0 using the correct PSK");

        dtls1.Close();

        SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk)));
        dtls1.SetReceiveCallback(HandleReceive, &node1);
        dtls1.SetConnectCallback(HandleConnectEvent, &node1);
        SuccessOrQuit(dtls1.Open(0));
        SuccessOrQuit(dtls1.Connect(sockAddr));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls0.IsConnected());
        VerifyOrQuit(dtls1.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kConnected);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kConnected);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Send message (random data and length) over DTLS session from node0 to node1");

        for (uint16_t iter = 0; iter < 20; iter++)
        {
            OwnedPtr<Message> msg(PrepareMessage(node0));

            SuccessOrQuit(dtls0.Send(*msg->Clone<kNoReservedHeader>()));
            nexus.AdvanceTime(100);

            VerifyOrQuit(sDtlsLastReceive[node1.GetId()].GetLength() == msg->GetLength());
            VerifyOrQuit(msg->CompareBytes(0, sDtlsLastReceive[node1.GetId()].GetArrayBuffer(), msg->GetLength()));
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Now send from node1 to node0");

        for (uint16_t iter = 0; iter < 20; iter++)
        {
            OwnedPtr<Message> msg(PrepareMessage(node1));

            SuccessOrQuit(dtls1.Send(*msg->Clone<kNoReservedHeader>()));
            nexus.AdvanceTime(100);

            VerifyOrQuit(sDtlsLastReceive[node0.GetId()].GetLength() == msg->GetLength());
            VerifyOrQuit(msg->CompareBytes(0, sDtlsLastReceive[node0.GetId()].GetArrayBuffer(), msg->GetLength()));
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Disconnect from node1 - validate the disconnect events (local/peer)");

        dtls1.Disconnect();

        nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

        VerifyOrQuit(!dtls0.IsConnected());
        VerifyOrQuit(!dtls1.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedPeerClosed);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kDisconnectedLocalClosed);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Establish a DTLS connection again");

        SuccessOrQuit(dtls1.Connect(sockAddr));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls0.IsConnected());
        VerifyOrQuit(dtls1.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kConnected);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kConnected);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Try to connect from node2 - validate that it fails to connect since already connected");

        SuccessOrQuit(dtls2.SetPsk(kPsk, sizeof(kPsk)));
        dtls2.SetReceiveCallback(HandleReceive, &node2);
        dtls2.SetReceiveCallback(HandleReceive, &node2);
        SuccessOrQuit(dtls2.Open(0));
        SuccessOrQuit(dtls2.Connect(sockAddr));

        nexus.AdvanceTime(20 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls0.IsConnected());
        VerifyOrQuit(dtls1.IsConnected());
        VerifyOrQuit(!dtls2.IsConnected());

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Disconnect from node0 - validate the disconnect events");

        dtls0.Disconnect();

        nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

        VerifyOrQuit(!dtls0.IsConnected());
        VerifyOrQuit(!dtls1.IsConnected());
        VerifyOrQuit(!dtls2.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedLocalClosed);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kDisconnectedPeerClosed);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        dtls0.Close();
        dtls1.Close();
        dtls2.Close();

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        Log("Start DTLS (server) on node0 bound to port %u with auto-close max attempt %u", kUdpPort, kMaxAttempts);

        memset(sDtlsAutoClosed, false, sizeof(sDtlsAutoClosed));

        SuccessOrQuit(dtls0.SetMaxConnectionAttempts(kMaxAttempts, HandleAutoClose, &node0));
        SuccessOrQuit(dtls0.SetPsk(kPsk, sizeof(kPsk)));
        dtls0.SetReceiveCallback(HandleReceive, &node0);
        dtls0.SetConnectCallback(HandleConnectEvent, &node0);
        SuccessOrQuit(dtls0.Open(kUdpPort));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls0.GetUdpPort() == kUdpPort);
        VerifyOrQuit(!dtls0.IsConnectionActive());

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Using wrong PSK try to establish DTLS connection with node0 %u times", kMaxAttempts - 1);

        SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk) - 1));
        dtls1.SetReceiveCallback(HandleReceive, &node1);
        dtls1.SetConnectCallback(HandleConnectEvent, &node1);
        SuccessOrQuit(dtls1.Open(0));

        for (uint16_t iter = 0; iter < kMaxAttempts - 1; iter++)
        {
            memset(sDtlsEvent, Dtls::Session::kConnected, sizeof(sDtlsEvent));

            SuccessOrQuit(dtls1.Connect(sockAddr));
            nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

            VerifyOrQuit(!dtls0.IsConnected());
            VerifyOrQuit(!dtls1.IsConnected());

            VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedError);
            VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kDisconnectedError);
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Using wrong PSK try one last time, validate the auto-close behavior");

        memset(sDtlsEvent, Dtls::Session::kConnected, sizeof(sDtlsEvent));

        SuccessOrQuit(dtls1.Connect(sockAddr));
        nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedError);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kDisconnectedError);

        VerifyOrQuit(sDtlsAutoClosed[node0.GetId()]);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        dtls0.Close();
        dtls1.Close();
        dtls2.Close();
    }
}

void TestDtlsMultiSession(void)
{
    Core  nexus;
    Node &node0 = nexus.CreateNode();
    Node &node1 = nexus.CreateNode();
    Node &node2 = nexus.CreateNode();

    Log("------------------------------------------------------------------------------------------------------");
    Log("TestDtlsMultiSession");

    nexus.AdvanceTime(0);

    // Form the topology: node0 leader, with node1 & node2 as its FTD children

    node0.Form();
    nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
    VerifyOrQuit(node0.Get<Mle::Mle>().IsLeader());

    node1.Join(node0, Node::kAsFed);
    nexus.AdvanceTime(20 * Time::kOneSecondInMsec);
    VerifyOrQuit(node1.Get<Mle::Mle>().IsChild());

    node2.Join(node0, Node::kAsFed);
    nexus.AdvanceTime(20 * Time::kOneSecondInMsec);
    VerifyOrQuit(node2.Get<Mle::Mle>().IsChild());

    {
        DtlsTransportAndHeapSession   dtls0(node0);
        DtlsTransportAndSingleSession dtls1(node1);
        DtlsTransportAndSingleSession dtls2(node2);
        Ip6::SockAddr                 sockAddr;
        uint16_t                      numSessions;

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Start DTLS (server) on node0 bound to port %u", kUdpPort);

        SuccessOrQuit(dtls0.SetPsk(kPsk, sizeof(kPsk)));
        SuccessOrQuit(dtls0.Open(kUdpPort));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls0.GetUdpPort() == kUdpPort);

        sockAddr.SetAddress(node0.Get<Mle::Mle>().GetMeshLocalRloc());
        sockAddr.SetPort(kUdpPort);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Establish a DTLS connection with node 0 from node1");

        memset(sDtlsEvent, Dtls::Session::kDisconnectedError, sizeof(sDtlsEvent));

        SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk)));
        dtls1.SetReceiveCallback(HandleReceive, &node1);
        dtls1.SetConnectCallback(HandleConnectEvent, &node1);
        SuccessOrQuit(dtls1.Open(0));
        SuccessOrQuit(dtls1.Connect(sockAddr));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls1.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kConnected);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kConnected);

        numSessions = 0;

        for (MeshCoP::SecureSession &session : dtls0.GetSessions())
        {
            VerifyOrQuit(session.IsConnected());
            numSessions++;
        }

        VerifyOrQuit(numSessions == 1);
        VerifyOrQuit(sHeapSessionsAllocated == 1);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Establish a second DTLS connection with node0 from node2");

        memset(sDtlsEvent, Dtls::Session::kDisconnectedError, sizeof(sDtlsEvent));

        SuccessOrQuit(dtls2.SetPsk(kPsk, sizeof(kPsk)));
        dtls2.SetReceiveCallback(HandleReceive, &node2);
        dtls2.SetConnectCallback(HandleConnectEvent, &node2);
        SuccessOrQuit(dtls2.Open(0));
        SuccessOrQuit(dtls2.Connect(sockAddr));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls2.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kConnected);
        VerifyOrQuit(sDtlsEvent[node2.GetId()] == Dtls::Session::kConnected);

        numSessions = 0;

        for (MeshCoP::SecureSession &session : dtls0.GetSessions())
        {
            VerifyOrQuit(session.IsConnected());
            numSessions++;
        }

        VerifyOrQuit(numSessions == 2);
        VerifyOrQuit(sHeapSessionsAllocated == 2);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Disconnect from node1 - validate the disconnect events");

        dtls1.Disconnect();

        nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

        VerifyOrQuit(!dtls1.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedPeerClosed);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kDisconnectedLocalClosed);

        numSessions = 0;

        for (MeshCoP::SecureSession &session : dtls0.GetSessions())
        {
            VerifyOrQuit(session.IsConnected());
            numSessions++;
        }

        VerifyOrQuit(numSessions == 1);
        VerifyOrQuit(sHeapSessionsAllocated == 1);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Disconnect session with node2 from node0 (server) - validate the disconnect events");

        memset(sDtlsEvent, Dtls::Session::kConnected, sizeof(sDtlsEvent));

        dtls0.GetSessions().GetHead()->Disconnect();

        nexus.AdvanceTime(3 * Time::kOneSecondInMsec);

        VerifyOrQuit(!dtls2.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kDisconnectedLocalClosed);
        VerifyOrQuit(sDtlsEvent[node2.GetId()] == Dtls::Session::kDisconnectedPeerClosed);

        VerifyOrQuit(dtls0.GetSessions().IsEmpty());
        VerifyOrQuit(sHeapSessionsAllocated == 0);

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Log("Establish two DTLS connections from node1 and node2 at the same time");

        memset(sDtlsEvent, Dtls::Session::kDisconnectedError, sizeof(sDtlsEvent));

        SuccessOrQuit(dtls1.Connect(sockAddr));
        SuccessOrQuit(dtls2.Connect(sockAddr));

        nexus.AdvanceTime(1 * Time::kOneSecondInMsec);

        VerifyOrQuit(dtls1.IsConnected());
        VerifyOrQuit(dtls2.IsConnected());

        VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::Session::kConnected);
        VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::Session::kConnected);
        VerifyOrQuit(sDtlsEvent[node2.GetId()] == Dtls::Session::kConnected);

        numSessions = 0;

        for (MeshCoP::SecureSession &session : dtls0.GetSessions())
        {
            VerifyOrQuit(session.IsConnected());
            numSessions++;
        }

        VerifyOrQuit(numSessions == 2);
        VerifyOrQuit(sHeapSessionsAllocated == 2);
    }
}

} // namespace Nexus
} // namespace ot

int main(void)
{
    ot::Nexus::TestDtlsSingleSession();
    ot::Nexus::TestDtlsMultiSession();
    printf("All tests passed\n");
    return 0;
}
