/*
 *
 * ===================================
 * 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.
 *
 *	@(#) $FreeBSD: src/sys/netatm/uni/sscop_subr.c,v 1.6 2000/01/17 20:49:52 mks Exp $
 *
 */

/*
 * ATM Forum UNI Support
 * ---------------------
 *
 * SSCOP - Subroutines
 *
 */

#include <netatm/kern_include.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("@(#) $FreeBSD: src/sys/netatm/uni/sscop_subr.c,v 1.6 2000/01/17 20:49:52 mks Exp $");
#endif


/*
 * Local functions
 */
static int sscop_proc_xmit __P((struct sscop *));


/*
 * Get Next Element from STAT PDU
 *
 * Arguments:
 *	m	pointer to current buffer in STAT PDU
 *	pelem	pointer to location to store element value
 *
 * Returns:
 *	addr	pointer to updated current buffer in STAT PDU
 *
 */
KBuffer *
sscop_stat_getelem(m, pelem)
	KBuffer		*m;
	sscop_seq	*pelem;
{
	caddr_t		cp;

	/*
	 * Get to start of element
	 *
	 * Note that we always ensure that the current buffer has
	 * at least one byte of the next element.
	 */
	KB_DATASTART(m, cp, caddr_t);

	/*
	 * See how much of element is in this buffer
	 */
	if (KB_LEN(m) >= sizeof(sscop_seq)) {
		/*
		 * Get element from this buffer
		 */
		if ((int)cp & (sizeof(sscop_seq) - 1))
			KM_COPY(cp, (caddr_t)pelem, sizeof(sscop_seq));
		else
			*pelem = *(sscop_seq *)cp;

		/*
		 * Update buffer controls
		 */
		KB_HEADADJ(m, -sizeof(sscop_seq));
	} else {
		/*
		 * Get element split between two buffers
		 */
		int	i, j;

		/*
		 * Copy what's in this buffer
		 */
		i = KB_LEN(m);
		KM_COPY(cp, (caddr_t)pelem, i);
		KB_LEN(m) = 0;

		/*
		 * Now get to next buffer
		 */
		while (m && (KB_LEN(m) == 0))
			m = KB_NEXT(m);

		/*
		 * And copy remainder of element
		 */
		j = sizeof(sscop_seq) - i;
		KB_DATASTART(m, cp, caddr_t);
		KM_COPY(cp, (caddr_t)pelem + i, j);

		/*
		 * Update buffer controls
		 */
		KB_HEADADJ(m, -j);
	}

	/*
	 * Put element (sequence number) into host order
	 */
	NTOHL(*pelem);

	/*
	 * Get pointers set for next call
	 */
	while (m && (KB_LEN(m) == 0))
		m = KB_NEXT(m);

	return (m);
}


/*
 * Locate SD PDU on Pending Ack Queue
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *	seq	sequence number of PDU to locate
 *
 * Returns:
 *	addr	pointer to located PDU header
 *	0	SD PDU sequence number not found
 *
 */
struct pdu_hdr *
sscop_pack_locate(sop, seq)
	struct sscop	*sop;
	sscop_seq	seq;
{
	struct pdu_hdr	*php;

	/*
	 * Loop thru queue until we either find the PDU or the queue's
	 * sequence numbers are greater than the PDU's sequence number,
	 * indicating that the PDU is not on the queue.
	 */
	for (php = sop->so_pack_hd; php; php = php->ph_pack_lk) {
		if (php->ph_ns == seq)
			break;

		if (SEQ_GT(php->ph_ns, seq, sop->so_ack)) {
			php = NULL;
			break;
		}
	}

	return (php);
}


/*
 * Free Acknowledged SD PDU
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *	seq	sequence number of PDU to free
 *
 * Returns:
 *	none
 *
 */
void
sscop_pack_free(sop, seq)
	struct sscop	*sop;
	sscop_seq	seq;
{
	struct pdu_hdr	*php, *prev;

	/*
	 * Unlink PDU from pending ack queue
	 *
	 * First, check for an empty queue
	 */
	php = sop->so_pack_hd;
	if (php == NULL)
		return;

	/*
	 * Now check for PDU at head of queue
	 */
	if (php->ph_ns == seq) {
		if ((sop->so_pack_hd = php->ph_pack_lk) == NULL)
			sop->so_pack_tl = NULL;
		goto found;
	}

	/*
	 * Otherwise, loop thru queue until we either find the PDU or
	 * the queue's sequence numbers are greater than the PDU's
	 * sequence number, indicating that the PDU is not on the queue.
	 */
	prev = php;
	php = php->ph_pack_lk;
	while (php) {
		if (php->ph_ns == seq) {
			if ((prev->ph_pack_lk = php->ph_pack_lk) == NULL)
				sop->so_pack_tl = prev;
			goto found;
		}

		if (SEQ_GT(php->ph_ns, seq, sop->so_ack))
			return;

		prev = php;
		php = php->ph_pack_lk;
	}

	return;

found:
	/*
	 * We've got the ack'ed PDU - unlink it from retransmit queue
	 */
	sscop_rexmit_unlink(sop, php);

	/*
	 * Free PDU buffers
	 */
	KB_FREEALL(php->ph_buf);

	return;
}


