/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2007 Emmanuelle Laprise
 *
 * 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: Emmanuelle Laprise <emmanuelle.laprise@bluekazoo.ca>
 */

#include "ns3/log.h"
#include "ns3/queue.h"
#include "ns3/simulator.h"
#include "ns3/ethernet-header.h"
#include "ns3/ethernet-trailer.h"
#include "ns3/llc-snap-header.h"
#include "ns3/error-model.h"
#include "ns3/enum.h"
#include "ns3/boolean.h"
#include "ns3/pointer.h"
#include "ns3/trace-source-accessor.h"
#include "csma-net-device.h"
#include "csma-channel.h"


NS_LOG_COMPONENT_DEFINE ("CsmaNetDevice");

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (CsmaNetDevice);

TypeId 
CsmaNetDevice::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::CsmaNetDevice")
    .SetParent<NetDevice> ()
    .AddConstructor<CsmaNetDevice> ()
    .AddAttribute ("Address", "The address of this device.",
                   Mac48AddressValue (Mac48Address ("ff:ff:ff:ff:ff:ff")),
                   MakeMac48AddressAccessor (&CsmaNetDevice::m_address),
                   MakeMac48AddressChecker ())
    .AddAttribute ("EncapsulationMode", "The mode of link-layer encapsulation to use.",
                   EnumValue (LLC),
                   MakeEnumAccessor (&CsmaNetDevice::m_encapMode),
                   MakeEnumChecker (ETHERNET_V1, "EthernetV1",
                                    IP_ARP, "IpArp",
                                    RAW, "Raw",
                                    LLC, "Llc"))
    .AddAttribute ("SendEnable", "should tx be enabled ?",
                   BooleanValue (true),
                   MakeBooleanAccessor (&CsmaNetDevice::m_sendEnable),
                   MakeBooleanChecker ())
    .AddAttribute ("ReceiveEnable", "should rx be enabled ?",
                   BooleanValue (true),
                   MakeBooleanAccessor (&CsmaNetDevice::m_receiveEnable),
                   MakeBooleanChecker ())
    .AddAttribute ("DataRate", "XXX",
                   DataRateValue (DataRate (0xffffffff)),
                   MakeDataRateAccessor (&CsmaNetDevice::m_bps),
                   MakeDataRateChecker ())
    .AddAttribute ("RxErrorModel", "XXX",
                   PointerValue (),
                   MakePointerAccessor (&CsmaNetDevice::m_receiveErrorModel),
                   MakePointerChecker<ErrorModel> ())
    .AddAttribute ("TxQueue", "XXX",
                   PointerValue (),
                   MakePointerAccessor (&CsmaNetDevice::m_queue),
                   MakePointerChecker<Queue> ())
    .AddTraceSource ("Rx", "Receive MAC packet.",
                     MakeTraceSourceAccessor (&CsmaNetDevice::m_rxTrace))
    .AddTraceSource ("Drop", "Drop MAC packet.",
                     MakeTraceSourceAccessor (&CsmaNetDevice::m_dropTrace))
    ;
  return tid;
}

CsmaNetDevice::CsmaNetDevice ()
  : m_name (""),
    m_linkUp (false),
    m_mtu (0xffff)
{
  NS_LOG_FUNCTION (this);
  m_txMachineState = READY;
  m_tInterframeGap = Seconds(0);
  m_channel = 0; 
}

CsmaNetDevice::~CsmaNetDevice()
{
  NS_LOG_FUNCTION_NOARGS ();
  m_queue = 0;
}

void 
CsmaNetDevice::DoDispose ()
{
  NS_LOG_FUNCTION_NOARGS ();
  m_channel = 0;
  m_node = 0;
  NetDevice::DoDispose ();
}

void 
CsmaNetDevice::SetAddress (Mac48Address self)
{
  m_address = self;
}

void
CsmaNetDevice::SetSendEnable (bool sendEnable)
{
  NS_LOG_FUNCTION_NOARGS ();
  m_sendEnable = sendEnable;
}

void
CsmaNetDevice::SetReceiveEnable (bool receiveEnable)
{
  NS_LOG_FUNCTION_NOARGS ();
  m_receiveEnable = receiveEnable;
}

bool
CsmaNetDevice::IsSendEnabled (void)
{
  NS_LOG_FUNCTION_NOARGS ();
  return (m_sendEnable);
}

