/*****************************************************************************

 @(#) $Id: mtp.c,v 0.7.2.4 2000/09/13 13:47:06 brian Exp $

 -----------------------------------------------------------------------------

 Copyright (C) 1997, 1998, 1999, 2000  Brian Bidulock <bidulock@dallas.net>

 All Rights Reserved.

 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 of the License, 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.

 Last Modified $Date: 2000/09/13 13:47:06 $ by $Author: brian $

 -----------------------------------------------------------------------------

 $Log: mtp.c,v $
 Revision 0.7.2.4  2000/09/13 13:47:06  brian
 A lot of work in state machine: haven't check compile yet,
 just wanting to save work for later.

 Revision 0.7.2.3  2000/09/12 04:57:30  brian
 Still working on state machine: still compiles.

 Revision 0.7.2.2  2000/09/11 20:20:48  brian
 Cleaned up routing structure.

 Revision 0.7.2.1  2000/09/11 14:08:18  brian
 Remove and updated some files for better structure.  Most compile now...

 Revision 0.7  2000/09/11 07:20:41  brian
 Addeds MTP state machine and mtp and sccp socket glue code.

 *****************************************************************************/

static char const ident[] = "$Name: Prerelease1 $($Revision: 0.7.2.4 $) $Date: 2000/09/13 13:47:06 $";

/*
 *  This file has all of the protocol-specific socket glue code for MTP
 *  level sockets.
 */

#include <linux/types.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/fcntl.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/in.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/mm.h>
#include <linux/config.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/poll.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include "af_ss7.h"
#include "mtp.h"
#include "mtp_sm.h"
#include "mtp_route.h"
#include "mtp_parse.h"


static void mtp_hash(struct sock *sk)
{
    /*
     *  Add sk to user part table at sk->snum in the ss7mtp_cb for address
     *  sk->saddr
     */
    struct ss7_addr addr;
    struct ss7_routeset *rt;
    struct ss7mtp_cb *cb;

    addr.s_addr = sk->saddr;
    if ((sk->num&0xf)<3) return;
    if (!(rt = ss7_rt_type(addr, 0, SS7_RST_LOCAL))) return; /* aaarrrgh! */
    if (!(cb = rt->cb)) return; /* aaarrrgh! */
    if (cb->sks[sk->num&0xf]) return; /* aaargh! */
    cb->sks[sk->num] = sk;
}

static void mtp_unhash(struct sock *sk)
{
    /*
     *  Remove sk from user part table at sk->snum in the ss7mtp_cb for
     *  address sk->saddr.
     */
    struct ss7_addr addr;
    struct ss7_routeset *rt;
    struct ss7mtp_cb *cb;

    addr.s_addr = sk->saddr;
    if ((sk->num&0xf)<3) return;
    if (!(rt = ss7_rt_type(addr, 0, SS7_RST_LOCAL))) return; /* aaarrrgh! */
    if (!(cb = rt->cb)) return; /* aaarrrgh! */
    cb->sks[sk->num&0xf] = NULL;
}

static int mtp_get_port(struct sock *sk, unsigned short upart)
{
    /*
     *  Check whether sk->saddr has upart already bound.  Return zero when
     *  and userpart is not yet bound.
     */
    struct ss7_addr addr;
    struct ss7_routeset *rt;
    struct ss7mtp_cb *cb;

    addr.s_addr = sk->saddr;
    if ((upart&0xf)<3) return -EFAULT;
    if (!(rt = ss7_rt_type(addr, 0, SS7_RST_LOCAL))) return -EFAULT;
    if (!(cb = rt->cb)) return -EFAULT;
    if (cb->sks[upart&0xf]) return -EADDRINUSE;
    return (0);
}

static void mtp_close(struct sock *sk, long timeout)
{
    sk->state = TCP_CLOSE;
    mtp_unhash(sk);
    sk->dead = 1;
    destroy_sock(sk);
}

static unsigned int mtp_poll(struct file * file, struct socket *sock, struct poll_table_struct *wait)
{
    struct sock *sk = sock->sk;
    unsigned int mask;

    poll_wait(file, sk->sleep, wait);
    mask = 0;

    if (sk->err || !skb_queue_empty(&sk->error_queue))
        mask |= POLLERR;
    if (!skb_queue_empty(&sk->receive_queue))
        mask |= POLLIN | POLLRDNORM;
    if (sock_writeable(sk))
        mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
    else
        sk->socket->flags |= SO_NOSPACE;
    return mask;
}

static int mtp_ioctl(struct sock *sk, int cmd, unsigned long arg)
{
    return -ENOIOCTLCMD;    /* no ioctls for now, more later... */
}

