/*
 * ccid3Sender.c -- support code for CCID3 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 "fixedPointMath.h"
#include "ccid3.h"

static int ccid3SenderRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt);
static int ccid3SenderDataXmtOK(dctpSocket *sock, uint_t len);
static int ccid3SenderXmtPkt(dctpSocket *sock, dctpPacket *pkt);
static uint32_t ccid3SenderGetCurrentRTO(dctpSocket *sock);
static int ccid3SenderProcessCcidOption(dctpSocket *sock, dctpPacket *pkt, uint_t op,
					uint_t oplen, uint8_t *ops);
static void ccid3SenderAddOptions(dctpSocket *sock, dctpPacket *pkt,
				  uint8_t *ops, uint_t *oplen);
static void rttTimeout(void *arg);
static void ccid3SenderRcvdElapsedTime(dctpSocket *sock, uint64_t ackno, uint32_t etime);
static void ccid3SenderRcvdTimestampEcho(dctpSocket *sock, uint32_t tsecho, uint32_t etime);
static void ccid3SenderDestroyCcidInfo(dctpSocket *sock);
static uint64_t ccid3SenderRcvdAckVector(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
					 uint_t oplen, uint_t vtype);
static void ccid3SenderRcvdNdpCount(dctpSocket *sock, dctpPacket *pkt, uint64_t ndpcount);
static void ccid3SenderRcvdDataDropped(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
				       uint_t oplen);
static void ccid3SenderRcvdSlowRcvr(dctpSocket *sock);

static dctpCcidSenderFuncs ccid3SenderFuncs = {
    ccid3SenderRcvAckblePacket,
    ccid3SenderGetCurrentRTO,
    ccid3SenderProcessCcidOption,
    ccid3SenderRcvdSlowRcvr,
    ccid3SenderRcvdDataDropped,
    ccid3SenderRcvdAckVector,
    ccid3SenderRcvdNdpCount,
    ccid3SenderRcvdTimestampEcho,
    ccid3SenderRcvdElapsedTime,
    ccid3SenderAddOptions,
    ccid3SenderDataXmtOK,
    ccid3SenderXmtPkt,
    ccid3SenderDestroyCcidInfo
};

#ifdef NEVER
static void dumpInfo(ccid3SenderInfo *info, fixedp now, char *from) {
    int i;
    static fixedp debugrate = FPTP_ZERO;

    if ((now - debugrate) >= FPTP_ONE) {
	dctpoLog(DCTPLOG_DEBUG, "%s: server = %d\n", from, info->sock->server);
	dctpoLog(DCTPLOG_DEBUG, "%s: tIpi = %s\n", from, fptp2str(info->tIpi));
	dctpoLog(DCTPLOG_DEBUG, "%s: segSize = %s\n", from, fptp2str(info->segSize));
	dctpoLog(DCTPLOG_DEBUG, "%s: xInst = %s\n", from, fptp2str(info->xInst));
	dctpoLog(DCTPLOG_DEBUG, "%s: rttSamp = %s\n", from, fptp2str(info->rttSamp));
	dctpoLog(DCTPLOG_DEBUG, "%s: rtt = %s\n", from ,fptp2str(info->rtt));
	dctpoLog(DCTPLOG_DEBUG, "%s: p = %s\n", from, fptp2str(info->lossEventRate));
	dctpoLog(DCTPLOG_DEBUG, "%s: xBps = %s\n", from, fptp2str(info->xBps));
	dctpoLog(DCTPLOG_DEBUG, "%s: xAllowed = %s\n", from, fptp2str(info->xAllowed));
	for (i = 0; i < DCTPCCID3_MAXLOSSINTERVALS; ++i) {
	    dctpoLog(DCTPLOG_DEBUG, "%s: int %d, start %llu, end %llu, dataLen %u\n",
		     from, i, info->lossIntervals[i].start,
		     info->lossIntervals[i].end, info->lossIntervals[i].dataLen);
	}
	debugrate = now;
    }
}
#endif  /* DEBUG */

static fixedp fmax(fixedp u1, fixedp u2) {
    return((u1 > u2) ? u1 : u2);
}

