/*
 * todevice.{cc,hh} -- element writes packets to network via pcap library
 * Douglas S. J. De Couto, Eddie Kohler, John Jannotti
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2005 Regents of the University of California
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#if HAVE_NET_BPF_H
# include <sys/types.h>
# include <sys/time.h>
# include <net/bpf.h>
# define PCAP_DONT_INCLUDE_PCAP_BPF_H 1
#endif
#include "todevice.hh"
#include <click/error.hh>
#include <click/etheraddress.hh>
#include <click/confparse.hh>
#include <click/router.hh>
#include <click/standard/scheduleinfo.hh>
#include <click/packet_anno.hh>
#include <click/straccum.hh>
#include <stdio.h>
#include <unistd.h>

#if TODEVICE_BSD_DEV_BPF
# include <fcntl.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <net/if.h>
#elif TODEVICE_LINUX
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <net/if.h>
# include <net/if_packet.h>
# include <features.h>
# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
#  include <netpacket/packet.h>
# else
#  include <linux/if_packet.h>
# endif
#endif

CLICK_DECLS

ToDevice::ToDevice()
  : _task(this), _timer(this), _fd(-1), _my_fd(false),
    _q(0),
    _pulls(0)
{
}

ToDevice::~ToDevice()
{
}

int
ToDevice::configure(Vector<String> &conf, ErrorHandler *errh)
{
  
  if (cp_va_parse(conf, this, errh,
		  cpString, "interface name", &_ifname,
		  cpKeywords,
		  "DEBUG", cpBool, "debug", &_debug,
		  cpEnd) < 0)
    return -1;
  if (!_ifname)
    return errh->error("interface not set");
  return 0;
}

int
ToDevice::initialize(ErrorHandler *errh)
{
  _timer.initialize(this);
  _fd = -1;

#if TODEVICE_BSD_DEV_BPF
  
  /* pcap_open_live() doesn't open for writing. */
  for (int i = 0; i < 16 && _fd < 0; i++) {
    char tmp[64];
    sprintf(tmp, "/dev/bpf%d", i);
    _fd = open(tmp, 1);
  }
  if (_fd < 0)
    return(errh->error("open /dev/bpf* for write: %s", strerror(errno)));

  struct ifreq ifr;
  strncpy(ifr.ifr_name, _ifname.c_str(), sizeof(ifr.ifr_name));
  ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0;
  if (ioctl(_fd, BIOCSETIF, (caddr_t)&ifr) < 0)
    return errh->error("BIOCSETIF %s failed", ifr.ifr_name);
# ifdef BIOCSHDRCMPLT
  int yes = 1;
  if (ioctl(_fd, BIOCSHDRCMPLT, (caddr_t)&yes) < 0)
      errh->warning("BIOCSHDRCMPLT %s failed", ifr.ifr_name);
# endif
  _my_fd = true;

#elif TODEVICE_LINUX || TODEVICE_PCAP
  
  // find a FromDevice and reuse its socket if possible
  for (int ei = 0; ei < router()->nelements() && _fd < 0; ei++) {
    Element *e = router()->element(ei);
    FromDevice *fdev = (FromDevice *)e->cast("FromDevice");
    if (fdev && fdev->ifname() == _ifname && fdev->fd() >= 0) {
      _fd = fdev->fd();
      _my_fd = false;
    }
  }
  if (_fd < 0) {
# if TODEVICE_LINUX
    _fd = FromDevice::open_packet_socket(_ifname, errh);
    _my_fd = true;
# else
    return errh->error("ToDevice requires an initialized FromDevice on this platform") ;
# endif
  }
  if (_fd < 0)
    return -1;
  
#else
  
  return errh->error("ToDevice is not supported on this platform");
  
#endif

  // check for duplicate writers
  void *&used = router()->force_attachment("device_writer_" + _ifname);
  if (used)
    return errh->error("duplicate writer for device `%s'", _ifname.c_str());
  used = this;

  ScheduleInfo::join_scheduler(this, &_task, errh);
  _signal = Notifier::upstream_empty_signal(this, 0, &_task);
  return 0;
}

void
ToDevice::cleanup(CleanupStage)
{
  if (_fd >= 0 && _my_fd)
    close(_fd);
  _fd = -1;
}



