/*
 * sock_dgram.c - Contains functions that supply SOCK_DGRAM type sockets for
 *                NetBEUI protocol stack which their names has a 'nbso_dgram_'
 *                prefix.
 *
 * Notes:
 *	- VRP in comments is the acronym of "Value Result Parameter"
 *
 * Copyright (c) 1997 by Procom Technology,Inc.
 *
 * This program can be redistributed or modified under the terms of the 
 * GNU General Public License as published by the Free Software Foundation.
 * This program is distributed without any warranty or implied warranty
 * of merchantability or fitness for a particular purpose.
 *
 * See the GNU General Public License for more details.
 *
 */
 
 


#include <linux/net.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/uio.h>
#include <linux/skbuff.h>
#include <linux/netbeui.h>



/*
 * SOCK_DGRAM Calls 
 */

/*
 * Function: nbso_dgram_create
 *	Performs additional actions at creation of SOCK_DGRAM sockets (if
 *	required).
 *
 * Parameters:
 *	sock     : pointer to 'struct socket' that created by system before
 *	           call this function.
 *	protocol : an integer that have protocol specific meaning and we
 *	           do not use it.
 *
 * Returns: int
 *	0 : No Additional Action Required.
 *
 */

static int
nbso_dgram_create (struct socket *sock, int protocol)
{
	return 0;
}


/*
 * Function: nbso_dgram_dup
 *	Performs additional actions at duplication of SOCK_DGRAM sockets (if
 *	required).
 *
 * Parameters:
 *	newsock : pointer to 'struct socket' that created by system before
 *	          call this function & is destination of copy operation.
 *	oldsock : pointer to original socket.
 *
 * Returns: int
 *	0 : No Additional Action Required.
 *
 */

static int 
nbso_dgram_dup (struct socket *newsock, struct socket *oldsock)
{
	return 0;
}


/*
 * Function: nbso_dgram_release
 *	Performs additional actions at release of SOCK_DGRAM sockets.
 *
 * Parameters:
 *	sock : pointer to socket that must be released.
 *	peer : pointer to peer socket. This parameter defined only for
 *	       UNIX domain sockets (AF_UNIX) and we do not use it.
 *
 * Returns: int
 *	0 : in all cases. (this function always succeed)
 */

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

	if (sk->dg_namep) {
		nbdg_del_name(sk->dg_namep);
		if (sk->name)
			nbns_del_name(sk->name);
	}

	return 0;
}


/*
 * Function: nbso_dgram_bind
 *	Performs additional actions at bind of SOCK_DGRAM sockets to names.
 *
 * Parameters:
 *	sock     : pointer to socket that must bind a name to it.
 *	uaddr    : pointer to 'struct sockaddr_netbeui' that contains
 *	           information about sightly name.
 *	addr_len : length of 'struct sockaddr_netbeui'.
 *
 * Returns: int
 *	0        : if name is binded to socket successfully.
 *	negative : if a fault occurs.
 *	           (-EINVAL) : if socket binds already, or given name is not
 *	                       valid.
 */

static int 
nbso_dgram_bind (struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	int   rc;
	name_t   *nb_name;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *addr = (struct sockaddr_netbeui *) uaddr;

	if (sk->dg_namep)
		return (-EINVAL);  /* return (-EALREADY); ?! */
	if (addr->snb_addr.name[0] == '*')
		return (-EINVAL);

	rc = nbns_add_name(addr->snb_addr.name, addr->snb_addr.name_type, &nb_name);
	if (rc)
		return rc; /* -ENOTUNIQ ?! */

	rc = nbdg_add_name(addr->snb_addr.name, sk->waitq, &sk->dg_namep);
	if (rc) {
		nbns_del_name(nb_name);
		return rc;
	}

	sk->name = nb_name;

	return 0;
}