/*
 * Insert SD PDU into Retransmit Queue
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *	php	pointer to SD PDU header
 *
 * Returns:
 *	none
 *
 */
void
sscop_rexmit_insert(sop, php)
	struct sscop	*sop;
	struct pdu_hdr	*php;
{
	struct pdu_hdr	*curr, *next;
	sscop_seq	seq = php->ph_ns;

	/*
	 * Check for an empty queue
	 */
	if ((curr = sop->so_rexmit_hd) == NULL) {
		php->ph_rexmit_lk = NULL;
		sop->so_rexmit_hd = php;
		sop->so_rexmit_tl = php;
		return;
	}

	/*
	 * Now see if PDU belongs at head of queue
	 */
	if (SEQ_LT(seq, curr->ph_ns, sop->so_ack)) {
		php->ph_rexmit_lk = curr;
		sop->so_rexmit_hd = php;
		return;
	}

	/*
	 * Otherwise, loop thru the queue until we find the
	 * proper insertion point for the PDU
	 */
	while ((next = curr->ph_rexmit_lk) != NULL) {
		if (SEQ_LT(seq, next->ph_ns, sop->so_ack)) {
			php->ph_rexmit_lk = next;
			curr->ph_rexmit_lk = php;
			return;
		}
		curr = next;
	}

	/*
	 * Insert PDU at end of queue
	 */
	php->ph_rexmit_lk = NULL;
	curr->ph_rexmit_lk = php;
	sop->so_rexmit_tl = php;

	return;
}


/*
 * Unlink SD PDU from Retransmit Queue
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *	php	pointer to PDU header to unlink
 *
 * Returns:
 *	none
 *
 */
void
sscop_rexmit_unlink(sop, php)
	struct sscop	*sop;
	struct pdu_hdr	*php;
{
	struct pdu_hdr	*curr;

	/*
	 * See if PDU is on retransmit queue
	 */
	if ((php->ph_rexmit_lk == NULL) && (sop->so_rexmit_tl != php))
		return;

	/*
	 * It's here somewhere, so first check for the PDU at the
	 * head of the queue
	 */
	if (php == sop->so_rexmit_hd) {
		if ((sop->so_rexmit_hd = php->ph_rexmit_lk) == NULL)
			sop->so_rexmit_tl = NULL;
		php->ph_rexmit_lk = NULL;
		return;
	}

	/*
	 * Otherwise, loop thru the queue until we find the PDU
	 */
	for (curr = sop->so_rexmit_hd; curr; curr = curr->ph_rexmit_lk) {
		if (curr->ph_rexmit_lk == php)
			break;
	}
	if (curr) {
		if ((curr->ph_rexmit_lk = php->ph_rexmit_lk) == NULL)
			sop->so_rexmit_tl = curr;
	} else {
		log(LOG_ERR,
			"sscop_rexmit_unlink: Not found - sop=%p, php=%p\n",
			sop, php);
#ifdef DIAGNOSTIC
		panic("sscop_rexmit_unlink: Not found");
#endif
	}
	php->ph_rexmit_lk = NULL;

	return;
}


/*
 * Drain Transmission Queues
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *
 * Returns:
 *	none
 *
 */
void
sscop_xmit_drain(sop)
	struct sscop	*sop;
{
	KBuffer		*m;
	struct pdu_hdr	*php;

	/*
	 * Free transmission queue buffers
	 */
	while ((m = sop->so_xmit_hd) != NULL) {
		sop->so_xmit_hd = KB_QNEXT(m);
		KB_FREEALL(m);
	}
	sop->so_xmit_tl = NULL;

	/*
	 * Free retransmission queue
	 *
	 * All retranmission buffers are also on the pending ack
	 * queue (but not the converse), so we just clear the queue
	 * pointers here and do all the real work below.
	 */
	sop->so_rexmit_hd = NULL;
	sop->so_rexmit_tl = NULL;

