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

 @(#) inet.c,v INETBeta2_4(0.8.2.19) 2002/10/24 21:51:51

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

 Copyright (c) 2001-2002  OpenSS7 Corporation <http://www.openss7.com>
 Copyright (c) 1997-2000  Brian F. G. 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.

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

 U.S. GOVERNMENT RESTRICTED RIGHTS.  If you are licensing this Software on
 behalf of the U.S. Government ("Government"), the following provisions apply
 to you.  If the Software is supplied by the Department of Defense ("DoD"), it
 is classified as "Commercial Computer Software" under paragraph 252.227-7014
 of the DoD Supplement to the Federal Acquisition Regulations ("DFARS") (or any
 successor regulations) and the Government is acquiring only the license rights
 granted herein (the license rights customarily provided to non-Government
 users).  If the Software is supplied to any unit or agency of the Government
 other than DoD, it is classified as "Restricted Computer Software" and the
 Government's rights in the Software are defined in paragraph 52.227-19 of the
 Federal Acquisition Regulations ("FAR") (or any success regulations) or, in
 the cases of NASA, in paragraph 18.52.227-86 of the NASA Supplement to the FAR
 (or any successor regulations).

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

 Commercial licensing and support of this software is available from OpenSS7
 Corporation at a fee.  See http://www.openss7.com/

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

 Last Modified 2002/10/24 21:51:51 by brian

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

#ident "@(#) inet.c,v INETBeta2_4(0.8.2.19) 2002/10/24 21:51:51"

static char const ident[] =
    "inet.c,v INETBeta2_4(0.8.2.19) 2002/10/24 21:51:51";

/*
 *  This driver provides the functionality of IP (Internet Protocol) over a
 *  connectionless network provider (NPI).  It provides a STREAMS-based
 *  encapsulation of the Linux IP stack.
 */

#include <linux/config.h>
#include <linux/version.h>

#ifdef CONFIG_MODVERSIONS
#undef  MODVERSIONS
#define MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/module.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/ip.h>

#include <net/sock.h>
#include <net/tcp.h>

#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>
#include <sys/tpi.h>
#include <sys/tpi_inet.h>
#include <sys/xti_inet.h>

#include "../debug.h"
#include "../bufq.h"

#define	SS_DESCRIP	"SOCKSYS STREAMS (TPI) DRIVER." "\n" \
			"Part of the OpenSS7 Stack for LiS STREAMS."
#define SS_COPYRIGHT	"Copyright (c) 1997-2002 OpenSS7 Corporation.  All Rights Reserved."
#define SS_DEVICE	"Supports OpenSS7 INET Drivers."
#define SS_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define SS_LICENSE	"GPL"
#define SS_BANNER	SS_DESCRIP	"\n" \
			SS_COPYRIGHT	"\n" \
			SS_DEVICE	"\n" \
			SS_CONTACT

MODULE_AUTHOR(SS_CONTACT);
MODULE_DESCRIPTION(SS_DESCRIP);
MODULE_SUPPORTED_DEVICE(SS_DEVICE);
#ifdef MODULE_LICENSE
MODULE_LICENSE(SS_LICENSE);
#endif

#ifndef SS_CMAJOR
#define SS_CMAJOR	30
#define IP_CMINOR	32
#define ICMP_CMINOR	33
#define GGP_CMINOR	34
#define IPIP_CMINOR	35
#define TCP_CMINOR	36
#define EGP_CMINOR	37
#define PUP_CMINOR	38
#define UDP_CMINOR	39
#define IDP_CMINOR	40
#define RAWIP_CMINOR	41
#define FREE_CMINOR	50
#endif

#ifndef SS_NMAJOR
#define SS_NMAJOR 4
#define SS_NMINOR 256
#endif

#ifndef SS_IOC_MAGIC
#define SS_IOC_MAGIC 'i'
#endif

#ifdef LINUX_2_4
#define INT int
#else
#define INT void
#endif

/*
 *  =========================================================================
 *
 *  STREAMS Definitions
 *
 *  =========================================================================
 */
#define SS_MOD_NAME	"socksys"
#define SS_MOD_ID	('I'<<8|SS_IOC_MAGIC)

STATIC struct module_info ss_minfo = {
	mi_idnum:SS_MOD_ID,		/* Module ID number */
	mi_idname:SS_MOD_NAME,		/* Module name */
	mi_minpsz:0,			/* Min packet size accepted */
	mi_maxpsz:INFPSZ,		/* Max packet size accepted */
	mi_hiwat:1 << 15,		/* Hi water mark */
	mi_lowat:1 << 10		/* Lo water mark */
};

STATIC int ss_open(queue_t *, dev_t *, int, int, cred_t *);
STATIC int ss_close(queue_t *, int, cred_t *);

STATIC INT ss_rput(queue_t *, mblk_t *);
STATIC INT ss_rsrv(queue_t *);

STATIC struct qinit ss_rinit = {
	qi_putp:ss_rput,		/* Read put (msg from below) */
	qi_srvp:ss_rsrv,		/* Read queue service */
	qi_qopen:ss_open,		/* Each open */
	qi_qclose:ss_close,		/* Last close */
	qi_minfo:&ss_minfo,		/* Information */
};

STATIC INT ss_wput(queue_t *, mblk_t *);
STATIC INT ss_wsrv(queue_t *);

STATIC struct qinit ss_winit = {
	qi_putp:ss_wput,		/* Write put (msg from above) */
	qi_srvp:ss_wsrv,		/* Write queue service */
	qi_minfo:&ss_minfo,		/* Information */
};

STATIC struct streamtab ss_info = {
	st_rdinit:&ss_rinit,		/* Upper read queue */
	st_wrinit:&ss_winit,		/* Lower read queue */
};

/*
 *  Queue put and service return values
 */
#define QR_DONE		0
#define QR_ABSORBED	1
#define QR_TRIMMED	2
#define QR_LOOP		3
#define QR_PASSALONG	4
#define QR_PASSFLOW	5
#define QR_DISABLE	6
#define QR_STRIP	7

/*
 *  TLI interface state flags
 */
#define TSF_UNBND	( 1 << TS_UNBND		)
#define TSF_WACK_BREQ	( 1 << TS_WACK_BREQ	)
#define TSF_WACK_UREQ	( 1 << TS_WACK_UREQ	)
#define TSF_IDLE	( 1 << TS_IDLE		)
#ifdef TS_WACK_OPTREQ
#define TSF_WACK_OPTREQ	( 1 << TS_WACK_OPTREQ	)
#endif
#define TSF_WACK_CREQ	( 1 << TS_WACK_CREQ	)
#define TSF_WCON_CREQ	( 1 << TS_WCON_CREQ	)
#define TSF_WRES_CIND	( 1 << TS_WRES_CIND	)
#define TSF_WACK_CRES	( 1 << TS_WACK_CRES	)
#define TSF_DATA_XFER	( 1 << TS_DATA_XFER	)
#define TSF_WIND_ORDREL	( 1 << TS_WIND_ORDREL	)
#define TSF_WREQ_ORDREL	( 1 << TS_WREQ_ORDREL	)
#define TSF_WACK_DREQ6	( 1 << TS_WACK_DREQ6	)
#define TSF_WACK_DREQ7	( 1 << TS_WACK_DREQ7	)
#define TSF_WACK_DREQ9	( 1 << TS_WACK_DREQ9	)
#define TSF_WACK_DREQ10	( 1 << TS_WACK_DREQ10	)
#define TSF_WACK_DREQ11	( 1 << TS_WACK_DREQ11	)
#define TSF_NOSTATES	( 1 << TS_NOSTATES	)
#define TSM_WACK_DREQ	(TSF_WACK_DREQ6 \
			|TSF_WACK_DREQ7 \
			|TSF_WACK_DREQ9 \
			|TSF_WACK_DREQ10 \
			|TSF_WACK_DREQ11)
#define TSM_LISTEN	(TSF_IDLE \
			|TSF_WRES_CIND)
#define TSM_CONNECTED	(TSF_WCON_CREQ\
			|TSF_WRES_CIND\
			|TSF_DATA_XFER\
			|TSF_WIND_ORDREL\
			|TSF_WREQ_ORDREL)
#define TSM_DISCONN	(TSF_IDLE\
			|TSF_UNBND)
#define TSM_INDATA	(TSF_DATA_XFER\
			|TSF_WIND_ORDREL)
#define TSM_OUTDATA	(TSF_DATA_XFER\
			|TSF_WREQ_ORDREL)
#ifndef T_PROVIDER
#define T_PROVIDER  0
#define T_USER	    1
#endif

/*
 *  Socket state masks
 */

/*
 *  TCP state masks
 */
#define TCPM_CLOSING	(TCPF_CLOSE\
			|TCPF_TIME_WAIT\
			|TCPF_CLOSE_WAIT)
#define TCPM_CONNIND	(TCPF_SYN_RECV\
			|TCPF_ESTABLISHED\
			|TCPF_LISTEN)

/*
 *  =========================================================================
 *
 *  IP Private Datastructures
 *
 *  =========================================================================
 */

typedef struct sockaddr_in ss_addr_t;

typedef struct ss_dflt {
	struct ip_options opt;		/* T_IP_OPTIONS */
	ulong tos;			/* T_IP_TOS */
	ulong ttl;			/* T_IP_TTL */
	ulong reuse;			/* T_IP_REUSE_ADDR */
	ulong norte;			/* T_IP_DONTROUTE */
	ulong bcast;			/* T_IP_BROADCAST */
	ulong nodly;			/* T_TCP_NODELAY */
	ulong mss;			/* T_TCP_MAXSEG */
	ulong alive;			/* T_TCP_KEEPALIVE */
	ulong csum;			/* T_UDP_CHECKSUM */
} ss_dflt_t;

typedef struct ss_profile {
	struct {
		uint family;
		uint type;
		uint protocol;
	} prot;
	struct T_info_ack info;
} ss_profile_t;

typedef struct inet {
	struct inet *next;		/* list of all IP-Users */
	struct inet **prev;		/* list of all IP-Users */
	size_t refcnt;			/* structure reference count */
	lis_spin_lock_t lock;		/* structure lock */
	ushort cmajor;			/* major device number */
	ushort cminor;			/* minor device number */
	queue_t *rq;			/* associated read queue */
	queue_t *wq;			/* associated write queue */
	cred_t cred;			/* credientials */
	lis_spin_lock_t qlock;		/* queue lock */
	queue_t *rwait;			/* RD queue waiting on lock */
	queue_t *wwait;			/* WR queue waiting on lock */
	uint rbid;			/* RD buffer call id */
	uint wbid;			/* WR buffer call id */
	ushort port;			/* port/protocol number */
	int tcp_state;			/* tcp state history */
	ss_profile_t p;			/* protocol profile */
	struct {
		void (*state_change) (struct sock *);
		void (*data_ready) (struct sock *, int);
		void (*write_space) (struct sock *);
		void (*error_report) (struct sock *);
	} cb_save;			/* call back holder */
	ss_addr_t src;			/* bound address */
	ss_addr_t dst;			/* connected address */
	ss_dflt_t options;		/* protocol options */
	unsigned char _pad[40];		/* pad for options */
	bufq_t conq;			/* connection queue */
	uint conind;			/* number of connection indications */
	struct socket *sock;		/* socket pointer */
} ss_t;

#define PRIV(__q) ((ss_t *)((__q)->q_ptr))
#define SOCK_PRIV(__sk) ((ss_t *)(__sk)->user_data)

typedef struct ss_opts {
	uint flags;			/* success flags */
	struct ip_options *opt;		/* T_IP_OPTIONS */
	ulong *tos;			/* T_IP_TOS */
	ulong *ttl;			/* T_IP_TTL */
	ulong *reuse;			/* T_IP_REUSE_ADDR */
	ulong *norte;			/* T_IP_DONTROUTE */
	ulong *bcast;			/* T_IP_BROADCAST */
	ulong *nodly;			/* T_TCP_NODELAY */
	ulong *mss;			/* T_TCP_MAXSEG */
	ulong *alive;			/* T_TCP_KEEPALIVE */
	ulong *csum;			/* T_UDP_CHECKSUM */
	ulong *ropt;
	struct in_pktinfo *pkinfo;
} ss_opts_t;

#define ip_default_opt		{ 0, }
#define ip_default_tos		1
#define ip_default_ttl		64
#define ip_default_reuse	1
#define ip_default_norte	1
#define ip_default_bcast	1
#define tcp_default_nodly	1
#define tcp_default_mss		576
#define tcp_default_alive	1
#define udp_default_csum	0

#define TF_IP_OPTIONS		0x1
#define TF_IP_TOS		0x2
#define TF_IP_TTL		0x4
#define TF_IP_REUSEADDR		0x8
#define TF_IP_DONTROUTE		0x10
#define TF_IP_BROADCAST		0x20
#define TF_IP_PKTINFO		0x40
#define TF_IP_RETOPTS		0x80
#define TF_TCP_NODELAY		0x100
#define TF_TCP_MAXSEG		0x200
#define TF_TCP_KEEPALIVE	0x400
#define TF_UDP_CHECKSUM		0x800
#define TM_OPT_SENDMSG	(	TF_IP_OPTIONS	\
			|	TF_IP_TOS	\
			|	TF_IP_TTL	\
			|	TF_IP_DONTROUTE	\
			|	TF_IP_PKTINFO	\
			)

#define _T_CHECK		1
#define _T_NEGOTIATE		2
#define _T_DEFAULT		3
#define _T_CURRENT		4

#define TF_CHECK		(1<<_T_CHECK)
#define TF_NEGOTIATE		(1<<_T_NEGOTIATE)
#define TF_DEFAULT		(1<<_T_DEFAULT)
#define TF_CURRENT		(1<<_T_CURRENT)

typedef struct ss_event {
	struct sock *sk;		/* sock (child) for event */
	int state;			/* state at time of event */
} ss_event_t;

STATIC lis_spin_lock_t ss_lock;		/* protects ss_opens lists */
STATIC ss_t *ss_opens = NULL;

#if 0

/*
 * for later when we support default destinations and default listeners 
 */
STATIC ss_t *ss_dflt_dest = NULL;
STATIC ss_t *ss_dflt_lstn = NULL;
#endif

/*
 *  =========================================================================
 *
 *  Socket Callbacks
 *
 *  =========================================================================
 */
STATIC void ss_state_change(struct sock *sk);
STATIC void ss_write_space(struct sock *sk);
STATIC void ss_error_report(struct sock *sk);
STATIC void ss_data_ready(struct sock *sk, int len);
STATIC void ss_socket_put(struct socket *sock)
{
	struct sock *sk;
	ensure(sock, return);
	if ((sk = sock->sk)) {
		write_lock(&sk->callback_lock);
		{
			ss_t *ss;
			if ((ss = SOCK_PRIV(sk))) {
				SOCK_PRIV(sk) = NULL;
				ss->refcnt--;
				sk->state_change = ss->cb_save.state_change;
				sk->data_ready = ss->cb_save.data_ready;
				sk->write_space = ss->cb_save.write_space;
				sk->error_report = ss->cb_save.error_report;
			} else
				assure(ss);
		}
		write_unlock(&sock->sk->callback_lock);
	} else
		assure(sk);
	sock_release(sock);
}
STATIC void ss_socket_get(struct socket *sock, ss_t * ss)
{
	struct sock *sk;
	ensure(sock, return);
	sk = sock->sk;
	ensure(sk, return);
	write_lock(&sock->sk->callback_lock);
	{
		SOCK_PRIV(sk) = ss;
		ss->refcnt++;
		ss->cb_save.state_change = sk->state_change;
		ss->cb_save.data_ready = sk->data_ready;
		ss->cb_save.write_space = sk->write_space;
		ss->cb_save.error_report = sk->error_report;
		sk->state_change = ss_state_change;
		sk->data_ready = ss_data_ready;
		sk->write_space = ss_write_space;
		sk->error_report = ss_error_report;
#ifdef LINUX_2_4
		sk->protinfo.af_inet.recverr = 1;
#else
		sk->ip_recverr = 1;
#endif
		ss->tcp_state = sk->state;	/* initialized tcp state */
	}
	write_unlock(&sock->sk->callback_lock);
}

/*
 *  =========================================================================
 *
 *  Locking
 *
 *  =========================================================================
 */
STATIC int ss_trylockq(queue_t * q)
{
	int res;
	ss_t *ss = PRIV(q);
	if (!(res = lis_spin_trylock(&ss->qlock))) {
		if (q == ss->rq)
			ss->rwait = q;
		if (q == ss->wq)
			ss->wwait = q;
	}
	return (res);
}
STATIC void ss_unlockq(queue_t * q)
{
	ss_t *ss = PRIV(q);
	lis_spin_unlock(&ss->qlock);
	if (ss->rwait)
		qenable(xchg(&ss->rwait, NULL));
	if (ss->wwait)
		qenable(xchg(&ss->wwait, NULL));
}

/*
 *  =========================================================================
 *
 *  Buffer Allocation
 *
 *  =========================================================================
 */
/*
 *  BUFSRV calls service routine
 *  -------------------------------------------------------------------------
 */
STATIC void ss_bufsrv(long data)
{
	queue_t *q = (queue_t *) data;
	if (q) {
		ss_t *ss = PRIV(q);
		if (q == ss->rq)
			if (ss->rbid) {
				ss->rbid = 0;
				ss->refcnt--;
			}
		if (q == ss->wq)
			if (ss->wbid) {
				ss->wbid = 0;
				ss->refcnt--;
			}
		qenable(q);
	}
}

/*
 *  UNBUFCALL
 *  -------------------------------------------------------------------------
 */
STATIC void ss_unbufcall(queue_t * q)
{
	ss_t *ss = PRIV(q);
	if (ss->rbid) {
		unbufcall(xchg(&ss->rbid, 0));
		ss->refcnt--;
	}
	if (ss->wbid) {
		unbufcall(xchg(&ss->wbid, 0));
		ss->refcnt--;
	}
}

/*
 *  ALLOCB
 *  -------------------------------------------------------------------------
 */
STATIC mblk_t *ss_allocb(queue_t * q, size_t size, int prior)
{
	mblk_t *mp;
	if ((mp = allocb(size, prior)))
		return (mp);
	else {
		ss_t *ss = PRIV(q);
		if (q == ss->rq) {
			if (!ss->rbid) {
				ss->rbid = bufcall(size, prior, &ss_bufsrv, (long) q);
				ss->refcnt++;
			}
			return (NULL);
		}
		if (q == ss->wq) {
			if (!ss->wbid) {
				ss->wbid = bufcall(size, prior, &ss_bufsrv, (long) q);
				ss->refcnt++;
			}
			return (NULL);
		}
	}
	swerr();
	return (NULL);
}

