/*	$OpenBSD: pk_usrreq.c,v 1.9 2003/12/10 07:22:43 itojun Exp $	*/
/*	$NetBSD: pk_usrreq.c,v 1.10 1996/02/13 22:05:43 christos Exp $	*/

/*
 * Copyright (c) University of British Columbia, 1984
 * Copyright (C) Computer Science Department IV,
 * 		 University of Erlangen-Nuremberg, Germany, 1992
 * Copyright (c) 1991, 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by the
 * Laboratory for Computation Vision and the Computer Science Department
 * of the the University of British Columbia and the Computer Science
 * Department (IV) of the University of Erlangen-Nuremberg, Germany.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)pk_usrreq.c	8.1 (Berkeley) 6/10/93
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#include <net/if.h>
#include <net/if_types.h>
#include <net/route.h>

#include <netccitt/x25.h>
#include <netccitt/pk.h>
#include <netccitt/pk_var.h>
#include <netccitt/pk_extern.h>

static void old_to_new(struct mbuf *);
static void new_to_old(struct mbuf *);
/*
 *
 *  X.25 Packet level protocol interface to socket abstraction.
 *
 *  Process an X.25 user request on a logical channel.  If this is a send
 *  request then m is the mbuf chain of the send data. If this is a timer
 *  expiration (called from the software clock routine) them timertype is
 *  the particular timer.
 *
 */
int
pk_usrreq(so, req, m, nam, control)
	struct socket  *so;
	int             req;
	struct mbuf *m, *nam;
	struct mbuf    *control;
{
	struct pklcd *lcp = (struct pklcd *) so->so_pcb;
	int    error = 0;

	if (req == PRU_CONTROL)
		return (pk_control(so, (long) m, (caddr_t) nam,
				   (struct ifnet *) control));
	if (control && control->m_len) {
		error = EINVAL;
		goto release;
	}
	if (lcp == NULL && req != PRU_ATTACH) {
		error = EINVAL;
		goto release;
	}
	/*
		pk_trace (pkcbhead, TR_USER, (struct pklcd *)0,
			req, (struct x25_packet *)0);
	*/

