/*
 * Copyright (c) 2004 Francisco J. Ros
 * Copyright (c) 2007 INESC Porto
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Authors: Francisco J. Ros  <fjrm@dif.um.es>
 *          Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
 */

///
/// @brief Implementation of OLSR agent and related classes.
///
/// This is the main file of this software because OLSR's behaviour is
/// implemented here.
///

#define NS_LOG_APPEND_CONTEXT                                                                      \
    if (GetObject<Node>())                                                                         \
    {                                                                                              \
        std::clog << "[node " << GetObject<Node>()->GetId() << "] ";                               \
    }

#include "olsr-routing-protocol.h"

#include "olsr-repositories.h"

#include "ns3/boolean.h"
#include "ns3/enum.h"
#include "ns3/inet-socket-address.h"
#include "ns3/ipv4-header.h"
#include "ns3/ipv4-packet-info-tag.h"
#include "ns3/ipv4-route.h"
#include "ns3/ipv4-routing-protocol.h"
#include "ns3/ipv4-routing-table-entry.h"
#include "ns3/log.h"
#include "ns3/names.h"
#include "ns3/simulator.h"
#include "ns3/socket-factory.h"
#include "ns3/trace-source-accessor.h"
#include "ns3/udp-socket-factory.h"
#include "ns3/uinteger.h"

#include <iomanip>
#include <iostream>

/********** Useful macros **********/

///
/// @brief Gets the delay between a given time and the current time.
///
/// If given time is previous to the current one, then this macro returns
/// a number close to 0. This is used for scheduling events at a certain moment.
///
#define DELAY(time)                                                                                \
    (((time) < (Simulator::Now())) ? Seconds(0.000001)                                             \
                                   : (time - Simulator::Now() + Seconds(0.000001)))

///
/// @brief Period at which a node must cite every link and every neighbor.
///
/// We only use this value in order to define OLSR_NEIGHB_HOLD_TIME.
///
#define OLSR_REFRESH_INTERVAL m_helloInterval

/********** Holding times **********/

/// Neighbor holding time.
#define OLSR_NEIGHB_HOLD_TIME Time(3 * OLSR_REFRESH_INTERVAL)
/// Top holding time.
#define OLSR_TOP_HOLD_TIME Time(3 * m_tcInterval)
/// Dup holding time.
#define OLSR_DUP_HOLD_TIME Seconds(30)
/// MID holding time.
#define OLSR_MID_HOLD_TIME Time(3 * m_midInterval)
/// HNA holding time.
#define OLSR_HNA_HOLD_TIME Time(3 * m_hnaInterval)

/********** Miscellaneous constants **********/

/// Maximum allowed jitter.
#define OLSR_MAXJITTER (m_helloInterval.GetSeconds() / 4)
/// Maximum allowed sequence number.
#define OLSR_MAX_SEQ_NUM 65535
/// Random number between [0-OLSR_MAXJITTER] used to jitter OLSR packet transmission.
#define JITTER (Seconds(m_uniformRandomVariable->GetValue(0, OLSR_MAXJITTER)))

/// Maximum number of messages per packet.
#define OLSR_MAX_MSGS 64

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("OlsrRoutingProtocol");

namespace olsr
{

/**
 * @ingroup olsr
 *
 * OLSR link types.
 * See \RFC{3626} section 18.5.
 */
enum class LinkType : uint8_t
{
    UNSPEC_LINK = 0, //!< Unspecified link type
    ASYM_LINK = 1,   //!< Asymmetric link type
    SYM_LINK = 2,    //!< Symmetric link type
    LOST_LINK = 3,   //!< Lost link type
};

/**
 * Stream insertion operator for OLSR link type.
 *
 * @param os Output stream.
 * @param linkType OLSR link type.
 * @return A reference to the output stream.
 */
inline std::ostream&
operator<<(std::ostream& os, LinkType linkType)
{
    switch (linkType)
    {
    case LinkType::UNSPEC_LINK:
        return (os << "UNSPEC_LINK");
    case LinkType::ASYM_LINK:
        return (os << "ASYM_LINK");
    case LinkType::SYM_LINK:
        return (os << "SYM_LINK");
    case LinkType::LOST_LINK:
        return (os << "LOST_LINK");
    default:
        return (os << "Unknown link type");
    }
}

/**
 * @ingroup olsr
 *
 * OLSR neighbor types.
 * See \RFC{3626} section 18.6.
 */
enum class NeighborType : uint8_t
{
    NOT_NEIGH = 0, //!< Not neighbor type
    SYM_NEIGH = 1, //!< Symmetric neighbor type
    MPR_NEIGH = 2, //!< Asymmetric neighbor type
};

/**
 * Stream insertion operator for OLSR link type.
 *
 * @param os Output stream.
 * @param neighborType OLSR neighbor type.
 * @return A reference to the output stream.
 */
inline std::ostream&
operator<<(std::ostream& os, NeighborType neighborType)
{
    switch (neighborType)
    {
    case NeighborType::NOT_NEIGH:
        return (os << "NOT_NEIGH");
    case NeighborType::SYM_NEIGH:
        return (os << "SYM_NEIGH");
    case NeighborType::MPR_NEIGH:
        return (os << "MPR_NEIGH");
    default:
        return (os << "Unknown neighbor type");
    }
}

/********** OLSR class **********/

NS_OBJECT_ENSURE_REGISTERED(RoutingProtocol);

/* see https://www.iana.org/assignments/service-names-port-numbers */
const uint16_t RoutingProtocol::OLSR_PORT_NUMBER = 698;

TypeId
RoutingProtocol::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::olsr::RoutingProtocol")
            .SetParent<Ipv4RoutingProtocol>()
            .SetGroupName("Olsr")
            .AddConstructor<RoutingProtocol>()
            .AddAttribute("HelloInterval",
                          "HELLO messages emission interval.",
                          TimeValue(Seconds(2)),
                          MakeTimeAccessor(&RoutingProtocol::m_helloInterval),
                          MakeTimeChecker())
            .AddAttribute("TcInterval",
                          "TC messages emission interval.",
                          TimeValue(Seconds(5)),
                          MakeTimeAccessor(&RoutingProtocol::m_tcInterval),
                          MakeTimeChecker())
            .AddAttribute("MidInterval",
                          "MID messages emission interval.  Normally it is equal to TcInterval.",
                          TimeValue(Seconds(5)),
                          MakeTimeAccessor(&RoutingProtocol::m_midInterval),
                          MakeTimeChecker())
            .AddAttribute("HnaInterval",
                          "HNA messages emission interval.  Normally it is equal to TcInterval.",
                          TimeValue(Seconds(5)),
                          MakeTimeAccessor(&RoutingProtocol::m_hnaInterval),
                          MakeTimeChecker())
            .AddAttribute("Willingness",
                          "Willingness of a node to carry and forward traffic for other nodes.",
                          EnumValue(Willingness::DEFAULT),
                          MakeEnumAccessor<Willingness>(&RoutingProtocol::m_willingness),
                          MakeEnumChecker(Willingness::NEVER,
                                          "never",
                                          Willingness::LOW,
                                          "low",
                                          Willingness::DEFAULT,
                                          "default",
                                          Willingness::HIGH,
                                          "high",
                                          Willingness::ALWAYS,
                                          "always"))
            .AddTraceSource("Rx",
                            "Receive OLSR packet.",
                            MakeTraceSourceAccessor(&RoutingProtocol::m_rxPacketTrace),
                            "ns3::olsr::RoutingProtocol::PacketTxRxTracedCallback")
            .AddTraceSource("Tx",
                            "Send OLSR packet.",
                            MakeTraceSourceAccessor(&RoutingProtocol::m_txPacketTrace),
                            "ns3::olsr::RoutingProtocol::PacketTxRxTracedCallback")
            .AddTraceSource("RoutingTableChanged",
                            "The OLSR routing table has changed.",
                            MakeTraceSourceAccessor(&RoutingProtocol::m_routingTableChanged),
                            "ns3::olsr::RoutingProtocol::TableChangeTracedCallback");
    return tid;
}

RoutingProtocol::RoutingProtocol()
    : m_routingTableAssociation(nullptr),
      m_ipv4(nullptr),
      m_helloTimer(Timer::CANCEL_ON_DESTROY),
      m_tcTimer(Timer::CANCEL_ON_DESTROY),
      m_midTimer(Timer::CANCEL_ON_DESTROY),
      m_hnaTimer(Timer::CANCEL_ON_DESTROY),
      m_queuedMessagesTimer(Timer::CANCEL_ON_DESTROY)
{
    m_uniformRandomVariable = CreateObject<UniformRandomVariable>();

    m_hnaRoutingTable = CreateObject<Ipv4StaticRouting>();
}

RoutingProtocol::~RoutingProtocol()
{
}

void
RoutingProtocol::SetIpv4(Ptr<Ipv4> ipv4)
{
    NS_ASSERT(ipv4);
    NS_ASSERT(!m_ipv4);
    NS_LOG_DEBUG("Created olsr::RoutingProtocol");
    m_helloTimer.SetFunction(&RoutingProtocol::HelloTimerExpire, this);
    m_tcTimer.SetFunction(&RoutingProtocol::TcTimerExpire, this);
    m_midTimer.SetFunction(&RoutingProtocol::MidTimerExpire, this);
    m_hnaTimer.SetFunction(&RoutingProtocol::HnaTimerExpire, this);
    m_queuedMessagesTimer.SetFunction(&RoutingProtocol::SendQueuedMessages, this);

    m_packetSequenceNumber = OLSR_MAX_SEQ_NUM;
    m_messageSequenceNumber = OLSR_MAX_SEQ_NUM;
    m_ansn = OLSR_MAX_SEQ_NUM;

    m_linkTupleTimerFirstTime = true;

    m_ipv4 = ipv4;

    m_hnaRoutingTable->SetIpv4(ipv4);
}

void
RoutingProtocol::DoDispose()
{
    m_ipv4 = nullptr;
    m_hnaRoutingTable = nullptr;
    m_routingTableAssociation = nullptr;

    if (m_recvSocket)
    {
        m_recvSocket->Close();
        m_recvSocket = nullptr;
    }

    for (auto iter = m_sendSockets.begin(); iter != m_sendSockets.end(); iter++)
    {
        iter->first->Close();
    }
    m_sendSockets.clear();
    m_table.clear();

    Ipv4RoutingProtocol::DoDispose();
}

void
RoutingProtocol::PrintRoutingTable(Ptr<OutputStreamWrapper> stream, Time::Unit unit) const
{
    std::ostream* os = stream->GetStream();
    // Copy the current ostream state
    std::ios oldState(nullptr);
    oldState.copyfmt(*os);

    *os << std::resetiosflags(std::ios::adjustfield) << std::setiosflags(std::ios::left);

    *os << "Node: " << m_ipv4->GetObject<Node>()->GetId() << ", Time: " << Now().As(unit)
        << ", Local time: " << m_ipv4->GetObject<Node>()->GetLocalTime().As(unit)
        << ", OLSR Routing table" << std::endl;

    *os << std::setw(16) << "Destination";
    *os << std::setw(16) << "NextHop";
    *os << std::setw(16) << "Interface";
    *os << "Distance" << std::endl;

    for (auto iter = m_table.begin(); iter != m_table.end(); iter++)
    {
        std::ostringstream dest;
        std::ostringstream nextHop;
        dest << iter->first;
        nextHop << iter->second.nextAddr;
        *os << std::setw(16) << dest.str();
        *os << std::setw(16) << nextHop.str();
        *os << std::setw(16);
        if (!Names::FindName(m_ipv4->GetNetDevice(iter->second.interface)).empty())
        {
            *os << Names::FindName(m_ipv4->GetNetDevice(iter->second.interface));
        }
        else
        {
            *os << iter->second.interface;
        }
        *os << iter->second.distance << std::endl;
    }
    *os << std::endl;

    // Also print the HNA routing table
    if (m_hnaRoutingTable->GetNRoutes() > 0)
    {
        *os << "HNA Routing Table:" << std::endl;
        m_hnaRoutingTable->PrintRoutingTable(stream, unit);
    }
    else
    {
        *os << "HNA Routing Table: empty" << std::endl << std::endl;
    }
    // Restore the previous ostream state
    (*os).copyfmt(oldState);
}

void
RoutingProtocol::DoInitialize()
{
    if (m_mainAddress == Ipv4Address())
    {
        Ipv4Address loopback("127.0.0.1");
        for (uint32_t i = 0; i < m_ipv4->GetNInterfaces(); i++)
        {
            // Use primary address, if multiple
            Ipv4Address addr = m_ipv4->GetAddress(i, 0).GetLocal();
            if (addr != loopback)
            {
                m_mainAddress = addr;
                break;
            }
        }

        NS_ASSERT(m_mainAddress != Ipv4Address());
    }

    NS_LOG_DEBUG("Starting OLSR on node " << m_mainAddress);

    Ipv4Address loopback("127.0.0.1");

    bool canRunOlsr = false;
    for (uint32_t i = 0; i < m_ipv4->GetNInterfaces(); i++)
    {
        Ipv4Address addr = m_ipv4->GetAddress(i, 0).GetLocal();
        if (addr == loopback)
        {
            continue;
        }

        if (addr != m_mainAddress)
        {
            // Create never expiring interface association tuple entries for our
            // own network interfaces, so that GetMainAddress () works to
            // translate the node's own interface addresses into the main address.
            IfaceAssocTuple tuple;
            tuple.ifaceAddr = addr;
            tuple.mainAddr = m_mainAddress;
            AddIfaceAssocTuple(tuple);
            NS_ASSERT(GetMainAddress(addr) == m_mainAddress);
        }

        if (m_interfaceExclusions.find(i) != m_interfaceExclusions.end())
        {
            continue;
        }

        // Create a socket to listen on all the interfaces
        if (!m_recvSocket)
        {
            m_recvSocket = Socket::CreateSocket(GetObject<Node>(), UdpSocketFactory::GetTypeId());
            m_recvSocket->SetAllowBroadcast(true);
            InetSocketAddress inetAddr(Ipv4Address::GetAny(), OLSR_PORT_NUMBER);
            m_recvSocket->SetRecvCallback(MakeCallback(&RoutingProtocol::RecvOlsr, this));
            if (m_recvSocket->Bind(inetAddr))
            {
                NS_FATAL_ERROR("Failed to bind() OLSR socket");
            }
            m_recvSocket->SetRecvPktInfo(true);
            m_recvSocket->ShutdownSend();
        }

        // Create a socket to send packets from this specific interfaces
        Ptr<Socket> socket = Socket::CreateSocket(GetObject<Node>(), UdpSocketFactory::GetTypeId());
        socket->SetAllowBroadcast(true);
        socket->SetIpTtl(1);
        InetSocketAddress inetAddr(m_ipv4->GetAddress(i, 0).GetLocal(), OLSR_PORT_NUMBER);
        socket->SetRecvCallback(MakeCallback(&RoutingProtocol::RecvOlsr, this));
        socket->BindToNetDevice(m_ipv4->GetNetDevice(i));
        if (socket->Bind(inetAddr))
        {
            NS_FATAL_ERROR("Failed to bind() OLSR socket");
        }
        socket->SetRecvPktInfo(true);
        m_sendSockets[socket] = m_ipv4->GetAddress(i, 0);

        canRunOlsr = true;
    }

    if (canRunOlsr)
    {
        HelloTimerExpire();
        TcTimerExpire();
        MidTimerExpire();
        HnaTimerExpire();

        NS_LOG_DEBUG("OLSR on node " << m_mainAddress << " started");
    }
}