static fixedp fmin(fixedp u1, fixedp u2) {
    return((u1 < u2) ? u1 : u2);
}

static fixedp ccid3Now(void) {
    return(fptpConvertUsec(dctpoNow()));
}

static void checkForXmits(dctpSocket *sock) {
    int i;

    if (sock->xpktqlen) {
	i = 0;
	while (dctpiXmtPacket(sock) == 0) ++i;
	if (i != 0) dctpoSocketSignal(sock);
    }
}

int dctpiCcid3SenderOpen(dctpSocket *sock) {
    ccid3SenderInfo *info;
    fixedp now;

    if (sock->ccidSenderFuncs != NULL) {
	return(0);
    } else if ((info = dctpoMalloc(sizeof(ccid3SenderInfo))) != NULL) {
	dctpoMemset(info, 0, sizeof(ccid3SenderInfo));
	info->rtt = fptpDiv(DCTPCCID3_INITRTO, FPTP_CONST(4, 0));
	info->segSize = DCTPCCID3_INITSEGSIZE;
	info->xAllowed = fptpDiv(info->segSize, info->rtt);
	info->tIpi = fptpDiv(info->segSize, info->xAllowed);
	info->rto = DCTPCCID3_INITRTO;
	now = ccid3Now();
	info->timestampBase = now;
	info->rtoTime = now + DCTPCCID3_INITRTO;
	info->lossIntervalContinue = (uint64_t)(-1);
#ifdef NEVER
	info->sock = sock;
#endif  /* DEBUG */
	sock->ccidSenderInfo = info;
	sock->ccidSenderFuncs = &ccid3SenderFuncs;
	sock->useDataAck = 0;
	dctpoStartTimer(&(info->rttTimer), fptpUsecs(info->rtt), rttTimeout, sock);
	return(0);
    } else{
	return(-1);
    }
}

static fixedp maxXRcvSet(ccid3SenderInfo *info, fixedp now) {
    /* Find max value in xRcvSet, including current xRcv, but not including
     * xRcvSet[2], since it gets pushed out when we add xRcv to set
     */
    info->xRcv = fmax(fmax(info->xRcv, info->xRcvSet[0]), info->xRcvSet[1]);
    /* Put max value in xRcvSet and delete other entries */
    info->xRcvSet[0] = info->xRcv;
    info->xRcvSetTs[0] = now;
    info->xRcvSet[1] = 0.0;
    info->xRcvSet[2] = 0.0;
    /* Return max value */
    return(info->xRcv);
}

static fixedp updateXRcvSet(ccid3SenderInfo *info, fixedp now) {
    fixedp twortt;
    fixedp mxval;

    /* Add X_recv to X_recv_set */
    info->xRcvSet[2] = info->xRcvSet[1];
    info->xRcvSetTs[2] = info->xRcvSet[1];
    info->xRcvSet[1] = info->xRcvSet[0];
    info->xRcvSetTs[1] = info->xRcvSet[0];
    info->xRcvSet[0] = info->xRcv;
    info->xRcvSetTs[0] = now;
    /* Delete values older than two rtts */
    twortt = now - fptpMult(FPTP_TWO, info->rtt);
    if (info->xRcvSetTs[1] < twortt) {
	info->xRcvSet[1] = FPTP_ZERO;
	info->xRcvSet[2] = FPTP_ZERO;
    } else if (info->xRcvSetTs[2] < twortt) {
	info->xRcvSet[2] = FPTP_ZERO;
    }
    /* Return max(X_recv_set) */
    mxval = fmax(fmax(info->xRcvSet[0], info->xRcvSet[1]), info->xRcvSet[2]);
    return(mxval);
}

