/* udp.c */
/*
    Copyright (C) 1992  Ross Biro

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2, or (at your option)
    any later version.

    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., 675 Mass Ave, Cambridge, MA 02139, USA. 

    The Author may be reached as bir7@leland.stanford.edu or
    C/O Department of Mathematics; Stanford University; Stanford, CA 94305
*/
/* $Id: udp.c,v 0.8.4.12 1993/01/26 22:04:00 bir7 Exp $ */
/* $Log: udp.c,v $
 * Revision 0.8.4.12  1993/01/26  22:04:00  bir7
 * Added support for proc fs.
 *
 * Revision 0.8.4.11  1993/01/23  18:00:11  bir7
 * added volatile keyword.
 *
 * Revision 0.8.4.10  1993/01/22  23:21:38  bir7
 * Merged with 99 pl4
 *
 * Revision 0.8.4.9  1992/12/12  19:25:04  bir7
 * cleaned up Log messages.
 *
 * Revision 0.8.4.8  1992/12/12  01:50:49  bir7
 * Changed connect.
 *
 * Revision 0.8.4.7  1992/12/05  21:35:53  bir7
 * Added more debuggin code.
 *
 * Revision 0.8.4.6  1992/12/03  19:52:20  bir7
 * fixed problems in udp_error.
 *
 * Revision 0.8.4.5  1992/11/18  15:38:03  bir7
 * fixed minor problem in waiting for memory.
 *
 * Revision 0.8.4.3  1992/11/15  14:55:30  bir7
 * Fixed ctrl-h and added NULL checking to print_uh
 *
 * Revision 0.8.4.2  1992/11/10  10:38:48  bir7
 * Change free_s to kfree_s and accidently changed free_skb to kfree_skb.
 *
 * Revision 0.8.4.1  1992/11/10  00:17:18  bir7
 * version change only.
 *
 * Revision 0.8.3.5  1992/11/10  00:14:47  bir7
 * Changed malloc to kmalloc and added Id and Log
 * */

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/socket.h>
#include <netinet/in.h>
#include "timer.h"
#include "ip.h"
#include "tcp.h"
#include "sock.h"
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/termios.h> /* for ioctl's */
#include <asm/system.h>
#include <asm/segment.h>
#include <linux/mm.h>
#include "../kern_sock.h" /* for PRINTK */
#include "udp.h"
#include "icmp.h"

#undef UDP_DEBUG

#ifdef PRINTK
#undef PRINTK
#endif

#ifdef UDP_DEBUG
#define PRINTK(x) printk x
#else
#define PRINTK(x) /**/
#endif

#define min(a,b) ((a)<(b)?(a):(b))



static void
print_uh(struct udp_header *uh)
{
  if (uh == NULL)
    {
      PRINTK (("(NULL)\n"));
      return;
    }
	PRINTK(("source = %d, dest = %d\n", net16(uh->source), net16(uh->dest)));
	PRINTK(("len = %d, check = %d\n", net16(uh->len), net16(uh->check)));
}


int
udp_select (volatile struct sock *sk, int sel_type, select_table *wait)
{
   select_wait(sk->sleep, wait);
   switch (sel_type)
     {
       case SEL_IN:
	if (sk->rqueue != NULL) 
	  {
	     return (1);
	  }
	return (0);

       case SEL_OUT:
	if (sk->prot->wspace(sk) >= MIN_WRITE_SPACE)
	  {
	     return (1);
	  }
	return (0);
	
       case SEL_EX:
	if (sk->err) return (1); /* can this ever happen? */
	return (0);
     }
   return (0);
}

/* this routine is called by the icmp module when it gets some
   sort of error condition.  If err < 0 then the socket should
   be closed and the error returned to the user.  If err > 0
   it's just the icmp type << 8 | icmp code.  
   header points to the first 8 bytes of the tcp header.  We need
   to find the appropriate port. */

void
udp_err (int err, unsigned char *header, unsigned long daddr,
	 unsigned long saddr, struct ip_protocol *protocol)
{
   struct udp_header *th;
   volatile struct sock *sk;
   
   PRINTK (("udp_err (err=%d, header=%X, daddr=%X, saddr=%X, ip_protocl=%X)\n"));

   th = (struct udp_header *)header;
   sk = get_sock (&udp_prot, net16(th->dest), saddr, th->source, daddr);

   if (sk == NULL) return;
   if (err & 0xff00 == (ICMP_SOURCE_QUENCH << 8))
     {
	if (sk->cong_window > 1)
	  sk->cong_window = sk->cong_window/2;
	return;
     }

   sk->err = icmp_err_convert[err & 0xff].errno;
   /* it's only fatal if we have connected to them. */
   if (icmp_err_convert[err & 0xff].fatal && sk->state == TCP_ESTABLISHED)
     {
	sk->prot->close(sk, 0);
     }

   return;

}