void
RoutingProtocol::SetMainInterface(uint32_t interface)
{
    m_mainAddress = m_ipv4->GetAddress(interface, 0).GetLocal();
}

void
RoutingProtocol::SetInterfaceExclusions(std::set<uint32_t> exceptions)
{
    m_interfaceExclusions = exceptions;
}

//
// \brief Processes an incoming %OLSR packet following \RFC{3626} specification.
void
RoutingProtocol::RecvOlsr(Ptr<Socket> socket)
{
    Ptr<Packet> receivedPacket;
    Address sourceAddress;
    receivedPacket = socket->RecvFrom(sourceAddress);

    Ipv4PacketInfoTag interfaceInfo;
    if (!receivedPacket->RemovePacketTag(interfaceInfo))
    {
        NS_ABORT_MSG("No incoming interface on OLSR message, aborting.");
    }
    uint32_t incomingIf = interfaceInfo.GetRecvIf();
    Ptr<Node> node = this->GetObject<Node>();
    Ptr<NetDevice> dev = node->GetDevice(incomingIf);
    uint32_t recvInterfaceIndex = m_ipv4->GetInterfaceForDevice(dev);

    if (m_interfaceExclusions.find(recvInterfaceIndex) != m_interfaceExclusions.end())
    {
        return;
    }

    InetSocketAddress inetSourceAddr = InetSocketAddress::ConvertFrom(sourceAddress);
    Ipv4Address senderIfaceAddr = inetSourceAddr.GetIpv4();

    int32_t interfaceForAddress = m_ipv4->GetInterfaceForAddress(senderIfaceAddr);
    if (interfaceForAddress != -1)
    {
        NS_LOG_LOGIC("Ignoring a packet sent by myself.");
        return;
    }

    Ipv4Address receiverIfaceAddr = m_ipv4->GetAddress(recvInterfaceIndex, 0).GetLocal();
    NS_ASSERT(receiverIfaceAddr != Ipv4Address());
    NS_LOG_DEBUG("OLSR node " << m_mainAddress << " received a OLSR packet from " << senderIfaceAddr
                              << " to " << receiverIfaceAddr);

    // All routing messages are sent from and to port RT_PORT,
    // so we check it.
    NS_ASSERT(inetSourceAddr.GetPort() == OLSR_PORT_NUMBER);

    Ptr<Packet> packet = receivedPacket;

    olsr::PacketHeader olsrPacketHeader;
    packet->RemoveHeader(olsrPacketHeader);
    NS_ASSERT(olsrPacketHeader.GetPacketLength() >= olsrPacketHeader.GetSerializedSize());
    uint32_t sizeLeft = olsrPacketHeader.GetPacketLength() - olsrPacketHeader.GetSerializedSize();

    MessageList messages;

    while (sizeLeft)
    {
        MessageHeader messageHeader;
        if (packet->RemoveHeader(messageHeader) == 0)
        {
            NS_ASSERT(false);
        }

        sizeLeft -= messageHeader.GetSerializedSize();

        NS_LOG_DEBUG("Olsr Msg received with type "
                     << std::dec << int(messageHeader.GetMessageType())
                     << " TTL=" << int(messageHeader.GetTimeToLive())
                     << " origAddr=" << messageHeader.GetOriginatorAddress());
        messages.push_back(messageHeader);
    }

    m_rxPacketTrace(olsrPacketHeader, messages);

    for (auto messageIter = messages.begin(); messageIter != messages.end(); messageIter++)
    {
        const MessageHeader& messageHeader = *messageIter;
        // If ttl is less than or equal to zero, or
        // the receiver is the same as the originator,
        // the message must be silently dropped
        if (messageHeader.GetTimeToLive() == 0 ||
            messageHeader.GetOriginatorAddress() == m_mainAddress)
        {
            packet->RemoveAtStart(messageHeader.GetSerializedSize() -
                                  messageHeader.GetSerializedSize());
            continue;
        }

        // If the message has been processed it must not be processed again
        bool do_forwarding = true;
        DuplicateTuple* duplicated =
            m_state.FindDuplicateTuple(messageHeader.GetOriginatorAddress(),
                                       messageHeader.GetMessageSequenceNumber());

        // Get main address of the peer, which may be different from the packet source address
        //       const IfaceAssocTuple *ifaceAssoc = m_state.FindIfaceAssocTuple
        //       (inetSourceAddr.GetIpv4 ()); Ipv4Address peerMainAddress; if (ifaceAssoc != NULL)
        //         {
        //           peerMainAddress = ifaceAssoc->mainAddr;
        //         }
        //       else
        //         {
        //           peerMainAddress = inetSourceAddr.GetIpv4 () ;
        //         }

        if (duplicated == nullptr)
        {
            switch (messageHeader.GetMessageType())
            {
            case olsr::MessageHeader::HELLO_MESSAGE:
                NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                             << " OLSR node " << m_mainAddress << " received HELLO message of size "
                             << messageHeader.GetSerializedSize());
                ProcessHello(messageHeader, receiverIfaceAddr, senderIfaceAddr);
                break;

            case olsr::MessageHeader::TC_MESSAGE:
                NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                             << " OLSR node " << m_mainAddress << " received TC message of size "
                             << messageHeader.GetSerializedSize());
                ProcessTc(messageHeader, senderIfaceAddr);
                break;

            case olsr::MessageHeader::MID_MESSAGE:
                NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                             << " OLSR node " << m_mainAddress << " received MID message of size "
                             << messageHeader.GetSerializedSize());
                ProcessMid(messageHeader, senderIfaceAddr);
                break;
            case olsr::MessageHeader::HNA_MESSAGE:
                NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                             << " OLSR node " << m_mainAddress << " received HNA message of size "
                             << messageHeader.GetSerializedSize());
                ProcessHna(messageHeader, senderIfaceAddr);
                break;

            default:
                NS_LOG_DEBUG("OLSR message type " << int(messageHeader.GetMessageType())
                                                  << " not implemented");
            }
        }
        else
        {
            NS_LOG_DEBUG("OLSR message is duplicated, not reading it.");

            // If the message has been considered for forwarding, it should
            // not be retransmitted again
            for (auto it = duplicated->ifaceList.begin(); it != duplicated->ifaceList.end(); it++)
            {
                if (*it == receiverIfaceAddr)
                {
                    do_forwarding = false;
                    break;
                }
            }
        }

        if (do_forwarding)
        {
            // HELLO messages are never forwarded.
            // TC and MID messages are forwarded using the default algorithm.
            // Remaining messages are also forwarded using the default algorithm.
            if (messageHeader.GetMessageType() != olsr::MessageHeader::HELLO_MESSAGE)
            {
                ForwardDefault(messageHeader,
                               duplicated,
                               receiverIfaceAddr,
                               inetSourceAddr.GetIpv4());
            }
        }
    }

    // After processing all OLSR messages, we must recompute the routing table
    RoutingTableComputation();
}

///
/// @brief This auxiliary function (defined in \RFC{3626}) is used for calculating the MPR Set.
///
/// @param tuple the neighbor tuple which has the main address of the node we are going to calculate
/// its degree to.
/// @return the degree of the node.
///
int
RoutingProtocol::Degree(const NeighborTuple& tuple)
{
    int degree = 0;
    for (auto it = m_state.GetTwoHopNeighbors().begin(); it != m_state.GetTwoHopNeighbors().end();
         it++)
    {
        const TwoHopNeighborTuple& nb2hop_tuple = *it;
        if (nb2hop_tuple.neighborMainAddr == tuple.neighborMainAddr)
        {
            const NeighborTuple* nb_tuple =
                m_state.FindNeighborTuple(nb2hop_tuple.neighborMainAddr);
            if (nb_tuple == nullptr)
            {
                degree++;
            }
        }
    }
    return degree;
}

namespace
{
///
/// @brief Remove all covered 2-hop neighbors from N2 set.
/// This is a helper function used by MprComputation algorithm.
///
/// @param neighborMainAddr Neighbor main address.
/// @param N2 Reference to the 2-hop neighbor set.
///
void
CoverTwoHopNeighbors(Ipv4Address neighborMainAddr, TwoHopNeighborSet& N2)
{
    // first gather all 2-hop neighbors to be removed
    std::set<Ipv4Address> toRemove;
    for (auto twoHopNeigh = N2.begin(); twoHopNeigh != N2.end(); twoHopNeigh++)
    {
        if (twoHopNeigh->neighborMainAddr == neighborMainAddr)
        {
            toRemove.insert(twoHopNeigh->twoHopNeighborAddr);
        }
    }
    // Now remove all matching records from N2
    for (auto twoHopNeigh = N2.begin(); twoHopNeigh != N2.end();)
    {
        if (toRemove.find(twoHopNeigh->twoHopNeighborAddr) != toRemove.end())
        {
            twoHopNeigh = N2.erase(twoHopNeigh);
        }
        else
        {
            twoHopNeigh++;
        }
    }
}
} // unnamed namespace