#if 0
/*
 *  ESBALLOC
 *  -------------------------------------------------------------------------
 */
STATIC mblk_t *ss_esballoc(queue_t * q, unsigned char *base, size_t size, int prior, frtn_t * frtn)
{
	mblk_t *mp;
	if ((mp = esballoc(base, size, prior, frtn)))
		return (mp);
	else {
		ss_t *ss = PRIV(q);
		if (q == ss->rq) {
			if (!ss->rbid) {
				ss->rbid = esbbcall(prior, &ss_bufsrv, (long) q);
				ss->refcnt++;
			}
			return (NULL);
		}
		if (q == ss->wq) {
			if (!ss->wbid) {
				ss->wbid = esbbcall(prior, &ss_bufsrv, (long) q);
				ss->refcnt++;
			}
			return (NULL);
		}
		swerr();
		return (NULL);
	}
}
#endif

/*
 *  =========================================================================
 *
 *  OPTION Handling
 *
 *  =========================================================================
 */
#define _T_ALIGN_SIZEOF(s) \
	((sizeof((s)) + _T_ALIGN_SIZE - 1) & ~(_T_ALIGN_SIZE - 1))
STATIC size_t ss_opts_size(ss_opts_t * ops)
{
	size_t len = 0;
	const size_t hlen = sizeof(struct t_opthdr);	/* 32 bytes */
	if (ops) {
		if (ops->opt)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->opt));
		if (ops->tos)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->tos));
		if (ops->ttl)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->ttl));
		if (ops->reuse)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->reuse));
		if (ops->norte)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->norte));
		if (ops->bcast)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->bcast));
		if (ops->nodly)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->nodly));
		if (ops->mss)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->mss));
		if (ops->alive)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->alive));
		if (ops->csum)
			len += hlen + _T_ALIGN_SIZEOF(*(ops->csum));
	}
	return (len);
}
STATIC void ss_build_opts(ss_opts_t * ops, unsigned char **p)
{
	struct t_opthdr *oh;
	const size_t hlen = sizeof(struct t_opthdr);
	const size_t olen = hlen + sizeof(t_scalar_t);
	if (ops) {
		if (ops->opt) {
			oh = ((typeof(oh)) * p)++;
			oh->len = hlen + sizeof(struct ip_options);
			oh->level = T_INET_IP;
			oh->name = T_IP_OPTIONS;
			oh->status = (ops->flags & TF_IP_OPTIONS) ? T_SUCCESS : T_FAILURE;
			*((struct ip_options *) *p) = *(ops->opt);
			*p += _T_ALIGN_SIZEOF(*ops->opt);
		}
		if (ops->tos) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_IP;
			oh->name = T_IP_TOS;
			oh->status = (ops->flags & TF_IP_TOS) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->tos);
			*p += _T_ALIGN_SIZEOF(*ops->tos);
		}
		if (ops->ttl) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_IP;
			oh->name = T_IP_TTL;
			oh->status = (ops->flags & TF_IP_TTL) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->ttl);
			*p += _T_ALIGN_SIZEOF(*ops->ttl);
		}
		if (ops->reuse) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_IP;
			oh->name = T_IP_REUSEADDR;
			oh->status = (ops->flags & TF_IP_REUSEADDR) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->reuse);
			*p += _T_ALIGN_SIZEOF(*ops->reuse);
		}
		if (ops->norte) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_IP;
			oh->name = T_IP_DONTROUTE;
			oh->status = (ops->flags & TF_IP_DONTROUTE) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->norte);
			*p += _T_ALIGN_SIZEOF(*ops->norte);
		}
		if (ops->bcast) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_IP;
			oh->name = T_IP_BROADCAST;
			oh->status = (ops->flags & TF_IP_BROADCAST) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->bcast);
			*p += _T_ALIGN_SIZEOF(*ops->bcast);
		}
		if (ops->nodly) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_TCP;
			oh->name = T_TCP_NODELAY;
			oh->status = (ops->flags & TF_TCP_NODELAY) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->nodly);
			*p += _T_ALIGN_SIZEOF(*ops->nodly);
		}
		if (ops->mss) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_TCP;
			oh->name = T_TCP_MAXSEG;
			oh->status = (ops->flags & TF_TCP_MAXSEG) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->mss);
			*p += _T_ALIGN_SIZEOF(*ops->mss);
		}
		if (ops->alive) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_TCP;
			oh->name = T_TCP_KEEPALIVE;
			oh->status = (ops->flags & TF_TCP_KEEPALIVE) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->alive);
			*p += _T_ALIGN_SIZEOF(*ops->alive);
		}
		if (ops->csum) {
			oh = ((typeof(oh)) * p)++;
			oh->len = olen;
			oh->level = T_INET_UDP;
			oh->name = T_UDP_CHECKSUM;
			oh->status = (ops->flags & TF_UDP_CHECKSUM) ? T_SUCCESS : T_FAILURE;
			*((t_scalar_t *) * p) = *(ops->csum);
			*p += _T_ALIGN_SIZEOF(*ops->csum);
		}
	}
}
STATIC int ss_parse_opts(ss_t * ss, ss_opts_t * ops, unsigned char *op, size_t len)
{
	struct t_opthdr *oh;
	for (oh = _T_OPT_FIRSTHDR_OFS(op, len, 0); oh; oh = _T_OPT_NEXTHDR_OFS(op, len, oh, 0)) {
		switch (oh->level) {
		case T_INET_IP:
			switch (oh->name) {
			case T_IP_OPTIONS:
				ops->opt = (void *) _T_OPT_DATA_OFS(oh, 0);
				ops->flags |= TF_IP_OPTIONS;
				continue;
			case T_IP_TOS:
				ops->tos = (void *) _T_OPT_DATA_OFS(oh, 0);
				ops->flags |= TF_IP_TOS;
				continue;
			case T_IP_TTL:
				ops->ttl = (void *) _T_OPT_DATA_OFS(oh, 0);
				ops->flags |= TF_IP_TTL;
				continue;
			case T_IP_REUSEADDR:
				ops->reuse = (void *) _T_OPT_DATA_OFS(oh, 0);
				ops->flags |= TF_IP_REUSEADDR;
				continue;
			case T_IP_DONTROUTE:
				ops->norte = (void *) _T_OPT_DATA_OFS(oh, 0);
				ops->flags |= TF_IP_DONTROUTE;
				continue;
			case T_IP_BROADCAST:
				ops->bcast = (void *) _T_OPT_DATA_OFS(oh, 0);
				ops->flags |= TF_IP_BROADCAST;
				continue;
			}
			break;
		case T_INET_TCP:
			if (ss->p.prot.protocol == T_INET_TCP)
				switch (oh->name) {
				case T_TCP_NODELAY:
					ops->nodly = (void *) _T_OPT_DATA_OFS(oh, 0);
					ops->flags |= TF_TCP_NODELAY;
					continue;
				case T_TCP_MAXSEG:
					ops->mss = (void *) _T_OPT_DATA_OFS(oh, 0);
					ops->flags |= TF_TCP_MAXSEG;
					continue;
				case T_TCP_KEEPALIVE:
					ops->alive = (void *) _T_OPT_DATA_OFS(oh, 0);
					ops->flags |= TF_TCP_KEEPALIVE;
					continue;
				}
			break;
		case T_INET_UDP:
			if (ss->p.prot.protocol == T_INET_UDP)
				switch (oh->name) {
				case T_UDP_CHECKSUM:
					ops->csum = (void *) _T_OPT_DATA_OFS(oh, 0);
					ops->flags |= TF_UDP_CHECKSUM;
					continue;
				}
			break;
		case T_INET_SCTP:
			/* 
			 * skip these for now 
			 */
			break;
		}
	}
	if (oh)
		return (TBADOPT);
	return (0);
}

STATIC size_t ss_cmsg_size(ss_opts_t * ops, int flags)
{
	size_t len = 0;
	const size_t hlen = CMSG_ALIGN(sizeof(struct cmsghdr));
	if (ops) {
		if (ops->opt && flags & TF_IP_OPTIONS)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->opt)));
		if (ops->tos && flags & TF_IP_TOS)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->tos)));
		if (ops->ttl && flags & TF_IP_TTL)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->ttl)));
		if (ops->reuse && flags & TF_IP_REUSEADDR)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->reuse)));
		if (ops->norte && flags & TF_IP_DONTROUTE)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->norte)));
		if (ops->bcast && flags & TF_IP_BROADCAST)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->bcast)));
		if (ops->nodly && flags & TF_TCP_NODELAY)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->nodly)));
		if (ops->mss && flags & TF_TCP_MAXSEG)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->mss)));
		if (ops->alive && flags & TF_TCP_KEEPALIVE)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->alive)));
#ifdef LINUX_2_4
#endif
#ifdef UDP_CHECKSUM
		if (ops->csum && flags & TF_UDP_CHECKSUM)
			len += hlen + CMSG_ALIGN(sizeof(*(ops->csum)));
#endif
#ifdef CONFIG_SCTP
#endif
	}
	return (len);
}
STATIC void ss_build_cmsg(ss_opts_t * ops, struct msghdr *msg, int flags)
{
	struct cmsghdr *ch;
	unsigned char *p = msg->msg_control;
	const size_t hlen = CMSG_ALIGN(sizeof(struct cmsghdr));
	if (ops) {
		if (ops->opt && flags & TF_IP_OPTIONS) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->opt));
			ch->cmsg_level = SOL_IP;
			ch->cmsg_type = IP_OPTIONS;
			*((typeof(ops->opt)) p) = *(ops->opt);
			p += CMSG_ALIGN(sizeof(*ops->opt));
		}
		if (ops->tos && flags & TF_IP_TOS) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->tos));
			ch->cmsg_level = SOL_IP;
			ch->cmsg_type = IP_TOS;
			*((typeof(ops->tos)) p) = *(ops->tos);
			p += CMSG_ALIGN(sizeof(*ops->tos));
		}
		if (ops->ttl && flags & TF_IP_TTL) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->ttl));
			ch->cmsg_level = SOL_IP;
			ch->cmsg_type = IP_TTL;
			*((typeof(ops->ttl)) p) = *(ops->ttl);
			p += CMSG_ALIGN(sizeof(*ops->ttl));
		}
		if (ops->reuse && flags & TF_IP_REUSEADDR) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->reuse));
			ch->cmsg_level = SOL_SOCKET;
			ch->cmsg_type = SO_REUSEADDR;
			*((typeof(ops->reuse)) p) = *(ops->reuse);
			p += CMSG_ALIGN(sizeof(*ops->reuse));
		}
		if (ops->norte && flags & TF_IP_DONTROUTE) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->norte));
			ch->cmsg_level = SOL_SOCKET;
			ch->cmsg_type = SO_DONTROUTE;
			*((typeof(ops->norte)) p) = *(ops->norte);
			p += CMSG_ALIGN(sizeof(*ops->norte));
		}
		if (ops->bcast && flags & TF_IP_BROADCAST) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->bcast));
			ch->cmsg_level = SOL_SOCKET;
			ch->cmsg_type = SO_BROADCAST;
			*((typeof(ops->bcast)) p) = *(ops->bcast);
			p += CMSG_ALIGN(sizeof(*ops->bcast));
		}
		if (ops->nodly && flags & TF_TCP_NODELAY) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->nodly));
			ch->cmsg_level = SOL_TCP;
			ch->cmsg_type = TCP_NODELAY;
			*((typeof(ops->nodly)) p) = *(ops->nodly);
			p += CMSG_ALIGN(sizeof(*ops->nodly));
		}
		if (ops->mss && flags & TF_TCP_MAXSEG) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->mss));
			ch->cmsg_level = SOL_TCP;
			ch->cmsg_type = TCP_MAXSEG;
			*((typeof(ops->mss)) p) = *(ops->mss);
			p += CMSG_ALIGN(sizeof(*ops->mss));
		}
		if (ops->alive && flags & TF_TCP_KEEPALIVE) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->alive));
			ch->cmsg_level = SOL_SOCKET;
			ch->cmsg_type = SO_KEEPALIVE;
			*((typeof(ops->alive)) p) = *(ops->alive);
			p += CMSG_ALIGN(sizeof(*ops->alive));
		}
#ifdef UDP_CHECKSUM
		if (ops->csum && flags & TF_UDP_CHECKSUM) {
			ch = ((typeof(ch)) p)++;
			ch->cmsg_len = hlen + CMSG_ALIGN(sizeof(*ops->csum));
			ch->cmsg_level = SOL_UDP;
			ch->cmsg_type = UDP_CHECKSUM;
			*((typeof(ops->csum)) p) = *(ops->csum);
			p += CMSG_ALIGN(sizeof(*ops->csum));
		}
#endif
	}
}
STATIC int ss_parse_cmsg(struct msghdr *msg, ss_opts_t * opts)
{
	struct cmsghdr *cmsg;
	for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
		if (cmsg->cmsg_len < sizeof(struct cmsghdr)
		    || (unsigned long) (((char *) cmsg - (char *) msg->msg_control)
					+ cmsg->cmsg_len) > msg->msg_controllen) {
			return -EINVAL;
		}
		switch (cmsg->cmsg_level) {
		case SOL_IP:
			switch (cmsg->cmsg_type) {
			case IP_OPTIONS:
				if (cmsg->cmsg_len <= 40) {
					opts->opt = (typeof(opts->opt))
					    CMSG_DATA(cmsg);
					opts->flags |= TF_IP_OPTIONS;
				}
				break;
			case IP_TTL:
				if (cmsg->cmsg_len == sizeof(*opts->ttl)) {
					opts->ttl = (typeof(opts->ttl))
					    CMSG_DATA(cmsg);
					opts->flags |= TF_IP_TTL;
				}
				break;
			case IP_TOS:
				if (cmsg->cmsg_len == sizeof(*opts->tos)) {
					opts->tos = (typeof(opts->tos))
					    CMSG_DATA(cmsg);
					opts->flags |= TF_IP_TOS;
				}
				break;
			case IP_RETOPTS:
				if (cmsg->cmsg_len == sizeof(*opts->ropt)) {
					opts->ropt = (typeof(opts->ropt))
					    CMSG_DATA(cmsg);
					opts->flags |= TF_IP_RETOPTS;
				}
				break;
			case IP_PKTINFO:
				if (cmsg->cmsg_len == sizeof(*opts->pkinfo)) {
					opts->pkinfo = (typeof(opts->pkinfo))
					    CMSG_DATA(cmsg);
					opts->flags |= TF_IP_PKTINFO;
				}
				break;
			case IP_HDRINCL:
			case IP_ROUTER_ALERT:
			case IP_RECVOPTS:
			case IP_PKTOPTIONS:
			case IP_MTU_DISCOVER:
			case IP_RECVERR:
			case IP_RECVTTL:
			case IP_RECVTOS:
			case IP_MTU:
				break;
			}
			break;
		case SOL_TCP:
			switch (cmsg->cmsg_type) {
			case TCP_NODELAY:
			case TCP_MAXSEG:
			case TCP_CORK:
#ifdef LINUX_2_4
			case TCP_KEEPIDLE:
			case TCP_KEEPINTVL:
			case TCP_KEEPCNT:
			case TCP_SYNCNT:
			case TCP_LINGER2:
			case TCP_DEFER_ACCEPT:
			case TCP_WINDOW_CLAMP:
			case TCP_INFO:
			case TCP_QUICKACK:
#endif
				break;
			}
			break;
		case SOL_UDP:
			switch (cmsg->cmsg_type) {
			}
			break;
#ifdef CONFIG_SCTP
		case SOL_SCTP:
			switch (cmsg->cmsg_type) {
			case SCTP_NODELAY:
			case SCTP_MAXSEG:
			case SCTP_CORK:
			case SCTP_RECVSID:
			case SCTP_RECVPPI:
			case SCTP_RECVHDR:
			case SCTP_SID:
			case SCTP_PPI:
			case SCTP_HB:
			case SCTP_RTO:
			case SCTP_HDR:
				break;
			}
			break;
#endif
		default:
			break;
		}
	}
	return (0);
}

/*
 *  =========================================================================
 *
 *  OPTIONS Handling
 *
 *  =========================================================================
 */
