/* $NetBSD: tp.trans,v 1.9 2004/02/13 17:56:17 wiz 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 */