/*
 * Function: nbso_dgram_connect
 *	Performs additional actions at attach of SOCK_DGRAM sockets to a
 *	specified peer.
 *
 * Parameters:
 *	sock     : pointer to socket that must attach to peer.
 *	uaddr    : pointer to 'struct sockaddr_netbeui' that contains
 *	           information about peer.
 *	addr_len : length of 'struct sockaddr_netbeui'.
 *	sflags   : bitwise integer that contains socket flags.
 *
 * Returns: int
 *	0        : if socket attaches to the specified peer successfully.
 *	negative : if a fault occurs.
 *	           (-EINVAL) : means socket not bounded normally.
 *	           (-EACCES) : permission denied for broadcasting. user must
 *	                       sets the SO_BROADCAST socket option to active,
 *	                       before try to connect to all ('*').
 */

static int 
nbso_dgram_connect (struct socket *sock, struct sockaddr *uaddr, int addr_len,
								 int sflags)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *addr = (struct sockaddr_netbeui *) uaddr;

	if (!sk->name)
		return (-EINVAL);

	if ((uaddr) && (addr_len == sizeof(struct sockaddr_netbeui))) {
		if ((addr->snb_addr.name[0] == '*') && (!sk->broadcast))
			return (-EACCES);

		sock->state = SS_CONNECTING;

		memcpy(&sk->dg_conn_name, addr, sizeof(struct sockaddr_netbeui));
		nbdg_register_peername(sk->dg_namep, sk->dg_conn_name.snb_addr.name);

		sock->state = SS_CONNECTED;
	}
	else { /* Invalid address means detach from previous address */
		if (sock->state == SS_UNCONNECTED)
			return 0;

		sock->state = SS_DISCONNECTING;

		nbdg_deregister_peername(sk->dg_namep);

		sock->state = SS_UNCONNECTED;
	}

	return 0;
}


/*
 * Function: nbso_dgram_socketpair
 *	This function was defined only for AF_UNIX sockets.
 *
 * Parameters:
 *
 * Returns: int
 *	   (-EOPNOTSUPP) : This operation NOT supported by us.
 */

static int 
nbso_dgram_socketpair (struct socket *sock1, struct socket *sock2)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_dgram_accept
 *	This function was defined only for SOCK_STREAM sockets.
 *
 * Parameters:
 *
 * Returns: int
 *	   (-EOPNOTSUPP) : This operation NOT supported by us.
 */

static int 
nbso_dgram_accept (struct socket *sock, struct socket *newsock, int sflags)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_dgram_getname
 *	Gets SOCK_DGRAM socket name or peer name that attached to it.
 *
 * Parameters:
 *	sock      : pointer to socket that we need to name of it or its peer.
 *	uaddr     : (VRP) pointer to 'struct sockaddr_netbeui' that be filled
 *	            with requested information.
 *	uaddr_len : (VRP) pointer to an integer that returns length of
 *	            'struct sockaddr_netbeui'.
 *	peer      : an integer that indicates type of request.
 *
 * Returns: int
 *	0        : if requested name is retrieved successfully.
 *	negative : if a fault occurs.
 *	           (-ENOTCONN) : name of peer was requested but socket has not
 *	                         any attachment.
 *	           (-EBADF)    : socket not bounded to a name but name of it
 *	                         was requested.
 */

static int 
nbso_dgram_getname (struct socket *sock, struct sockaddr *uaddr, int *uaddr_len,
								 int peer)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *addr = (struct sockaddr_netbeui *) uaddr;

	if (peer) {
		if (sock->state != SS_CONNECTED)
			return (-ENOTCONN);
		memcpy(addr, &sk->dg_conn_name, sizeof(struct sockaddr_netbeui));
	}
	else {
		if (!sk->dg_namep)
			return (-EBADF);

		memcpy(addr->snb_addr.name, sk->dg_namep->name, NB_NAME_LEN);
		addr->snb_addr.name_type = sk->name->type;
	}

	return 0;
}


/*
 * Function: nbso_dgram_select
 *	Determines operational (particularly I/O) condition of SOCK_DGRAM
 *	socket.
 *
 * Parameters:
 *	sock     : pointer to socket that check it.
 *	sel_type : an integer that determines type of checking.
 *	wait     : pointer to a particular structure that contains some
 *	           wait queues. The system itself checks members of these 
 *	           wait queues for their time outs. we only sleep on this
 *	           structure if there is not exist a categoric answer, so far.
 *
 * Returns: int
 *	0 : means that however must wait.
 *	1 : means that answer is positive or an error occured.
 */

