/*
 * ccod2Rcvr.c -- support code for CCID2 receive-side functions
 *
 * Copyright (C) 2008 Tom Phelan
 *
 * This file is part of dccp-tp.
 *
 * Dccp-tp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Dccp-tp is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with dccp-tp.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Documentation and source code for dccp-tp is available at
 * http://www.phelan-4.com/dccp-tp/.
 */

#include "dctpCore.h"
#include "dctpSupport.h"
#include "dctpInternal.h"
#include "ccid2.h"

/*
 * Rcvr CCID2 functions
 */

/*
 * Function prototypes
 */
static void ccid2RcvrDestroyCcidInfo(dctpSocket *sock);
static int ccid2RcvrProcessCcidOption(dctpSocket *sock, uint_t, uint_t, uint8_t **);
static uint_t ackBufGetState(ccid2RcvrInfo *info, uint_t i);
static void ackBufSetState(ccid2RcvrInfo *info, uint_t i, uint_t val);
static int ackBufGrow(ccid2RcvrInfo *info);
static uint_t ackBufGetSize(ccid2RcvrInfo *info);
static int ccid2RcvrRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt);
static ccid2AckVectorInfo *ackBufGet(ccid2RcvrInfo *info);
static void ackBufPush(ccid2RcvrInfo *info, ccid2AckVectorInfo *ai);
static void ackBufFreeChain(ccid2RcvrInfo *info, ccid2AckVectorInfo *ai);
static void ccid2RcvrAddOptions(dctpSocket *sock, dctpPacket *pkt,
				uint8_t *opts, uint_t *oplen);
static void ccid2AckAcked(dctpSocket *sock, uint64_t seqno, uint8_t ackState);

static dctpCcidRcvrFuncs ccid2RcvrFuncs = {
    ccid2RcvrRcvAckblePacket,
    ccid2RcvrProcessCcidOption,
    ccid2AckAcked,
    ccid2RcvrAddOptions,
    ccid2RcvrDestroyCcidInfo
};

int dctpiCcid2RcvrOpen(dctpSocket *sock) {
    ccid2RcvrInfo *info;

    if (sock->ccidRcvrFuncs != NULL) {
	return(0);
    } else if ((info = dctpoMalloc(sizeof(ccid2RcvrInfo))) != NULL) {
	dctpoMemset(info, 0, sizeof(ccid2RcvrInfo));
	if ((info->ackBuf = dctpoMalloc(DCTPCCID2_ACKBUFSIZEINC)) == NULL) {
	    dctpoFree(info);
	    return(-1);
	}
	info->ackBufSize = DCTPCCID2_ACKBUFSIZEBITSINC;
	info->ackBufHead = info->ackBufSize - 1;
	info->ackBufTail = info->ackBufSize - 1;
	info->ackBufAckno = dctpiSeqNoSub(sock->gsr, 1);
	ackBufSetState(info, 0, DCTPCCID2_ACKBUFNOTRCVD);
	sock->ccidRcvrFuncs = &ccid2RcvrFuncs;
	sock->ccidRcvrInfo = info;
	return(0);
    } else {
	return(-1);
    }
}

static void ccid2RcvrDestroyCcidInfo(dctpSocket *sock) {
    ccid2RcvrInfo *info = sock->ccidRcvrInfo;
    ccid2AckVectorInfo *ai, *ai2;

    if (info) {
	if (info->ackBuf) dctpoFree(info->ackBuf);
	ai = info->ackVecHist;
	while (ai) {
	    ai2 = ai->next;
	    dctpoFree(ai);
	    ai = ai2;
	}
	ai = info->ackVecFreeList;
	while (ai) {
	    ai2 = ai->next;
	    dctpoFree(ai);
	    ai = ai2;
	}
	dctpoFree(info);
	sock->ccidRcvrInfo = NULL;
    }
    sock->ccidRcvrInfo = NULL;
}

static int ccid2RcvrProcessCcidOption(dctpSocket *sock, uint_t a, uint_t b, uint8_t **c) {
    return(0);
}

static uint_t ackBufGetState(ccid2RcvrInfo *info, uint_t i) {
    uint_t bit, byte, st;

    i = (i + info->ackBufHead) % info->ackBufSize;
    byte = i/DCTPCCID2_ACKBUFSTATEPERBYTE;
    bit = (i % DCTPCCID2_ACKBUFSTATEPERBYTE)*DCTPCCID2_ACKBUFSTATEBITSIZE;
    st = (info->ackBuf[byte] >> bit) & DCTPCCID2_ACKBUFSTATEMASK;
    return(st);
}

static void ackBufSetState(ccid2RcvrInfo *info, uint_t i, uint_t val) {
    uint_t bit, byte;

    i = (i + info->ackBufHead) % info->ackBufSize;
    byte = i/DCTPCCID2_ACKBUFSTATEPERBYTE;
    bit = (i % DCTPCCID2_ACKBUFSTATEPERBYTE)*DCTPCCID2_ACKBUFSTATEBITSIZE;
    info->ackBuf[byte] = (info->ackBuf[byte] & (~(DCTPCCID2_ACKBUFSTATEMASK << bit))) |
	((val & DCTPCCID2_ACKBUFSTATEMASK) << bit);
}

