/*
 * DECnet       An implementation of the DECnet protocol suite for the LINUX
 *              operating system.  DECnet is implemented using the  BSD Socket
 *              interface as the means of communication with the user level.
 *
 *              DECnet Raw Sockets Interface
 *
 * Author:      Steve Whitehouse <SteveW@ACM.org>
 *
 *
 * Changes:
 *           Steve Whitehouse - connect() function.
 */

#include <linux/config.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <net/sock.h>
#include <net/dn.h>
#include <net/dn_raw.h>

static struct sock *dn_raw_nsp_sklist = NULL;
static struct sock *dn_raw_routing_sklist = NULL;
#ifdef CONFIG_DECNET_MOP
static struct sock *dn_raw_mop_sklist = NULL;
#endif /* CONFIG_DECNET_MOP */

static void dn_raw_autobind(struct sock *sk)
{

	switch(sk->protocol) {
		case DNPROTO_NSP:
			sklist_insert_socket(&dn_raw_nsp_sklist, sk);
			break;
		case DNPROTO_ROU:
			sklist_insert_socket(&dn_raw_routing_sklist, sk);
			break;
#ifdef CONFIG_DECNET_MOP
		case DNPROTO_MOP:
			sklist_insert_socket(&dn_raw_mop_sklist, sk);
#endif /* CONFIG_DECNET_MOP */
		default:
			printk(KERN_DEBUG "dn_raw_autobind: Unknown protocol\n");
			return;
	}

	sk->zapped = 0;
}

static int dn_raw_release(struct socket *sock, struct socket *peer)
{
	struct sock *sk = sock->sk;

	if (sk == NULL)
		return 0;

	if (!sk->dead) sk->state_change(sk);

	sk->dead = 1;
	sk->socket = NULL;
	sock->sk = NULL;

	switch(sk->protocol) {
		case DNPROTO_NSP:
			sklist_destroy_socket(&dn_raw_nsp_sklist, sk);
			break;
		case DNPROTO_ROU:
			sklist_destroy_socket(&dn_raw_routing_sklist, sk);
			break;
#ifdef CONFIG_DECNET_MOP
		case DNPROTO_MOP:
			sklist_destroy_socket(&dn_raw_mop_sklist, sk);
			break;
#endif /* CONFIG_DECNET_MOP */
	}

	return 0;
}

/*
 * Bind does odd things with raw sockets. Its basically used to filter
 * the incomming packets, but this differs with the different layers
 * at which you extract packets.
 *
 * For Routing layer sockets, the object name is a host ordered unsigned
 * short which is a mask for the 16 different types of possible routing
 * packet. I'd like to also select by destination address of the packets
 * but alas, this is rather too dificult to do at the moment.
 */
static int dn_raw_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	struct sock *sk = sock->sk;
	struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;

	if (addr_len != sizeof(struct sockaddr_dn))
		return -EINVAL;

	if (sk->zapped == 0)
		return -EINVAL;

	switch(sk->protocol) {
		case DNPROTO_ROU:
			if (addr->sdn_objnamel && (addr->sdn_objnamel != 2))
				return -EINVAL;
			/* Fall through here */
		case DNPROTO_NSP:
			if (addr->sdn_add.a_len && (addr->sdn_add.a_len != 2))
				return -EINVAL;
			break;
		default:
			return -EPROTONOSUPPORT;
	}

	if (addr->sdn_objnamel > (DN_MAXOBJL-1))
		return -EINVAL;

	if (addr->sdn_add.a_len > DN_MAXADDL)
		return -EINVAL;


	memcpy(&sk->protinfo.dn.addr, addr, sizeof(struct sockaddr_dn));

	dn_raw_autobind(sk);

	return 0;
}

/*
 * This is to allow send() and write() to work. You set the destination address
 * with this function.
 */
static int dn_raw_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
	struct sock *sk = sock->sk;
	struct dn_scp *scp = &sk->protinfo.dn;
	struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;

	if (addr_len != sizeof(struct sockaddr_dn))
		return -EINVAL;

	if (saddr->sdn_family != AF_DECnet)
		return -EINVAL;

	if (saddr->sdn_objnamel > (DN_MAXOBJL-1))
		return -EINVAL;

	if (saddr->sdn_add.a_len > DN_MAXADDL)
		return -EINVAL;

	if (sk->zapped)
		dn_raw_autobind(sk);

	memcpy(&scp->peer, saddr, sizeof(struct sockaddr_dn));

	return 0;
}

/*
 * TBD.
 */
static int dn_raw_sendmsg(struct socket *sock, struct msghdr *hdr, int size,
			struct scm_cookie *scm)
{
	struct sock *sk = sock->sk;

	if (sk->zapped)
		dn_raw_autobind(sk);

	if (sk->protocol != DNPROTO_NSP)
		return -EOPNOTSUPP;

	return 0;
}

/*
 * This works fine, execpt that it doesn't report the originating address
 * or anything at the moment.
 */