static int 
nbso_dgram_select (struct socket *sock , int sel_type, select_table *wait)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (!sk->dg_namep)
		return 1;

	switch (sel_type) {
		case SEL_IN:
			if (nbdg_receive_ready(sk->dg_namep) == 0)
				return 1;
			break;

		case SEL_OUT:
			/* We always can send datagrams */
			return 1; 

		case SEL_EX:
	     	/* Currently, we haven't any exception definition in NetBEUI */
			return 1;
	}

	select_wait(sk->waitq, wait);

	return 0;
}


/*
 * Function: nbso_dgram_ioctl
 *	Performs some particular operations on SOCK_DGRAM socket, that can not
 *	do with regular system calls.
 *
 * Parameters:
 *	sock : pointer to socket that action must perform on it.
 *	cmd  : an integer that indicates type of operation.
 *	arg  : this parameter often is a pointer to 'cmd' relative data structure
 *	       that be used by it as an argument.
 *
 * Returns: int
 *	0        : if cmd is performed successfully.
 *	negative : if a fault occurs. error codes that bubble to user are
 *	           dependent to cmd.
 */

static int 
nbso_dgram_ioctl (struct socket *sock, unsigned int cmd, unsigned long arg)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	switch (cmd) {
		case SIOCRUWDGF: {
			int   rc,
			      len;

			if (!sk->dg_namep)
				return (-EINVAL);

			rc = verify_area(VERIFY_READ, (void *) arg, sizeof(int));
			if (rc)
				return rc;

			len = get_user((int *) arg);
			if (len < 0)
				return (-EINVAL);

			return (nbdg_remove_unwanted_dgf(sk->dg_namep, len));
		}

		default:
			return (-EINVAL);
	}
}


/*
 * Function: nbso_dgram_listen
 *	This function was defined only for SOCK_STREAM sockets.
 *
 * Parameters:
 *
 * Returns: int
 *	   (-EOPNOTSUPP) : This operation NOT supported by us.
 */

static int 
nbso_dgram_listen (struct socket *sock, int backlog)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_dgram_shutdown
 *	This function was defined only for SOCK_STREAM sockets.
 *
 * Parameters:
 *
 * Returns: int
 *	   (-EOPNOTSUPP) : This operation NOT supported by us.
 */

static int 
nbso_dgram_shutdown (struct socket *sock, int how)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_dgram_setsockopt
 *	Sets some operational options of SOCK_DGRAM sockets.
 *
 * Parameters:
 *	sock    : a pointer to socket that must tune its options.
 *	level   : this parameter is not used in this function and always is zero.
 *	optname : an integer that indicates option that must tune.
 *	optval  : a pointer to related data structure which used for assign
 *	          value(s) to option.
 *	optlen  : length of data structure that 'optval' points to it.
 *
 * Returns: int
 *	0        : if tuning is performed successfully.
 *	negative : if a fault occurs.
 *	           (-ENOPROTOOPT) : Option name is not defined for us.
 */

static int 
nbso_dgram_setsockopt (struct socket *sock, int level, int optname, char *optval,
								int optlen)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

  	switch (optname) {
		case SO_BROADCAST:
			memcpy_fromfs((void *)&sk->broadcast, optval,
			              MIN(sizeof(sk->broadcast), optlen));
			return 0;
  	}

 	return(-ENOPROTOOPT);
}


/*
 * Function: nbso_dgram_getsockopt
 *	Gets some operational options of SOCK_DGRAM sockets.
 *
 * Parameters:
 *	sock    : a pointer to socket that action performs on it.
 *	level   : this parameter is not used in this function and always is zero.
 *	optname : an integer that indicates option that must be gotten.
 *	optval  : (VRP) a pointer to related data structure which used for
 *	          getting value(s) of option.
 *	optlen  : (VRP) length of data structure that 'optval' points to it.
 *
 * Returns: int
 *	0        : if operation is performed successfully.
 *	negative : if a fault occurs.
 *	           (-ENOPROTOOPT) : Option name is not defined for us.
 */

