/*
 * ccod3Rcvr.c -- support code for CCID3 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 "ccid3.h"

/*
 * Rcvr CCID3 functions
 */

/*
 * Function prototypes
 */
static void ccid3RcvrDestroyCcidInfo(dctpSocket *sock);
static int ccid3RcvrProcessCcidOption(dctpSocket *sock, uint_t, uint_t, uint8_t **);
static int ccid3RcvrRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt);
static void ccid3RcvrAddOptions(dctpSocket *sock, dctpPacket *pkt,
				uint8_t *opts, uint_t *oplen);
static void ccid3AckAcked(dctpSocket *sock, uint64_t seqno, uint8_t ackState);

static dctpCcidRcvrFuncs ccid3RcvrFuncs = {
    ccid3RcvrRcvAckblePacket,
    ccid3RcvrProcessCcidOption,
    ccid3AckAcked,
    ccid3RcvrAddOptions,
    ccid3RcvrDestroyCcidInfo
};

#ifdef NEVER
static void dumpRInfo(ccid3RcvrInfo *info, char *from) {
    uint64_t now = dctpoNow();
    int i;
    static uint64_t debugrate = 0;

    if ((now - debugrate) >= 1000000) {
	dctpoLog(DCTPLOG_DEBUG, "%s: xRcv = %u\n", from, info->xRcv);
	dctpoLog(DCTPLOG_DEBUG, "%s: lastXRcv = %u\n", from, info->lastXRcv);
	i = info->lossIntIndex;
	do {
	    dctpoLog(DCTPLOG_DEBUG, "%s: loss interval %d, lless %u, loss %u, data %u",
		     from, i, info->lossIntervals[i].llessLength,
		     info->lossIntervals[i].lossLength,
		     info->lossIntervals[i].dataLength);
	    if (--i < 0) i = DCTPCCID3_MAXLOSSINTERVALS - 1;
	} while (i != info->lossIntIndex);
	debugrate = now;
    }
}
#endif  /* DEBUG */

int dctpiCcid3RcvrOpen(dctpSocket *sock) {
    ccid3RcvrInfo *info;

    if (sock->ccidRcvrFuncs != NULL) {
	return(0);
    } else if ((info = dctpoMalloc(sizeof(ccid3RcvrInfo))) != NULL) {
	dctpoMemset(info, 0, sizeof(ccid3RcvrInfo));
	info->pktHistTop = sock->gsr, 1;
	info->pktHist[0].status = DCTPCCID3_PKTRECEIVED;
	info->currLossInt = &(info->lossIntervals[0]);
	sock->ccidRcvrFuncs = &ccid3RcvrFuncs;
	sock->ccidRcvrInfo = info;
	return(0);
    } else {
	return(-1);
    }
}

static void ccid3RcvrDestroyCcidInfo(dctpSocket *sock) {
    ccid3RcvrInfo *info = sock->ccidRcvrInfo;

    if (info) {
	dctpoLog(DCTPLOG_DEBUG,
		 "ccid3RcvrDestroy: freeing info 0x%x for sock 0x%x, server %d\n",
		 (uint_t)info, (uint_t)sock, sock->server);
	dctpoFree(info);
    }
    sock->ccidRcvrInfo = NULL;
    sock->ccidRcvrFuncs = NULL;
}

static uint_t ccvalSub(uint_t a, uint_t b) {
    if (a >= b) return(a - b);
    else return(b - a);
}

static void sendAck(dctpSocket *sock, ccid3RcvrInfo *info) {
    info->ccvalDiff = ccvalSub(info->lastCcval, info->rcvdCcval);
    info->lastCcval = info->rcvdCcval;
    dctpiSendAck(sock);
}

static void startNewLossInterval(dctpSocket *sock, ccid3RcvrInfo *info) {
    sendAck(sock, info);
    if (++info->lossIntIndex >= DCTPCCID3_MAXLOSSINTERVALS) info->lossIntIndex = 0;
    info->currLossInt = &(info->lossIntervals[info->lossIntIndex]);
    dctpoMemset(info->currLossInt, 0, sizeof(ccid3RcvrLossInterval));
}