static int dn_raw_recvmsg(struct socket *sock, struct msghdr *msg, int size,
			int flags, struct scm_cookie *scm)
{
	struct sock *sk = sock->sk;
	struct sk_buff *skb;
	int err = 0;
	int copied = 0;

	if (sk->zapped)
		dn_raw_autobind(sk);

	if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &err)) == NULL)
		goto out;

	copied = skb->len;

	if (copied > size) {
		copied = size;
		msg->msg_flags |= MSG_TRUNC;
	}

	if ((err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied)) != 0) {
		if (flags & MSG_PEEK)
			atomic_dec(&skb->users);
		else
			skb_queue_head(&sk->receive_queue, skb);

		goto out;
	}

	skb_free_datagram(sk, skb);

out:
	return copied ? copied : err;
}

struct proto_ops dn_raw_proto_ops = {
	AF_DECnet,

	sock_no_dup,
	dn_raw_release,
	dn_raw_bind,
	dn_raw_connect,
	sock_no_socketpair,
	sock_no_accept,
	sock_no_getname,
	datagram_poll,
	sock_no_ioctl,
	sock_no_listen,
	sock_no_shutdown,
	sock_no_setsockopt,
	sock_no_getsockopt,
	sock_no_fcntl,
	dn_raw_sendmsg,
	dn_raw_recvmsg
};

#ifdef CONFIG_PROC_FS
int dn_raw_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
{
	int len = 0;
	off_t pos = 0;
	off_t begin = 0;
	struct sock *sk;

	cli();	
	for(sk = dn_raw_nsp_sklist; sk; sk = sk->next) {	
		len += sprintf(buffer+len, "NSP\n");

		pos = begin + len;

		if (pos < offset) {
			len = 0;
			begin = pos;
		}

		if (pos > offset + length)
			goto all_done;

	}

	for(sk = dn_raw_routing_sklist; sk; sk = sk->next) {
		len += sprintf(buffer+len, "ROU\n");

		pos = begin + len;

		if (pos < offset) {
			len = 0;
			begin = pos;
		}

		if (pos > offset + length)
			goto all_done;
	}

#ifdef CONFIG_DECNET_MOP
	for(sk = dn_raw_mop_sklist; sk; sk = sk->next) {
		len += sprintf(buffer+len, "MOP\n");

		pos = begin + len;

		if (pos < offset) {
			len = 0;
			begin = pos;
		}

		if (pos > offset + length)
			goto all_done;
	}
#endif /* CONFIG_DECNET_MOP */

all_done:
	sti();

	*start = buffer + (offset - begin);
	len -= (offset - begin);

	if (len > length) len = length;

	return(len);
}
#endif /* CONFIG_PROC_FS */

void dn_raw_rx_nsp(struct sk_buff *skb)
{
	struct sock *sk;
	struct sk_buff *skb2;
	unsigned long cpuflags;

	save_flags(cpuflags);
	cli();
	for(sk = dn_raw_nsp_sklist; sk != NULL; sk = sk->next) {
		if (skb->len > sock_rspace(sk))
			continue;
		if (sk->dead)
			continue;
		if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
			skb_set_owner_r(skb2, sk);
			__skb_queue_tail(&sk->receive_queue, skb2);
			sk->data_ready(sk, skb->len);	
		}
	}
	restore_flags(cpuflags);
}

void dn_raw_rx_routing(struct sk_buff *skb)
{
	struct sock *sk;
	struct sk_buff *skb2;
	struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
	unsigned long cpuflags;
	unsigned short rt_flagmask;
	unsigned short objnamel;
	struct dn_scp *scp;

	save_flags(cpuflags);
	cli();
	for(sk = dn_raw_routing_sklist; sk != NULL; sk = sk->next) {
		if (skb->len > sock_rspace(sk))
			continue;
		if (sk->dead)
			continue;
		scp = &sk->protinfo.dn;

		rt_flagmask = *(unsigned short *)scp->addr.sdn_objname;
		objnamel = dn_ntohs(scp->addr.sdn_objnamel);

		if ((objnamel == 2) && (!((1 << (cb->rt_flags & 0x0f)) & rt_flagmask)))
			continue;

		if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
			skb_set_owner_r(skb2, sk);
			__skb_queue_tail(&sk->receive_queue, skb2);
			sk->data_ready(sk, skb->len);
		}
	}
	restore_flags(cpuflags);
}

#ifdef CONFIG_DECNET_MOP
void dn_raw_rx_mop(struct sk_buff *skb)
{
	struct sock *sk;
	struct sk_buff *skb2;
	unsigned long cpuflags;

	save_flags(cpuflags);
	cli();
	for(sk = dn_raw_mop_sklist; sk != NULL; sk = sk->next) {
		if (skb->len > sock_rspace(sk))
			continue;
		if (sk->dead)
			continue;
		if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
			skb_set_owner_r(skb2, sk);
			__skb_queue_tail(&sk->receive_queue, skb2);
			sk->data_ready(sk, skb->len);
		}
	}
	restore_flags(cpuflags);
}
#endif /* CONFIG_DECNET_MOP */
