NetBSD-5.0.2/sys/netiso/tp.trans

/*	$NetBSD: tp.trans,v 1.12 2005/12/11 12:25:12 christos Exp $	*/

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

/***********************************************************
		Copyright IBM Corporation 1987

                      All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of IBM not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

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

/*
 * ARGO Project, Computer Sciences Dept., University of Wisconsin - Madison
 */
/*
 * Transition file for TP.
 *
 * DO NOT:
 * - change the order of any of the events or states.  to do so will
 *   make tppt, netstat, etc. cease working.
 *
 * NOTE:
 * some hooks exist for data on (dis)connect, but it's ***NOT***SUPPORTED***
 * (read: may not work!)
 *
 * I tried to put everything that causes a change of state in here, hence
 * there are some seemingly trivial events  like T_DETACH and T_LISTEN_req.
 *
 * Almost everything having to do w/ setting & cancelling timers is here
 * but once it was debugged, I moved the setting of the
 * keepalive (sendack) timer to tp_emit(), where an AK_TPDU is sent.
 * This is so the code wouldn't be duplicated all over creation in here.
 *
 */
*PROTOCOL tp

*INCLUDE
{
/* @(#)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/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 	trick_hc = 1;

int 	tp_emit(),
		tp_goodack(),				tp_goodXack(),
		tp_stash()
;
void	tp_indicate(),				tp_getoptions(),
		tp_soisdisconnecting(), 	tp_soisdisconnected(),
		tp_recycle_tsuffix(),
#ifdef TP_DEBUG_TIMERS
		tp_etimeout(),				tp_euntimeout(),
		tp_ctimeout(),				tp_cuntimeout(),
		tp_ctimeout_MIN(),
#endif
		tp_freeref(),				tp_detach(),
		tp0_stash(), 				tp0_send(),
		tp_netcmd(),				tp_send()
;

typedef  struct tp_pcb tpcb_struct;


}

*PCB    tpcb_struct 	SYNONYM  P

*STATES

TP_CLOSED
TP_CRSENT
TP_AKWAIT
TP_OPEN
TP_CLOSING
TP_REFWAIT
TP_LISTENING	/* Local to this implementation */
TP_CONFIRMING	/* Local to this implementation */

*EVENTS		{ struct timeval e_time; } 		SYNONYM  E

 /*
  * C (typically cancelled) timers  -
  *
  * let these be the first ones so for the sake of convenience
  * their values are 0--> n-1
  * DO NOT CHANGE THE ORDER OF THESE TIMER EVENTS!!
  */
 TM_inact
 TM_retrans
				/* TM_retrans is used for all
				 * simple retransmissions - CR,CC,XPD,DR
				 */

 TM_sendack
				/* TM_sendack does dual duty - keepalive AND closed-window
				 * Probes.
				 * It's set w/ keepalive-ticks every time an ack is sent.
				 * (this is done in (void) tp_emit() ).
				 * Whenever a DT arrives which doesn't require immediate acking,
				 * a separate fast-timeout flag is set ensuring 200ms response.
				 */
 TM_notused

 /*
  * E (typically expired) timers - these may be in any order.
  * These cause procedures to be executed directly; may not
  * cause an 'event' as we know them here.
  */
 TM_reference		{ SeqNum e_low; SeqNum e_high; int e_retrans; }
 TM_data_retrans	{ SeqNum e_low; SeqNum e_high; int e_retrans; }

/* NOTE: in tp_input is a minor optimization that assumes that
 * for all tpdu types that can take e_data and e_datalen, these
 * fields fall in the same place in the event structure, that is,
 * e_data is the first field and e_datalen is the 2nd field.
 */

 ER_TPDU  	 	{
				  u_char		e_reason;
				}
 CR_TPDU  	 	{ struct mbuf 	*e_data;	/* first field */
				  int 			e_datalen; /* 2nd field */
				  u_int			e_cdt;
				}
 DR_TPDU   	 	{ struct mbuf 	*e_data;	/* first field */
				  int 			e_datalen; /* 2nd field */
				  u_short		e_sref;
				  u_char		e_reason;
				}
 DC_TPDU
 CC_TPDU   	 	{ struct mbuf 	*e_data;	/* first field */
				  int 			e_datalen; /* 2nd field */
				  u_short		e_sref;
				  u_int			e_cdt;
				}
 AK_TPDU		{ u_int			e_cdt;
				  SeqNum 	 	e_seq;
				  SeqNum 	 	e_subseq;
				  u_char 	 	e_fcc_present;
				}
 DT_TPDU		{ struct mbuf	*e_data; 	/* first field */
				  int 			e_datalen; /* 2nd field */
				  u_int 		e_eot;
				  SeqNum		e_seq;
				}
 XPD_TPDU		{ struct mbuf 	*e_data;	/* first field */
				  int 			e_datalen; 	/* 2nd field */
				  SeqNum 		e_seq;
				}
 XAK_TPDU		{ SeqNum 		e_seq;		}

 T_CONN_req
 T_DISC_req		{ u_char		e_reason; 	}
 T_LISTEN_req
 T_DATA_req
 T_XPD_req
 T_USR_rcvd
 T_USR_Xrcvd
 T_DETACH
 T_NETRESET
 T_ACPT_req


*TRANSITIONS


/* TP_AKWAIT doesn't exist in TP 0 */
SAME			<==			TP_AKWAIT			[ CC_TPDU, DC_TPDU, XAK_TPDU ]
	DEFAULT
	NULLACTION
;


/* applicable in TP4, TP0 */
SAME			<==			TP_REFWAIT								DR_TPDU
	( $$.e_sref !=  0 )
	{
		(void) tp_emit(DC_TPDU_type, $P, 0, 0, NULL);
	}
;

/* applicable in TP4, TP0 */
SAME			<==			TP_REFWAIT			[ CR_TPDU, CC_TPDU, DT_TPDU,
					DR_TPDU, XPD_TPDU, AK_TPDU, XAK_TPDU, DC_TPDU, ER_TPDU ]
	DEFAULT
	{
#		ifdef TP_DEBUG
		if( $E.ev_number != AK_TPDU )
			printf("TPDU 0x%x in REFWAIT!!!!\n", $E.ev_number);
#		endif TP_DEBUG
	}
;

/* applicable in TP4, TP0 */
SAME			<==			TP_REFWAIT				[ T_DETACH, T_DISC_req ]
	DEFAULT
	NULLACTION
;

/* applicable in TP4, TP0 */
SAME			<==			TP_CRSENT								 AK_TPDU
	($P.tp_class == TP_CLASS_0)
	{
		/* oh, man is this grotesque or what? */
		(void) tp_goodack($P, $$.e_cdt, $$.e_seq,  $$.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
		 */
	}
;

/* applicable in TP4, TP0 */
SAME			<==			TP_CRSENT
					[ CR_TPDU, DC_TPDU, DT_TPDU, XPD_TPDU,  XAK_TPDU ]
	DEFAULT
	NULLACTION
;

/* applicable in TP4, TP0 */
SAME			<==			TP_CLOSED					[ DT_TPDU, XPD_TPDU,
										ER_TPDU, DC_TPDU, AK_TPDU, XAK_TPDU ]
	DEFAULT
	NULLACTION
;

/* TP_CLOSING doesn't exist in TP 0 */
SAME 			<== 		TP_CLOSING
					[ CC_TPDU, CR_TPDU, DT_TPDU, XPD_TPDU, AK_TPDU, XAK_TPDU ]
	DEFAULT
	NULLACTION
;


/* DC_TPDU doesn't exist in TP 0 */
SAME			<==			TP_OPEN						  DC_TPDU
	DEFAULT
	NULLACTION
;

/* applicable in TP4, TP0 */
SAME			<==		 	TP_LISTENING  [DR_TPDU, CC_TPDU, DT_TPDU, XPD_TPDU,
										 ER_TPDU, DC_TPDU, AK_TPDU, XAK_TPDU ]
	DEFAULT
	NULLACTION
;

/* applicable in TP4, TP0 */
TP_LISTENING	<==			TP_CLOSED  							T_LISTEN_req
	DEFAULT
	NULLACTION
;

/* applicable in TP4, TP0 */
TP_CLOSED  		<== 		[ TP_LISTENING, TP_CLOSED ] 			T_DETACH
	DEFAULT
	{
		tp_detach($P);
	}
;

TP_CONFIRMING	<==		 TP_LISTENING  								CR_TPDU
	( $P.tp_class == TP_CLASS_0)
	{
		$P.tp_refstate = REF_OPEN; /* has timers ??? */
	}
;

TP_CONFIRMING		<==		 TP_LISTENING  							CR_TPDU
	DEFAULT
	{
		IFTRACE(D_CONN)
			tptrace(TPPTmisc, "CR datalen data", $$.e_datalen, $$.e_data,0,0);
		ENDTRACE
		IFDEBUG(D_CONN)
			printf("CR datalen 0x%x data 0x%x", $$.e_datalen, $$.e_data);
		ENDDEBUG
		$P.tp_refstate = REF_OPEN; /* has timers */
		$P.tp_fcredit = $$.e_cdt;

		if ($$.e_datalen > 0) {
			/* n/a for class 0 */
			ASSERT($P.tp_Xrcv.sb_cc == 0);
			sbappendrecord(&$P.tp_Xrcv, $$.e_data);
			$$.e_data = NULL;
		}
	}
;

TP_OPEN		<==		 TP_CONFIRMING  								T_ACPT_req
	( $P.tp_class == TP_CLASS_0 )
	{
		IncStat(ts_tp0_conn);
		IFTRACE(D_CONN)
			tptrace(TPPTmisc, "Confiming", $P, 0,0,0);
		ENDTRACE
		IFDEBUG(D_CONN)
			printf("Confirming connection: $P" );
		ENDDEBUG
		soisconnected($P.tp_sock);
		(void) tp_emit(CC_TPDU_type, $P, 0,0, NULL) ;
		$P.tp_fcredit = 1;
	}
;

TP_AKWAIT		<==		 TP_CONFIRMING  							T_ACPT_req
	(tp_emit(CC_TPDU_type, $P, 0,0, MCPY($P.tp_ucddata, M_NOWAIT)) == 0)
	{
		IncStat(ts_tp4_conn); /* even though not quite open */
		IFTRACE(D_CONN)
			tptrace(TPPTmisc, "Confiming", $P, 0,0,0);
		ENDTRACE
		IFDEBUG(D_CONN)
			printf("Confirming connection: $P" );
		ENDDEBUG
		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);
	}
;

/* TP4 only */
TP_CLOSED		<==		 TP_CONFIRMING								T_ACPT_req
	DEFAULT /* emit failed */
	{
		IFDEBUG(D_CONN)
			printf("event: CR_TPDU emit CC failed done " );
		ENDDEBUG
		soisdisconnected($P.tp_sock);
		tp_recycle_tsuffix($P);
		tp_freeref($P.tp_lref);
		tp_detach($P);
	}
;

/* applicable in TP4, TP0 */
TP_CRSENT		<==		TP_CLOSED								T_CONN_req
	DEFAULT
	{
		int error;
		struct mbuf *data = NULL;

		IFTRACE(D_CONN)
			tptrace(TPPTmisc, "T_CONN_req flags ucddata", (int)$P.tp_flags,
			$P.tp_ucddata, 0, 0);
		ENDTRACE
		data =  MCPY($P.tp_ucddata, M_WAIT);
		if (data) {
			IFDEBUG(D_CONN)
				printf("T_CONN_req.trans m_copy cc 0x%x\n",
					$P.tp_ucddata);
				dump_mbuf(data, "sosnd @ T_CONN_req");
			ENDDEBUG
		}

		if (error = tp_emit(CR_TPDU_type, $P, 0, 0, data) )
			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);
		}
	}
