/*
 * Copyright (c) 1997, by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)tcp_sack.c	1.4	99/08/06 SMI"

#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <netinet/tcp.h>
#include <sys/systm.h>
#include <sys/stropts.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/tcp.h>

/*
 * To insert a new blk to the array of SACK blk in receiver.
 *
 * Parameters:
 *	sack_blk_t *head: pointer to the array of SACK blks.
 *	tcp_seq begin: starting seq num of the new blk.
 *	tcp_seq end: ending seq num of the new blk.
 *	int32_t *num: (referenced) total num of SACK blks on the list.
 */
void
tcp_sack_insert(sack_blk_t *head, tcp_seq begin, tcp_seq end, int32_t *num)
{
	int32_t	i, j, old_num, new_num;
	sack_blk_t tmp[MAX_SACK_BLK - 1];

	/* The array is empty, just add the new one. */
	if (*num == 0) {
		head[0].begin = begin;
		head[0].end = end;
		*num = 1;
		return;
	}

	/*
	 * Check for overlap.  There are five cases.
	 *
	 * 1. there is no overlap with any other SACK blks.
	 * 2. new SACK blk is completely contained in another blk.
	 * 3. tail part of new SACK blk overlaps with another blk.
	 * 4. head part of new SACK blk overlaps with another blk.
	 * 5. new SACK blk completely contains another blk.
	 *
	 * Use tmp to hold old SACK blks.  After the loop, copy them back
	 * to head.
	 */
	old_num = *num;
	if (old_num > MAX_SACK_BLK - 1) {
		old_num = MAX_SACK_BLK - 1;
	}
	new_num = old_num;
	j = 0;
	for (i = 0; i < old_num; i++) {
		if (SEQ_LT(end, head[i].begin) || SEQ_GT(begin, head[i].end)) {
			/* Case 1: continue to check. */
			tmp[j].begin = head[i].begin;
			tmp[j].end = head[i].end;
			j++;
			continue;
		} else if (SEQ_GEQ(begin, head[i].begin) &&
		    SEQ_LEQ(end, head[i].end)) {
			/* Case 2: re-insert the old blk to the head. */
			begin = head[i].begin;
			end = head[i].end;
			new_num--;
			break;
		} else if (SEQ_LEQ(end, head[i].end) &&
		    SEQ_GEQ(end, head[i].begin)) {
			/*
			 * Case 3: Extend the new blk, remove the old one
			 * and continue to check.
			 */
			end = head[i].end;
		} else if (SEQ_GEQ(begin, head[i].begin) &&
		    SEQ_LEQ(begin, head[i].end)) {
			/* Case 4 */
			begin = head[i].begin;
		}
		/* Common code for case 3, 4, and 5. */
		new_num--;
	}

	head[0].begin = begin;
	head[0].end = end;
	for (i = 0; i < new_num; i++) {
		head[i+1].begin = tmp[i].begin;
		head[i+1].end = tmp[i].end;
	}
	*num = new_num + 1;
}


/*
 * To remove a SACK block.
 *
 * Parameters:
 *	sack_blk_t *head: pointer to the array of SACK blks.
 *	tcp_seq end: to remove all sack blk with seq num less than end.
 *	int32_t *num: (referenced) total num of SACK blks in the array.
 */
void
tcp_sack_remove(sack_blk_t *head, tcp_seq end, int32_t *num)
{
	sack_blk_t tmp[MAX_SACK_BLK];
	int32_t i, j, old_num, new_num;

	if (*num == 0)
		return;

	old_num = *num;
	new_num = old_num;
	j = 0;
	/* Walk thru the whole list and copy the new list to tmp[]. */
	for (i = 0; i < old_num; i++) {
		if (SEQ_GT(end, head[i].begin)) {
			/*
			 * Check to see if the old SACK blk needs to be
			 * removed or updated.  If the old blk is just
			 * partially covered, update begin and continue.
			 * If the old blk is completely covered, remove it
			 * and continue to check.
			 */
			if (SEQ_GEQ(end, head[i].end)) {
				new_num--;
				continue;
			} else {
				tmp[j].begin = end;
				tmp[j].end = head[i].end;
			}
		} else {
			tmp[j].begin = head[i].begin;
			tmp[j].end = head[i].end;
		}
		j++;
	}
	/* Copy tmp[] back to the original list. */
	for (i = 0; i < new_num; i++) {
		head[i].begin = tmp[i].begin;
		head[i].end = tmp[i].end;
	}
	*num = new_num;
}