void
RoutingProtocol::MprComputation()
{
    NS_LOG_FUNCTION(this);

    // MPR computation should be done for each interface. See section 8.3.1
    // (RFC 3626) for details.
    MprSet mprSet;

    // N is the subset of neighbors of the node, which are
    // neighbor "of the interface I"
    NeighborSet N;
    for (auto neighbor = m_state.GetNeighbors().begin(); neighbor != m_state.GetNeighbors().end();
         neighbor++)
    {
        if (neighbor->status == NeighborTuple::STATUS_SYM) // I think that we need this check
        {
            N.push_back(*neighbor);
        }
    }

    // N2 is the set of 2-hop neighbors reachable from "the interface
    // I", excluding:
    // (i)   the nodes only reachable by members of N with willingness Willingness::NEVER
    // (ii)  the node performing the computation
    // (iii) all the symmetric neighbors: the nodes for which there exists a symmetric
    //       link to this node on some interface.
    TwoHopNeighborSet N2;
    for (auto twoHopNeigh = m_state.GetTwoHopNeighbors().begin();
         twoHopNeigh != m_state.GetTwoHopNeighbors().end();
         twoHopNeigh++)
    {
        // excluding:
        // (ii)  the node performing the computation
        if (twoHopNeigh->twoHopNeighborAddr == m_mainAddress)
        {
            continue;
        }

        //  excluding:
        // (i)   the nodes only reachable by members of N with willingness Willingness::NEVER
        bool ok = false;
        for (auto neigh = N.begin(); neigh != N.end(); neigh++)
        {
            if (neigh->neighborMainAddr == twoHopNeigh->neighborMainAddr)
            {
                ok = (neigh->willingness != Willingness::NEVER);
                break;
            }
        }
        if (!ok)
        {
            continue;
        }

        // excluding:
        // (iii) all the symmetric neighbors: the nodes for which there exists a symmetric
        //       link to this node on some interface.
        for (auto neigh = N.begin(); neigh != N.end(); neigh++)
        {
            if (neigh->neighborMainAddr == twoHopNeigh->twoHopNeighborAddr)
            {
                ok = false;
                break;
            }
        }

        if (ok)
        {
            N2.push_back(*twoHopNeigh);
        }
    }

#ifdef NS3_LOG_ENABLE
    {
        std::ostringstream os;
        os << "[";
        for (auto iter = N2.begin(); iter != N2.end(); iter++)
        {
            auto next = iter;
            next++;
            os << iter->neighborMainAddr << "->" << iter->twoHopNeighborAddr;
            if (next != N2.end())
            {
                os << ", ";
            }
        }
        os << "]";
        NS_LOG_DEBUG("N2: " << os.str());
    }
#endif // NS3_LOG_ENABLE

    // 1. Start with an MPR set made of all members of N with
    // N_willingness equal to Willingness::ALWAYS
    for (auto neighbor = N.begin(); neighbor != N.end(); neighbor++)
    {
        if (neighbor->willingness == Willingness::ALWAYS)
        {
            mprSet.insert(neighbor->neighborMainAddr);
            // (not in RFC but I think is needed: remove the 2-hop
            // neighbors reachable by the MPR from N2)
            CoverTwoHopNeighbors(neighbor->neighborMainAddr, N2);
        }
    }

    // 2. Calculate D(y), where y is a member of N, for all nodes in N.
    // (we do this later)

    // 3. Add to the MPR set those nodes in N, which are the *only*
    // nodes to provide reachability to a node in N2.
    std::set<Ipv4Address> coveredTwoHopNeighbors;
    for (auto twoHopNeigh = N2.begin(); twoHopNeigh != N2.end(); twoHopNeigh++)
    {
        bool onlyOne = true;
        // try to find another neighbor that can reach twoHopNeigh->twoHopNeighborAddr
        for (auto otherTwoHopNeigh = N2.begin(); otherTwoHopNeigh != N2.end(); otherTwoHopNeigh++)
        {
            if (otherTwoHopNeigh->twoHopNeighborAddr == twoHopNeigh->twoHopNeighborAddr &&
                otherTwoHopNeigh->neighborMainAddr != twoHopNeigh->neighborMainAddr)
            {
                onlyOne = false;
                break;
            }
        }
        if (onlyOne)
        {
            NS_LOG_LOGIC("Neighbor " << twoHopNeigh->neighborMainAddr
                                     << " is the only that can reach 2-hop neigh. "
                                     << twoHopNeigh->twoHopNeighborAddr << " => select as MPR.");

            mprSet.insert(twoHopNeigh->neighborMainAddr);

            // take note of all the 2-hop neighbors reachable by the newly elected MPR
            for (auto otherTwoHopNeigh = N2.begin(); otherTwoHopNeigh != N2.end();
                 otherTwoHopNeigh++)
            {
                if (otherTwoHopNeigh->neighborMainAddr == twoHopNeigh->neighborMainAddr)
                {
                    coveredTwoHopNeighbors.insert(otherTwoHopNeigh->twoHopNeighborAddr);
                }
            }
        }
    }
    // Remove the nodes from N2 which are now covered by a node in the MPR set.
    for (auto twoHopNeigh = N2.begin(); twoHopNeigh != N2.end();)
    {
        if (coveredTwoHopNeighbors.find(twoHopNeigh->twoHopNeighborAddr) !=
            coveredTwoHopNeighbors.end())
        {
            // This works correctly only because it is known that twoHopNeigh is reachable by
            // exactly one neighbor, so only one record in N2 exists for each of them. This record
            // is erased here.
            NS_LOG_LOGIC("2-hop neigh. " << twoHopNeigh->twoHopNeighborAddr
                                         << " is already covered by an MPR.");
            twoHopNeigh = N2.erase(twoHopNeigh);
        }
        else
        {
            twoHopNeigh++;
        }
    }

    // 4. While there exist nodes in N2 which are not covered by at
    // least one node in the MPR set:
    while (N2.begin() != N2.end())
    {
#ifdef NS3_LOG_ENABLE
        {
            std::ostringstream os;
            os << "[";
            for (auto iter = N2.begin(); iter != N2.end(); iter++)
            {
                auto next = iter;
                next++;
                os << iter->neighborMainAddr << "->" << iter->twoHopNeighborAddr;
                if (next != N2.end())
                {
                    os << ", ";
                }
            }
            os << "]";
            NS_LOG_DEBUG("Step 4 iteration: N2=" << os.str());
        }
#endif // NS3_LOG_ENABLE

        // 4.1. For each node in N, calculate the reachability, i.e., the
        // number of nodes in N2 which are not yet covered by at
        // least one node in the MPR set, and which are reachable
        // through this 1-hop neighbor
        std::map<int, std::vector<const NeighborTuple*>> reachability;
        std::set<int> rs;
        for (auto it = N.begin(); it != N.end(); it++)
        {
            const NeighborTuple& nb_tuple = *it;
            int r = 0;
            for (auto it2 = N2.begin(); it2 != N2.end(); it2++)
            {
                const TwoHopNeighborTuple& nb2hop_tuple = *it2;
                if (nb_tuple.neighborMainAddr == nb2hop_tuple.neighborMainAddr)
                {
                    r++;
                }
            }
            rs.insert(r);
            reachability[r].push_back(&nb_tuple);
        }

        // 4.2. Select as a MPR the node with highest N_willingness among
        // the nodes in N with non-zero reachability. In case of
        // multiple choice select the node which provides
        // reachability to the maximum number of nodes in N2. In
        // case of multiple nodes providing the same amount of
        // reachability, select the node as MPR whose D(y) is
        // greater. Remove the nodes from N2 which are now covered
        // by a node in the MPR set.
        const NeighborTuple* max = nullptr;
        int max_r = 0;
        for (auto it = rs.begin(); it != rs.end(); it++)
        {
            int r = *it;
            if (r == 0)
            {
                continue;
            }
            for (auto it2 = reachability[r].begin(); it2 != reachability[r].end(); it2++)
            {
                const NeighborTuple* nb_tuple = *it2;
                if (max == nullptr || nb_tuple->willingness > max->willingness)
                {
                    max = nb_tuple;
                    max_r = r;
                }
                else if (nb_tuple->willingness == max->willingness)
                {
                    if (r > max_r)
                    {
                        max = nb_tuple;
                        max_r = r;
                    }
                    else if (r == max_r)
                    {
                        if (Degree(*nb_tuple) > Degree(*max))
                        {
                            max = nb_tuple;
                            max_r = r;
                        }
                    }
                }
            }
        }

        if (max != nullptr)
        {
            mprSet.insert(max->neighborMainAddr);
            CoverTwoHopNeighbors(max->neighborMainAddr, N2);
            NS_LOG_LOGIC(N2.size() << " 2-hop neighbors left to cover!");
        }
    }

#ifdef NS3_LOG_ENABLE
    {
        std::ostringstream os;
        os << "[";
        for (auto iter = mprSet.begin(); iter != mprSet.end(); iter++)
        {
            auto next = iter;
            next++;
            os << *iter;
            if (next != mprSet.end())
            {
                os << ", ";
            }
        }
        os << "]";
        NS_LOG_DEBUG("Computed MPR set for node " << m_mainAddress << ": " << os.str());
    }
#endif // NS3_LOG_ENABLE

    m_state.SetMprSet(mprSet);
}

Ipv4Address
RoutingProtocol::GetMainAddress(Ipv4Address iface_addr) const
{
    const IfaceAssocTuple* tuple = m_state.FindIfaceAssocTuple(iface_addr);

    if (tuple != nullptr)
    {
        return tuple->mainAddr;
    }
    else
    {
        return iface_addr;
    }
}

void
RoutingProtocol::RoutingTableComputation()
{
    NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                 << " : Node " << m_mainAddress << ": RoutingTableComputation begin...");

    // 1. All the entries from the routing table are removed.
    Clear();

    // 2. The new routing entries are added starting with the
    // symmetric neighbors (h=1) as the destination nodes.
    const NeighborSet& neighborSet = m_state.GetNeighbors();
    for (auto it = neighborSet.begin(); it != neighborSet.end(); it++)
    {
        const NeighborTuple& nb_tuple = *it;
        NS_LOG_DEBUG("Looking at neighbor tuple: " << nb_tuple);
        if (nb_tuple.status == NeighborTuple::STATUS_SYM)
        {
            bool nb_main_addr = false;
            const LinkTuple* lt = nullptr;
            const LinkSet& linkSet = m_state.GetLinks();
            for (auto it2 = linkSet.begin(); it2 != linkSet.end(); it2++)
            {
                const LinkTuple& link_tuple = *it2;
                NS_LOG_DEBUG("Looking at link tuple: "
                             << link_tuple
                             << (link_tuple.time >= Simulator::Now() ? "" : " (expired)"));
                if ((GetMainAddress(link_tuple.neighborIfaceAddr) == nb_tuple.neighborMainAddr) &&
                    link_tuple.time >= Simulator::Now())
                {
                    NS_LOG_LOGIC("Link tuple matches neighbor "
                                 << nb_tuple.neighborMainAddr
                                 << " => adding routing table entry to neighbor");
                    lt = &link_tuple;
                    AddEntry(link_tuple.neighborIfaceAddr,
                             link_tuple.neighborIfaceAddr,
                             link_tuple.localIfaceAddr,
                             1);
                    if (link_tuple.neighborIfaceAddr == nb_tuple.neighborMainAddr)
                    {
                        nb_main_addr = true;
                    }
                }
                else
                {
                    NS_LOG_LOGIC("Link tuple: linkMainAddress= "
                                 << GetMainAddress(link_tuple.neighborIfaceAddr)
                                 << "; neighborMainAddr =  " << nb_tuple.neighborMainAddr
                                 << "; expired=" << int(link_tuple.time < Simulator::Now())
                                 << " => IGNORE");
                }
            }

            // If, in the above, no R_dest_addr is equal to the main
            // address of the neighbor, then another new routing entry
            // with MUST be added, with:
            //      R_dest_addr  = main address of the neighbor;
            //      R_next_addr  = L_neighbor_iface_addr of one of the
            //                     associated link tuple with L_time >= current time;
            //      R_dist       = 1;
            //      R_iface_addr = L_local_iface_addr of the
            //                     associated link tuple.
            if (!nb_main_addr && lt != nullptr)
            {
                NS_LOG_LOGIC("no R_dest_addr is equal to the main address of the neighbor "
                             "=> adding additional routing entry");
                AddEntry(nb_tuple.neighborMainAddr, lt->neighborIfaceAddr, lt->localIfaceAddr, 1);
            }
        }
    }

    //  3. for each node in N2, i.e., a 2-hop neighbor which is not a
    //  neighbor node or the node itself, and such that there exist at
    //  least one entry in the 2-hop neighbor set where
    //  N_neighbor_main_addr correspond to a neighbor node with
    //  willingness different of Willingness::NEVER,
    const TwoHopNeighborSet& twoHopNeighbors = m_state.GetTwoHopNeighbors();
    for (auto it = twoHopNeighbors.begin(); it != twoHopNeighbors.end(); it++)
    {
        const TwoHopNeighborTuple& nb2hop_tuple = *it;

        NS_LOG_LOGIC("Looking at two-hop neighbor tuple: " << nb2hop_tuple);

        // a 2-hop neighbor which is not a neighbor node or the node itself
        if (m_state.FindSymNeighborTuple(nb2hop_tuple.twoHopNeighborAddr))
        {
            NS_LOG_LOGIC("Two-hop neighbor tuple is also neighbor; skipped.");
            continue;
        }

        if (nb2hop_tuple.twoHopNeighborAddr == m_mainAddress)
        {
            NS_LOG_LOGIC("Two-hop neighbor is self; skipped.");
            continue;
        }

        // ...and such that there exist at least one entry in the 2-hop
        // neighbor set where N_neighbor_main_addr correspond to a
        // neighbor node with willingness different of Willingness::NEVER...
        bool nb2hopOk = false;
        for (auto neighbor = neighborSet.begin(); neighbor != neighborSet.end(); neighbor++)
        {
            if (neighbor->neighborMainAddr == nb2hop_tuple.neighborMainAddr &&
                neighbor->willingness != Willingness::NEVER)
            {
                nb2hopOk = true;
                break;
            }
        }
        if (!nb2hopOk)
        {
            NS_LOG_LOGIC("Two-hop neighbor tuple skipped: 2-hop neighbor "
                         << nb2hop_tuple.twoHopNeighborAddr << " is attached to neighbor "
                         << nb2hop_tuple.neighborMainAddr
                         << ", which was not found in the Neighbor Set.");
            continue;
        }

        // one selects one 2-hop tuple and creates one entry in the routing table with:
        //                R_dest_addr  =  the main address of the 2-hop neighbor;
        //                R_next_addr  = the R_next_addr of the entry in the
        //                               routing table with:
        //                                   R_dest_addr == N_neighbor_main_addr
        //                                                  of the 2-hop tuple;
        //                R_dist       = 2;
        //                R_iface_addr = the R_iface_addr of the entry in the
        //                               routing table with:
        //                                   R_dest_addr == N_neighbor_main_addr
        //                                                  of the 2-hop tuple;
        RoutingTableEntry entry;
        bool foundEntry = Lookup(nb2hop_tuple.neighborMainAddr, entry);
        if (foundEntry)
        {
            NS_LOG_LOGIC("Adding routing entry for two-hop neighbor.");
            AddEntry(nb2hop_tuple.twoHopNeighborAddr, entry.nextAddr, entry.interface, 2);
        }
        else
        {
            NS_LOG_LOGIC("NOT adding routing entry for two-hop neighbor ("
                         << nb2hop_tuple.twoHopNeighborAddr << " not found in the routing table)");
        }
    }

    for (uint32_t h = 2;; h++)
    {
        bool added = false;

        // 3.1. For each topology entry in the topology table, if its
        // T_dest_addr does not correspond to R_dest_addr of any
        // route entry in the routing table AND its T_last_addr
        // corresponds to R_dest_addr of a route entry whose R_dist
        // is equal to h, then a new route entry MUST be recorded in
        // the routing table (if it does not already exist)
        const TopologySet& topology = m_state.GetTopologySet();
        for (auto it = topology.begin(); it != topology.end(); it++)
        {
            const TopologyTuple& topology_tuple = *it;
            NS_LOG_LOGIC("Looking at topology tuple: " << topology_tuple);

            RoutingTableEntry destAddrEntry;
            RoutingTableEntry lastAddrEntry;
            bool have_destAddrEntry = Lookup(topology_tuple.destAddr, destAddrEntry);
            bool have_lastAddrEntry = Lookup(topology_tuple.lastAddr, lastAddrEntry);
            if (!have_destAddrEntry && have_lastAddrEntry && lastAddrEntry.distance == h)
            {
                NS_LOG_LOGIC("Adding routing table entry based on the topology tuple.");
                // then a new route entry MUST be recorded in
                //                the routing table (if it does not already exist) where:
                //                     R_dest_addr  = T_dest_addr;
                //                     R_next_addr  = R_next_addr of the recorded
                //                                    route entry where:
                //                                    R_dest_addr == T_last_addr
                //                     R_dist       = h+1; and
                //                     R_iface_addr = R_iface_addr of the recorded
                //                                    route entry where:
                //                                       R_dest_addr == T_last_addr.
                AddEntry(topology_tuple.destAddr,
                         lastAddrEntry.nextAddr,
                         lastAddrEntry.interface,
                         h + 1);
                added = true;
            }
            else
            {
                NS_LOG_LOGIC("NOT adding routing table entry based on the topology tuple: "
                             "have_destAddrEntry="
                             << have_destAddrEntry << " have_lastAddrEntry=" << have_lastAddrEntry
                             << " lastAddrEntry.distance=" << (int)lastAddrEntry.distance
                             << " (h=" << h << ")");
            }
        }

        if (!added)
        {
            break;
        }
    }

    // 4. For each entry in the multiple interface association base
    // where there exists a routing entry such that:
    // R_dest_addr == I_main_addr (of the multiple interface association entry)
    // AND there is no routing entry such that:
    // R_dest_addr == I_iface_addr
    const IfaceAssocSet& ifaceAssocSet = m_state.GetIfaceAssocSet();
    for (auto it = ifaceAssocSet.begin(); it != ifaceAssocSet.end(); it++)
    {
        const IfaceAssocTuple& tuple = *it;
        RoutingTableEntry entry1;
        RoutingTableEntry entry2;
        bool have_entry1 = Lookup(tuple.mainAddr, entry1);
        bool have_entry2 = Lookup(tuple.ifaceAddr, entry2);
        if (have_entry1 && !have_entry2)
        {
            // then a route entry is created in the routing table with:
            //       R_dest_addr  =  I_iface_addr (of the multiple interface
            //                                     association entry)
            //       R_next_addr  =  R_next_addr  (of the recorded route entry)
            //       R_dist       =  R_dist       (of the recorded route entry)
            //       R_iface_addr =  R_iface_addr (of the recorded route entry).
            AddEntry(tuple.ifaceAddr, entry1.nextAddr, entry1.interface, entry1.distance);
        }
    }

    // 5. For each tuple in the association set,
    //    If there is no entry in the routing table with:
    //        R_dest_addr     == A_network_addr/A_netmask
    //   and if the announced network is not announced by the node itself,
    //   then a new routing entry is created.
    const AssociationSet& associationSet = m_state.GetAssociationSet();

    // Clear HNA routing table
    for (uint32_t i = 0; i < m_hnaRoutingTable->GetNRoutes(); i++)
    {
        m_hnaRoutingTable->RemoveRoute(0);
    }

    for (auto it = associationSet.begin(); it != associationSet.end(); it++)
    {
        const AssociationTuple& tuple = *it;

        // Test if HNA associations received from other gateways
        // are also announced by this node. In such a case, no route
        // is created for this association tuple (go to the next one).
        bool goToNextAssociationTuple = false;
        const Associations& localHnaAssociations = m_state.GetAssociations();
        NS_LOG_DEBUG("Nb local associations: " << localHnaAssociations.size());
        for (auto assocIterator = localHnaAssociations.begin();
             assocIterator != localHnaAssociations.end();
             assocIterator++)
        {
            const Association& localHnaAssoc = *assocIterator;
            if (localHnaAssoc.networkAddr == tuple.networkAddr &&
                localHnaAssoc.netmask == tuple.netmask)
            {
                NS_LOG_DEBUG("HNA association received from another GW is part of local HNA "
                             "associations: no route added for network "
                             << tuple.networkAddr << "/" << tuple.netmask);
                goToNextAssociationTuple = true;
            }
        }
        if (goToNextAssociationTuple)
        {
            continue;
        }

        RoutingTableEntry gatewayEntry;

        bool gatewayEntryExists = Lookup(tuple.gatewayAddr, gatewayEntry);
        bool addRoute = false;

        uint32_t routeIndex = 0;

        for (routeIndex = 0; routeIndex < m_hnaRoutingTable->GetNRoutes(); routeIndex++)
        {
            Ipv4RoutingTableEntry route = m_hnaRoutingTable->GetRoute(routeIndex);
            if (route.GetDestNetwork() == tuple.networkAddr &&
                route.GetDestNetworkMask() == tuple.netmask)
            {
                break;
            }
        }

        if (routeIndex == m_hnaRoutingTable->GetNRoutes())
        {
            addRoute = true;
        }
        else if (gatewayEntryExists &&
                 m_hnaRoutingTable->GetMetric(routeIndex) > gatewayEntry.distance)
        {
            m_hnaRoutingTable->RemoveRoute(routeIndex);
            addRoute = true;
        }

        if (addRoute && gatewayEntryExists)
        {
            m_hnaRoutingTable->AddNetworkRouteTo(tuple.networkAddr,
                                                 tuple.netmask,
                                                 gatewayEntry.nextAddr,
                                                 gatewayEntry.interface,
                                                 gatewayEntry.distance);
        }
    }

    NS_LOG_DEBUG("Node " << m_mainAddress << ": RoutingTableComputation end.");
    m_routingTableChanged(GetSize());
}