;

/* applicable in TP4, TP0, but state TP_AKWAIT doesn't apply to TP0 */
TP_REFWAIT 		<==		[ TP_CRSENT, TP_AKWAIT, TP_OPEN ] 			DR_TPDU
	DEFAULT
	{
		sbflush(&$P.tp_Xrcv); /* purge non-delivered data data */
		if ($$.e_datalen > 0) {
			sbappendrecord(&$P.tp_Xrcv, $$.e_data);
			$$.e_data = NULL;
		}
		if ($P.tp_state == TP_OPEN)
			tp_indicate(T_DISCONNECT, $P, 0);
		else {
			int so_error = ECONNREFUSED;
			if ($$.e_reason != (E_TP_NO_SESSION ^ TP_ERROR_MASK) &&
			    $$.e_reason != (E_TP_NO_CR_ON_NC ^ TP_ERROR_MASK) &&
			    $$.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_sref !=  0 )
				(void) tp_emit(DC_TPDU_type, $P, 0, 0, NULL);
		}
	}
;

SAME 			<==		TP_CLOSED 									DR_TPDU
	DEFAULT
	{
		if( $$.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);
	}
;

/* NBS(34) */
TP_REFWAIT 		<==  	TP_CRSENT  									ER_TPDU
	DEFAULT
	{
		tp_cuntimeout($P, TM_retrans);
		tp_indicate(ER_TPDU, $P, $$.e_reason);
		tp_soisdisconnected($P);
	}