static int ackBufGrow(ccid2RcvrInfo *info) {
    uint_t osize, nsize, bhead;
    uint8_t *nbuf;

    info->ackBufHead = (info->ackBufHead == 0) ? info->ackBufSize - 1 :
	info->ackBufHead - 1;
    if (info->ackBufHead == info->ackBufTail) {
	/* Out of space in buffer, must get bigger buf */
	osize = info->ackBufSize/DCTPCCID2_ACKBUFSTATEPERBYTE;
	nsize = osize + DCTPCCID2_ACKBUFSIZEINC;
	if (nsize > DCTPCCID2_ACKBUFMAXSIZE) {
	    return(-1);
	}
	if ((nbuf = dctpoRealloc(info->ackBuf, nsize)) == NULL) {
	    return(-1);
	}
	bhead = info->ackBufHead/DCTPCCID2_ACKBUFSTATEPERBYTE;
	/* Copy stuff that needs to move from front of buffer to end */
	dctpoMemcpy(&(nbuf[osize]), nbuf, (bhead < (DCTPCCID2_ACKBUFSIZEINC - 1)) ?
		    bhead + 1 : DCTPCCID2_ACKBUFSIZEINC);
	if (bhead >= DCTPCCID2_ACKBUFSIZEINC) {
	    /* Copy stuff that needs to move from middle to front */
	    dctpoMemcpy(nbuf, &(nbuf[DCTPCCID2_ACKBUFSIZEINC]),
			(bhead < (2*DCTPCCID2_ACKBUFSIZEINC - 1)) ?
			bhead - (DCTPCCID2_ACKBUFSIZEINC - 1) :
			DCTPCCID2_ACKBUFSIZEINC);
	}
	info->ackBufTail = (info->ackBufTail + info->ackBufSize) %
	    nsize*DCTPCCID2_ACKBUFSTATEPERBYTE;
	info->ackBufSize = nsize*DCTPCCID2_ACKBUFSTATEPERBYTE;
    }
    ackBufSetState(info, 0, DCTPCCID2_ACKBUFNOTRCVD);
    return(0);
}

static uint_t ackBufGetSize(ccid2RcvrInfo *info) {
    uint_t size;

    if (info->ackBufHead == info->ackBufTail) return(0);
    size = (info->ackBufHead < info->ackBufTail) ?
	info->ackBufTail - info->ackBufHead :
	info->ackBufSize - (info->ackBufHead - info->ackBufTail);
    return(size);
}

static int ccid2RcvrRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt) {
    ccid2RcvrInfo *info;
    uint64_t i, ackno;
    uint_t size;

    if (sock->ccidRcvrInfo == NULL) {
	return(0);
    }
    info = (ccid2RcvrInfo *)(sock->ccidRcvrInfo);
    /* Grow ack buf if necessary */
    if (dctpiCmpSeqNo(pkt->chdr.seqno, info->ackBufAckno) > 0) {
	for (ackno = dctpiSeqNoAdd(info->ackBufAckno, 1);
	     dctpiCmpSeqNo(pkt->chdr.seqno, ackno) >= 0;
	     ackno = dctpiSeqNoAdd(ackno, 1)) {
	    if (ackBufGrow(info) < 0) {
		/* Disaster, can't hold ack state, close connection */
		dctpiCloseConnection(sock, NULL);
		return(0);
	    }
	    info->ackBufAckno = ackno;
	}
    }
    /* Find this packet in ack buf and set new state */
    size = ackBufGetSize(info);
    i = dctpiSeqNoSub(info->ackBufAckno, pkt->chdr.seqno);
    if (i < size) {
	switch (pkt->chdr.ecn) {
	case DCTPECN_MARKED:
	    if (ackBufGetState(info, (uint_t)i) == DCTPCCID2_ACKBUFNOTRCVD) {
		ackBufSetState(info, (uint_t)i, DCTPCCID2_ACKBUFMARKED);
	    }
	    break;
	case DCTPECN_ECN1:
	    info->nonce ^= 1;
	default:
	    if (ackBufGetState(info, (uint_t)i) == DCTPCCID2_ACKBUFNOTRCVD) {
		ackBufSetState(info, (uint_t)i, DCTPCCID2_ACKBUFRCVD);
	    }
	    break;
	}
    }
    switch (pkt->chdr.type) {
    case DCTPPKT_DATA:
    case DCTPPKT_DATAACK:
	if (++info->pktsWaitingAck >= sock->localFeatsCurr.ackRatio) {
	    dctpiSendAck(sock);
	}
	break;
    default:
	if (++info->ackPktsWaitingAck >= sock->localFeatsCurr.ackRatio) {
	    dctpiSendAck(sock);
	}
	break;
    }
    return(0);
}