/*
 * calculateXBps -- do the TCP throughput equation
 *
 * In RFC3448bis, the TCP throughput equation is given as:
 *
 *                                s
 *   X_Bps = ---------------------------------------------
 *           R*(sqrt(2*p/3) + 12*sqrt(3*p/8)*p*(1+32*p^2))
 *
 * Reformatting that so that various constants such as sqrt(2/3) become
 * obvious, we get:
 *
 *                                               s
 *   X_Bps = ----------------------------------------------------------------------------
 *           R*(sqrt(2/3)*sqrt(p) + 12*sqrt(3/8)*p*sqrt(p) + 32*12*sqrt(3/8)*p^3*sqrt(p))
 *
 * We can pre-compute the constants to save run time, so:
 *   C1 = sqrt(2/3) = 0.816496581
 *   C2 = 12*sqrt(3/8) = 7.348469228
 *   C3 = 32*12*sqrt(3/8) = 235.1510153
 *
 * And then the equation becomes:
 *
 *                                 s
 *   X_Bps = ----------------------------------------------
 *           R*(C1*sqrt(p) + C2*p*sqrt(p) + C3*p^3*sqrt(p))
 */

#define DCTPCCID3_TCPC1   FPTP_CONST(0, 816496581)
#define DCTPCCID3_TCPC2   FPTP_CONST(7, 348469228)
#define DCTPCCID3_TCPC3   FPTP_CONST(235, 151015307)

static uint64_t calculateXBps(ccid3SenderInfo *info) {
    fixedp sqrtp = fptpSqrt(info->lossEventRate);
    fixedp psqrtp = fptpMult(info->lossEventRate, sqrtp);
    fixedp p3sqrtp = fptpMult(info->lossEventRate, fptpMult(info->lossEventRate, psqrtp));
    fixedp xBps;

    xBps = fptpDiv(info->segSize,
	fptpMult(info->rtt, (fptpMult(DCTPCCID3_TCPC1, sqrtp) +
		    fptpMult(DCTPCCID3_TCPC2, psqrtp) +
		    fptpMult(DCTPCCID3_TCPC3, p3sqrtp))));
    return(xBps);
}

static void calculateXAllowed(ccid3SenderInfo *info, fixedp now) {
    fixedp rcvLimit;
    fixedp rttSqrt;

    if (info->dataLimited) {
	if (info->newLoss) {
	    info->xRcvSet[0] = fptpDiv(info->xRcvSet[0], FPTP_TWO);
	    info->xRcvSet[1] = fptpDiv(info->xRcvSet[1], FPTP_TWO);
	    info->xRcv = fptpMult(FPTP_CONST(0, 850000000), info->xRcv);
	    rcvLimit = maxXRcvSet(info, now);
	} else {
	    rcvLimit = fptpMult(FPTP_TWO, maxXRcvSet(info, now));
	}
    } else {
	/* Update X_recv_set */
	rcvLimit = fptpMult(FPTP_TWO, updateXRcvSet(info, now));
    }
    info->dataLimited = 1;
    if (info->lossEventRate > FPTP_ZERO) {
	info->xBps = calculateXBps(info);
	info->xAllowed =
	    fmax(fmin(info->xBps, rcvLimit), DCTPCCID3_TMBI);
    } else if ((now - info->tld) >= info->rtt) {
	/* Initial slow-start */
	info->xAllowed =
	    fmax(fmin(fptpMult(FPTP_TWO, info->xAllowed), rcvLimit), DCTPCCID3_TMBI);
    }
    /* Do oscillation reduction */
    rttSqrt = fptpSqrt(info->rttSamp);
    if (info->rttSqmean == FPTP_ZERO) {
	info->rttSqmean = rttSqrt;
    } else {
	info->rttSqmean = fptpMult(FPTP_CONST(0, 900000000), info->rttSqmean) +
	    fptpMult(FPTP_CONST(0, 100000000), rttSqrt);
    }
    info->xInst = fptpDiv(fptpMult(info->xAllowed, info->rttSqmean), rttSqrt);
    if (info->lossEventRate) {
	info->xInst = fmax(info->xInst, DCTPCCID3_TMBI);
    } else if ((now - info->tld) >= info->rtt) {
	info->xInst = fmax(info->xInst, fptpDiv(info->segSize, info->rtt));
	info->tld = now;
    }
    /* All this was to figure out the inter-packet interval */
    info->tIpi = fptpDiv(info->segSize, info->xInst);
#ifdef NEVER
    dumpInfo(info, now, "calcXAllowed");
#endif  /* DEBUG */
}