static int 
nbso_dgram_getsockopt (struct socket *sock, int level, int optname, char *optval,
								 int *optlen)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

  	switch (optname) {
		case SO_BROADCAST: {
			int   len;

			len= MIN(get_user((int *)optlen), sizeof(sk->broadcast));
			memcpy_tofs(optval, (void *)&sk->broadcast, len);
			put_user(len, (int *)optlen);
			return 0;
		}
  	}

  	return(-ENOPROTOOPT);
}


/*
 * Function: nbso_dgram_fcntl
 *	Performs some miscellaneous operations on SOCK_DGRAM sockets.
 *
 * Parameters:
 *	sock : pointer to socket that function must perform on it.
 *	cmd  : an integer that indicates what action must perform.
 *	arg  : this parameter often is a pointer to 'cmd' relative data structure
 *	       that be used by it as an argument.
 *
 * Returns: int
 *	0        : if operation is performed successfully. for GET commands
 *	           return code may be non-zero in successful return! (see
 *	           'fcntl()' man page for more).
 *	negative : if a fault occurs.
 *	           (-EOPNOTSUPP) : No additional fcntl() command supported for
 *	                           SOCK_DGRAM sockets.
 */

static int 
nbso_dgram_fcntl (struct socket *sock, unsigned int cmd, unsigned long arg)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_dgram_sendmsg
 *	Sends a DATAGRAM message through a SOCK_DGRAM socket to desired
 *	target(s).
 *
 * Parameters:
 *	sock     : a pointer to socket that data sends through it.
 *	msg      : a pointer to 'struct msghdr' that contains message body,
 *	           target name and etc.
 *	           Note: msg->msg_control AND msg->msg_controllen are per
 *	                 protocol magic fields, and in our sendmsg() they
 *	                 indicate alias name to use for message sender name
 *	                 instead of the name that socket binds to it.
 *	len      : length of message all around.
 *	nonblock : an integer that if be set to non-zero value means that
 *	           no waiting (sleeping, blocking & ...) acceptable during
 *	           operation.
 *	sflags   : bitwise integer that contains socket flags.
 *
 * Returns: int
 *	positive : indicates how many bytes of data was sent.
 *	negative : if a fault occurs.
 *	           (-EINVAL)   : if a flag specified (we do not support any
 *	                         flags), or socket not bounded normally, or
 *	                         given target name is not valid.
 *	           (-EACCES)   : permission denied for broadcasting. user must
 *	                         sets the SO_BROADCAST socket option to active,
 *	                         before try to broadcasts a message.
 *	           (-ENOTCONN) : target name not given and socket is not
 *	                         attached to any peer.
 */

static int 
nbso_dgram_sendmsg (struct socket *sock, struct msghdr *msg, int len, int nonblock,
								int sflags)
{
	int   rc = 0,
	      iov_no,
	      bytes_sent;
	char   *local_name,
	       name_buff[NB_NAME_LEN];
	struct iovec   *iov;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *remote_addr;

	if (sflags)
		return (-EINVAL);

	if (msg->msg_name) {
		struct sockaddr_netbeui   *addr;
		addr = (struct sockaddr_netbeui *) msg->msg_name;

		if (!sk->name)
			return (-EINVAL);
		if (msg->msg_namelen < sizeof(struct sockaddr_netbeui))
			return (-EINVAL);
		if (addr->snb_family && addr->snb_family != AF_NETBEUI)
			return (-EINVAL);
		if ((addr->snb_addr.name[0] == '*') && (!sk->broadcast))
			return (-EACCES);

		remote_addr = addr;
	}
	else {
		if (sock->state != SS_CONNECTED)
			return (-ENOTCONN);

		remote_addr = &sk->dg_conn_name;
	}

	if ((msg->msg_control) && (msg->msg_controllen == NB_NAME_LEN)) {
		rc = verify_area(VERIFY_READ, msg->msg_control, NB_NAME_LEN);
		if (rc)
			return rc;

		memcpy_fromfs(name_buff, msg->msg_control, NB_NAME_LEN);
		local_name = name_buff;
	}
	else
		local_name = sk->name->name;

	bytes_sent = 0;
	iov = msg->msg_iov;
	iov_no = msg->msg_iovlen;
	
	/* All things are good, so start to send data ... */
	while (iov_no--) {
		rc = verify_area(VERIFY_READ, iov->iov_base, iov->iov_len);
		if (rc) {
			rc = (bytes_sent ? bytes_sent : rc);
			break;
		}

		rc = nbdg_send(local_name, remote_addr->snb_addr.name,
		               remote_addr->snb_addr.name_type, iov->iov_base,
		               iov->iov_len);

		if (rc < 0) {
			rc = (bytes_sent ? bytes_sent : rc);
			break;
		}

		bytes_sent += rc;
		++iov;
		rc = bytes_sent;
	}

	return rc;
}