STATIC ss_dflt_t opt_defaults = {
	ip_default_opt,
	ip_default_tos,
	ip_default_ttl,
	ip_default_reuse,
	ip_default_norte,
	ip_default_bcast,
	tcp_default_nodly,
	tcp_default_mss,
	tcp_default_alive,
	udp_default_csum
};
/*
 *  CHECK Options
 *  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
STATIC int ss_opt_check(ss_t * ss, ss_opts_t * opt)
{
	if (opt->flags) {
		opt->flags = 0;
		if (opt->opt)
			opt->flags |= TF_IP_OPTIONS;
		if (opt->tos && !(*(opt->tos) & 0x1e))
			opt->flags |= TF_IP_TOS;
		if (opt->ttl && !(*(opt->ttl) & 0xff))
			opt->flags |= TF_IP_TTL;
		if (opt->reuse)
			opt->flags |= TF_IP_REUSEADDR;
		if (opt->norte)
			opt->flags |= TF_IP_DONTROUTE;
		if (opt->bcast)
			opt->flags |= TF_IP_BROADCAST;
		if (ss->p.prot.protocol == T_INET_TCP) {
			if (opt->nodly)
				opt->flags |= TF_TCP_NODELAY;
			if (opt->mss)
				opt->flags |= TF_TCP_MAXSEG;
			if (opt->alive)
				opt->flags |= TF_TCP_KEEPALIVE;
		}
		if (ss->p.prot.protocol == T_INET_UDP) {
			if (opt->csum)
				opt->flags |= TF_UDP_CHECKSUM;
		}
	}
	return (0);
}

/*
 *  DEFAULT Options
 *  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
STATIC int ss_opt_default(ss_t * ss, ss_opts_t * opt)
{
	int flags = opt->flags;
	opt->flags = 0;
	if (!flags || opt->tos) {
		opt->tos = &opt_defaults.tos;
		opt->flags |= TF_IP_TOS;
	}
	if (!flags || opt->ttl) {
		opt->ttl = &opt_defaults.ttl;
		opt->flags |= TF_IP_TTL;
	}
	if (!flags || opt->reuse) {
		opt->reuse = &opt_defaults.reuse;
		opt->flags |= TF_IP_REUSEADDR;
	}
	if (!flags || opt->norte) {
		opt->norte = &opt_defaults.norte;
		opt->flags |= TF_IP_DONTROUTE;
	}
	if (!flags || opt->bcast) {
		opt->bcast = &opt_defaults.bcast;
		opt->flags |= TF_IP_BROADCAST;
	}
	if (ss->p.prot.protocol == T_INET_TCP) {
		if (!flags || opt->nodly) {
			opt->nodly = &opt_defaults.nodly;
			opt->flags |= TF_TCP_NODELAY;
		}
		if (!flags || opt->mss) {
			opt->mss = &opt_defaults.mss;
			opt->flags |= TF_TCP_MAXSEG;
		}
		if (!flags || opt->alive) {
			opt->alive = &opt_defaults.alive;
			opt->flags |= TF_TCP_KEEPALIVE;
		}
	}
	if (ss->p.prot.protocol == T_INET_UDP) {
		if (!flags || opt->csum) {
			opt->csum = &opt_defaults.csum;
			opt->flags |= TF_UDP_CHECKSUM;
		}
	}
	return (0);
}

/*
 *  NEGOTIATE Options
 *  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
STATIC int ss_opt_negotiate(ss_t * ss, ss_opts_t * opt)
{
	struct sock *sk = NULL;
	if (ss->sock)
		sk = ss->sock->sk;
	if (opt) {
		opt->flags = 0;
		if (opt->opt) {
			ss->options.opt = *(opt->opt);
#ifdef LINUX_2_4
			if (sk)
				sk->protinfo.af_inet.opt = &ss->options.opt;
#else
			if (sk)
				sk->opt = &ss->options.opt;
#endif
			opt->flags |= TF_IP_OPTIONS;
		}
		if (opt->tos) {
			ss->options.tos = *(opt->tos);
#ifdef LINUX_2_4
			if (sk)
				sk->protinfo.af_inet.tos = ss->options.tos;
#else
			if (sk)
				sk->ip_tos = ss->options.tos;
#endif
			opt->flags |= TF_IP_TOS;
		}
		if (opt->ttl) {
			ss->options.ttl = *(opt->ttl);
#ifdef LINUX_2_4
			if (sk)
				sk->protinfo.af_inet.ttl = ss->options.ttl;
#else
			if (sk)
				sk->ip_ttl = ss->options.ttl;
#endif
			opt->flags |= TF_IP_TTL;
		}
		if (opt->reuse) {
			ss->options.reuse = *(opt->reuse) ? 1 : 0;
			if (sk)
				sk->reuse = ss->options.reuse;
			opt->flags |= TF_IP_REUSEADDR;
		}
		if (opt->norte) {
			ss->options.norte = *(opt->norte) ? 1 : 0;
			if (sk)
				sk->localroute = ss->options.norte;
			opt->flags |= TF_IP_DONTROUTE;
		}
		if (opt->bcast) {
			ss->options.bcast = *(opt->bcast) ? 1 : 0;
			if (sk)
				sk->broadcast = ss->options.bcast;
			opt->flags |= TF_IP_BROADCAST;
		}
		if (opt->nodly) {
			ss->options.nodly = *(opt->nodly) ? 1 : 0;
#ifdef LINUX_2_4
			if (sk)
				sk->tp_pinfo.af_tcp.nonagle = ss->options.nodly;
#else
			if (sk)
				sk->nonagle = ss->options.nodly;
#endif
			opt->flags |= TF_TCP_NODELAY;
		}
		if (opt->mss) {
			ss->options.mss = *(opt->mss) ? 1 : 0;
			if (sk)
				sk->tp_pinfo.af_tcp.user_mss = ss->options.mss;
			opt->flags |= TF_TCP_MAXSEG;
		}
		if (opt->alive) {
			ss->options.alive = *(opt->alive) ? 1 : 0;
			if (sk)
				sk->keepopen = ss->options.alive;
			opt->flags |= TF_TCP_KEEPALIVE;
		}
		if (opt->csum) {
			ss->options.csum = *(opt->csum) ? 1 : 0;
			// if ( sk ) sk->csum = ss->options.csum;
			opt->flags |= TF_UDP_CHECKSUM;
		}
	}
	return (0);
}

/*
 *  CURRENT Options
 *  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
STATIC int ss_opt_current(ss_t * ss, ss_opts_t * opt)
{
	uint flags = opt->flags;
	opt->flags = 0;
	if (!flags || opt->opt) {
		opt->opt = &ss->options.opt;
		opt->flags |= TF_IP_OPTIONS;
	}
	if (!flags || opt->tos) {
		opt->tos = &ss->options.tos;
		opt->flags |= TF_IP_TOS;
	}
	if (!flags || opt->ttl) {
		opt->ttl = &ss->options.ttl;
		opt->flags |= TF_IP_TTL;
	}
	if (!flags || opt->reuse) {
		opt->reuse = &ss->options.reuse;
		opt->flags |= TF_IP_REUSEADDR;
	}
	if (!flags || opt->norte) {
		opt->norte = &ss->options.norte;
		opt->flags |= TF_IP_DONTROUTE;
	}
	if (!flags || opt->bcast) {
		opt->bcast = &ss->options.bcast;
		opt->flags |= TF_IP_BROADCAST;
	}
	if (ss->p.prot.protocol == T_INET_TCP) {
		if (!flags || opt->nodly) {
			opt->nodly = &ss->options.nodly;
			opt->flags |= TF_TCP_NODELAY;
		}
		if (!flags || opt->mss) {
			opt->mss = &ss->options.mss;
			opt->flags |= TF_TCP_MAXSEG;
		}
		if (!flags || opt->alive) {
			opt->alive = &ss->options.alive;
			opt->flags |= TF_TCP_KEEPALIVE;
		}
	}
	if (ss->p.prot.protocol == T_INET_UDP) {
		if (!flags || opt->csum) {
			opt->csum = &ss->options.csum;
			opt->flags |= TF_UDP_CHECKSUM;
		}
	}
	return (0);
}

/*
 *  =========================================================================
 *
 *  STATE Changes
 *
 *  =========================================================================
 */
#ifdef _DEBUG
STATIC const char *state_name(long state)
{
	switch (state) {
	case TS_UNBND:
		return ("TS_UNBND");
	case TS_WACK_BREQ:
		return ("TS_WACK_BREQ");
	case TS_WACK_UREQ:
		return ("TS_WACK_UREQ");
	case TS_IDLE:
		return ("TS_IDLE");
	case TS_WACK_OPTREQ:
		return ("TS_WACK_OPTREQ");
	case TS_WACK_CREQ:
		return ("TS_WACK_CREQ");
	case TS_WCON_CREQ:
		return ("TS_WCON_CREQ");
	case TS_WRES_CIND:
		return ("TS_WRES_CIND");
	case TS_WACK_CRES:
		return ("TS_WACK_CRES");
	case TS_DATA_XFER:
		return ("TS_DATA_XFER");
	case TS_WIND_ORDREL:
		return ("TS_WIND_ORDREL");
	case TS_WREQ_ORDREL:
		return ("TS_WREQ_ORDREL");
	case TS_WACK_DREQ6:
		return ("TS_WACK_DREQ6");
	case TS_WACK_DREQ7:
		return ("TS_WACK_DREQ7");
	case TS_WACK_DREQ9:
		return ("TS_WACK_DREQ9");
	case TS_WACK_DREQ10:
		return ("TS_WACK_DREQ10");
	case TS_WACK_DREQ11:
		return ("TS_WACK_DREQ11");
	case TS_NOSTATES:
		return ("TS_NOSTATES");
	default:
		return ("(unknown)");
	}
}
#endif
STATIC void ss_set_state(ss_t * ss, long state)
{
	printd(("%s: %p: %s <- %s\n", SS_MOD_NAME, ss, state_name(state),
		state_name(ss->p.info.CURRENT_state)));
	ss->p.info.CURRENT_state = state;
}
STATIC long ss_get_state(ss_t * ss)
{
	return (ss->p.info.CURRENT_state);
}

#ifdef _DEBUG
STATIC const char *tcp_state_name(int state)
{
	switch (state) {
	case TCP_ESTABLISHED:
		return ("TCP_ESTABLISHED");
	case TCP_SYN_SENT:
		return ("TCP_SYN_SENT");
	case TCP_SYN_RECV:
		return ("TCP_SYN_RECV");
	case TCP_FIN_WAIT1:
		return ("TCP_FIN_WAIT1");
	case TCP_FIN_WAIT2:
		return ("TCP_FIN_WAIT2");
	case TCP_TIME_WAIT:
		return ("TCP_TIME_WAIT");
	case TCP_CLOSE:
		return ("TCP_CLOSE");
	case TCP_CLOSE_WAIT:
		return ("TCP_CLOSE_WAIT");
	case TCP_LAST_ACK:
		return ("TCP_LAST_ACK");
	case TCP_LISTEN:
		return ("TCP_LISTEN");
	case TCP_CLOSING:
		return ("TCP_CLOSING");
	case TCP_MAX_STATES:
		return ("TCP_MAX_STATES");
	default:
		return ("(unknown)");
	}
}
#endif

/*
 *  ------------------------------------------------------------------------
 *
 *  Socket Calls
 *
 *  ------------------------------------------------------------------------
 *  These are wrappered versions of socket calls.
 */
/*
 *  SOCKET CREATE
 *  ------------------------------------------------------------------------
 */
STATIC int ss_socket(ss_t * ss)
{
	int err;
	int family, type, protocol;
	ensure(ss, return (-EFAULT));
	unless(ss->sock, return (-EFAULT));
	family = ss->p.prot.family;
	type = ss->p.prot.type;
	protocol = (ss->p.prot.protocol == IPPROTO_RAW) ? ss->port : ss->p.prot.protocol;
	printd(("%s: %p: SS_CREATE %d:%d:%d\n", SS_MOD_NAME, ss, family, type, protocol));
	if (!(err = sock_create(family, type, protocol, &ss->sock))) {
		ensure(ss->sock, return (-EFAULT));
		ensure(ss->sock->sk, return (-EFAULT));
		ss->sock->sk->allocation = GFP_ATOMIC;
		ss_socket_get(ss->sock, ss);
		return (0);
	}
	printd(("%s: %p: ERROR: from sock_create %d\n", SS_MOD_NAME, ss, err));
	return (err);
}

/*
 *  SOCKET BIND
 *  ------------------------------------------------------------------------
 */
STATIC int ss_bind(ss_t * ss, ss_addr_t * add)
{
	int err;
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->bind, return (-EFAULT));
	printd(("%s: %p: SS_BIND\n", SS_MOD_NAME, ss));
	if (!(err = ss->sock->ops->bind(ss->sock, (struct sockaddr *) add, sizeof(*add)))) {
		ss->src = *add;
		return (0);
	}
	printd(("%s: %p: ERROR: from sock->ops->bind %d\n", SS_MOD_NAME, ss, err));
	return (err);
}

/*
 *  SOCKET LISTEN
 *  ------------------------------------------------------------------------
 */
STATIC int ss_listen(ss_t * ss, uint cons)
{
	int err;
	int type;
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->listen, return (-EFAULT));
	type = ss->p.prot.type;
	ensure(type == SOCK_STREAM || type == SOCK_SEQPACKET, return (-EFAULT));
	printd(("%s: %p: SS_LISTEN %d\n", SS_MOD_NAME, ss, cons));
	if (!(err = ss->sock->ops->listen(ss->sock, cons))) {
		ss->conind = cons;
		ss->tcp_state = ss->sock->sk->state;
		return (0);
	}
	printd(("%s: %p: ERROR: from sock->ops->listen %d\n", SS_MOD_NAME, ss, err));
	return (err);
}

/*
 *  SOCKET ACCEPT
 *  ------------------------------------------------------------------------
 *  Unfortunately, sock->ops->accept will only accept the next sequential
 *  connection indication.  In TLI's case, we want to be able to accept or
 *  release connection indications other than the next sequential indication.
 *  To do this we must muck with TCP's accept queue when the SEQ_number is not
 *  the next in the queue.  To do this we mimic some of the tcp_accept
 *  behavior.
 */
STATIC int ss_accept(ss_t * ss, struct socket **newsock, mblk_t * cp)
{
	struct socket *sock;
	ensure(newsock, return (-EFAULT));
	ensure(cp, return (-EFAULT));
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->accept, return (-EFAULT));
	printd(("%s: %p: SS_ACCEPT\n", SS_MOD_NAME, ss));
	if ((sock = sock_alloc())) {
		struct sock *sk = ss->sock->sk;
		struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
		struct open_request *req, *req_prev, **reqp;
		struct sock *ask = ((ss_event_t *) cp->b_rptr)->sk;
		sock->type = ss->sock->type;
		sock->ops = ss->sock->ops;
		lock_sock(sk);
		if (tp->accept_queue) {
			/* find connection in queue */
			for (reqp = &tp->accept_queue, req_prev = NULL;
			     *reqp && (*reqp)->sk != ask;
			     req_prev = (*reqp), reqp = &(*reqp)->dl_next);
			if ((req = *reqp)) {
				if (!((*reqp) = (*reqp)->dl_next))
					tp->accept_queue_tail = req_prev;
				sk->ack_backlog--;
				tcp_openreq_fastfree(req);
			}
			release_sock(sk);
			lock_sock(ask);
			sock_graft(ask, sock);
			release_sock(ask);
			sock->state = SS_CONNECTED;
			*newsock = sock;
			bufq_unlink(&ss->conq, cp);
			freemsg(cp);
			return (0);
		}
		release_sock(sk);
		ss_socket_put(sock);
		printd(("%s: %p: invalid accept\n", SS_MOD_NAME, ss));
		return (-EAGAIN);
	}
	printd(("%s: %p: ERROR: couldn't allocate accepting socket\n", SS_MOD_NAME, ss));
	return (-EFAULT);
}

/*
 *  SOCKET UNBIND
 *  ------------------------------------------------------------------------
 *  There is no good way to unbind and rebind a socket in Linux, so we just
 *  close the entire socket.  The next time we go to bind, we will create
 *  a fresh socket to bind.
 */
STATIC int ss_unbind(ss_t * ss)
{
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	printd(("%s: %p: SS_UNBIND\n", SS_MOD_NAME, ss));
	ss_socket_put(xchg(&ss->sock, NULL));
	return (0);
}

/*
 *  SOCKET CONNECT
 *  ------------------------------------------------------------------------
 */
STATIC int ss_connect(ss_t * ss, ss_addr_t * dst)
{
	int err;
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->connect, return (-EFAULT));
	if ((err = ss->sock->ops->connect(ss->sock, (struct sockaddr *) dst, sizeof(*dst),
					  O_NONBLOCK)) == 0 || err == -EINPROGRESS) {
		return (0);
	}
	printd(("%s: %p: ERROR: from sock->ops->connect %d\n", SS_MOD_NAME, ss, err));
	return (err);
}

/*
 *  SOCKET SENDMSG
 *  ------------------------------------------------------------------------
 */
STATIC int ss_sendmsg(ss_t * ss, struct msghdr *msg, int len)
{
	int res;
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->sendmsg, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->sk->prot, return (-EFAULT));
	ensure(ss->sock->sk->prot->sendmsg, return (-EFAULT));
	{
		mm_segment_t fs = get_fs();
		set_fs(KERNEL_DS);
		res = sock_sendmsg(ss->sock, msg, len);
		set_fs(fs);
	}
	if (res <= 0)
		printd(("%s: %p: ERROR: from sock->sk->prot->sendmsg %d\n", SS_MOD_NAME, ss, res));
	return (res);
}

/*
 *  SOCKET RECVMSG
 *  ------------------------------------------------------------------------
 */
STATIC int ss_recvmsg(ss_t * ss, struct msghdr *msg, int size)
{
	int res;
	int sflags = MSG_DONTWAIT | MSG_NOSIGNAL;
	ensure(ss, return (-EFAULT));
	ensure(ss->sock, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->recvmsg, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->sk->prot, return (-EFAULT));
	ensure(ss->sock->sk->prot->recvmsg, return (-EFAULT));
	{
		mm_segment_t fs = get_fs();
		set_fs(KERNEL_DS);
		res = sock_recvmsg(ss->sock, msg, size, sflags);
		set_fs(fs);
	}
	if (res < 0)
		printd(("%s: %p: ERROR: from sock->ops->recvmsg %d\n", SS_MOD_NAME, ss, res));
	return (res);
}

/*
 *  SOCKET DISCONNECT
 *  ------------------------------------------------------------------------
 *  Performing a sock_release (ss_socket_put) from the established state does
 *  not affect an abortive release for TCP, but rather, initiates an orderly
 *  shutdown rather than an abortive release.  We can try performing a
 *  protocol disconnect and see if that works better.
 */
STATIC int ss_disconnect(ss_t * ss)
{
	int err;
	ensure(ss, return (-EFAULT));
	ensure(ss->sock->ops, return (-EFAULT));
	ensure(ss->sock->ops->connect, return (-EFAULT));
	ensure(ss->sock->sk, return (-EFAULT));
	ensure(ss->sock->sk->prot, return (-EFAULT));
	ensure(ss->sock->sk->prot->disconnect, return (-EFAULT));
	if (!(err = ss->sock->sk->prot->disconnect(ss->sock->sk, O_NONBLOCK))) {
		ss->sock->state = SS_UNCONNECTED;
		return (0);
	}
	ss->sock->state = SS_DISCONNECTING;
	printd(("%s: %p: ERROR: from sock->sk->prot->disconnect %d\n", SS_MOD_NAME, ss, err));
	return (err);
}

/*
 *  =========================================================================
 *
 *  IP T-Provider --> T-User Primitives (Indication, Confirmation and Ack)
 *
 *  =========================================================================
 */
/*
 *  M_ERROR
 *  ---------------------------------------------------------------
 */
STATIC int m_error(queue_t * q, int error)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	int hangup = 0;
	if (error < 0)
		error = -error;
	switch (error) {
	case EBUSY:
	case ENOBUFS:
	case EAGAIN:
	case ENOMEM:
		return (-error);
	case EPIPE:
	case ENETDOWN:
	case EHOSTUNREACH:
		hangup = 1;
	}
	if ((mp = ss_allocb(q, 2, BPRI_HI))) {
		if (ss->sock)
			ss_socket_put(xchg(&ss->sock, NULL));
		if (hangup) {
			printd(("%s: %p: <- M_HANGUP\n", SS_MOD_NAME, ss));
			mp->b_datap->db_type = M_HANGUP;
			putnext(ss->rq, mp);
			error = EPIPE;
		} else {
			printd(("%s: %p: <- M_ERROR %d\n", SS_MOD_NAME, ss, error));
			mp->b_datap->db_type = M_ERROR;
			*(mp->b_wptr)++ = error;
			*(mp->b_wptr)++ = error;
			putnext(ss->rq, mp);
		}
		return (-error);
	}
	rare();
	return (-ENOBUFS);
}

/*
 *  T_CONN_IND          11 - Connection Indication
 *  ---------------------------------------------------------------
 */