void
RoutingProtocol::ProcessHello(const olsr::MessageHeader& msg,
                              const Ipv4Address& receiverIface,
                              const Ipv4Address& senderIface)
{
    NS_LOG_FUNCTION(msg << receiverIface << senderIface);

    const olsr::MessageHeader::Hello& hello = msg.GetHello();

    LinkSensing(msg, hello, receiverIface, senderIface);

#ifdef NS3_LOG_ENABLE
    {
        const LinkSet& links = m_state.GetLinks();
        NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                     << " ** BEGIN dump Link Set for OLSR Node " << m_mainAddress);
        for (auto link = links.begin(); link != links.end(); link++)
        {
            NS_LOG_DEBUG(*link);
        }
        NS_LOG_DEBUG("** END dump Link Set for OLSR Node " << m_mainAddress);

        const NeighborSet& neighbors = m_state.GetNeighbors();
        NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                     << " ** BEGIN dump Neighbor Set for OLSR Node " << m_mainAddress);
        for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); neighbor++)
        {
            NS_LOG_DEBUG(*neighbor);
        }
        NS_LOG_DEBUG("** END dump Neighbor Set for OLSR Node " << m_mainAddress);
    }
#endif // NS3_LOG_ENABLE

    PopulateNeighborSet(msg, hello);
    PopulateTwoHopNeighborSet(msg, hello);

#ifdef NS3_LOG_ENABLE
    {
        const TwoHopNeighborSet& twoHopNeighbors = m_state.GetTwoHopNeighbors();
        NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                     << " ** BEGIN dump TwoHopNeighbor Set for OLSR Node " << m_mainAddress);
        for (auto tuple = twoHopNeighbors.begin(); tuple != twoHopNeighbors.end(); tuple++)
        {
            NS_LOG_DEBUG(*tuple);
        }
        NS_LOG_DEBUG("** END dump TwoHopNeighbor Set for OLSR Node " << m_mainAddress);
    }
#endif // NS3_LOG_ENABLE

    MprComputation();
    PopulateMprSelectorSet(msg, hello);
}

void
RoutingProtocol::ProcessTc(const olsr::MessageHeader& msg, const Ipv4Address& senderIface)
{
    const olsr::MessageHeader::Tc& tc = msg.GetTc();
    Time now = Simulator::Now();

    // 1. If the sender interface of this message is not in the symmetric
    // 1-hop neighborhood of this node, the message MUST be discarded.
    const LinkTuple* link_tuple = m_state.FindSymLinkTuple(senderIface, now);
    if (link_tuple == nullptr)
    {
        return;
    }

    // 2. If there exist some tuple in the topology set where:
    //    T_last_addr == originator address AND
    //    T_seq       >  ANSN,
    // then further processing of this TC message MUST NOT be
    // performed.
    const TopologyTuple* topologyTuple =
        m_state.FindNewerTopologyTuple(msg.GetOriginatorAddress(), tc.ansn);
    if (topologyTuple != nullptr)
    {
        return;
    }

    // 3. All tuples in the topology set where:
    //    T_last_addr == originator address AND
    //    T_seq       <  ANSN
    // MUST be removed from the topology set.
    m_state.EraseOlderTopologyTuples(msg.GetOriginatorAddress(), tc.ansn);

    // 4. For each of the advertised neighbor main address received in
    // the TC message:
    for (auto i = tc.neighborAddresses.begin(); i != tc.neighborAddresses.end(); i++)
    {
        const Ipv4Address& addr = *i;
        // 4.1. If there exist some tuple in the topology set where:
        //      T_dest_addr == advertised neighbor main address, AND
        //      T_last_addr == originator address,
        // then the holding time of that tuple MUST be set to:
        //      T_time      =  current time + validity time.
        TopologyTuple* topologyTuple = m_state.FindTopologyTuple(addr, msg.GetOriginatorAddress());

        if (topologyTuple != nullptr)
        {
            topologyTuple->expirationTime = now + msg.GetVTime();
        }
        else
        {
            // 4.2. Otherwise, a new tuple MUST be recorded in the topology
            // set where:
            //      T_dest_addr = advertised neighbor main address,
            //      T_last_addr = originator address,
            //      T_seq       = ANSN,
            //      T_time      = current time + validity time.
            TopologyTuple topologyTuple;
            topologyTuple.destAddr = addr;
            topologyTuple.lastAddr = msg.GetOriginatorAddress();
            topologyTuple.sequenceNumber = tc.ansn;
            topologyTuple.expirationTime = now + msg.GetVTime();
            AddTopologyTuple(topologyTuple);

            // Schedules topology tuple deletion
            m_events.Track(Simulator::Schedule(DELAY(topologyTuple.expirationTime),
                                               &RoutingProtocol::TopologyTupleTimerExpire,
                                               this,
                                               topologyTuple.destAddr,
                                               topologyTuple.lastAddr));
        }
    }

#ifdef NS3_LOG_ENABLE
    {
        const TopologySet& topology = m_state.GetTopologySet();
        NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                     << " ** BEGIN dump TopologySet for OLSR Node " << m_mainAddress);
        for (auto tuple = topology.begin(); tuple != topology.end(); tuple++)
        {
            NS_LOG_DEBUG(*tuple);
        }
        NS_LOG_DEBUG("** END dump TopologySet Set for OLSR Node " << m_mainAddress);
    }
#endif // NS3_LOG_ENABLE
}

void
RoutingProtocol::ProcessMid(const olsr::MessageHeader& msg, const Ipv4Address& senderIface)
{
    const olsr::MessageHeader::Mid& mid = msg.GetMid();
    Time now = Simulator::Now();

    NS_LOG_DEBUG("Node " << m_mainAddress << " ProcessMid from " << senderIface);
    // 1. If the sender interface of this message is not in the symmetric
    // 1-hop neighborhood of this node, the message MUST be discarded.
    const LinkTuple* linkTuple = m_state.FindSymLinkTuple(senderIface, now);
    if (linkTuple == nullptr)
    {
        NS_LOG_LOGIC("Node " << m_mainAddress
                             << ": the sender interface of this message is not in the "
                                "symmetric 1-hop neighborhood of this node,"
                                " the message MUST be discarded.");
        return;
    }

    // 2. For each interface address listed in the MID message
    for (auto i = mid.interfaceAddresses.begin(); i != mid.interfaceAddresses.end(); i++)
    {
        bool updated = false;
        IfaceAssocSet& ifaceAssoc = m_state.GetIfaceAssocSetMutable();
        for (auto tuple = ifaceAssoc.begin(); tuple != ifaceAssoc.end(); tuple++)
        {
            if (tuple->ifaceAddr == *i && tuple->mainAddr == msg.GetOriginatorAddress())
            {
                NS_LOG_LOGIC("IfaceAssoc updated: " << *tuple);
                tuple->time = now + msg.GetVTime();
                updated = true;
            }
        }
        if (!updated)
        {
            IfaceAssocTuple tuple;
            tuple.ifaceAddr = *i;
            tuple.mainAddr = msg.GetOriginatorAddress();
            tuple.time = now + msg.GetVTime();
            AddIfaceAssocTuple(tuple);
            NS_LOG_LOGIC("New IfaceAssoc added: " << tuple);
            // Schedules iface association tuple deletion
            Simulator::Schedule(DELAY(tuple.time),
                                &RoutingProtocol::IfaceAssocTupleTimerExpire,
                                this,
                                tuple.ifaceAddr);
        }
    }

    // 3. (not part of the RFC) iterate over all NeighborTuple's and
    // TwoHopNeighborTuples, update the neighbor addresses taking into account
    // the new MID information.
    NeighborSet& neighbors = m_state.GetNeighbors();
    for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); neighbor++)
    {
        neighbor->neighborMainAddr = GetMainAddress(neighbor->neighborMainAddr);
    }

    TwoHopNeighborSet& twoHopNeighbors = m_state.GetTwoHopNeighbors();
    for (auto twoHopNeighbor = twoHopNeighbors.begin(); twoHopNeighbor != twoHopNeighbors.end();
         twoHopNeighbor++)
    {
        twoHopNeighbor->neighborMainAddr = GetMainAddress(twoHopNeighbor->neighborMainAddr);
        twoHopNeighbor->twoHopNeighborAddr = GetMainAddress(twoHopNeighbor->twoHopNeighborAddr);
    }
    NS_LOG_DEBUG("Node " << m_mainAddress << " ProcessMid from " << senderIface << " -> END.");
}

void
RoutingProtocol::ProcessHna(const olsr::MessageHeader& msg, const Ipv4Address& senderIface)
{
    const olsr::MessageHeader::Hna& hna = msg.GetHna();
    Time now = Simulator::Now();

    // 1. If the sender interface of this message is not in the symmetric
    // 1-hop neighborhood of this node, the message MUST be discarded.
    const LinkTuple* link_tuple = m_state.FindSymLinkTuple(senderIface, now);
    if (link_tuple == nullptr)
    {
        return;
    }

    // 2. Otherwise, for each (network address, netmask) pair in the
    // message:

    for (auto it = hna.associations.begin(); it != hna.associations.end(); it++)
    {
        AssociationTuple* tuple =
            m_state.FindAssociationTuple(msg.GetOriginatorAddress(), it->address, it->mask);

        // 2.1  if an entry in the association set already exists, where:
        //          A_gateway_addr == originator address
        //          A_network_addr == network address
        //          A_netmask      == netmask
        //      then the holding time for that tuple MUST be set to:
        //          A_time         =  current time + validity time
        if (tuple != nullptr)
        {
            tuple->expirationTime = now + msg.GetVTime();
        }

        // 2.2 otherwise, a new tuple MUST be recorded with:
        //          A_gateway_addr =  originator address
        //          A_network_addr =  network address
        //          A_netmask      =  netmask
        //          A_time         =  current time + validity time
        else
        {
            AssociationTuple assocTuple = {msg.GetOriginatorAddress(),
                                           it->address,
                                           it->mask,
                                           now + msg.GetVTime()};
            AddAssociationTuple(assocTuple);

            // Schedule Association Tuple deletion
            Simulator::Schedule(DELAY(assocTuple.expirationTime),
                                &RoutingProtocol::AssociationTupleTimerExpire,
                                this,
                                assocTuple.gatewayAddr,
                                assocTuple.networkAddr,
                                assocTuple.netmask);
        }
    }
}

