/*
 * Copyright (c) 2022 ZHIHENG DONG
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Zhiheng Dong <dzh2077@gmail.com>
 */

/**
 * This example shows how neighbor caches generate dynamically, when
 * user is generating neighbor caches globally, neighbor caches will
 * update dynamically when IPv4/IPv6 addresses are removed or added;
 * when user is generating neighbor caches partially, NeighborCacheHelper
 * will take care of address removal, for adding address user may manually
 * add entry to keep the neighbor cache up-to-date.
 *
 * IPv4 Network Topology
 * @verbatim
  n1   n2   n3
  |    |    |
  ===========
  LAN 10.1.1.0
  \endverbatim
 *
 * IPv6 Network Topology
 * @verbatim
  n1   n2   n3
  |    |    |
  ===========
  LAN 2001:1::/64
  \endverbatim
 *
 * Expected Outputs:
 * IPv4 (default):
 * @verbatim
  ARP Cache of node 0 at time 0
  10.1.1.2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  10.1.1.3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  ARP Cache of node 1 at time 0
  10.1.1.1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  10.1.1.3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  ARP Cache of node 2 at time 0
  10.1.1.1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  10.1.1.2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED

  Arp caches after add address 10.1.1.4 to n1
  ARP Cache of node 0 at time 1
  10.1.1.2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  10.1.1.3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  ARP Cache of node 1 at time 1
  10.1.1.1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  10.1.1.3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  10.1.1.4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  ARP Cache of node 2 at time 1
  10.1.1.1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  10.1.1.2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  10.1.1.4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED

  Arp caches after remove the first address (10.1.1.1) from n1
  ARP Cache of node 0 at time 2
  10.1.1.2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  10.1.1.3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  ARP Cache of node 1 at time 2
  10.1.1.3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  10.1.1.4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  ARP Cache of node 2 at time 2
  10.1.1.2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  10.1.1.4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  \endverbatim
 *
 * IPv6 (--useIPv6):
 * @verbatim
  NDISC Cache of node 0 at time +0s
  2001:1::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  NDISC Cache of node 1 at time +0s
  2001:1::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  NDISC Cache of node 2 at time +0s
  2001:1::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED

  Ndisc caches after add address 2001:1::200:ff:fe00:4 n1
  NDISC Cache of node 0 at time +1s
  2001:1::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  NDISC Cache of node 1 at time +1s
  2001:1::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  NDISC Cache of node 2 at time +1s
  2001:1::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED

  Ndisc caches after remove the second address (2001:1::200:ff:fe00:1) from n1
  NDISC Cache of node 0 at time +2s
  2001:1::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  NDISC Cache of node 1 at time +2s
  2001:1::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:3 dev 0 lladdr 02-06-00:00:00:00:00:03 STATIC_AUTOGENERATED
  NDISC Cache of node 2 at time +2s
  2001:1::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
  2001:1::200:ff:fe00:4 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:1 dev 0 lladdr 02-06-00:00:00:00:00:01 STATIC_AUTOGENERATED
  fe80::200:ff:fe00:2 dev 0 lladdr 02-06-00:00:00:00:00:02 STATIC_AUTOGENERATED
   \endverbatim
 */

#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("NeighborCacheDynamic");

void
AddIpv4Address(Ptr<Ipv4Interface> ipv4Interface, Ipv4InterfaceAddress ifaceAddr)
{
    ipv4Interface->AddAddress(ifaceAddr);
    std::cout << "\nArp caches after add address 10.1.1.4 to n1" << std::endl;
}

void
AddIpv6Address(Ptr<Ipv6Interface> ipv6Interface, Ipv6InterfaceAddress ifaceAddr)
{
    ipv6Interface->AddAddress(ifaceAddr);
    std::cout << "\nNdisc caches after add address 2001:1::200:ff:fe00:4 n1" << std::endl;
}

void
RemoveIpv4Address(Ptr<Ipv4Interface> ipv4Interface, uint32_t index)
{
    ipv4Interface->RemoveAddress(index);
    std::cout << "\nArp caches after remove the first address (10.1.1.1) from n1" << std::endl;
}

void
RemoveIpv6Address(Ptr<Ipv6Interface> ipv6Interface, uint32_t index)
{
    ipv6Interface->RemoveAddress(index);
    std::cout << "\nNdisc caches after remove the second address (2001:1::200:ff:fe00:1) from n1"
              << std::endl;
}

