/*
 *
 * ===================================
 * HARP  |  Host ATM Research Platform
 * ===================================
 *
 *
 * This Host ATM Research Platform ("HARP") file (the "Software") is
 * made available by Network Computing Services, Inc. ("NetworkCS")
 * "AS IS".  NetworkCS does not provide maintenance, improvements or
 * support of any kind.
 *
 * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
 * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
 * In no event shall NetworkCS be responsible for any damages, including
 * but not limited to consequential damages, arising from or relating to
 * any use of the Software or related support.
 *
 * Copyright 1994-1998 Network Computing Services, Inc.
 *
 * Copies of this Software may be made, however, the above copyright
 * notice must be reproduced on all copies.
 *
 *	@(#) $Id: qsaal1_sigcpcs.c,v 1.2 1998/10/31 20:06:59 phk Exp $
 *
 */

/*
 * ATM Forum UNI Support
 * ---------------------
 *
 * ITU-T Q.SAAL1 - Process CPCS-signals (SSCOP PDUs)
 *
 */

#include <netatm/kern_include.h>

#include <netatm/uni/uni.h>
#include <netatm/uni/sscop.h>
#include <netatm/uni/sscop_misc.h>
#include <netatm/uni/sscop_pdu.h>
#include <netatm/uni/sscop_var.h>

#ifndef lint
__RCSID("@(#) $Id: qsaal1_sigcpcs.c,v 1.2 1998/10/31 20:06:59 phk Exp $");
#endif


/*
 * Local functions
 */
static void	sscop_bgn_outconn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_end_outresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_end_conresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_end_ready __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_endak_outresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_rs_outresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_rs_ready __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_rsak_conresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_sd_inresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_sd_conresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_sd_process __P((struct sscop *, KBuffer *, caddr_t, int));
static void	sscop_sd_ready __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_sdp_ready __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_poll_inresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_poll_conresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_poll_ready __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_stat_conresyn __P((struct sscop *, KBuffer *, caddr_t));
static void	sscop_ustat_conresyn __P((struct sscop *, KBuffer *, caddr_t));


/*
 * PDU type state lookup tables
 */