void
RoutingProtocol::ForwardDefault(olsr::MessageHeader olsrMessage,
                                DuplicateTuple* duplicated,
                                const Ipv4Address& localIface,
                                const Ipv4Address& senderAddress)
{
    Time now = Simulator::Now();

    // If the sender interface address is not in the symmetric
    // 1-hop neighborhood the message must not be forwarded
    const LinkTuple* linkTuple = m_state.FindSymLinkTuple(senderAddress, now);
    if (linkTuple == nullptr)
    {
        return;
    }

    // If the message has already been considered for forwarding,
    // it must not be retransmitted again
    if (duplicated != nullptr && duplicated->retransmitted)
    {
        NS_LOG_LOGIC(Simulator::Now()
                     << "Node " << m_mainAddress
                     << " does not forward a message received"
                        " from "
                     << olsrMessage.GetOriginatorAddress() << " because it is duplicated");
        return;
    }

    // If the sender interface address is an interface address
    // of a MPR selector of this node and ttl is greater than 1,
    // the message must be retransmitted
    bool retransmitted = false;
    if (olsrMessage.GetTimeToLive() > 1)
    {
        const MprSelectorTuple* mprselTuple =
            m_state.FindMprSelectorTuple(GetMainAddress(senderAddress));
        if (mprselTuple != nullptr)
        {
            olsrMessage.SetTimeToLive(olsrMessage.GetTimeToLive() - 1);
            olsrMessage.SetHopCount(olsrMessage.GetHopCount() + 1);
            // We have to introduce a random delay to avoid
            // synchronization with neighbors.
            QueueMessage(olsrMessage, JITTER);
            retransmitted = true;
        }
    }

    // Update duplicate tuple...
    if (duplicated != nullptr)
    {
        duplicated->expirationTime = now + OLSR_DUP_HOLD_TIME;
        duplicated->retransmitted = retransmitted;
        duplicated->ifaceList.push_back(localIface);
    }
    // ...or create a new one
    else
    {
        DuplicateTuple newDup;
        newDup.address = olsrMessage.GetOriginatorAddress();
        newDup.sequenceNumber = olsrMessage.GetMessageSequenceNumber();
        newDup.expirationTime = now + OLSR_DUP_HOLD_TIME;
        newDup.retransmitted = retransmitted;
        newDup.ifaceList.push_back(localIface);
        AddDuplicateTuple(newDup);
        // Schedule dup tuple deletion
        Simulator::Schedule(OLSR_DUP_HOLD_TIME,
                            &RoutingProtocol::DupTupleTimerExpire,
                            this,
                            newDup.address,
                            newDup.sequenceNumber);
    }
}

void
RoutingProtocol::QueueMessage(const olsr::MessageHeader& message, Time delay)
{
    m_queuedMessages.push_back(message);
    if (not m_queuedMessagesTimer.IsRunning())
    {
        m_queuedMessagesTimer.SetDelay(delay);
        m_queuedMessagesTimer.Schedule();
    }
}

void
RoutingProtocol::SendPacket(Ptr<Packet> packet, const MessageList& containedMessages)
{
    NS_LOG_DEBUG("OLSR node " << m_mainAddress << " sending a OLSR packet");

    // Add a header
    olsr::PacketHeader header;
    header.SetPacketLength(header.GetSerializedSize() + packet->GetSize());
    header.SetPacketSequenceNumber(GetPacketSequenceNumber());
    packet->AddHeader(header);

    // Trace it
    m_txPacketTrace(header, containedMessages);

    // Send it
    for (auto i = m_sendSockets.begin(); i != m_sendSockets.end(); i++)
    {
        Ptr<Packet> pkt = packet->Copy();
        Ipv4Address bcast = i->second.GetLocal().GetSubnetDirectedBroadcast(i->second.GetMask());
        i->first->SendTo(pkt, 0, InetSocketAddress(bcast, OLSR_PORT_NUMBER));
    }
}

void
RoutingProtocol::SendQueuedMessages()
{
    Ptr<Packet> packet = Create<Packet>();
    int numMessages = 0;

    NS_LOG_DEBUG("Olsr node " << m_mainAddress << ": SendQueuedMessages");

    MessageList msglist;

    for (auto message = m_queuedMessages.begin(); message != m_queuedMessages.end(); message++)
    {
        Ptr<Packet> p = Create<Packet>();
        p->AddHeader(*message);
        packet->AddAtEnd(p);
        msglist.push_back(*message);
        if (++numMessages == OLSR_MAX_MSGS)
        {
            SendPacket(packet, msglist);
            msglist.clear();
            // Reset variables for next packet
            numMessages = 0;
            packet = Create<Packet>();
        }
    }

    if (packet->GetSize())
    {
        SendPacket(packet, msglist);
    }

    m_queuedMessages.clear();
}

void
RoutingProtocol::SendHello()
{
    NS_LOG_FUNCTION(this);

    olsr::MessageHeader msg;
    Time now = Simulator::Now();

    msg.SetVTime(OLSR_NEIGHB_HOLD_TIME);
    msg.SetOriginatorAddress(m_mainAddress);
    msg.SetTimeToLive(1);
    msg.SetHopCount(0);
    msg.SetMessageSequenceNumber(GetMessageSequenceNumber());
    olsr::MessageHeader::Hello& hello = msg.GetHello();

    hello.SetHTime(m_helloInterval);
    hello.willingness = m_willingness;

    std::vector<olsr::MessageHeader::Hello::LinkMessage>& linkMessages = hello.linkMessages;

    const LinkSet& links = m_state.GetLinks();
    for (auto link_tuple = links.begin(); link_tuple != links.end(); link_tuple++)
    {
        if (!(GetMainAddress(link_tuple->localIfaceAddr) == m_mainAddress &&
              link_tuple->time >= now))
        {
            continue;
        }

        LinkType linkType;
        NeighborType neighborType;

        // Establishes link type
        if (link_tuple->symTime >= now)
        {
            linkType = LinkType::SYM_LINK;
        }
        else if (link_tuple->asymTime >= now)
        {
            linkType = LinkType::ASYM_LINK;
        }
        else
        {
            linkType = LinkType::LOST_LINK;
        }
        // Establishes neighbor type.
        if (m_state.FindMprAddress(GetMainAddress(link_tuple->neighborIfaceAddr)))
        {
            neighborType = NeighborType::MPR_NEIGH;
            NS_LOG_DEBUG("I consider neighbor " << GetMainAddress(link_tuple->neighborIfaceAddr)
                                                << " to be MPR_NEIGH.");
        }
        else
        {
            bool ok = false;
            for (auto nb_tuple = m_state.GetNeighbors().begin();
                 nb_tuple != m_state.GetNeighbors().end();
                 nb_tuple++)
            {
                if (nb_tuple->neighborMainAddr == GetMainAddress(link_tuple->neighborIfaceAddr))
                {
                    if (nb_tuple->status == NeighborTuple::STATUS_SYM)
                    {
                        NS_LOG_DEBUG("I consider neighbor "
                                     << GetMainAddress(link_tuple->neighborIfaceAddr)
                                     << " to be SYM_NEIGH.");
                        neighborType = NeighborType::SYM_NEIGH;
                    }
                    else if (nb_tuple->status == NeighborTuple::STATUS_NOT_SYM)
                    {
                        neighborType = NeighborType::NOT_NEIGH;
                        NS_LOG_DEBUG("I consider neighbor "
                                     << GetMainAddress(link_tuple->neighborIfaceAddr)
                                     << " to be NOT_NEIGH.");
                    }
                    else
                    {
                        NS_FATAL_ERROR("There is a neighbor tuple with an unknown status!\n");
                    }
                    ok = true;
                    break;
                }
            }
            if (!ok)
            {
                NS_LOG_WARN("I don't know the neighbor "
                            << GetMainAddress(link_tuple->neighborIfaceAddr) << "!!!");
                continue;
            }
        }

        olsr::MessageHeader::Hello::LinkMessage linkMessage;
        linkMessage.linkCode = (static_cast<uint8_t>(linkType) & 0x03) |
                               ((static_cast<uint8_t>(neighborType) << 2) & 0x0f);
        linkMessage.neighborInterfaceAddresses.push_back(link_tuple->neighborIfaceAddr);

        std::vector<Ipv4Address> interfaces =
            m_state.FindNeighborInterfaces(link_tuple->neighborIfaceAddr);

        linkMessage.neighborInterfaceAddresses.insert(linkMessage.neighborInterfaceAddresses.end(),
                                                      interfaces.begin(),
                                                      interfaces.end());

        linkMessages.push_back(linkMessage);
    }
    NS_LOG_DEBUG("OLSR HELLO message size: " << int(msg.GetSerializedSize()) << " (with "
                                             << int(linkMessages.size()) << " link messages)");
    QueueMessage(msg, JITTER);
}

void
RoutingProtocol::SendTc()
{
    NS_LOG_FUNCTION(this);

    olsr::MessageHeader msg;

    msg.SetVTime(OLSR_TOP_HOLD_TIME);
    msg.SetOriginatorAddress(m_mainAddress);
    msg.SetTimeToLive(255);
    msg.SetHopCount(0);
    msg.SetMessageSequenceNumber(GetMessageSequenceNumber());

    olsr::MessageHeader::Tc& tc = msg.GetTc();
    tc.ansn = m_ansn;

    for (auto mprsel_tuple = m_state.GetMprSelectors().begin();
         mprsel_tuple != m_state.GetMprSelectors().end();
         mprsel_tuple++)
    {
        tc.neighborAddresses.push_back(mprsel_tuple->mainAddr);
    }
    QueueMessage(msg, JITTER);
}

void
RoutingProtocol::SendMid()
{
    olsr::MessageHeader msg;
    olsr::MessageHeader::Mid& mid = msg.GetMid();

    // A node which has only a single interface address participating in
    // the MANET (i.e., running OLSR), MUST NOT generate any MID
    // message.

    // A node with several interfaces, where only one is participating
    // in the MANET and running OLSR (e.g., a node is connected to a
    // wired network as well as to a MANET) MUST NOT generate any MID
    // messages.

    // A node with several interfaces, where more than one is
    // participating in the MANET and running OLSR MUST generate MID
    // messages as specified.

    // [ Note: assuming here that all interfaces participate in the
    // MANET; later we may want to make this configurable. ]

    Ipv4Address loopback("127.0.0.1");
    for (uint32_t i = 0; i < m_ipv4->GetNInterfaces(); i++)
    {
        Ipv4Address addr = m_ipv4->GetAddress(i, 0).GetLocal();
        if (addr != m_mainAddress && addr != loopback &&
            m_interfaceExclusions.find(i) == m_interfaceExclusions.end())
        {
            mid.interfaceAddresses.push_back(addr);
        }
    }
    if (mid.interfaceAddresses.empty())
    {
        return;
    }

    msg.SetVTime(OLSR_MID_HOLD_TIME);
    msg.SetOriginatorAddress(m_mainAddress);
    msg.SetTimeToLive(255);
    msg.SetHopCount(0);
    msg.SetMessageSequenceNumber(GetMessageSequenceNumber());

    QueueMessage(msg, JITTER);
}

void
RoutingProtocol::SendHna()
{
    olsr::MessageHeader msg;

    msg.SetVTime(OLSR_HNA_HOLD_TIME);
    msg.SetOriginatorAddress(m_mainAddress);
    msg.SetTimeToLive(255);
    msg.SetHopCount(0);
    msg.SetMessageSequenceNumber(GetMessageSequenceNumber());
    olsr::MessageHeader::Hna& hna = msg.GetHna();

    std::vector<olsr::MessageHeader::Hna::Association>& associations = hna.associations;

    // Add all local HNA associations to the HNA message
    const Associations& localHnaAssociations = m_state.GetAssociations();
    for (auto it = localHnaAssociations.begin(); it != localHnaAssociations.end(); it++)
    {
        olsr::MessageHeader::Hna::Association assoc = {it->networkAddr, it->netmask};
        associations.push_back(assoc);
    }
    // If there is no HNA associations to send, return without queuing the message
    if (associations.empty())
    {
        return;
    }

    // Else, queue the message to be sent later on
    QueueMessage(msg, JITTER);
}

void
RoutingProtocol::AddHostNetworkAssociation(Ipv4Address networkAddr, Ipv4Mask netmask)
{
    // Check if the (networkAddr, netmask) tuple already exist
    // in the list of local HNA associations
    const Associations& localHnaAssociations = m_state.GetAssociations();
    for (auto assocIterator = localHnaAssociations.begin();
         assocIterator != localHnaAssociations.end();
         assocIterator++)
    {
        const Association& localHnaAssoc = *assocIterator;
        if (localHnaAssoc.networkAddr == networkAddr && localHnaAssoc.netmask == netmask)
        {
            NS_LOG_INFO("HNA association for network " << networkAddr << "/" << netmask
                                                       << " already exists.");
            return;
        }
    }
    // If the tuple does not already exist, add it to the list of local HNA associations.
    NS_LOG_INFO("Adding HNA association for network " << networkAddr << "/" << netmask << ".");
    m_state.InsertAssociation(Association{networkAddr, netmask});
}

void
RoutingProtocol::RemoveHostNetworkAssociation(Ipv4Address networkAddr, Ipv4Mask netmask)
{
    NS_LOG_INFO("Removing HNA association for network " << networkAddr << "/" << netmask << ".");
    m_state.EraseAssociation(Association{networkAddr, netmask});
}

