#include "global.h"
#include "udp.h"
#include "socket.h"
#include "usock.h"

static void s_urcall __ARGS((struct iface *iface,struct udp_cb *udp,int cnt));
static void autobind __ARGS((struct usock *up));
static int checkipaddr __ARGS((char *name,int namelen));

int
so_udp(up,protocol)
struct usock *up;
int protocol;
{
	return 0;
}
int
so_udp_bind(up)
struct usock *up;
{
	int s;
	struct sockaddr_in *sp;
	struct socket lsock;

	s = up - Usock + SOCKBASE;
	sp = (struct sockaddr_in *)up->name;
	lsock.address = sp->sin_addr.s_addr;
	lsock.port = sp->sin_port;
	up->cb.udp = open_udp(&lsock,s_urcall);
	up->cb.udp->user = s;
	return 0;
}
int
so_udp_conn(up)
struct usock *up;
{
	if(up->name == NULLCHAR){
		autobind(up);
	}
	return 0;
}
int
so_udp_recv(up,bpp,from,fromlen)
struct usock *up;
struct mbuf **bpp;
char *from;
int *fromlen;
{
	int cnt;
	struct udp_cb *udp;
	struct sockaddr_in *remote;
	struct socket fsocket;

	while((udp = up->cb.udp) != NULLUDP
	&& (cnt = recv_udp(udp,&fsocket,bpp)) == -1){
		if(up->noblock){
			errno = EWOULDBLOCK;
			return -1;
		} else if((errno = pwait(up)) != 0){
			return -1;
		}
	}
	if(udp == NULLUDP){
		/* Connection went away */
		errno = ENOTCONN;
		return -1;
	}
	if(from != NULLCHAR && fromlen != (int *)NULL && *fromlen >= SOCKSIZE){
		remote = (struct sockaddr_in *)from;
		remote->sin_family = AF_INET;
		remote->sin_addr.s_addr = fsocket.address;
		remote->sin_port = fsocket.port;
		*fromlen = SOCKSIZE;
	}
	return cnt;
}
int
so_udp_send(up,bp,to)
struct usock *up;
struct mbuf *bp;
char *to;
{
	struct sockaddr_in *local,*remote;
	struct socket lsock,fsock;

	if(up->name == NULLCHAR)
		autobind(up);
	local = (struct sockaddr_in *)up->name;
	lsock.address = local->sin_addr.s_addr;
	lsock.port = local->sin_port;
	if(to != NULLCHAR) {
		remote = (struct sockaddr_in *)to;
	} else if(up->peername != NULLCHAR){
		remote = (struct sockaddr_in *)up->peername;
	} else {
		free_p(bp);
		errno = ENOTCONN;
		return -1;
	}	
	if(checkipaddr(to,up->namelen) == -1){
		errno = EAFNOSUPPORT;
		return -1;
	}
	fsock.address = remote->sin_addr.s_addr;
	fsock.port = remote->sin_port;
	send_udp(&lsock,&fsock,up->tos,0,bp,0,0,0);
	return 0;
}
int
so_udp_qlen(up,rtx)
struct usock *up;
int rtx;
{
	int len;

	switch(rtx){
	case 0:
		len = up->cb.udp->rcvcnt;
		break;
	case 1:
		len = 0;
		break;
	}
	return len;
}
int
so_udp_close(up)
struct usock *up;
{
	if(up->cb.udp != NULLUDP){
		del_udp(up->cb.udp);
	}
	return 0;
}
int
so_udp_shut(up,how)
struct usock *up;
int how;
{
	int s;

	s = up - Usock + SOCKBASE;
	close_s(s);
	return 0;
}
static void
s_urcall(iface,udp,cnt)
struct iface *iface;
struct udp_cb *udp;
int cnt;
{
	psignal(itop(udp->user),1);
	pwait(NULL);
}

/* Issue an automatic bind of a local address */
static void
autobind(up)
struct usock *up;
{
	struct sockaddr_in local;
	int s;

	s = up - Usock + SOCKBASE;
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = INADDR_ANY;
	local.sin_port = Lport++;
	bind(s,(char *)&local,sizeof(struct sockaddr_in));
}
static int
checkipaddr(name,namelen)
char *name;
int namelen;
{
	struct sockaddr_in *sock;

	sock = (struct sockaddr_in *)name;
	if(sock->sin_family != AF_INET || namelen != sizeof(struct sockaddr_in))
		return -1;
	return 0;
}
int
so_udp_stat(up)
struct usock *up;
{
	st_udp(up->cb.udp,0);
	return 0;
}