static fixedp liWeights[DCTPCCID3_MAXLOSSINTERVALS] = {
    FPTP_ONE, FPTP_ONE, FPTP_ONE, FPTP_ONE, FPTP_CONST(0, 800000000),
    FPTP_CONST(0, 600000000), FPTP_CONST(0, 400000000), FPTP_CONST(0, 200000000)
};

static void calculateLossEventRate(ccid3SenderInfo *info) {
    uint_t i;
    fixedp i0, i1, w, oldp = info->lossEventRate;

    i0 = FPTP_ZERO;
    i1 = FPTP_ZERO;
    w = FPTP_ZERO;
    for (i = 0; i < (DCTPCCID3_MAXLOSSINTERVALS - 1); ++i) {
	if (info->lossIntervals[i].dataLen == 0) break;
	i0 += fptpMult(fptpCreateInt(info->lossIntervals[i].dataLen), liWeights[i]);
	w += liWeights[i];
    }
    for (i = 1; i < DCTPCCID3_MAXLOSSINTERVALS; ++i) {
	if (info->lossIntervals[i].dataLen == 0) break;
	i1 += fptpMult(fptpCreateInt(info->lossIntervals[i].dataLen), liWeights[i - 1]);
    }
    if (w == FPTP_ZERO) w = FPTP_ONE;
    i0 = fmax(i0, i1);     /* I_tot */
    i0 = fptpDiv(i0, w);   /* I_mean */
    if (i0 == FPTP_ZERO) {
	info->lossEventRate = FPTP_ZERO;
    } else {
	info->lossEventRate = fptpDiv(FPTP_ONE, i0);
	info->newLoss = (info->lossEventRate > oldp);
    }
}

static void calculateXRcv(ccid3SenderInfo *info) {
    fixedp xDrop, xSlowRcvr;
    fixedp pktPerRtt;

    if ((info->dataDropped != FPTP_ZERO) || info->slowRcvr) {
	if (info->dataDropped != FPTP_ZERO) {
	    pktPerRtt = fptpDiv(info->segSize, info->rtt);
	    xDrop = fmax(info->xInRcv - fptpMult(info->dataDropped, pktPerRtt),
			 fmin(info->xInRcv, pktPerRtt));
	    info->dataDropped = FPTP_ZERO;
	} else {
	    xDrop = FPTP_MAX;
	}
	if (info->slowRcvr) {
	    xSlowRcvr = info->xInRcv;
	    info->slowRcvr = 0;
	} else {
	    xSlowRcvr = FPTP_MAX;
	}
	info->xRcv = fmin(xSlowRcvr, fptpDiv(xDrop, FPTP_TWO));
    } else {
	info->xRcv = info->xInRcv;
    }
}

static int ccid3SenderRcvAckblePacket(dctpSocket *sock, dctpPacket *pkt) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;
    fixedp now, wInit;

    /* Options processing done, don't need to worry about more loss intervals */
    info->lossIntervalContinue = (uint64_t)(-1);
    if (pkt->chdr.hasAck &&
	((info->isFeedbackPkt & DCTPCCID3_REQFDBKOPTIONS) == DCTPCCID3_REQFDBKOPTIONS)) {
	now = ccid3Now();
	calculateXRcv(info);
	calculateLossEventRate(info);
	/* Calculate RTT sample */
	info->rttSamp = info->tsEcho - 
	    (info->isFeedbackPkt & DCTPCCID3_FDBKTSECHO) ?
	    info->elapsedTime : info->tsElapsedTime;
	if (info->rttSamp == FPTP_ZERO) info->rttSamp = FPTP_CONST(0, 10000); /* Min 10 us */
	/* Check that echo and elapsed time weren't bogus, ignore if are */
	if (info->rttSamp < DCTPCCID3_MAXRTT) {
	    /* Update RTT estimate */
	    if (info->rttSampled == 0) {
		info->rttSampled = 1;
		info->rtt = info->rttSamp;
	    } else {
		info->rtt = fptpMult(FPTP_CONST(0, 900000000), info->rtt) + 
		    fptpMult(FPTP_CONST(0, 100000000), info->rttSamp);
	    }
	}
	/* Update RTO */
	info->rto = fmax(fptpMult(FPTP_CONST(4, 0), info->rtt),
			 fptpDiv(fptpMult(FPTP_TWO, info->segSize), info->xAllowed));
	/* Update allowed sending rate */
	if (info->gotFdbk) {
	    calculateXAllowed(info, now);
	} else {
	    info->gotFdbk = 1;
	    wInit = fmin(fptpMult(FPTP_CONST(4, 0), info->segSize),
			 fmax(fptpMult(FPTP_TWO, info->segSize), DCTPCCID3_WINITMAX));
	    info->xAllowed = fptpDiv(wInit, info->rtt);
	    info->tIpi = fptpDiv(info->segSize, info->xAllowed);
	}
	/* Reschedule the no feedback timeout */
	info->rtoTime = now + info->rto;
	info->idle = 1;
    }
    info->isFeedbackPkt = 0;
    checkForXmits(sock);
    return(0);
}