/*
 * Use the SACK info to insert a "notsack'ed" blk.  The notsack'ed blk list
 * contains the list of blks which have not been selectively acknowledged
 * by the receiver.  The SACK info is a blk which is being selectively
 * acknowledged by the receiver.
 *
 * Parameters:
 *	notsack_blk_t **head: address of the pointer to the list of notsack'ed
 *		blks.
 *	tcp_seq begin: starting seq num of the SACK info.
 *	tcp_seq end: ending seq num of the SACK info.
 *	int32_t *num: (referenced) total num of notsack'ed blk on the list.
 *	uint32_t *sum: (referenced) total num of bytes of all the notsack'ed
 *		blks.
 */
void
tcp_notsack_insert(notsack_blk_t **head, tcp_seq begin, tcp_seq end,
    int32_t *num, uint32_t *sum)
{
	notsack_blk_t *prev, *tmp, *new;
	uint32_t tmp_sum, tmp_num;

	if (*head == NULL) {
		return;
	}

	tmp = *head;
	prev = NULL;
	/* Find the right place of updating the list. */
	while ((tmp != NULL) && SEQ_LEQ(tmp->end, begin)) {
		prev = tmp;
		(tmp->sack_cnt)++;
		tmp = tmp->next;
	}

	/*
	 * This can happen only when TCP sends new data but the notsack list
	 * is not updated.
	 */
	if (tmp == NULL) {
		return;
	}

	/*
	 * This means the new SACK info covers something that is not on
	 * the list anymore.
	 */
	if (SEQ_LEQ(end, tmp->begin)) {
		return;
	}

	/* The SACK info covers up to this blk.  So just check for this blk. */
	if (SEQ_LEQ(end, tmp->end)) {
		/*
		 * Only this notsack'ed blk is completely covered.  Delete
		 * it and return.
		 */
		if (end == tmp->end && SEQ_LEQ(begin, tmp->begin)) {
			if (prev != NULL) {
				prev->next = tmp->next;
			} else {
				*head = tmp->next;
			}
			(*num)--;
			*sum -= tmp->end - tmp->begin;
			kmem_free(tmp, sizeof (notsack_blk_t));
			return;
		}
		/* This blk is partially covered. */
		if (SEQ_GEQ(begin, tmp->begin)) {
			/* Check what needs to be updated. */
			if (begin == tmp->begin) {
				*sum -= end - tmp->begin;
				tmp->begin = end;
			} else if (end == tmp->end) {
				*sum -= tmp->end - begin;
				tmp->end = begin;
				(tmp->sack_cnt)++;
			} else {
				/* Split the notsack blk. */
				if ((new = kmem_alloc(sizeof (notsack_blk_t),
				    KM_NOSLEEP)) == NULL) {
					return;
				}
				new->end = tmp->end;
				new->begin = end;
				new->next = tmp->next;
				new->sack_cnt = 0;
				tmp->end = begin;
				tmp->next = new;
				(tmp->sack_cnt)++;
				(*num)++;
				*sum -= end - begin;
			}
		} else {
			*sum -= end - tmp->begin;
			tmp->begin = end;
		}
		return;
	}

	/* Need to check for coverage of this blk and later blks. */
	tmp_sum = *sum;
	tmp_num = *num;
	if (SEQ_LT(tmp->begin, begin)) {
		tmp_sum -= tmp->end - begin;
		tmp->end = begin;
		(tmp->sack_cnt)++;
		prev = tmp;
		tmp = tmp->next;
	}

	while (tmp != NULL) {
		/* The coverage stops here. */
		if (SEQ_GT(tmp->begin, end)) {
			break;
		} else {
			/* Is the blk completely or partially covered? */
			if (SEQ_LEQ(tmp->end, end)) {
				tmp_num--;
				tmp_sum -= tmp->end - tmp->begin;
				if (prev != NULL) {
					prev->next = tmp->next;
					kmem_free(tmp, sizeof (notsack_blk_t));
					tmp = prev->next;
				} else {
					*head = tmp->next;
					kmem_free(tmp, sizeof (notsack_blk_t));
					tmp = *head;
				}
			} else {
				/*
				 * This blk is partially covered.  It also
				 * means it should be the end of coverage.
				 */
				tmp_sum -= end - tmp->begin;
				tmp->begin = end;
				break;
			}
		}
	}
	*num = tmp_num;
	*sum = tmp_sum;
}


