/*
 * Copyright (c) 2022 Chandrakant Jena
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Chandrakant Jena <chandrakant.barcelona@gmail.com>
 */

// Example to demonstrate the working of the Ping Application
// Network topology:
// A ------------------------------ B ------------------------------ C
//             100 Mbps                           100 Mbps
//               5 ms (one way)                     5 ms (one way)
// IPv4 addresses:
// 10.1.1.1    <->         10.1.1.2 / 10.1.2.1     <->         10.1.2.2
//
// IPv6 addresses:
// 2001:1::200:ff:fe00:1
//     <->    2001:1::200:ff:fe00:2 / 2001:1:0:1:200:ff:fe00:3
//                                       <-> 2001:1:0:1:200:ff:fe00:4
//
// The topology has three nodes interconnected by two point-to-point links.
// Each link has 5 ms one-way delay, for a round-trip propagation delay
// of 20 ms.  The transmission rate on each link is 100 Mbps.  The routing
// between links is enabled by ns-3's NixVector routing.
//
// By default, this program will send 5 pings from node A to node C using IPv6.
// When using IPv6, the output will look like this:
//
// PING 2001:1:0:1:200:ff:fe00:4 - 56 bytes of data; 104 bytes including ICMP and IPv6 headers.
// 64 bytes from (2001:1:0:1:200:ff:fe00:4): icmp_seq=0 ttl=63 time=20.033 ms
// 64 bytes from (2001:1:0:1:200:ff:fe00:4): icmp_seq=1 ttl=63 time=20.033 ms
// 64 bytes from (2001:1:0:1:200:ff:fe00:4): icmp_seq=2 ttl=63 time=20.033 ms
// 64 bytes from (2001:1:0:1:200:ff:fe00:4): icmp_seq=3 ttl=63 time=20.033 ms
// 64 bytes from (2001:1:0:1:200:ff:fe00:4): icmp_seq=4 ttl=63 time=20.033 ms
//
// --- 2001:1:0:1:200:ff:fe00:4 ping statistics ---
// 5 packets transmitted, 5 received, 0% packet loss, time 4020ms
// rtt min/avg/max/mdev = 20/20/20/0 ms
//
// When using IPv4, the output will look like this:
//
//   PING 10.1.2.2 - 56 bytes of data; 84 bytes including ICMP and IPv4 headers.
//   64 bytes from (10.1.2.2): icmp_seq=0 ttl=63 time=20.027 ms
//   64 bytes from (10.1.2.2): icmp_seq=1 ttl=63 time=20.027 ms
//   64 bytes from (10.1.2.2): icmp_seq=2 ttl=63 time=20.027 ms
//   64 bytes from (10.1.2.2): icmp_seq=3 ttl=63 time=20.027 ms
//   64 bytes from (10.1.2.2): icmp_seq=4 ttl=63 time=20.027 ms
//   --- 10.1.2.2 ping statistics ---
//   5 packets transmitted, 5 received, 0% packet loss, time 4020ms
//   rtt min/avg/max/mdev = 20/20/20/0 ms
//
// The example program will also produce four pcap traces (one for each
// NetDevice in the scenario) that can be viewed using tcpdump or Wireshark.
//
// Other program options include options to change the destination and
// source addresses, number of packets (count), packet size, interval,
// and whether to enable logging (if logging is enabled in the build).
//
// The Ping application in this example starts at simulation time 1 and will
// stop either at simulation time 50 or once 'Count' pings have been responded
// to, whichever comes first.

#include "ns3/core-module.h"
#include "ns3/internet-apps-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"
#include "ns3/nix-vector-routing-module.h"
#include "ns3/point-to-point-module.h"

#include <fstream>

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("PingExample");

