/* 
 * $Id: idrp_transport.c,v 1.6 1995/10/08 17:05:21 skh Exp $
 * Merit IDRP release 1.1 (gated 3.5.4).  Copyright (c) 1994 by Merit Network, Inc. 
 */

#include "include.h"
#include "iso.h"
#include "idrp.h"
#include <stdio.h>


/* idrp_transport.c
 *
 * idrp transport queue handling 
 *     	flush_xmit queue(peer)
 *	path_down(peer)
 *	free_acked_pdu(peer)
 *	flush_rexmit_queue(peer)
 *	close_peer(peer) - close all queues and reset timers
 *	begin_close(peer) - flush queues, reset timers for close wait
 * 	ack_pdu(peer,ack,credit,type)	 
 */ 
  
 

/*
 * Flush the transmit queue
 */
void 
flush_xmit_queue(peer)
idrpPeer *peer;
{
	idrpBuffer *curbuf;
	while ((curbuf = peer->xmit_queue_head) != NULL) {
		peer->xmit_queue_head = curbuf->next;
		free_buffer(curbuf);
	}
	peer->xmit_queue_tail = &peer->xmit_queue_head;
}


/*
 * Bring down the path to our neighbor
 */
static void 
path_down(peer)
idrpPeer *peer;
{
	trace_tf(idrp_trace_options, 0, LOG_ERR, ("IDRP path to %s is down", peer->name));
	idrp_peer_down(peer);
}


/*
 * Free the head of the retransmit queue.
 */
static void 
free_acked_pdu(peer)
idrpPeer *peer;
{
	idrpBuffer *buf;
	if (peer->rexmit_queue_head) {
		buf = peer->rexmit_queue_head;
		peer->rexmit_queue_head = buf->next;
		if (buf->next == NULL)
			peer->rexmit_queue_tail = &peer->rexmit_queue_head;
		buf->next = NULL;
		free_buffer(buf);
	}

	/* If the queue is empty, kill the timer. */

	if (!peer->rexmit_queue_head) {
		kill_rexmit_timer(peer);
	}
}


/*
 * Flush the retransmit queue
 */
void 
flush_rexmit_queue(peer)
idrpPeer *peer;
{
	while (peer->rexmit_queue_head) {
		free_acked_pdu(peer);
	}
}


/* 
 * begin close sequence - flush transmit and re-transmitt queues, reset timers
 */ 
void 
begin_close(peer)
idrpPeer *peer;
{
	flush_rexmit_queue(peer);
	flush_xmit_queue(peer);
	peer->cred_avail_zero_seq_last = 0;
	if (peer->task) {
	  /***** hack - chl to stop seg fault ********/
	  task_timer_set(peer->idrp_closewait_timer, IDRP_CLOSE_WAIT_TIME, (time_t) 0);
	  task_timer_reset(peer->idrp_keepalive_timer);
	  task_timer_reset(peer->idrp_hold_timer);
	}
	kill_rexmit_timer(peer);

	/* if the close comes in established state -
	 * take the pathway down
	 * decrement the number of active idrp peers
	 * set the flag on the idrp peer to IDRPF_TRY_CONNECT 
	 * unless peer is being idled in configuration change
	 */ 
	
	if (peer->state == IDRP_ESTABLISHED)
		{
		path_down(peer);
		idrp_n_established--;
		if (peer->idrp_flags & IDRPF_IDLED)
			{
			/* if peer being idled - don't set TRY_CONNECT flag 
			 */
			peer->idrp_flags = IDRPF_IDLED;
			}
		else
			{
			peer->idrp_flags = IDRPF_TRY_CONNECT; 
			}
		} 
	trace_tf(idrp_trace_options, TR_NORMAL,  0, ("IDRP %s connection closing", peer->name));
}




/* 
 * close now - flush transmit and re-transmitt queues, reset timers
 */ 