	/*
	 * Free pending ack queue buffers
	 */
	while ((php = sop->so_pack_hd) != NULL) {
		sop->so_pack_hd = php->ph_pack_lk;
		KB_FREEALL(php->ph_buf);
	}
	sop->so_pack_tl = NULL;

	/*
	 * Clear service required flag
	 */
	sop->so_flags &= ~SOF_XMITSRVC;

	return;
}


/*
 * Insert SD PDU into Receive Queue
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *	php	pointer to SD PDU header
 *
 * Returns:
 *	0	PDU successfully inserted into queue
 *	1	duplicate sequence number PDU on queue, PDU not inserted
 *
 */
int
sscop_recv_insert(sop, php)
	struct sscop	*sop;
	struct pdu_hdr	*php;
{
	struct pdu_hdr	*curr, *next;
	sscop_seq	seq = php->ph_ns;

	/*
	 * Check for an empty queue
	 */
	if ((curr = sop->so_recv_hd) == NULL) {
		php->ph_recv_lk = NULL;
		sop->so_recv_hd = php;
		sop->so_recv_tl = php;
		return (0);
	}

	/*
	 * Now see if PDU belongs at head of queue
	 */
	if (SEQ_LT(seq, curr->ph_ns, sop->so_rcvnext)) {
		php->ph_recv_lk = curr;
		sop->so_recv_hd = php;
		return (0);
	}

	/*
	 * Otherwise, loop thru the queue until we find the
	 * proper insertion point for the PDU.  We also check
	 * to make sure there isn't a PDU already on the queue
	 * with a matching sequence number.
	 */
	while ((next = curr->ph_recv_lk) != NULL) {
		if (SEQ_LT(seq, next->ph_ns, sop->so_rcvnext)) {
			if (seq == curr->ph_ns)
				return (1);
			php->ph_recv_lk = next;
			curr->ph_recv_lk = php;
			return (0);
		}
		curr = next;
	}

	/*
	 * Insert PDU at end of queue
	 */
	if (seq == curr->ph_ns)
		return (1);
	php->ph_recv_lk = NULL;
	curr->ph_recv_lk = php;
	sop->so_recv_tl = php;

	return (0);
}


/*
 * Drain Receiver Queues
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *
 * Returns:
 *	none
 *
 */
void
sscop_rcvr_drain(sop)
	struct sscop	*sop;
{
	struct pdu_hdr	*php;

	/*
	 * Free receive queue buffers
	 */
	while ((php = sop->so_recv_hd) != NULL) {
		sop->so_recv_hd = php->ph_recv_lk;
		KB_FREEALL(php->ph_buf);
	}
	sop->so_recv_tl = NULL;

	return;
}


/*
 * Service connection's transmit queues
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *
 * Returns:
 *	none
 *
 */
void
sscop_service_xmit(sop)
	struct sscop	*sop;
{
	KBuffer		*m, *n;
	struct pdu_hdr	*php;
	int		err = 0, pollsent = 0;

	/*
	 * Initially assume we need service
	 */
	sop->so_flags |= SOF_XMITSRVC;