bool
CsmaNetDevice::IsReceiveEnabled (void)
{
  NS_LOG_FUNCTION_NOARGS ();
  return (m_receiveEnable);
}

void 
CsmaNetDevice::SetDataRate (DataRate bps)
{
  NS_LOG_FUNCTION_NOARGS ();
  if (!m_channel || bps <= m_channel->GetDataRate ())
    {
      m_bps = bps;
    }
}

void 
CsmaNetDevice::SetInterframeGap (Time t)
{
  NS_LOG_FUNCTION_NOARGS ();
  m_tInterframeGap = t;
}

void 
CsmaNetDevice::SetBackoffParams (Time slotTime, uint32_t minSlots, 
                                 uint32_t maxSlots, uint32_t ceiling, 
                                 uint32_t maxRetries)
{
  NS_LOG_FUNCTION_NOARGS ();
  m_backoff.m_slotTime = slotTime;
  m_backoff.m_minSlots = minSlots;
  m_backoff.m_maxSlots = maxSlots;
  m_backoff.m_ceiling = ceiling;
  m_backoff.m_maxRetries = maxRetries;
}

void 
CsmaNetDevice::AddHeader (Ptr<Packet> p, Mac48Address dest,
                            uint16_t protocolNumber)
{
  NS_LOG_FUNCTION_NOARGS ();
  if (m_encapMode == RAW)
    {
      return;
    }
  EthernetHeader header (false);
  EthernetTrailer trailer;
  Mac48Address source = Mac48Address::ConvertFrom (GetAddress ());
  header.SetSource(source);
  header.SetDestination(dest);

  uint16_t lengthType = 0;
  switch (m_encapMode) 
    {
    case ETHERNET_V1:
      lengthType = p->GetSize() + header.GetSerializedSize() + trailer.GetSerializedSize();
      break;
    case IP_ARP:
      lengthType = protocolNumber;
      break;
    case LLC: {
      lengthType = p->GetSize() + header.GetSerializedSize() + trailer.GetSerializedSize();
      LlcSnapHeader llc;
      llc.SetType (protocolNumber);
      p->AddHeader (llc);
    } break;
    case RAW:
      NS_ASSERT (false);
      break;
    }
  header.SetLengthType (lengthType);
  p->AddHeader(header);
  trailer.CalcFcs(p);
  p->AddTrailer(trailer);
}

bool 
CsmaNetDevice::ProcessHeader (Ptr<Packet> p, uint16_t & param)
{
  NS_LOG_FUNCTION_NOARGS ();
  if (m_encapMode == RAW)
    {
      return true;
    }
  EthernetHeader header (false);
  EthernetTrailer trailer;
      
  p->RemoveTrailer(trailer);
  trailer.CheckFcs(p);
  p->RemoveHeader(header);

  if ((header.GetDestination() != GetBroadcast ()) &&
      (header.GetDestination() != GetAddress ()))
    {
      return false;
    }

  switch (m_encapMode)
    {
    case ETHERNET_V1:
    case IP_ARP:
      param = header.GetLengthType();
      break;
    case LLC: {
      LlcSnapHeader llc;
      p->RemoveHeader (llc);
      param = llc.GetType ();
    } break;
    case RAW:
      NS_ASSERT (false);
      break;
    }
  return true;
}