	switch (req) {
		/*
		 * X.25 attaches to socket via PRU_ATTACH and allocates a
		 * logical channel descriptor.  If the socket is to  receive
		 * connections, then the LISTEN state is entered.
		 */
	case PRU_ATTACH:
		if (lcp) {
			error = EISCONN;
			/* Socket already connected. */
			break;
		}
		lcp = pk_attach(so);
		if (lcp == 0)
			error = ENOBUFS;
		break;

		/*
		 * Detach a logical channel from the socket. If the state of
		 * the channel is embryonic, simply discard it. Otherwise we
		 * have to initiate a PRU_DISCONNECT which will finish later.
		 */
	case PRU_DETACH:
		pk_disconnect(lcp);
		break;

		/*
		 * Give the socket an address.
		 */
	case PRU_BIND:
		if (nam->m_len == sizeof(struct x25_sockaddr))
			old_to_new(nam);
		error = pk_bind(lcp, nam);
		break;

		/*
		 * Prepare to accept connections.
		 */
	case PRU_LISTEN:
		error = pk_listen(lcp);
		break;

		/*
		 * Initiate a CALL REQUEST to peer entity. Enter state
		 * SENT_CALL and mark the socket as connecting. Set timer
		 * waiting for CALL ACCEPT or CLEAR.
		 */
	case PRU_CONNECT:
		if (nam->m_len == sizeof(struct x25_sockaddr))
			old_to_new(nam);
		if (pk_checksockaddr(nam))
			return (EINVAL);
		error = pk_connect(lcp, mtod(nam, struct sockaddr_x25 *));
		break;

		/*
		 * Initiate a disconnect to peer entity via a CLEAR REQUEST
		 * packet. The socket will be disconnected when we receive a
		 * confirmation or a clear collision.
		 */
	case PRU_DISCONNECT:
		pk_disconnect(lcp);
		break;

		/*
		 * Accept an INCOMING CALL. Most of the work has already been
		 * done by pk_input. Just return the callers address to the
		 * user.
		 */
	case PRU_ACCEPT:
		if (lcp->lcd_craddr == NULL)
			break;
		bcopy((caddr_t) lcp->lcd_craddr, mtod(nam, caddr_t),
		      sizeof(struct sockaddr_x25));
		nam->m_len = sizeof(struct sockaddr_x25);
		if (lcp->lcd_flags & X25_OLDSOCKADDR)
			new_to_old(nam);
		break;

		/*
		 * After a receive, we should send a RR.
		 */
	case PRU_RCVD:
		pk_flowcontrol(lcp, /* sbspace (&so -> so_rcv) <= */ 0, 1);
		break;

		/*
		 * Send INTERRUPT packet.
		 */
	case PRU_SENDOOB:
		if (m == 0) {
			MGETHDR(m, M_WAITOK, MT_OOBDATA);
			m->m_pkthdr.len = m->m_len = 1;
			*mtod(m, octet *) = 0;
		}
		if (m->m_pkthdr.len > 32) {
			m_freem(m);
			error = EMSGSIZE;
			break;
		}
		MCHTYPE(m, MT_OOBDATA);
		/* FALLTHROUGH */

		/*
		 * Do send by placing data on the socket output queue.
		 */
	case PRU_SEND:
		if (control) {
			struct cmsghdr *ch = mtod(m, struct cmsghdr *);
			control->m_len -= sizeof(*ch);
			control->m_data += sizeof(*ch);
			error = pk_ctloutput(PRCO_SETOPT, so, ch->cmsg_level,
					     ch->cmsg_type, &control);
		}
		if (error == 0 && m)
			error = pk_send(m, lcp);
		break;

		/*
		 * Abort a virtual circuit. For example all completed calls
		 * waiting acceptance.
		 */
	case PRU_ABORT:
		pk_disconnect(lcp);
		break;

		/* Begin unimplemented hooks. */

	case PRU_SHUTDOWN:
		error = EOPNOTSUPP;
		break;

	case PRU_CONTROL:
		error = EOPNOTSUPP;
		break;

	case PRU_SENSE:
#ifdef BSD4_3
		((struct stat *) m)->st_blksize = so->so_snd.sb_hiwat;
#else
		error = EOPNOTSUPP;
#endif
		break;

		/* End unimplemented hooks. */

	case PRU_SOCKADDR:
		if (lcp->lcd_ceaddr == 0)
			return (EADDRNOTAVAIL);
		nam->m_len = sizeof(struct sockaddr_x25);
		bcopy((caddr_t) lcp->lcd_ceaddr, mtod(nam, caddr_t),
		      sizeof(struct sockaddr_x25));
		if (lcp->lcd_flags & X25_OLDSOCKADDR)
			new_to_old(nam);
		break;

	case PRU_PEERADDR:
		if (lcp->lcd_state != DATA_TRANSFER)
			return (ENOTCONN);
		nam->m_len = sizeof(struct sockaddr_x25);
		bcopy(lcp->lcd_craddr ? (caddr_t) lcp->lcd_craddr :
		      (caddr_t) lcp->lcd_ceaddr,
		      mtod(nam, caddr_t), sizeof(struct sockaddr_x25));
		if (lcp->lcd_flags & X25_OLDSOCKADDR)
			new_to_old(nam);
		break;

		/*
		 * Receive INTERRUPT packet.
		 */
	case PRU_RCVOOB:
		if (so->so_options & SO_OOBINLINE) {
			struct mbuf *n = so->so_rcv.sb_mb;
			if (n && n->m_type == MT_OOBDATA) {
				unsigned        len = n->m_pkthdr.len;
				so->so_rcv.sb_mb = n->m_nextpkt;
				SB_EMPTY_FIXUP(&so->so_rcv);
				if (len != n->m_len &&
				    (n = m_pullup(n, len)) == 0)
					break;
				m->m_len = len;
				bcopy(mtod(m, caddr_t), mtod(n, caddr_t), len);
				m_freem(n);
			}
			break;
		}
		m->m_len = 1;
		*mtod(m, char *) = lcp->lcd_intrdata;
		break;

	default:
		panic("pk_usrreq");
	}
release:
	if (control != NULL)
		m_freem(control);
	return (error);
}

/*
 * If you want to use UBC X.25 level 3 in conjunction with some other X.25
 * level 2 driver, have the ifp -> if_ioctl routine assign pk_start to ia ->
 * ia_start when called with SIOCSIFCONF_X25.
 */
/* ARGSUSED */
int
pk_start(lcp)
	struct pklcd *lcp;
{
	pk_output(lcp);
	return (0);		/* XXX pk_output should return a value */
}

#ifndef _offsetof
#define _offsetof(t, m) ((int)((caddr_t)&((t *)0)->m))
#endif
struct sockaddr_x25 pk_sockmask = {
	_offsetof(struct sockaddr_x25, x25_addr[0]),	/* x25_len */
	0,			/* x25_family */
	-1,			/* x25_net id */
};

/* ARGSUSED */
int
pk_control(so, cmd, data, ifp)
	struct socket  *so;
	u_long          cmd;
	caddr_t         data;
	struct ifnet *ifp;
{
	struct ifreq_x25 *ifr = (struct ifreq_x25 *) data;
	struct ifaddr *ifa = 0;
	struct x25_ifaddr *ia = 0;
	int             error = 0, s, old_maxlcn;