STATIC int t_conn_ind(queue_t * q, ss_addr_t * src, ss_opts_t * opts, mblk_t * cp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_conn_ind *p;
	size_t src_len = src ? sizeof(*src) : 0;
	size_t opt_len = ss_opts_size(opts);
	if (bufq_length(&ss->conq) <= ss->conind) {
		if (canputnext(ss->rq)) {
			if ((mp = ss_allocb(q, sizeof(*p) + src_len + opt_len, BPRI_MED))) {
				mp->b_datap->db_type = M_PROTO;
				p = ((typeof(p)) mp->b_wptr)++;
				p->PRIM_type = T_CONN_IND;
				p->SRC_length = src_len;
				p->SRC_offset = src_len ? sizeof(*p) : 0;
				p->OPT_length = opt_len;
				p->OPT_offset = opt_len ? sizeof(*p) + src_len : 0;
				p->SEQ_number = (ulong) cp;
				if (src_len) {
					bcopy(src, mp->b_wptr, src_len);
					mp->b_wptr += src_len;
				}
				if (opt_len) {
					ss_build_opts(opts, &mp->b_wptr);
				}
				bufq_queue(&ss->conq, cp);
				ss_set_state(ss, TS_WRES_CIND);
				printd(("%s: %p: <- T_CONN_IND\n", SS_MOD_NAME, ss));
				putnext(ss->rq, mp);
				return (QR_ABSORBED);	/* absorbed cp */
			}
			ptrace(("%s: ERROR: no buffers\n", SS_MOD_NAME));
			return (-ENOBUFS);
		}
		ptrace(("%s: ERROR: flow controlled\n", SS_MOD_NAME));
		return (-EBUSY);
	}
	ptrace(("%s: ERROR: too many conn inds\n", SS_MOD_NAME));
	return (-EAGAIN);
}

/*
 *  T_CONN_CON          12 - Connection Confirmation
 *  ---------------------------------------------------------------
 */
STATIC int t_conn_con(queue_t * q, ss_addr_t * res, ss_opts_t * opts, mblk_t * dp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_conn_con *p;
	size_t res_len = res ? sizeof(*res) : 0;
	size_t opt_len = ss_opts_size(opts);
	/* 
	 * this shouldn't happen, we probably shouldn't even check 
	 */
	if (canputnext(ss->rq)) {
		if ((mp = ss_allocb(q, sizeof(*p) + res_len + opt_len, BPRI_MED))) {
			mp->b_datap->db_type = M_PROTO;
			mp->b_band = 1;	/* expedite */
			p = ((typeof(p)) mp->b_wptr)++;
			p->PRIM_type = T_CONN_CON;
			p->RES_length = res_len;
			p->RES_offset = res_len ? sizeof(*p) : 0;
			p->OPT_length = opt_len;
			p->OPT_offset = opt_len ? sizeof(*p) + res_len : 0;
			if (res_len) {
				bcopy(res, mp->b_wptr, res_len);
				mp->b_wptr += res_len;
			}
			if (opt_len) {
				ss_build_opts(opts, &mp->b_wptr);
			}
			ss_set_state(ss, TS_DATA_XFER);
			printd(("%s: %p: <- T_CONN_CON\n", SS_MOD_NAME, ss));
			putnext(ss->rq, mp);
			return (0);
		}
		ptrace(("%s: ERROR: no buffers\n", SS_MOD_NAME));
		return (-ENOBUFS);
	}
	ptrace(("%s: ERROR: flow controlled\n", SS_MOD_NAME));
	return (-EBUSY);
}

/*
 *  T_DISCON_IND        13 - Disconnect Indication
 *  ---------------------------------------------------------------
 */
STATIC mblk_t *t_seq_find(ss_t * ss, mblk_t * rp)
{
	mblk_t *mp;
	if ((mp = rp)) {
		struct sock *sk = ((ss_event_t *) rp->b_rptr)->sk;
		lis_spin_lock(&ss->conq.q_lock);
		{
			for (mp = bufq_head(&ss->conq); mp && ((ss_event_t *) mp->b_rptr)->sk != sk;
			     mp = mp->b_next);
		}
		lis_spin_unlock(&ss->conq.q_lock);
	}
	return (mp);
}
STATIC ulong t_seq_delete(ss_t * ss, mblk_t * rp)
{
	mblk_t *mp;
	if ((mp = t_seq_find(ss, rp))) {
		struct socket *sock = NULL;
		if (!ss_accept(ss, &sock, rp) && sock)
			sock_release(sock);
		return ((ulong) mp);
	}
	return (0);
}
STATIC int
t_discon_ind(queue_t * q, ss_addr_t * res, uint orig, uint reason, mblk_t * cp, mblk_t * dp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_discon_ind *p;
	ulong seq = 0;
	(void) res;
	if (canputnext(ss->rq)) {
		if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
			if (!cp || (seq = t_seq_delete(ss, cp))) {
				mp->b_datap->db_type = M_PROTO;
				p = ((typeof(p)) mp->b_wptr)++;
				p->PRIM_type = T_DISCON_IND;
				p->DISCON_reason = reason;
				p->SEQ_number = seq;
				if (!bufq_length(&ss->conq))
					ss_set_state(ss, TS_IDLE);
				else
					ss_set_state(ss, TS_WRES_CIND);
				mp->b_cont = dp;
				printd(("%s: %p: <- T_DISCON_IND\n", SS_MOD_NAME, ss));
				putnext(ss->rq, mp);
				return (0);
			}
			freemsg(mp);
			ptrace(("%s: ERROR: bad sequence number\n", SS_MOD_NAME));
			return (-EFAULT);
		}
		ptrace(("%s: ERROR: no buffers\n", SS_MOD_NAME));
		return (-ENOBUFS);
	}
	ptrace(("%s: ERROR: flow controlled\n", SS_MOD_NAME));
	return (-EBUSY);
}

/*
 *  T_DATA_IND          14 - Data Indication
 *  ---------------------------------------------------------------
 */