static void updateLimits(ccid3SenderInfo *info, fixedp timerLimit, fixedp now) {
    if (timerLimit < DCTPCCID3_TMBI) {
	timerLimit = DCTPCCID3_TMBI;
    }
    info->xRcvSet[0] = timerLimit;
    info->xRcvSetTs[0] = ccid3Now();
    info->xRcvSet[1] = FPTP_ZERO;
    info->xRcvSet[2] = FPTP_ZERO;
    calculateXAllowed(info, now);
}

static void noFdbkTimeout(dctpSocket *sock, ccid3SenderInfo *info, fixedp now) {
    fixedp xRcv;

    xRcv = fmax(fmax(info->xRcvSet[0], info->xRcvSet[1]), info->xRcvSet[2]);
    if ((!info->rttSampled) && (!info->gotFdbk) && (!info->idle)) {
	info->xAllowed = fmax(fptpDiv(info->xAllowed, FPTP_TWO), DCTPCCID3_TMBI);
    } else if ((((info->lossEventRate > FPTP_ZERO) && (xRcv < DCTPCCID3_RECOVERRATE)) ||
		((info->lossEventRate == FPTP_ZERO) &&
		 (info->xAllowed < fptpMult(FPTP_TWO, DCTPCCID3_RECOVERRATE)))) &&
		info->idle) {
	/* Do nothing */
    } else if (info->lossEventRate == FPTP_ZERO) {
	info->xAllowed = fmax(fptpDiv(info->xAllowed, FPTP_TWO), DCTPCCID3_TMBI);
    } else if (info->xBps > fptpMult(FPTP_TWO, xRcv)) {
	updateLimits(info, xRcv, now);
    } else {
	updateLimits(info, fptpDiv(info->xBps, FPTP_TWO), now);
    }
}

static void rttTimeout(void *arg) {
    dctpSocket *sock = arg;
    ccid3SenderInfo *info;
    fixedp now;

    dctpoSocketLock(sock);
    if ((info = sock->ccidSenderInfo) == NULL) {
	/* Connection  terminated while we waited for lock */
	dctpoSocketUnlock(sock);
	return;
    }
    now = ccid3Now();
    if (now >= info->rtoTime) noFdbkTimeout(sock, info, now);
    checkForXmits(sock);
    dctpoStartTimer(&(info->rttTimer), fptpUsecs(info->rtt),
		    rttTimeout, sock);
    dctpoSocketUnlock(sock);
}

static int ccid3SenderDataXmtOK(dctpSocket *sock, uint_t len) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;
    fixedp now;

    if (info->stopData) {
	return(-1);
    }
    now = ccid3Now();
    if (now >= info->nextPktTime) {
	//info->nextPktTime += info->tIpi;
	info->nextPktTime += FPTP_CONST(0, 10000000);
	if (info->nextPktTime < (now - info->rtt)) {
	    /* If the connection's been idle, can only bank one RTT's worth
	     * of send credits
	     */
	    info->nextPktTime = now - info->rtt;
	}
	return(0);
    } else {
	info->dataLimited = 0;
	return(-1);
    }
}

