/*
 * ccid2Sender.c -- support code for CCID2 sender-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"

/*
 * Private function prototypes
 */
static void computeRTO(dctpSocket *sock, ccid2XmtInfo *xi);
static int ccid2SenderRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt);
static uint32_t ccid2SenderGetCurrentRTT(dctpSocket *sock);
static int ccid2SenderProcessCcidOption(dctpSocket *sock, dctpPacket *pkt, uint_t op,
					uint_t oplen, uint8_t *ops);
static void ccid2SenderRcvdSlowRcvr(dctpSocket *sock);
static void ccid2SlowRcvrTimeout(void *arg);
static void ccid2SenderRcvdDataDropped(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
				       uint_t oplen);
static uint64_t ccid2SenderRcvdAckVector(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
					 uint_t oplen, uint_t vtype);
static void ccid2XmtTimeout(void * arg);
static void ccid2SenderRcvdNdpCount(dctpSocket *sock, dctpPacket *pkt, uint64_t ndpcount);
static void ccid2SenderRcvdTimestampEcho(dctpSocket *sock, uint32_t tsecho, uint32_t etime);
static void ccid2SenderRcvdElapsedTime(dctpSocket *sock, uint64_t ackno, uint32_t etime);
static void ccid2SenderAddOptions(dctpSocket *sock, dctpPacket *pkt,
				  uint8_t *ops, uint_t *oplen);
static int ccid2SenderDataXmtOK(dctpSocket *sock, uint_t len);
static void ccid2IdleTimeout(void *arg);
static int ccid2SenderXmtPkt(dctpSocket *sock, dctpPacket *pkt);
static void pushXmtHistory(ccid2SenderInfo *info, ccid2XmtInfo *xi);
static void removeXmtHistory(ccid2SenderInfo *info, ccid2XmtInfo *xi);
static void freeXmtInfoBuf(ccid2SenderInfo *info, ccid2XmtInfo *xi);
static ccid2XmtInfo *getXmtInfoBuf(ccid2SenderInfo *info);
static void ccid2SenderDestroyCcidInfo(dctpSocket *sock);
static uint_t xmtInfoGetDataPkt(ccid2XmtInfo *xi);
static void xmtInfoSetDataPkt(ccid2XmtInfo *xi);
static uint_t xmtInfoGetAckPkt(ccid2XmtInfo *xi);
static void xmtInfoSetAckPkt(ccid2XmtInfo *xi);
static uint_t xmtInfoGetDupAcks(ccid2XmtInfo *xi);
static void xmtInfoSetDupAcks(ccid2XmtInfo *xi, uint_t d);
static uint64_t xmtInfoGetSeqno(ccid2XmtInfo *xi);
static uint8_t xmtInfoGetState(ccid2XmtInfo *xi);
static void xmtInfoSetState(ccid2XmtInfo *xi, uint8_t st);

static dctpCcidSenderFuncs ccid2SenderFuncs = {
    ccid2SenderRcvAckblePacket,
    ccid2SenderGetCurrentRTT,
    ccid2SenderProcessCcidOption,
    ccid2SenderRcvdSlowRcvr,
    ccid2SenderRcvdDataDropped,
    ccid2SenderRcvdAckVector,
    ccid2SenderRcvdNdpCount,
    ccid2SenderRcvdTimestampEcho,
    ccid2SenderRcvdElapsedTime,
    ccid2SenderAddOptions,
    ccid2SenderDataXmtOK,
    ccid2SenderXmtPkt,
    ccid2SenderDestroyCcidInfo
};

static void computeRTO(dctpSocket *sock, ccid2XmtInfo *xi) {
    ccid2SenderInfo *info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    uint64_t now;
    uint32_t rEst;

    if (info->pktsSinceLastRttEst >= info->cwnd) {
	/* Time to make the doughnuts */
	info->pktsSinceLastRttEst -= info->cwnd;
	now = dctpoNow();
	/* Time since xmt in microseconds */
	rEst = now - xi->xtime;
	if (info->srtt == 0) {
	    info->srtt = rEst;
	    info->rttvar = rEst/2;
	} else {
	    info->rttvar = (3*info->rttvar)/4 +
		(info->srtt > rEst) ? (info->srtt - rEst)*4 : (rEst - info->srtt)*4;
	    info->srtt = (7*info->srtt)/8 + rEst/8;
	}
	info->rto = info->srtt + (DCTPCCID2_CLOCKGRAN >= (4*info->rttvar)) ?
	    DCTPCCID2_CLOCKGRAN : 4*info->rttvar;
    }
}