static  unsigned short
udp_check (struct udp_header *uh, int len,
	   unsigned long saddr, unsigned long daddr)
{
   unsigned long sum;
   PRINTK (("udp_check (uh=%X, len = %d, saddr = %X, daddr = %X)\n",
	   uh, len, saddr, daddr));

   print_uh (uh);

  __asm__("\t addl %%ecx,%%ebx\n"
	  "\t adcl %%edx,%%ebx\n"
	  "\t adcl $0, %%ebx\n"
	  : "=b" (sum)
	  : "0" (daddr), "c" (saddr), "d" ((net16(len) << 16) + IPPROTO_UDP*256)
	  : "cx","bx","dx" );

  if (len > 3)
    {
      __asm__(
	      "\tclc\n"
	      "1:\n"
	      "\t lodsl\n"
	      "\t adcl %%eax, %%ebx\n"
	      "\t loop 1b\n"
	      "\t adcl $0, %%ebx\n"
	      : "=b" (sum) , "=S" (uh)
	      : "0" (sum), "c" (len/4) ,"1" (uh)
	      : "ax", "cx", "bx", "si" );
    }

  /* convert from 32 bits to 16 bits. */
  __asm__(
	  "\t movl %%ebx, %%ecx\n"
	  "\t shrl $16,%%ecx\n"
	  "\t addw %%cx, %%bx\n"
	  "\t adcw $0, %%bx\n"
	  : "=b" (sum)
	  : "0" (sum)
	  : "bx", "cx");


  /* check for an extra word. */
  if ((len & 2) != 0)
    {
      __asm__("\t lodsw\n"
	      "\t addw %%ax,%%bx\n"
	      "\t adcw $0, %%bx\n"
	      : "=b" (sum), "=S" (uh)
	      : "0" (sum) ,"1" (uh)
	      : "si", "ax", "bx");
    }

  /* now check for the extra byte. */
  if ((len & 1) != 0)
    {
      __asm__("\t lodsb\n"
	      "\t movb $0,%%ah\n"
	      "\t addw %%ax,%%bx\n"
	      "\t adcw $0, %%bx\n"
	      : "=b" (sum)
	      : "0" (sum) ,"S" (uh)
	      : "si", "ax", "bx");
    }
  /* we only want the bottom 16 bits, but we never cleared
     the top 16. */
   return ((~sum) & 0xffff);
}

static  void
udp_send_check (struct udp_header *uh, unsigned long saddr, 
		unsigned long daddr, int len, volatile struct sock *sk)
{
  uh->check = 0;
  if (sk && sk->no_check) return;
  uh->check = udp_check (uh, len, saddr, daddr);
}

static  int
udp_loopback (volatile struct sock *sk, unsigned short port,
	      unsigned char *from,
	      int len, unsigned long daddr, unsigned long saddr)
{
	struct udp_header *uh;
	struct sk_buff *skb;
	volatile struct sock *pair;
	sk->inuse = 1;

	PRINTK (("udp_loopback \n"));

	pair = get_sock (sk->prot, net16(port), saddr,
			 sk->dummy_th.source, daddr);

	if (pair == NULL) return (0);

	skb = pair->prot->rmalloc (pair,
				   sizeof (*skb) + sizeof (*uh) + len + 4,
				   0, GFP_KERNEL);

	/* if we didn't get the memory, just drop the packet. */
	if (skb == NULL) return (len);
	skb->lock = 0;
	skb->mem_addr = skb;
	skb->mem_len = sizeof (*skb) + len + sizeof (*uh) + 4;

	skb->daddr = saddr;
	skb->saddr = daddr;

	skb->len = len;
	skb->h.raw = (unsigned char *)(skb+1);

	uh = skb->h.uh;
	uh -> source = sk->dummy_th.source;
	uh -> dest = port;
	uh -> len = len + sizeof (*uh);
/*	verify_area (VERIFY_WRITE, from , len); */
	memcpy_fromfs(uh+1, from, len);
	pair->inuse = 1;
	if (pair->rqueue == NULL)
	  {
		  pair->rqueue = skb;
		  skb->next = skb;
		  skb->prev = skb;
	  }
	else
	  {
		  skb->next = pair->rqueue;
		  skb->prev = pair->rqueue->prev;
		  skb->prev->next = skb;
		  skb->next->prev = skb;
	  }
	wake_up (pair->sleep);
	release_sock (pair);
	release_sock (sk);
	return (len);

}

