/***********************************************************
Copyright IBM Corporation 1987
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
******************************************************************/
* ARGO Project, Computer Sciences Dept., University of Wisconsin - Madison
/* $Header: tp.trans,v 5.1 88/10/12 12:22:07 root Exp $
* Transition file for TP.
* - change the order of any of the events or states. to do so will
* make tppt, netstat, etc. cease working.
* some hooks exist for data on (dis)connect, but it's ***NOT***SUPPORTED***
* 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.
/* @(#)tp.trans 7.5 (Berkeley) %G% */
#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)
tp_goodack(), tp_goodXack(),
void tp_indicate(), tp_getoptions(),
tp_soisdisconnecting(), tp_soisdisconnected(),
tp_etimeout(), tp_euntimeout(),
tp_euntimeout_lss(), tp_ctimeout(),
tp_cuntimeout(), tp_ctimeout_MIN(),
tp_freeref(), tp_detach(),
typedef struct tp_pcb tpcb_struct;
*PCB tpcb_struct SYNONYM P
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_retrans is used for all
* simple retransmissions - CR,CC,XPD,DR
/* TM_sendack does dual duty - keepalive AND sendack.
* It's set w/ keepalive-ticks every time an ack is sent.
* (this is done in (void) tp_emit() ).
* It's cancelled and reset whenever a DT
* arrives and it doesn't require immediate acking.
* Note that in this case it's set w/ the minimum of
* its prev value and the sendack-ticks value so the
* purpose of the keepalive is preserved.
* 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.
CR_TPDU { struct mbuf *e_data; /* first field */
int e_datalen; /* 2nd field */
DR_TPDU { struct mbuf *e_data; /* first field */
int e_datalen; /* 2nd field */
CC_TPDU { struct mbuf *e_data; /* first field */
int e_datalen; /* 2nd field */
DT_TPDU { struct mbuf *e_data; /* first field */
int e_datalen; /* 2nd field */
XPD_TPDU { struct mbuf *e_data; /* first field */
int e_datalen; /* 2nd field */
XAK_TPDU { SeqNum e_seq; }
T_DISC_req { u_char e_reason; }
/* TP_AKWAIT doesn't exist in TP 0 */
SAME <== TP_AKWAIT [ CC_TPDU, DC_TPDU, XAK_TPDU ]
/* applicable in TP4, TP0 */
SAME <== TP_REFWAIT DR_TPDU
(void) tp_emit(DC_TPDU_type, $P, 0, 0, MNULL);
/* 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 ]
if( $E.ev_number != AK_TPDU )
printf("TPDU 0x%x in REFWAIT!!!!\n", $E.ev_number);
/* applicable in TP4, TP0 */
SAME <== TP_REFWAIT [ T_DETACH, T_DISC_req ]
/* 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 */
[ CR_TPDU, DC_TPDU, DT_TPDU, XPD_TPDU, XAK_TPDU ]
/* applicable in TP4, TP0 */
SAME <== TP_CLOSED [ DT_TPDU, XPD_TPDU,
ER_TPDU, DC_TPDU, AK_TPDU, XAK_TPDU ]
/* TP_CLOSING doesn't exist in TP 0 */
[ CC_TPDU, CR_TPDU, DT_TPDU, XPD_TPDU, AK_TPDU, XAK_TPDU ]
/* DC_TPDU doesn't exist in TP 0 */
/* applicable in TP4, TP0 */
SAME <== TP_LISTENING [DR_TPDU, CC_TPDU, DT_TPDU, XPD_TPDU,
ER_TPDU, DC_TPDU, AK_TPDU, XAK_TPDU ]
/* applicable in TP4, TP0 */
TP_LISTENING <== TP_CLOSED T_LISTEN_req
/* applicable in TP4, TP0 */
TP_CLOSED <== [ TP_LISTENING, TP_CLOSED ] T_DETACH
TP_CONFIRMING <== TP_LISTENING CR_TPDU
( $P.tp_class == TP_CLASS_0)
$P.tp_refp->tpr_state = REF_OPEN; /* has timers ??? */
TP_CONFIRMING <== TP_LISTENING CR_TPDU
tptrace(TPPTmisc, "CR datalen data", $$.e_datalen, $$.e_data,0,0);
printf("CR datalen 0x%x data 0x%x", $$.e_datalen, $$.e_data);
$P.tp_refp->tpr_state = REF_OPEN; /* has timers */
$P.tp_fcredit = $$.e_cdt;
ASSERT($P.tp_Xrcv.sb_cc == 0);
sbappendrecord(&$P.tp_Xrcv, $$.e_data);
/*$P.tp_flags |= TPF_CONN_DATA_IN;*/
TP_OPEN <== TP_CONFIRMING T_ACPT_req
( $P.tp_class == TP_CLASS_0 )
tptrace(TPPTmisc, "Confiming", $P, 0,0,0);
printf("Confirming connection: $P" );
soisconnected($P.tp_sock);
(void) tp_emit(CC_TPDU_type, $P, 0,0, MNULL) ;
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 */
tptrace(TPPTmisc, "Confiming", $P, 0,0,0);
printf("Confirming connection: $P" );
soisconnecting($P.tp_sock);
if($P.tp_rx_strat & TPRX_FASTSTART)
$P.tp_cong_win = $P.tp_fcredit;
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_cc_ticks);
TP_CLOSED <== TP_CONFIRMING T_ACPT_req
DEFAULT /* emit failed */
register struct tp_ref *r = $P.tp_refp;
printf("event: CR_TPDU emit CC failed done " );
soisdisconnected($P.tp_sock);
tp_recycle_tsuffix( $P );
/* applicable in TP4, TP0 */
TP_CRSENT <== TP_CLOSED T_CONN_req
struct mbuf *data = MNULL;
tptrace(TPPTmisc, "T_CONN_req flags ucddata", (int)$P.tp_flags,
data = MCPY($P.tp_ucddata, M_WAIT);
printf("T_CONN_req.trans m_copy cc 0x%x\n",
dump_mbuf(data, "sosnd @ T_CONN_req");
if (error = tp_emit(CR_TPDU_type, $P, 0, 0, data) )
return error; /* driver WON'T change state; will return error */
$P.tp_refp->tpr_state = REF_OPEN; /* has timers */
if($P.tp_class != TP_CLASS_0) {
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, 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
if ($$.e_datalen > 0 && $P.tp_class != TP_CLASS_0) {
/*sbdrop(&$P.tp_Xrcv, $P.tp_Xrcv.sb_cc); /* purge expedited data */
$P.tp_flags |= TPF_DISC_DATA_IN;
sbappendrecord(&$P.tp_Xrcv, $$.e_data);
tp_indicate(T_DISCONNECT, $P, TP_ERROR_MASK | (u_short)$$.e_reason);
if ($P.tp_class != TP_CLASS_0) {
if ($P.tp_state == TP_OPEN ) {
tp_euntimeout($P.tp_refp, TM_data_retrans); /* all */
tp_cuntimeout($P.tp_refp, TM_retrans);
tp_cuntimeout($P.tp_refp, TM_inact);
tp_cuntimeout($P.tp_refp, TM_sendack);
tp_cuntimeout($P.tp_refp, TM_retrans);
(void) tp_emit(DC_TPDU_type, $P, 0, 0, MNULL);
SAME <== TP_CLOSED DR_TPDU
(void) tp_emit(DC_TPDU_type, $P, 0, 0, MNULL);
/* reference timer already set - reset it to be safe (???) */
tp_euntimeout($P.tp_refp, TM_reference); /* all */
tp_etimeout($P.tp_refp, TM_reference, 0, 0, 0, (int)$P.tp_refer_ticks);
TP_REFWAIT <== TP_CRSENT ER_TPDU
tp_cuntimeout($P.tp_refp, TM_retrans);
tp_indicate(T_DISCONNECT, $P,
TP_ERROR_MASK |(u_short)($$.e_reason | 0x40));
TP_REFWAIT <== TP_CLOSING DR_TPDU
$P.tp_sock->so_error = (u_short)$$.e_reason;
tp_cuntimeout($P.tp_refp, TM_retrans);
/* 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
TP_REFWAIT <== TP_CLOSING ER_TPDU
$P.tp_sock->so_error = (u_short)$$.e_reason;
tp_cuntimeout($P.tp_refp, TM_retrans);
TP_REFWAIT <== TP_CLOSING DC_TPDU
tp_cuntimeout($P.tp_refp, TM_retrans);
SAME <== TP_CLOSED [ CC_TPDU, CR_TPDU ]
{ /* 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, MNULL);
/* don't bother with retransmissions of the DR */
TP_REFWAIT <== TP_OPEN ER_TPDU
($P.tp_class == TP_CLASS_0)
tp_soisdisconnecting($P.tp_sock);
tp_indicate(T_DISCONNECT, $P,
TP_ERROR_MASK |(u_short)($$.e_reason | 0x40));
tp_netcmd( $P, CONN_CLOSE );
TP_CLOSING <== [ TP_AKWAIT, TP_OPEN ] ER_TPDU
if ($P.tp_state == TP_OPEN) {
tp_euntimeout($P.tp_refp, TM_data_retrans); /* all */
tp_cuntimeout($P.tp_refp, TM_inact);
tp_cuntimeout($P.tp_refp, TM_sendack);
tp_soisdisconnecting($P.tp_sock);
tp_indicate(T_DISCONNECT, $P,
TP_ERROR_MASK |(u_short)($$.e_reason | 0x40));
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_dr_ticks);
(void) tp_emit(DR_TPDU_type, $P, 0, E_TP_PROTO_ERR, MNULL);
TP_OPEN <== TP_CRSENT CC_TPDU
($P.tp_class == TP_CLASS_0)
tp_cuntimeout($P.tp_refp, TM_retrans);
soisconnected($P.tp_sock);
TP_OPEN <== TP_CRSENT CC_TPDU
printf("trans: CC_TPDU in CRSENT state flags 0x%x\n",
$P.tp_fcredit = $$.e_cdt;
if($P.tp_rx_strat & TPRX_FASTSTART)
$P.tp_cong_win = $$.e_cdt;
tp_cuntimeout($P.tp_refp, TM_retrans);
printf("dropping user connect data cc 0x%x\n",
soisconnected($P.tp_sock);
ASSERT($P.tp_Xrcv.sb_cc == 0); /* should be empty */
sbappendrecord(&$P.tp_Xrcv, $$.e_data);
$P.tp_flags |= TPF_CONN_DATA_IN;
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL);
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
SAME <== TP_CRSENT TM_retrans
struct mbuf *data = MNULL;
data = MCPY($P.tp_ucddata, M_NOWAIT);
printf("TM_retrans.trans m_copy cc 0x%x\n", data);
dump_mbuf($P.tp_ucddata, "sosnd @ TM_retrans");
if( error = tp_emit(CR_TPDU_type, $P, 0, 0, data) ) {
$P.tp_sock->so_error = error;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_cr_ticks);
TP_REFWAIT <== TP_CRSENT TM_retrans
DEFAULT /* no more CR retransmissions */
$P.tp_sock->so_error = ETIMEDOUT;
tp_indicate(T_DISCONNECT, $P, ETIMEDOUT);
SAME <== TP_AKWAIT CR_TPDU
/* duplicate CR (which doesn't really exist in the context of
* a connectionless network layer)
* Doesn't occur in class 0.
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.tp_refp, TM_retrans, (int)$P.tp_cc_ticks);
TP_OPEN <== TP_AKWAIT DT_TPDU
( IN_RWINDOW( $P, $$.e_seq,
$P.tp_rcvnxt, SEQ($P, $P.tp_rcvnxt + $P.tp_lcredit)) )
* Get rid of any confirm or connect data, so that if we
* crash or close, it isn't thought of as disconnect data.
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
tp_cuntimeout($P.tp_refp, TM_retrans);
soisconnected($P.tp_sock);
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
/* see also next 2 transitions, if you make any changes */
doack = tp_stash($P, $E);
printf("tp_stash returns %d\n",doack);
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL );
tp_ctimeout($P.tp_refp, TM_sendack, (int)$P.tp_keepalive_ticks);
tp_ctimeout( $P.tp_refp, TM_sendack, (int)$P.tp_sendack_ticks);
printf("after stash calling sbwakeup\n");
( $P.tp_class == TP_CLASS_0 )
sbwakeup( &$P.tp_sock->so_rcv );
printf("after stash calling sbwakeup\n");
( 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.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
sbwakeup( &$P.tp_sock->so_rcv );
doack = tp_stash($P, $E);
printf("tp_stash returns %d\n",doack);
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL );
tp_ctimeout_MIN( $P.tp_refp, TM_sendack, (int)$P.tp_sendack_ticks);
printf("after stash calling sbwakeup\n");
/* 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 ^^^.
* b) seq number is > uwe but < uwe + previously sent & withdrawn credit
* (see 12.2.3.8.1 of ISO spec, p. 73)
SAME <== [ TP_OPEN, TP_AKWAIT ] DT_TPDU
DEFAULT /* Not in window */
tptrace(TPPTmisc, "NIW seq rcvnxt lcredit ",
$$.e_seq, $P.tp_rcvnxt, $P.tp_lcredit, 0);
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL );
TP_OPEN <== TP_AKWAIT AK_TPDU
(void) tp_goodack($P, $$.e_cdt, $$.e_seq, $$.e_subseq);
tp_cuntimeout($P.tp_refp, TM_retrans);
soisconnected($P.tp_sock);
struct socket *so = $P.tp_sock;
"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);
"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);
tp_ctimeout($P.tp_refp, TM_sendack, (int)$P.tp_keepalive_ticks);
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
TP_OPEN <== [ TP_OPEN, TP_AKWAIT ] XPD_TPDU
( $P.tp_Xrcvnxt == $$.e_seq /* && $P.tp_Xrcv.sb_cc == 0*/)
if( $P.tp_state == TP_AKWAIT ) {
tp_cuntimeout($P.tp_refp, TM_retrans);
soisconnected($P.tp_sock);
tp_ctimeout($P.tp_refp, TM_sendack, (int)$P.tp_keepalive_ticks);
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
tptrace(TPPTmisc, "XPD tpdu accepted Xrcvnxt, e_seq datalen m_len\n",
$P.tp_Xrcvnxt,$$.e_seq, $$.e_datalen, $$.e_data->m_len);
$P.tp_sock->so_state |= SS_RCVATMARK;
sbinsertoob(&$P.tp_Xrcv, $$.e_data);
dump_mbuf($$.e_data, "XPD TPDU: tp_Xrcv");
tp_indicate(T_XDATA, $P, 0);
(void) tp_emit(XAK_TPDU_type, $P, $P.tp_Xrcvnxt, 0, MNULL);
SEQ_INC($P, $P.tp_Xrcvnxt);
SAME <== TP_OPEN T_USR_Xrcvd
if( $P.tp_Xrcv.sb_cc == 0 ) {
/*$P.tp_flags &= ~TPF_XPD_PRESENT;*/
/* kludge for select(): */
/* $P.tp_sock->so_state &= ~SS_OOBAVAIL; */
* 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, MNULL);
SEQ_INC($P, $P.tp_Xrcvnxt);
/* 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.
SAME <== [ TP_AKWAIT, TP_OPEN ] XPD_TPDU
DEFAULT /* not in window or cdt==0 */
tptrace(TPPTmisc, "XPD tpdu niw (Xrcvnxt, e_seq) or not cdt (cc)\n",
$P.tp_Xrcvnxt, $$.e_seq, $P.tp_Xrcv.sb_cc , 0);
if( $P.tp_Xrcvnxt != $$.e_seq )
if( $P.tp_flags & TPF_CONN_DATA_IN ) {
/* user isn't reading the connection data; see note above */
sbdrop(&$P.tp_Xrcv, $P.tp_Xrcv.sb_cc);
$P.tp_flags &= ~TPF_CONN_DATA_IN;
/* might as well kick 'em again */
tp_indicate(T_XDATA, $P, 0);
tp_ctimeout($P.tp_refp, 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 (!soqremque(so, 0) && !soqremque(so, 1))
tp_soisdisconnecting($P.tp_sock);
tp_netcmd( $P, CONN_CLOSE);
TP_CLOSING <== [ TP_CLOSING, TP_AKWAIT, TP_CRSENT, TP_CONFIRMING ] T_DETACH
struct socket *so = $P.tp_sock;
struct mbuf *data = MNULL;
/* detach from parent socket so it can finish closing */
if (!soqremque(so, 0) && !soqremque(so, 1))
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.tp_refp, 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_CLOSING <== [ TP_AKWAIT, TP_OPEN, TP_CRSENT, TP_CONFIRMING ] T_DISC_req
struct mbuf *data = MCPY($P.tp_ucddata, M_WAIT);
if($P.tp_state == TP_OPEN) {
tp_euntimeout($P.tp_refp, TM_data_retrans); /* all */
tp_cuntimeout($P.tp_refp, TM_inact);
tp_cuntimeout($P.tp_refp, TM_sendack);
printf("T_DISC_req.trans tp_ucddata 0x%x\n",
dump_mbuf(data, "ucddata @ T_DISC_req");
tp_soisdisconnecting($P.tp_sock);
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_dr_ticks);
return tp_emit(DR_TPDU_type, $P, 0, $$.e_reason, data);
SAME <== TP_AKWAIT TM_retrans
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;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_cc_ticks);
TP_CLOSING <== TP_AKWAIT TM_retrans
DEFAULT /* out of time */
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, MNULL);
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, 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
TP_CLOSING <== TP_OPEN [ TM_inact, TM_retrans, TM_data_retrans ]
tp_euntimeout($P.tp_refp, TM_data_retrans); /* all */
tp_cuntimeout($P.tp_refp, TM_inact);
tp_cuntimeout($P.tp_refp, TM_sendack);
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, MNULL);
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_dr_ticks);
SAME <== TP_OPEN TM_retrans
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.
tptrace(TPPTmisc, "XPD retrans: Xuna Xsndnxt sndhiwat snduna",
$P.tp_Xuna, $P.tp_Xsndnxt, $P.tp_sndhiwat,
dump_mbuf(m, "XPD retrans emitting M");
(void) tp_emit(XPD_TPDU_type, $P, $P.tp_Xuna, 1, m);
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_xpd_ticks);
SAME <== TP_OPEN TM_data_retrans
register SeqNum low, lowsave = 0;
register struct tp_rtc *r = $P.tp_snduna_rtc;
register SeqNum high = $$.e_high;
tp_euntimeout_lss($P.tp_refp, TM_data_retrans,
SEQ_ADD($P, $P.tp_sndhiwat, 1));
$P.tp_retrans_hiwat = $P.tp_sndhiwat;
if (($P.tp_rx_strat & TPRX_EACH) == 0)
high = (high>low)?low:high;
if( $P.tp_rx_strat & TPRX_USE_CW ) {
i = SEQ_ADD($P, low, $P.tp_cong_win);
high = SEQ_MIN($P, high, $P.tp_sndhiwat);
while( SEQ_LEQ($P, low, high) ){
if ( r == (struct tp_rtc *)0 ){
printf( "tp: retrans rtc list is GONE!\n");
if ( r->tprt_seq == low ){
if(( m = m_copy(r->tprt_data, 0, r->tprt_octets ))== MNULL)
(void) tp_emit(DT_TPDU_type, $P, low, r->tprt_eot, m);
if ( SEQ_LEQ($P, lowsave, high) ){
tp_etimeout($P.tp_refp, TM_data_retrans, (caddr_t)lowsave,
(caddr_t)high, $$.e_retrans,
($P.tp_Nretrans - $$.e_retrans) * (int)$P.tp_dt_ticks);
SAME <== TP_CLOSING TM_retrans
(void) tp_emit(DR_TPDU_type, $P, 0, E_TP_DR_NO_REAS, MNULL);
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_dr_ticks);
TP_REFWAIT <== TP_CLOSING TM_retrans
DEFAULT /* no more retrans - gave up */
$P.tp_sock->so_error = ETIMEDOUT;
$P.tp_refp->tpr_state = REF_FROZEN;
tp_recycle_tsuffix( $P );
tp_etimeout($P.tp_refp, TM_reference, 0,0,0, (int)$P.tp_refer_ticks);
* The resources are kept around until the ref timer goes off.
* The suffices are wiped out sooner so they can be reused right away.
/* applicable in TP4, TP0 */
TP_CLOSED <== TP_REFWAIT TM_reference
/* applicable in TP4, TP0 */
/* A duplicate CR from connectionless network layer can't happen */
SAME <== TP_OPEN [ CR_TPDU, CC_TPDU ]
if( $P.tp_class != TP_CLASS_0) {
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
if ( $E.ev_number == CC_TPDU )
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL);
/* ignore it if class 0 - state tables are blank for this */
/* applicable in TP4, TP0 */
SAME <== TP_OPEN T_DATA_req
tptrace(TPPTmisc, "T_DATA_req sndhiwat snduna fcredit, tpcb",
$P.tp_sndhiwat, $P.tp_snduna, $P.tp_fcredit, $P);
SAME <== TP_OPEN T_XPD_req
/* T_XPD_req was issued by sosend iff xpd socket buf was empty
* AND (which means) there were no unacknowledged XPD tpdus outstanding!
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.
tptrace(TPPTmisc, "XPD req: Xuna Xsndnxt sndhiwat snduna",
$P.tp_Xuna, $P.tp_Xsndnxt, $P.tp_sndhiwat,
printf("T_XPD_req: sb_cc 0x%x\n", $P.tp_Xsnd.sb_cc);
dump_mbuf(m, "XPD req emitting M");
tp_emit(XPD_TPDU_type, $P, $P.tp_Xuna, 1, m);
$P.tp_retrans = $P.tp_Nretrans;
tp_ctimeout($P.tp_refp, TM_retrans, (int)$P.tp_xpd_ticks);
SEQ_INC($P, $P.tp_Xsndnxt);
/* TP4, faked ack in TP0 when cons send completes */
( 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.
printf("GOOD ACK seq 0x%x cdt 0x%x\n", $$.e_seq, $$.e_cdt);
if( $P.tp_class != TP_CLASS_0) {
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
tp_euntimeout_lss($P.tp_refp, TM_data_retrans, $$.e_seq);
sbwakeup( &$P.tp_sock->so_snd );
if ($P.tp_sndhiwat <= $P.tp_retrans_hiwat &&
$P.tp_snduna <= $P.tp_retrans_hiwat) {
/* extern struct mbuf *m_copy(); */
register struct tp_rtc *r;
SeqNum high, retrans, low_save;
high = SEQ_MIN($P, SEQ_ADD($P, $P.tp_snduna,
MIN($P.tp_cong_win, $P.tp_fcredit)) - 1,
low_save = retrans = SEQ_MAX($P, SEQ_ADD($P, $P.tp_last_retrans, 1),
for (; SEQ_LEQ($P, retrans, high); SEQ_INC($P, retrans)) {
for (r = $P.tp_snduna_rtc; r; r = r->tprt_next){
if ( r->tprt_seq == retrans ){
if(( m = m_copy(r->tprt_data, 0, r->tprt_octets ))
(void) tp_emit(DT_TPDU_type, $P, retrans,
$P.tp_last_retrans = retrans;
if ( r == (struct tp_rtc *)0 ){
printf( "tp: retrans rtc list is GONE!\n");
tp_etimeout($P.tp_refp, TM_data_retrans, (caddr_t)low_save,
(caddr_t)high, $P.tp_retrans, (int)$P.tp_dt_ticks);
if (SEQ_DEC($P, retrans) == $P.tp_retrans_hiwat)
printf("GOOD ACK new sndhiwat 0x%x\n", $P.tp_sndhiwat);
/* TP4, and TP0 after sending a CC or possibly a CR */
tptrace(TPPTmisc, "BOGUS ACK fcc_present, tp_r_subseq e_subseq",
$$.e_fcc_present, $P.tp_r_subseq, $$.e_subseq, 0);
if( $P.tp_class != TP_CLASS_0 ) {
if ( !$$.e_fcc_present ) {
IncStat( ts_ackreason[_ACK_FCC_] );
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 1, MNULL);
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
/* 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.tp_refp, TM_retrans, (int)$P.tp_xpd_ticks);
SEQ_INC($P, $P.tp_Xsndnxt);
/* end of the above hack */
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.
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
tp_cuntimeout($P.tp_refp, TM_retrans);
sbwakeup( &$P.tp_sock->so_snd );
/* TP4, and TP0 after sending a CC or possibly a CR */
SAME <== TP_OPEN XAK_TPDU
tptrace(TPPTmisc, "BOGUS XACK eventtype ", $E.ev_number, 0, 0,0);
if( $P.tp_class != TP_CLASS_0 ) {
tp_ctimeout($P.tp_refp, TM_inact, (int)$P.tp_inact_ticks);
SAME <== TP_OPEN TM_sendack
tptrace(TPPTsendack, -1, $P.tp_lcredit, $P.tp_sent_uwe,
IncPStat($P, tps_n_TMsendack);
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL);
SAME <== TP_OPEN T_USR_rcvd
($P.tp_class == TP_CLASS_0)
/* 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
SAME <== TP_OPEN T_USR_rcvd
IncStat(ts_ackreason[_ACK_USRRCV_]);
/* send an ACK only if there's new information */
if (($P.tp_rcvnxt != $P.tp_sent_rcvnxt) ||
($P.tp_lcredit != $P.tp_sent_lcdt))
return tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL);
/* applicable in TP4, TP0 */
SAME <== TP_REFWAIT [ T_USR_rcvd, T_USR_Xrcvd ]
/* This happens if other end sent a DR when the user was waiting
* Processing the DR includes putting us in REFWAIT state.
TP_REFWAIT <== [ TP_OPEN, TP_CRSENT, TP_LISTENING ] T_NETRESET
( $P.tp_class != TP_CLASS_4 )
/* 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);
SAME <== [ TP_OPEN, TP_CRSENT, TP_AKWAIT,
TP_CLOSING, TP_LISTENING ] T_NETRESET
/* applicable in TP4, TP0 */
SAME <== [ TP_CLOSED, TP_REFWAIT ] T_NETRESET