void
CsmaNetDevice::TransmitStart ()
{
  NS_LOG_FUNCTION_NOARGS ();
  NS_LOG_LOGIC ("m_currentPkt=" << m_currentPkt);
  NS_LOG_LOGIC ("UID is " << m_currentPkt->GetUid ());
//
// This function is called to start the process of transmitting a packet.
// We need to tell the channel that we've started wiggling the wire and
// schedule an event that will be executed when it's time to tell the 
// channel that we're done wiggling the wire.
//
  NS_ASSERT_MSG((m_txMachineState == READY) || (m_txMachineState == BACKOFF), 
                "Must be READY to transmit. Tx state is: " 
                << m_txMachineState);

  // Only transmit if send side of net device is enabled
  if (!IsSendEnabled())
    return;

  if (m_channel->GetState() != IDLE)
    { // Channel busy, backoff and rechedule TransmitStart()
      m_txMachineState = BACKOFF;
      if (m_backoff.MaxRetriesReached())
        { // Too many retries reached, abort transmission of packet
          TransmitAbort();
        } 
      else 
        {
          m_backoff.IncrNumRetries();
          Time backoffTime = m_backoff.GetBackoffTime();

          NS_LOG_LOGIC ("Channel busy, backing off for " << 
            backoffTime.GetSeconds () << " sec");

          Simulator::Schedule (backoffTime, 
                               &CsmaNetDevice::TransmitStart, 
                               this);
        }
    } 
  else 
    {
      // Channel is free, transmit packet
      m_txMachineState = BUSY;
      Time tEvent = Seconds (m_bps.CalculateTxTime(m_currentPkt->GetSize()));
      
      NS_LOG_LOGIC ("Schedule TransmitCompleteEvent in " << 
        tEvent.GetSeconds () << "sec");
      
      Simulator::Schedule (tEvent, 
                           &CsmaNetDevice::TransmitCompleteEvent, 
                           this);
      if (!m_channel->TransmitStart (m_currentPkt, m_deviceId))
        {
          NS_LOG_WARN ("Channel transmit start did not work at " << 
            tEvent.GetSeconds () << "sec");
          m_txMachineState = READY;
        } 
      else 
        {
          // Transmission success, reset backoff time parameters.
          m_backoff.ResetBackoffTime();
        }
    }
}


void
CsmaNetDevice::TransmitAbort (void)
{
  NS_LOG_FUNCTION_NOARGS ();
  NS_LOG_LOGIC ("Pkt UID is " << m_currentPkt->GetUid () << ")");

  // Try to transmit a new packet
  m_currentPkt = m_queue->Dequeue ();
  NS_ASSERT_MSG(m_currentPkt != 0, "IsEmpty false but no Packet on queue?");
  m_backoff.ResetBackoffTime();
  m_txMachineState = READY;
  TransmitStart ();
}

void
CsmaNetDevice::TransmitCompleteEvent (void)
{
  NS_LOG_FUNCTION_NOARGS ();
//
// This function is called to finish the  process of transmitting a packet.
// We need to tell the channel that we've stopped wiggling the wire and
// schedule an event that will be executed when it's time to re-enable
// the transmitter after the interframe gap.
//
  NS_ASSERT_MSG(m_txMachineState == BUSY, "Must be BUSY if transmitting");
  // Channel should be transmitting
  NS_ASSERT(m_channel->GetState() == TRANSMITTING);
  m_txMachineState = GAP;

  NS_LOG_LOGIC ("Pkt UID is " << m_currentPkt->GetUid () << ")");
  m_channel->TransmitEnd (); 

  NS_LOG_LOGIC ("Schedule TransmitReadyEvent in "
    << m_tInterframeGap.GetSeconds () << "sec");

  Simulator::Schedule (m_tInterframeGap, 
                       &CsmaNetDevice::TransmitReadyEvent, 
                       this);
}

void
CsmaNetDevice::TransmitReadyEvent (void)
{
  NS_LOG_FUNCTION_NOARGS ();
//
// This function is called to enable the transmitter after the interframe
// gap has passed.  If there are pending transmissions, we use this opportunity
// to start the next transmit.
//
  NS_ASSERT_MSG(m_txMachineState == GAP, "Must be in interframe gap");
  m_txMachineState = READY;

  // Get the next packet from the queue for transmitting
  if (m_queue->IsEmpty())
    {
      return;
    }
  else
    {
      m_currentPkt = m_queue->Dequeue ();
      NS_ASSERT_MSG(m_currentPkt != 0, "IsEmpty false but no Packet on queue?");
      TransmitStart ();
    }
}

bool
CsmaNetDevice::Attach (Ptr<CsmaChannel> ch)
{
  NS_LOG_FUNCTION (this << &ch);

  m_channel = ch;

  m_deviceId = m_channel->Attach(this);
  m_bps = m_channel->GetDataRate ();
  m_tInterframeGap = m_channel->GetDelay ();

  /* 
   * For now, this device is up whenever a channel is attached to it.
   */
  NotifyLinkUp ();
  return true;
}

void
CsmaNetDevice::SetQueue (Ptr<Queue> q)
{
  NS_LOG_FUNCTION (this << q);

  m_queue = q;
}