	/*
	 * Find address for this interface, if it exists.
	 */
	if (ifp)
		for (ifa = ifp->if_addrlist.tqh_first; ifa != 0;
		     ifa = ifa->ifa_list.tqe_next)
			if (ifa->ifa_addr->sa_family == AF_CCITT)
				break;

	ia = (struct x25_ifaddr *) ifa;
	switch (cmd) {
	case SIOCGIFCONF_X25:
		if (ifa == 0)
			return (EADDRNOTAVAIL);
		ifr->ifr_xc = ia->ia_xc;
		return (0);

	case SIOCSIFCONF_X25:
		if ((so->so_state & SS_PRIV) == 0)
			return (EPERM);
		if (ifp == 0)
			panic("pk_control");
		if (ifa == (struct ifaddr *) 0) {
			MALLOC(ia, struct x25_ifaddr *, sizeof(*ia),
			       M_IFADDR, M_WAITOK);
			bzero((caddr_t) ia, sizeof(*ia));
			TAILQ_INSERT_TAIL(&ifp->if_addrlist, &ia->ia_ifa,
					  ifa_list);
			ifa = &ia->ia_ifa;
			ifa->ifa_netmask = (struct sockaddr *) & pk_sockmask;
			ifa->ifa_addr = (struct sockaddr *) & ia->ia_xc.xc_addr;
			ifa->ifa_dstaddr = (struct sockaddr *) & ia->ia_dstaddr;	/* XXX */
			ia->ia_ifp = ifp;
			ia->ia_dstaddr.x25_family = AF_CCITT;
			ia->ia_dstaddr.x25_len = pk_sockmask.x25_len;
		} else if (ISISO8802(ifp) == 0) {
			rtinit(ifa, (int) RTM_DELETE, 0);
		}
		old_maxlcn = ia->ia_maxlcn;
		ia->ia_xc = ifr->ifr_xc;
		ia->ia_dstaddr.x25_net = ia->ia_xc.xc_addr.x25_net;
		if (ia->ia_maxlcn != old_maxlcn && old_maxlcn != 0) {
			/* VERY messy XXX */
			struct pkcb *pkp;
			FOR_ALL_PKCBS(pkp)
				if (pkp->pk_ia == ia)
				pk_resize(pkp);
		}
		/*
		 * Give the interface a chance to initialize if this
p		 * is its first address, and to validate the address.
		 */
		ia->ia_start = pk_start;
		s = splimp();
		if (ifp->if_ioctl)
			error = (*ifp->if_ioctl) (ifp, SIOCSIFCONF_X25,
						  (caddr_t) ifa);
		if (error)
			ifp->if_flags &= ~IFF_UP;
		else if (ISISO8802(ifp) == 0)
			error = rtinit(ifa, (int) RTM_ADD, RTF_UP);
		splx(s);
		return (error);

	default:
		if (ifp == 0 || ifp->if_ioctl == 0)
			return (EOPNOTSUPP);
		return ((*ifp->if_ioctl) (ifp, cmd, data));
	}
}

int
pk_ctloutput(cmd, so, level, optname, mp)
	struct socket  *so;
	struct mbuf   **mp;
	int             cmd, level, optname;
{
	struct mbuf *m = *mp;
	struct pklcd *lcp = (struct pklcd *) so->so_pcb;
	int             error = EOPNOTSUPP;

	if (m == 0)
		return (EINVAL);
	if (cmd == PRCO_SETOPT)
		switch (optname) {
		case PK_FACILITIES:
			if (m == 0)
				return (EINVAL);
			lcp->lcd_facilities = m;
			*mp = 0;
			return (0);

		case PK_ACCTFILE:
			if ((so->so_state & SS_PRIV) == 0)
				error = EPERM;
			else if (m->m_len)
				error = pk_accton(mtod(m, char *));
			else
				error = pk_accton((char *) 0);
			break;

		case PK_RTATTACH:
			error = pk_rtattach(so, m);
			break;

		case PK_PRLISTEN:
			error = pk_user_protolisten(mtod(m, u_char *));
		}
	if (*mp) {
		(void) m_freem(*mp);
		*mp = 0;
	}
	return (error);

}


/*
 * Do an in-place conversion of an "old style"
 * socket address to the new style
 */

static void
old_to_new(m)
	struct mbuf *m;
{
	struct x25_sockaddr *oldp;
	struct sockaddr_x25 *newp;
	char  *ocp, *ncp;
	struct sockaddr_x25 new;

	oldp = mtod(m, struct x25_sockaddr *);
	newp = &new;
	bzero((caddr_t) newp, sizeof(*newp));