static int ccid3SenderXmtPkt(dctpSocket *sock, dctpPacket *pkt) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;
    uint_t qrtts;
    fixedp now = ccid3Now();
    fixedp plen;

    qrtts = fptpInteger(fptpDiv(now - info->lastWcTime, fptpDiv(info->rtt, FPTP_CONST(4, 0))));
    if (qrtts > 0) {
	info->ccval = (info->ccval + ((qrtts > 5) ? 5 : qrtts)) & 15;
	info->lastWcTime = now;
    }
    pkt->chdr.ccval = info->ccval;
    info->idle = 0;
    /* Maintain average segment size */
    if ((pkt->chdr.type == DCTPPKT_DATA) || (pkt->chdr.type == DCTPPKT_DATAACK)) {
	plen = fptpCreateInt(pkt->appdatalen);
	if (plen > info->segSize) {
	    info->segSize = plen;
	} else if (plen < info->segSize) {
	    info->segSize = fptpMult(FPTP_CONST(0, 900000000), info->segSize) +
		fptpMult(FPTP_CONST(0, 100000000), plen);
	}
    }
    return(0);
}

static uint32_t ccid3SenderGetCurrentRTO(dctpSocket *sock) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;

    if (info == NULL) {
	return(fptpUsecs(DCTPCCID3_INITRTO));
    } else {
	return(fptpUsecs(info->rto));
    }
}

static void ccid3SenderAddOptions(dctpSocket *sock, dctpPacket *pkt,
				  uint8_t *ops, uint_t *oplen) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;
    uint64_t ts;

    /* Add timestamp option */
    ts = fptpUsecs(ccid3Now() - info->timestampBase)/10;
    if (ts >= 0x100000000) {
	ts -= 0x100000000;
	info->timestampBase += FPTP_CONST(0x100000000*10, 0);
	info->timestampWrapped = 1;
    }
    ops[(*oplen)++] = DCTPOPTION_TIMESTAMP;
    ops[(*oplen)++] = DCTPOPTION_TIMESTAMPLENGTH;
    *((uint32_t *)(&ops[*oplen])) = dctpoHtonl((uint32_t)ts);
    *oplen += sizeof(uint32_t);
}

static int rcvdRcvRate(dctpSocket *soc, dctpPacket *pkt,
		       uint_t oplen, uint8_t *ops) {
    ccid3SenderInfo *info;

    if (oplen < (DCTPCCID3_RCVRATEOPLEN - 2)) {
	return(-1);
    }
    info->xInRcv = fptpCreate(dctpoHtonl(*((uint32_t *)ops)), 0);
    info->isFeedbackPkt |= DCTPCCID3_FDBKRCVRATE;
    return(0);
}

static int rcvdLossInterval(dctpSocket *sock, dctpPacket *pkt,
			    uint_t oplen, uint8_t *ops) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;
    uint64_t intStart, intEnd;
    uint32_t llessLen, lossLen, dataLen;

    if (oplen < (DCTPCCID3_LOSSINTERVALOPMINLEN - 2)) {
	return(-1);
    }
    if ((oplen % 9) != 1) {
	return(-1);
    }
    if (info->lossIntervalContinue == (uint64_t)(-1)) {
	/* This is first loss interval op in packet */
	if (*ops > 3) {
	    /* Invalid skip length */
	    return(-1);
	}
	intStart = dctpiSeqNoSub(pkt->chdr.ackno, *ops);
	info->lossIntNum = 0;
    } else if (*ops != 0) {
	/* Subsequent loss interval op with invalid skip length */
	return(-1);
    } else {
	/* Subsequent loss interval op picks up where previous left off */
	intStart = info->lossIntervalContinue;
    }
    ++ops;         /* Move past skip length to loss intervals */
    --oplen;
    /* Record what we've received */
    while ((info->lossIntNum < DCTPCCID3_MAXLOSSINTERVALS) && (oplen > 0)) {
	llessLen = (ops[0] << 16) | (ops[1] << 8) | ops[2];
	lossLen = ((ops[3] & 0x7f) << 16) | (ops[4] << 8) | ops[5];
	dataLen = (ops[6] << 16) | (ops[7] <<8) | ops[8];
	oplen -= 9;
	ops += 9;
	intEnd = dctpiSeqNoSub(intStart, llessLen + lossLen);
	info->lossIntervals[info->lossIntNum].start = intStart;
	info->lossIntervals[info->lossIntNum].end = intEnd;
	info->lossIntervals[info->lossIntNum].dataLen = dataLen;
	intStart = dctpiSeqNoSub(info->lossIntervals[info->lossIntNum].end, 1);
	++info->lossIntNum;
    }
    /* Set up to continue with more ops in packet */
    info->lossIntervalContinue = intStart;
    info->isFeedbackPkt |= DCTPCCID3_FDBKINTERVALS;
    return(0);
}