;

/* NBS(27) */
TP_REFWAIT		<==		TP_CLOSING									DR_TPDU
	DEFAULT
	{
		tp_cuntimeout($P, TM_retrans);
		tp_soisdisconnected($P);
	}
;
/* these two transitions are the same but can't be combined because xebec
 * can't handle the use of $$.e_reason if they're combined
 */
/* NBS(27) */
TP_REFWAIT		<==		TP_CLOSING									ER_TPDU
	DEFAULT
	{
		tp_indicate(ER_TPDU, $P, $$.e_reason);
		tp_cuntimeout($P, TM_retrans);
		tp_soisdisconnected($P);
	}
;
/* NBS(27) */
TP_REFWAIT		<==		TP_CLOSING									DC_TPDU
	DEFAULT
	{
		tp_cuntimeout($P, TM_retrans);
		tp_soisdisconnected($P);
	}
;

/* NBS(21) */
SAME 			<== 	TP_CLOSED 						[ CC_TPDU, CR_TPDU ]
	DEFAULT
	{	/* 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 */
	}
;

/* NBS(34) */
TP_REFWAIT 		<== 	TP_OPEN  				 					ER_TPDU
	($P.tp_class == TP_CLASS_0)
	{
		tp_soisdisconnecting($P.tp_sock);
		tp_indicate(ER_TPDU, $P, $$.e_reason);
		tp_soisdisconnected($P);
		tp_netcmd( $P, CONN_CLOSE );
	}