void CsmaNetDevice::SetReceiveErrorModel (Ptr<ErrorModel> em)
{
  NS_LOG_FUNCTION (em);
  
  m_receiveErrorModel = em; 
}

void
CsmaNetDevice::Receive (Ptr<Packet> packet)
{
  NS_LOG_FUNCTION_NOARGS ();

  EthernetHeader header (false);
  EthernetTrailer trailer;
  Mac48Address broadcast;
  Mac48Address multicast;
  Mac48Address destination;

  NS_LOG_LOGIC ("UID is " << packet->GetUid());

  // Only receive if send side of net device is enabled
  if (!IsReceiveEnabled())
    {
      m_dropTrace (packet);
      return;
    }

  m_rxTrace (packet);

  if (m_encapMode == RAW)
    {
      m_rxCallback (this, packet, 0, GetBroadcast ());
      m_dropTrace (packet);
      return;
    }
  packet->RemoveTrailer(trailer);
  trailer.CheckFcs(packet);
  packet->RemoveHeader(header);

  NS_LOG_LOGIC ("Pkt destination is " << header.GetDestination ());
//
// An IP host group address is mapped to an Ethernet multicast address
// by placing the low-order 23-bits of the IP address into the low-order
// 23 bits of the Ethernet multicast address 01-00-5E-00-00-00 (hex).
//
// We are going to receive all packets destined to any multicast address,
// which means clearing the low-order 23 bits the header destination 
//
  Mac48Address mcDest;
  uint8_t      mcBuf[6];

  header.GetDestination ().CopyTo (mcBuf);
  mcBuf[3] &= 0x80;
  mcBuf[4] = 0;
  mcBuf[5] = 0;
  mcDest.CopyFrom (mcBuf);

  multicast = Mac48Address::ConvertFrom (GetMulticast ());
  broadcast = Mac48Address::ConvertFrom (GetBroadcast ());
  destination = Mac48Address::ConvertFrom (GetAddress ());

  if ((header.GetDestination () != broadcast) &&
      (mcDest != multicast) &&
      (header.GetDestination () != destination))
    {
      NS_LOG_LOGIC ("Dropping pkt ");
      m_dropTrace (packet);
      return;
    }

  if (m_receiveErrorModel && m_receiveErrorModel->IsCorrupt (packet) )
    {
      NS_LOG_LOGIC ("Dropping pkt due to error model ");
      m_dropTrace (packet);
      // Do not forward up; let this packet go
    }
  else
    {
//
// protocol must be initialized to avoid a compiler warning in the RAW
// case that breaks the optimized build.
//
      uint16_t protocol = 0;

      switch (m_encapMode)
        {
        case ETHERNET_V1:
        case IP_ARP:
          protocol = header.GetLengthType();
          break;
        case LLC: 
          {
            LlcSnapHeader llc;
            packet->RemoveHeader (llc);
            protocol = llc.GetType ();
          } 
          break;
        case RAW:
          NS_ASSERT (false);
          break;
        }
      m_rxCallback (this, packet, protocol, header.GetSource ());
    }
}

Ptr<Queue>
CsmaNetDevice::GetQueue(void) const 
{ 
  NS_LOG_FUNCTION_NOARGS ();
  return m_queue;
}

void
CsmaNetDevice::NotifyLinkUp (void)
{
  m_linkUp = true;
  if (!m_linkChangeCallback.IsNull ())
    {
      m_linkChangeCallback ();
    }
}