/*
 * Linux select marks datagram fd's as writeable when the socket
 * buffer has enough space to do a send (sock_writeable() in
 * sock.h). BSD select always marks datagram fd's as writeable
 * (bpf_poll() in sys/net/bpf.c) This function should behave
 * appropriately under both.  It makes use of select if it correctly
 * tells us when buffers are available, and it schedules a backoff
 * timer if buffers are not available.  
 * --jbicket
 */
void
ToDevice::selected(int) 
{
  Packet *p;
  if (_q) {
    p = _q;
    _q = 0;
  } else {
    p = input(0).pull();
    _pulls++;
  }
  if (p) {
    int retval;
    const char *syscall;
    
#if TODEVICE_WRITE
    retval = ((uint32_t) write(_fd, p->data(), p->length()) == p->length() ? 0 : -1);
    syscall = "write";
#elif TODEVICE_SEND
    retval = send(_fd, p->data(), p->length(), 0);
    syscall = "send";
#else
    retval = 0;
#endif
    
    if (retval < 0) {
      if (errno == ENOBUFS || errno == EAGAIN) {
	assert(!_q);
	_q = p;
	/* we should backoff */
	remove_select(_fd, SELECT_WRITE);

	_backoff = (!_backoff) ? 1 : _backoff*2;
	_timer.schedule_after(Timestamp::make_usec(_backoff));

	if (_debug) {
	    Timestamp now = Timestamp::now();
	    click_chatter("%{element} backing off for %d at %{timestamp}\n",
			  this, _backoff, &now);
	}
	return;
      } else {
	click_chatter("ToDevice(%s) %s: %s", _ifname.c_str(), syscall, strerror(errno));
	checked_output_push(1, p);
      }
    } else {
      _backoff = 0;
      checked_output_push(0, p);
    }
  }
  if (!_q && !p && !_signal) {
    if (remove_select(_fd, SELECT_WRITE) < 0) {
      click_chatter("%s %{element} remove_select failed %d\n", 
		    Timestamp::now().unparse().c_str(), this, _fd);
    }
  }
}

void
ToDevice::run_timer(Timer *)
{
  if (_debug) {
    click_chatter("%s %{element}::%s\n",
		  Timestamp::now().unparse().c_str(), this, __func__);

  }

  if (_q || _signal) {
    if (add_select(_fd, SELECT_WRITE) < 0) {
      click_chatter("%s %{element}::%s add_select failed %d\n", 
		    Timestamp::now().unparse().c_str(), this, __func__, _fd);
    }
    selected(_fd);
  }
}

bool
ToDevice::run_task()
{
  if (_q || _signal) {
    if (add_select(_fd, SELECT_WRITE) < 0) {
      click_chatter("%s %{element}::%s add_select failed %d\n", 
		    Timestamp::now().unparse().c_str(), this, __func__, _fd);
    }
    selected(_fd);
    return true;
  }
  return false;
}


enum {H_DEBUG, H_SIGNAL, H_PULLS, H_Q};

String
ToDevice::read_param(Element *e, void *thunk)
{
  ToDevice *td = (ToDevice *)e;
  switch((uintptr_t) thunk) {
  case H_DEBUG:
      return String(td->_debug);
  case H_SIGNAL:
      return String(td->_signal);
  case H_PULLS:
      return String(td->_pulls);
  case H_Q:
      return String((bool) td->_q);
  default:
      return String();
  }
}

int 
ToDevice::write_param(const String &in_s, Element *e, void *vparam,
		     ErrorHandler *errh)
{
  ToDevice *td = (ToDevice *)e;
  String s = cp_uncomment(in_s);
  switch ((intptr_t)vparam) {
  case H_DEBUG: {
    bool debug;
    if (!cp_bool(s, &debug)) 
      return errh->error("debug parameter must be boolean");
    td->_debug = debug;
    break;
  }
  }
  return 0;
}
void
ToDevice::add_handlers()
{
  add_task_handlers(&_task);

  add_read_handler("debug", read_param, (void *) H_DEBUG);
  add_read_handler("pulls", read_param, (void *) H_PULLS);
  add_read_handler("signal", read_param, (void *) H_SIGNAL);
  add_read_handler("q", read_param, (void *) H_Q);

  add_write_handler("debug", write_param, (void *) H_DEBUG);

}

CLICK_ENDDECLS
ELEMENT_REQUIRES(FromDevice userlevel)
EXPORT_ELEMENT(ToDevice)


syntax highlighted by Code2HTML, v. 0.9.1