/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2008 Telecom Bretagne
 * Copyright (c) 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>
 *         Mehdi Benamor <benamor.mehdi@ensi.rnu.tn>
 */

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

#include "radvd.h"

namespace ns3
{

NS_LOG_COMPONENT_DEFINE ("RadvdApplication");

NS_OBJECT_ENSURE_REGISTERED (Radvd);

TypeId Radvd::GetTypeId ()
{
  static TypeId tid = TypeId ("ns3::Radvd")
    .SetParent<Application> ()
    .AddConstructor<Radvd> ()
    ;
  return tid;
}

Radvd::Radvd ()
{
  NS_LOG_FUNCTION_NOARGS ();
}

Radvd::~Radvd ()
{
  NS_LOG_FUNCTION_NOARGS ();
  for (RadvdInterfaceListI it = m_configurations.begin () ; it != m_configurations.end () ; ++it)
    {
      *it = 0;
    }
  m_configurations.clear ();
  m_socket = 0;
}

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

void Radvd::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->Connect (Inet6SocketAddress (Ipv6Address::GetAllNodesMulticast (), 0)); */
      m_socket->SetAttribute ("Protocol", UintegerValue (Ipv6Header::IPV6_ICMPV6));
      m_socket->SetRecvCallback (MakeCallback (&Radvd::HandleRead, this));
    }

  for (RadvdInterfaceListCI it = m_configurations.begin () ; it != m_configurations.end () ; it++)
    {
      m_eventIds[(*it)->GetInterface ()] = EventId ();
      ScheduleTransmit (Seconds (0.), (*it), m_eventIds[(*it)->GetInterface ()], Ipv6Address::GetAllNodesMulticast (), true); 
    }
}

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

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

  for (EventIdMapI it = m_eventIds.begin () ; it != m_eventIds.end () ; ++it)
    {
      Simulator::Cancel ((*it).second);
    }
  m_eventIds.clear ();
}

void Radvd::AddConfiguration (Ptr<RadvdInterface> routerInterface)
{
  m_configurations.push_back (routerInterface);
}

void Radvd::ScheduleTransmit (Time dt, Ptr<RadvdInterface> config, EventId& eventId, Ipv6Address dst, bool reschedule)
{
  NS_LOG_FUNCTION (this << dt);
  eventId = Simulator::Schedule (dt, &Radvd::Send, this, config, dst, reschedule);
}

