/*
 * 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-2008 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(&_task), _fd(-1), _my_fd(false),
    _q(0),
    _pulls(0)
{
}

ToDevice::~ToDevice()
{
}

int
ToDevice::configure(Vector<String> &conf, ErrorHandler *errh)
{

  if (cp_va_kparse(conf, this, errh,
		   "DEVNAME", cpkP+cpkM, cpString, &_ifname,
		   "DEBUG", 0, cpBool, &_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
 */
bool
ToDevice::run_task(Task *)
{
    Packet *p = _q;
    _q = 0;
    if (!p) {
	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) {
	    _backoff = 0;
	    checked_output_push(0, p);

	} else if (errno == ENOBUFS || errno == EAGAIN) {
	    assert(!_q);
	    _q = p;

	    if (!_backoff) {
		_backoff = 1;
		add_select(_fd, SELECT_WRITE);
	    } else {
		_timer.schedule_after(Timestamp::make_usec(_backoff));
		if (_backoff < 32768)
		    _backoff *= 2;
		if (_debug) {
		    Timestamp now = Timestamp::now();
		    click_chatter("%{element} backing off for %d at %{timestamp}\n", this, _backoff, &now);
		}
	    }

	    return false;

	} else {
	    click_chatter("ToDevice(%s) %s: %s", _ifname.c_str(), syscall, strerror(errno));
	    checked_output_push(1, p);
	}
    }

    if (!p && !_signal)
	return false;
    _task.fast_reschedule();
    return p != 0;
}

void
ToDevice::selected(int, int)
{
    _task.reschedule();
    remove_select(_fd, SELECT_WRITE);
}


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, Handler::CHECKBOX);
  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)