static int mtp_setsockopt(struct sock *sk, int level, int optname, char *optval, int optlen)
{
    return -ENOPROTOOPT;    /* no sockops for now, more later... */
}

static int mtp_getsockopt(struct sock *sk, int level, int optname, char *optval, int *option)
{
    return -ENOPROTOOPT;    /* no sockops for now, more later... */
}

static int mtp_sendmsg(struct sock *sk, struct msghdr *msg, int len)
{
    struct sockaddr_ss7 *oaddr, *uaddr = (struct sockaddr_ss7 *)msg->msg_name;
    struct ss7_routeset *rt, *rs;
    struct sk_buff *skb;
    int err = 0;

    if (len+sizeof(struct __itu_rl)>SS7_SIF_MAX)
        return -EINVAL;

    if (!sk->saddr)
        return -EAGAIN;

    if (msg->msg_flags&MSG_OOB)
        return -EOPNOTSUPP;

    if (msg->msg_flags&~(MSG_DONTWAIT|MSG_NOSIGNAL))
        return -EINVAL;

    if (!uaddr)
        return -EINVAL;

    if (msg->msg_namelen<sizeof(*uaddr))
        return -EINVAL;

    if (uaddr->ss7_family!=AF_SS7)
        return -EINVAL;

    if (uaddr->ss7_si<3 || uaddr->ss7_si>15)
        return -EINVAL;

    if (!sk->saddr && !uaddr->ss7_opc.s_addr)
        return -EADDRNOTAVAIL;

    if (!sk->saddr)
        oaddr->ss7_opc.s_addr = uaddr->ss7_opc.s_addr;
    else
        oaddr->ss7_opc.s_addr = sk->saddr;

    if (!(rs = ss7_rt_type(oaddr->ss7_opc, 0, SS7_RST_LOCAL)))
        return -EADDRNOTAVAIL;

    if (rs->flags&SS7_RS_MTP_RESTART)
        return -ENETUNREACH;

    if (!rs->cb)
        return -EADDRNOTAVAIL;

    if (!(rt = ss7_rt(uaddr->ss7_dpc)))
        return -EHOSTUNREACH;

    if (rt->flags&SS7_RS_DONTUSE) {
        /* one more try at cluster address */
        if (!(rt = ss7_rt_type(uaddr->ss7_dpc, 0, SS7_RST_LOCAL)))
            return -EHOSTUNREACH;

        if (rt->flags&SS7_RS_PROHIBITED)
            return -ENODEV;

        if (rt->flags&SS7_RS_MTP_RESTART)
            return -EHOSTDOWN;
    }

    skb = sock_alloc_send_skb(sk,len+16,0,msg->msg_flags&MSG_DONTWAIT,&err);
    skb_reserve(skb,4); /* cmd fsn/fib bsn/bib li */
    skb->priority = sk->priority;
    skb->dst = NULL;
    {
        ss7mtp_msg *m;

        skb->nh.raw = skb_put(skb,len+sizeof(m->itu.mh)-1);
        m = (ss7mtp_msg *)skb->nh.raw;

        skb_pull(skb,sizeof(m->itu.mh)-1);
        err = memcpy_fromiovec(skb->data,msg->msg_iov,len);
        if (err) {
            kfree_skb(skb);
            return err;
        }
        skb_push(skb,sizeof(m->itu.mh)-1);

        m->itu.mh.si = uaddr->ss7_si;
        m->itu.mh.mp = uaddr->ss7_mp;
        m->itu.mh.ni = uaddr->ss7_ni;
        m->itu.mh.rl.dpc = uaddr->ss7_dpc.s_addr;
        if (sk->saddr) m->itu.mh.rl.opc = sk->saddr;
        else           m->itu.mh.rl.opc = uaddr->ss7_opc.s_addr;
        m->itu.mh.rl.sls = uaddr->ss7_sls;
    }
    /*
     *  Use the local MTP control block output method.  For normal MTP this
     *  is the HMRT output selecting a link and routing the call to the
     *  device driver.  For M3UA, M3PEER, or TALI/MTP it might be different.
     */
    return rs->cb->output(skb,rs->cb,rt);
}

/*
 *  I had to import the following from net/core/datagram.c because it
 *  included some TCP specific connection checks which are not applicable
 *  to SS7 and SCCP.  I only modified the connection oriented check,
 *  otherwise the code is an identical copy...
 */

/*
 * Wait for a packet..
 *
 * Interrupts off so that no packet arrives before we begin sleeping.
 * Otherwise we might miss our wake up
 */