	newp->x25_family = AF_CCITT;
	newp->x25_len = sizeof(*newp);
	newp->x25_opts.op_flags = (oldp->xaddr_facilities & X25_REVERSE_CHARGE)
		| X25_MQBIT | X25_OLDSOCKADDR;
	if (oldp->xaddr_facilities & XS_HIPRIO)	/* Datapac specific */
		newp->x25_opts.op_psize = X25_PS128;
	bcopy((caddr_t) oldp->xaddr_addr, newp->x25_addr,
	      (unsigned) min(oldp->xaddr_len, sizeof(newp->x25_addr) - 1));
	if (bcmp((caddr_t) oldp->xaddr_proto, newp->x25_udata, 4) != 0) {
		bcopy((caddr_t) oldp->xaddr_proto, newp->x25_udata, 4);
		newp->x25_udlen = 4;
	}
	ocp = (caddr_t) oldp->xaddr_userdata;
	ncp = newp->x25_udata + 4;
	while (*ocp && ocp < (caddr_t) oldp->xaddr_userdata + 12) {
		if (newp->x25_udlen == 0)
			newp->x25_udlen = 4;
		*ncp++ = *ocp++;
		newp->x25_udlen++;
	}
	bcopy((caddr_t) newp, mtod(m, char *), sizeof(*newp));
	m->m_len = sizeof(*newp);
}

/*
 * Do an in-place conversion of a new style
 * socket address to the old style
 */

static void
new_to_old(m)
	struct mbuf *m;
{
	struct x25_sockaddr *oldp;
	struct sockaddr_x25 *newp;
	char  *ocp, *ncp;
	struct x25_sockaddr old;

	oldp = &old;
	newp = mtod(m, struct sockaddr_x25 *);
	bzero((caddr_t) oldp, sizeof(*oldp));

	oldp->xaddr_facilities = newp->x25_opts.op_flags & X25_REVERSE_CHARGE;
	if (newp->x25_opts.op_psize == X25_PS128)
		oldp->xaddr_facilities |= XS_HIPRIO;	/* Datapac specific */
	ocp = (char *) oldp->xaddr_addr;
	ncp = newp->x25_addr;
	while (*ncp) {
		*ocp++ = *ncp++;
		oldp->xaddr_len++;
	}

	bcopy(newp->x25_udata, (caddr_t) oldp->xaddr_proto, 4);
	if (newp->x25_udlen > 4)
		bcopy(newp->x25_udata + 4, (caddr_t) oldp->xaddr_userdata,
		      (unsigned) (newp->x25_udlen - 4));

	bcopy((caddr_t) oldp, mtod(m, char *), sizeof(*oldp));
	m->m_len = sizeof(*oldp);
}


int
pk_checksockaddr(m)
	struct mbuf    *m;
{
	struct sockaddr_x25 *sa = mtod(m, struct sockaddr_x25 *);
	char  *cp;

	if (m->m_len != sizeof(struct sockaddr_x25))
		return (1);
	if (sa->x25_family != AF_CCITT ||
	    sa->x25_udlen > sizeof(sa->x25_udata))
		return (1);
	for (cp = sa->x25_addr; *cp; cp++) {
		if (*cp < '0' || *cp > '9' ||
		    cp >= &sa->x25_addr[sizeof(sa->x25_addr) - 1])
			return (1);
	}
	return (0);
}

int
pk_send(m, v)
	struct mbuf *m;
	void *v;
{
	struct pklcd   *lcp = v;
	int             mqbit = 0, error = 0;
	struct x25_packet *xp;
	struct socket *so;

	if (m->m_type == MT_OOBDATA) {
		if (lcp->lcd_intrconf_pending)
			error = ETOOMANYREFS;
		if (m->m_pkthdr.len > 32)
			error = EMSGSIZE;
		M_PREPEND(m, PKHEADERLN, M_WAITOK);
		if (error)
			goto bad;
		*(mtod(m, octet *)) = 0;
		xp = mtod(m, struct x25_packet *);
		X25SBITS(xp->bits, fmt_identifier, 1);
		xp->packet_type = X25_INTERRUPT;
		SET_LCN(xp, lcp->lcd_lcn);
		sbinsertoob((so = lcp->lcd_so) ?
			    &so->so_snd : &lcp->lcd_sb, m);
		goto send;
	}
	/*
	 * Application has elected (at call setup time) to prepend
	 * a control byte to each packet written indicating m-bit
	 * and q-bit status.  Examine and then discard this byte.
	 */
	if (lcp->lcd_flags & X25_MQBIT) {
		if (m->m_len < 1) {
			m_freem(m);
			return (EMSGSIZE);
		}
		mqbit = *(mtod(m, u_char *));
		m->m_len--;
		m->m_data++;
		m->m_pkthdr.len--;
	}
	error = pk_fragment(lcp, m, mqbit & 0x80, mqbit & 0x40, 1);
send:
	if (error == 0 && lcp->lcd_state == DATA_TRANSFER)
		lcp->lcd_send(lcp);	/* XXXXXXXXX fix pk_output!!! */
	return (error);
bad:
	if (m)
		m_freem(m);
	return (error);
}