BSD 4_4 release
[unix-history] / usr / src / sys / netiso / tp.trans
/* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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
*/
/* $Header: tp.trans,v 5.1 88/10/12 12:22:07 root Exp $
*
* 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, 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 ]
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 = MNULL;
}
}
;
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, MNULL) ;
$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 = MNULL;
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 = MNULL;
}
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, MNULL);
}
}
;
SAME <== TP_CLOSED DR_TPDU
DEFAULT
{
if( $$.e_sref != 0 )
(void) tp_emit(DC_TPDU_type, $P, 0, 0, MNULL);
/* 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, MNULL);
/* 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, MNULL);
}
;
/* 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 = MNULL;
}
(void) tp_emit(AK_TPDU_type, $P, $P.tp_rcvnxt, 0, MNULL);
tp_ctimeout($P, TM_inact, (int)$P.tp_inact_ticks);
}
;
/* TP4 only */
SAME <== TP_CRSENT TM_retrans
( $P.tp_retrans > 0 )
{
struct mbuf *data = MNULL;
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 == MNULL )
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, MNULL );
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, MNULL );
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, MNULL );
}
;
/* 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, MNULL);
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, MNULL);
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 = MNULL;
/* 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, MNULL);
$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, MNULL);
$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, MNULL);
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 suffices 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, MNULL);
}
/* 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, MNULL);
}
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, MNULL);
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, MNULL);
}
}
}
;
/* 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 */