static inline void wait_for_packet(struct sock * sk)
{
	struct wait_queue wait = { current, NULL };

	add_wait_queue(sk->sleep, &wait);
	current->state = TASK_INTERRUPTIBLE;

	if (skb_peek(&sk->receive_queue) == NULL)
		schedule();

	current->state = TASK_RUNNING;
	remove_wait_queue(sk->sleep, &wait);
}

/*   *
 *   *	Get a datagram skbuff, understands the peeking, nonblocking wakeups and possible
 *   *	races. This replaces identical code in packet,raw and udp, as well as the IPX
 *   *	AX.25 and Appletalk. It also finally fixes the long standing peek and read
 *   *	race for datagram sockets. If you alter this routine remember it must be
 *   *	re-entrant.
 *   *
 *   *	This function will lock the socket if a skb is returned, so the caller
 *   *	needs to unlock the socket in that case (usually by calling skb_free_datagram)
 *   *
 *   *	* It does not lock socket since today. This function is
 *   *	* free of race conditions. This measure should/can improve
 *   *	* significantly datagram socket latencies at high loads,
 *   *	* when data copying to user space takes lots of time.
 *   *	* (BTW I've just killed the last cli() in IP/IPv6/core/netlink/packet
 *   *	*  8) Great win.)
 *   *	*			                    --ANK (980729)
 *   *
 *   *	The order of the tests when we find no data waiting are specified
 *   *	quite explicitly by POSIX 1003.1g, don't change them without having
 *   *	the standard around please.
 *   * 
 */

static __inline struct sk_buff *skb_recv_ss7msg(struct sock *sk, unsigned flags, int noblock, int *err)
{
	int error;
	struct sk_buff *skb;

	/* Caller is allowed not to check sk->err before skb_recv_datagram() */
	error = sock_error(sk);
	if (error)
		goto no_packet;

restart:
	while(skb_queue_empty(&sk->receive_queue))	/* No data */
	{
		/* Socket errors? */
		error = sock_error(sk);
		if (error)
			goto no_packet;

		/* Socket shut down? */
		if (sk->shutdown & RCV_SHUTDOWN)
			goto no_packet;

#if 0 /* here's the problem */
		/* Sequenced packets can come disconnected. If so we report the problem */
		error = -ENOTCONN;
		if(connection_based(sk) && sk->state!=TCP_ESTABLISHED)
			goto no_packet;
#endif

		/* handle signals */
		error = -ERESTARTSYS;
		if (signal_pending(current))
			goto no_packet;

		/* User doesn't want to wait */
		error = -EAGAIN;
		if (noblock)
			goto no_packet;

		wait_for_packet(sk);
	}

	/* Again only user level code calls this function, so nothing interrupt level
	   will suddenly eat the receive_queue */
	if (flags & MSG_PEEK)
	{
		unsigned long cpu_flags;

		/* It is the only POTENTIAL race condition
		   in this function. skb may be stolen by
		   another receiver after peek, but before
		   incrementing use count, provided kernel
		   is reentearble (it is not) or this function
		   is called by interrupts.

		   Protect it with global skb spinlock,
		   though for now even this is overkill.
		                                --ANK (980728)
		 */
		spin_lock_irqsave(&skb_queue_lock, cpu_flags);
		skb = skb_peek(&sk->receive_queue);
		if(skb!=NULL)
			atomic_inc(&skb->users);
		spin_unlock_irqrestore(&skb_queue_lock, cpu_flags);
	} else
		skb = skb_dequeue(&sk->receive_queue);

	if (!skb)	/* Avoid race if someone beats us to the data */
		goto restart;
	return skb;

no_packet:
	*err = error;
	return NULL;
}

static int mtp_recvmsg(struct sock *sk, struct msghdr *msg, int len, int noblock, int flags, int *addr_len)
{
    struct sockaddr_ss7 *raddr = (struct sockaddr_ss7 *)msg->msg_name;
    struct sk_buff *skb;
    int copied, err;

    /* don't think I'll ever get this working .... */
#if 0
    if (flags & MSG_ERRQUEUE)
        return ss7_recv_error(sk,msg,len); 
#endif

    skb = skb_recv_ss7msg(sk,flags,noblock,&err);
    if (!skb) goto error_out;

    copied = skb->len - sizeof(struct __itu_mtph);
    if (copied > len) {
        copied = len;
        msg->msg_flags |= MSG_TRUNC;
    }

    err = skb_copy_datagram_iovec(skb, sizeof(struct __itu_mtph), msg->msg_iov, copied);
    if (err) goto error_out_free;
    sk->stamp = skb->stamp;

    if (raddr)
    {
        ss7mtp_msg *m = (ss7mtp_msg *)skb->nh.raw;
        
        if (addr_len) *addr_len=sizeof(*raddr);

        raddr->ss7_family = AF_SS7;
        raddr->ss7_si = m->itu.mh.si;
        raddr->ss7_mp = m->itu.mh.mp;
        raddr->ss7_ni = m->itu.mh.ni;
        raddr->ss7_dpc.s_addr = m->itu.mh.rl.dpc;
        raddr->ss7_opc.s_addr = m->itu.mh.rl.opc;
        raddr->ss7_sls = m->itu.mh.rl.sls;

        err = copied;
    }

error_out_free:
        skb_free_datagram(sk,skb);
error_out:
        return err;
}