void 
CsmaNetDevice::SetName(const std::string name)
{
  m_name = name;
}
std::string 
CsmaNetDevice::GetName(void) const
{
  return m_name;
}
void 
CsmaNetDevice::SetIfIndex(const uint32_t index)
{
  m_ifIndex = index;
}
uint32_t 
CsmaNetDevice::GetIfIndex(void) const
{
  return m_ifIndex;
}
Ptr<Channel> 
CsmaNetDevice::GetChannel (void) const
{
  return m_channel;
}
Address 
CsmaNetDevice::GetAddress (void) const
{
  return m_address;
}
bool 
CsmaNetDevice::SetMtu (const uint16_t mtu)
{
  m_mtu = mtu;
  return true;
}
uint16_t 
CsmaNetDevice::GetMtu (void) const
{
  return m_mtu;
}
bool 
CsmaNetDevice::IsLinkUp (void) const
{
  return m_linkUp;
}
void 
CsmaNetDevice::SetLinkChangeCallback (Callback<void> callback)
{
  m_linkChangeCallback = callback;
}
bool 
CsmaNetDevice::IsBroadcast (void) const
{
  return true;
}
Address
CsmaNetDevice::GetBroadcast (void) const
{
  return Mac48Address ("ff:ff:ff:ff:ff:ff");
}
bool 
CsmaNetDevice::IsMulticast (void) const
{
  return true;
}
Address 
CsmaNetDevice::GetMulticast (void) const
{
  return Mac48Address ("01:00:5e:00:00:00");
}
Address 
CsmaNetDevice::MakeMulticastAddress (Ipv4Address multicastGroup) const
{
  NS_LOG_FUNCTION (this << multicastGroup);
//
// First, get the generic multicast address.
//
  Address hardwareDestination = GetMulticast ();

  NS_LOG_LOGIC ("Device multicast address: " << hardwareDestination);
//
// It's our address, and we know we're playing with an EUI-48 address here
// primarily since we know that by construction, but also since the parameter
// is an Ipv4Address.
//
  Mac48Address etherAddr = Mac48Address::ConvertFrom (hardwareDestination);
//
// We now have the multicast address in an abstract 48-bit container.  We 
// need to pull it out so we can play with it.  When we're done, we have the 
// high order bits in etherBuffer[0], etc.
//
  uint8_t etherBuffer[6];
  etherAddr.CopyTo (etherBuffer);
//
// Now we need to pull the raw bits out of the Ipv4 destination address.
//
  uint8_t ipBuffer[4];
  multicastGroup.Serialize (ipBuffer);
//
// RFC 1112 says that an Ipv4 host group address is mapped to an EUI-48
// multicast address by placing the low-order 23-bits of the IP address into 
// the low-order 23 bits of the Ethernet multicast address 
// 01-00-5E-00-00-00 (hex). 
//
  etherBuffer[3] |= ipBuffer[1] & 0x7f;
  etherBuffer[4] = ipBuffer[2];
  etherBuffer[5] = ipBuffer[3];
//
// Now, etherBuffer has the desired ethernet multicast address.  We have to
// suck these bits back into the Mac48Address,
//
  etherAddr.CopyFrom (etherBuffer);
//
// Implicit conversion (operator Address ()) is defined for Mac48Address, so
// use it by just returning the EUI-48 address which is automagically converted
// to an Address.
//
  NS_LOG_LOGIC ("multicast address is " << etherAddr);

  return etherAddr;
}
bool 
CsmaNetDevice::IsPointToPoint (void) const
{
  return false;
}
bool 
CsmaNetDevice::Send(Ptr<Packet> packet, const Address& dest, uint16_t protocolNumber)
{
  NS_LOG_FUNCTION_NOARGS ();
  NS_LOG_LOGIC ("p=" << packet);
  NS_LOG_LOGIC ("UID is " << packet->GetUid () << ")");

  NS_ASSERT (IsLinkUp ());

  // Only transmit if send side of net device is enabled
  if (!IsSendEnabled())
    return false;

  Mac48Address destination = Mac48Address::ConvertFrom (dest);
  AddHeader(packet, destination, protocolNumber);

  // Place the packet to be sent on the send queue
  if (m_queue->Enqueue(packet) == false )
    {
      return false;
    }
  // If the device is idle, we need to start a transmission. Otherwise,
  // the transmission will be started when the current packet finished
  // transmission (see TransmitCompleteEvent)
  if (m_txMachineState == READY) 
    {
      // Store the next packet to be transmitted
      m_currentPkt = m_queue->Dequeue ();
      if (m_currentPkt != 0)
        {
          TransmitStart();
        }
    }
  return true;
}
Ptr<Node> 
CsmaNetDevice::GetNode (void) const
{
  return m_node;
}
void 
CsmaNetDevice::SetNode (Ptr<Node> node)
{
  m_node = node;
}
bool 
CsmaNetDevice::NeedsArp (void) const
{
  if ((m_encapMode == IP_ARP) || (m_encapMode == LLC))
    {
      return true;
    } 
  else 
    {
      return false;
    }
}
void 
CsmaNetDevice::SetReceiveCallback (NetDevice::ReceiveCallback cb)
{
  m_rxCallback = cb;
}

} // namespace ns3