static int
udp_sendto (volatile struct sock *sk, unsigned char *from, int len,
	    int noblock,
	    unsigned flags, struct sockaddr_in *usin, int addr_len)
{
	/* this should be easy, we just send the packet. */
	struct sk_buff *skb;
	struct udp_header *uh;
	unsigned char *buff;
	unsigned long saddr;
	int copied=0;
	int amt;
	struct device *dev=NULL;
	struct sockaddr_in sin;

	/* check the flags. */
	if (flags) return (-EINVAL);
	if (len < 0) return (-EINVAL);
	if (len == 0) return (0);

	PRINTK (("sendto len = %d\n", len));

	/* get and verify the address. */
	if (usin)
	  {
		  if (addr_len < sizeof (sin))
		    return (-EINVAL);
/*		  verify_area (VERIFY_WRITE, usin, sizeof (sin));*/
		  memcpy_fromfs (&sin, usin, sizeof(sin));
		  if (sin.sin_family &&
		      sin.sin_family != AF_INET)
		    return (-EINVAL);
		  if (sin.sin_port == 0)
		    return (-EINVAL);
	  }
	else
	  {
		  if (sk->state != TCP_ESTABLISHED)
		    return (-EINVAL);
		  sin.sin_family = AF_INET;
		  sin.sin_port = sk->dummy_th.dest;
		  sin.sin_addr.s_addr = sk->daddr;
	  }

	/* check for a valid saddr. */
	saddr = sk->saddr;
	if ((saddr &  0xff000000) == 0)
	  {
	     saddr = MY_IP_ADDR;
	  }

	/* if it's a broadcast, make sure we get it. */
	if ((sin.sin_addr.s_addr & 0xff000000) == 0)
	  {
	     int err;
	     err = udp_loopback (sk, sin.sin_port, from, len,
				 sin.sin_addr.s_addr, saddr);
	     if (err < 0)
	       return (err);
	  }

	sk->inuse = 1;

	while (len > 0)
	  {
		  int tmp;
		  skb = sk->prot->wmalloc (sk, len + sizeof (*skb)
					       + sk->prot->max_header, 0,
					   GFP_KERNEL);
		  /* this should never happen, but it is possible. */

		  if (skb == NULL)
		    {
		       tmp = sk->wmem_alloc;
		       release_sock (sk);
		       if (copied) return (copied);
		       if (noblock) return (-EAGAIN);
		       cli();
		       if (tmp <= sk->wmem_alloc)
			 {
			   interruptible_sleep_on (sk->sleep);
			   if (current->signal & ~current->blocked)
			     {
			       sti();
			       if (copied) return (copied);
			       return (-ERESTARTSYS);
			     }
			 }
		       sk->inuse = 1;
		       sti();
		       continue;
		    }

		  skb->lock = 0;
		  skb->mem_addr = skb;
		  skb->mem_len = len + sizeof (*skb) + sk->prot->max_header;
		  skb->sk = sk;
		  skb->free = 1;
		  skb->arp = 0;

		  /* now build the ip and dev header. */
		  buff = (unsigned char *)(skb+1);
		  tmp = sk->prot->build_header (skb, saddr,
						sin.sin_addr.s_addr, &dev,
						IPPROTO_UDP, sk->opt, skb->mem_len);
		  if (tmp < 0 )
		    {
			    sk->prot->wfree (sk, skb->mem_addr, skb->mem_len);
			    release_sock (sk);
			    return (tmp);
		    }
		  buff += tmp;

		  /* we shouldn't do this, instead we should just
		     let the ip protocol fragment the packet. */
		  amt = min (len + tmp + sizeof (*uh), dev->mtu);

		  PRINTK (("amt = %d, dev = %X, dev->mtu = %d\n",
			  amt, dev, dev->mtu));

		  skb->len = amt;
		  amt -= tmp; 

		  uh = (struct udp_header *)buff;
		  uh->len = net16(amt);
		  uh->source = sk->dummy_th.source;
		  uh->dest = sin.sin_port;

		  amt -= sizeof (*uh);
		  buff += sizeof (*uh);
		  if (amt < 0)
		    {
		      printk ("udp.c: amt = %d < 0\n",amt);
		      release_sock (sk);
		      return (copied);
		    }

/*		  verify_area (VERIFY_WRITE, from, amt);*/
		  memcpy_fromfs( buff, from, amt);

		  len -= amt;
		  copied += amt;
		  from += amt;
		  udp_send_check (uh, saddr, sin.sin_addr.s_addr,
				  amt+sizeof (*uh), sk);
				  
		  sk->prot->queue_xmit (sk, dev, skb, 1);
	  }
	release_sock (sk);
	return (copied);
}