int
main(int argc, char* argv[])
{
    bool logging{false};
    Time interPacketInterval{Seconds(1.0)};
    uint32_t size{56};
    uint32_t count{5};
    std::string destinationStr;
    Address destination;
    std::string sourceStr;
    Address source;
    bool useIpv6{true};

    GlobalValue::Bind("ChecksumEnabled", BooleanValue(true));

    CommandLine cmd(__FILE__);
    cmd.AddValue("logging", "Tell application to log if true", logging);
    cmd.AddValue("interval", "The time to wait between two packets", interPacketInterval);
    cmd.AddValue("size", "Data bytes to be sent, per-packet", size);
    cmd.AddValue("count", "Number of packets to be sent", count);
    cmd.AddValue("destination",
                 "Destination IPv4 or IPv6 address, e.g., \"10.1.2.2\"",
                 destinationStr);
    cmd.AddValue("source",
                 "Source address, needed only for multicast or broadcast destinations",
                 sourceStr);
    cmd.Parse(argc, argv);

    if (!destinationStr.empty())
    {
        Ipv4Address v4Dst = Ipv4Address(destinationStr.c_str());
        Ipv6Address v6Dst = Ipv6Address(destinationStr.c_str());
        if (v4Dst.IsInitialized())
        {
            useIpv6 = false;
            destination = v4Dst;
        }
        else if (v6Dst.IsInitialized())
        {
            useIpv6 = true;
            destination = v6Dst;
        }
    }

    if (!sourceStr.empty())
    {
        Ipv4Address v4Src = Ipv4Address(sourceStr.c_str());
        Ipv6Address v6Src = Ipv6Address(sourceStr.c_str());
        if (v4Src.IsInitialized())
        {
            source = v4Src;
        }
        else if (v6Src.IsInitialized())
        {
            source = v6Src;
        }
    }
    if (sourceStr.empty())
    {
        if (useIpv6)
        {
            Ipv6Address v6Dst = Ipv6Address(destinationStr.c_str());
            if (v6Dst.IsInitialized() && v6Dst.IsMulticast())
            {
                std::cout << "Specify a source address to use when pinging multicast addresses"
                          << std::endl;
                std::cout << "Program exiting..." << std::endl;
                return 0;
            }
        }
        else
        {
            Ipv4Address v4Dst = Ipv4Address(destinationStr.c_str());
            if (v4Dst.IsInitialized() && (v4Dst.IsBroadcast() || v4Dst.IsMulticast()))
            {
                std::cout << "Specify a source address to use when pinging broadcast or multicast "
                             "addresses"
                          << std::endl;
                std::cout << "Program exiting..." << std::endl;
                return 0;
            }
        }
    }

    if (logging)
    {
        LogComponentEnableAll(LogLevel(LOG_PREFIX_TIME | LOG_PREFIX_NODE));
        LogComponentEnable("Ping", LOG_LEVEL_ALL);
    }

    NodeContainer nodes;
    nodes.Create(3);
    NodeContainer link1Nodes;
    link1Nodes.Add(nodes.Get(0));
    link1Nodes.Add(nodes.Get(1));
    NodeContainer link2Nodes;
    link2Nodes.Add(nodes.Get(1));
    link2Nodes.Add(nodes.Get(2));

    PointToPointHelper pointToPoint;
    pointToPoint.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
    pointToPoint.SetChannelAttribute("Delay", StringValue("5ms"));

    NetDeviceContainer link1Devices;
    link1Devices = pointToPoint.Install(link1Nodes);
    NetDeviceContainer link2Devices;
    link2Devices = pointToPoint.Install(link2Nodes);

    // The following block of code inserts an optional packet loss model
    Ptr<PointToPointNetDevice> p2pSender = DynamicCast<PointToPointNetDevice>(link1Devices.Get(0));
    Ptr<ReceiveListErrorModel> errorModel = CreateObject<ReceiveListErrorModel>();
    std::list<uint32_t> dropList;
    // Enable one or more of the below lines to force specific packet losses
    // dropList.push_back (0);
    // dropList.push_back (1);
    // dropList.push_back (2);
    // dropList.push_back (3);
    // dropList.push_back (4);
    // etc. (other lines may be added)
    errorModel->SetList(dropList);
    p2pSender->SetReceiveErrorModel(errorModel);

    if (!useIpv6)
    {
        Ipv4NixVectorHelper nixRouting;
        InternetStackHelper stack;
        stack.SetRoutingHelper(nixRouting);
        stack.SetIpv6StackInstall(false);
        stack.Install(nodes);

        Ipv4AddressHelper addressV4;
        addressV4.SetBase("10.1.1.0", "255.255.255.0");
        addressV4.Assign(link1Devices);
        addressV4.NewNetwork();
        Ipv4InterfaceContainer link2InterfacesV4 = addressV4.Assign(link2Devices);

        if (destination.IsInvalid())
        {
            destination = link2InterfacesV4.GetAddress(1, 0);
        }
    }
    else
    {
        Ipv6NixVectorHelper nixRouting;
        InternetStackHelper stack;
        stack.SetRoutingHelper(nixRouting);
        stack.SetIpv4StackInstall(false);
        stack.Install(nodes);

        Ipv6AddressHelper addressV6;
        addressV6.SetBase("2001:1::", 64);
        addressV6.Assign(link1Devices);
        addressV6.NewNetwork();
        Ipv6InterfaceContainer link2InterfacesV6 = addressV6.Assign(link2Devices);

        if (destination.IsInvalid())
        {
            destination = link2InterfacesV6.GetAddress(1, 1);
        }
    }

    // Create Ping application and installing on node A
    PingHelper pingHelper(destination, source);
    pingHelper.SetAttribute("Interval", TimeValue(interPacketInterval));
    pingHelper.SetAttribute("Size", UintegerValue(size));
    pingHelper.SetAttribute("Count", UintegerValue(count));
    ApplicationContainer apps = pingHelper.Install(nodes.Get(0));
    apps.Start(Seconds(1));
    apps.Stop(Seconds(50));

    pointToPoint.EnablePcapAll("ping-example");

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