void
RoutingProtocol::SetRoutingTableAssociation(Ptr<Ipv4StaticRouting> routingTable)
{
    // If a routing table has already been associated, remove
    // corresponding entries from the list of local HNA associations
    if (m_routingTableAssociation)
    {
        NS_LOG_INFO("Removing HNA entries coming from the old routing table association.");
        for (uint32_t i = 0; i < m_routingTableAssociation->GetNRoutes(); i++)
        {
            Ipv4RoutingTableEntry route = m_routingTableAssociation->GetRoute(i);
            // If the outgoing interface for this route is a non-olsr interface
            if (UsesNonOlsrOutgoingInterface(route))
            {
                // remove the corresponding entry
                RemoveHostNetworkAssociation(route.GetDestNetwork(), route.GetDestNetworkMask());
            }
        }
    }

    // Sets the routingTableAssociation to its new value
    m_routingTableAssociation = routingTable;

    // Iterate over entries of the associated routing table and
    // add the routes using non-olsr outgoing interfaces to the list
    // of local HNA associations
    NS_LOG_DEBUG("Nb local associations before adding some entries from"
                 " the associated routing table: "
                 << m_state.GetAssociations().size());
    for (uint32_t i = 0; i < m_routingTableAssociation->GetNRoutes(); i++)
    {
        Ipv4RoutingTableEntry route = m_routingTableAssociation->GetRoute(i);
        Ipv4Address destNetworkAddress = route.GetDestNetwork();
        Ipv4Mask destNetmask = route.GetDestNetworkMask();

        // If the outgoing interface for this route is a non-olsr interface,
        if (UsesNonOlsrOutgoingInterface(route))
        {
            // Add this entry's network address and netmask to the list of local HNA entries
            AddHostNetworkAssociation(destNetworkAddress, destNetmask);
        }
    }
    NS_LOG_DEBUG("Nb local associations after having added some entries from "
                 "the associated routing table: "
                 << m_state.GetAssociations().size());
}

bool
RoutingProtocol::UsesNonOlsrOutgoingInterface(const Ipv4RoutingTableEntry& route)
{
    auto ci = m_interfaceExclusions.find(route.GetInterface());
    // The outgoing interface is a non-OLSR interface if a match is found
    // before reaching the end of the list of excluded interfaces
    return ci != m_interfaceExclusions.end();
}

void
RoutingProtocol::LinkSensing(const olsr::MessageHeader& msg,
                             const olsr::MessageHeader::Hello& hello,
                             const Ipv4Address& receiverIface,
                             const Ipv4Address& senderIface)
{
    Time now = Simulator::Now();
    bool updated = false;
    bool created = false;
    NS_LOG_DEBUG("@" << now.As(Time::S) << ": Olsr node " << m_mainAddress
                     << ": LinkSensing(receiverIface=" << receiverIface
                     << ", senderIface=" << senderIface << ") BEGIN");

    NS_ASSERT(msg.GetVTime().IsStrictlyPositive());
    LinkTuple* link_tuple = m_state.FindLinkTuple(senderIface);
    if (link_tuple == nullptr)
    {
        LinkTuple newLinkTuple;
        // We have to create a new tuple
        newLinkTuple.neighborIfaceAddr = senderIface;
        newLinkTuple.localIfaceAddr = receiverIface;
        newLinkTuple.symTime = now - Seconds(1);
        newLinkTuple.time = now + msg.GetVTime();
        link_tuple = &m_state.InsertLinkTuple(newLinkTuple);
        created = true;
        NS_LOG_LOGIC("Existing link tuple did not exist => creating new one");
    }
    else
    {
        NS_LOG_LOGIC("Existing link tuple already exists => will update it");
        updated = true;
    }

    link_tuple->asymTime = now + msg.GetVTime();
    for (auto linkMessage = hello.linkMessages.begin(); linkMessage != hello.linkMessages.end();
         linkMessage++)
    {
        auto linkType = LinkType(linkMessage->linkCode & 0x03);
        auto neighborType = NeighborType((linkMessage->linkCode >> 2) & 0x03);

        NS_LOG_DEBUG("Looking at HELLO link messages with Link Type "
                     << linkType << " and Neighbor Type " << neighborType);

        // We must not process invalid advertised links
        if ((linkType == LinkType::SYM_LINK && neighborType == NeighborType::NOT_NEIGH) ||
            (neighborType != NeighborType::SYM_NEIGH && neighborType != NeighborType::MPR_NEIGH &&
             neighborType != NeighborType::NOT_NEIGH))
        {
            NS_LOG_LOGIC("HELLO link code is invalid => IGNORING");
            continue;
        }

        for (auto neighIfaceAddr = linkMessage->neighborInterfaceAddresses.begin();
             neighIfaceAddr != linkMessage->neighborInterfaceAddresses.end();
             neighIfaceAddr++)
        {
            NS_LOG_DEBUG("   -> Neighbor: " << *neighIfaceAddr);
            if (*neighIfaceAddr == receiverIface)
            {
                if (linkType == LinkType::LOST_LINK)
                {
                    NS_LOG_LOGIC("link is LOST => expiring it");
                    link_tuple->symTime = now - Seconds(1);
                    updated = true;
                }
                else if (linkType == LinkType::SYM_LINK || linkType == LinkType::ASYM_LINK)
                {
                    NS_LOG_DEBUG(*link_tuple << ": link is SYM or ASYM => should become SYM now"
                                                " (symTime being increased to "
                                             << now + msg.GetVTime());
                    link_tuple->symTime = now + msg.GetVTime();
                    link_tuple->time = link_tuple->symTime + OLSR_NEIGHB_HOLD_TIME;
                    updated = true;
                }
                else
                {
                    NS_FATAL_ERROR("bad link type");
                }
                break;
            }
            else
            {
                NS_LOG_DEBUG("     \\-> *neighIfaceAddr (" << *neighIfaceAddr
                                                           << " != receiverIface (" << receiverIface
                                                           << ") => IGNORING!");
            }
        }
        NS_LOG_DEBUG("Link tuple updated: " << int(updated));
    }
    link_tuple->time = std::max(link_tuple->time, link_tuple->asymTime);

    if (updated)
    {
        LinkTupleUpdated(*link_tuple, hello.willingness);
    }

    // Schedules link tuple deletion
    if (created)
    {
        LinkTupleAdded(*link_tuple, hello.willingness);
        m_events.Track(Simulator::Schedule(DELAY(std::min(link_tuple->time, link_tuple->symTime)),
                                           &RoutingProtocol::LinkTupleTimerExpire,
                                           this,
                                           link_tuple->neighborIfaceAddr));
    }
    NS_LOG_DEBUG("@" << now.As(Time::S) << ": Olsr node " << m_mainAddress << ": LinkSensing END");
}

void
RoutingProtocol::PopulateNeighborSet(const olsr::MessageHeader& msg,
                                     const olsr::MessageHeader::Hello& hello)
{
    NeighborTuple* nb_tuple = m_state.FindNeighborTuple(msg.GetOriginatorAddress());
    if (nb_tuple != nullptr)
    {
        nb_tuple->willingness = hello.willingness;
    }
}

void
RoutingProtocol::PopulateTwoHopNeighborSet(const olsr::MessageHeader& msg,
                                           const olsr::MessageHeader::Hello& hello)
{
    Time now = Simulator::Now();

    NS_LOG_DEBUG("Olsr node " << m_mainAddress << ": PopulateTwoHopNeighborSet BEGIN");

    for (auto link_tuple = m_state.GetLinks().begin(); link_tuple != m_state.GetLinks().end();
         link_tuple++)
    {
        NS_LOG_LOGIC("Looking at link tuple: " << *link_tuple);
        if (GetMainAddress(link_tuple->neighborIfaceAddr) != msg.GetOriginatorAddress())
        {
            NS_LOG_LOGIC(
                "Link tuple ignored: "
                "GetMainAddress (link_tuple->neighborIfaceAddr) != msg.GetOriginatorAddress ()");
            NS_LOG_LOGIC("(GetMainAddress("
                         << link_tuple->neighborIfaceAddr
                         << "): " << GetMainAddress(link_tuple->neighborIfaceAddr)
                         << "; msg.GetOriginatorAddress (): " << msg.GetOriginatorAddress());
            continue;
        }

        if (link_tuple->symTime < now)
        {
            NS_LOG_LOGIC("Link tuple ignored: expired.");
            continue;
        }

        for (auto linkMessage = hello.linkMessages.begin(); linkMessage != hello.linkMessages.end();
             linkMessage++)
        {
            auto neighborType = NeighborType((linkMessage->linkCode >> 2) & 0x3);
            NS_LOG_DEBUG(
                "Looking at Link Message from HELLO message: neighborType=" << neighborType);

            for (auto nb2hop_addr_iter = linkMessage->neighborInterfaceAddresses.begin();
                 nb2hop_addr_iter != linkMessage->neighborInterfaceAddresses.end();
                 nb2hop_addr_iter++)
            {
                Ipv4Address nb2hop_addr = GetMainAddress(*nb2hop_addr_iter);
                NS_LOG_DEBUG("Looking at 2-hop neighbor address from HELLO message: "
                             << *nb2hop_addr_iter << " (main address is " << nb2hop_addr << ")");
                if (neighborType == NeighborType::SYM_NEIGH ||
                    neighborType == NeighborType::MPR_NEIGH)
                {
                    // If the main address of the 2-hop neighbor address == main address
                    // of the receiving node, silently discard the 2-hop
                    // neighbor address.
                    if (nb2hop_addr == m_mainAddress)
                    {
                        NS_LOG_LOGIC("Ignoring 2-hop neighbor (it is the node itself)");
                        continue;
                    }

                    // Otherwise, a 2-hop tuple is created
                    TwoHopNeighborTuple* nb2hop_tuple =
                        m_state.FindTwoHopNeighborTuple(msg.GetOriginatorAddress(), nb2hop_addr);
                    NS_LOG_LOGIC("Adding the 2-hop neighbor"
                                 << (nb2hop_tuple ? " (refreshing existing entry)" : ""));
                    if (nb2hop_tuple == nullptr)
                    {
                        TwoHopNeighborTuple new_nb2hop_tuple;
                        new_nb2hop_tuple.neighborMainAddr = msg.GetOriginatorAddress();
                        new_nb2hop_tuple.twoHopNeighborAddr = nb2hop_addr;
                        new_nb2hop_tuple.expirationTime = now + msg.GetVTime();
                        AddTwoHopNeighborTuple(new_nb2hop_tuple);
                        // Schedules nb2hop tuple deletion
                        m_events.Track(Simulator::Schedule(DELAY(new_nb2hop_tuple.expirationTime),
                                                           &RoutingProtocol::Nb2hopTupleTimerExpire,
                                                           this,
                                                           new_nb2hop_tuple.neighborMainAddr,
                                                           new_nb2hop_tuple.twoHopNeighborAddr));
                    }
                    else
                    {
                        nb2hop_tuple->expirationTime = now + msg.GetVTime();
                    }
                }
                else if (neighborType == NeighborType::NOT_NEIGH)
                {
                    // For each 2-hop node listed in the HELLO message
                    // with Neighbor Type equal to NOT_NEIGH all 2-hop
                    // tuples where: N_neighbor_main_addr == Originator
                    // Address AND N_2hop_addr == main address of the
                    // 2-hop neighbor are deleted.
                    NS_LOG_LOGIC(
                        "2-hop neighbor is NOT_NEIGH => deleting matching 2-hop neighbor state");
                    m_state.EraseTwoHopNeighborTuples(msg.GetOriginatorAddress(), nb2hop_addr);
                }
                else
                {
                    NS_LOG_LOGIC("*** WARNING *** Ignoring link message (inside HELLO) with bad"
                                 " neighbor type value: "
                                 << neighborType);
                }
            }
        }
    }

    NS_LOG_DEBUG("Olsr node " << m_mainAddress << ": PopulateTwoHopNeighborSet END");
}

void
RoutingProtocol::PopulateMprSelectorSet(const olsr::MessageHeader& msg,
                                        const olsr::MessageHeader::Hello& hello)
{
    NS_LOG_FUNCTION(this);

    Time now = Simulator::Now();

    for (auto linkMessage = hello.linkMessages.begin(); linkMessage != hello.linkMessages.end();
         linkMessage++)
    {
        auto neighborType = NeighborType(linkMessage->linkCode >> 2);
        if (neighborType == NeighborType::MPR_NEIGH)
        {
            NS_LOG_DEBUG("Processing a link message with neighbor type MPR_NEIGH");

            for (auto nb_iface_addr = linkMessage->neighborInterfaceAddresses.begin();
                 nb_iface_addr != linkMessage->neighborInterfaceAddresses.end();
                 nb_iface_addr++)
            {
                if (GetMainAddress(*nb_iface_addr) == m_mainAddress)
                {
                    NS_LOG_DEBUG("Adding entry to mpr selector set for neighbor "
                                 << *nb_iface_addr);

                    // We must create a new entry into the mpr selector set
                    MprSelectorTuple* existing_mprsel_tuple =
                        m_state.FindMprSelectorTuple(msg.GetOriginatorAddress());
                    if (existing_mprsel_tuple == nullptr)
                    {
                        MprSelectorTuple mprsel_tuple;

                        mprsel_tuple.mainAddr = msg.GetOriginatorAddress();
                        mprsel_tuple.expirationTime = now + msg.GetVTime();
                        AddMprSelectorTuple(mprsel_tuple);

                        // Schedules mpr selector tuple deletion
                        m_events.Track(Simulator::Schedule(DELAY(mprsel_tuple.expirationTime),
                                                           &RoutingProtocol::MprSelTupleTimerExpire,
                                                           this,
                                                           mprsel_tuple.mainAddr));
                    }
                    else
                    {
                        existing_mprsel_tuple->expirationTime = now + msg.GetVTime();
                    }
                }
            }
        }
    }
    NS_LOG_DEBUG("Computed MPR selector set for node " << m_mainAddress << ": "
                                                       << m_state.PrintMprSelectorSet());
}