/*
 *  Ok, this should be a fun one.  In SS7 the bind depends on the socket
 *  type (MTP or SCCP).  Maybe I should have made them different address
 *  families, I don't know, but SCCP is only roughed in so far, so this is
 *  for MTP only, really.
 *
 *  An MTP address is a Point Code, but when routing packets, the MTP uses
 *  the Point Code and User Part.  If a socket is bound to a User Part it
 *  delivers messages to that user part.  If no socket is bound to the User
 *  Part it returns User Part Unavailable to the distant end.
 *
 *  Therefore, when we bind, we bind to a PC and user-part which is very
 *  analogous to IP and port (except you can count the number of user parts
 *  on one hand, unlike ports).  There is no concept of allocating a `free'
 *  port.  All user parts in SS7 are assigned dedicated numbers.  These are
 *  defined at the top of <linux/ss7.h> and there are only 16 of them.
 *
 *  For some of these protocols (e.g., ISUP) we may wish to restrict the
 *  range of what the socket receives, by splitting up CIC ranges, for
 *  example, however, this is something which could be left to the
 *  application to deal with for now.  (Besides, that would be an ISUP
 *  transport layer socket not an MTP network layer socket.)
 *
 */
static int mtp_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
	struct sockaddr_ss7 *addr=(struct sockaddr_ss7 *)uaddr;
	unsigned short snum;

	/* Check these errors (bad address length, double bind). */
	if ((addr_len < sizeof(struct sockaddr_ss7)) || (sk->num != 0))
		return -EINVAL;

        /*
         *  There are really only two types of addresses here: a Point Code
         *  which belongs to a Signaling Point (i.e., host) and a Point
         *  Code which represents a Cluster (i.e., network addrewss).  We
         *  don't have broadcast or multicast addesses.  We only allow
         *  binds to local point codes.
         */
        if (addr->ss7_opc.s_addr !=0 )
            if ( !( ss7_addr_type(addr->ss7_opc) & SS7_RST_LOCAL ) )
                return -EADDRNOTAVAIL;

	/*
	 *  We use the same address for sending as receiving.
	 */
	sk->rcv_saddr = sk->saddr = addr->ss7_opc.s_addr;

        /* 
         *  This will have to be subsystem number or global title for SCCP
         *  when we support SCCP.
         */
	snum = addr->ss7_si; /* I might pack ni in here too, later... */

        return(0);
}

static int mtp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
        /* FIXME: should do some protocol specific stats here... */
        if (sock_queue_rcv_skb(sk,skb)<0) {
            kfree_skb(skb);
            return -1;
        }
        return(0);
}


/*
 *  This is the socket layer -> transport layer interface
 *  transport -> network interface is defined by struct ss7_proto
 */

struct proto mtp_prot = {
    (struct sock *)&mtp_prot,    /* sklist_next */
    (struct sock *)&mtp_prot,    /* sklist_prev */
    mtp_close,          /* close called from ss7_release */
    NULL,               /* connect */
    NULL,               /* accept */
    NULL,               /* retransmit */
    NULL,               /* write wakeup */
    NULL,               /* read wakeup */
    mtp_poll,
    mtp_ioctl,
    NULL,               /* init freshly created struct sock */
    NULL,               /* destroy struct sock */
    NULL,               /* shutdown */
    mtp_setsockopt,
    mtp_getsockopt,
    mtp_sendmsg,
    mtp_recvmsg,
    mtp_bind,           /* UDP doesn't do this? */
    mtp_backlog_rcv,
    mtp_hash,           /* put bound addr/user-part in list */
    mtp_unhash,         /* remove addr/user-part from bound list */
    mtp_get_port,       /* get_port called to check user part bind available */
    128,                /* max_header */
    0,                  /* retransmits */
    "SS7/MTP",          /* name [32] */
    0,                  /* inuse */
    0                   /* highest inuse */
};