void Radvd::Send (Ptr<RadvdInterface> config, Ipv6Address dst, bool reschedule)
{
  NS_LOG_FUNCTION (this << dst);
  NS_ASSERT (m_eventIds[config->GetInterface ()].IsExpired ());
  Icmpv6RA raHdr;
  Icmpv6OptionLinkLayerAddress llaHdr;
  Icmpv6OptionMtu mtuHdr;
  Icmpv6OptionPrefixInformation prefixHdr;

  if (m_eventIds.size () == 0)
    {
      return;
    }

  std::list<Ptr<RadvdPrefix> > prefixes = config->GetPrefixes ();
  Ptr<Packet> p = Create<Packet> ();
  Ptr<Ipv6> ipv6 = GetNode ()->GetObject<Ipv6> ();

  /* set RA header information */
  raHdr.SetFlagM (config->IsManagedFlag ());
  raHdr.SetFlagO (config->IsOtherConfigFlag ());
  raHdr.SetFlagH (config->IsHomeAgentFlag ());
  raHdr.SetCurHopLimit (config->GetCurHopLimit ());
  raHdr.SetLifeTime (config->GetDefaultLifeTime ());
  raHdr.SetReachableTime (config->GetReachableTime ());
  raHdr.SetRetransmissionTime (config->GetRetransTimer ());

  if (config->IsSourceLLAddress ())
    {
      /* Get L2 address from NetDevice */
      Address addr = ipv6->GetNetDevice (config->GetInterface ())->GetAddress ();
      llaHdr = Icmpv6OptionLinkLayerAddress (true, addr);
      p->AddHeader (llaHdr);
    }

  if (config->GetLinkMtu ())
    {
      NS_ASSERT (config->GetLinkMtu () >= 1280);
      mtuHdr = Icmpv6OptionMtu (config->GetLinkMtu ());
      p->AddHeader (mtuHdr);
    }

  /* add list of prefixes */
  for (std::list<Ptr<RadvdPrefix> >::const_iterator jt = prefixes.begin () ; jt != prefixes.end () ; jt++)
    {
      uint8_t flags = 0;
      prefixHdr = Icmpv6OptionPrefixInformation ();
      prefixHdr.SetPrefix ((*jt)->GetNetwork ());
      prefixHdr.SetPrefixLength ((*jt)->GetPrefixLength ());
      prefixHdr.SetValidTime ((*jt)->GetValidLifeTime ());
      prefixHdr.SetPreferredTime ((*jt)->GetPreferredLifeTime ());

      if ((*jt)->IsOnLinkFlag ())
        {
          flags += 1 << 7;
        }

      if ((*jt)->IsAutonomousFlag ())
        {
          flags += 1 << 6;
        }

      if ((*jt)->IsRouterAddrFlag ())
        {
          flags += 1 << 5;
        }

      prefixHdr.SetFlags (flags);

      p->AddHeader (prefixHdr);
    }

  Ipv6Address src = ipv6->GetAddress (config->GetInterface (), 0).GetAddress ();
  m_socket->Bind (Inet6SocketAddress (src, 0));
  m_socket->Connect (Inet6SocketAddress (dst, 0));

  /* as we know interface index that will be used to send RA and 
   * we always send RA with router's link-local address, we can 
   * calculate checksum here.
   */
  raHdr.CalculatePseudoHeaderChecksum (src, dst, p->GetSize () + raHdr.GetSerializedSize (), 58 /* ICMPv6 */);
  p->AddHeader (raHdr);

  /* Router advertisements MUST always have a ttl of 255
   * The ttl value should be set as a socket option, but this is not yet implemented
   */
  SocketIpTtlTag ttl;
  ttl.SetTtl(255);
  p->AddPacketTag(ttl);

  /* send RA */
  NS_LOG_LOGIC ("Send RA");
  m_socket->Send (p, 0);

  if (reschedule)
    {
      UniformVariable rnd;
      uint64_t delay = static_cast<uint64_t> (rnd.GetValue (config->GetMinRtrAdvInterval (), config->GetMaxRtrAdvInterval ()) + 0.5);
      NS_LOG_INFO ("Reschedule in " << delay);
      Time t = MilliSeconds (delay);
      ScheduleTransmit (t, config, m_eventIds[config->GetInterface ()], Ipv6Address::GetAllNodesMulticast (), reschedule);
    }
}

void Radvd::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;
          Icmpv6RS rsHdr;
          Inet6SocketAddress address = Inet6SocketAddress::ConvertFrom (from);
          uint64_t delay = 0;
          UniformVariable rnd;
          Time t;

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

          switch (type)
            {
            case Icmpv6Header::ICMPV6_ND_ROUTER_SOLICITATION:
              packet->RemoveHeader (rsHdr);
              NS_LOG_INFO ("Received ICMPv6 Router Solicitation from " << hdr.GetSourceAddress () << " code = " << (uint32_t)rsHdr.GetCode ());

              /* XXX advertise just prefix(es) for the interface not all */
              for (RadvdInterfaceListCI it = m_configurations.begin () ; it != m_configurations.end () ; it++)
                {
                  /* calculate minimum delay between RA */
                  delay = static_cast<uint64_t> (rnd.GetValue (0, MAX_RA_DELAY_TIME) + 0.5); 
                  t = Simulator::Now () + MilliSeconds (delay); /* absolute time of solicited RA */

                  /* if our solicited RA is before the next periodic RA, we schedule it */
                  if (t.GetTimeStep () < static_cast<int64_t> (m_eventIds[(*it)->GetInterface ()].GetTs ()))
                    {
                      NS_LOG_INFO ("schedule new RA");
                      EventId ei;

                      ScheduleTransmit (MilliSeconds (delay), (*it), ei, address.GetIpv6 (), false);
                    }
                }
              break;
            default:
              break;
            }
        }
    }
}

} /* namespace ns3 */