/* BGN PDU */
static void	(*sscop_bgn_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_bgn_idle,		/* SOS_IDLE */
			sscop_bgn_outconn,	/* SOS_OUTCONN */
			sscop_noop,		/* SOS_INCONN */
			sscop_bgn_outdisc,	/* SOS_OUTDISC */
			sscop_bgn_outresyn,	/* SOS_OUTRESYN */
			sscop_bgn_inresyn,	/* SOS_INRESYN */
			sscop_bgn_outresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_bgn_inresyn,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* BGAK PDU */
static void	(*sscop_bgak_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_bgak_idle,	/* SOS_IDLE */
			sscop_bgak_outconn,	/* SOS_OUTCONN */
			sscop_bgak_error,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_bgak_error,	/* SOS_OUTRESYN */
			sscop_bgak_error,	/* SOS_INRESYN */
			sscop_bgak_error,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_noop,		/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* BGREJ PDU */
static void	(*sscop_bgrej_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_bgrej_error,	/* SOS_IDLE */
			sscop_bgrej_outconn,	/* SOS_OUTCONN */
			sscop_bgrej_inconn,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_bgrej_outresyn,	/* SOS_OUTRESYN */
			sscop_bgrej_ready,	/* SOS_INRESYN */
			sscop_bgrej_outresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_bgrej_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* END PDU */
static void	(*sscop_end_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_end_idle,		/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_end_inconn,	/* SOS_INCONN */
			sscop_end_outdisc,	/* SOS_OUTDISC */
			sscop_end_outresyn,	/* SOS_OUTRESYN */
			sscop_end_ready,	/* SOS_INRESYN */
			sscop_end_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_end_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* ENDAK PDU */
static void	(*sscop_endak_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_noop,		/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_endak_inconn,	/* SOS_INCONN */
			sscop_endak_outdisc,	/* SOS_OUTDISC */
			sscop_endak_outresyn,	/* SOS_OUTRESYN */
			sscop_endak_ready,	/* SOS_INRESYN */
			sscop_endak_outresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_endak_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* RS PDU */
static void	(*sscop_rs_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_rs_idle,		/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_rs_error,		/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_rs_outresyn,	/* SOS_OUTRESYN */
			sscop_noop,		/* SOS_INRESYN */
			sscop_noop,		/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_rs_ready,		/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* RSAK PDU */
static void	(*sscop_rsak_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_rsak_idle,	/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_rsak_error,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_rsak_outresyn,	/* SOS_OUTRESYN */
			sscop_rsak_error,	/* SOS_INRESYN */
			sscop_rsak_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_rsak_error,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* SD PDU */
static void	(*sscop_sd_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_sd_idle,		/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_sd_inconn,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_sd_ready,		/* SOS_OUTRESYN */
			sscop_sd_inresyn,	/* SOS_INRESYN */
			sscop_sd_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_sd_ready,		/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* SDP PDU */
static void	(*sscop_sdp_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_sd_idle,		/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_sd_inconn,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_sdp_ready,	/* SOS_OUTRESYN */
			sscop_sd_inresyn,	/* SOS_INRESYN */
			sscop_sd_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_sdp_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* POLL PDU */
static void	(*sscop_poll_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_poll_idle,	/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_poll_inconn,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_poll_ready,	/* SOS_OUTRESYN */
			sscop_poll_inresyn,	/* SOS_INRESYN */
			sscop_poll_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_poll_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* STAT PDU */
static void	(*sscop_stat_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_stat_idle,	/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_stat_inconn,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_noop,		/* SOS_OUTRESYN */
			sscop_stat_ready,	/* SOS_INRESYN */
			sscop_stat_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_stat_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* USTAT PDU */
static void	(*sscop_ustat_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_ustat_idle,	/* SOS_IDLE */
			sscop_noop,		/* SOS_OUTCONN */
			sscop_ustat_inconn,	/* SOS_INCONN */
			sscop_noop,		/* SOS_OUTDISC */
			sscop_noop,		/* SOS_OUTRESYN */
			sscop_ustat_ready,	/* SOS_INRESYN */
			sscop_ustat_conresyn,	/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_ustat_ready,	/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* UD PDU */
static void	(*sscop_ud_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_ud_all,		/* SOS_IDLE */
			sscop_ud_all,		/* SOS_OUTCONN */
			sscop_ud_all,		/* SOS_INCONN */
			sscop_ud_all,		/* SOS_OUTDISC */
			sscop_ud_all,		/* SOS_OUTRESYN */
			sscop_ud_all,		/* SOS_INRESYN */
			sscop_ud_all,		/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_ud_all,		/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};

/* MD PDU */
static void	(*sscop_md_tab[SOS_NUMSTATES])
				__P((struct sscop *, KBuffer *, caddr_t)) = {
			NULL,			/* SOS_INST */
			sscop_md_all,		/* SOS_IDLE */
			sscop_md_all,		/* SOS_OUTCONN */
			sscop_md_all,		/* SOS_INCONN */
			sscop_md_all,		/* SOS_OUTDISC */
			sscop_md_all,		/* SOS_OUTRESYN */
			sscop_md_all,		/* SOS_INRESYN */
			sscop_md_all,		/* SOS_CONRESYN */
			NULL,			/* invalid */
			NULL,			/* invalid */
			sscop_md_all,		/* SOS_READY */
			sscop_noop		/* SOS_TERM */
};


/*
 * PDU type lookup table
 */
void	(*(*sscop_qsaal_pdutab[]))
				__P((struct sscop *, KBuffer *, caddr_t)) = {
		NULL,
		sscop_bgn_tab,
		sscop_bgak_tab,
		sscop_end_tab,
		sscop_endak_tab,
		sscop_rs_tab,
		sscop_rsak_tab,
		sscop_bgrej_tab,
		sscop_sd_tab,
		sscop_sdp_tab,
		sscop_poll_tab,
		sscop_stat_tab,
		sscop_ustat_tab,
		sscop_ud_tab,
		sscop_md_tab,
		NULL
};


/*
 * BGN PDU / SOS_OUTCONN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_bgn_outconn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	struct bgn_pdu	*bp = (struct bgn_pdu *)trlr;
	int		err;

	/*
	 * Initialize transmit window
	 */
	SEQ_SET(sop->so_sendmax, ntohl(bp->bgn_nmr));

	/*
	 * Notify user of connection establishment
	 */
	if (sop->so_flags & SOF_REESTAB) {
		KB_FREEALL(m);
		STACK_CALL(SSCOP_ESTABLISH_IND, sop->so_upper, sop->so_toku, 
			sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
		if (err) {
			sscop_abort(sop, "sscop_bgn_outconn: stack memory\n");
			return;
		}
		sop->so_flags &= ~SOF_REESTAB;
	} else {
		STACK_CALL(SSCOP_ESTABLISH_CNF, sop->so_upper, sop->so_toku, 
			sop->so_connvc, (int)m, 0, err);
		if (err) {
			KB_FREEALL(m);
			sscop_abort(sop, "sscop_bgn_outconn: stack memory\n");
			return;
		}
	}

	/*
	 * Return an ACK to peer
	 */
	(void) sscop_send_bgak(sop);

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Reset receiver variables
	 */
	qsaal1_reset_rcvr(sop);
	
	/*
	 * Start polling timer
	 */
	sscop_set_poll(sop);

	/*
	 * Start lost poll/stat timer
	 */
	sop->so_timer[SSCOP_T_NORESP] = sop->so_parm.sp_timeresp;

	/*
	 * OK, we're ready for data
	 */
	sop->so_state = SOS_READY;

	/*
	 * See if transmit queues need servicing
	 */
	if (sop->so_flags & SOF_XMITSRVC)
		sscop_service_xmit(sop);

	return;
}


/*
 * END PDU / SOS_OUTRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_end_outresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	struct end_pdu	*ep = (struct end_pdu *)trlr;
	int		err, source;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Acknowledge END
	 */
	(void) sscop_send_endak(sop);

	/*
	 * Get Source value
	 */
	if (ep->end_type & PT_SOURCE_SSCOP)
		source = SSCOP_SOURCE_SSCOP;
	else
		source = SSCOP_SOURCE_USER;

	/*
	 * Notify user of connection termination
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, (int)m, source, err);
	if (err) {
		KB_FREEALL(m);
		sscop_abort(sop, "sscop_end_outresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * END PDU / SOS_CONRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_end_conresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Free up buffers
	 */
	KB_FREEALL(m);

	/*
	 * Acknowledge END
	 */
	(void) sscop_send_endak(sop);

	/*
	 * Notify user of connection termination
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_end_conresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * END PDU / SOS_READY Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_end_ready(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	struct end_pdu	*ep = (struct end_pdu *)trlr;
	int		err, source;

	/*
	 * Stop poll timer
	 */
	sop->so_timer[SSCOP_T_POLL] = 0;
	sop->so_flags &= ~SOF_KEEPALIVE;

	/*
	 * Stop lost poll/stat timer
	 */
	sop->so_timer[SSCOP_T_NORESP] = 0;

	/*
	 * Acknowledge END
	 */
	(void) sscop_send_endak(sop);

	/*
	 * Get Source value
	 */
	if (ep->end_type & PT_SOURCE_SSCOP)
		source = SSCOP_SOURCE_SSCOP;
	else
		source = SSCOP_SOURCE_USER;

	/*
	 * Notify user of connection termination
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, (int)m, source, err);
	if (err) {
		KB_FREEALL(m);
		sscop_abort(sop, "sscop_end_ready: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * ENDAK PDU / SOS_OUTRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_endak_outresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Report protocol error
	 */
	sscop_endak_error(sop, m, trlr);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_endak_outresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * RS PDU / SOS_OUTRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_rs_outresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Notify user of resynchronization
	 */
	STACK_CALL(SSCOP_RESYNC_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, (int)m, 0, err);
	if (err) {
		KB_FREEALL(m);
		sscop_abort(sop, "sscop_rs_outresyn: stack memory\n");
		return;
	}

	/*
	 * Reset receiver state variables
	 */
	qsaal1_reset_rcvr(sop);

	/*
	 * Wait for both peer and user responses
	 */
	sop->so_state = SOS_CONRESYN;

	return;
}


/*
 * RS PDU / SOS_READY Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_rs_ready(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Notify user of resynchronization
	 */
	STACK_CALL(SSCOP_RESYNC_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, (int)m, 0, err);
	if (err) {
		KB_FREEALL(m);
		sscop_abort(sop, "sscop_rs_ready: stack memory\n");
		return;
	}

	/*
	 * Reset receiver state variables
	 */
	qsaal1_reset_rcvr(sop);

	/*
	 * Wait for user response
	 */
	sop->so_state = SOS_INRESYN;

	return;
}


/*
 * RSAK PDU / SOS_CONRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_rsak_conresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Free buffers
	 */
	KB_FREEALL(m);

	/*
	 * Notify user of resynchronization completion
	 */
	STACK_CALL(SSCOP_RESYNC_CNF, sop->so_upper, sop->so_toku, 
		sop->so_connvc, 0, 0, err);
	if (err) {
		sscop_abort(sop, "sscop_rsak_conresyn: stack memory\n");
		return;
	}

	/*
	 * Start the polling timer
	 */
	sscop_set_poll(sop);

	/*
	 * Start lost poll/stat timer
	 */
	sop->so_timer[SSCOP_T_NORESP] = sop->so_parm.sp_timeresp;

	/*
	 * Continue waiting for user response
	 */
	sop->so_state = SOS_INRESYN;

	/*
	 * See if transmit queues need servicing
	 */
	if (sop->so_flags & SOF_XMITSRVC)
		sscop_service_xmit(sop);

	return;
}


/*
 * SD PDU / SOS_INRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_sd_inresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop poll timer
	 */
	sop->so_timer[SSCOP_T_POLL] = 0;
	sop->so_flags &= ~SOF_KEEPALIVE;

	/*
	 * Stop lost poll/stat timer
	 */
	sop->so_timer[SSCOP_T_NORESP] = 0;

	/*
	 * Record error condition
	 */
	sscop_sd_error(sop, m, trlr);

	/*
	 * Return an END to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_sd_inresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Go back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * SD PDU / SOS_CONRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_sd_conresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Record error condition
	 */
	sscop_sd_error(sop, m, trlr);

	/*
	 * Return an END to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_sd_conresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Go back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * SD/SDP PDU Common Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU user data buffer chain
 *	trlr	pointer to PDU trailer
 *	type	PDU type (SD or SDP)
 *
 * Returns:
 *	none
 *
 */
static void
sscop_sd_process(sop, m, trlr, type)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
	int		type;
{
	struct sd_pdu	*sp;
	struct sdp_pdu	*spp;
	struct poll_pdu	poll;
	struct pdu_hdr	*php;
	KBuffer		*n;
	sscop_seq	ns, nps;
	int		err, space;

	/*
	 * Get PDU sequence number(s)
	 */
	if (type == PT_SD) {
		sp = (struct sd_pdu *)trlr;
		SEQ_SET(ns, ntohl(sp->sd_ns));
		SEQ_SET(nps, 0);
	} else {
		spp = (struct sdp_pdu *)trlr;
		SEQ_SET(ns, ntohl(spp->sdp_ns));
		SEQ_SET(nps, ntohl(spp->sdp_nps));
	}

	/*
	 * Ensure that the sequence number fits within the window
	 */
	if (SEQ_GEQ(ns, sop->so_rcvmax, sop->so_rcvnext)) {
		KB_FREEALL(m);
		return;
	}

	/*
	 * If this is the next in-sequence PDU, hand it to user
	 */
	if (ns == sop->so_rcvnext) {
		STACK_CALL(SSCOP_DATA_IND, sop->so_upper, sop->so_toku, 
			sop->so_connvc, (int)m, ns, err);
		if (err) {
			KB_FREEALL(m);
			return;
		}

		/*
		 * Bump next expected sequence number
		 */
		SEQ_INCR(sop->so_rcvnext, 1);

		/*
		 * Slide receive window down
		 */
		SEQ_INCR(sop->so_rcvmax, 1);

		/*
		 * Is this the highest sequence PDU we've received??
		 */
		if (ns == sop->so_rcvhigh) {
			/*
			 * Yes, bump the limit and exit
			 */
			sop->so_rcvhigh = sop->so_rcvnext;
			if (type == PT_SDP)
				goto dopoll;
			return;
		}

		/*
		 * This is a retransmitted PDU, so see if we have
		 * more in-sequence PDUs already queued up
		 */
		while ((php = sop->so_recv_hd) && 
		       (php->ph_ns == sop->so_rcvnext)) {

			/*
			 * Yup we do, so remove next PDU from queue and
			 * pass it up to the user as well
			 */
			sop->so_recv_hd = php->ph_recv_lk;
			if (sop->so_recv_hd == NULL)
				sop->so_recv_tl = NULL;
			STACK_CALL(SSCOP_DATA_IND, sop->so_upper, sop->so_toku,
				sop->so_connvc, (int)php->ph_buf, php->ph_ns,
				err);
			if (err) {
				/*
				 * Should never happen, but...
				 */
				KB_FREEALL(php->ph_buf);
				sscop_abort(sop,
					"sscop_sd_process: stack memory\n");
				return;
			}

			/*
			 * Bump next expected sequence number
			 */
			SEQ_INCR(sop->so_rcvnext, 1);

			/*
			 * Slide receive window down
			 */
			SEQ_INCR(sop->so_rcvmax, 1);
		}

		/*
		 * Finished with data...see if we need to poll
		 */
		if (type == PT_SDP)
			goto dopoll;
		return;
	}

	/*
	 * We're gonna have to queue this PDU, so find space 
	 * for the PDU header
	 */
	KB_HEADROOM(m, space);

	/*
	 * If there's not enough room in the received buffer,
	 * allocate & link a new buffer for the header
	 */
	if (space < sizeof(struct pdu_hdr)) {

		KB_ALLOC(n, sizeof(struct pdu_hdr), KB_F_NOWAIT, KB_T_HEADER);
		if (n == NULL) {
			KB_FREEALL(m);
			return;
		}
		KB_HEADSET(n, sizeof(struct pdu_hdr));
		KB_LEN(n) = 0;
		KB_LINKHEAD(n, m);
		m = n;
	}

	/*
	 * Build PDU header
	 *
	 * We can at least assume/require that the start of
	 * the user data is aligned.  Also note that we don't
	 * include this header in the buffer len/offset fields.
	 */
	KB_DATASTART(m, php, struct pdu_hdr *);
	php--;
	php->ph_ns = ns;
	php->ph_buf = m;

	/*
	 * Insert PDU into the receive queue
	 */
	if (sscop_recv_insert(sop, php)) {
		/*
		 * Oops, a duplicate sequence number PDU is already on
		 * the queue, somethings wrong here.
		 */
		sscop_maa_error(sop, 'Q');

		/*
		 * Free buffers
		 */
		KB_FREEALL(m);

		/*
		 * Reestablish a new connection
		 */
		qsaal1_reestablish(sop);

		return;
	}

	/*
	 * Are we at the high-water mark??
	 */
	if (ns == sop->so_rcvhigh) {
		/*
		 * Yes, just bump the mark
		 */
		SEQ_INCR(sop->so_rcvhigh, 1);

		if (type == PT_SDP)
			goto dopoll;
		return;
	}

	/*
	 * Are we beyond the high-water mark??
	 */
	if (SEQ_GT(ns, sop->so_rcvhigh, sop->so_rcvnext)) {
		/*
		 * Yes, then there's a missing PDU, so inform the transmitter
		 */
		if (type == PT_SD)
			(void) sscop_send_ustat(sop, ns);

		/*
		 * Update high-water mark
		 */
		sop->so_rcvhigh = SEQ_ADD(ns, 1);
	}

	if (type == PT_SD)
		return;

dopoll:
	/*
	 * Do the "poll" part of an SDP PDU
	 */
	poll.poll_nps = htonl(nps);
	poll.poll_ns = htonl((PT_POLL << PT_TYPE_SHIFT) | ns);
	sscop_poll_ready(sop, NULL, (caddr_t)&poll);
	return;
}


/*
 * SD PDU / SOS_READY Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_sd_ready(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	/*
	 * Just call common SD/SDP processor
	 */
	sscop_sd_process(sop, m, trlr, PT_SD);

	return;
}


/*
 * SDP PDU / SOS_READY Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_sdp_ready(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	/*
	 * Just call common SD/SDP processor
	 */
	sscop_sd_process(sop, m, trlr, PT_SDP);

	return;
}


/*
 * POLL PDU / SOS_INRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_poll_inresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop poll timer
	 */
	sop->so_timer[SSCOP_T_POLL] = 0;
	sop->so_flags &= ~SOF_KEEPALIVE;

	/*
	 * Stop lost poll/stat timer
	 */
	sop->so_timer[SSCOP_T_NORESP] = 0;

	/*
	 * Report protocol error
	 */
	sscop_poll_error(sop, m, trlr);

	/*
	 * Return an END to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_poll_inresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * POLL PDU / SOS_CONRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_poll_conresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Record error condition
	 */
	sscop_poll_error(sop, m, trlr);

	/*
	 * Return an END to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_poll_conresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Go back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * POLL PDU / SOS_READY Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_poll_ready(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	struct poll_pdu	*pp = (struct poll_pdu *)trlr;
	sscop_seq	nps;

	NTOHL(pp->poll_ns);

	/*
	 * If the poll sequence number is less than highest number
	 * we've already seen, something's wrong - so attempt to
	 * reestablish a new connection.
	 */
	if (SEQ_LT(pp->poll_ns, sop->so_rcvhigh, sop->so_rcvnext)) {
		/*
		 * Record error condition
		 */
		sscop_maa_error(sop, 'Q');

		/*
		 * Free buffers
		 */
		KB_FREEALL(m);

		/*
		 * Reestablish a new connection
		 */
		qsaal1_reestablish(sop);

		return;
	}

	/*
	 * Set a new "next highest" sequence number expected
	 */
	if (SEQ_LT(pp->poll_ns, sop->so_rcvmax, sop->so_rcvnext))
		SEQ_SET(sop->so_rcvhigh, pp->poll_ns);
	else
		sop->so_rcvhigh = sop->so_rcvmax;

	/*
	 * Return a STAT PDU to peer
	 */
	SEQ_SET(nps, ntohl(pp->poll_nps));
	KB_FREEALL(m);
	(void) sscop_send_stat(sop, nps);

	return;
}


/*
 * STAT PDU / SOS_CONRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_stat_conresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Record error condition
	 */
	sscop_stat_error(sop, m, trlr);

	/*
	 * Return an END to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_stat_conresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Go back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}


/*
 * USTAT PDU / SOS_CONRESYN Processor
 * 
 * Arguments:
 *	sop	pointer to sscop connection block
 *	m	pointer to PDU buffer (without trailer)
 *	trlr	pointer to PDU trailer
 *
 * Returns:
 *	none
 *
 */
static void
sscop_ustat_conresyn(sop, m, trlr)
	struct sscop	*sop;
	KBuffer		*m;
	caddr_t		trlr;
{
	int		err;

	/*
	 * Stop retransmit timer
	 */
	sop->so_timer[SSCOP_T_CC] = 0;

	/*
	 * Record error condition
	 */
	sscop_ustat_error(sop, m, trlr);

	/*
	 * Return an END to peer
	 */
	(void) sscop_send_end(sop, SSCOP_SOURCE_SSCOP);

	/*
	 * Notify user of connection failure
	 */
	STACK_CALL(SSCOP_RELEASE_IND, sop->so_upper, sop->so_toku, 
		sop->so_connvc, SSCOP_UU_NULL, SSCOP_SOURCE_SSCOP, err);
	if (err) {
		sscop_abort(sop, "sscop_ustat_conresyn: stack memory\n");
		return;
	}

	/*
	 * Clear connection data
	 */
	qsaal1_clear_connection(sop);

	/*
	 * Go back to idle state
	 */
	sop->so_state = SOS_IDLE;

	return;
}