;

TP_CLOSING 		<== 	[ TP_AKWAIT, TP_OPEN ]  					ER_TPDU
	DEFAULT
	{
		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_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);
	}
;
/* NBS(6) */
TP_OPEN			<==		TP_CRSENT									CC_TPDU
	($P.tp_class == TP_CLASS_0)
	{
		tp_cuntimeout($P, TM_retrans);
		IncStat(ts_tp0_conn);
		$P.tp_fcredit = 1;
		soisconnected($P.tp_sock);
	}
;

TP_OPEN			<==		TP_CRSENT									CC_TPDU
	DEFAULT
	{
		IFDEBUG(D_CONN)
			printf("trans: CC_TPDU in CRSENT state flags 0x%x\n",
			    (int)$P.tp_flags);
		ENDDEBUG
		IncStat(ts_tp4_conn);
		$P.tp_fref = $$.e_sref;
		$P.tp_fcredit = $$.e_cdt;
		if (($P.tp_rx_strat & TPRX_FASTSTART) && ($$.e_cdt > 0))
			$P.tp_cong_win = $$.e_cdt * $P.tp_l_tpdusize;
		tp_getoptions($P);
		tp_cuntimeout($P, TM_retrans);
		if ($P.tp_ucddata) {
			IFDEBUG(D_CONN)
				printf("dropping user connect data cc 0x%x\n",
				    $P.tp_ucddata->m_len);
			ENDDEBUG
			m_freem($P.tp_ucddata);
			$P.tp_ucddata = 0;
		}
		soisconnected($P.tp_sock);
		if ($$.e_datalen > 0) {
			ASSERT($P.tp_Xrcv.sb_cc == 0); /* should be empty */
			sbappendrecord(&$P.tp_Xrcv, $$.e_data);
			$$.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);
	}
;

/* TP4 only */
SAME			<==		TP_CRSENT									TM_retrans
	(	$P.tp_retrans > 0 )
	{
		struct mbuf *data = NULL;
		int error;

		IncStat(ts_retrans_cr);
		$P.tp_cong_win = 1 * $P.tp_l_tpdusize;
		data = MCPY($P.tp_ucddata, M_NOWAIT);
		if($P.tp_ucddata) {
			IFDEBUG(D_CONN)
				printf("TM_retrans.trans m_copy cc 0x%x\n", data);
				dump_mbuf($P.tp_ucddata, "sosnd @ TM_retrans");
			ENDDEBUG
			if( data == NULL )
				return ENOBUFS;
		}

		$P.tp_retrans--;
		if( error = tp_emit(CR_TPDU_type, $P, 0, 0, data) ) {
			$P.tp_sock->so_error = error;
		}
		tp_ctimeout($P, TM_retrans, (int)$P.tp_cr_ticks);
	}
;

/* TP4 only  */
TP_REFWAIT		<==		TP_CRSENT									TM_retrans
	DEFAULT /* no more CR retransmissions */
	{
		IncStat(ts_conn_gaveup);
		$P.tp_sock->so_error = ETIMEDOUT;
		tp_indicate(T_DISCONNECT, $P, ETIMEDOUT);
		tp_soisdisconnected($P);
	}