static int
udp_write (volatile struct sock *sk, unsigned char *buff, int len, int noblock,
	   unsigned flags)
{
	return (udp_sendto (sk, buff, len, noblock, flags, NULL, 0));
}


static int
udp_ioctl (volatile struct sock *sk, int cmd, unsigned long arg)
{
  switch (cmd)
    {
    default:
      return (-EINVAL);

      case TIOCOUTQ:
	{
	  unsigned long amount;
	  if (sk->state == TCP_LISTEN)
	    return (-EINVAL);
	  amount = sk->prot->wspace(sk)/2;
	  verify_area (VERIFY_WRITE, (void *)arg, sizeof (unsigned long));
	  put_fs_long (amount, (unsigned long *)arg);
	  return (0);
	}


      case TIOCINQ:
/*      case FIONREAD:*/
	{
	  struct sk_buff *skb;
	  unsigned long amount;
	  if (sk->state == TCP_LISTEN)
	    return (-EINVAL);
	  amount = 0;
	  skb = sk->rqueue;
	  if (skb != NULL)
	    {
	      /* we will only return the amount of this packet since that is all
		 that will be read. */
	      amount = skb->len;
	    }

	  verify_area (VERIFY_WRITE, (void *)arg, sizeof (unsigned long));
	  put_fs_long (amount, (unsigned long *)arg);
	  return (0);
	}
    }
}

int
udp_recvfrom (volatile struct sock *sk, unsigned char *to, int len,
	      int noblock,
	      unsigned flags, struct sockaddr_in *sin, int *addr_len)
{
	/* this should be easy, if there is something there we
	   return it, otherwise we block. */
	int copied=0;
	struct sk_buff *skb;
	if (len == 0) return (0);
	if (len < 0) return (-EINVAL);

	/* this will pick up errors that occured
	   while the program was doing something
	   else. */
	if (sk->err)
	  {
	    int err;
	    err = -sk->err;
	    sk->err = 0;
	    return (err);
	  }
	if (addr_len)
	  {
		  verify_area (VERIFY_WRITE, addr_len, sizeof(*addr_len));
		  put_fs_long (sizeof (*sin), addr_len);
	  }
	sk->inuse = 1;
	while (sk->rqueue == NULL)
	  {
	    if (sk->shutdown & RCV_SHUTDOWN)
	      {
		return (0);
	      }

	    if (noblock)
	       {
		  release_sock (sk);
		  return (-EAGAIN);
	       }
	     release_sock (sk);
	     cli();
	     if (sk->rqueue == NULL)
	       {
		  interruptible_sleep_on (sk->sleep);
		  if (current->signal & ~current->blocked)
		    {
		       return (-ERESTARTSYS);
		    }
	       }
  	     sk->inuse = 1;
	     sti();
	  }
	skb = sk->rqueue;

	if (!(flags & MSG_PEEK))
	  {
		  if (skb->next == skb )
		    {
			    sk->rqueue = NULL;
		    }
		  else
		    {
			    sk->rqueue = (struct sk_buff *)sk->rqueue ->next;
			    skb->prev->next = skb->next;
			    skb->next->prev = skb->prev;
		    }
	  }
	copied = min (len, skb->len);
	verify_area (VERIFY_WRITE, to, copied);
	memcpy_tofs (to, skb->h.raw + sizeof (struct udp_header), copied);
	/* copy the address. */
	if (sin)
	  {
		  struct sockaddr_in addr;
		  addr.sin_family = AF_INET;
		  addr.sin_port = skb->h.uh->source;
		  addr.sin_addr.s_addr = skb->daddr;
		  verify_area (VERIFY_WRITE, sin, sizeof (*sin));
		  memcpy_tofs(sin, &addr, sizeof (*sin));
	  }

	if (!(flags & MSG_PEEK))
	  {
	     kfree_skb (skb, FREE_READ);
	  }
	release_sock (sk);
	return (copied);

}


