/*	$NetBSD: tp_driver.c,v 1.15 2001/11/13 01:10:49 lukem Exp $	*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tp_driver.c,v 1.15 2001/11/13 01:10:49 lukem Exp $");

#include "tp_states.h"

static const struct act_ent {
	int             a_newstate;
	int             a_action;
} statetable[] = {{
		0, 0
},
#include "tp_states.init"
};

/* @(#)tp.trans	8.1 (Berkeley) 6/10/93 */

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

#include <netiso/tp_param.h>
#include <netiso/tp_stat.h>
#include <netiso/tp_pcb.h>
#include <netiso/tp_tpdu.h>
#include <netiso/argo_debug.h>
#include <netiso/tp_trace.h>
#include <netiso/iso_errno.h>
#include <netiso/tp_seq.h>
#include <netiso/tp_var.h>
#include <netiso/cons.h>

#define DRIVERTRACE TPPTdriver
#define sbwakeup(sb)	sowakeup(p->tp_sock, sb);
#define MCPY(d, w) (d ? m_copym(d, 0, (int)M_COPYALL, w): 0)

static int trick_hc = 1;

#include "tp_events.h"
static int _Xebec_action __P((int, struct tp_event *, struct tp_pcb *));
static int _Xebec_index __P((struct tp_event *, struct tp_pcb *));

static int
_Xebec_action(a, e, p)
	int             a;
	struct tp_event *e;
	struct tp_pcb  *p;
{
	int             error;
	struct mbuf    *data = NULL;
	int             doack;
	struct socket  *so = p->tp_sock;
	struct sockbuf *sb;
	int             timo;