int
main(int argc, char* argv[])
{
    bool useIpv6 = false;
    bool enableLog = false;

    CommandLine cmd(__FILE__);
    cmd.AddValue("useIPv6", "Use IPv6 instead of IPv4", useIpv6);
    cmd.AddValue("enableLog", "Enable ArpL3Protocol and Icmpv6L4Protocol logging", enableLog);
    cmd.Parse(argc, argv);

    if (enableLog)
    {
        LogComponentEnable("ArpL3Protocol", LOG_LEVEL_LOGIC);
        LogComponentEnable("Icmpv6L4Protocol", LOG_LEVEL_LOGIC);
    }

    uint32_t nCsma = 3;
    NodeContainer csmaNodes;
    csmaNodes.Create(nCsma);

    CsmaHelper csma;
    csma.SetChannelAttribute("DataRate", StringValue("100Mbps"));
    csma.SetChannelAttribute("Delay", TimeValue(NanoSeconds(6560)));

    NetDeviceContainer csmaDevices;
    csmaDevices = csma.Install(csmaNodes);

    InternetStackHelper stack;
    if (!useIpv6)
    {
        stack.SetIpv6StackInstall(false);
    }
    else
    {
        stack.SetIpv4StackInstall(false);
    }
    stack.Install(csmaNodes);

    if (!useIpv6)
    {
        Ipv4AddressHelper address;
        address.SetBase("10.1.1.0", "255.255.255.0");
        Ipv4InterfaceContainer csmaInterfaces;
        csmaInterfaces = address.Assign(csmaDevices);
    }
    else
    {
        Ipv6AddressHelper address;
        address.SetBase(Ipv6Address("2001:1::"), Ipv6Prefix(64));
        Ipv6InterfaceContainer csmaInterfaces;
        csmaInterfaces = address.Assign(csmaDevices);
    }

    // Populate neighbor caches for all devices
    NeighborCacheHelper neighborCache;
    neighborCache.SetDynamicNeighborCache(true);
    neighborCache.PopulateNeighborCache();

    if (!useIpv6)
    {
        // Add address 10.1.1.4 to interface 1 in 0.5 seconds
        Ptr<Node> n1 = csmaNodes.Get(0);
        uint32_t ipv4ifIndex = 1;
        Ptr<Ipv4Interface> ipv4Interface =
            n1->GetObject<Ipv4L3Protocol>()->GetInterface(ipv4ifIndex);
        Ipv4InterfaceAddress ifaceAddr = Ipv4InterfaceAddress("10.1.1.4", "255.255.255.0");
        Simulator::Schedule(Seconds(0.5), &AddIpv4Address, ipv4Interface, ifaceAddr);

        // Remove the first address (10.1.1.1) from interface 1 in 1.5 seconds
        uint32_t addressIndex = 0;
        Simulator::Schedule(Seconds(1.5), &RemoveIpv4Address, ipv4Interface, addressIndex);

        Ptr<OutputStreamWrapper> outputStream = Create<OutputStreamWrapper>(&std::cout);
        Ipv4RoutingHelper::PrintNeighborCacheAllAt(Seconds(0), outputStream);
        Ipv4RoutingHelper::PrintNeighborCacheAllAt(Seconds(1), outputStream);
        Ipv4RoutingHelper::PrintNeighborCacheAllAt(Seconds(2), outputStream);
    }
    else
    {
        // Add address 2001:1::200:ff:fe00:4 to interface 1 in 0.5 seconds
        Ptr<Node> n1 = csmaNodes.Get(0);
        uint32_t ipv6ifIndex = 1;
        Ptr<Ipv6Interface> ipv6Interface =
            n1->GetObject<Ipv6L3Protocol>()->GetInterface(ipv6ifIndex);
        Ipv6InterfaceAddress ifaceAddr =
            Ipv6InterfaceAddress("2001:1::200:ff:fe00:4", Ipv6Prefix(64));
        Simulator::Schedule(Seconds(0.5), &AddIpv6Address, ipv6Interface, ifaceAddr);

        // Remove the second address (2001:1::200:ff:fe00:1) from interface 1 in 1.5 seconds
        uint32_t addressIndex = 1;
        Simulator::Schedule(Seconds(1.5), &RemoveIpv6Address, ipv6Interface, addressIndex);

        Ptr<OutputStreamWrapper> outputStream = Create<OutputStreamWrapper>(&std::cout);
        Ipv6RoutingHelper::PrintNeighborCacheAllAt(Seconds(0), outputStream);
        Ipv6RoutingHelper::PrintNeighborCacheAllAt(Seconds(1), outputStream);
        Ipv6RoutingHelper::PrintNeighborCacheAllAt(Seconds(2), outputStream);
    }

    Simulator::Stop(Seconds(10));
    Simulator::Run();
    Simulator::Destroy();
    return 0;
}