static int ccid2SenderRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt) {
    ccid2SenderInfo *info;
    ccid2XmtInfo *xi, *xitmp;
    uint_t newacks, dataMarked, remove, dupacks, rcvd;

    if (sock->ccidSenderInfo == NULL) {
	return(0);
    }
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    if (pkt->chdr.hasAck) {
	/* Figure out what's received, lost */
	newacks = 0;
	dataMarked = 0;
	rcvd = 0;
	xi = info->xmtHistory;
	while (xi) {
#ifdef DEBUG
	    if (xi->mgc != DCTPSOCK_MAGICNO) {
		dctpoLog(DCTPLOG_DEBUG, "ccid2SenderRcvAckblePacket: bad xi magic\n");
		bpoint("ccid2SenderRcvAckblePacket");
	    }
#endif
	    if (dctpiCmpSeqNo(xmtInfoGetSeqno(xi), pkt->chdr.ackno) == 0) {
		computeRTO(sock, xi);
	    }
	    switch (xmtInfoGetState(xi)) {
	    case DCTPCCID2_ACKVECTORRCVD:
		++newacks;
		if (xmtInfoGetDataPkt(xi)) {
		    ++rcvd;
		    ++info->dataRcvdOK;
		}
		if (xmtInfoGetAckPkt(xi)) {
		    sock->ccidRcvrFuncs->ackAcked(sock, xmtInfoGetSeqno(xi),
						  DCTPCCID2_ACKVECTORRCVD);
		}
		remove = 1;
		break;
	    case DCTPCCID2_ACKVECTORMARKED:
		++newacks;
		if (xmtInfoGetDataPkt(xi)) {
		    ++rcvd;
		    ++dataMarked;
		}
		if (xmtInfoGetAckPkt(xi)) {
		    ++info->ackMarked;
		    sock->ccidRcvrFuncs->ackAcked(sock, xmtInfoGetSeqno(xi),
						  DCTPCCID2_ACKVECTORMARKED);
		}
		remove = 1;
		break;
	    default:
		dupacks = xmtInfoGetDupAcks(xi) + newacks;
		if (dupacks >= DCTPCCID2_NUMDUPACKS) {
		    /* Packet has been lost */
		    if (xmtInfoGetDataPkt(xi)) {
			++rcvd;
			++dataMarked;
		    } else {
			++info->ackMarked;
		    }
		    remove = 1;
		} else {
		    xmtInfoSetDupAcks(xi, dupacks);
		    remove = 0;
		}
	    }
	    if (remove) {
		xitmp = xi->next;
		removeXmtHistory(info, xi);
		freeXmtInfoBuf(info, xi);
		xi = xitmp;
	    } else {
		xi = xi->next;
	    }
	}
	/* Now make window adjustments */
	if (rcvd > info->pipe) info->pipe = 0;
	else info->pipe -= rcvd;
	if (info->pipe == 0) {
	    dctpoStopTimer(&(info->rtoTimer));
	} else if (rcvd) {
	    dctpoStartTimer(&(info->rtoTimer), info->rto, ccid2XmtTimeout, sock);
	}
	if (info->dataDropped || (dataMarked != 0)) {
	    if ((info->cwnd /= 2) == 0) info->cwnd = 1;
	    info->ssthresh = info->cwnd;
	    info->dataRcvdOK = 0;
	    info->dataDropped = 0;
	} else if ((info->cwnd < info->ssthresh) && (!info->slowReceiver)) {
	    info->cwnd += (info->dataRcvdOK <= sock->remoteFeatsCurr.ackRatio) ?
		info->dataRcvdOK/2 : sock->remoteFeatsCurr.ackRatio/2;
	    info->dataRcvdOK = (info->dataRcvdOK & 1) ? 1 : 0;
	} else if ((info->dataRcvdOK >= info->cwnd) && (!info->slowReceiver)) {
	    info->dataRcvdOK -= info->cwnd;
	    ++info->cwnd;
	}
	if (info->cwnd > info->maxpipe*2) {
	    if (info->ssthresh == info->cwnd) {
		info->ssthresh = info->maxpipe*2;
	    }
	    info->cwnd = info->maxpipe*2;
	}
	if (info->pipe < info->cwnd) dctpiXmtPacket(sock);
	/* Adjust Ack Ratio */
	if (info->pktsSinceLastAckRatio >= info->cwnd) {
	    info->pktsSinceLastAckRatio -= info->cwnd;
	    if (info->ackMarked) {
		sock->remoteFeatsDesired.ackRatio *= 2;
		if (sock->remoteFeatsDesired.ackRatio > info->cwnd) {
		    sock->remoteFeatsDesired.ackRatio = info->cwnd;
		}
	    } else if (sock->remoteFeatsDesired.ackRatio > 2) {
		--sock->remoteFeatsDesired.ackRatio;
	    }
	    info->ackMarked = 0;
	    if (sock->remoteFeatsDesired.ackRatio != sock->remoteFeatsCurr.ackRatio) {
		sock->sendFeatureNegotiation = 1;
	    }
	}
    }
    return(0);
}