	switch (a) {
	case -1:
		return tp_protocol_error(e, p);
	case 0x1:
		(void) tp_emit(DC_TPDU_type, p, 0, 0, NULL);
		break;
	case 0x2:
#ifdef TP_DEBUG
		if (e->ev_number != AK_TPDU)
			printf("TPDU 0x%x in REFWAIT!!!!\n", e->ev_number);
#endif	/* TP_DEBUG */
		break;
	case 0x3:
		/* oh, man is this grotesque or what? */
		(void) tp_goodack(p, e->ev_union.EV_AK_TPDU.e_cdt, e->ev_union.EV_AK_TPDU.e_seq, e->ev_union.EV_AK_TPDU.e_subseq);
		/*
		 * but it's necessary because this pseudo-ack may
		 * happen before the CC arrives, but we HAVE to
		 * adjust the snduna as a result of the ack, WHENEVER
		 * it arrives
		 */
		break;
	case 0x4:
		tp_detach(p);
		break;
	case 0x5:
		p->tp_refstate = REF_OPEN;	/* has timers ??? */
		break;
	case 0x6:
#ifdef TPPT
		if (tp_traceflags[D_CONN])
			tptrace(TPPTmisc, "CR datalen data",
				e->ev_union.EV_CR_TPDU.e_datalen,
				e->ev_union.EV_CR_TPDU.e_data, 0, 0);
#endif
#ifdef ARGO_DEBUG
		if (argo_debug[D_CONN]) {
			printf("CR datalen 0x%x data %p",
				e->ev_union.EV_CR_TPDU.e_datalen,
				e->ev_union.EV_CR_TPDU.e_data);
		}
#endif
		p->tp_refstate = REF_OPEN;	/* has timers */
		p->tp_fcredit = e->ev_union.EV_CR_TPDU.e_cdt;

		if (e->ev_union.EV_CR_TPDU.e_datalen > 0) {
			/* n/a for class 0 */
			ASSERT(p->tp_Xrcv.sb_cc == 0);
			sbappendrecord(&p->tp_Xrcv,
				       e->ev_union.EV_CR_TPDU.e_data);
			e->ev_union.EV_CR_TPDU.e_data = NULL;
		}
		break;
	case 0x7:
		IncStat(ts_tp0_conn);
#ifdef TPPT
		if (tp_traceflags[D_CONN])
			tptrace(TPPTmisc, "Confiming", p, 0, 0, 0);
#endif
#ifdef ARGO_DEBUG
		    if (argo_debug[D_CONN]) {
			printf("Confirming connection: p");
		}
#endif
		soisconnected(p->tp_sock);
		(void) tp_emit(CC_TPDU_type, p, 0, 0, NULL);
		p->tp_fcredit = 1;
		break;
	case 0x8:
		IncStat(ts_tp4_conn);	/* even though not quite open */
#ifdef TPPT
		if (tp_traceflags[D_CONN])
			tptrace(TPPTmisc, "Confiming", p, 0, 0, 0);
#endif
#ifdef ARGO_DEBUG
		if (argo_debug[D_CONN]) {
			printf("Confirming connection: p");
		}
#endif
		tp_getoptions(p);
		soisconnecting(p->tp_sock);
		if ((p->tp_rx_strat & TPRX_FASTSTART) && (p->tp_fcredit > 0))
			p->tp_cong_win = p->tp_fcredit * p->tp_l_tpdusize;
		p->tp_retrans = p->tp_Nretrans;
		tp_ctimeout(p, TM_retrans, (int) p->tp_cc_ticks);
		break;
	case 0x9:
#ifdef ARGO_DEBUG
		if (argo_debug[D_CONN]) {
			printf("event: CR_TPDU emit CC failed done ");
		}
#endif
		soisdisconnected(p->tp_sock);
		tp_recycle_tsuffix(p);
		tp_freeref(p->tp_lref);
		tp_detach(p);
		break;
	case 0xa:
#ifdef TPPT
		if (tp_traceflags[D_CONN])
			tptrace(TPPTmisc, "T_CONN_req flags ucddata",
				(int) p->tp_flags, p->tp_ucddata, 0, 0);
#endif
		data = MCPY(p->tp_ucddata, M_WAIT);
		if (data) {
#ifdef ARGO_DEBUG
			if (argo_debug[D_CONN]) {
				printf("T_CONN_req.trans m_copy cc %p\n",
				       p->tp_ucddata);
				dump_mbuf(data, "sosnd @ T_CONN_req");
			}
#endif
		}
		if ((error = tp_emit(CR_TPDU_type, p, 0, 0, data)) != 0)
			return error;	/* driver WON'T change state;
					 * will return error */

		p->tp_refstate = REF_OPEN;	/* has timers */
		if (p->tp_class != TP_CLASS_0) {
			p->tp_retrans = p->tp_Nretrans;
			tp_ctimeout(p, TM_retrans, (int) p->tp_cr_ticks);
		}
		break;
	case 0xb:
		sbflush(&p->tp_Xrcv);	/* purge non-delivered data
					 * data */
		if (e->ev_union.EV_DR_TPDU.e_datalen > 0) {
			sbappendrecord(&p->tp_Xrcv, e->ev_union.EV_DR_TPDU.e_data);
			e->ev_union.EV_DR_TPDU.e_data = NULL;
		}
		if (p->tp_state == TP_OPEN)
			tp_indicate(T_DISCONNECT, p, 0);
		else {
			int             so_error = ECONNREFUSED;
			if (e->ev_union.EV_DR_TPDU.e_reason != (E_TP_NO_SESSION ^ TP_ERROR_MASK) &&
			    e->ev_union.EV_DR_TPDU.e_reason != (E_TP_NO_CR_ON_NC ^ TP_ERROR_MASK) &&
			    e->ev_union.EV_DR_TPDU.e_reason != (E_TP_REF_OVERFLOW ^ TP_ERROR_MASK))
				so_error = ECONNABORTED;
			tp_indicate(T_DISCONNECT, p, so_error);
		}
		tp_soisdisconnected(p);
		if (p->tp_class != TP_CLASS_0) {
			if (p->tp_state == TP_OPEN) {
				tp_euntimeout(p, TM_data_retrans);/* all */
				tp_cuntimeout(p, TM_retrans);
				tp_cuntimeout(p, TM_inact);
				tp_cuntimeout(p, TM_sendack);
				p->tp_flags &= ~TPF_DELACK;
			}
			tp_cuntimeout(p, TM_retrans);
			if (e->ev_union.EV_DR_TPDU.e_sref != 0)
				(void) tp_emit(DC_TPDU_type, p, 0, 0, NULL);
		}
		break;
	case 0xc:
		if (e->ev_union.EV_DR_TPDU.e_sref != 0)
			(void) tp_emit(DC_TPDU_type, p, 0, 0, NULL);
		/*
		 * reference timer already set - reset it to be safe
		 * (???)
		 */
		tp_euntimeout(p, TM_reference);	/* all */
		tp_etimeout(p, TM_reference, (int) p->tp_refer_ticks);
		break;
	case 0xd:
		tp_cuntimeout(p, TM_retrans);
		tp_indicate(ER_TPDU, p, e->ev_union.EV_ER_TPDU.e_reason);
		tp_soisdisconnected(p);
		break;
	case 0xe:
		tp_cuntimeout(p, TM_retrans);
		tp_soisdisconnected(p);
		break;
	case 0xf:
		tp_indicate(ER_TPDU, p, e->ev_union.EV_ER_TPDU.e_reason);
		tp_cuntimeout(p, TM_retrans);
		tp_soisdisconnected(p);
		break;
	case 0x10:
		tp_cuntimeout(p, TM_retrans);
		tp_soisdisconnected(p);
		break;
	case 0x11:
		/* don't ask me why we have to do this - spec
		 * says so */
		(void) tp_emit(DR_TPDU_type, p, 0, E_TP_NO_SESSION, NULL);
		/* don't bother with retransmissions of the DR */
		break;
	case 0x12:
		tp_soisdisconnecting(p->tp_sock);
		tp_indicate(ER_TPDU, p, e->ev_union.EV_ER_TPDU.e_reason);
		tp_soisdisconnected(p);
		tp_netcmd(p, CONN_CLOSE);
		break;
	case 0x13:
		if (p->tp_state == TP_OPEN) {
			tp_euntimeout(p, TM_data_retrans);	/* all */
			tp_cuntimeout(p, TM_inact);
			tp_cuntimeout(p, TM_sendack);
		}
		tp_soisdisconnecting(p->tp_sock);
		tp_indicate(ER_TPDU, p, e->ev_union.EV_ER_TPDU.e_reason);
		p->tp_retrans = p->tp_Nretrans;
		tp_ctimeout(p, TM_retrans, (int) p->tp_dr_ticks);
		(void) tp_emit(DR_TPDU_type, p, 0, E_TP_PROTO_ERR, NULL);
		break;
	case 0x14:
		tp_cuntimeout(p, TM_retrans);
		IncStat(ts_tp0_conn);
		p->tp_fcredit = 1;
		soisconnected(p->tp_sock);
		break;
	case 0x15:
#ifdef ARGO_DEBUG
		if (argo_debug[D_CONN]) {
			printf("trans: CC_TPDU in CRSENT state flags 0x%x\n",
			       (int) p->tp_flags);
		}
#endif
		IncStat(ts_tp4_conn);
		p->tp_fref = e->ev_union.EV_CC_TPDU.e_sref;
		p->tp_fcredit = e->ev_union.EV_CC_TPDU.e_cdt;
		if ((p->tp_rx_strat & TPRX_FASTSTART) &&
		    (e->ev_union.EV_CC_TPDU.e_cdt > 0))
			p->tp_cong_win = e->ev_union.EV_CC_TPDU.e_cdt * p->tp_l_tpdusize;
		tp_getoptions(p);
		tp_cuntimeout(p, TM_retrans);
		if (p->tp_ucddata) {
#ifdef ARGO_DEBUG
			if (argo_debug[D_CONN]) {
				printf("dropping user connect data cc 0x%x\n",
				       p->tp_ucddata->m_len);
			}
#endif
			m_freem(p->tp_ucddata);
			p->tp_ucddata = 0;
		}
		soisconnected(p->tp_sock);
		if (e->ev_union.EV_CC_TPDU.e_datalen > 0) {
			ASSERT(p->tp_Xrcv.sb_cc == 0);	/* should be empty */
			sbappendrecord(&p->tp_Xrcv,
					e->ev_union.EV_CC_TPDU.e_data);
			e->ev_union.EV_CC_TPDU.e_data = NULL;
		}
		(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		break;
	case 0x16:
		IncStat(ts_retrans_cr);
		p->tp_cong_win = 1 * p->tp_l_tpdusize;
		data = MCPY(p->tp_ucddata, M_NOWAIT);
		if (p->tp_ucddata) {
#ifdef ARGO_DEBUG
			if (argo_debug[D_CONN]) {
				printf("TM_retrans.trans m_copy cc %p\n",
					data);
				dump_mbuf(p->tp_ucddata, "sosnd @ TM_retrans");
			}
#endif
			if (data == NULL)
				return ENOBUFS;
		}
		p->tp_retrans--;
		if ((error = tp_emit(CR_TPDU_type, p, 0, 0, data)) != 0) {
			p->tp_sock->so_error = error;
		}
		tp_ctimeout(p, TM_retrans, (int) p->tp_cr_ticks);
		break;
	case 0x17:
		IncStat(ts_conn_gaveup);
		p->tp_sock->so_error = ETIMEDOUT;
		tp_indicate(T_DISCONNECT, p, ETIMEDOUT);
		tp_soisdisconnected(p);
		break;
	case 0x18:
		data = MCPY(p->tp_ucddata, M_WAIT);

		if ((error = tp_emit(CC_TPDU_type, p, 0, 0, data)) != 0) {
			p->tp_sock->so_error = error;
		}
		p->tp_retrans = p->tp_Nretrans;
		tp_ctimeout(p, TM_retrans, (int) p->tp_cc_ticks);
		break;
	case 0x19:
		/*
		 * Get rid of any confirm or connect data, so that if we
		 * crash or close, it isn't thought of as disconnect data.
		 */
		if (p->tp_ucddata) {
			m_freem(p->tp_ucddata);
			p->tp_ucddata = 0;
		}
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		tp_cuntimeout(p, TM_retrans);
		soisconnected(p->tp_sock);
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);

		/*
		 * see also next 2 transitions, if you make any
		 * changes
		 */

		doack = tp_stash(p, e);
#ifdef ARGO_DEBUG
		if (argo_debug[D_DATA]) {
			printf("tp_stash returns %d\n", doack);
		}
#endif

		if (doack) {
			(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
			tp_ctimeout(p, TM_sendack, (int) p->tp_keepalive_ticks);
		} else
			tp_ctimeout(p, TM_sendack, (int) p->tp_sendack_ticks);

#ifdef ARGO_DEBUG
		if (argo_debug[D_DATA]) {
			printf("after stash calling sbwakeup\n");
		}
#endif
		break;
	case 0x1a:
		tp0_stash(p, e);
		sbwakeup(&p->tp_sock->so_rcv);

#ifdef ARGO_DEBUG
		if (argo_debug[D_DATA]) {
			printf("after stash calling sbwakeup\n");
		}
#endif
		break;
	case 0x1b:
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		sbwakeup(&p->tp_sock->so_rcv);

		doack = tp_stash(p, e);
#ifdef ARGO_DEBUG
		if (argo_debug[D_DATA]) {
			printf("tp_stash returns %d\n", doack);
		}
#endif

		if (doack)
			(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
		else
			tp_ctimeout_MIN(p, TM_sendack, (int) p->tp_sendack_ticks);

#ifdef ARGO_DEBUG
		if (argo_debug[D_DATA]) {
			printf("after stash calling sbwakeup\n");
		}
#endif
		break;
	case 0x1c:
#ifdef TPPT
		if (tp_traceflags[D_DATA])
			tptrace(TPPTmisc, "NIW seq rcvnxt lcredit ",
				e->ev_union.EV_DT_TPDU.e_seq,
				p->tp_rcvnxt, p->tp_lcredit, 0);
#endif
		IncStat(ts_dt_niw);
		m_freem(e->ev_union.EV_DT_TPDU.e_data);
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
		break;
	case 0x1d:
		if (p->tp_ucddata) {
			m_freem(p->tp_ucddata);
			p->tp_ucddata = 0;
		}
		(void) tp_goodack(p, e->ev_union.EV_AK_TPDU.e_cdt, e->ev_union.EV_AK_TPDU.e_seq, e->ev_union.EV_AK_TPDU.e_subseq);
		tp_cuntimeout(p, TM_retrans);

		soisconnected(p->tp_sock);
#ifdef TPPT
		if (tp_traceflags[D_CONN]) {
			struct socket  *so = p->tp_sock;
			tptrace(TPPTmisc,
			"called sosiconn: so so_state rcv.sb_sel rcv.sb_flags",
				so, so->so_state, so->so_rcv.sb_sel,
				so->so_rcv.sb_flags);
			tptrace(TPPTmisc,
		    "called sosiconn 2: so_qlen so_error so_rcv.sb_cc so_head",
				so->so_qlen, so->so_error, so->so_rcv.sb_cc,
				so->so_head);
		}
#endif

		tp_ctimeout(p, TM_sendack, (int) p->tp_keepalive_ticks);
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		break;
	case 0x1e:
		if (p->tp_state == TP_AKWAIT) {
			if (p->tp_ucddata) {
				m_freem(p->tp_ucddata);
				p->tp_ucddata = 0;
			}
			tp_cuntimeout(p, TM_retrans);
			soisconnected(p->tp_sock);
			tp_ctimeout(p, TM_sendack, (int) p->tp_keepalive_ticks);
			tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		}
#ifdef TPPT
		if (tp_traceflags[D_XPD]) {
			tptrace(TPPTmisc, "XPD tpdu accepted Xrcvnxt, "
				"e_seq datalen m_len\n", p->tp_Xrcvnxt,
				e->ev_union.EV_XPD_TPDU.e_seq,
				e->ev_union.EV_XPD_TPDU.e_datalen,
				e->ev_union.EV_XPD_TPDU.e_data->m_len);
		}
#endif

		p->tp_sock->so_state |= SS_RCVATMARK;
		e->ev_union.EV_XPD_TPDU.e_data->m_flags |= M_EOR;
		sbinsertoob(&p->tp_Xrcv, e->ev_union.EV_XPD_TPDU.e_data);
#ifdef ARGO_DEBUG
		if (argo_debug[D_XPD]) {
			dump_mbuf(e->ev_union.EV_XPD_TPDU.e_data, "XPD TPDU: tp_Xrcv");
		}
#endif
		tp_indicate(T_XDATA, p, 0);
		sbwakeup(&p->tp_Xrcv);

		(void) tp_emit(XAK_TPDU_type, p, p->tp_Xrcvnxt, 0, NULL);
		SEQ_INC(p, p->tp_Xrcvnxt);
		break;
	case 0x1f:
		if (p->tp_Xrcv.sb_cc == 0) {
			/* kludge for select(): */
			/* p->tp_sock->so_state &= ~SS_OOBAVAIL; */
		}
		break;
	case 0x20:
#ifdef TPPT
		if (tp_traceflags[D_XPD])
			tptrace(TPPTmisc,
			"XPD tpdu niw (Xrcvnxt, e_seq) or not cdt (cc)\n",
				p->tp_Xrcvnxt, e->ev_union.EV_XPD_TPDU.e_seq,
				p->tp_Xrcv.sb_cc, 0);
#endif
		if (p->tp_Xrcvnxt != e->ev_union.EV_XPD_TPDU.e_seq)
			IncStat(ts_xpd_niw);
		if (p->tp_Xrcv.sb_cc) {
			/* might as well kick 'em again */
			tp_indicate(T_XDATA, p, 0);
			IncStat(ts_xpd_dup);
		}
		m_freem(e->ev_union.EV_XPD_TPDU.e_data);
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		/*
		 * don't send an xack because the xak gives "last one
		 * received", not "next one i expect" (dumb)
		 */
		break;
	case 0x21:
		/* detach from parent socket so it can finish closing */
		if (so->so_head) {
			if (!soqremque(so, 0) && !soqremque(so, 1))
				panic("tp: T_DETACH");
			so->so_head = 0;
		}
		tp_soisdisconnecting(p->tp_sock);
		tp_netcmd(p, CONN_CLOSE);
		tp_soisdisconnected(p);
		break;
	case 0x22:
		/* detach from parent socket so it can finish closing */
		if (so->so_head) {
			if (!soqremque(so, 0) && !soqremque(so, 1))
				panic("tp: T_DETACH");
			so->so_head = 0;
		}
		if (p->tp_state != TP_CLOSING) {
			tp_soisdisconnecting(p->tp_sock);
			data = MCPY(p->tp_ucddata, M_NOWAIT);
			(void) tp_emit(DR_TPDU_type, p, 0, E_TP_NORMAL_DISC, data);
			p->tp_retrans = p->tp_Nretrans;
			tp_ctimeout(p, TM_retrans, (int) p->tp_dr_ticks);
		}
		break;
	case 0x23:
		tp_soisdisconnecting(p->tp_sock);
		tp_netcmd(p, CONN_CLOSE);
		tp_soisdisconnected(p);
		break;
	case 0x24:
		data = MCPY(p->tp_ucddata, M_WAIT);

		if (p->tp_state == TP_OPEN) {
			tp_euntimeout(p, TM_data_retrans);	/* all */
			tp_cuntimeout(p, TM_inact);
			tp_cuntimeout(p, TM_sendack);
			p->tp_flags &= ~TPF_DELACK;
		}
		if (data) {
#ifdef ARGO_DEBUG
			if (argo_debug[D_CONN]) {
				printf("T_DISC_req.trans tp_ucddata %p\n",
				       p->tp_ucddata);
				dump_mbuf(data, "ucddata @ T_DISC_req");
			}
#endif
		}
		tp_soisdisconnecting(p->tp_sock);
		p->tp_retrans = p->tp_Nretrans;
		tp_ctimeout(p, TM_retrans, (int) p->tp_dr_ticks);

		if (trick_hc)
			return tp_emit(DR_TPDU_type, p, 0,
				       e->ev_union.EV_REQ_TPDU.e_reason,
				       data);
		break;
	case 0x25:
		data = MCPY(p->tp_ucddata, M_WAIT);

		IncStat(ts_retrans_cc);
		p->tp_retrans--;
		p->tp_cong_win = 1 * p->tp_l_tpdusize;

		if ((error = tp_emit(CC_TPDU_type, p, 0, 0, data)) != 0)
			p->tp_sock->so_error = error;
		tp_ctimeout(p, TM_retrans, (int) p->tp_cc_ticks);
		break;
	case 0x26:
		IncStat(ts_conn_gaveup);
		tp_soisdisconnecting(p->tp_sock);
		p->tp_sock->so_error = ETIMEDOUT;
		tp_indicate(T_DISCONNECT, p, ETIMEDOUT);
		(void) tp_emit(DR_TPDU_type, p, 0, E_TP_CONGEST, NULL);
		p->tp_retrans = p->tp_Nretrans;
		tp_ctimeout(p, TM_retrans, (int) p->tp_dr_ticks);
		break;
	case 0x27:
		tp_euntimeout(p, TM_data_retrans);	/* all */
		tp_cuntimeout(p, TM_inact);
		tp_cuntimeout(p, TM_sendack);

		IncStat(ts_conn_gaveup);
		tp_soisdisconnecting(p->tp_sock);
		p->tp_sock->so_error = ETIMEDOUT;
		tp_indicate(T_DISCONNECT, p, ETIMEDOUT);
		(void) tp_emit(DR_TPDU_type, p, 0, E_TP_CONGEST_2, NULL);
		p->tp_retrans = p->tp_Nretrans;
		tp_ctimeout(p, TM_retrans, (int) p->tp_dr_ticks);
		break;
	case 0x28:
		p->tp_cong_win = 1 * p->tp_l_tpdusize;
		/* resume XPD */
		if (p->tp_Xsnd.sb_mb) {
			struct mbuf    *m = m_copy(p->tp_Xsnd.sb_mb, 0,
						   (int) p->tp_Xsnd.sb_cc);
			int             shift;

#ifdef TPPT
			if (tp_traceflags[D_XPD]) {
				tptrace(TPPTmisc,
				"XPD retrans: Xuna Xsndnxt sndnxt snduna",
					p->tp_Xuna, p->tp_Xsndnxt, p->tp_sndnxt,
					p->tp_snduna);
			}
#endif
#ifdef ARGO_DEBUG
			if (argo_debug[D_XPD]) {
				dump_mbuf(m, "XPD retrans emitting M");
			}
#endif
			IncStat(ts_retrans_xpd);
			p->tp_retrans--;
			shift = max(p->tp_Nretrans - p->tp_retrans, 6);
			(void) tp_emit(XPD_TPDU_type, p, p->tp_Xuna, 1, m);
			tp_ctimeout(p, TM_retrans, ((int) p->tp_dt_ticks) << shift);
		}
		break;
	case 0x29:
		p->tp_rxtshift++;
		(void) tp_data_retrans(p);
		break;
	case 0x2a:
		p->tp_retrans--;
		(void) tp_emit(DR_TPDU_type, p, 0, E_TP_DR_NO_REAS, NULL);
		IncStat(ts_retrans_dr);
		tp_ctimeout(p, TM_retrans, (int) p->tp_dr_ticks);
		break;
	case 0x2b:
		p->tp_sock->so_error = ETIMEDOUT;
		p->tp_refstate = REF_FROZEN;
		tp_recycle_tsuffix(p);
		tp_etimeout(p, TM_reference, (int) p->tp_refer_ticks);
		break;
	case 0x2c:
		tp_freeref(p->tp_lref);
		tp_detach(p);
		break;
	case 0x2d:
		if (p->tp_class != TP_CLASS_0) {
			tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
			if (e->ev_number == CC_TPDU)
				(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
		}
		/*
		 * ignore it if class 0 - state tables are blank for
		 * this
		 */
		break;
	case 0x2e:
#ifdef TPPT
		if (tp_traceflags[D_DATA])
			tptrace(TPPTmisc,
			"T_DATA_req sndnxt snduna fcredit, tpcb",
				p->tp_sndnxt, p->tp_snduna, p->tp_fcredit, p);
#endif

		tp_send(p);
		break;
	case 0x2f:
		error = 0;

		/* resume XPD */
		if (p->tp_Xsnd.sb_mb) {
			struct mbuf    *m = m_copy(p->tp_Xsnd.sb_mb, 0, (int) p->tp_Xsnd.sb_cc);
			/*
			 * m_copy doesn't preserve the m_xlink field,
			 * but at this pt. that doesn't matter
			 */

#ifdef TPPT
			if (tp_traceflags[D_XPD])
				tptrace(TPPTmisc,
				"XPD req: Xuna Xsndnxt sndnxt snduna",
					p->tp_Xuna, p->tp_Xsndnxt, p->tp_sndnxt,
					p->tp_snduna);
#endif
#ifdef ARGO_DEBUG
			if (argo_debug[D_XPD]) {
				printf("T_XPD_req: sb_cc 0x%lx\n", p->tp_Xsnd.sb_cc);
				dump_mbuf(m, "XPD req emitting M");
			}
#endif
			error =
				tp_emit(XPD_TPDU_type, p, p->tp_Xuna, 1, m);
			p->tp_retrans = p->tp_Nretrans;

			tp_ctimeout(p, TM_retrans, (int) p->tp_rxtcur);
			SEQ_INC(p, p->tp_Xsndnxt);
		}
		if (trick_hc)
			return error;
		break;
	case 0x30:
		sb = &p->tp_sock->so_snd;

#ifdef ARGO_DEBUG
		if (argo_debug[D_ACKRECV]) {
			printf("GOOD ACK seq 0x%x cdt 0x%x\n", e->ev_union.EV_AK_TPDU.e_seq, e->ev_union.EV_AK_TPDU.e_cdt);
		}
#endif
		if (p->tp_class != TP_CLASS_0) {
			tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		}
		sbwakeup(sb);
#ifdef ARGO_DEBUG
		if (argo_debug[D_ACKRECV]) {
			printf("GOOD ACK new sndnxt 0x%x\n", p->tp_sndnxt);
		}
#endif
		break;
	case 0x31:
#ifdef TPPT
		if (tp_traceflags[D_ACKRECV])
			tptrace(TPPTmisc,
				"BOGUS ACK fcc_present, tp_r_subseq e_subseq",
				e->ev_union.EV_AK_TPDU.e_fcc_present,
				p->tp_r_subseq,
				e->ev_union.EV_AK_TPDU.e_subseq, 0);
#endif
		if (p->tp_class != TP_CLASS_0) {

			if (!e->ev_union.EV_AK_TPDU.e_fcc_present) {
				/* send ACK with FCC */
				IncStat(ts_ackreason[_ACK_FCC_]);
				(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt,
					       1, NULL);
			}
			tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		}
		break;
	case 0x32:
		tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		tp_cuntimeout(p, TM_retrans);

		sbwakeup(&p->tp_sock->so_snd);

		/* resume normal data */
		tp_send(p);
		break;
	case 0x33:
#ifdef TPPT
		if (tp_traceflags[D_ACKRECV])
			tptrace(TPPTmisc, "BOGUS XACK eventtype ",
				e->ev_number, 0, 0, 0);
#endif
		if (p->tp_class != TP_CLASS_0) {
			tp_ctimeout(p, TM_inact, (int) p->tp_inact_ticks);
		}
		break;
	case 0x34:
#ifdef TPPT
		if (tp_traceflags[D_TIMER])
			tptrace(TPPTsendack, -1, p->tp_lcredit, p->tp_sent_uwe,
				p->tp_sent_lcdt, 0);
#endif
		IncPStat(p, tps_n_TMsendack);
		(void) tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
		if (p->tp_fcredit == 0) {
			if (p->tp_rxtshift < TP_MAXRXTSHIFT)
				p->tp_rxtshift++;
			timo = (p->tp_dt_ticks) << p->tp_rxtshift;
		} else
			timo = p->tp_sendack_ticks;
		tp_ctimeout(p, TM_sendack, timo);
		break;
	case 0x35:
		if (sbspace(&p->tp_sock->so_rcv) > 0)
			tp0_openflow(p);
		break;
	case 0x36:
		if (trick_hc) {
			SeqNum          ack_thresh;
			/*
			 * If the upper window edge has advanced a reasonable
			 * amount beyond what was known, send an ACK.
			 * A reasonable amount is 2 packets, unless the max window
			 * is only 1 or 2 packets, in which case we
			 * should send an ack for any advance in the upper window edge.
			 */
			LOCAL_CREDIT(p);
			ack_thresh = SEQ_SUB(p, p->tp_lcredit + p->tp_rcvnxt,
				    (p->tp_maxlcredit > 2 ? 2 : 1));
			if (SEQ_GT(p, ack_thresh, p->tp_sent_uwe)) {
				IncStat(ts_ackreason[_ACK_USRRCV_]);
				p->tp_flags &= ~TPF_DELACK;
				return tp_emit(AK_TPDU_type, p, p->tp_rcvnxt, 0, NULL);
			}
		}
		break;
	case 0x37:
		if (trick_hc)
			return ECONNABORTED;
		break;
	case 0x38:
		ASSERT(p->tp_state != TP_LISTENING);
		tp_indicate(T_DISCONNECT, p, ECONNRESET);
		tp_soisdisconnected(p);
		break;
	}
	return 0;
}

static int
_Xebec_index(e, p)
	struct tp_event *e;
	struct tp_pcb  *p;
{
	switch ((e->ev_number << 4) + (p->tp_state)) {
	case 0x12:
		if (p->tp_retrans > 0)
			return 0x1e;
		else
			return 0x1f;
	case 0x13:
		if (p->tp_retrans > 0)
			return 0x2f;
		else
			return 0x30;
	case 0x14:
		if (p->tp_retrans > 0)
			return 0x32;
		else
			return 0x31;
	case 0x15:
		if (p->tp_retrans > 0)
			return 0x34;
		else
			return 0x35;
	case 0x54:
		if (p->tp_rxtshift < TP_NRETRANS)
			return 0x33;
		else
			return 0x31;
	case 0x64:
		if (p->tp_class == TP_CLASS_0)
			return 0x1a;
		else
			return 0x1b;
	case 0x77:
		if (p->tp_class == TP_CLASS_0)
			return 0xd;
		else
			return 0xe;
	case 0x86:
		if (e->ev_union.EV_DR_TPDU.e_sref != 0)
			return 0x2;
		else
			return 0x3;
	case 0xa2:
		if (p->tp_class == TP_CLASS_0)
			return 0x1c;
		else
			return 0x1d;
	case 0xb2:
		if (p->tp_class == TP_CLASS_0)
			return 0x5;
		else
			return 0x0;
	case 0xb4:
		if (tp_goodack(p, e->ev_union.EV_AK_TPDU.e_cdt, e->ev_union.EV_AK_TPDU.e_seq, e->ev_union.EV_AK_TPDU.e_subseq))
			return 0x3a;
		else
			return 0x3b;
	case 0xc3:
		if (IN_RWINDOW(p, e->ev_union.EV_DT_TPDU.e_seq,
			p->tp_rcvnxt, SEQ(p, p->tp_rcvnxt + p->tp_lcredit)))
			return 0x21;
		else
			return 0x24;
	case 0xc4:
		if (p->tp_class == TP_CLASS_0)
			return 0x22;
		else if (IN_RWINDOW(p, e->ev_union.EV_DT_TPDU.e_seq,
			p->tp_rcvnxt, SEQ(p, p->tp_rcvnxt + p->tp_lcredit)))
			return 0x23;
		else
			return 0x25;
	case 0xd3:
		if (p->tp_Xrcvnxt == e->ev_union.EV_XPD_TPDU.e_seq)
			return 0x27;
		else
			return 0x2a;
	case 0xd4:
		if (p->tp_Xrcvnxt == e->ev_union.EV_XPD_TPDU.e_seq)
			return 0x27;
		else
			return 0x29;
	case 0xe4:
		if (tp_goodXack(p, e->ev_union.EV_XAK_TPDU.e_seq))
			return 0x3c;
		else
			return 0x3d;
	case 0x102:
		if (p->tp_class == TP_CLASS_0)
			return 0x2d;
		else
			return 0x2e;
	case 0x104:
		if (p->tp_class == TP_CLASS_0)
			return 0x2d;
		else
			return 0x2e;
	case 0x144:
		if (p->tp_class == TP_CLASS_0)
			return 0x3f;
		else
			return 0x40;
	case 0x162:
		if (p->tp_class == TP_CLASS_0)
			return 0x2b;
		else
			return 0x2c;
	case 0x172:
		if (p->tp_class != TP_CLASS_4)
			return 0x42;
		else
			return 0x46;
	case 0x174:
		if (p->tp_class != TP_CLASS_4)
			return 0x42;
		else
			return 0x47;
	case 0x177:
		if (p->tp_class != TP_CLASS_4)
			return 0x42;
		else
			return 0x43;
	case 0x188:
		if (p->tp_class == TP_CLASS_0)
			return 0xf;
		else if (tp_emit(CC_TPDU_type, p, 0, 0, MCPY(p->tp_ucddata, M_NOWAIT)) == 0)
			return 0x10;
		else
			return 0x11;
	default:
		return 0;
	}			/* end switch */
}				/* _Xebec_index() */
static const int      inx[26][9] =
{
    {0, 0, 0, 0, 0, 0, 0, 0, 0,},
    {0x0, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, -1, -1, -1, -1, 0x0, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x3e, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, -1, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x7, 0x15, 0x1b, -1, 0x17, 0x3, 0xa, 0x0,},
    {0x0, 0x19, 0x6, 0x20, 0x37, 0x8, 0x3, -1, 0x0,},
    {0x0, 0x14, 0x13, 0x13, 0x13, 0x16, -1, 0xa, 0x0,},
    {0x0, 0x7, 0x6, 0x1, 0x9, 0x18, 0x3, 0xa, 0x0,},
    {0x0, 0x19, -1, 0x1, 0x37, 0x8, 0x3, 0xa, 0x0,},
    {0x0, 0x7, -1, 0x26, -1, 0x8, 0x3, 0xa, 0x0,},
    {0x0, 0x7, 0x6, -1, -1, 0x8, 0x3, 0xa, 0x0,},
    {0x0, 0x7, 0x6, -1, -1, 0x8, 0x3, 0xa, 0x0,},
    {0x0, 0x7, 0x6, 0x1, -1, 0x8, 0x3, 0xa, 0x0,},
    {0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, -1, 0x2e, -1, 0x0, 0x4, 0x0, 0x2e,},
    {0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x39, 0x0, 0x0, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, -1, 0x0, 0x41, 0x0, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x28, 0x0, 0x41, 0x0, 0x0,},
    {0x0, 0xc, -1, 0x2c, 0x0, 0x2c, 0x4, 0xc, 0x2c,},
    {0x0, 0x49, -1, 0x45, -1, 0x44, 0x48, -1, 0x0,},
    {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -1,},
};
int
tp_driver(p, e)
	struct tp_pcb *p;
	struct tp_event *e;
{
	int    index, error = 0;
	const struct act_ent *a;
	static struct act_ent erroraction = {0, -1};

	index = inx[1 + e->ev_number][p->tp_state];
	if (index < 0)
		index = _Xebec_index(e, p);
	if (index == 0) {
		a = &erroraction;
	} else
		a = &statetable[index];

	if (a->a_action)
		error = _Xebec_action(a->a_action, e, p);
#ifdef TPPT
	if (tp_traceflag[D_DRIVER])
		tptrace(DRIVERTRACE, a->a_newstate, p->tp_state,
			e->ev_number, a->a_action, 0);
#endif
	if (error == 0)
		p->tp_state = a->a_newstate;
	return error;
}