/*
 * To remove notsack'ed blks.
 *
 * Parameters:
 *	notsack_blk_t **head: address of the pointer to the list of notsack'ed
 *		blks.
 *	tcp_seq end: to remove all notsack'ed blk with seq num less than end.
 *	int32_t *num: (referenced) total num of notsack'ed blks.
 *	uint32_t *sum: (referenced) total num of bytes of all the notsack'ed
 *		blks.
 */
void
tcp_notsack_remove(notsack_blk_t **head, tcp_seq end, int32_t *num,
    uint32_t *sum)
{
	notsack_blk_t *prev, *tmp;
	uint32_t tmp_sum = *sum;

	if (*head == NULL)
		return;

	prev = NULL;
	tmp = *head;
	while (tmp != NULL) {
		/* There is nothing to discard. */
		if (SEQ_GT(tmp->begin, end)) {
			break;
		}

		/* Is the blk completely or partially covered? */
		if (SEQ_GEQ(end, tmp->end)) {
			(*num)--;
			tmp_sum -= tmp->end - tmp->begin;
			if (prev == NULL) {
				*head = tmp->next;
				kmem_free(tmp, sizeof (notsack_blk_t));
				tmp = *head;
			} else {
				prev->next = tmp->next;
				kmem_free(tmp, sizeof (notsack_blk_t));
				tmp = tmp->next;
			}
		} else {
			tmp_sum -= end - tmp->begin;
			tmp->begin = end;
			break;
		}
	}
	*sum = tmp_sum;
}


/*
 * To update the notsack'ed list when new data is sent.
 *
 * Assumption: this should only be called when new notsack blk is to be added.
 *
 * Parameters:
 *	notsack_blk_t **head: address of the pointer to the list of notsack'ed
 *		blks.
 *	tcp_seq begin: beginning seq num of new data.
 *	tcp_seq end: ending seq num of new data.
 *	int32_t *num: (referenced) total num of notsack'ed blks.
 *	uint32_t *sum: (referenced) total num of bytes of all the notsack'ed
 *		blks.
 */
void tcp_notsack_update(notsack_blk_t **head, tcp_seq begin, tcp_seq end,
    int32_t *num, uint32_t *sum)
{
	notsack_blk_t *tmp;

	tmp = *head;
	/* If the list is empty, create a new one. */
	if (tmp == NULL) {
		if ((tmp = kmem_alloc(sizeof (notsack_blk_t), KM_NOSLEEP)) ==
		    NULL) {
			return;
		}
		tmp->begin = begin;
		tmp->end = end;
		tmp->next = NULL;
		tmp->sack_cnt = 0;
		*head = tmp;
		*num = 1;
		*sum = end - begin;
		return;
	}

	/*
	 * Find the place to add the new blk.  This assumes that new data
	 * is being sent, so the place to insert the new notsack blk is at
	 * the end of the list.
	 */
	while (tmp->next != NULL) {
		tmp = tmp->next;
	}

	/* Does the new blk overlap with old one? */
	if (SEQ_GEQ(tmp->end, begin)) {
		*sum += end - tmp->end;
		tmp->end = end;
	} else {
		/* No.  Need to create a new notsack blk. */
		tmp->next = kmem_alloc(sizeof (notsack_blk_t), KM_NOSLEEP);
		if (tmp->next != NULL) {
			tmp = tmp->next;
			tmp->begin = begin;
			tmp->end = end;
			tmp->next = NULL;
			tmp->sack_cnt = 0;
			(*num)++;
			*sum += end - begin;
		}
	}
}