static uint32_t ccid2SenderGetCurrentRTT(dctpSocket *sock) {
    ccid2SenderInfo *info ;

    if (sock->ccidSenderInfo == NULL) {
	return(DCTPCCID2_INITRTO);
    }
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    return(info->rto);
}

static int ccid2SenderProcessCcidOption(dctpSocket *sock, dctpPacket *pkt, uint_t op,
					uint_t oplen, uint8_t *ops) {
    return(0);
}

static void ccid2SlowRcvrTimeout(void *arg) {
    dctpSocket *sock = (dctpSocket *)arg;
    ccid2SenderInfo *info;

    dctpoSocketLock(sock);
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    info->slowReceiver = 0;
    dctpoSocketUnlock(sock);
}

static void ccid2SenderRcvdSlowRcvr(dctpSocket *sock) {
    ccid2SenderInfo *info;

    if (sock->ccidSenderInfo == NULL) {
	return;
    }
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    info->slowReceiver = 1;
    dctpoStartTimer(&(info->slowRcvrTimer), info->rto, ccid2SlowRcvrTimeout, sock);
}

static void ccid2SenderRcvdDataDropped(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
				       uint_t oplen) {
    uint_t i, j;
    ccid2SenderInfo *info = (ccid2SenderInfo *)(sock->ccidSenderInfo);

    for (i = 0; i < oplen; ++i) {
	if ((ops[i] & DCTPOPTION_DDROP_NORMALBIT) == 1) {
	    for (j = ((ops[i] & DCTPOPTION_DDROP_RUNLEN) + 1);
	     j > 0; --j) {
		switch (ops[i] & (~DCTPOPTION_DDROP_RUNLEN)) {
		case DCTPOPTION_DDROP_PROTOCONSTRAINT:
		    break;
		case DCTPOPTION_DDROP_APPNOTLISTENING:
		    info->stopData = 1;
		    break;
		case DCTPOPTION_DDROP_RCVBUFFER:
		    if (info->cwnd > 1) --info->cwnd;
		    break;
		default:
		    info->dataDropped = 1;
		    break;
		}
	    }
	}
    }
}

static uint64_t ccid2SenderRcvdAckVector(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
					 uint_t oplen, uint_t vtype) {
    ccid2SenderInfo *info;
    ccid2XmtInfo *xi;
    uint_t i, j;

    if (sock->ccidSenderInfo == NULL) {
	return(0);
    }
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    xi = info->xmtHistory;
    for (i = 0; i < oplen; ++i) {
	for (j = (ops[i] & DCTPCCID2_ACKVECTORCOUNT) + 1; j > 0; --j) {
	    while (xi && (dctpiCmpSeqNo(xmtInfoGetSeqno(xi), ackno) > 0)) {
		xi = xi->next;
	    }
	    if (xi && (dctpiCmpSeqNo(xmtInfoGetSeqno(xi), ackno) == 0)) {
		/* Only change state for packets not already ack'ed */
		if (xmtInfoGetState(xi) == DCTPCCID2_ACKVECTORNOTRCVD) {
		    xmtInfoSetState(xi, ops[i]);
		}
	    }
	    ackno = dctpiSeqNoSub(ackno, 1);
	}
    }
    return(ackno);
}

static void ccid2XmtTimeout(void * arg) {
    dctpSocket *sock = (dctpSocket *)arg;
    ccid2SenderInfo *info;

    dctpoSocketLock(sock);
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    info->pipe = 0;
    info->rto *= 2;
    dctpoStartTimer(&(info->rtoTimer), info->rto, ccid2XmtTimeout, sock);
    info->ssthresh = info->cwnd/2;
    if (info->ssthresh < 2) info->ssthresh = 2;
    info->cwnd = 1;
    dctpiXmtPacket(sock);
    dctpoSocketUnlock(sock);
}