static ccid2AckVectorInfo *ackBufGet(ccid2RcvrInfo *info) {
    ccid2AckVectorInfo *ai, *ai2;

    if ((ai = info->ackVecFreeList) != NULL) {
	info->ackVecFreeList = ai->next;
	return(ai);
    } else if ((info->ackVecCount < DCTPCCID2_MAXACKVECS) &&
	       (ai = dctpoMalloc(sizeof(ccid2AckVectorInfo))) != NULL) {
	++info->ackVecCount;
	return(ai);
    } else {
	/* Out memory!  Reuse least recent ack buf */
	ai = info->ackVecHist;
	ai2 = NULL;
	/* Assume we have at least one buf in history */
	while (ai->next != NULL) {
	    ai2 = ai;
	    ai = ai->next;
	}
	if (ai2) ai2->next = NULL;
	else info->ackVecHist = NULL;
	return(ai);
    }
}

static void ackBufPush(ccid2RcvrInfo *info, ccid2AckVectorInfo *ai) {
    ai->next = info->ackVecHist;
    info->ackVecHist = ai;
}

static void ackBufFreeChain(ccid2RcvrInfo *info, ccid2AckVectorInfo *ai) {
    ccid2AckVectorInfo *ai2, *aip;

    /* If ai isn't on the history list, nothing freed */
    ai2 = info->ackVecHist;
    aip = NULL;
    while (ai2 && (ai2 != ai)) {
	aip = ai2;
	ai2 = ai2->next;
    }
    if (aip) {
	aip->next = NULL;
    } else {
	info->ackVecHist = NULL;
    }
    while (ai2) {
	ai = ai2->next;
	ai2->next = info->ackVecFreeList;
	info->ackVecFreeList = ai2;
	ai2 = ai;
    }
}

static void ccid2RcvrAddOptions(dctpSocket *sock, dctpPacket *pkt,
				uint8_t *opts, uint_t *oplen) {
    uint_t lenptr, veclen, i, size, st, lastSt;
    ccid2RcvrInfo *info;
    ccid2AckVectorInfo *ai;

    if ((sock->ccidRcvrInfo == NULL) || (sock->localFeatsCurr.sendAckVector == 0)) {
	return;
    }
    info = (ccid2RcvrInfo *)(sock->ccidRcvrInfo);
    if (pkt->chdr.hasAck && (size = ackBufGetSize(info)) > 0) {
	/* Add ack vector */
	opts[(*oplen)++] = (info->nonce) ?
	    DCTPOPTION_ACKVECTORN1 : DCTPOPTION_ACKVECTORN0;
	lenptr = *oplen;
	veclen = 2;
	lastSt = -1;
	for (i = 0; i < size; ++i) {
	    st = ackBufGetState(info, i) << DCTPCCID2_ACKBUF2VECTORSTATESHIFT;
	    if ((st == lastSt) &&
		((opts[*oplen] & DCTPCCID2_ACKVECTORCOUNT) != DCTPCCID2_ACKVECTORCOUNT)) {
		opts[*oplen] = (opts[*oplen] & DCTPCCID2_ACKVECTORSTATEMASK) |
		    ((opts[*oplen] & DCTPCCID2_ACKVECTORCOUNT) + 1);
	    } else {
		if (++veclen >= 256) {
		    opts[lenptr] = 255;
		    veclen = 3;
		    opts[++(*oplen)] = (info->nonce) ?
			DCTPOPTION_ACKVECTORN1 : DCTPOPTION_ACKVECTORN0;
		    lenptr = ++(*oplen);
		}
		opts[++(*oplen)] = st;
		lastSt = st;
	    }
	}
	++(*oplen);
	opts[lenptr] = veclen;
	info->ackPktsWaitingAck = 0;
	info->pktsWaitingAck = 0;
	ai = ackBufGet(info);
	ai->ackSeqno = pkt->chdr.seqno;
	ai->ackPtr = info->ackBufHead;
	ai->nonce = info->nonce;
	ackBufPush(info, ai);
    }
}

static void ccid2AckAcked(dctpSocket *sock, uint64_t seqno, uint8_t ackState) {
    ccid2RcvrInfo *info = (ccid2RcvrInfo *)(sock->ccidRcvrInfo);
    ccid2AckVectorInfo *ai, *ai2;
    uint_t newNonce;

    for (ai = info->ackVecHist; ai != NULL; ai = ai->next) {
	if (ai->ackSeqno == seqno) {
	    /* This ack has been acked, but keep it around until one more
	     * time in case reordered packets change its state.  Release
	     * info for everything sent before this one though.
	     */
	    if ((ai = ai->next) != NULL) {
		newNonce = ai->nonce;
		for (ai2 = ai; ai2 != NULL; ai2 = ai2->next) {
		    newNonce ^= ai2->nonce;
		}
		/* Remove acked info */
		info->ackBufTail = (ai->ackPtr == 0) ? (info->ackBufSize - 1) :
		    (ai->ackPtr - 1);
		ackBufFreeChain(info, ai);
		/* Clean up ECN nonce */
		info->nonce ^= newNonce;
		for (ai = info->ackVecHist; ai != NULL; ai = ai->next) {
		    ai->nonce ^= newNonce;
		}
	    }
	    /* All done */
	    return;
	} else if (dctpiCmpSeqNo(ai->ackSeqno, seqno) < 0) {
	    /* Can't find match */
	    return;
	}
    }
}