;

/* TP4 only */
SAME 			<==	 TP_AKWAIT											CR_TPDU
	DEFAULT
	/* duplicate CR (which doesn't really exist in the context of
	 * a connectionless network layer)
	 * Doesn't occur in class 0.
	 */
	{
		int error;
		struct mbuf *data = MCPY($P.tp_ucddata, M_WAIT);

		if( error = tp_emit(CC_TPDU_type, $P, 0, 0, data) ) {
			$P.tp_sock->so_error = error;
		}
		$P.tp_retrans = $P.tp_Nretrans;
		tp_ctimeout($P, TM_retrans, (int)$P.tp_cc_ticks);
	}
;

/* TP4 only */
TP_OPEN			<==		TP_AKWAIT 										DT_TPDU
	( IN_RWINDOW( $P, $$.e_seq,
					$P.tp_rcvnxt, SEQ($P, $P.tp_rcvnxt + $P.tp_lcredit)) )
	{
		int doack;

		/*
		 * 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);
		IFDEBUG(D_DATA)
			printf("tp_stash returns %d\n",doack);
		ENDDEBUG

		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);

		IFDEBUG(D_DATA)
			printf("after stash calling sbwakeup\n");
		ENDDEBUG
	}
;

SAME			<==		TP_OPEN 									DT_TPDU
	( $P.tp_class == TP_CLASS_0 )
	{
		tp0_stash($P, $E);
		sbwakeup( &$P.tp_sock->so_rcv );

		IFDEBUG(D_DATA)
			printf("after stash calling sbwakeup\n");
		ENDDEBUG
	}
;

/* TP4 only */
SAME			<==		TP_OPEN 									DT_TPDU
	( IN_RWINDOW( $P, $$.e_seq,
					$P.tp_rcvnxt, SEQ($P, $P.tp_rcvnxt + $P.tp_lcredit)) )
	{
		int doack; /* tells if we must ack immediately */

		tp_ctimeout($P, TM_inact, (int)$P.tp_inact_ticks);
		sbwakeup( &$P.tp_sock->so_rcv );

		doack = tp_stash($P, $E);
		IFDEBUG(D_DATA)
			printf("tp_stash returns %d\n",doack);
		ENDDEBUG

		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);

		IFDEBUG(D_DATA)
			printf("after stash calling sbwakeup\n");
		ENDDEBUG
	}
;

/* Not in window  - we must ack under certain circumstances, namely
 * a) if the seq number is below lwe but > lwe - (max credit ever given)
 * (to handle lost acks) Can use max-possible-credit for this ^^^.
 * and
 * b) seq number is > uwe but < uwe + previously sent & withdrawn credit
 *
 * (see 12.2.3.8.1 of ISO spec, p. 73)
 * We just always ack.
 */
/* TP4 only */
SAME 			<== 	[ TP_OPEN, TP_AKWAIT ]							DT_TPDU
	DEFAULT /* Not in window */
	{
		IFTRACE(D_DATA)
			tptrace(TPPTmisc, "NIW seq rcvnxt lcredit ",
				$$.e_seq, $P.tp_rcvnxt, $P.tp_lcredit, 0);
		ENDTRACE
		IncStat(ts_dt_niw);
		m_freem($$.e_data);
		tp_ctimeout($P, TM_inact, (int)$P.tp_inact_ticks);
		(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, NULL );
	}
;

/* TP4 only */
TP_OPEN			<==		TP_AKWAIT										AK_TPDU
	DEFAULT
	{
		if ($P.tp_ucddata) {
			m_freem($P.tp_ucddata);
			$P.tp_ucddata = 0;
		}
		(void) tp_goodack($P, $$.e_cdt, $$.e_seq, $$.e_subseq);
		tp_cuntimeout($P, TM_retrans);

		soisconnected($P.tp_sock);
		IFTRACE(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);
		ENDTRACE

		tp_ctimeout($P, TM_sendack, (int)$P.tp_keepalive_ticks);
		tp_ctimeout($P, TM_inact, (int)$P.tp_inact_ticks);
	}
;