#if 0
///
/// @brief Drops a given packet because it couldn't be delivered to the corresponding
/// destination by the MAC layer. This may cause a neighbor loss, and appropriate
/// actions are then taken.
///
/// @param p the packet which couldn't be delivered by the MAC layer.
///
void
OLSR::mac_failed(Ptr<Packet> p)
{
    double now = Simulator::Now ();
    struct hdr_ip* ih = HDR_IP (p);
    struct hdr_cmn* ch = HDR_CMN (p);

    debug("%f: Node %d MAC Layer detects a breakage on link to %d\n",
          now,
          OLSR::node_id (ra_addr ()),
          OLSR::node_id (ch->next_hop ()));

    if ((uint32_t)ih->daddr () == IP_BROADCAST)
    {
        drop (p, DROP_RTR_MAC_CALLBACK);
        return;
    }

    OLSR_link_tuple* link_tuple = state_.find_link_tuple(ch->next_hop());
    if (link_tuple)
    {
        link_tuple->lost_time() = now + OLSR_NEIGHB_HOLD_TIME;
        link_tuple->time() = now + OLSR_NEIGHB_HOLD_TIME;
        nb_loss(link_tuple);
    }
    drop(p, DROP_RTR_MAC_CALLBACK);
}
#endif

void
RoutingProtocol::NeighborLoss(const LinkTuple& tuple)
{
    NS_LOG_DEBUG(Simulator::Now().As(Time::S) << ": OLSR Node " << m_mainAddress << " LinkTuple "
                                              << tuple.neighborIfaceAddr << " -> neighbor loss.");
    LinkTupleUpdated(tuple, Willingness::DEFAULT);
    m_state.EraseTwoHopNeighborTuples(GetMainAddress(tuple.neighborIfaceAddr));
    m_state.EraseMprSelectorTuples(GetMainAddress(tuple.neighborIfaceAddr));

    MprComputation();
    RoutingTableComputation();
}

void
RoutingProtocol::AddDuplicateTuple(const DuplicateTuple& tuple)
{
    /*debug("%f: Node %d adds dup tuple: addr = %d seq_num = %d\n",
            Simulator::Now (),
            OLSR::node_id(ra_addr()),
            OLSR::node_id(tuple->addr()),
            tuple->seq_num());*/
    m_state.InsertDuplicateTuple(tuple);
}

void
RoutingProtocol::RemoveDuplicateTuple(const DuplicateTuple& tuple)
{
    /*debug("%f: Node %d removes dup tuple: addr = %d seq_num = %d\n",
      Simulator::Now (),
      OLSR::node_id(ra_addr()),
      OLSR::node_id(tuple->addr()),
      tuple->seq_num());*/
    m_state.EraseDuplicateTuple(tuple);
}

void
RoutingProtocol::LinkTupleAdded(const LinkTuple& tuple, Willingness willingness)
{
    // Creates associated neighbor tuple
    NeighborTuple nb_tuple;
    nb_tuple.neighborMainAddr = GetMainAddress(tuple.neighborIfaceAddr);
    nb_tuple.willingness = willingness;

    if (tuple.symTime >= Simulator::Now())
    {
        nb_tuple.status = NeighborTuple::STATUS_SYM;
    }
    else
    {
        nb_tuple.status = NeighborTuple::STATUS_NOT_SYM;
    }

    AddNeighborTuple(nb_tuple);
}

void
RoutingProtocol::RemoveLinkTuple(const LinkTuple& tuple)
{
    NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                 << ": OLSR Node " << m_mainAddress << " LinkTuple " << tuple << " REMOVED.");

    m_state.EraseNeighborTuple(GetMainAddress(tuple.neighborIfaceAddr));
    m_state.EraseLinkTuple(tuple);
}

void
RoutingProtocol::LinkTupleUpdated(const LinkTuple& tuple, Willingness willingness)
{
    // Each time a link tuple changes, the associated neighbor tuple must be recomputed

    NS_LOG_DEBUG(Simulator::Now().As(Time::S)
                 << ": OLSR Node " << m_mainAddress << " LinkTuple " << tuple << " UPDATED.");

    NeighborTuple* nb_tuple = m_state.FindNeighborTuple(GetMainAddress(tuple.neighborIfaceAddr));

    if (nb_tuple == nullptr)
    {
        LinkTupleAdded(tuple, willingness);
        nb_tuple = m_state.FindNeighborTuple(GetMainAddress(tuple.neighborIfaceAddr));
    }

    if (nb_tuple != nullptr)
    {
        int statusBefore = nb_tuple->status;

        bool hasSymmetricLink = false;

        const LinkSet& linkSet = m_state.GetLinks();
        for (auto it = linkSet.begin(); it != linkSet.end(); it++)
        {
            const LinkTuple& link_tuple = *it;
            if (GetMainAddress(link_tuple.neighborIfaceAddr) == nb_tuple->neighborMainAddr &&
                link_tuple.symTime >= Simulator::Now())
            {
                hasSymmetricLink = true;
                break;
            }
        }

        if (hasSymmetricLink)
        {
            nb_tuple->status = NeighborTuple::STATUS_SYM;
            NS_LOG_DEBUG(*nb_tuple << "->status = STATUS_SYM; changed:"
                                   << int(statusBefore != nb_tuple->status));
        }
        else
        {
            nb_tuple->status = NeighborTuple::STATUS_NOT_SYM;
            NS_LOG_DEBUG(*nb_tuple << "->status = STATUS_NOT_SYM; changed:"
                                   << int(statusBefore != nb_tuple->status));
        }
    }
    else
    {
        NS_LOG_WARN("ERROR! Wanted to update a NeighborTuple but none was found!");
    }
}

void
RoutingProtocol::AddNeighborTuple(const NeighborTuple& tuple)
{
    //   debug("%f: Node %d adds neighbor tuple: nb_addr = %d status = %s\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->neighborMainAddr),
    //         ((tuple->status() == OLSR_STATUS_SYM) ? "sym" : "not_sym"));

    m_state.InsertNeighborTuple(tuple);
    IncrementAnsn();
}

void
RoutingProtocol::RemoveNeighborTuple(const NeighborTuple& tuple)
{
    //   debug("%f: Node %d removes neighbor tuple: nb_addr = %d status = %s\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->neighborMainAddr),
    //         ((tuple->status() == OLSR_STATUS_SYM) ? "sym" : "not_sym"));

    m_state.EraseNeighborTuple(tuple);
    IncrementAnsn();
}

void
RoutingProtocol::AddTwoHopNeighborTuple(const TwoHopNeighborTuple& tuple)
{
    //   debug("%f: Node %d adds 2-hop neighbor tuple: nb_addr = %d nb2hop_addr = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->neighborMainAddr),
    //         OLSR::node_id(tuple->twoHopNeighborAddr));

    m_state.InsertTwoHopNeighborTuple(tuple);
}

void
RoutingProtocol::RemoveTwoHopNeighborTuple(const TwoHopNeighborTuple& tuple)
{
    //   debug("%f: Node %d removes 2-hop neighbor tuple: nb_addr = %d nb2hop_addr = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->neighborMainAddr),
    //         OLSR::node_id(tuple->twoHopNeighborAddr));

    m_state.EraseTwoHopNeighborTuple(tuple);
}

void
RoutingProtocol::IncrementAnsn()
{
    m_ansn = (m_ansn + 1) % (OLSR_MAX_SEQ_NUM + 1);
}

void
RoutingProtocol::AddMprSelectorTuple(const MprSelectorTuple& tuple)
{
    //   debug("%f: Node %d adds MPR selector tuple: nb_addr = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->main_addr()));

    m_state.InsertMprSelectorTuple(tuple);
    IncrementAnsn();
}

void
RoutingProtocol::RemoveMprSelectorTuple(const MprSelectorTuple& tuple)
{
    //   debug("%f: Node %d removes MPR selector tuple: nb_addr = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->main_addr()));

    m_state.EraseMprSelectorTuple(tuple);
    IncrementAnsn();
}

void
RoutingProtocol::AddTopologyTuple(const TopologyTuple& tuple)
{
    //   debug("%f: Node %d adds topology tuple: dest_addr = %d last_addr = %d seq = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->dest_addr()),
    //         OLSR::node_id(tuple->last_addr()),
    //         tuple->seq());

    m_state.InsertTopologyTuple(tuple);
}

void
RoutingProtocol::RemoveTopologyTuple(const TopologyTuple& tuple)
{
    //   debug("%f: Node %d removes topology tuple: dest_addr = %d last_addr = %d seq = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->dest_addr()),
    //         OLSR::node_id(tuple->last_addr()),
    //         tuple->seq());

    m_state.EraseTopologyTuple(tuple);
}

void
RoutingProtocol::AddIfaceAssocTuple(const IfaceAssocTuple& tuple)
{
    //   debug("%f: Node %d adds iface association tuple: main_addr = %d iface_addr = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->main_addr()),
    //         OLSR::node_id(tuple->iface_addr()));

    m_state.InsertIfaceAssocTuple(tuple);
}

void
RoutingProtocol::RemoveIfaceAssocTuple(const IfaceAssocTuple& tuple)
{
    //   debug("%f: Node %d removes iface association tuple: main_addr = %d iface_addr = %d\n",
    //         Simulator::Now (),
    //         OLSR::node_id(ra_addr()),
    //         OLSR::node_id(tuple->main_addr()),
    //         OLSR::node_id(tuple->iface_addr()));

    m_state.EraseIfaceAssocTuple(tuple);
}

void
RoutingProtocol::AddAssociationTuple(const AssociationTuple& tuple)
{
    m_state.InsertAssociationTuple(tuple);
}

void
RoutingProtocol::RemoveAssociationTuple(const AssociationTuple& tuple)
{
    m_state.EraseAssociationTuple(tuple);
}

uint16_t
RoutingProtocol::GetPacketSequenceNumber()
{
    m_packetSequenceNumber = (m_packetSequenceNumber + 1) % (OLSR_MAX_SEQ_NUM + 1);
    return m_packetSequenceNumber;
}

uint16_t
RoutingProtocol::GetMessageSequenceNumber()
{
    m_messageSequenceNumber = (m_messageSequenceNumber + 1) % (OLSR_MAX_SEQ_NUM + 1);
    return m_messageSequenceNumber;
}

void
RoutingProtocol::HelloTimerExpire()
{
    SendHello();
    m_helloTimer.Schedule(m_helloInterval);
}

void
RoutingProtocol::TcTimerExpire()
{
    if (!m_state.GetMprSelectors().empty())
    {
        SendTc();
    }
    else
    {
        NS_LOG_DEBUG("Not sending any TC, no one selected me as MPR.");
    }
    m_tcTimer.Schedule(m_tcInterval);
}

void
RoutingProtocol::MidTimerExpire()
{
    SendMid();
    m_midTimer.Schedule(m_midInterval);
}

void
RoutingProtocol::HnaTimerExpire()
{
    if (!m_state.GetAssociations().empty())
    {
        SendHna();
    }
    else
    {
        NS_LOG_DEBUG("Not sending any HNA, no associations to advertise.");
    }
    m_hnaTimer.Schedule(m_hnaInterval);
}

void
RoutingProtocol::DupTupleTimerExpire(Ipv4Address address, uint16_t sequenceNumber)
{
    DuplicateTuple* tuple = m_state.FindDuplicateTuple(address, sequenceNumber);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->expirationTime < Simulator::Now())
    {
        RemoveDuplicateTuple(*tuple);
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(tuple->expirationTime),
                                           &RoutingProtocol::DupTupleTimerExpire,
                                           this,
                                           address,
                                           sequenceNumber));
    }
}

void
RoutingProtocol::LinkTupleTimerExpire(Ipv4Address neighborIfaceAddr)
{
    Time now = Simulator::Now();

    // the tuple parameter may be a stale copy; get a newer version from m_state
    LinkTuple* tuple = m_state.FindLinkTuple(neighborIfaceAddr);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->time < now)
    {
        RemoveLinkTuple(*tuple);
    }
    else if (tuple->symTime < now)
    {
        if (m_linkTupleTimerFirstTime)
        {
            m_linkTupleTimerFirstTime = false;
        }
        else
        {
            NeighborLoss(*tuple);
        }

        m_events.Track(Simulator::Schedule(DELAY(tuple->time),
                                           &RoutingProtocol::LinkTupleTimerExpire,
                                           this,
                                           neighborIfaceAddr));
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(std::min(tuple->time, tuple->symTime)),
                                           &RoutingProtocol::LinkTupleTimerExpire,
                                           this,
                                           neighborIfaceAddr));
    }
}

void
RoutingProtocol::Nb2hopTupleTimerExpire(Ipv4Address neighborMainAddr,
                                        Ipv4Address twoHopNeighborAddr)
{
    TwoHopNeighborTuple* tuple;
    tuple = m_state.FindTwoHopNeighborTuple(neighborMainAddr, twoHopNeighborAddr);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->expirationTime < Simulator::Now())
    {
        RemoveTwoHopNeighborTuple(*tuple);
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(tuple->expirationTime),
                                           &RoutingProtocol::Nb2hopTupleTimerExpire,
                                           this,
                                           neighborMainAddr,
                                           twoHopNeighborAddr));
    }
}

void
RoutingProtocol::MprSelTupleTimerExpire(Ipv4Address mainAddr)
{
    MprSelectorTuple* tuple = m_state.FindMprSelectorTuple(mainAddr);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->expirationTime < Simulator::Now())
    {
        RemoveMprSelectorTuple(*tuple);
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(tuple->expirationTime),
                                           &RoutingProtocol::MprSelTupleTimerExpire,
                                           this,
                                           mainAddr));
    }
}

void
RoutingProtocol::TopologyTupleTimerExpire(Ipv4Address destAddr, Ipv4Address lastAddr)
{
    TopologyTuple* tuple = m_state.FindTopologyTuple(destAddr, lastAddr);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->expirationTime < Simulator::Now())
    {
        RemoveTopologyTuple(*tuple);
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(tuple->expirationTime),
                                           &RoutingProtocol::TopologyTupleTimerExpire,
                                           this,
                                           tuple->destAddr,
                                           tuple->lastAddr));
    }
}

void
RoutingProtocol::IfaceAssocTupleTimerExpire(Ipv4Address ifaceAddr)
{
    IfaceAssocTuple* tuple = m_state.FindIfaceAssocTuple(ifaceAddr);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->time < Simulator::Now())
    {
        RemoveIfaceAssocTuple(*tuple);
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(tuple->time),
                                           &RoutingProtocol::IfaceAssocTupleTimerExpire,
                                           this,
                                           ifaceAddr));
    }
}