void 
close_now(peer)
idrpPeer *peer;
{
	flush_rexmit_queue(peer);
	flush_xmit_queue(peer);
	peer->cred_avail_zero_seq_last = 0;
	task_timer_reset(peer->idrp_closewait_timer);
	task_timer_reset(peer->idrp_keepalive_timer);
	task_timer_reset(peer->idrp_hold_timer);
	task_timer_set(peer->idrp_closestay_timer,IDRP_CLOSE_STAY_TIME,(time_t) 0);
	kill_rexmit_timer(peer);
#ifdef ECHOKLUDGE
	task_timer_reset(peer->idrp_echo_timer);
#endif

	/* if the close comes in established state -
	 * take the pathway down
	 * decrement the number of active idrp peers
	 * set the flag on the idrp peer to IDRPF_TRY_CONNECT 
	 * unless peer is being idled in configuration change
	 */ 
	
	if (peer->state == IDRP_ESTABLISHED)
		{
		path_down(peer);
		idrp_n_established--;
		if (peer->idrp_flags & IDRPF_IDLED)
			{
			/* if peer being idled - don't set TRY_CONNECT flag 
			 */
			peer->idrp_flags = IDRPF_IDLED;
			}
		else
			{
			peer->idrp_flags = IDRPF_TRY_CONNECT; 
			}
		} 
	trace_tf(idrp_trace_options, TR_NORMAL, 0, ("IDRP %s connection is now closed", peer->name));
}



/* 
 * ack_pdu (peer,ack,credit,type) 
 *     process end to end acknoweldgements in a pdu, set flag if acked a 
 *     sequence number.
 */
void 
ack_pdu(peer, ack, credit,type)
idrpPeer *peer;
u_int ack;
u_int credit;
u_int type;
{
	idrpBuffer *buf;
	u_int old_upper_window_edge;

	/* Pull any acknowledged packets off of the retransmission queue */

	if (ack >= peer->send_unack) {
		acked_something = TRUE;
		while (ack >= peer->send_unack) {
			if (!peer->rexmit_queue_head) {
				trace_tf(idrp_trace_options, 0, LOG_ERR, ("IDRP %s rexmit queue underflow\n", peer->name));
				break;
			} else {
				if (peer->rexmit_queue_head->seq != peer->send_unack) {
					trace_tf(idrp_trace_options, 0, LOG_ERR, ("IDRP %s rexmit queue broken", peer->name));
				}
				free_acked_pdu(peer);
			}
			peer->send_unack++;
		}
	}

	/* Update the upper window edge.  If we were flow blocked, and now we're not, make sure
	 * our peer knows that we're not blocked any longer (send a keepalive if there's nothing
	 * otherwise queued to send).
	 */

	old_upper_window_edge = peer->upper_window_edge;
	peer->upper_window_edge = MAX(peer->upper_window_edge, ack + credit + 1);
	if (peer->state == IDRP_ESTABLISHED &&
	    old_upper_window_edge != peer->upper_window_edge &&
	    old_upper_window_edge <= peer->send_next &&
	    peer->xmit_queue_head == NULL) {
		send_keepalive(peer);
	}

	/* If there are any packets waiting in the transmission queue, post as many as possible. */
	/* CEASE, ERROR, and OPEN with errors do not see the light of this processing */
	/* ACK only processed for valid open, valid KEEPALIVE, any (??) update, any (??) refresh */
	/* %% wonder if only good refreshes should have ack processed?? - spec unclear */
	/* either one seems to be ok, since an error in either will soon shutdown peer */ 

	post_enqueued_pdus(peer);
}

void
pull_old_open_rexmit(peer)
idrpPeer        *peer;
{
        if (!peer->rexmit_queue_head)
                {
                trace_tf(idrp_trace_options,0, LOG_ERR, ("IDRP %s no old open to toss in old open toss \n", peer->name));
                }
         else
                {
                 if (peer->rexmit_queue_head->seq != peer->send_unack)
                        {
                        trace_tf(idrp_trace_options,0, LOG_ERR, ("IDRP %s in old open toss rexmit queue broken", peer->name));
                        }

                if (peer->rexmit_queue_head->pdu->header.pdu_type == IDRP_OPEN_TYPE)
                        {
                        free_acked_pdu(peer);
                        }
                else
                        {
                        trace_tf(idrp_trace_options,0, LOG_ERR, ("IDRP %s in old open toss top of queue is not open type it is type = %x ",
                                 peer->name,peer->rexmit_queue_head->pdu->header.pdu_type));
                        }
                }
}