	/*
	 * Loop until done with queues
	 *
	 * (Congestion control will be added later)
	 */
	while (1) {
		if ((php = sop->so_rexmit_hd) != NULL) {

			/*
			 * Send SD PDU from retransmit queue
			 *
			 * First, get a copy of the PDU to send
			 */
			m = php->ph_buf;
			if (KB_LEN(m) == 0)
				m = KB_NEXT(m);
			KB_COPY(m, 0, KB_COPYALL, n, KB_F_NOWAIT);
			if (n == NULL) {
				err = 1;
				break;
			}

			/*
			 * Now pass it down the stack
			 */
			STACK_CALL(CPCS_UNITDATA_INV, sop->so_lower,
				sop->so_tokl, sop->so_connvc, (int)n, 0, err);
			if (err) {
				KB_FREEALL(n);
				break;
			}

			/*
			 * PDU is on its way, so remove it from
			 * the retransmit queue
			 */
			if (sop->so_rexmit_tl == php) {
				sop->so_rexmit_hd = NULL;
				sop->so_rexmit_tl = NULL;
			} else {
				sop->so_rexmit_hd = php->ph_rexmit_lk;
			}
			php->ph_rexmit_lk = NULL;

			/*
			 * Update PDU's poll sequence
			 */
			php->ph_nps = sop->so_pollsend;

		} else if (sop->so_xmit_hd) {

			/*
			 * Newly arrived data waiting to be sent.
			 * See if transmit window allows us to send it.
			 */
			if (SEQ_LT(sop->so_send, sop->so_sendmax, sop->so_ack)){
				/*
				 * OK, send SD PDU from transmission queue
				 */
				err = sscop_proc_xmit(sop);
				if (err)
					break;
			} else {
				/*
				 * Can't send now, so leave idle phase.
				 */
				if (sop->so_timer[SSCOP_T_IDLE] != 0) {
					sop->so_timer[SSCOP_T_IDLE] = 0;
					sop->so_timer[SSCOP_T_NORESP] =
						sop->so_parm.sp_timeresp;
					err = 1;
				}
				break;
			}

		} else {

			/*
			 * We're finished, so clear service required flag
			 */
			sop->so_flags &= ~SOF_XMITSRVC;
			break;
		}

		/*
		 * We've sent another SD PDU
		 */
		sop->so_polldata++;

		/*
		 * Transition into active (polling) phase
		 */
		if (sop->so_timer[SSCOP_T_POLL] != 0) {
			if (sop->so_flags & SOF_KEEPALIVE) {
				/*
				 * Leaving transient phase
				 */
				sop->so_flags &= ~SOF_KEEPALIVE;
				sop->so_timer[SSCOP_T_POLL] =
						sop->so_parm.sp_timepoll;
			}
		} else {
			/*
			 * Leaving idle phase
			 */
			sop->so_timer[SSCOP_T_IDLE] = 0;
			sop->so_timer[SSCOP_T_NORESP] =
						sop->so_parm.sp_timeresp;
			sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll;
		}

		/*
		 * Let's see if we need to send a POLL yet
		 */
		if (sop->so_polldata < sop->so_parm.sp_maxpd)
			continue;

		/*
		 * Yup, send another poll out
		 */
		SEQ_INCR(sop->so_pollsend, 1);
		(void) sscop_send_poll(sop);
		pollsent++;

		/*
		 * Reset data counter for this poll cycle
		 */
		sop->so_polldata = 0;

		/*
		 * Restart polling timer in active phase
		 */
		sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll;
	}

	/*
	 * If we need/want to send a poll, but haven't sent any yet
	 * on this servicing, send one now
	 */
	if (err && (pollsent == 0)) {
		/*
		 * Send poll
		 */
		SEQ_INCR(sop->so_pollsend, 1);
		(void) sscop_send_poll(sop);

		/*
		 * Reset data counter for this poll cycle
		 */
		sop->so_polldata = 0;

		/*
		 * Restart polling timer in active phase
		 */
		sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll;
		sop->so_flags &= ~SOF_KEEPALIVE;
	}

	return;
}


/*
 * Process Transmission Queue PDU
 *
 * For the first entry on the transmission queue: add a PDU header and
 * trailer, send a copy of the PDU down the stack and move the PDU from
 * the transmission queue to the pending ack queue.
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *
 * Returns:
 *	0	head of transmission queue successfully processed
 *	else	processing error, tranmission queue unchanged
 *
 */
static int
sscop_proc_xmit(sop)
	struct sscop	*sop;
{
	KBuffer		*m, *ml, *n;
	struct pdu_hdr	*php;
	sscop_seq	seq;
	int		len = 0, err;
	int		pad, trlen, space;
	u_char		*cp;

	/*
	 * Get first buffer chain on queue
	 */
	if ((m = sop->so_xmit_hd) == NULL)
		return (0);

	/*
	 * Count data and get to last buffer in chain
	 */
	for (ml = m; ; ml = KB_NEXT(ml)) {
		len += KB_LEN(ml);
		if (KB_NEXT(ml) == NULL)
			break;
	}

	/*
	 * Verify data length
	 */
	if (len > sop->so_parm.sp_maxinfo) {
		sscop_abort(sop, "sscop: maximum data size exceeded\n");
		return (1);
	}

	/*
	 * Get space for PDU header
	 */
	KB_HEADROOM(m, space);
	if (space < sizeof(struct pdu_hdr)) {
		/*
		 * Allocate & link buffer for header
		 */
		KB_ALLOC(n, sizeof(struct pdu_hdr), KB_F_NOWAIT, KB_T_HEADER);
		if (n == NULL)
			return (1);

		KB_LEN(n) = 0;
		KB_HEADSET(n, sizeof(struct pdu_hdr));
		KB_LINKHEAD(n, m);
		KB_QNEXT(n) = KB_QNEXT(m);
		KB_QNEXT(m) = NULL;
		sop->so_xmit_hd = n;
		if (sop->so_xmit_tl == m)
			sop->so_xmit_tl = n;
		m = n;
	}