static void addLostPktToLossInterval(dctpSocket *sock, ccid3RcvrInfo *info,
				     dctpPacket *pkt, uint_t ccval) {
    if (ccvalSub(ccval, info->currLossInt->startCcval) >= 4) {
	/* Lost packet should've been received more than RTT after start of
	 * old loss interval so start a new one */
	startNewLossInterval(sock, info);
    }
    info->currLossInt->lossLength += info->currLossInt->llessLength + 1;
    info->currLossInt->llessLength = 0;
    if ((pkt == NULL) || (pkt->chdr.type == DCTPPKT_DATA) ||
	(pkt->chdr.type == DCTPPKT_DATAACK)) {
	++info->currLossInt->dataLength;
    }
    if ((info->currLossInt->lossLength >= DCTPCCID3_LOSSLENGTHMAX) ||
	(info->currLossInt->dataLength >= DCTPCCID3_DATALENGTHMAX)) {
	startNewLossInterval(sock, info);
    }
}

static void addRcvdPktToLossInterval(dctpSocket *sock, ccid3RcvrInfo *info,
				     dctpPacket *pkt) {
    ++info->currLossInt->llessLength;
    if ((pkt->chdr.type == DCTPPKT_DATA) || (pkt->chdr.type == DCTPPKT_DATAACK)) {
	++info->currLossInt->dataLength;
    }
    if ((info->currLossInt->llessLength >= DCTPCCID3_LLESSLENGTHMAX) ||
	(info->currLossInt->dataLength >= DCTPCCID3_DATALENGTHMAX)) {
	startNewLossInterval(sock, info);
    }
}

static void updateRcvdPktStatus(dctpSocket *sock, ccid3RcvrInfo *info,
				dctpPacket *pkt, uint_t hi) {
    switch (pkt->chdr.ecn) {
    case DCTPECN_MARKED:
	info->pktHist[hi].status = DCTPCCID3_PKTMARKED;
	addLostPktToLossInterval(sock, info, pkt, pkt->chdr.ccval);
	break;
    case DCTPECN_ECN1:
	info->currLossInt->nonce ^= 1;
    default:
	info->pktHist[hi].status = DCTPCCID3_PKTRECEIVED;
	addRcvdPktToLossInterval(sock, info, pkt);
	break;
    }
    info->currLossInt->nonce ^= (pkt->chdr.ecn == DCTPECN_ECN1) ? 0x80 : 0;
}

static void updateLossEvents(dctpSocket *sock, ccid3RcvrInfo *info, dctpPacket *pkt) {
    fixedp ccvalInc;
    fixedp ccvalCount;
    uint_t ccvalDiff;

    if (dctpiCmpSeqNo(pkt->chdr.seqno, info->pktHistTop) > 0) {
	/* Received later packet than previously received */
	ccvalDiff = ccvalSub(pkt->chdr.ccval, info->pktHist[0].ccval);
	if (ccvalDiff == 0) {
	    ccvalInc = FPTP_ZERO;
	} else {
	    ccvalInc =
		fptpDiv(fptpCreateInt(dctpiSeqNoSub(pkt->chdr.seqno, info->pktHistTop)),
			fptpCreateInt(ccvalDiff));
	}
	ccvalCount = fptpCreateInt(pkt->chdr.ccval);
	/* Fill in status of any missing packets */
	while (dctpiCmpSeqNo(info->pktHistTop, pkt->chdr.seqno) < 0) {
	    if (info->pktHist[2].status == DCTPCCID3_PKTMISSING) {
		/* Packet is now lost, update loss interval */
		addLostPktToLossInterval(sock, info, NULL, info->pktHist[2].ccval);
	    }
	    info->pktHist[2] = info->pktHist[1];
	    info->pktHist[1] = info->pktHist[0];
	    info->pktHistTop = dctpiSeqNoAdd(info->pktHistTop, 1);
	    if (info->pktHistTop != pkt->chdr.seqno) {
		ccvalCount += ccvalInc;
		info->pktHist[0].ccval = fptpInteger(ccvalCount);
		info->pktHist[0].status = DCTPCCID3_PKTMISSING;
	    } else {
		info->pktHist[0].ccval = pkt->chdr.ccval;
		updateRcvdPktStatus(sock, info, pkt, 0);
	    }
	}
    } else switch (dctpiSeqNoSub(info->pktHistTop, pkt->chdr.seqno)) {
	/* Got an old packet */
    case 0:
	/* pktHist[0] is always a received pkt, so this is duplicate, ignore */
	break;
    case 1:
	/* Update pktHist[1] status only if it was missing */
	if (info->pktHist[1].status == DCTPCCID3_PKTMISSING) {
	    updateRcvdPktStatus(sock, info, pkt, 1);
	}
	break;
    case 2:
	/* Update pktHist[2] status only if it was missing */
	if (info->pktHist[2].status == DCTPCCID3_PKTMISSING) {
	    updateRcvdPktStatus(sock, info, pkt, 2);
	}
	break;
    default:
	/* Ignore packets already declared lost */
	break;
    }
}