static void ccid2SenderRcvdNdpCount(dctpSocket *sock, dctpPacket *pkt, uint64_t ndpcount) {
    return;
}

static void ccid2SenderRcvdTimestampEcho(dctpSocket *sock, uint32_t tsecho, uint32_t etime) {
    return;
}

static void ccid2SenderRcvdElapsedTime(dctpSocket *sock, uint64_t ackno, uint32_t etime) {
    return;
}

static void ccid2SenderAddOptions(dctpSocket *sock, dctpPacket *pkt,
				  uint8_t *ops, uint_t *oplen) {
    return;
}

static int ccid2SenderDataXmtOK(dctpSocket *sock, uint_t len) {
    ccid2SenderInfo *info;

    if ((sock->ccidSenderInfo == NULL) || (sock->remoteFeatsCurr.sendAckVector == 0)) {
	return(-1);
    }
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    if (info->stopData) {
	return(-1);
    } else if (info->pipe < info->cwnd) {
	++info->pipe;
	if (info->pipe > info->maxpipe) info->maxpipe = info->pipe;
	return(0);
    } else {
	return(-1);
    }
}

static void ccid2IdleTimeout(void *arg) {
    dctpSocket *sock = (dctpSocket *)arg;
    ccid2SenderInfo *info;

    dctpoSocketLock(sock);
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    if (info->cwnd > DCTPCCID2_INITCWND) {
	info->cwnd = DCTPCCID2_INITCWND;
    }
    dctpoSocketUnlock(sock);
}

static int ccid2SenderXmtPkt(dctpSocket *sock, dctpPacket *pkt) {
    ccid2SenderInfo *info;
    ccid2XmtInfo *xi;

    if (sock->ccidSenderInfo == NULL) {
	return(0);
    }
    info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    if ((xi = getXmtInfoBuf(info)) == NULL) {
	return(-1);
    }
#ifdef DEBUG
    if (xi->mgc != DCTPSOCK_MAGICNO) {
	dctpoLog(DCTPLOG_DEBUG, "ccid2SenderXmtPkt: bad xi magic\n");
	bpoint("ccid2SenderXmtPkt");
    }
#endif
    xi->seqno = pkt->chdr.seqno;
    if ((pkt->chdr.type == DCTPPKT_DATA) || (pkt->chdr.type == DCTPPKT_DATAACK)) {
	xmtInfoSetDataPkt(xi);
	++info->pktsSinceLastRttEst;
	++info->pktsSinceLastAckRatio;
	dctpoStartTimer(&(info->rtoTimer), info->rto, ccid2XmtTimeout, sock);
    }
    if (pkt->chdr.hasAck) {
	xmtInfoSetAckPkt(xi);
    }
    xmtInfoSetState(xi, DCTPCCID2_ACKVECTORNOTRCVD);
    xi->xtime = dctpoNow();
    dctpoStartTimer(&(info->idleTimer), info->rto, ccid2IdleTimeout, sock);
    pushXmtHistory(info, xi);
    return(0);
}

static void pushXmtHistory(ccid2SenderInfo *info, ccid2XmtInfo *xi) {
#ifdef DEBUG
    if (xi->mgc != DCTPSOCK_MAGICNO) {
	dctpoLog(DCTPLOG_DEBUG, "pushXmtHistory: bad magic number 0x%x in 0x%x\n",
		 xi->mgc, (uint32_t)xi);
	bpoint("pushXmtHistory");
    }
#endif
    if ((xi->next = info->xmtHistory) != NULL) {
	xi->next->prev = xi;
    }
    xi->prev = NULL;
    info->xmtHistory = xi;
}

static void removeXmtHistory(ccid2SenderInfo *info, ccid2XmtInfo *xi) {
#ifdef DEBUG
    if (xi->mgc != DCTPSOCK_MAGICNO) {
	dctpoLog(DCTPLOG_DEBUG, "removeXmtHistory: bad magic number 0x%x in 0x%x\n",
		 xi->mgc, (uint32_t)xi);
	bpoint("removeXmtHistory");
    }
#endif
    if (xi->prev) {
	xi->prev->next = xi->next;
    } else {
	info->xmtHistory = xi->next;
    }
    if (xi->next) {
	xi->next->prev = xi->prev;
    }
}