int
udp_read (volatile struct sock *sk, unsigned char *buff, int len, int noblock,
	  unsigned flags)
{
	return (udp_recvfrom (sk, buff, len, noblock, flags, NULL, NULL));
}

int
udp_connect (volatile struct sock *sk, struct sockaddr_in *usin, int addr_len)
{
	struct sockaddr_in sin;
	if (addr_len < sizeof (sin)) return (-EINVAL);
/*	verify_area (VERIFY_WRITE, usin, sizeof (sin)); */
	memcpy_fromfs (&sin, usin, sizeof (sin));
	if (sin.sin_family && sin.sin_family != AF_INET)
	  return (-EAFNOSUPPORT);
	sk->daddr = sin.sin_addr.s_addr;
	sk->dummy_th.dest = sin.sin_port;
	sk->state = TCP_ESTABLISHED;
	return(0);
}

static void
udp_close(volatile struct sock *sk, int timeout)
{
	sk->inuse = 1;
	sk->state = TCP_CLOSE;
	if (sk->dead)
	  destroy_sock (sk);
	else
	  release_sock (sk);
}

int
udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
	unsigned long daddr, unsigned short len,
	unsigned long saddr, int redo, struct ip_protocol *protocol)
{
	/* all we need to do is get the socket, and then do a checksum. */
	struct proto *prot=&udp_prot;
	volatile struct sock *sk;
	struct udp_header *uh;

	uh = (struct udp_header *) skb->h.uh;

	if (dev->add_arp) dev->add_arp (saddr, skb, dev);

	sk = get_sock (prot, net16(uh->dest), saddr, uh->source, daddr);

	/* if we don't know about the socket, forget about it. */
	if (sk == NULL)
	  {
	    if ((daddr & 0xff000000 != 0) &&
		(daddr & 0xff000000 != 0xff000000))
	      {
		icmp_reply (skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, dev);
	      }
	    skb->sk = NULL;
	    kfree_skb (skb, 0);
	    return (0);
	  }


	if (!redo)
	  {
	     if (uh->check && udp_check (uh, len, saddr, daddr))
	       {
		  PRINTK (("bad udp checksum\n"));
		  skb->sk = NULL;
		  kfree_skb (skb, 0);
		  return (0);
	       }

	     skb->sk = sk;
	     skb->dev = dev;
	     skb->len = len;

	     /* these are supposed to be switched. */
	     skb->daddr = saddr;
	     skb->saddr = daddr;

	     /* Now deal with the in use. */
	     cli();
	     if (sk->inuse)
	       {
		  if (sk->back_log == NULL)
		    {
		       sk->back_log = skb;
		       skb->next = skb;
		       skb->prev = skb;
		    }
		  else
		    {
		       skb->next = sk->back_log;
		       skb->prev = sk->back_log->prev;
		       skb->prev->next = skb;
		       skb->next->prev = skb;
		    }
		  sti();
		  return (0);
	       }
	     sk->inuse = 1;
	     sti();
	  }

	/* charge it too the socket. */
	if (sk->rmem_alloc + skb->mem_len >= SK_RMEM_MAX)
	  {
	     skb->sk = NULL;
	     kfree_skb (skb, 0);
	     release_sock (sk);
	     return (0);
	  }
	     
	sk->rmem_alloc += skb->mem_len;

	/* At this point we should print the thing out. */
	PRINTK (("<< \n"));

	/* now add it to the data chain and wake things up. */
	if (sk->rqueue == NULL)
	  {
		  sk->rqueue = skb;
		  skb->next = skb;
		  skb->prev = skb;
	  }
	else
	  {
		  skb->next = sk->rqueue;
		  skb->prev = sk->rqueue->prev;
		  skb->prev->next = skb;
		  skb->next->prev = skb;
	  }

	skb->len = len - sizeof (*uh);

	if (!sk->dead)
	  wake_up (sk->sleep);

	release_sock (sk);
	return (0);
}



struct proto udp_prot =
{
  sock_wmalloc,
  sock_rmalloc,
  sock_wfree,
  sock_rfree,
  sock_rspace,
  sock_wspace,
  udp_close,
  udp_read,
  udp_write,
  udp_sendto,
  udp_recvfrom,
  ip_build_header,
  udp_connect,
  NULL,
  ip_queue_xmit,
  ip_retransmit,
  NULL,
  NULL,
  udp_rcv,
  udp_select,
  udp_ioctl,
  NULL,
  NULL,
  128,
  0,
  {NULL,},
  "UDP"
};