/* TP4 only */
TP_OPEN 		<== 	[ TP_OPEN, TP_AKWAIT ]						XPD_TPDU
	($P.tp_Xrcvnxt == $$.e_seq)
	{
		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);
		}
		IFTRACE(D_XPD)
		tptrace(TPPTmisc, "XPD tpdu accepted Xrcvnxt, e_seq datalen m_len\n",
				$P.tp_Xrcvnxt,$$.e_seq,  $$.e_datalen, $$.e_data->m_len);
		ENDTRACE

		$P.tp_sock->so_state |= SS_RCVATMARK;
		$$.e_data->m_flags |= M_EOR;
		sbinsertoob(&$P.tp_Xrcv, $$.e_data);
		IFDEBUG(D_XPD)
			dump_mbuf($$.e_data, "XPD TPDU: tp_Xrcv");
		ENDDEBUG
		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);
	}
;

/* TP4 only */
SAME			<==		TP_OPEN 									T_USR_Xrcvd
	DEFAULT
	{
		if( $P.tp_Xrcv.sb_cc == 0 ) {
			/* kludge for select(): */
			/* $P.tp_sock->so_state &= ~SS_OOBAVAIL; */
		}
	}
	/* OLD WAY:
	 * Ack only after the user receives the XPD.  This is better for
	 * users that use one XPD right after another.
	 * Acking right away (the NEW WAY, see the prev. transition) is
	 * better for occasional * XPD, when the receiving user doesn't
	 * want to read the XPD immediately (which is session's behavior).
	 *
		int error = tp_emit(XAK_TPDU_type, $P, $P.tp_Xrcvnxt, 0, NULL);
		SEQ_INC($P, $P.tp_Xrcvnxt);
		return error;
	*/
;

/* NOTE: presently if the user doesn't read the connection data
 * before and expedited data PDU comes in, the connection data will
 * be dropped. This is a bug.  To avoid it, we need somewhere else
 * to put the connection data.
 * On the other hand, we need not to have it sitting around forever.
 * This is a problem with the idea of trying to accommodate
 * data on connect w/ a passive-open user interface.
 */
/* TP4 only */

SAME	 		<== 	[ TP_AKWAIT, TP_OPEN ] 							XPD_TPDU
	DEFAULT /* not in window or cdt==0 */
	{
		IFTRACE(D_XPD)
			tptrace(TPPTmisc, "XPD tpdu niw (Xrcvnxt, e_seq) or not cdt (cc)\n",
				$P.tp_Xrcvnxt, $$.e_seq,  $P.tp_Xrcv.sb_cc , 0);
		ENDTRACE
		if( $P.tp_Xrcvnxt != $$.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_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)
		 */
	}
;

/* Occurs (AKWAIT, OPEN) when parent (listening) socket gets aborted, and tries
 * to detach all its "children"
 * Also (CRSENT) when user kills a job that's doing a connect()
 */
TP_REFWAIT		<== 	TP_CRSENT 										T_DETACH
	($P.tp_class == TP_CLASS_0)
	{
		struct socket *so = $P.tp_sock;

		/* 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);
	}
;

/* TP4 only */
TP_CLOSING		<== [ TP_CLOSING, TP_AKWAIT, TP_CRSENT, TP_CONFIRMING ]	T_DETACH
	DEFAULT
	{
		struct socket *so = $P.tp_sock;
		struct mbuf *data = NULL;

		/* 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);
		}
	}
;

TP_REFWAIT		<==		[ TP_OPEN, TP_CRSENT ]		 	  			T_DISC_req
	( $P.tp_class == TP_CLASS_0 )
	{
		tp_soisdisconnecting($P.tp_sock);
		tp_netcmd( $P, CONN_CLOSE);
		tp_soisdisconnected($P);
	}
;

/* TP4 only */
TP_CLOSING		<==	[ TP_AKWAIT, TP_OPEN, TP_CRSENT, TP_CONFIRMING ]  T_DISC_req
	DEFAULT
	{
		struct mbuf *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) {
			IFDEBUG(D_CONN)
				printf("T_DISC_req.trans tp_ucddata 0x%x\n",
					$P.tp_ucddata);
				dump_mbuf(data, "ucddata @ T_DISC_req");
			ENDDEBUG
		}
		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_reason, data);
	}
;

/* TP4 only */
SAME			<==		TP_AKWAIT									TM_retrans
	( $P.tp_retrans > 0 )
	{
		int error;
		struct mbuf *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) )
			$P.tp_sock->so_error = error;
		tp_ctimeout($P, TM_retrans, (int)$P.tp_cc_ticks);
	}
;

/* TP4 only */
TP_CLOSING		<==		TP_AKWAIT									TM_retrans
	DEFAULT  /* out of time */
	{
		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);
	}
;

