/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2007-2009 Strasbourg University
 *
 * 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: Sebastien Vincent <vincent@clarinet.u-strasbg.fr>
 */

#include "ns3/log.h"
#include "ns3/nstime.h"
#include "ns3/simulator.h"
#include "ns3/socket-factory.h"
#include "ns3/packet.h"
#include "ns3/socket.h"
#include "ns3/uinteger.h"
#include "ns3/ipv6.h"
#include "ns3/ipv6-address.h"
#include "ns3/inet6-socket-address.h"
#include "ns3/icmpv6-header.h"
#include "ns3/ipv6-raw-socket-factory.h"
#include "ns3/ipv6-header.h"
#include "ns3/ipv6-extension-header.h"

#include "ping6.h"

namespace ns3 
{

NS_LOG_COMPONENT_DEFINE ("Ping6Application");

NS_OBJECT_ENSURE_REGISTERED (Ping6);

TypeId Ping6::GetTypeId ()
{
  static TypeId tid = TypeId ("ns3::Ping6")
    .SetParent<Application>()
    .AddConstructor<Ping6>()
    .AddAttribute ("MaxPackets", 
                   "The maximum number of packets the application will send",
                   UintegerValue (100),
                   MakeUintegerAccessor (&Ping6::m_count),
                   MakeUintegerChecker<uint32_t>())
    .AddAttribute ("Interval", 
                   "The time to wait between packets",
                   TimeValue (Seconds (1.0)),
                   MakeTimeAccessor (&Ping6::m_interval),
                   MakeTimeChecker ())
    .AddAttribute ("RemoteIpv6", 
                   "The Ipv6Address of the outbound packets",
                   Ipv6AddressValue (),
                   MakeIpv6AddressAccessor (&Ping6::m_peerAddress),
                   MakeIpv6AddressChecker ())
    .AddAttribute ("LocalIpv6", 
                   "Local Ipv6Address of the sender",
                   Ipv6AddressValue (),
                   MakeIpv6AddressAccessor (&Ping6::m_localAddress),
                   MakeIpv6AddressChecker ())
    .AddAttribute ("PacketSize", 
                   "Size of packets generated",
                   UintegerValue (100),
                   MakeUintegerAccessor (&Ping6::m_size),
                   MakeUintegerChecker<uint32_t>())
  ;
  return tid;
}

Ping6::Ping6 ()
{
  NS_LOG_FUNCTION_NOARGS ();
  m_sent = 0;
  m_socket = 0;
  m_seq = 0;
  m_sendEvent = EventId ();
}

Ping6::~Ping6 ()
{
  NS_LOG_FUNCTION_NOARGS ();
  m_socket = 0;
}

void Ping6::DoDispose ()
{
  NS_LOG_FUNCTION_NOARGS ();
  Application::DoDispose ();
}

void Ping6::StartApplication ()
{
  NS_LOG_FUNCTION_NOARGS ();

  if (!m_socket)
    {
      TypeId tid = TypeId::LookupByName ("ns3::Ipv6RawSocketFactory");
      m_socket = Socket::CreateSocket (GetNode (), tid);

      NS_ASSERT (m_socket);

      m_socket->Bind (Inet6SocketAddress (m_localAddress, 0));
      m_socket->SetAttribute ("Protocol", UintegerValue (Ipv6Header::IPV6_ICMPV6));
      m_socket->SetRecvCallback (MakeCallback (&Ping6::HandleRead, this));
    }

  ScheduleTransmit (Seconds (0.));
}

void Ping6::SetLocal (Ipv6Address ipv6) 
{
  NS_LOG_FUNCTION (this << ipv6);
  m_localAddress = ipv6;
}

void Ping6::SetRemote (Ipv6Address ipv6)
{
  NS_LOG_FUNCTION (this << ipv6);
  m_peerAddress = ipv6;
}

void Ping6::StopApplication ()
{
  NS_LOG_FUNCTION_NOARGS ();

  if (m_socket)
    {
      m_socket->SetRecvCallback (MakeNullCallback<void, Ptr<Socket> >());
    }

  Simulator::Cancel (m_sendEvent);
}

void Ping6::SetIfIndex (uint32_t ifIndex)
{
  m_ifIndex = ifIndex;
}

void Ping6::ScheduleTransmit (Time dt)
{
  NS_LOG_FUNCTION (this << dt);
  m_sendEvent = Simulator::Schedule (dt, &Ping6::Send, this);
}

void Ping6::SetRouters (std::vector<Ipv6Address> routers)
{
  m_routers = routers;
}

void Ping6::Send ()
{
  NS_LOG_FUNCTION_NOARGS ();
  NS_ASSERT (m_sendEvent.IsExpired ());
  Ptr<Packet> p = 0;
  uint8_t* data = new uint8_t[m_size];
  Ipv6Address src;
  Ptr<Ipv6> ipv6 = GetNode ()->GetObject<Ipv6> ();

  if (m_ifIndex > 0)
    {
      /* hack to have ifIndex in Ipv6RawSocketImpl
       * maybe add a SetIfIndex in Ipv6RawSocketImpl directly 
       */
      for (uint32_t i = 0; i < GetNode ()->GetObject<Ipv6> ()->GetNAddresses (m_ifIndex); i++)
        {
          Ipv6InterfaceAddress srcIa;
          srcIa = GetNode ()->GetObject<Ipv6> ()->GetAddress (m_ifIndex, i);

          if (srcIa.IsInSameSubnet (m_peerAddress))
            {
              src = srcIa.GetAddress ();
              break;
            }
        }
     }
  else
    {
      src = m_localAddress;
    }

  NS_ASSERT_MSG (m_size >= 4, "ICMPv6 echo request payload size must be >= 4");
  data[0] = 0xDE;
  data[1] = 0xAD;
  data[2] = 0xBE;
  data[3] = 0xEF;

  p = Create<Packet> (data, 4);
  p->AddAtEnd (Create<Packet> (m_size - 4));
  Icmpv6Echo req (1);

  req.SetId (0xBEEF);
  req.SetSeq (m_seq);
  m_seq++;

  /* we do not calculate pseudo header checksum here, because we are not sure about 
   * source IPv6 address. Checksum is calculated in Ipv6RawSocketImpl.
   */

  p->AddHeader (req);
  m_socket->Bind (Inet6SocketAddress (src, 0));

  /* use Loose Routing (routing type 0) */
  if (m_routers.size ())
    {
      Ipv6ExtensionLooseRoutingHeader routingHeader;
      routingHeader.SetNextHeader (Ipv6Header::IPV6_ICMPV6);
      routingHeader.SetLength (m_routers.size () * 16 + 8);
      routingHeader.SetTypeRouting (0);
      routingHeader.SetSegmentsLeft (m_routers.size ());
      routingHeader.SetRoutersAddress (m_routers);
      p->AddHeader (routingHeader);
      m_socket->SetAttribute ("Protocol", UintegerValue (Ipv6Header::IPV6_EXT_ROUTING));
    }

  m_socket->SendTo (p, 0, Inet6SocketAddress (m_peerAddress, 0));
  ++m_sent;

  NS_LOG_INFO ("Sent " << p->GetSize () << " bytes to " << m_peerAddress);

  if (m_sent < m_count)
    {
      ScheduleTransmit (m_interval);
    }

  delete[] data;
}

void Ping6::HandleRead (Ptr<Socket> socket)
{
  NS_LOG_FUNCTION (this << socket);

  Ptr<Packet> packet=0;
  Address from;
  while ((packet = socket->RecvFrom (from)))
    {
      if (Inet6SocketAddress::IsMatchingType (from))
        {
          Ipv6Header hdr;
          Icmpv6Echo reply (0);
          Icmpv6DestinationUnreachable destUnreach;
          Icmpv6TimeExceeded timeExceeded;
          Inet6SocketAddress address = Inet6SocketAddress::ConvertFrom (from);

          packet->RemoveHeader (hdr);

          uint8_t type;
          packet->CopyData (&type, sizeof(type));

          switch (type)
            {
            case Icmpv6Header::ICMPV6_ECHO_REPLY:
              packet->RemoveHeader (reply);

              NS_LOG_INFO ("Received Echo Reply size  = " << std::dec << packet->GetSize () <<
                           " bytes from " << address.GetIpv6 () <<
                           " id =  " << (uint16_t)reply.GetId () <<
                           " seq = " << (uint16_t)reply.GetSeq () <<
                           " Hop Count = " << (uint16_t) (64 - hdr.GetHopLimit ()));
              break;
            case Icmpv6Header::ICMPV6_ERROR_DESTINATION_UNREACHABLE:
              packet->RemoveHeader (destUnreach);

              NS_LOG_INFO ("Received Destination Unreachable from " << address.GetIpv6 ());
              break;
            case Icmpv6Header::ICMPV6_ERROR_TIME_EXCEEDED:
              packet->RemoveHeader (timeExceeded);

              NS_LOG_INFO ("Received Time Exceeded from " << address.GetIpv6 ());
              break;
            default:
              break;
            }
        }
    }
}

} /* namespace ns3 */