static int ccid3SenderProcessCcidOption(dctpSocket *sock, dctpPacket *pkt, uint_t op,
					uint_t oplen, uint8_t *ops) {
    if (pkt->chdr.hasAck) {
	switch (op) {
	case DCTPCCID3_LOSSEVENTRATEOP:
	    return(-1);
	case DCTPCCID3_LOSSINTERVALSOP:
	    return(rcvdLossInterval(sock, pkt, oplen, ops));
	case DCTPCCID3_RECEIVERATEOP:
	    return(rcvdRcvRate(sock, pkt, oplen, ops));
	default:
	    return(-1);
	}
    } else {
	return(-1);
    }
}

static void ccid3SenderRcvdElapsedTime(dctpSocket *sock, uint64_t ackno, uint32_t etime) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;

    info->elapsedTime = fptpConvertUsec(((uint64_t)etime)*10);
    info->isFeedbackPkt |= DCTPCCID3_FDBKELAPTIME;
}

static void ccid3SenderRcvdTimestampEcho(dctpSocket *sock, uint32_t tsecho, uint32_t etime) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;
    fixedp now = ccid3Now();

    if (info->timestampWrapped) {
	/* Long connection!  More than 11.9 hours... */
	if (tsecho > 0xC0000000) {
	    /* This timestamp was sent before the wrap */
	    info->tsEcho = now - info->timestampBase + FPTP_CONST(0x100000000*10, 0) -
		 fptpConvertUsec(tsecho*10);
	} else if (tsecho > 0x40000000) {
	    /* Gotten enough past the wrap to stop worrying about late arrivals */
	    info->timestampWrapped = 0;
	    info->tsEcho = now - info->timestampBase - fptpConvertUsec(tsecho*10);
	} else {
	    /* This was sent after the wrap */
	    info->tsEcho = now - info->timestampBase - fptpConvertUsec(tsecho*10);
	}
    } else {
	/* No wrapping to worry about */
	info->tsEcho = now - info->timestampBase - fptpConvertUsec(tsecho*10);
    }
    info->tsElapsedTime = fptpConvertUsec(((uint64_t)etime)*10);
    info->elapsedTime = info->tsElapsedTime;
    info->isFeedbackPkt |= DCTPCCID3_FDBKELAPTIME | DCTPCCID3_FDBKTSECHO;
}

static void ccid3SenderDestroyCcidInfo(dctpSocket *sock) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;

    if (info) {
	dctpoLog(DCTPLOG_DEBUG,
		 "ccid3SenderDestroy: freeing info 0x%x for sock 0x%x, server %d\n",
		 (uint_t)info, (uint_t)sock, sock->server);
	dctpoStopTimer(&(info->rttTimer));
	dctpiDelayedFree(&(info->fdelay), info, 1);
    }
    sock->ccidSenderInfo = NULL;
    sock->ccidSenderFuncs = NULL;
}

static uint64_t ccid3SenderRcvdAckVector(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
					 uint_t oplen, uint_t vtype) {
    return(ackno);
}

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

static void ccid3SenderRcvdDataDropped(dctpSocket *sock, uint64_t ackno, uint8_t *ops,
				       uint_t oplen) {
    uint_t i, j;
    ccid3SenderInfo *info = 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_APPNOTLISTENING:
		    info->stopData = 1;
		    break;
		case DCTPOPTION_DDROP_RCVBUFFER:
		    info->dataDropped += FPTP_ONE;
		    break;
		}
	    }
	}
    }
}

static void ccid3SenderRcvdSlowRcvr(dctpSocket *sock) {
    ccid3SenderInfo *info = sock->ccidSenderInfo;

    info->slowRcvr = 1;
}