void
RoutingProtocol::AssociationTupleTimerExpire(Ipv4Address gatewayAddr,
                                             Ipv4Address networkAddr,
                                             Ipv4Mask netmask)
{
    AssociationTuple* tuple = m_state.FindAssociationTuple(gatewayAddr, networkAddr, netmask);
    if (tuple == nullptr)
    {
        return;
    }
    if (tuple->expirationTime < Simulator::Now())
    {
        RemoveAssociationTuple(*tuple);
    }
    else
    {
        m_events.Track(Simulator::Schedule(DELAY(tuple->expirationTime),
                                           &RoutingProtocol::AssociationTupleTimerExpire,
                                           this,
                                           gatewayAddr,
                                           networkAddr,
                                           netmask));
    }
}

void
RoutingProtocol::Clear()
{
    NS_LOG_FUNCTION_NOARGS();
    m_table.clear();
}

void
RoutingProtocol::RemoveEntry(const Ipv4Address& dest)
{
    m_table.erase(dest);
}

bool
RoutingProtocol::Lookup(const Ipv4Address& dest, RoutingTableEntry& outEntry) const
{
    // Get the iterator at "dest" position
    auto it = m_table.find(dest);
    // If there is no route to "dest", return NULL
    if (it == m_table.end())
    {
        return false;
    }
    outEntry = it->second;
    return true;
}

bool
RoutingProtocol::FindSendEntry(const RoutingTableEntry& entry, RoutingTableEntry& outEntry) const
{
    outEntry = entry;
    while (outEntry.destAddr != outEntry.nextAddr)
    {
        if (not Lookup(outEntry.nextAddr, outEntry))
        {
            return false;
        }
    }
    return true;
}

Ptr<Ipv4Route>
RoutingProtocol::RouteOutput(Ptr<Packet> p,
                             const Ipv4Header& header,
                             Ptr<NetDevice> oif,
                             Socket::SocketErrno& sockerr)
{
    NS_LOG_FUNCTION(this << " " << m_ipv4->GetObject<Node>()->GetId() << " "
                         << header.GetDestination() << " " << oif);
    Ptr<Ipv4Route> rtentry;
    RoutingTableEntry entry1;
    RoutingTableEntry entry2;
    bool found = false;

    if (Lookup(header.GetDestination(), entry1))
    {
        bool foundSendEntry = FindSendEntry(entry1, entry2);
        if (!foundSendEntry)
        {
            NS_FATAL_ERROR("FindSendEntry failure");
        }
        uint32_t interfaceIdx = entry2.interface;
        if (oif && m_ipv4->GetInterfaceForDevice(oif) != static_cast<int>(interfaceIdx))
        {
            // We do not attempt to perform a constrained routing search
            // if the caller specifies the oif; we just enforce that
            // that the found route matches the requested outbound interface
            NS_LOG_DEBUG("Olsr node " << m_mainAddress
                                      << ": RouteOutput for dest=" << header.GetDestination()
                                      << " Route interface " << interfaceIdx
                                      << " does not match requested output interface "
                                      << m_ipv4->GetInterfaceForDevice(oif));
            sockerr = Socket::ERROR_NOROUTETOHOST;
            return rtentry;
        }
        rtentry = Create<Ipv4Route>();
        rtentry->SetDestination(header.GetDestination());
        // the source address is the interface address that matches
        // the destination address (when multiple are present on the
        // outgoing interface, one is selected via scoping rules)
        NS_ASSERT(m_ipv4);
        uint32_t numOifAddresses = m_ipv4->GetNAddresses(interfaceIdx);
        NS_ASSERT(numOifAddresses > 0);
        Ipv4InterfaceAddress ifAddr;
        if (numOifAddresses == 1)
        {
            ifAddr = m_ipv4->GetAddress(interfaceIdx, 0);
        }
        else
        {
            /// @todo Implement IP aliasing and OLSR
            NS_FATAL_ERROR("XXX Not implemented yet:  IP aliasing and OLSR");
        }
        rtentry->SetSource(ifAddr.GetLocal());
        rtentry->SetGateway(entry2.nextAddr);
        rtentry->SetOutputDevice(m_ipv4->GetNetDevice(interfaceIdx));
        sockerr = Socket::ERROR_NOTERROR;
        NS_LOG_DEBUG("Olsr node " << m_mainAddress << ": RouteOutput for dest="
                                  << header.GetDestination() << " --> nextHop=" << entry2.nextAddr
                                  << " interface=" << entry2.interface);
        NS_LOG_DEBUG("Found route to " << rtentry->GetDestination() << " via nh "
                                       << rtentry->GetGateway() << " with source addr "
                                       << rtentry->GetSource() << " and output dev "
                                       << rtentry->GetOutputDevice());
        found = true;
    }
    else
    {
        rtentry = m_hnaRoutingTable->RouteOutput(p, header, oif, sockerr);

        if (rtentry)
        {
            found = true;
            NS_LOG_DEBUG("Found route to " << rtentry->GetDestination() << " via nh "
                                           << rtentry->GetGateway() << " with source addr "
                                           << rtentry->GetSource() << " and output dev "
                                           << rtentry->GetOutputDevice());
        }
    }

    if (!found)
    {
        NS_LOG_DEBUG("Olsr node " << m_mainAddress << ": RouteOutput for dest="
                                  << header.GetDestination() << " No route to host");
        sockerr = Socket::ERROR_NOROUTETOHOST;
    }
    return rtentry;
}

bool
RoutingProtocol::RouteInput(Ptr<const Packet> p,
                            const Ipv4Header& header,
                            Ptr<const NetDevice> idev,
                            const UnicastForwardCallback& ucb,
                            const MulticastForwardCallback& mcb,
                            const LocalDeliverCallback& lcb,
                            const ErrorCallback& ecb)
{
    NS_LOG_FUNCTION(this << " " << m_ipv4->GetObject<Node>()->GetId() << " "
                         << header.GetDestination());

    Ipv4Address dst = header.GetDestination();
    Ipv4Address origin = header.GetSource();

    // Consume self-originated packets
    if (IsMyOwnAddress(origin))
    {
        return true;
    }

    // Local delivery
    NS_ASSERT(m_ipv4->GetInterfaceForDevice(idev) >= 0);
    uint32_t iif = m_ipv4->GetInterfaceForDevice(idev);
    if (m_ipv4->IsDestinationAddress(dst, iif))
    {
        if (!lcb.IsNull())
        {
            NS_LOG_LOGIC("Local delivery to " << dst);
            lcb(p, header, iif);
            return true;
        }
        else
        {
            // The local delivery callback is null.  This may be a multicast
            // or broadcast packet, so return false so that another
            // multicast routing protocol can handle it.  It should be possible
            // to extend this to explicitly check whether it is a unicast
            // packet, and invoke the error callback if so
            NS_LOG_LOGIC("Null local delivery callback");
            return false;
        }
    }

    NS_LOG_LOGIC("Forward packet");
    // Forwarding
    Ptr<Ipv4Route> rtentry;
    RoutingTableEntry entry1;
    RoutingTableEntry entry2;
    if (Lookup(header.GetDestination(), entry1))
    {
        bool foundSendEntry = FindSendEntry(entry1, entry2);
        if (!foundSendEntry)
        {
            NS_FATAL_ERROR("FindSendEntry failure");
        }
        rtentry = Create<Ipv4Route>();
        rtentry->SetDestination(header.GetDestination());
        uint32_t interfaceIdx = entry2.interface;
        // the source address is the interface address that matches
        // the destination address (when multiple are present on the
        // outgoing interface, one is selected via scoping rules)
        NS_ASSERT(m_ipv4);
        uint32_t numOifAddresses = m_ipv4->GetNAddresses(interfaceIdx);
        NS_ASSERT(numOifAddresses > 0);
        Ipv4InterfaceAddress ifAddr;
        if (numOifAddresses == 1)
        {
            ifAddr = m_ipv4->GetAddress(interfaceIdx, 0);
        }
        else
        {
            /// @todo Implement IP aliasing and OLSR
            NS_FATAL_ERROR("XXX Not implemented yet:  IP aliasing and OLSR");
        }
        rtentry->SetSource(ifAddr.GetLocal());
        rtentry->SetGateway(entry2.nextAddr);
        rtentry->SetOutputDevice(m_ipv4->GetNetDevice(interfaceIdx));

        NS_LOG_DEBUG("Olsr node " << m_mainAddress << ": RouteInput for dest="
                                  << header.GetDestination() << " --> nextHop=" << entry2.nextAddr
                                  << " interface=" << entry2.interface);

        ucb(rtentry, p, header);
        return true;
    }
    else
    {
        NS_LOG_LOGIC("No dynamic route, check network routes");
        if (m_hnaRoutingTable->RouteInput(p, header, idev, ucb, mcb, lcb, ecb))
        {
            // NOLINTNEXTLINE(readability-simplify-boolean-expr)
            return true;
        }
        else
        {
#ifdef NS3_LOG_ENABLE
            NS_LOG_DEBUG("Olsr node " << m_mainAddress
                                      << ": RouteInput for dest=" << header.GetDestination()
                                      << " --> NOT FOUND; ** Dumping routing table...");

            for (auto iter = m_table.begin(); iter != m_table.end(); iter++)
            {
                NS_LOG_DEBUG("dest=" << iter->first << " --> next=" << iter->second.nextAddr
                                     << " via interface " << iter->second.interface);
            }

            NS_LOG_DEBUG("** Routing table dump end.");
#endif // NS3_LOG_ENABLE

            return false;
        }
    }
}

void
RoutingProtocol::NotifyInterfaceUp(uint32_t i)
{
}

void
RoutingProtocol::NotifyInterfaceDown(uint32_t i)
{
}

void
RoutingProtocol::NotifyAddAddress(uint32_t interface, Ipv4InterfaceAddress address)
{
}

void
RoutingProtocol::NotifyRemoveAddress(uint32_t interface, Ipv4InterfaceAddress address)
{
}

void
RoutingProtocol::AddEntry(const Ipv4Address& dest,
                          const Ipv4Address& next,
                          uint32_t interface,
                          uint32_t distance)
{
    NS_LOG_FUNCTION(this << dest << next << interface << distance << m_mainAddress);

    NS_ASSERT(distance > 0);

    // Creates a new rt entry with specified values
    RoutingTableEntry& entry = m_table[dest];

    entry.destAddr = dest;
    entry.nextAddr = next;
    entry.interface = interface;
    entry.distance = distance;
}

void
RoutingProtocol::AddEntry(const Ipv4Address& dest,
                          const Ipv4Address& next,
                          const Ipv4Address& interfaceAddress,
                          uint32_t distance)
{
    NS_LOG_FUNCTION(this << dest << next << interfaceAddress << distance << m_mainAddress);

    NS_ASSERT(distance > 0);
    NS_ASSERT(m_ipv4);

    RoutingTableEntry entry;
    for (uint32_t i = 0; i < m_ipv4->GetNInterfaces(); i++)
    {
        for (uint32_t j = 0; j < m_ipv4->GetNAddresses(i); j++)
        {
            if (m_ipv4->GetAddress(i, j).GetLocal() == interfaceAddress)
            {
                AddEntry(dest, next, i, distance);
                return;
            }
        }
    }
    NS_ASSERT(false); // should not be reached
    AddEntry(dest, next, 0, distance);
}

std::vector<RoutingTableEntry>
RoutingProtocol::GetRoutingTableEntries() const
{
    std::vector<RoutingTableEntry> retval;
    for (auto iter = m_table.begin(); iter != m_table.end(); iter++)
    {
        retval.push_back(iter->second);
    }
    return retval;
}

MprSet
RoutingProtocol::GetMprSet() const
{
    return m_state.GetMprSet();
}

const MprSelectorSet&
RoutingProtocol::GetMprSelectors() const
{
    return m_state.GetMprSelectors();
}

const NeighborSet&
RoutingProtocol::GetNeighbors() const
{
    return m_state.GetNeighbors();
}

const TwoHopNeighborSet&
RoutingProtocol::GetTwoHopNeighbors() const
{
    return m_state.GetTwoHopNeighbors();
}

const TopologySet&
RoutingProtocol::GetTopologySet() const
{
    return m_state.GetTopologySet();
}

const OlsrState&
RoutingProtocol::GetOlsrState() const
{
    return m_state;
}

int64_t
RoutingProtocol::AssignStreams(int64_t stream)
{
    NS_LOG_FUNCTION(this << stream);
    m_uniformRandomVariable->SetStream(stream);
    return 1;
}

bool
RoutingProtocol::IsMyOwnAddress(const Ipv4Address& a) const
{
    for (auto j = m_sendSockets.begin(); j != m_sendSockets.end(); ++j)
    {
        Ipv4InterfaceAddress iface = j->second;
        if (a == iface.GetLocal())
        {
            return true;
        }
    }
    return false;
}

void
RoutingProtocol::Dump()
{
#ifdef NS3_LOG_ENABLE
    Time now = Simulator::Now();
    NS_LOG_DEBUG("Dumping for node with main address " << m_mainAddress);
    NS_LOG_DEBUG(" Neighbor set");
    for (auto iter = m_state.GetNeighbors().begin(); iter != m_state.GetNeighbors().end(); iter++)
    {
        NS_LOG_DEBUG("  " << *iter);
    }
    NS_LOG_DEBUG(" Two-hop neighbor set");
    for (auto iter = m_state.GetTwoHopNeighbors().begin();
         iter != m_state.GetTwoHopNeighbors().end();
         iter++)
    {
        if (now < iter->expirationTime)
        {
            NS_LOG_DEBUG("  " << *iter);
        }
    }
    NS_LOG_DEBUG(" Routing table");
    for (auto iter = m_table.begin(); iter != m_table.end(); iter++)
    {
        NS_LOG_DEBUG("  dest=" << iter->first << " --> next=" << iter->second.nextAddr
                               << " via interface " << iter->second.interface);
    }
    NS_LOG_DEBUG("");
#endif // NS3_LOG_ENABLE
}

Ptr<const Ipv4StaticRouting>
RoutingProtocol::GetRoutingTableAssociation() const
{
    return m_hnaRoutingTable;
}

} // namespace olsr
} // namespace ns3