static void freeXmtInfoBuf(ccid2SenderInfo *info, ccid2XmtInfo *xi) {
#ifdef DEBUG
    if (xi->mgc != DCTPSOCK_MAGICNO) {
	dctpoLog(DCTPLOG_DEBUG, "freeXmtHistory: bad magic number 0x%x in 0x%x\n",
		 xi->mgc, (uint32_t)xi);
	bpoint("freeXmtHistory");
    }
#endif
    if ((xi->next = info->xmtInfoBufs) != NULL) {
	xi->next->prev = xi;
    }
    xi->prev = NULL;
    info->xmtInfoBufs = xi;
}

static ccid2XmtInfo *getXmtInfoBuf(ccid2SenderInfo *info) {
    ccid2XmtInfo *xi;

    if ((xi = info->xmtInfoBufs) != NULL) {
	info->xmtInfoBufs = xi->next;
	if (xi->next) xi->next->prev = NULL;
	return(xi);
    } else if ((info->xmtInfoCount < DCTPCCID2_MAXXMTINFOBUFS) &&
	  ((xi = dctpoMalloc(sizeof(ccid2XmtInfo))) != NULL)) {
	++info->xmtInfoCount;
#ifdef DEBUG
	xi->mgc = DCTPSOCK_MAGICNO;
#endif
	xi->next = NULL;
	xi->prev = NULL;
	return(xi);
    } else {
	dctpoLog(DCTPLOG_ERR, "getXmtInfoBuf: can't malloc new buf\n");
	return(NULL);
    }
}

int dctpiCcid2SenderOpen(dctpSocket *sock) {
    ccid2SenderInfo *info;

    if (sock->ccidSenderFuncs != NULL) {
	return(0);
    } else if ((info = dctpoMalloc(sizeof(ccid2SenderInfo))) != NULL) {
	dctpoMemset(info, 0, sizeof(ccid2SenderInfo));
	info->cwnd = DCTPCCID2_INITCWND;
	info->maxpipe = info->cwnd;
	info->ssthresh = DCTPCCID2_INITSSTHRESH;
	info->rto = DCTPCCID2_INITRTO;
	sock->ccidSenderFuncs = &ccid2SenderFuncs;
	sock->ccidSenderInfo = info;
	sock->useDataAck = 1;
	return(0);
    } else {
	return(-1);
    }
}

static void ccid2SenderDestroyCcidInfo(dctpSocket *sock) {
    ccid2SenderInfo *info = (ccid2SenderInfo *)(sock->ccidSenderInfo);
    ccid2XmtInfo *xi, *xin;

    if (info) {
	xi = info->xmtInfoBufs;
	while (xi != NULL) {
	    xin = xi->next;
	    dctpoFree(xi);
	    xi = xin;
	}
	xi = info->xmtHistory;
	while (xi != NULL) {
	    xin = xi->next;
	    dctpoFree(xi);
	    xi = xin;
	}
	dctpoFree(info);
	sock->ccidSenderInfo = NULL;
    }
    sock->ccidSenderFuncs = NULL;
}

static uint_t xmtInfoGetDataPkt(ccid2XmtInfo *xi) {
    return((xi->seqno & 0x0100000000000000) != 0);
}

static void xmtInfoSetDataPkt(ccid2XmtInfo *xi) {
    xi->seqno |= 0x0100000000000000;
}

static uint_t xmtInfoGetAckPkt(ccid2XmtInfo *xi) {
    return((xi->seqno & 0x0200000000000000) != 0);
}

static void xmtInfoSetAckPkt(ccid2XmtInfo *xi) {
    xi->seqno |= 0x0200000000000000;
}

static uint_t xmtInfoGetDupAcks(ccid2XmtInfo *xi) {
    return((uint_t)((xi->seqno >> 48) & 0xff));
}

static void xmtInfoSetDupAcks(ccid2XmtInfo *xi, uint_t d) {
    xi->seqno = (xi->seqno & 0xff00ffffffffffff) |
	(((uint64_t)(d & 0xff)) << 48);
}

static uint64_t xmtInfoGetSeqno(ccid2XmtInfo *xi) {
    return(xi->seqno & DCTP_48BITMASK);
}

static uint8_t xmtInfoGetState(ccid2XmtInfo *xi) {
    return((uint8_t)((xi->seqno >> 56) & 0xc0));
}

static void xmtInfoSetState(ccid2XmtInfo *xi, uint8_t st) {
    xi->seqno = (xi->seqno & 0x3fffffffffffffff) |
	(((uint64_t)(st & 0xc0)) << 56);
}