/* the retrans timers had better go off BEFORE the inactivity timer does,
 * if transmissions are going on.
 * (i.e., TM_inact should be greater than timer for all retrans plus ack
 * turnaround)
 */
/* TP4 only */
TP_CLOSING 		<==		TP_OPEN		   [ TM_inact, TM_retrans, TM_data_retrans ]
	DEFAULT
	{
		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);
	}
;

/* TP4 only */
SAME			<==		TP_OPEN										TM_retrans
	( $P.tp_retrans > 0 )
	{
		$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;

			IFTRACE(D_XPD)
				tptrace(TPPTmisc, "XPD retrans: Xuna Xsndnxt sndnxt snduna",
					$P.tp_Xuna, $P.tp_Xsndnxt, $P.tp_sndnxt,
					$P.tp_snduna);
			ENDTRACE
			IFDEBUG(D_XPD)
				dump_mbuf(m, "XPD retrans emitting M");
			ENDDEBUG
			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);
		}
	}
;

/* TP4 only */
SAME 			<==		TP_OPEN									TM_data_retrans
	($P.tp_rxtshift < TP_NRETRANS)
	{
		$P.tp_rxtshift++;
		(void) tp_data_retrans($P);
	}
;

/* TP4 only */
SAME	 		<==		TP_CLOSING									TM_retrans
	(	$P.tp_retrans > 0 )
	{
		$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);
	}
;

/* TP4 only */
TP_REFWAIT 		<==		TP_CLOSING									TM_retrans
	DEFAULT	/* no more retrans - gave up */
	{
		$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);
	}
;

/*
 * The resources are kept around until the ref timer goes off.
 * The suffixes are wiped out sooner so they can be reused right away.
 */
/* applicable in TP4, TP0 */
TP_CLOSED 		<==		TP_REFWAIT 									TM_reference
	DEFAULT
	{
		tp_freeref($P.tp_lref);
		tp_detach($P);
	}
;

/* applicable in TP4, TP0 */
/* A duplicate CR from connectionless network layer can't happen */
SAME 			<== 	TP_OPEN 							[ CR_TPDU, CC_TPDU ]
	DEFAULT
	{
		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 */
	}
;

/* applicable in TP4, TP0 */
SAME			<== 	TP_OPEN									T_DATA_req
	DEFAULT
	{
		IFTRACE(D_DATA)
			tptrace(TPPTmisc, "T_DATA_req sndnxt snduna fcredit, tpcb",
				$P.tp_sndnxt, $P.tp_snduna, $P.tp_fcredit, $P);
		ENDTRACE

		tp_send($P);
	}
;

/* TP4 only */
SAME			<==		TP_OPEN										T_XPD_req
	DEFAULT
		/* T_XPD_req was issued by sosend iff xpd socket buf was empty
		 * at time of sosend(),
		 * AND (which means) there were no unacknowledged XPD tpdus outstanding!
		 */
	{
		int 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
			 */

			IFTRACE(D_XPD)
				tptrace(TPPTmisc, "XPD req: Xuna Xsndnxt sndnxt snduna",
					$P.tp_Xuna, $P.tp_Xsndnxt, $P.tp_sndnxt,
					$P.tp_snduna);
			ENDTRACE
			IFDEBUG(D_XPD)
				printf("T_XPD_req: sb_cc 0x%x\n", $P.tp_Xsnd.sb_cc);
				dump_mbuf(m, "XPD req emitting M");
			ENDDEBUG
			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;
	}
;

/* TP4, faked ack in TP0 when cons send completes */
SAME 			<==		TP_OPEN 									AK_TPDU
	( tp_goodack($P, $$.e_cdt, $$.e_seq, $$.e_subseq)  )

	/* tp_goodack == true means
	 * EITHER it actually acked something heretofore unacknowledged
	 * OR no news but the credit should be processed.
	 */
	{
		struct sockbuf *sb = &$P.tp_sock->so_snd;

		IFDEBUG(D_ACKRECV)
			printf("GOOD ACK seq 0x%x cdt 0x%x\n", $$.e_seq, $$.e_cdt);
		ENDDEBUG
		if( $P.tp_class != TP_CLASS_0) {
			tp_ctimeout($P, TM_inact, (int)$P.tp_inact_ticks);
		}
		sbwakeup(sb);
		IFDEBUG(D_ACKRECV)
			printf("GOOD ACK new sndnxt 0x%x\n", $P.tp_sndnxt);
		ENDDEBUG
	}
;