/*
 * Function: nbso_dgram_recvmsg
 *	Receives a DATAGRAM message through a SOCK_DGRAM socket.
 *
 * Parameters:
 *	sock     : a pointer to socket that data receives through it.
 *	msg      : (VRP) a pointer to 'struct msghdr' that at return contains
 *	           message body, source name and etc.
 *	           Note: msg->msg_control AND msg->msg_controllen are per
 *	                 protocol magic fields, and in our recvmsg() at return
 *	                 they indicate target name of received message, that
 *	                 for sockets which connect to all ('*') may be different
 *	                 from the socket name.
 *	size     : MAXimum length of message all around.
 *	nonblock : an integer that if be set to non-zero value means that
 *	           no waiting (sleeping, blocking & ...) acceptable during
 *	           operation.
 *	sflags   : bitwise integer that contains socket flags.
 *	addr_len : (VRP) a pointer to an integer that if it is not NULL, at
 *	           return will be filled with length of 'struct sockaddr_netbeui'.
 *
 * Returns: int
 *	positive : indicates how many bytes of data was received.
 *	negative : if a fault occurs.
 *	           (-EINVAL)      : if a flag specified (we do not support any
 *	                            flags), or socket not bounded to a name
 *	                            normally.
 */

static int 
nbso_dgram_recvmsg (struct socket *sock, struct msghdr *msg, int size, int nonblock,
						int sflags, int *addr_len)
{
	int   rc = 0,
	      iov_no,
	      bytes_received;
	struct iovec   *iov;
	char   *dest_name = NULL,
	       *source_name = NULL;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (sflags || !sk->dg_namep)
		return (-EINVAL);

	if (msg->msg_name) {
		struct sockaddr_netbeui   *addr;
		addr = (struct sockaddr_netbeui *) msg->msg_name;

		*addr_len = sizeof(struct sockaddr_netbeui);

		addr->snb_family = AF_NETBEUI;
		addr->snb_addr.reserved = 0;
		source_name = addr->snb_addr.name;
	}

	if ((msg->msg_control) && (msg->msg_controllen == NB_NAME_LEN)) {
		rc = verify_area(VERIFY_WRITE, msg->msg_control, NB_NAME_LEN);
		if (rc)
			return rc;

		dest_name = msg->msg_control;
	}

	bytes_received = 0;
	iov = msg->msg_iov;
	iov_no = msg->msg_iovlen;

	/* All things are good, so start to receive data ... */
	while (iov_no--) {
		rc = verify_area(VERIFY_WRITE, iov->iov_base, iov->iov_len);
		if (rc) {
			rc = (bytes_received ? bytes_received : rc);
			break;
		}

		rc = nbdg_receive(sk->dg_namep, source_name, dest_name,
		                  iov->iov_base, iov->iov_len, nonblock);

		if (rc < 0) {
			rc = (bytes_received ? bytes_received : rc);
			break;
		}

		bytes_received += rc;

		++iov;
		rc = bytes_received;
	}

	return rc;
}


/* Dispatcher struct for SOCK_DGRAM calls */

struct proto_ops   nbso_dgram_proto = {
	AF_NETBEUI,
	nbso_dgram_create,
	nbso_dgram_dup,
	nbso_dgram_release,
	nbso_dgram_bind,
	nbso_dgram_connect,
	nbso_dgram_socketpair,
	nbso_dgram_accept,
	nbso_dgram_getname,
	nbso_dgram_select,
	nbso_dgram_ioctl,
	nbso_dgram_listen,
	nbso_dgram_shutdown,
	nbso_dgram_setsockopt,
	nbso_dgram_getsockopt,
	nbso_dgram_fcntl,
	nbso_dgram_sendmsg,
	nbso_dgram_recvmsg
};