static int ccid3RcvrRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt) {
    ccid3RcvrInfo *info = sock->ccidRcvrInfo;

    info->rcvdCcval = pkt->chdr.ccval;
    updateLossEvents(sock, info, pkt);
    info->xRcv += pkt->appdatalen;
    if (!info->pktRcvd) {
	/* First packet, send ack */
	info->pktRcvd = 1;
	info->lastXRcv = pkt->appdatalen;
	sendAck(sock, info);
    } else if (((pkt->chdr.type == DCTPPKT_DATA) || (pkt->chdr.type == DCTPPKT_DATAACK)) &&
	       (ccvalSub(pkt->chdr.ccval, info->lastCcval) >= 4)) {
	/* RTT expired, send ack */
	sendAck(sock, info);
    }
    return(0);
}

static void ccid3RcvrAddOptions(dctpSocket *sock, dctpPacket *pkt,
				uint8_t *opts, uint_t *oplen) {
    ccid3RcvrInfo *info;
    int i;
    uint32_t etime, xRcv;
    uint8_t *liOplen, *ops = &opts[*oplen];
    uint_t skip;

    if (pkt->chdr.hasAck) {
	info = sock->ccidRcvrInfo;
	/* Add loss intervals */
	*(ops++) = DCTPCCID3_LOSSINTERVALOPT;
	liOplen = ops++;
	*liOplen = 3;
	skip = 0;
	if (info->pktHist[1].status == DCTPCCID3_PKTMISSING) ++skip;
	if (info->pktHist[2].status == DCTPCCID3_PKTMISSING) ++skip;
	*(ops++) = skip;
	i = info->lossIntIndex;
	do {
	    *(ops++) = info->lossIntervals[i].llessLength >> 16;
	    *(ops++) = info->lossIntervals[i].llessLength >> 8;
	    *(ops++) = info->lossIntervals[i].llessLength;
	    *(ops++) = ((info->lossIntervals[i].lossLength >> 16) & 0x7f) |
		info->lossIntervals[i].nonce;
	    *(ops++) = info->lossIntervals[i].lossLength >> 8;
	    *(ops++) = info->lossIntervals[i].lossLength;
	    *(ops++) = info->lossIntervals[i].dataLength >> 16;
	    *(ops++) = info->lossIntervals[i].dataLength >> 8;
	    *(ops++) = info->lossIntervals[i].dataLength;
	    *liOplen += 9;
	    if (info->lossIntervals[i].dataLength == 0) {
		break;
	    }
	    if (--i < 0) i = DCTPCCID3_MAXLOSSINTERVALS - 1;;
	} while (i != info->lossIntIndex);
	/* Add receive rate */
	*(ops++) = DCTPCCID3_RCVRATEOPT;
	*(ops++) = 6;
	/* Scale xRcv if it's an early feedback */
	switch (info->ccvalDiff) {
	case 0:
	    /* Really early!  Use last xRcv */
	    xRcv = info->lastXRcv;
	    break;
	case 1:
	    xRcv = info->xRcv*4;
	    break;
	case 2:
	    xRcv = info->xRcv*2;
	    break;
	case 3:
	    xRcv = (4*info->xRcv)/3;
	    break;
	default:
	    xRcv = info->xRcv;
	    break;
	}
	info->lastXRcv = xRcv;
	*((uint32_t *)ops) = dctpoHtonl(xRcv);
	ops += sizeof(uint32_t);
	/* Add elaspsed time */
	*(ops++) = DCTPOPTION_ELAPSEDTIME;
	etime = (dctpoNow() - sock->lastRcvTime)/10;
	if (etime > 0xffffffff) {
	    /* Need big version */
	    *(ops++) = 6;
	    *((uint32_t *)ops) = dctpoHtonl(etime);
	    ops += sizeof(uint32_t);
	} else {
	    *(ops++) = 4;
	    *((uint32_t *)ops) = dctpoHtons((uint16_t)etime);
	    ops += sizeof(uint16_t);
	}
	*oplen = ops - opts;
    }
}

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

static void ccid3AckAcked(dctpSocket *sock, uint64_t seqno, uint8_t ackState) {
    return;
}