	/*
	 * Figure out how much padding we'll need
	 */
	pad = ((len + (PDU_PAD_ALIGN - 1)) & ~(PDU_PAD_ALIGN - 1)) - len;
	trlen = pad + sizeof(struct sd_pdu);

	/*
	 * Now get space for PDU trailer and padding
	 */
	KB_TAILROOM(ml, space);
	if (space < trlen) {
		/*
		 * Allocate & link buffer for pad and trailer
		 */
		KB_ALLOC(n, trlen, KB_F_NOWAIT, KB_T_HEADER);
		if (n == NULL)
			return (1);

		KB_LEN(n) = 0;
		KB_LINK(n, ml);
		ml = n;
	}

	/*
	 * Build the PDU trailer
	 *
	 * Since we can't be sure of alignment in the buffers, we
	 * have to move this a byte at a time and we have to be
	 * careful with host byte order issues.
	 */
	KB_DATASTART(ml, cp, u_char *);
	cp += KB_LEN(ml) + pad;
	*cp++ = (pad << PT_PAD_SHIFT) | PT_SD;
	seq = sop->so_send;
	*(cp + 2) = (u_char)(seq & 0xff);
	seq >>= 8;
	*(cp + 1) = (u_char)(seq & 0xff);
	seq >>= 8;
	*(cp) = (u_char)(seq & 0xff);
	KB_LEN(ml) += trlen;

	/*
	 * Get a copy of the SD PDU to send
	 */
	if (KB_LEN(m) == 0)
		n = KB_NEXT(m);
	else
		n = m;
	KB_COPY(n, 0, KB_COPYALL, n, KB_F_NOWAIT);
	if (n == NULL) {
		KB_LEN(ml) -= trlen;
		return (1);
	}

	/*
	 * Now pass copy down the stack
	 */
	STACK_CALL(CPCS_UNITDATA_INV, sop->so_lower, sop->so_tokl,
		sop->so_connvc, (int)n, 0, err);
	if (err) {
		KB_FREEALL(n);
		KB_LEN(ml) -= trlen;
		return (1);
	}

	/*
	 * PDU is on its way, so remove buffer from
	 * the transmission queue
	 */
	if (sop->so_xmit_tl == m) {
		sop->so_xmit_hd = NULL;
		sop->so_xmit_tl = NULL;
	} else {
		sop->so_xmit_hd = KB_QNEXT(m);
	}
	KB_QNEXT(m) = NULL;

	/*
	 * 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 = sop->so_send;
	php->ph_nps = sop->so_pollsend;
	php->ph_buf = m;
	php->ph_rexmit_lk = NULL;
	php->ph_pack_lk = NULL;

	/*
	 * Put PDU onto the pending ack queue
	 */
	if (sop->so_pack_hd == NULL)
		sop->so_pack_hd = php;
	else
		sop->so_pack_tl->ph_pack_lk = php;
	sop->so_pack_tl = php;

	/*
	 * Finally, bump send sequence number
	 */
	SEQ_INCR(sop->so_send, 1);

	return (0);
}


/*
 * Detect Retransmitted PDUs
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *	nsq	connection sequence value (N(SQ)) from received PDU
 *
 * Returns:
 *	0	received PDU was NOT retransmitted
 *	1	received PDU was retransmitted
 *
 */
int
sscop_is_rexmit(sop, nsq)
	struct sscop	*sop;
	u_char		nsq;
{

	/*
	 * For Q.SAAL1, N(SQ) doesn't exist
	 */
	if (sop->so_vers == SSCOP_VERS_QSAAL)
		return (0);

	/*
	 * If we've already received the N(SQ) value,
	 * then this PDU has been retransmitted
	 */
	if (nsq == sop->so_rcvconn)
		return (1);

	/*
	 * New PDU, save its N(SQ)
	 */
	sop->so_rcvconn = nsq;

	return (0);
}


/*
 * Start connection poll timer
 *
 * Arguments:
 *	sop	pointer to sscop connection block
 *
 * Returns:
 *	none
 *
 */
void
sscop_set_poll(sop)
	struct sscop	*sop;
{

	/*
	 * Decide which polling timer value to set
	 */
	if ((sop->so_xmit_hd != NULL) || SEQ_NEQ(sop->so_send, sop->so_ack)) {
		/*
		 * Data outstanding, poll frequently
		 */
		sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timepoll;
		sop->so_flags &= ~SOF_KEEPALIVE;
	} else {
		/*
		 * No data outstanding, just poll occassionally
		 */
		sop->so_timer[SSCOP_T_POLL] = sop->so_parm.sp_timekeep;
		sop->so_flags |= SOF_KEEPALIVE;
	}

	return;
}

