#include <net/ftcp.h>
#include <net/tcp.h>
#include <linux/sched.h>
#include <linux/errno.h>

/* ftcp system call */
asmlinkage int sys_ftcp(int func, ftcp_t *fp)
{
  if (!capable(CAP_NET_ADMIN))
    return -EACCES;

  switch (func) {
  case FTCP_FREEZE_CHECK:
    ftcp_freeze_check(fp);
    break;
  case FTCP_FREEZE_OFF:
    ftcp_freeze_off(fp);
    break;
  case FTCP_FREEZE_ON:
    ftcp_freeze_on(fp);
    break;
  case FTCP_IF_CHECK:
    ftcp_if_check(fp);
    break;
  case FTCP_IF_DISABLE:
    ftcp_if_disable(fp);
    break;
  case FTCP_IF_ENABLE:
    ftcp_if_enable(fp);
    break;
  case FTCP_DUP_ACKS1:
    ftcp_send_dup_acks(fp, 1);
    break;
  case FTCP_DUP_ACKS3:
    ftcp_send_dup_acks(fp, 3);
    break;
  default:
    return -EINVAL;
  }
  return 0;
}

/* Check if freeze TCP is on for an interface */
void ftcp_freeze_check(ftcp_t *fp)
{
  struct device *d;

  if ((d = dev_get(fp->ifname)) == NULL)
  {
    fp->val = -ENODEV;
    return;
  }

  fp->val = (d->ftcp & FTCP_FREEZE) ? 1 : 0;
  return;
}

/* Turn freeze TCP on for an interface */
void ftcp_freeze_on(ftcp_t *fp)
{
  struct device *d;

  if ((d = dev_get(fp->ifname)) == NULL)
  {
    fp->val = -ENODEV;
    return;
  }

  fp->val = (d->ftcp & FTCP_FREEZE) ? 1 : 0;
  d->ftcp |= FTCP_FREEZE;
  return;
}

/* Turn freeze TCP off for an interface */
void ftcp_freeze_off(ftcp_t *fp)
{
  struct device *d;

  if ((d = dev_get(fp->ifname)) == NULL)
  {
    fp->val = -ENODEV;
    return;
  }

  fp->val = (d->ftcp & FTCP_FREEZE) ? 1 : 0;
  d->ftcp &= ~FTCP_FREEZE;
  return;
}

/* Check if an interface is disabled */
void ftcp_if_check(ftcp_t *fp)
{
  struct device *d;

  if ((d = dev_get(fp->ifname)) == NULL)
  {
    fp->val = -ENODEV;
    return;
  }

  fp->val = (d->ftcp & FTCP_IFOFF) ? 1 : 0;
  return;
}

/* Disable an interface */
void ftcp_if_disable(ftcp_t *fp)
{
  struct device *d;

  if ((d = dev_get(fp->ifname)) == NULL)
  {
    fp->val = -ENODEV;
    return;
  }

  fp->val = (d->ftcp & FTCP_IFOFF) ? 1 : 0;
  d->ftcp |= FTCP_IFOFF;
  return;
}

/* Endable an interface */
void ftcp_if_enable(ftcp_t *fp)
{
  struct device *d;

  if ((d = dev_get(fp->ifname)) == NULL)
  {
    fp->val = -ENODEV;
    return;
  }

  fp->val = (d->ftcp & FTCP_IFOFF) ? 1 : 0;
  d->ftcp &= ~FTCP_IFOFF;
  return;
}

/* Send duplicate ACKs out on all established TCP connections */
void ftcp_send_dup_acks(ftcp_t *fp, int num)
{
  extern struct sock *tcp_established_hash[TCP_HTABLE_SIZE];
  int i;

  fp->val = 0;

  /*
   *  Find established tcp connections that are not in TIME_WAIT state
   *    (1st half of hash table)
   */
  for(i = 0; i < TCP_HTABLE_SIZE/2; i++)
  {
    struct sock *sk = tcp_established_hash[i];

    for (sk = tcp_established_hash[i]; sk != NULL; sk = sk->next)
    {
      if (!(sk->reuse))
      {
	int j;

	/* FIXME: Check if this connection uses the given device */

	/* Send num duplicate ACKs */
	for(j = 0; j < num; j++)
	  tcp_send_ack(sk);
	fp->val++;
      }
    }
  }
  return;
}