/* TP4, and TP0 after sending a CC or possibly a CR */
SAME			<==		TP_OPEN 			 						 AK_TPDU
	DEFAULT
	{
		IFTRACE(D_ACKRECV)
			tptrace(TPPTmisc, "BOGUS ACK fcc_present, tp_r_subseq e_subseq",
				$$.e_fcc_present, $P.tp_r_subseq, $$.e_subseq, 0);
		ENDTRACE
		if( $P.tp_class != TP_CLASS_0 ) {

			if ( !$$.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);
		}
	}
;

/* NBS(47) */
	/* goes in at *** */
		/* just so happens that this is never true now, because we allow
		 * only 1 packet in the queue at once (this could be changed)
		if	( $P.tp_Xsnd.sb_mb )  {
			struct mbuf *m = m_copy($P.tp_Xsnd.sb_mb, 0, ??);

			(void) 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_xpd_ticks);
			SEQ_INC($P, $P.tp_Xsndnxt);
		}
		 */
	/* end of the above hack */

/* TP4 only */
SAME			<== 	TP_OPEN										XAK_TPDU
	( tp_goodXack($P, $$.e_seq) )
	/* tp_goodXack checks for good ack, removes the correct
	 * tpdu from the queue and  returns 1 if ack was legit, 0 if not.
	 * also updates tp_Xuna
	 */
	{
		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);
	}
;

/* TP4, and TP0 after sending a CC or possibly a CR */
SAME			<==		TP_OPEN 			 						XAK_TPDU
	DEFAULT
	{
		IFTRACE(D_ACKRECV)
			tptrace(TPPTmisc, "BOGUS XACK eventtype ", $E.ev_number, 0, 0,0);
		ENDTRACE
		if( $P.tp_class != TP_CLASS_0 ) {
			tp_ctimeout($P, TM_inact, (int)$P.tp_inact_ticks);
		}
	}
;

/* TP4 only */
SAME			<==		TP_OPEN 								TM_sendack
	DEFAULT
	{
		int timo;
		IFTRACE(D_TIMER)
			tptrace(TPPTsendack, -1, $P.tp_lcredit, $P.tp_sent_uwe,
			$P.tp_sent_lcdt, 0);
		ENDTRACE
		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);
	}
;

/* TP0 only */
SAME			<==		TP_OPEN 									T_USR_rcvd
	($P.tp_class == TP_CLASS_0)
	{
		if (sbspace(&$P.tp_sock->so_rcv) > 0)
			tp0_openflow($P);
	}
;

/* TP4 only */
		/* If old credit was zero,
		 * we'd better inform other side that we now have space
		 * But this is not enough.  Sender might not yet have
		 * seen an ack with cdt 0 but it might still think the
		 * window is closed, so it's going to wait.
		 * Best to send an ack each time.
		 * Strictly speaking, this ought to be a function of the
		 * general ack strategy.
		 */
SAME			<==		TP_OPEN 									T_USR_rcvd
	DEFAULT
	{
		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);
			}
		}
	}
;

/* applicable in TP4, TP0 */
SAME			<==		TP_REFWAIT 				[ T_USR_rcvd, T_USR_Xrcvd ]
	DEFAULT
	/* This happens if other end sent a DR when  the user was waiting
	 * on a receive.
	 * Processing the DR includes putting us in REFWAIT state.
	 */
	{
		if(trick_hc)
		return ECONNABORTED;
	}
;

/* TP0 only */
TP_REFWAIT		<==		[ TP_OPEN, TP_CRSENT, TP_LISTENING ] 	T_NETRESET
	( $P.tp_class != TP_CLASS_4 )
		/* 0 or (4 and 0) */
		/* in OPEN class will be 0 or 4 but not both */
		/* in CRSENT or LISTENING it could be in negotiation, hence both */
		/* Actually, this shouldn't ever happen in LISTENING */
	{
		ASSERT( $P.tp_state != TP_LISTENING );
		tp_indicate(T_DISCONNECT, $P, ECONNRESET);
		tp_soisdisconnected($P);
	}
;

/* TP4: ignore resets */
SAME		<==		[ TP_OPEN, TP_CRSENT, TP_AKWAIT,
						TP_CLOSING, TP_LISTENING ] 				T_NETRESET
	DEFAULT
	NULLACTION
;

/* applicable in TP4, TP0 */
SAME			<==		[ TP_CLOSED, TP_REFWAIT ]				T_NETRESET
	DEFAULT
	NULLACTION
;

/* C'EST TOUT */