STATIC int t_data_ind(queue_t * q, uint flags, mblk_t * dp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_data_ind *p;
	if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_DATA_IND;
		p->MORE_flag = flags;
		mp->b_cont = dp;
		printd(("%s: %p: <- T_DATA_IND\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (QR_ABSORBED);
	}
	ptrace(("%s: ERROR: no buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_EXDATA_IND        15 - Expedited Data Indication
 *  ---------------------------------------------------------------
 */
STATIC int t_exdata_ind(queue_t * q, uint flags, mblk_t * dp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_exdata_ind *p;
	if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		mp->b_band = 1;	/* expedite */
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_EXDATA_IND;
		p->MORE_flag = flags;
		mp->b_cont = dp;
		printd(("%s: %p: <- T_EXDATA_IND\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (QR_ABSORBED);
	}
	ptrace(("%s: ERROR: no buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_INFO_ACK          16 - Information acknowledgement
 *  ---------------------------------------------------------------
 */
STATIC int t_info_ack(queue_t * q)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_info_ack *p;
	if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		*p = ss->p.info;
		printd(("%s: %p: <- T_INFO_ACK\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (0);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_BIND_ACK          17 - Bind Acknowledgement
 *  ---------------------------------------------------------------
 */
STATIC int t_bind_ack(queue_t * q, ss_addr_t * add)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_bind_ack *p;
	size_t add_len = add ? sizeof(*add) : 0;
	if ((mp = ss_allocb(q, sizeof(*p) + add_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_BIND_ACK;
		p->ADDR_length = add_len;
		p->ADDR_offset = add_len ? sizeof(*p) : 0;
		p->CONIND_number = 0;
		if (add_len) {
			bcopy(add, mp->b_wptr, add_len);
			mp->b_wptr += add_len;
		}
		ss_set_state(ss, TS_IDLE);
		printd(("%s: %p: <- T_BIND_ACK\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (0);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_ERROR_ACK         18 - Error Acknowledgement
 *  -------------------------------------------------------------------------
 */
STATIC int t_error_ack(queue_t * q, ulong prim, long error)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_error_ack *p;
	switch (error) {
	case -EBUSY:
	case -EAGAIN:
	case -ENOMEM:
	case -ENOBUFS:
		seldom();
		return (error);
	case 0:
		never();
		return (error);
	}
	if (!(mp = ss_allocb(q, sizeof(*p), BPRI_MED)))
		goto enobufs;
	mp->b_datap->db_type = M_PCPROTO;
	p = ((typeof(p)) mp->b_wptr)++;
	p->PRIM_type = T_ERROR_ACK;
	p->ERROR_prim = prim;
	p->TLI_error = error < 0 ? TSYSERR : error;
	p->UNIX_error = error < 0 ? -error : 0;
	/* 
	 *  This is to only try and get the state correct for putnext.
	 */
	if (error != TOUTSTATE) {
		switch (ss_get_state(ss)) {
#ifdef TS_WACK_OPTREQ
		case TS_WACK_OPTREQ:
			ss_set_state(ss, TS_IDLE);
			break;
#endif
		case TS_WACK_UREQ:
			ss_set_state(ss, TS_IDLE);
			break;
		case TS_WACK_CREQ:
			ss_set_state(ss, TS_IDLE);
			break;
		case TS_WACK_BREQ:
			ss_set_state(ss, TS_UNBND);
			break;
		case TS_WACK_CRES:
			ss_set_state(ss, TS_WRES_CIND);
			break;
		case TS_WACK_DREQ6:
			ss_set_state(ss, TS_WCON_CREQ);
			break;
		case TS_WACK_DREQ7:
			ss_set_state(ss, TS_WRES_CIND);
			break;
		case TS_WACK_DREQ9:
			ss_set_state(ss, TS_DATA_XFER);
			break;
		case TS_WACK_DREQ10:
			ss_set_state(ss, TS_WIND_ORDREL);
			break;
		case TS_WACK_DREQ11:
			ss_set_state(ss, TS_WREQ_ORDREL);
			break;
			/* 
			 *  Note: if we are not in a WACK state we simply do
			 *  not change state.  This occurs normally when we
			 *  are responding to a T_OPTMGMT_REQ in other than
			 *  TS_IDLE state.
			 */
		}
	}
	printd(("%s: %p: <- T_ERROR_ACK\n", SS_MOD_NAME, ss));
	putnext(ss->rq, mp);
	/* 
	 *  Returning -EPROTO here will make sure that the old state is
	 *  restored correctly.  If we return QR_DONE, then the state will
	 *  never be restored.
	 */
	if (error < 0)
		return (error);
	return (-EPROTO);
      enobufs:
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_OK_ACK            19 - Success Acknowledgement
 *  -------------------------------------------------------------------------
 */
STATIC int t_ok_ack(queue_t * q, ulong prim, mblk_t * cp, ss_t * as)
{
	int err = -EFAULT;
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_ok_ack *p;
	struct socket *sock = NULL;
	if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_OK_ACK;
		p->CORRECT_prim = prim;
		switch (ss_get_state(ss)) {
		case TS_WACK_CREQ:
			if ((err = ss_connect(ss, &ss->dst)))
				goto free_error;
			ss_set_state(ss, TS_WCON_CREQ);
			break;
		case TS_WACK_UREQ:
			if ((err = ss_unbind(ss)))
				goto free_error;
			ss_set_state(ss, TS_UNBND);
			break;
		case TS_WACK_CRES:
			ensure(as && cp, goto free_error);
			if ((err = ss_accept(ss, &sock, cp)))
				goto free_error;
			if (as->sock)
				ss_socket_put(as->sock);	/* get rid of old socket */
			as->sock = sock;
			ss_socket_get(as->sock, as);
			ss_set_state(as, TS_DATA_XFER);
			if (bufq_length(&ss->conq))
				ss_set_state(ss, TS_WRES_CIND);
			else {
				ss_set_state(ss, TS_IDLE);
				/* make sure any backed up indications are processed */
				qenable(ss->rq);
			}
			break;
		case TS_WACK_DREQ7:
			ensure(cp, goto free_error);
			if (!(err = ss_accept(ss, &sock, cp)))
				sock_release(sock);
			if (bufq_length(&ss->conq))
				ss_set_state(ss, TS_WRES_CIND);
			else {
				ss_set_state(ss, TS_IDLE);
				/* make sure any backed up indications are processed */
				qenable(ss->rq);
			}
			break;
		case TS_WACK_DREQ6:
		case TS_WACK_DREQ9:
		case TS_WACK_DREQ10:
		case TS_WACK_DREQ11:
			if ((err = ss_disconnect(ss)))
				goto free_error;
			ss_set_state(ss, TS_IDLE);
			break;
		default:
			break;
			/* 
			 *  Note: if we are not in a WACK state we simply do
			 *  not change state.  This occurs normally when we
			 *  are responding to a T_OPTMGMT_REQ in other than
			 *  the TS_IDLE state.
			 */
		}
		printd(("%s: %p: <- T_OK_ACK\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (QR_DONE);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
      free_error:
	freemsg(mp);
	return t_error_ack(q, prim, err);
}

/*
 *  T_UNIDATA_IND       20 - Unitdata indication
 *  -------------------------------------------------------------------------
 */
STATIC int t_unitdata_ind(queue_t * q, ss_addr_t * src, ss_opts_t * opts, mblk_t * dp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_unitdata_ind *p;
	size_t src_len = src ? sizeof(*src) : 0;
	size_t opt_len = ss_opts_size(opts);
	if ((mp = ss_allocb(q, sizeof(*p) + src_len + opt_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_UNITDATA_IND;
		p->SRC_length = src_len;
		p->SRC_offset = src_len ? sizeof(*p) : 0;
		p->OPT_length = opt_len;
		p->OPT_offset = opt_len ? sizeof(*p) + src_len : 0;
		if (src_len) {
			bcopy(src, mp->b_wptr, src_len);
			mp->b_wptr += src_len;
		}
		if (opt_len) {
			ss_build_opts(opts, &mp->b_wptr);
		}
		mp->b_cont = dp;
		printd(("%s: %p: <- T_UNITDATA_IND\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (QR_ABSORBED);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_UDERROR_IND       21 - Unitdata Error Indication
 *  -------------------------------------------------------------------------
 *  This primitive indicates to the transport user that a datagram with the
 *  specified destination address and options produced an error.
 */
STATIC INLINE int t_uderror_ind(queue_t * q, ss_addr_t * src, ss_addr_t * dst,
				ss_opts_t * opts, mblk_t * dp, int etype)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_uderror_ind *p;
	size_t dst_len = dst ? sizeof(*dst) : 0;
	size_t opt_len = ss_opts_size(opts);
	(void) src;
	if (canputnext(ss->rq)) {
		if ((mp = ss_allocb(q, sizeof(*p) + dst_len + opt_len, BPRI_MED))) {
			mp->b_datap->db_type = M_PROTO;
			mp->b_band = 2;	/* XXX move ahead of data indications */
			p = ((typeof(p)) mp->b_wptr)++;
			p->PRIM_type = T_UDERROR_IND;
			p->DEST_length = dst_len;
			p->DEST_offset = dst_len ? sizeof(*p) : 0;
			p->OPT_length = opt_len;
			p->OPT_offset = opt_len ? sizeof(*p) + dst_len : 0;
			p->ERROR_type = etype;
			if (dst_len) {
				bcopy(dst, mp->b_wptr, dst_len);
				mp->b_wptr += dst_len;
			}
			if (opt_len) {
				ss_build_opts(opts, &mp->b_wptr);
			}
			printd(("%s: %p: <- T_UDERROR_IND\n", SS_MOD_NAME, ss));
			putnext(ss->rq, mp);
			return (0);
		}
		ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
		return (-ENOBUFS);
	}
	ptrace(("%s: ERROR: Flow controlled\n", SS_MOD_NAME));
	return (-EBUSY);
}

/*
 *  T_OPTMGMT_ACK       22 - Options Management Acknowledge
 *  -------------------------------------------------------------------------
 */
STATIC int t_optmgmt_ack(queue_t * q, ulong flags, ss_opts_t * opts)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_optmgmt_ack *p;
	size_t opt_len = ss_opts_size(opts);
	if ((mp = ss_allocb(q, sizeof(*p) + opt_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_OPTMGMT_ACK;
		p->OPT_length = opt_len;
		p->OPT_offset = opt_len ? sizeof(*p) : 0;
		p->MGMT_flags = flags;
		if (opt_len) {
			ss_build_opts(opts, &mp->b_wptr);
		}
#ifdef TS_WACK_OPTREQ
		if (ss_get_state(ss) == TS_WACK_OPTREQ)
			ss_set_state(ss, TS_IDLE);
#endif
		printd(("%s: %p: <- T_OPTMGMT_ACK\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (0);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

/*
 *  T_ORDREL_IND        23 - Orderly Release Indication
 *  -------------------------------------------------------------------------
 */
STATIC int t_ordrel_ind(queue_t * q)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_ordrel_ind *p;
	if (canputnext(ss->rq)) {
		if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
			mp->b_datap->db_type = M_PROTO;
			p = ((typeof(p)) mp->b_wptr)++;
			p->PRIM_type = T_ORDREL_IND;
			switch (ss_get_state(ss)) {
			case TS_DATA_XFER:
				ss_set_state(ss, TS_WREQ_ORDREL);
				break;
			case TS_WIND_ORDREL:
				ss_set_state(ss, TS_IDLE);
				break;
			}
			printd(("%s: %p: <- T_ORDREL_IND\n", SS_MOD_NAME, ss));
			putnext(ss->rq, mp);
			return (0);
		}
		ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
		return (-ENOBUFS);
	}
	ptrace(("%s: ERROR: Flow controlled\n", SS_MOD_NAME));
	return (-EBUSY);
}

/*
 *  T_OPTDATA_IND       26 - Data with Options Indication
 *  -------------------------------------------------------------------------
 */
STATIC int t_optdata_ind(queue_t * q, uint flags, ss_opts_t * opts, mblk_t * dp)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_optdata_ind *p;
	size_t opt_len = ss_opts_size(opts);
	if ((mp = ss_allocb(q, sizeof(*p) + opt_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PROTO;
		mp->b_band = flags & T_ODF_EX ? 1 : 0;	/* expedite */
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_OPTDATA_IND;
		p->DATA_flag = flags & (T_ODF_MORE | T_ODF_EX);
		p->OPT_length = opt_len;
		p->OPT_offset = opt_len ? sizeof(*p) : 0;
		if (opt_len) {
			ss_build_opts(opts, &mp->b_wptr);
		}
		mp->b_cont = dp;
		printd(("%s: %p: <- T_OPTDATA_IND\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (QR_ABSORBED);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}

#ifdef T_ADDR_ACK
/*
 *  T_ADDR_ACK          27 - Address Acknowledgement
 *  -------------------------------------------------------------------------
 */
STATIC int t_addr_ack(queue_t * q, ss_addr_t * loc, ss_addr_t * rem)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_addr_ack *p;
	size_t loc_len = loc ? sizeof(*loc) : 0;
	size_t rem_len = rem ? sizeof(*rem) : 0;
	if ((mp = ss_allocb(q, sizeof(*p) + loc_len + rem_len, BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_ADDR_ACK;
		p->LOCADDR_length = loc_len;
		p->LOCADDR_offset = loc_len ? sizeof(*p) : 0;
		p->REMADDR_length = rem_len;
		p->REMADDR_offset = rem_len ? sizeof(*p) + loc_len : 0;
		if (loc_len) {
			bcopy(loc, mp->b_wptr, loc_len);
			mp->b_wptr += loc_len;
		}
		if (rem_len) {
			bcopy(rem, mp->b_wptr, rem_len);
			mp->b_wptr += rem_len;
		}
		printd(("%s: %p: <- T_ADDR_ACK\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (0);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}
#endif
#ifdef T_CAPABILITY_ACK
/*
 *  T_CAPABILITY_ACK    ?? - Protocol Capability Ack
 *  -------------------------------------------------------------------------
 */
STATIC int t_capability_ack(queue_t * q, ulong caps)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	struct T_capability_ack *p;
	if ((mp = ss_allocb(q, sizeof(*p), BPRI_MED))) {
		mp->b_datap->db_type = M_PCPROTO;
		p = ((typeof(p)) mp->b_wptr)++;
		p->PRIM_type = T_CAPABILITY_ACK;
		p->CAP_bits1 = TC1_INFO;
		p->ACCPETOR_id = (caps & TC1_ACCEPTOR) ? (ulong) ss->rq : 0;
		if (caps & TC1_INFO)
			p->INFO_ack = ss->p.info;
		else
			bzero(&p->INFO_ack, sizeof(p->INFO_ack));
		printd(("%s: %p: <- T_CAPABILITY_ACK\n", SS_MOD_NAME, ss));
		putnext(ss->rq, mp);
		return (0);
	}
	ptrace(("%s: ERROR: No buffers\n", SS_MOD_NAME));
	return (-ENOBUFS);
}
#endif

/*
 *  =========================================================================
 *
 *  CONNECTION INNDICATIONS
 *
 *  =========================================================================
 */
/*
 *  CONIND
 *  -------------------------------------------------------------------------
 *  An unfortunate mis-match between the socket model and the TLI model is
 *  that in TLI it is possible to determine the remote address from a
 *  T_CONN_IND *before* accepting the connection.  Under sockets, the
 *  connection must be accepted before it is possible to determine the peer's
 *  address.  This means that connection must be accepted and then released,
 *  rather than refused.
 */
STATIC int ss_conn_ind(queue_t * q, mblk_t * cp)
{
	ss_t *ss = PRIV(q);
	struct sock *sk;
	struct ss_event *p = (typeof(p)) cp->b_rptr;
	ss_addr_t dst = { AF_UNSPEC, 0, };
	ss_opts_t opt = { 0, };
	if ((1 << ss_get_state(ss)) & ~TSM_LISTEN)
		goto outstate;
	if (!ss->conind || ss->sock->sk->state != TCP_LISTEN)
		goto nolisten;
	if (cp->b_wptr < cp->b_rptr + sizeof(*p))
		goto einval;
	if (ss->sock->sk == p->sk)
		goto duplicate;
	sk = p->sk;
	dst.sin_family = AF_INET;
	dst.sin_port = sk->dport;
	dst.sin_addr.s_addr = sk->daddr;
#ifdef LINUX_2_4
	opt.opt = sk->protinfo.af_inet.opt;
	opt.tos = (ulong *) & sk->protinfo.af_inet.tos;
	opt.ttl = (ulong *) & sk->protinfo.af_inet.ttl;
#else
	opt.opt = sk->opt;
	opt.tos = (ulong *) & sk->ip_tos;
	opt.ttl = (ulong *) & sk->ip_ttl;
#endif
	opt.reuse = (ulong *) & sk->reuse;
	opt.norte = (ulong *) & sk->localroute;
	opt.bcast = (ulong *) & sk->broadcast;
	if (opt.opt)
		opt.flags |= TF_IP_OPTIONS;
	opt.flags |= TF_IP_TOS;
	opt.flags |= TF_IP_TTL;
	opt.flags |= TF_IP_REUSEADDR;
	opt.flags |= TF_IP_DONTROUTE;
	opt.flags |= TF_IP_BROADCAST;
	if (ss->p.prot.protocol == T_INET_TCP) {
#ifdef LINUX_2_4
		opt.nodly = (ulong *) & sk->tp_pinfo.af_tcp.nonagle;
#else
		opt.nodly = (ulong *) & sk->nonagle;
#endif
		opt.mss = (ulong *) & sk->tp_pinfo.af_tcp.user_mss;
		opt.alive = (ulong *) & sk->keepopen;
		opt.flags |= TF_TCP_NODELAY;
		opt.flags |= TF_TCP_MAXSEG;
		opt.flags |= TF_TCP_KEEPALIVE;
	}
	if (ss->p.prot.protocol == T_INET_SCTP) {
		/* 
		 * skip for now 
		 */
	}
	return t_conn_ind(q, &dst, &opt, cp);
      einval:
	swerr();
	ptrace(("%s: SWERR: invalid primitive format\n", SS_MOD_NAME));
	return (-EFAULT);
      outstate:
	ptrace(("%s: ERROR: connect indication in wrong state %ld\n", SS_MOD_NAME,
		ss_get_state(ss)));
	return (-EPROTO);
      nolisten:
	ptrace(("%s: ERROR: connect indication received while not listening\n", SS_MOD_NAME));
	return (-EPROTO);
      duplicate:
	ptrace(("%s: %p: INFO: discarding duplicate connection indication\n", SS_MOD_NAME, ss));
	return (QR_DONE);
}

/*
 *  =========================================================================
 *
 *  SENDMSG and RECVMSG
 *
 *  =========================================================================
 */
/*
 *  SENDMSG
 *  -------------------------------------------------------------------------
 *  Convert the mblk to send into an iovec and the options into a control
 *  message and then call sendmsg on the socket with the kernel data segment.
 *  The socket will handle moving data from the mblks.
 */
STATIC int ss_sock_sendmsg(ss_t * ss, mblk_t * mp, ss_addr_t * dst, int flags, ss_opts_t * opts)
{
	int err = 0;
	int len, sdu, n;
	mblk_t *dp;
	for (len = 0, n = 0, dp = mp; dp; dp = dp->b_cont) {
		if (dp->b_datap->db_type == M_DATA && dp->b_wptr > dp->b_rptr) {
			len += dp->b_wptr - dp->b_rptr;
			n++;
		}
	}
	err = -EPROTO;
	if (!(ss->p.info.PROVIDER_flag & T_SNDZERO) && (!n || !len))
		goto out;
	if ((sdu = (int) ss->p.info.TSDU_size) > 0 && (!n || len > sdu))
		goto out;
	err = -EBUSY;
	if (len > sock_wspace(ss->sock->sk))
		goto out;
	/* 
	 *  This has the ramification that we can never do zero length writes,
	 *  but we have ~T_SNDZERO set anyway.
	 */
	{
		int i;
		struct iovec iov[n];
		struct msghdr msg;
		size_t clen = opts ? ss_cmsg_size(opts, TF_IP_OPTIONS | TF_IP_PKTINFO) : 0;
		unsigned char cbuf[clen > 1024 ? 1024 : clen];
		flags |= (MSG_DONTWAIT | MSG_NOSIGNAL);
		flags |= ss->options.norte ? MSG_DONTROUTE : 0;
		msg.msg_name = dst;
		msg.msg_namelen = dst ? sizeof(*dst) : 0;
		msg.msg_iov = iov;
		msg.msg_iovlen = n;
		msg.msg_control = clen ? cbuf : NULL;
		msg.msg_controllen = clen;
		msg.msg_flags = flags;
		/* 
		 * convert message blocks to an iovec 
		 */
		for (i = 0, dp = mp; dp; dp = dp->b_cont) {
			if (dp->b_datap->db_type == M_DATA && dp->b_wptr > dp->b_rptr) {
				iov[i].iov_base = dp->b_rptr;
				iov[i].iov_len = dp->b_wptr - dp->b_rptr;
				i++;
			}
		}
		if (opts) {
			ss_build_cmsg(opts, &msg, TF_IP_OPTIONS | TF_IP_PKTINFO);
			ss_opt_negotiate(ss, opts);
		}
		ptrace(("%s: %p: sendmsg with len = %d\n", SS_MOD_NAME, ss, len));
		err = ss_sendmsg(ss, &msg, len);
	}
	if (err < 0)
		goto out;
	if (!err) {
		err = -EBUSY;
		goto out;
	}
	if (err >= len)
		return (QR_DONE);	/* full write */
	switch (ss->p.prot.type) {	/* partial write */
	case SOCK_DGRAM:
	case SOCK_RAW:
	case SOCK_RDM:
		err = -EMSGSIZE;
		goto out;
	case SOCK_SEQPACKET:
	case SOCK_STREAM:
		adjmsg(mp, err);
		err = -EAGAIN;
		goto out;
	}
	err = -EFAULT;
      out:
	switch (-err) {
	case ENOMEM:
		/* 
		 *  This buffer call is just to kick us.  Because LiS uses kmalloc for
		 *  mblks, if we can allocate an mblk, then another kernel routine can
		 *  allocate that much memory too.
		 */
		if (ss->wbid) {
			unbufcall(xchg(&ss->wbid, 0));
			ss->refcnt--;
		}
		ss->wbid = bufcall(len, BPRI_LO, &ss_bufsrv, (long) ss->wq);
		ss->refcnt++;
	default:
		return m_error(ss->rq, err);
	}
}

/*
 *  RECVMSG
 *  -------------------------------------------------------------------------
 */
STATIC int ss_sock_recvmsg(queue_t * q)
{
	ss_t *ss = PRIV(q);
	mblk_t *mp;
	int more, expd, res, oldsize = 0x7fffffff, size = 0;
	struct msghdr msg = { NULL, };
	unsigned char cbuf[128] = { 0, };
	ss_addr_t src = { AF_INET, 0, {0}, };
	struct sk_buff *skb;
	while (canputnext(ss->rq)) {
		switch (ss->p.prot.type) {
		case SOCK_DGRAM:
		case SOCK_RAW:
		case SOCK_RDM:
		case SOCK_STREAM:
		{
			size = (skb = skb_peek(&ss->sock->sk->receive_queue)) ? skb->len : 0;
			break;
		}
		case SOCK_SEQPACKET:
			size = atomic_read(&ss->sock->sk->rmem_alloc);
			break;
		}
		if (!size || size >= oldsize)
			return (QR_DONE);
		oldsize = size;
		if (!(mp = ss_allocb(ss->rq, size, BPRI_MED)))
			return (-ENOBUFS);
		{
			struct iovec iov[1];
			iov[0].iov_base = mp->b_rptr;
			iov[0].iov_len = size;
			if (ss->p.prot.type != SOCK_STREAM) {
				msg.msg_name = &src;
				msg.msg_namelen = sizeof(src);
			} else {
				msg.msg_name = NULL;
				msg.msg_namelen = 0;
			}
			msg.msg_iov = iov;
			msg.msg_iovlen = sizeof(iov) / sizeof(struct iovec);
			msg.msg_control = cbuf;
			msg.msg_controllen = sizeof(cbuf);
			msg.msg_flags = 0;
			if ((res = ss_recvmsg(ss, &msg, size)) < 0) {
				freemsg(mp);
				return (res);
			}
			if (res == 0) {
				freemsg(mp);
				return (QR_DONE);
			}
			mp->b_wptr = mp->b_rptr + res;
			ptrace(("%s: %p: recvmsg with len = %d\n", SS_MOD_NAME, ss, res));
		}
#if 0
		if (msg.msg_flags & MSG_TRUNC) {
			freemsg(mp);
			return (-EMSGSIZE);
		}
#endif
		{
			int err;
			ss_opts_t opt = { 0, };
			if (msg.msg_controllen)
				ss_parse_cmsg(&msg, &opt);
			more = (msg.msg_flags & MSG_EOR) ? 0 : T_ODF_MORE;
			expd = (msg.msg_flags & MSG_OOB) ? T_ODF_EX : 0;
			if (ss->p.info.SERV_type == T_CLTS) {
				if ((err = t_unitdata_ind(q, &src, &opt, mp)) != QR_ABSORBED)
					return (err);
			} else if (!opt.flags) {
				if (msg.msg_flags & MSG_OOB) {
					if ((err = t_exdata_ind(q, more, mp)) != QR_ABSORBED)
						return (err);
				} else {
					if ((err = t_data_ind(q, more, mp)) != QR_ABSORBED)
						return (err);
				}
			} else {
				if ((err = t_optdata_ind(q, more | expd, &opt, mp)) != QR_ABSORBED)
					return (err);
			}
		}
	}
	return (-EBUSY);
}

/*
 *  =========================================================================
 *
 *  STATE MACHINE
 *
 *  =========================================================================
 */
/*
 *  -------------------------------------------------------------------------
 *
 *  Socket CALLBACKS
 *
 *  -------------------------------------------------------------------------
 */
STATIC void ss_putctl(ss_t * ss, queue_t * q, int type, void (*func) (long), struct sock *sk)
{
	int flags;
	mblk_t *mp;
	ss_event_t *p;
	if ((mp = allocb(sizeof(*p), BPRI_HI))) {
		mp->b_datap->db_type = type;
		p = ((typeof(p)) mp->b_wptr)++;
		p->sk = sk;
		p->state = sk->state;	/* capture current state */
		putq(q, mp);
		return (void) (0);
	}
	/* 
	 * set up bufcall so we don't lose events 
	 */
	lis_spin_lock_irqsave(&ss->lock, &flags); {
		/* 
		 * make sure bufcalls don't happen now 
		 */
		if (q == ss->rq) {
			if (ss->rbid)
				unbufcall(xchg(&ss->rbid, 0));
			ss->rbid = bufcall(FASTBUF, BPRI_HI, func, (long) sk);
		} else if (q == ss->wq) {
			if (ss->wbid)
				unbufcall(xchg(&ss->wbid, 0));
			ss->wbid = bufcall(FASTBUF, BPRI_HI, func, (long) sk);
		}
	}
	lis_spin_unlock_irqrestore(&ss->lock, &flags);
}

/*
 *  STATE change
 *  -------------------------------------------------------------------------
 *  We get state changes on sockets that we hold.  We also get state changes
 *  on accepting sockets.
 */
STATIC void _ss_sock_state_change(long data)
{
	struct sock *sk;
	ss_t *ss;
	if ((sk = (struct sock *) data)) {
		if ((ss = SOCK_PRIV(sk))) {
			read_lock(&sk->callback_lock);
			ss_putctl(ss, ss->rq, M_PCRSE, &_ss_sock_state_change, sk);
			read_unlock(&sk->callback_lock);
		} else
			assure(ss);
	} else
		assure(sk);
}
STATIC void ss_state_change(struct sock *sk)
{
	if (sk) {
		ss_t *ss;
		if ((ss = SOCK_PRIV(sk))) {
			_ss_sock_state_change((long) sk);
			// ss->cb_save.state_change(sk);
		} else
			assure(ss);
	} else
		assure(sk);
}

/*
 *  WRITE Available
 *  -------------------------------------------------------------------------
 */
STATIC void _ss_sock_write_space(long data)
{
	struct sock *sk;
	ss_t *ss;
	if ((sk = (struct sock *) data)) {
		if ((ss = SOCK_PRIV(sk))) {
			read_lock(&sk->callback_lock);
			ss_putctl(ss, ss->wq, M_READ, &_ss_sock_write_space, sk);
			read_unlock(&sk->callback_lock);
		} else
			assure(ss);
	} else
		assure(sk);
}
STATIC void ss_write_space(struct sock *sk)
{
	if (sk) {
		ss_t *ss;
		if ((ss = SOCK_PRIV(sk))) {
			_ss_sock_write_space((long) sk);
			// ss->cb_save.write_space(sk);
		} else
			assure(ss);
	} else
		assure(sk);
}

/*
 *  ERROR Available
 *  -------------------------------------------------------------------------
 */
STATIC void _ss_sock_error_report(long data)
{
	struct sock *sk;
	ss_t *ss;
	if ((sk = (struct sock *) data)) {
		if ((ss = SOCK_PRIV(sk))) {
			read_lock(&sk->callback_lock);
			ss_putctl(ss, ss->rq, M_ERROR, &_ss_sock_error_report, sk);
			read_unlock(&sk->callback_lock);
		} else
			assure(ss);
	} else
		assure(sk);
}
STATIC void ss_error_report(struct sock *sk)
{
	if (sk) {
		ss_t *ss;
		if ((ss = SOCK_PRIV(sk))) {
			_ss_sock_error_report((long) sk);
			// ss->cb_save.error_report(sk);
		} else
			assure(ss);
	} else
		assure(sk);
}

/*
 *  READ Available
 *  -------------------------------------------------------------------------
 */
STATIC void _ss_sock_data_ready(long data)
{
	struct sock *sk;
	ss_t *ss;
	if ((sk = (struct sock *) data)) {
		if ((ss = SOCK_PRIV(sk))) {
			read_lock(&sk->callback_lock);
			ss_putctl(ss, ss->rq, M_READ, &_ss_sock_data_ready, sk);
			read_unlock(&sk->callback_lock);
		} else
			assure(ss);
	} else
		assure(sk);
}
STATIC void ss_data_ready(struct sock *sk, int len)
{
	if (sk) {
		ss_t *ss;
		if ((ss = SOCK_PRIV(sk))) {
			(void) len;
			_ss_sock_data_ready((long) sk);
			// ss->cb_save.data_ready(sk, len);
		} else
			assure(ss);
	} else
		assure(sk);
}

/*
 *  =========================================================================
 *
 *  IP T-User --> T-Provider Primitives (Request and Response)
 *
 *  =========================================================================
 *
 *  T_CONN_REQ           0 - TC Request
 *  -------------------------------------------------------------------
 */
STATIC int t_conn_req(queue_t * q, mblk_t * mp)
{
	int err = -EFAULT;
	ss_t *ss = PRIV(q);
	const struct T_conn_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if (ss_get_state(ss) != TS_IDLE)
		goto outstate;
	if (mp->b_wptr < mp->b_rptr + sizeof(*p)
	    || mp->b_wptr < mp->b_rptr + p->DEST_offset + p->DEST_length
	    || mp->b_wptr < mp->b_rptr + p->OPT_offset + p->OPT_length)
		goto einval;
	else {
		ss_addr_t *dst = (typeof(dst)) (mp->b_rptr + p->DEST_offset);
		if (p->DEST_length < sizeof(*dst))
			goto badaddr;
		if (dst->sin_port == 0)
			goto badaddr;
		if (dst->sin_family != ss->p.prot.family)
			goto badaddr;
		if (ss->cred.cr_uid != 0 && htons(dst->sin_port) == IPPROTO_RAW)
			goto acces;
		else {
			unsigned char *opt = mp->b_rptr + p->OPT_offset;
			size_t opt_len = p->OPT_length;
			struct ss_opts opts = { 0L, NULL, };
			if (opt_len && ss_parse_opts(ss, &opts, opt, opt_len))
				goto badopt;
			/* 
			 * TODO: set options first 
			 */
			ss->dst = *dst;
			if (mp->b_cont) {
				putbq(q, mp->b_cont);	/* hold back data */
				mp->b_cont = NULL;	/* abosrbed mp->b_cont */
			}
			ss_set_state(ss, TS_WACK_CREQ);
			return t_ok_ack(q, T_CONN_REQ, NULL, NULL);
		}
	}
      badopt:
	err = TBADOPT;
	ptrace(("%s: ERROR: bad options\n", SS_MOD_NAME));
	goto error;
      acces:
	err = TACCES;
	ptrace(("%s: ERROR: no permission for address\n", SS_MOD_NAME));
	goto error;
      badaddr:
	err = TBADADDR;
	ptrace(("%s: ERROR: address is unusable\n", SS_MOD_NAME));
	goto error;
      einval:
	err = -EINVAL;
	ptrace(("%s: ERROR: invalid message format\n", SS_MOD_NAME));
	goto error;
      outstate:
	err = TOUTSTATE;
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      notsupport:
	err = TNOTSUPPORT;
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return t_error_ack(q, T_CONN_REQ, err);
}

/*
 *  T_CONN_RES           1 - Accept previous connection indication
 *  -------------------------------------------------------------------
 */
STATIC mblk_t *t_seq_check(ss_t * ss, ulong seq)
{
	mblk_t *mp;
	lis_spin_lock(&ss->conq.q_lock); {
		for (mp = bufq_head(&ss->conq); mp && mp != (mblk_t *) seq; mp = mp->b_next);
	}
	lis_spin_unlock(&ss->conq.q_lock);
	usual(mp);
	return (mp);
}
STATIC ss_t *t_tok_check(ulong acceptor)
{
	ss_t *as;
	queue_t *aq = (queue_t *) acceptor;
	for (as = ss_opens; as && as->rq != aq; as = as->next);
	usual(as);
	return (as);
}
STATIC int t_conn_res(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	int err = 0;
	mblk_t *cp;
	ss_t *as;
	const size_t mlen = mp->b_wptr - mp->b_rptr;
	const struct T_conn_res *p = (typeof(p)) mp->b_rptr;
	struct ss_opts opts = { 0L, NULL, };
	size_t opt_len, opt_off;
	unsigned char *opt;
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if (ss_get_state(ss) != TS_WRES_CIND)
		goto outstate;
	ss_set_state(ss, TS_WACK_CRES);
	if (mlen < sizeof(*p))
		goto inval;
	if ((cp = t_seq_check(ss, p->SEQ_number)) == NULL)
		goto badseq;
	if (((as = t_tok_check(p->ACCEPTOR_id)) == NULL)
	    || !(as == ss || ((1 << ss_get_state(as)) & TSM_DISCONN)))
		goto badf;
	if (ss->p.prot.protocol != as->p.prot.protocol)
		goto provmismatch;
	if (ss_get_state(as) == TS_IDLE && as->conind)
		goto resqlen;
	if (ss->cred.cr_uid != 0 && as->cred.cr_uid == 0)
		goto acces;
	opt_len = p->OPT_length;
	opt_off = p->OPT_offset;
	opt = mp->b_rptr + opt_off;
	if (opt_len && (mlen < opt_off + opt_len || (err = ss_parse_opts(ss, &opts, opt, opt_len))))
		goto badopt;
	if (mp->b_cont != NULL && msgdsize(mp->b_cont))
		goto baddata;
	return t_ok_ack(q, T_CONN_RES, cp, as);
      baddata:
	err = TBADDATA;
	ptrace(("%s: ERROR: bad connection data\n", SS_MOD_NAME));
	goto error;
      badopt:
	err = TBADOPT;
	ptrace(("%s: ERROR: bad options\n", SS_MOD_NAME));
	goto error;
      acces:
	err = TACCES;
	ptrace(("%s: ERROR: no access to accepting queue\n", SS_MOD_NAME));
	goto error;
      resqlen:
	err = TRESQLEN;
	ptrace(("%s: ERROR: accepting queue is listening\n", SS_MOD_NAME));
	goto error;
      provmismatch:
	err = TPROVMISMATCH;
	ptrace(("%s: ERROR: not same transport provider\n", SS_MOD_NAME));
	goto error;
      badf:
	err = TBADF;
	ptrace(("%s: ERROR: accepting queue id is invalid\n", SS_MOD_NAME));
	goto error;
      badseq:
	err = TBADSEQ;
	ptrace(("%s: ERROR: sequence number is invalid\n", SS_MOD_NAME));
	goto error;
      inval:
	err = -EINVAL;
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	goto error;
      outstate:
	err = TOUTSTATE;
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      notsupport:
	err = TNOTSUPPORT;
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return t_error_ack(q, T_CONN_RES, err);
}

/*
 *  T_DISCON_REQ         2 - TC disconnection request
 *  -------------------------------------------------------------------
 */
STATIC int t_discon_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	int err;
	mblk_t *cp = NULL;
	struct T_discon_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if ((1 << ss_get_state(ss)) & ~TSM_CONNECTED)
		goto outstate;
	if (mp->b_wptr - mp->b_rptr < sizeof(*p))
		goto einval;
	switch (ss_get_state(ss)) {
	case TS_WCON_CREQ:
		ss_set_state(ss, TS_WACK_DREQ6);
		break;
	case TS_WRES_CIND:
		if ((cp = t_seq_check(ss, p->SEQ_number))) {
			ss_set_state(ss, TS_WACK_DREQ7);
			break;
		}
		goto badseq;
	case TS_DATA_XFER:
		ss_set_state(ss, TS_WACK_DREQ9);
		break;
	case TS_WIND_ORDREL:
		ss_set_state(ss, TS_WACK_DREQ10);
		break;
	case TS_WREQ_ORDREL:
		ss_set_state(ss, TS_WACK_DREQ11);
		break;
	}
	return t_ok_ack(q, T_DISCON_REQ, cp, NULL);
      badseq:
	err = TBADSEQ;
	ptrace(("%s: ERROR: sequence number is invalid\n", SS_MOD_NAME));
	goto error;
      einval:
	err = -EINVAL;
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	goto error;
      outstate:
	err = TOUTSTATE;
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      notsupport:
	err = TNOTSUPPORT;
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return t_error_ack(q, T_DISCON_REQ, err);
}

/*
 *  M_DATA
 *  -------------------------------------------------------------------
 */
STATIC int ss_w_data(queue_t * q, mblk_t * mp)
{
	int flags, mlen, mmax;
	ss_t *ss = PRIV(q);
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if (ss_get_state(ss) == TS_IDLE)
		goto discard;
	if ((1 << ss_get_state(ss)) & ~TSM_OUTDATA)
		goto outstate;
	mlen = msgdsize(mp);
	mmax = ss->p.info.TIDU_size;
	if (mmax < ss->p.info.TSDU_size && ss->p.info.TSDU_size != T_INVALID)
		mmax = ss->p.info.ETSDU_size;
	if (mlen > mmax)
		goto emsgsize;
	flags = (ss->p.prot.type == SOCK_SEQPACKET) ? MSG_EOR : 0;
	return ss_sock_sendmsg(ss, mp, NULL, flags, NULL);
      emsgsize:
	ptrace(("%s: ERROR: message too large %d > %d\n", SS_MOD_NAME, mlen, mmax));
	goto error;
      outstate:
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      discard:
	ptrace(("%s: ERROR: ignore in idle state\n", SS_MOD_NAME));
	return (QR_DONE);
      notsupport:
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return m_error(q, EPROTO);
}

/*
 *  T_DATA_REQ           3 - Connection-Mode data transfer request
 *  -------------------------------------------------------------------
 */
STATIC int t_data_req(queue_t * q, mblk_t * mp)
{
	int flags, mlen, mmax;
	ss_t *ss = PRIV(q);
	const struct T_data_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if (ss_get_state(ss) == TS_IDLE)
		goto discard;
	if (mp->b_wptr - mp->b_rptr < sizeof(*p))
		goto einval;
	if ((1 << ss_get_state(ss)) & ~TSM_OUTDATA)
		goto outstate;
	mlen = msgdsize(mp);
	mmax = ss->p.info.TIDU_size;
	if (mmax < ss->p.info.TSDU_size && ss->p.info.TSDU_size != T_INVALID)
		mmax = ss->p.info.ETSDU_size;
	if (mlen > mmax)
		goto emsgsize;
	flags = (ss->p.prot.type == SOCK_SEQPACKET && !p->MORE_flag) ? MSG_EOR : 0;
	return ss_sock_sendmsg(ss, mp, NULL, flags, NULL);
      emsgsize:
	ptrace(("%s: ERROR: message too large %d > %d\n", SS_MOD_NAME, mlen, mmax));
	goto error;
      outstate:
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      einval:
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	goto error;
      discard:
	ptrace(("%s: ERROR: ignore in idle state\n", SS_MOD_NAME));
	return (QR_DONE);
      notsupport:
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return m_error(q, EPROTO);
}

/*
 *  T_EXDATA_REQ         4 - Expedited data request
 *  -------------------------------------------------------------------
 */
STATIC int t_exdata_req(queue_t * q, mblk_t * mp)
{
	int flags, mlen, mmax;
	ss_t *ss = PRIV(q);
	const struct T_exdata_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if (ss_get_state(ss) == TS_IDLE)
		goto discard;
	if (mp->b_wptr - mp->b_rptr < sizeof(*p))
		goto einval;
	if ((1 << ss_get_state(ss)) & ~TSM_OUTDATA)
		goto outstate;
	mlen = msgdsize(mp);
	mmax = ss->p.info.TIDU_size;
	if (mmax < ss->p.info.ETSDU_size && ss->p.info.ETSDU_size != T_INVALID)
		mmax = ss->p.info.ETSDU_size;
	if (mlen > mmax)
		goto emsgsize;
	flags = (ss->p.prot.type == SOCK_SEQPACKET && !p->MORE_flag) ? MSG_EOR : 0;
	return ss_sock_sendmsg(ss, mp, NULL, MSG_OOB | flags, NULL);
      emsgsize:
	ptrace(("%s: ERROR: message too large %d > %d\n", SS_MOD_NAME, mlen, mmax));
	goto error;
      outstate:
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      einval:
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	goto error;
      discard:
	ptrace(("%s: ERROR: ignore in idle state\n", SS_MOD_NAME));
	return (QR_DONE);
      notsupport:
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return m_error(q, EPROTO);
}

/*
 *  T_INFO_REQ           5 - Information Request
 *  -------------------------------------------------------------------
 */
STATIC int t_info_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	(void) mp;
	(void) ss;
	return t_info_ack(q);
}

/*
 *  T_BIND_REQ           6 - Bind a TS user to a transport address
 *  -------------------------------------------------------------------
 */
STATIC int t_bind_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	int err;
	const struct T_bind_req *p = (typeof(p)) mp->b_rptr;
	if (ss_get_state(ss) != TS_UNBND)
		goto outstate;
	ss_set_state(ss, TS_WACK_BREQ);
	if (mp->b_wptr - mp->b_rptr < sizeof(*p))
		goto einval;
	if (ss->p.info.SERV_type == T_CLTS && p->CONIND_number)
		goto notsupport;
	if ((mp->b_wptr - mp->b_rptr < p->ADDR_offset + p->ADDR_length)
	    || (p->ADDR_length != sizeof(ss_addr_t)))
		goto badaddr;
	{
		ss_addr_t *add = (typeof(add)) (mp->b_rptr + p->ADDR_offset);
		/* 
		 * we don't allow wildcards yet 
		 */
		if (!add->sin_port)
			goto noaddr;
		if (add->sin_family != ss->p.prot.family)
			goto badaddr;
		if (ss->p.prot.type == SOCK_RAW && ss->cred.cr_uid != 0)
			goto acces;
		ss->port = htons(add->sin_port);
		if ((err = ss_socket(ss)))
			goto error;
		if ((err = ss_bind(ss, add)))
			goto error_close;
		ss->conind = 0;
		if (p->CONIND_number && (ss->p.prot.type == SOCK_STREAM
					 || ss->p.prot.type == SOCK_SEQPACKET))
			if ((err = ss_listen(ss, p->CONIND_number)))
				goto error_close;
		return t_bind_ack(q, &ss->src);
	}
      acces:
	err = TACCES;
	ptrace(("%s: ERROR: no permission for address\n", SS_MOD_NAME));
	goto error;
      noaddr:
	err = TNOADDR;
	ptrace(("%s: ERROR: couldn't allocate address\n", SS_MOD_NAME));
	goto error;
      badaddr:
	err = TBADADDR;
	ptrace(("%s: ERROR: address is invalid\n", SS_MOD_NAME));
	goto error;
      einval:
	err = -EINVAL;
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	goto error;
      outstate:
	err = TOUTSTATE;
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      notsupport:
	err = TNOTSUPPORT;
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error_close:
	ss_socket_put(xchg(&ss->sock, NULL));
      error:
	return t_error_ack(q, T_BIND_REQ, err);
}

/*
 *  T_UNBIND_REQ         7 - Unbind TS user from transport address
 *  -------------------------------------------------------------------
 */
STATIC int t_unbind_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	if (ss_get_state(ss) != TS_IDLE)
		goto outstate;
	ss_set_state(ss, TS_WACK_UREQ);
	return t_ok_ack(q, T_UNBIND_REQ, NULL, NULL);
      outstate:
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	return t_error_ack(q, T_UNBIND_REQ, TOUTSTATE);
}

/*
 *  T_UNITDATA_REQ       8 -Unitdata Request 
 *  -------------------------------------------------------------------
 */
STATIC int t_unitdata_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	size_t mlen = mp->b_wptr - mp->b_rptr;
	size_t dlen = mp->b_cont ? msgdsize(mp->b_cont) : 0;
	const struct T_unitdata_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type != T_CLTS)
		goto notsupport;
	if (dlen == 0 && !(ss->p.info.PROVIDER_flag & T_SNDZERO))
		goto baddata;
	if (dlen > ss->p.info.TSDU_size || dlen > ss->p.info.TIDU_size)
		goto baddata;
	if (ss_get_state(ss) != TS_IDLE)
		goto outstate;
	if (mlen < sizeof(*p))
		goto einval;
	if ((mlen < p->DEST_offset + p->DEST_length) || (p->DEST_length != sizeof(ss_addr_t)))
		goto badadd;
	if (ss->p.prot.type == SOCK_RAW && ss->cred.cr_uid != 0)
		goto acces;
	else {
		size_t opt_len = p->OPT_length;
		size_t opt_off = p->OPT_offset;
		unsigned char *opt = mp->b_rptr + opt_off;
		struct ss_opts opts = { 0L, NULL, };
		if (opt_len && (mlen < opt_off + opt_len || ss_parse_opts(ss, &opts, opt, opt_len)))
			goto badopt;
		else {
			int flags = (opts.norte && *(opts.norte)) ? MSG_DONTROUTE : 0;
			ss_addr_t *d = (typeof(d)) (mp->b_rptr + p->DEST_offset);
			return ss_sock_sendmsg(ss, mp, d, flags, &opts);
		}
	}
      badopt:
	ptrace(("%s: ERROR: bad options\n", SS_MOD_NAME));
	goto error;
      acces:
	ptrace(("%s: ERROR: no permission to address\n", SS_MOD_NAME));
	goto error;
      badadd:
	ptrace(("%s: ERROR: bad destination address\n", SS_MOD_NAME));
	goto error;
      einval:
	ptrace(("%s: ERROR: invalid primitive\n", SS_MOD_NAME));
	goto error;
      outstate:
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      baddata:
	ptrace(("%s: ERROR: bad data size\n", SS_MOD_NAME));
	goto error;
      notsupport:
	ptrace(("%s: ERROR: primitive not supported for T_COTS\n", SS_MOD_NAME));
	goto error;
      error:
	return m_error(q, EPROTO);
}

/*
 *  T_OPTMGMT_REQ        9 - Options management request
 *  -------------------------------------------------------------------
 */
STATIC int t_optmgmt_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	int err = 0;
	const size_t mlen = mp->b_wptr - mp->b_rptr;
	const struct T_optmgmt_req *p = (typeof(p)) mp->b_rptr;
	struct ss_opts opts = { 0L, NULL, };
	size_t opt_len, opt_off;
	unsigned char *opt;
#ifdef TS_WACK_OPTREQ
	if (ss_get_state(ss) == TS_IDLE)
		ss_set_state(ss, TS_WACK_OPTREQ);
#endif
	if (mlen < sizeof(*p))
		goto einval;
	opt_len = p->OPT_length;
	opt_off = p->OPT_offset;
	opt = mp->b_rptr + opt_off;
	if (opt_len && (mlen < opt_off + opt_len || (err = ss_parse_opts(ss, &opts, opt, opt_len))))
		goto badopt;
	switch (p->MGMT_flags) {
	case T_CHECK:
		err = ss_opt_check(ss, &opts);
		break;
	case T_NEGOTIATE:
		if (!opts.flags)
			ss_opt_default(ss, &opts);
		else if ((err = ss_opt_check(ss, &opts)))
			break;
		err = ss_opt_negotiate(ss, &opts);
		break;
	case T_DEFAULT:
		err = ss_opt_default(ss, &opts);
		break;
	case T_CURRENT:
		err = ss_opt_current(ss, &opts);
		break;
	default:
		goto badflag;
	}
	if (err)
		goto provspec;
	return t_optmgmt_ack(q, p->MGMT_flags, &opts);
      provspec:
	err = err;
	ptrace(("%s: ERROR: provider specific\n", SS_MOD_NAME));
	goto error;
      badflag:
	err = TBADFLAG;
	ptrace(("%s: ERROR: bad options flags\n", SS_MOD_NAME));
	goto error;
      badopt:
	err = TBADOPT;
	ptrace(("%s: ERROR: bad options\n", SS_MOD_NAME));
	goto error;
      einval:
	err = -EINVAL;
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	goto error;
      error:
	return t_error_ack(q, T_OPTMGMT_REQ, err);
}

/*
 *  T_ORDREL_REQ        10 - TS user is finished sending
 *  -------------------------------------------------------------------
 */
STATIC int t_ordrel_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	const struct T_ordrel_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type != T_COTS_ORD)
		goto notsupport;
	if ((1 << ss_get_state(ss)) & ~TSM_OUTDATA)
		goto outstate;
	ss->sock->ops->shutdown(ss->sock, SEND_SHUTDOWN);
	switch (ss_get_state(ss)) {
	case TS_DATA_XFER:
		ss_set_state(ss, TS_WIND_ORDREL);
		break;
	case TS_WREQ_ORDREL:
		ss_set_state(ss, TS_IDLE);
		break;
	}
	return (QR_DONE);
      outstate:
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      notsupport:
	ptrace(("%s: ERROR: primitive not supported for T_CLTS or T_COTS\n", SS_MOD_NAME));
	goto error;
      error:
	return m_error(q, EPROTO);
}

/*
 *  T_OPTDATA_REQ       24 - Data with options request
 *  -------------------------------------------------------------------
 */
STATIC int t_optdata_req(queue_t * q, mblk_t * mp)
{
	int err;
	ss_t *ss = PRIV(q);
	const size_t mlen = mp->b_wptr - mp->b_rptr;
	const struct T_optdata_req *p = (typeof(p)) mp->b_rptr;
	if (ss->p.info.SERV_type == T_CLTS)
		goto notsupport;
	if (ss_get_state(ss) == TS_IDLE)
		goto discard;
	if (mlen < sizeof(*p))
		goto einval;
	if ((1 << ss_get_state(ss)) & ~TSM_OUTDATA)
		goto outstate;
	else {
		struct ss_opts opts = { 0L, NULL, };
		size_t opt_len = p->OPT_length;
		size_t opt_off = p->OPT_offset;
		unsigned char *opt = mp->b_rptr + opt_off;
		if (opt_len && (mlen < opt_off + opt_len || ss_parse_opts(ss, &opts, opt, opt_len)))
			goto badopt;
		else {
			int flags = 0;
			if (opts.norte && *(opts.norte))
				flags |= MSG_DONTROUTE;
			if ((p->DATA_flag & T_ODF_EX))
				flags |= MSG_OOB;
			if (!(p->DATA_flag & T_ODF_MORE) && ss->p.prot.type == SOCK_SEQPACKET)
				flags |= MSG_EOR;
			return ss_sock_sendmsg(ss, mp, NULL, flags, &opts);
		}
	}
      badopt:
	err = TBADOPT;
	ptrace(("%s: ERROR: bad options\n", SS_MOD_NAME));
	goto error;
      outstate:
	err = TOUTSTATE;
	ptrace(("%s: ERROR: would place i/f out of state\n", SS_MOD_NAME));
	goto error;
      einval:
	ptrace(("%s: ERROR: invalid primitive format\n", SS_MOD_NAME));
	return m_error(q, EPROTO);
      discard:
	ptrace(("%s: ERROR: ignore in idle state\n", SS_MOD_NAME));
	return (QR_DONE);
      notsupport:
	err = TNOTSUPPORT;
	ptrace(("%s: ERROR: primitive not supported for T_CLTS\n", SS_MOD_NAME));
	goto error;
      error:
	return t_error_ack(q, T_OPTDATA_REQ, err);
}

#ifdef T_ADDR_REQ
/*
 *  T_ADDR_REQ          25 - Address Request
 *  -------------------------------------------------------------------
 */
STATIC int t_addr_req(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	(void) mp;
	switch (ss_get_state(ss)) {
	case TS_UNBND:
		return t_addr_ack(q, NULL, NULL);
	case TS_IDLE:
		return t_addr_ack(q, &ss->src, NULL);
	case TS_WCON_CREQ:
	case TS_DATA_XFER:
	case TS_WIND_ORDREL:
	case TS_WREQ_ORDREL:
		return t_addr_ack(q, &ss->src, &ss->dst);
	case TS_WRES_CIND:
		return t_addr_ack(q, NULL, &ss->dst);
	}
	return t_error_ack(q, T_ADDR_REQ, TOUTSTATE);
}
#endif
#ifdef T_CAPABILITY_REQ
/*
 *  T_CAPABILITY_REQ    ?? - Capability Request
 *  -------------------------------------------------------------------
 */
#endif
/*
 *  Other primitives    XX - other invalid primitives
 *  -------------------------------------------------------------------------
 */
STATIC int t_other_req(queue_t * q, mblk_t * mp)
{
	ulong prim = *((ulong *) mp->b_rptr);
	return t_error_ack(q, prim, TNOTSUPPORT);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  IO Controls
 *
 *  -------------------------------------------------------------------------
 */
#if 0
STATIC int ti_getinfo(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_optmgmt(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_bind(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_unbind(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_getmyname(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_getpeername(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_setmyname(queue_t * q, mblk_t * mp)
{
}
STATIC int ti_setpeername(queue_t * q, mblk_t * mp)
{
}
#endif

/*
 *  =========================================================================
 *
 *  STREAMS Message Handling
 *
 *  =========================================================================
 *
 *  M_IOCTL Handling
 *
 *  -------------------------------------------------------------------------
 *  There are a standard set of IOCTLs for TLI.  Check the iBCS package for
 *  the numbering.
 */
STATIC int ss_w_ioctl(queue_t * q, mblk_t * mp)
{
	(void) q;
	(void) mp;
	fixme(("Support finalized IOCTLs\n"));
#if 0
	switch (nr) {
	case _IOC_NR(TI_GETINFO):
		rtn = ti_getinfo(q, mp);
		break;
	case _IOC_NR(TI_OPTMGMT):
		rtn = ti_optmgmt(q, mp);
		break;
	case _IOC_NR(TI_BIND):
		rtn = ti_bind(q, mp);
		break;
	case _IOC_NR(TI_UNBIND):
		rtn = ti_unbind(q, mp);
		break;
	case _IOC_NR(TI_GETMYNAME):
		rtn = ti_getmyname(q, mp);
		break;
	case _IOC_NR(TI_GETPEERNAME):
		rtn = ti_getpeername(q, mp);
		break;
	case _IOC_NR(TI_SETMYNAME):
		rtn = ti_setmyname(q, mp);
		break;
	case _IOC_NR(TI_SETPEERNAME):
		rtn = ti_setpeername(q, mp);
		break;
	default:
		rtn = -EOPNOTSUPP;
		break;
	}
#endif
	return (-EOPNOTSUPP);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_PROTO, M_PCPROTO Handling
 *
 *  -------------------------------------------------------------------------
 */
STATIC int ss_w_proto(queue_t * q, mblk_t * mp)
{
	int rtn;
	ulong prim;
	ss_t *ss = PRIV(q);
	ulong oldstate = ss_get_state(ss);
	switch ((prim = *((ulong *) mp->b_rptr))) {
	case T_CONN_REQ:
		printd(("%s: %p: -> T_CONN_REQ\n", SS_MOD_NAME, ss));
		rtn = t_conn_req(q, mp);
		break;
	case T_CONN_RES:
		printd(("%s: %p: -> T_CONN_RES\n", SS_MOD_NAME, ss));
		rtn = t_conn_res(q, mp);
		break;
	case T_DISCON_REQ:
		printd(("%s: %p: -> T_DISCON_REQ\n", SS_MOD_NAME, ss));
		rtn = t_discon_req(q, mp);
		break;
	case T_DATA_REQ:
		printd(("%s: %p: -> T_DATA_REQ\n", SS_MOD_NAME, ss));
		rtn = t_data_req(q, mp);
		break;
	case T_EXDATA_REQ:
		printd(("%s: %p: -> T_EXDATA_REQ\n", SS_MOD_NAME, ss));
		rtn = t_exdata_req(q, mp);
		break;
	case T_INFO_REQ:
		printd(("%s: %p: -> T_INFO_REQ\n", SS_MOD_NAME, ss));
		rtn = t_info_req(q, mp);
		break;
	case T_BIND_REQ:
		printd(("%s: %p: -> T_BIND_REQ\n", SS_MOD_NAME, ss));
		rtn = t_bind_req(q, mp);
		break;
	case T_UNBIND_REQ:
		printd(("%s: %p: -> T_UNBIND_REQ\n", SS_MOD_NAME, ss));
		rtn = t_unbind_req(q, mp);
		break;
	case T_OPTMGMT_REQ:
		printd(("%s: %p: -> T_OPTMGMT_REQ\n", SS_MOD_NAME, ss));
		rtn = t_optmgmt_req(q, mp);
		break;
	case T_UNITDATA_REQ:
		printd(("%s: %p: -> T_UNITDATA_REQ\n", SS_MOD_NAME, ss));
		rtn = t_unitdata_req(q, mp);
		break;
	case T_ORDREL_REQ:
		printd(("%s: %p: -> T_ORDREL_REQ\n", SS_MOD_NAME, ss));
		rtn = t_ordrel_req(q, mp);
		break;
	case T_OPTDATA_REQ:
		printd(("%s: %p: -> T_OPTDATA_REQ\n", SS_MOD_NAME, ss));
		rtn = t_optdata_req(q, mp);
		break;
#ifdef T_ADDR_REQ
	case T_ADDR_REQ:
		printd(("%s: %p: -> T_ADDR_REQ\n", SS_MOD_NAME, ss));
		rtn = t_addr_req(q, mp);
		break;
#endif
#ifdef T_CAPABILITY_REQ
	case T_CAPABILITY_REQ:
		printd(("%s: %p: -> T_CAPABILITY_REQ\n", SS_MOD_NAME, ss));
		rtn = t_capability_req(q, mp);
		break;
#endif
	default:
		rtn = t_other_req(q, mp);
		break;
	}
	if (rtn < 0) {
		rare();
		ss_set_state(ss, oldstate);
	}
	return (rtn);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_FLUSH Handling
 *
 *  -------------------------------------------------------------------------
 */
STATIC int ss_w_flush(queue_t * q, mblk_t * mp)
{
	if (*mp->b_rptr & FLUSHW) {
		if (*mp->b_rptr & FLUSHBAND)
			flushband(q, mp->b_rptr[1], FLUSHALL);
		else
			flushq(q, FLUSHALL);
		*mp->b_rptr &= ~FLUSHW;
	}
	if (*mp->b_rptr & FLUSHR) {
		if (*mp->b_rptr & FLUSHBAND)
			flushband(OTHER(q), mp->b_rptr[1], FLUSHALL);
		else
			flushq(OTHER(q), FLUSHALL);
		qreply(q, mp);
		return (QR_ABSORBED);
	}
	return (QR_DONE);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_PCRSE Handling
 *
 *  -------------------------------------------------------------------------
 *  On the read queue, an M_PCRSE is used to indicate a state_change callback
 *  on the socket.  This can mean that the socket has completed connecting or
 *  has disconnected, or it may signal a connection indication on a listening
 *  socket.
 *
 *  We always examine both the state of the TPI interface and the state of the
 *  socket to determine which actions need to be performed.  This makes the
 *  state machine interworking more robust.
 *
 *  Note that the sk pointer passed in here is not necessarily the same as
 *  ss->sock->sk, sk may be a child (connection indication) of the primary
 *  socket.
 */
STATIC int ss_r_pcrse(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	ss_event_t *p = (typeof(p)) mp->b_rptr;
	int oldstate = xchg(&ss->tcp_state, p->state);
	if (!ss->sock)
		goto discard;
	assure(p->state != oldstate);
	printd(("%s: %p: state_change [%s <- %s] %p\n", SS_MOD_NAME, ss, tcp_state_name(p->state),
		tcp_state_name(oldstate), p->sk));
	switch (ss->p.prot.type) {
	case SOCK_STREAM:
	case SOCK_SEQPACKET:
		switch (ss_get_state(ss)) {
		case TS_WCON_CREQ:
			switch (p->state) {
			case TCP_ESTABLISHED:
				return (t_conn_con(q, &ss->dst, NULL, NULL));
			case TCP_TIME_WAIT:
			case TCP_CLOSE:
				return (t_discon_ind(q, &ss->dst, T_PROVIDER, 0, NULL, NULL));
			}
			break;
		case TS_WIND_ORDREL:
			switch (p->state) {
			case TCP_TIME_WAIT:
			case TCP_CLOSE:
				switch (oldstate) {
				case TCP_FIN_WAIT2:
					return (t_ordrel_ind(q));
				case TCP_ESTABLISHED:
				case TCP_FIN_WAIT1:
					return (t_discon_ind(q, NULL, T_PROVIDER, 0, NULL, NULL));
				}
				break;
			case TCP_FIN_WAIT1:
			case TCP_FIN_WAIT2:
				return (QR_DONE);
			}
			break;
		case TS_DATA_XFER:
			switch (p->state) {
			case TCP_CLOSE_WAIT:
				return (t_ordrel_ind(q));
			case TCP_TIME_WAIT:
			case TCP_CLOSE:
				return (t_discon_ind(q, NULL, T_PROVIDER, 0, NULL, NULL));
			}
			break;
		case TS_WREQ_ORDREL:
			switch (p->state) {
			case TCP_TIME_WAIT:
			case TCP_CLOSE:
				switch (oldstate) {
				case TCP_CLOSE_WAIT:
					return (t_discon_ind(q, NULL, T_PROVIDER, 0, NULL, NULL));
				}
				break;
			}
			break;
		case TS_IDLE:
		case TS_WRES_CIND:
			switch (p->state) {
			case TCP_LAST_ACK:
				switch (oldstate) {
				case TCP_CLOSE_WAIT:
					return (QR_DONE);
				}
				break;
			case TCP_TIME_WAIT:
			case TCP_CLOSE:
				switch (oldstate) {
				case TCP_FIN_WAIT2:
				case TCP_LAST_ACK:
					return (QR_DONE);
				case TCP_LISTEN:
				{
					mblk_t *cp;
					/* state change was on child */
					ss->tcp_state = TCP_LISTEN;
					/* look for the child */
					if ((cp = t_seq_find(ss, mp)))
						return (t_discon_ind(q, NULL, T_PROVIDER,
								     0, cp, NULL));
					return (QR_DONE);
				}
				}
				break;
			case TCP_ESTABLISHED:
				switch (oldstate) {
				case TCP_LISTEN:
					/* state change was on child */
					ss->tcp_state = TCP_LISTEN;
					return (ss_conn_ind(q, mp));
				}
				break;
			}
			break;
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	case SOCK_DGRAM:
	case SOCK_RAW:
	case SOCK_RDM:
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	default:
		printd(("%s: SWERR: unsupported socket type\n", SS_MOD_NAME));
		return (-EFAULT);
	}
      discard:
	printd(("%s: ERROR: lingering event, ignoring\n", SS_MOD_NAME));
	return (QR_DONE);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_READ Handling
 *
 *  -------------------------------------------------------------------------
 *
 *  M_READ on the RD(q)
 *  -------------------------------------------------------------------------
 *
 *  On the read queue, an M_READ is used to indicate a data_ready callback on
 *  the socket.  This can mean that the socket is now readable (when it was
 *  blocked before), or it may mean that a socket has completed connecting, or
 *  disconnected, or that a connection indication may be availabel on a
 *  listening socket.
 *
 *  We always examine both the state of the TPI interface and the state of the
 *  socket to determine which actions need to be performed.  This makes the
 *  state machine interworking more robust.
 *
 *  Note that the sk pointer passed in here is not necessarily the same as
 *  ss->sock->sk, sk may be a child (connection indication) of the primary
 *  socket.
 */
STATIC int ss_r_read(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	ss_event_t *p = (typeof(p)) mp->b_rptr;
	if (!ss->sock)
		goto discard;
	assure(ss->tcp_state == p->state || ss->tcp_state == TCP_LISTEN);
	printd(("%s: %p: data_ready [%s <- %s] %p\n", SS_MOD_NAME, ss, tcp_state_name(p->state),
		tcp_state_name(ss->tcp_state), p->sk));
	switch (ss->p.prot.type) {
	case SOCK_STREAM:	/* TCP */
	case SOCK_SEQPACKET:	/* SCTP */
		switch (ss_get_state(ss)) {
		case TS_IDLE:
		case TS_WRES_CIND:
			if (p->state == TCP_LISTEN)
				return (QR_DONE);
		case TS_DATA_XFER:
		case TS_WIND_ORDREL:
		case TS_WREQ_ORDREL:	/* TCP bug I believe */
			return ss_sock_recvmsg(q);
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	case SOCK_DGRAM:
	case SOCK_RAW:
	case SOCK_RDM:
		switch (ss_get_state(ss)) {
		case TS_IDLE:
			return ss_sock_recvmsg(q);
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	}
	printd(("%s: SWERR: unsupported socket type %d\n", SS_MOD_NAME, ss->p.prot.type));
	return (-EFAULT);
      discard:
	printd(("%s: ERROR: lingering event, ignoring\n", SS_MOD_NAME));
	return (QR_DONE);
}

/*
 *  M_READ on the WR(q)
 *  -------------------------------------------------------------------------
 *
 *  On the write queue, an M_READ is used to indicate a write_space callback
 *  on the socket.  This can mean that the socket is now writeable (when it
 *  was blocked before).  This can also mean that a connecting socket has
 *  completed connecting (or has disconnected), or that a listening socket has
 *  a connection indication on the socket.
 *
 *  We always examine both the state of the TPI interface and the state of the
 *  socket to determine which actions need to be performed.  This makes the
 *  state machine interworking more robust.
 *
 *  Note that the sk pointer passed in here is not necessarily the same as
 *  ss->sock->sk, sk may be a child (connection indication) of the primary
 *  socket.
 *
 */
STATIC int ss_w_read(queue_t * q, mblk_t * mp)
{
	ss_t *ss = PRIV(q);
	ss_event_t *p = (typeof(p)) mp->b_rptr;
	if (!ss->sock)
		goto discard;
	assure(ss->tcp_state == p->state || ss->tcp_state == TCP_LISTEN);
	printd(("%s: %p: write_space [%s <- %s] %p\n", SS_MOD_NAME, ss, tcp_state_name(p->state),
		tcp_state_name(ss->tcp_state), p->sk));
	switch (ss->p.prot.type) {
	case SOCK_STREAM:
	case SOCK_SEQPACKET:
		switch (ss_get_state(ss)) {
		case TS_DATA_XFER:
		case TS_WREQ_ORDREL:
			return (QR_DONE);
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	case SOCK_DGRAM:
	case SOCK_RAW:
	case SOCK_RDM:
		switch (ss_get_state(ss)) {
		case TS_IDLE:
			return (QR_DONE);
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	}
	printd(("%s: SWERR: unsupported socket type %d\n", SS_MOD_NAME, ss->p.prot.type));
	return (-EFAULT);
      discard:
	printd(("%s: ERROR: lingering event, ignoring\n", SS_MOD_NAME));
	return (QR_DONE);
}

/*
 *  -------------------------------------------------------------------------
 *
 *  M_ERROR Handling
 *
 *  -------------------------------------------------------------------------
 *  On the read queue, an M_ERROR is used to indicate an error_report callback
 *  on the socket.  This can mean that the socket has disconected.  We might
 *  not received any other indication of the error.
 *
 *  We always examine both the state of the TPI interface and the state of the
 *  socket to determine which actions need to be performed.  This makes the
 *  state machine interworking more robust.
 *
 *  Note that the sk pointer passed in here is not necessarily the same as
 *  ss->sock->sk, sk may be a child (connection indication) of the primary
 *  socket.
 */
STATIC int ss_r_error(queue_t * q, mblk_t * mp)
{
	int err;
	ss_t *ss = PRIV(q);
	ss_event_t *p = (typeof(p)) mp->b_rptr;
	err = sock_error(p->sk);
	if (!ss->sock)
		goto discard;
	assure(ss->tcp_state == p->state || ss->tcp_state == TCP_LISTEN);
	printd(("%s: %p: error_report [%s <- %s] %p\n", SS_MOD_NAME, ss, tcp_state_name(p->state),
		tcp_state_name(ss->tcp_state), p->sk));
	switch (ss->p.prot.type) {
	case SOCK_STREAM:
	case SOCK_SEQPACKET:
		switch (ss_get_state(ss)) {
		default:
			fixme(("%s: %p: FIXME: save errors for later\n", SS_MOD_NAME, ss));
			return (QR_DONE);
		case TS_IDLE:
			printd(("%s: %p: INFO: ignoring error event %d\n", SS_MOD_NAME, ss, err));
			return (QR_DONE);
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	case SOCK_DGRAM:
	case SOCK_RAW:
	case SOCK_RDM:
		switch (ss_get_state(ss)) {
		case TS_IDLE:
			fixme(("%s: %p: FIXME: generate uderror\n", SS_MOD_NAME, ss));
			printd(("%s: %p: INFO: ignoring error event %d\n", SS_MOD_NAME, ss, err));
			return (QR_DONE);
		}
		printd(("%s: %p: SWERR: socket state %s in TPI state %s\n",
			SS_MOD_NAME, ss, tcp_state_name(p->state), state_name(ss_get_state(ss))));
		return (-EFAULT);
	}
	printd(("%s: SWERR: unsupported socket type %d\n", SS_MOD_NAME, ss->p.prot.type));
	return (-EFAULT);
      discard:
	printd(("%s: ERROR: lingering event, ignoring\n", SS_MOD_NAME));
	return (QR_DONE);
}

/*
 *  =========================================================================
 *
 *  QUEUE PUT and SERVICE routines
 *
 *  =========================================================================
 *
 *  WRITE PUT ad SERVICE (Message from above IP-User --> IP-Provider
 *  -------------------------------------------------------------------------
 */
STATIC int ss_w_prim(queue_t * q, mblk_t * mp)
{
	switch (mp->b_datap->db_type) {
	case M_DATA:
		return ss_w_data(q, mp);
	case M_PROTO:
	case M_PCPROTO:
		return ss_w_proto(q, mp);
	case M_CTL:
	case M_READ:
		return ss_w_read(q, mp);
	case M_FLUSH:
		return ss_w_flush(q, mp);
	case M_IOCTL:
		return ss_w_ioctl(q, mp);
	}
	return (-EOPNOTSUPP);
}

/*
 *  READ PUT ad SERVICE (Message from below IP-Provider --> IP-User
 *  -------------------------------------------------------------------------
 */
STATIC int ss_r_prim(queue_t * q, mblk_t * mp)
{
	switch (mp->b_datap->db_type) {
	case M_RSE:
	case M_PCRSE:
		return ss_r_pcrse(q, mp);
	case M_CTL:
	case M_READ:
		return ss_r_read(q, mp);
	case M_ERROR:
		return ss_r_error(q, mp);
	}
	return (QR_PASSFLOW);
}

/*
 *  =========================================================================
 *
 *  QUEUE PUT and SERVICE routines
 *
 *  =========================================================================
 *
 *  PUTQ Put Routine
 *  -----------------------------------
 */
STATIC int ss_putq(queue_t * q, mblk_t * mp, int (*proc) (queue_t *, mblk_t *))
{
	int rtn = 0;
	if (mp->b_datap->db_type < QPCTL || q->q_count) {
		putq(q, mp);
		return (0);
	}
	if (ss_trylockq(q)) {
		do {
			/* 
			 * Fast Path 
			 */
			if ((rtn = (*proc) (q, mp)) == QR_DONE) {
				freemsg(mp);
				break;
			}
			switch (rtn) {
			case QR_DONE:
				freemsg(mp);
			case QR_ABSORBED:
				break;
			case QR_STRIP:
				if (mp->b_cont)
					putq(q, mp->b_cont);
			case QR_TRIMMED:
				freeb(mp);
				break;
			case QR_LOOP:
				if (!q->q_next) {
					qreply(q, mp);
					break;
				}
			case QR_PASSALONG:
				if (q->q_next) {
					putnext(q, mp);
					break;
				}
				rtn = -EOPNOTSUPP;
			default:
				freemsg(mp);
				break;
			case QR_DISABLE:
				putq(q, mp);
				rtn = 0;
				break;
			case QR_PASSFLOW:
				if (mp->b_datap->db_type >= QPCTL || canputnext(q)) {
					putnext(q, mp);
					break;
				}
			case -ENOBUFS:
			case -EBUSY:
			case -EAGAIN:
			case -ENOMEM:
				putq(q, mp);
				break;
			}
		}
		while (0);
		ss_unlockq(q);
	} else {
		putq(q, mp);
	}
	return (rtn);
}

/*
 *  SRVQ Service Routine
 *  -----------------------------------
 */
STATIC int ss_srvq(queue_t * q, int (*proc) (queue_t *, mblk_t *))
{
	int rtn = 0;
	if (ss_trylockq(q)) {
		mblk_t *mp;
		while ((mp = getq(q))) {
			/* 
			 * Fast Path 
			 */
			if ((rtn = proc(q, mp)) == QR_DONE) {
				freemsg(mp);
				continue;
			}
			switch (rtn) {
			case QR_DONE:
				freemsg(mp);
			case QR_ABSORBED:
				continue;
			case QR_STRIP:
				if (mp->b_cont)
					putbq(q, mp->b_cont);
			case QR_TRIMMED:
				freeb(mp);
				continue;
			case QR_LOOP:
				if (!q->q_next) {
					qreply(q, mp);
					continue;
				}
			case QR_PASSALONG:
				if (q->q_next) {
					putnext(q, mp);
					continue;
				}
				rtn = -EOPNOTSUPP;
			default:
				ptrace(("%s: ERROR: (q dropping) %d\n", SS_MOD_NAME, rtn));
				freemsg(mp);
				continue;
			case QR_DISABLE:
				ptrace(("%s: ERROR: (q disabling)\n", SS_MOD_NAME));
				noenable(q);
				putbq(q, mp);
				rtn = 0;
				break;
			case QR_PASSFLOW:
				if (mp->b_datap->db_type >= QPCTL || canputnext(q)) {
					putnext(q, mp);
					continue;
				}
			case -ENOBUFS:	/* caller must schedule bufcall */
			case -EBUSY:	/* caller must have failed canput */
			case -EAGAIN:	/* caller must re-enable queue */
			case -ENOMEM:	/* caller must re-enable queue */
				if (mp->b_datap->db_type < QPCTL) {
					ptrace(("%s: ERROR: (q stalled) %d\n", SS_MOD_NAME, rtn));
					putbq(q, mp);
					break;
				}
				/* 
				 *  Be careful not to put a priority message
				 *  back on the queue.
				 */
				switch (mp->b_datap->db_type) {
				case M_PCPROTO:
					mp->b_datap->db_type = M_PROTO;
					break;
				case M_PCRSE:
					mp->b_datap->db_type = M_RSE;
					break;
				case M_READ:
					mp->b_datap->db_type = M_CTL;
					break;
				default:
					ptrace(("%s: ERROR: (q dropping) %d\n", SS_MOD_NAME, rtn));
					freemsg(mp);
					continue;
				}
				mp->b_band = 255;
				putq(q, mp);
				break;
			}
			break;
		}
		ss_unlockq(q);
	}
	return (rtn);
}

STATIC INT ss_rput(queue_t * q, mblk_t * mp)
{
	return (INT) ss_putq(q, mp, &ss_r_prim);
}

STATIC INT ss_rsrv(queue_t * q)
{
	return (INT) ss_srvq(q, &ss_r_prim);
}

STATIC INT ss_wput(queue_t * q, mblk_t * mp)
{
	return (INT) ss_putq(q, mp, &ss_w_prim);
}

STATIC INT ss_wsrv(queue_t * q)
{
	return (INT) ss_srvq(q, &ss_w_prim);
}

/*
 *  =========================================================================
 *
 *  Private Structure allocation, deallocation and cache
 *
 *  =========================================================================
 */
STATIC kmem_cache_t *ss_priv_cachep = NULL;
STATIC int ss_init_caches(void)
{
	if (!ss_priv_cachep &&
	    !(ss_priv_cachep = kmem_cache_create
	      ("ss_priv_cachep", sizeof(ss_t), 0, SLAB_HWCACHE_ALIGN, NULL, NULL))) {
		cmn_err(CE_PANIC, "%s: Cannot allocate ss_priv_cachep", __FUNCTION__);
		return (-ENOMEM);
	} else
		printd(("%s: initialized driver private structure cache\n", SS_MOD_NAME));
	return (0);
}
STATIC void ss_term_caches(void)
{
	if (ss_priv_cachep) {
		if (kmem_cache_destroy(ss_priv_cachep))
			cmn_err(CE_WARN, "%s: did not destroy ss_priv_cachep", __FUNCTION__);
		else
			printd(("%s: destroyed ss_priv_cachep\n", SS_MOD_NAME));
	}
	return;
}
STATIC ss_t *ss_alloc_priv(queue_t * q, ss_t ** slp, ushort cmajor,
			   ushort cminor, cred_t * crp, const ss_profile_t * prof)
{
	ss_t *ss;
	if ((ss = kmem_cache_alloc(ss_priv_cachep, SLAB_ATOMIC))) {
		// printd(("%s: allocated module private structure\n", SS_MOD_NAME));
		bzero(ss, sizeof(*ss));
		ss->cmajor = cmajor;
		ss->cminor = cminor;
		ss->rq = RD(q);
		ss->rq->q_ptr = ss;
		ss->refcnt++;
		ss->wq = WR(q);
		ss->wq->q_ptr = ss;
		ss->refcnt++;
		ss->cred = *crp;
		ss->p = *prof;
		lis_spin_lock_init(&ss->qlock, "ss-queue-lock");
		ss->rwait = NULL;
		ss->wwait = NULL;
		ss_set_state(ss, TS_UNBND);
		lis_spin_lock_init(&ss->lock, "ss-priv-lock");
		if ((ss->next = *slp))
			ss->next->prev = &ss->next;
		ss->prev = slp;
		*slp = ss;
		ss->refcnt++;
		// printd(("%s: linked private structure, reference count %d\n", SS_MOD_NAME,
		// ss->refcnt));
	} else
		ptrace(("%s: ERROR: Could not allocate module private structure\n", SS_MOD_NAME));
	return (ss);
}
STATIC void ss_free_priv(queue_t * q)
{
	ss_t *ss = PRIV(q);
	int flags = 0;
	// printd(("%s: unlinking private structure, reference count = %d\n", SS_MOD_NAME,
	// ss->refcnt));
	lis_spin_lock_irqsave(&ss->lock, &flags); {
		bufq_purge(&ss->conq);
		if (ss->sock)
			ss_socket_put(ss->sock);
		// printd(("%s: removed socket, reference count = %d\n", SS_MOD_NAME, ss->refcnt));
		ss_unbufcall(q);
		// printd(("%s: removed bufcalls, reference count = %d\n", SS_MOD_NAME,
		// ss->refcnt));
		if ((*ss->prev = ss->next))
			ss->next->prev = ss->prev;
		ss->next = NULL;
		ss->prev = NULL;
		ss->refcnt--;
		// printd(("%s: unlinked, reference count = %d\n", SS_MOD_NAME, ss->refcnt));
		ss->rq->q_ptr = NULL;
		ss->refcnt--;
		ss->wq->q_ptr = NULL;
		ss->refcnt--;
	}
	lis_spin_unlock_irqrestore(&ss->lock, &flags);
	if (ss->refcnt) {
		assure(ss->refcnt);
		printd(("%s: WARNING: ss->refcnt = %d\n", SS_MOD_NAME, ss->refcnt));
	}
	kmem_cache_free(ss_priv_cachep, ss);
	// printd(("%s: freed module private structure\n", SS_MOD_NAME));
	return;
}

/*
 *  =========================================================================
 *
 *  OPEN and CLOSE
 *
 *  =========================================================================
 *
 *  OPEN
 *  -------------------------------------------------------------------------
 */
STATIC const ss_profile_t ss_profiles[] = {
	{{PF_INET, SOCK_RAW, IPPROTO_ICMP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_RAW, IPPROTO_IGMP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_RAW, IPPROTO_IPIP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_STREAM, IPPROTO_TCP},
	 {T_INFO_ACK, T_INVALID, 1, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_COTS_ORD, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_RAW, IPPROTO_EGP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_RAW, IPPROTO_PUP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_DGRAM, IPPROTO_UDP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_RAW, IPPROTO_IDP},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}},
	{{PF_INET, SOCK_RAW, IPPROTO_RAW},
	 {T_INFO_ACK, 0xffff, T_INVALID, T_INVALID, T_INVALID,
	  sizeof(struct sockaddr_in),
	  T_INFINITE, 0xffff, T_CLTS, TS_UNBND, XPG4_1 & ~T_SNDZERO}
	 }
};
STATIC int ss_majors[SS_NMAJOR] = { SS_CMAJOR, };
STATIC int ss_open(queue_t * q, dev_t * devp, int flag, int sflag, cred_t * crp)
{
	int flags, mindex = 0;
	int cmajor = getmajor(*devp);
	int cminor = getminor(*devp);
	ss_t *ss, **ipp = &ss_opens;
	const ss_profile_t *prof;
	MOD_INC_USE_COUNT;	/* keep module from unloading in our face */
	if (q->q_ptr != NULL) {
		MOD_DEC_USE_COUNT;
		return (0);	/* already open */
	}
	if (sflag == MODOPEN || WR(q)->q_next) {
		ptrace(("%s: ERROR: can't push as module\n", SS_MOD_NAME));
		MOD_DEC_USE_COUNT;
		return (EIO);
	}
	if (cmajor != SS_CMAJOR || cminor < ICMP_CMINOR || cminor > RAWIP_CMINOR) {
		MOD_DEC_USE_COUNT;
		return (ENXIO);
	}
	prof = &ss_profiles[cminor - ICMP_CMINOR];
	cminor = FREE_CMINOR;
	lis_spin_lock_irqsave(&ss_lock, &flags);
	for (; *ipp; ipp = &(*ipp)->next) {
		if (cmajor != (*ipp)->cmajor)
			break;
		if (cmajor == (*ipp)->cmajor) {
			if (cminor < (*ipp)->cminor)
				break;
			if (cminor == (*ipp)->cminor) {
				if (++cminor >= SS_NMINOR) {
					if (++mindex >= SS_NMAJOR || !(cmajor = ss_majors[mindex]))
						break;
					cminor = 0;
				}
				continue;
			}
		}
	}
	if (mindex >= SS_NMAJOR || !cmajor) {
		ptrace(("%s: ERROR: no device numbers available\n", SS_MOD_NAME));
		lis_spin_unlock_irqrestore(&ss_lock, &flags);
		MOD_DEC_USE_COUNT;
		return (ENXIO);
	}
	printd(("%s: opened character device %d:%d\n", SS_MOD_NAME, cmajor, cminor));
	*devp = makedevice(cmajor, cminor);
	if (!(ss = ss_alloc_priv(q, ipp, cmajor, cminor, crp, prof))) {
		ptrace(("%s: ERROR: No memory\n", SS_MOD_NAME));
		lis_spin_unlock_irqrestore(&ss_lock, &flags);
		MOD_DEC_USE_COUNT;
		return (ENOMEM);
	}
	lis_spin_unlock_irqrestore(&ss_lock, &flags);
	return (0);
}

/*
 *  CLOSE
 *  -------------------------------------------------------------------------
 */
STATIC int ss_close(queue_t * q, int flag, cred_t * crp)
{
	ss_t *ss = PRIV(q);
	int flags;
	(void) flag;
	(void) crp;
	(void) ss;
	printd(("%s: closing character device %d:%d\n", SS_MOD_NAME, ss->cmajor, ss->cminor));
	lis_spin_lock_irqsave(&ss_lock, &flags);
	ss_free_priv(q);
	lis_spin_unlock_irqrestore(&ss_lock, &flags);
	MOD_DEC_USE_COUNT;
	return (0);
}

/*
 *  =========================================================================
 *
 *  LiS Module Initialization
 *
 *  =========================================================================
 */
STATIC int ss_initialized = 0;
STATIC void ss_init(void)
{
	int err, mindex;
	cmn_err(CE_NOTE, SS_BANNER);	/* console splash */
	if ((err = ss_init_caches())) {
		cmn_err(CE_PANIC, "%s: ERROR: Counld not allocated caches", SS_MOD_NAME);
		ss_initialized = err;
		return;
	}
	for (mindex = 0; mindex < SS_NMAJOR; mindex++) {
		if ((err = lis_register_strdev(ss_majors[mindex], &ss_info, SS_NMINOR,
					       SS_MOD_NAME)) < 0) {
			if (!mindex) {
				cmn_err(CE_PANIC, "%s: Can't register 1'st major %d",
					SS_MOD_NAME, ss_majors[0]);
				ss_term_caches();
				ss_initialized = err;
				return;
			}
			cmn_err(CE_WARN, "%s: Can't register %d'th major", SS_MOD_NAME, mindex + 1);
			ss_majors[mindex] = 0;
		}
	}
	lis_spin_lock_init(&ss_lock, "ss-open-list-lock");
	ss_initialized = 1;
	return;
}
STATIC void ss_terminate(void)
{
	int err, mindex;
	for (mindex = 0; mindex < SS_NMAJOR; mindex++) {
		if (ss_majors[mindex]) {
			if ((err = lis_unregister_strdev(ss_majors[mindex])))
				cmn_err(CE_PANIC, "%s: Can't unregister major %d\n", SS_MOD_NAME,
					ss_majors[mindex]);
			ss_majors[mindex] = 0;
		}
	}
	ss_term_caches();
	return;
}

/*
 *  =========================================================================
 *
 *  LINUX MODULE INITIALIZATION
 *
 *  =========================================================================
 */
int init_module(void)
{
	ss_init();
	if (ss_initialized < 0)
		return ss_initialized;
	return (0);
}
void cleanup_module(void)
{
	ss_terminate();
}
