// Copyright (C) 1999-2005 Open Source Telecom Corporation.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// ccRTP.  If you copy code from other releases into a copy of GNU
// ccRTP, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU ccRTP, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

/**
 * @file control.cpp
 *
 * @short QueueRTCPManager classes implementation.
 **/

#include "private.h"
#include <ccrtp/cqueue.h>

#ifdef  CCXX_NAMESPACES
namespace ost {
#endif

const uint16 QueueRTCPManager::TIMEOUT_MULTIPLIER = 5;
const double QueueRTCPManager::RECONSIDERATION_COMPENSATION = 2.718281828 - 1.5;
const SDESItemType QueueRTCPManager::firstSchedulable = SDESItemTypeNAME;
const SDESItemType QueueRTCPManager::lastSchedulable = SDESItemTypePRIV;
/// maximum end to end delay: unlimited
const microtimeout_t QueueRTCPManager::defaultEnd2EndDelay = 0;

QueueRTCPManager::QueueRTCPManager(uint32 size, RTPApplication& app):
	RTPDataQueue(size),
	RTCPCompoundHandler(RTCPCompoundHandler::defaultPathMTU),
	queueApplication(app)
{
	controlServiceActive = false;
	controlBwFract = 0.05f;
	sendControlBwFract = 0.25;
	recvControlBwFract = 1-sendControlBwFract;
	ctrlSendCount = 0;

	lowerHeadersSize = networkHeaderSize() + transportHeaderSize();

	nextScheduledSDESItem = SDESItemTypeNAME;

	// initialize RTCP timing
	reconsInfo.rtcpTp.tv_sec = reconsInfo.rtcpTc.tv_sec =
		reconsInfo.rtcpTn.tv_sec = 0;
	reconsInfo.rtcpTp.tv_usec = reconsInfo.rtcpTc.tv_usec =
		reconsInfo.rtcpTn.tv_usec = 0;
	reconsInfo.rtcpPMembers = 1;

	rtcpWeSent = false;
	rtcpAvgSize = sizeof(RTCPFixedHeader) + sizeof(uint32) +
		sizeof(SenderInfo);
	rtcpInitial = true;
	// force an initial check for incoming RTCP packets
	gettimeofday(&rtcpNextCheck,NULL);
	// check for incoming RTCP packets every 1/4 seconds.
	rtcpCheckInterval.tv_sec = 0;
	rtcpCheckInterval.tv_usec = 250000;
	timersub(&rtcpNextCheck,&rtcpCheckInterval,&rtcpLastCheck);

	lastSendPacketCount = 0;

	rtcpMinInterval = 5000000;  // 5 seconds.

	leavingDelay = 1000000; // 1 second
	end2EndDelay = getDefaultEnd2EndDelay();

	// Fill in fixed fields that will never change
	RTCPPacket* pkt = reinterpret_cast<RTCPPacket*>(rtcpSendBuffer);
	pkt->fh.version = CCRTP_VERSION;
	// (SSRCCollision will have to take this into account)
	pkt->info.SR.ssrc = getLocalSSRCNetwork();

	// allow to start RTCP service once everything is set up
	controlServiceActive = true;
}

// TODO Streamline this code (same as above, put into a separate method)
QueueRTCPManager::QueueRTCPManager(uint32 ssrc, uint32 size, RTPApplication& app):
                RTPDataQueue(&ssrc, size),
        RTCPCompoundHandler(RTCPCompoundHandler::defaultPathMTU),
        queueApplication(app)
{
        controlServiceActive = false;
        controlBwFract = 0.05f;
        sendControlBwFract = 0.25;
        recvControlBwFract = 1-sendControlBwFract;
        ctrlSendCount = 0;

        lowerHeadersSize = networkHeaderSize() + transportHeaderSize();

        nextScheduledSDESItem = SDESItemTypeNAME;

        // initialize RTCP timing
        reconsInfo.rtcpTp.tv_sec = reconsInfo.rtcpTc.tv_sec =
                        reconsInfo.rtcpTn.tv_sec = 0;
        reconsInfo.rtcpTp.tv_usec = reconsInfo.rtcpTc.tv_usec =
                        reconsInfo.rtcpTn.tv_usec = 0;
        reconsInfo.rtcpPMembers = 1;

        rtcpWeSent = false;
        rtcpAvgSize = sizeof(RTCPFixedHeader) + sizeof(uint32) +
                        sizeof(SenderInfo);
        rtcpInitial = true;
        // force an initial check for incoming RTCP packets
        gettimeofday(&rtcpNextCheck,NULL);
        // check for incoming RTCP packets every 1/4 seconds.
        rtcpCheckInterval.tv_sec = 0;
        rtcpCheckInterval.tv_usec = 250000;
        timersub(&rtcpNextCheck,&rtcpCheckInterval,&rtcpLastCheck);

        lastSendPacketCount = 0;

        rtcpMinInterval = 5000000;  // 5 seconds.

        leavingDelay = 1000000; // 1 second
        end2EndDelay = getDefaultEnd2EndDelay();

        // Fill in fixed fields that will never change
        RTCPPacket* pkt = reinterpret_cast<RTCPPacket*>(rtcpSendBuffer);
        pkt->fh.version = CCRTP_VERSION;
        // (SSRCCollision will have to take this into account)
        pkt->info.SR.ssrc = getLocalSSRCNetwork();

        // allow to start RTCP service once everything is set up
        controlServiceActive = true;
}

QueueRTCPManager::~QueueRTCPManager()
{
	endQueueRTCPManager();
}

void
QueueRTCPManager::endQueueRTCPManager()
{
	controlServiceActive = false;
	controlBwFract = sendControlBwFract = 0;
}

bool QueueRTCPManager::checkSSRCInRTCPPkt(SyncSourceLink& sourceLink,
					  bool is_new,
					  InetAddress& network_address,
					  tpport_t transport_port)
{
	bool result = true;

	// Test if the source is new and it is not the local one.
	if ( is_new &&
	     sourceLink.getSource()->getID() != getLocalSSRC() )
		return result;

	SyncSource *s = sourceLink.getSource();
	if ( s->getControlTransportPort() != transport_port ||
	     s->getNetworkAddress() != network_address ) {
		// SSRC collision or a loop has happened
		if ( s->getID() != getLocalSSRC() ) {
			// TODO: Optional error counter.

			// Note this differs from the default in the RFC.
			// Discard packet only when the collision is
			// repeating (to avoid flip-flopping)
			if ( sourceLink.getPrevConflict() &&
			     (
			      (network_address ==
			       sourceLink.getPrevConflict()->networkAddress)
			      &&
			      (transport_port ==
			       sourceLink.getPrevConflict()->controlTransportPort)
			      ) ) {
				// discard packet and do not flip-flop
				result = false;
			} else {
				// Record who has collided so that in
				// the future we can how if the
				// collision repeats.
				sourceLink.setPrevConflict(network_address,
							   0,transport_port);
				// Change sync source transport address
				setControlTransportPort(*s,transport_port);
				setNetworkAddress(*s,network_address);
			}

		} else {
			// Collision or loop of own packets.
			ConflictingTransportAddress* conflicting =
				searchControlConflict(network_address,
						      transport_port);
			if ( conflicting ) {
				// Optional error counter.
				updateConflict(*conflicting);
				result = false;
			} else {
				// New collision
				addConflict(s->getNetworkAddress(),
					    s->getDataTransportPort(),
					    s->getControlTransportPort());
				dispatchBYE("SSRC collision detected when receiving RTCP packet");
				renewLocalSSRC();
				setNetworkAddress(*s,network_address);
				setControlTransportPort(*s,transport_port);
				setControlTransportPort(*s,0);
				sourceLink.initStats();
			}
		}
	}
	return result;
}

void
QueueRTCPManager::controlReceptionService()
{
	if ( !controlServiceActive )
		return;

	// A) see if there are incoming RTCP packets
	gettimeofday(&(reconsInfo.rtcpTc),NULL);
	if ( timercmp(&(reconsInfo.rtcpTc),&rtcpNextCheck,>=) ) {
		while ( isPendingControl(0) )
			takeInControlPacket();
		// If this do loops more than once, then we have not
		// been in time. So it skips until the next future
		// instant.
		do {
			timeval tmp = rtcpNextCheck;
			timeradd(&rtcpLastCheck,&rtcpCheckInterval,
				 &rtcpNextCheck);
			rtcpLastCheck = tmp;
		} while ( timercmp(&(reconsInfo.rtcpTc),
				   &(rtcpNextCheck),
				     >=) );
	}
}

void
QueueRTCPManager::controlTransmissionService()
{
	if ( !controlServiceActive )
		return;

	// B) send RTCP packets
	gettimeofday(&(reconsInfo.rtcpTc),NULL);
	if ( timercmp(&(reconsInfo.rtcpTc),&(reconsInfo.rtcpTn),>=) ) {
		if ( timerReconsideration() ) {
			// this would update to last received RTCP packets
			//while ( isPendingControl(0) )
			//	takeInControlPacket();
			rtcpLastCheck = reconsInfo.rtcpTc;
			dispatchControlPacket();
			if (rtcpInitial)
				rtcpInitial = false;
			expireSSRCs();
			reconsInfo.rtcpTp = reconsInfo.rtcpTc;
			// we have updated tp and sent a report, so we
			// have to recalculate the sending interval
			timeval T = computeRTCPInterval();
			timeradd(&(reconsInfo.rtcpTc),&T,&(reconsInfo.rtcpTn));

			// record current number of members for the
			// next check.
			reconsInfo.rtcpPMembers = getMembersCount();
		}
	}
}

bool
QueueRTCPManager::timerReconsideration()
{
	bool result = false;
	// compute again the interval to confirm it under current
	// circumstances
	timeval T = computeRTCPInterval();
	timeradd(&(reconsInfo.rtcpTp),&T,&(reconsInfo.rtcpTn));
	gettimeofday(&(reconsInfo.rtcpTc),NULL);
	if ( timercmp(&(reconsInfo.rtcpTc),&(reconsInfo.rtcpTn),>=) ) {
		reconsInfo.rtcpTp = reconsInfo.rtcpTc;
		result = true;
	}
	return result;
}

void
QueueRTCPManager::expireSSRCs()
{
}

void
QueueRTCPManager::takeInControlPacket()
{
	size_t len = 0;
	InetHostAddress network_address;
	tpport_t transport_port;
	len = recvControl(rtcpRecvBuffer,getPathMTU(),network_address,
			  transport_port);

	// get time of arrival
	struct timeval recvtime;
	gettimeofday(&recvtime,NULL);

	// process a 'len' octets long RTCP compound packet

	// Check validity of the header fields of the compound packet
	if ( !RTCPCompoundHandler::checkCompoundRTCPHeader(len) )
		return;
	RTCPPacket *pkt =
		reinterpret_cast<RTCPPacket *>(rtcpRecvBuffer);

	// TODO: for now, we do nothing with the padding bit
	// in the header.

	bool source_created;
	SyncSourceLink* sourceLink =
		getSourceBySSRC(pkt->getSSRC(),source_created);
	SyncSource* s = sourceLink->getSource();
	if ( source_created ) {
		// Set control transport address.
		setControlTransportPort(*s,transport_port);
		// Network address is assumed to be the same as the control one
		setNetworkAddress(*s,network_address);
		sourceLink->initStats();
		sourceLink->setProbation(getMinValidPacketSequence());
		if ( sourceLink->getHello() )
			onNewSyncSource(*s);
	} else if ( s->getControlTransportPort() == 0 ) {
		// Test if RTP data packets had been received but this
		// is the first control packet from this source.
		setControlTransportPort(*s,transport_port);
	}
	// record reception time
	sourceLink->lastRTCPPacketTime = recvtime;
	sourceLink->lastRTCPSRTime = recvtime;

	size_t pointer = 0;
	// Check the first packet is a report and do special
	// processing for SR reports.
	if ( RTCPPacket::tRR == pkt->fh.type ) {
		// no special initialization is required for
		// RR reports, all reports will be processed
		// in the do-while down here.
	} else if ( RTCPPacket::tSR == pkt->fh.type ){
		if ( checkSSRCInRTCPPkt(*sourceLink,source_created,
					network_address,
					transport_port) )
			sourceLink->lastRTCPSRTime = recvtime;
			onGotSR(*s,pkt->info.SR,pkt->fh.block_count);
		// Advance to the next packet in the compound.
		pointer += pkt->getLength();
		pkt = reinterpret_cast<RTCPPacket *>(rtcpRecvBuffer +pointer);
	} else if ( RTCPPacket::tXR == pkt->fh.type ) {
		// TODO: handle XR reports.
	} else {
		// Ignore RTCP types unknown.
	}

	// Process all RR reports.
	while ( (pointer < len) && (RTCPPacket::tRR == pkt->fh.type) ) {
		sourceLink = getSourceBySSRC(pkt->getSSRC(),
					     source_created);
		if ( checkSSRCInRTCPPkt(*sourceLink,source_created,
					network_address,transport_port) )
			onGotRR(*s,pkt->info.RR,pkt->fh.block_count);
		// Advance to the next packet in the compound
		pointer += pkt->getLength();
		pkt = reinterpret_cast<RTCPPacket *>(rtcpRecvBuffer +pointer);
	}

	// SDES, APP and BYE. process first everything but the
	// BYE packets.
	bool cname_found = false;
	while ( (pointer < len ) &&
	        (pkt->fh.type == RTCPPacket::tSDES ||
		 pkt->fh.type == RTCPPacket::tAPP) ) {
		I ( cname_found || !pkt->fh.padding );
		sourceLink = getSourceBySSRC(pkt->getSSRC(),
					     source_created);
		if ( checkSSRCInRTCPPkt(*sourceLink,source_created,
					network_address,
					transport_port) ) {
			if ( pkt->fh.type == RTCPPacket::tSDES ) {
				bool cname = onGotSDES(*s,*pkt);
				cname_found = cname_found? cname_found : cname;
			} else if ( pkt->fh.type == RTCPPacket::tAPP ) {
				onGotAPP(*s,pkt->info.APP,pkt->getLength());
		//		pointer += pkt->getLength();
			} else {
				// error?
			}
		}
		// Get the next packet in the compound.
		pointer += pkt->getLength();
		pkt = reinterpret_cast<RTCPPacket *>(rtcpRecvBuffer +pointer);
	}

	// TODO: error? if !cname_found

	// process BYE packets
	while ( pointer < len ) {
		if ( pkt->fh.type == RTCPPacket::tBYE ) {
			sourceLink = getSourceBySSRC(pkt->getSSRC(),
						     source_created);
			if ( checkSSRCInRTCPPkt(*sourceLink,source_created,
						network_address,
						transport_port) )
				getBYE(*pkt,pointer,len);
		} else if ( pkt->fh.type != RTCPPacket::tBYE ) {
			break; // TODO: check non-BYE out of place.
		} else {
			break;
		}
	}

	// Call plug-in in case there are profile extensions
	// at the end of the SR/RR.
	if ( pointer != len ) {
		onGotRRSRExtension(rtcpRecvBuffer + pointer,
				   len - pointer);
	}

	// Everything went right, update the RTCP average size
	updateAvgRTCPSize(len);
}

bool
QueueRTCPManager::end2EndDelayed(IncomingRTPPktLink& pl)
{
	bool result = false;

	if ( 0 != getEnd2EndDelay() ) {
		SyncSourceLink* sl = pl.getSourceLink();
		void* si = sl->getSenderInfo();
		if ( NULL != si ) {
			RTCPSenderInfo rsi(si);
			uint32 tsInc = pl.getPacket()->getTimestamp() -
				rsi.getRTPTimestamp();
			// approx.
			microtimeout_t Inc = tsInc * 1000 /
				(getCurrentRTPClockRate() / 1000);
			timeval timevalInc = microtimeout2Timeval(Inc);

			timeval tNTP = NTP2Timeval(rsi.getNTPTimestampInt(),
						rsi.getNTPTimestampFrac());
			timeval packetTime;
			timeradd(&tNTP,&timevalInc,&packetTime);
			timeval now, diff;
			gettimeofday(&now,NULL);
			timersub(&now,&packetTime,&diff);

			if ( timeval2microtimeout(diff) > getEnd2EndDelay() )
				result = true;
		}
	}
	return result;
}

void
QueueRTCPManager::onGotSR(SyncSource& source, SendReport& SR, uint8)
{
	// We ignore the receiver blocks and just get the sender info
	// at the beginning of the SR.
	getLink(source)->setSenderInfo
		(reinterpret_cast<unsigned char*>(&(SR.sinfo)));
}

void
QueueRTCPManager::onGotRR(SyncSource& source, RecvReport& RR, uint8 blocks)
{
	for ( uint8 i = 0; i < blocks; i++) {
		// this generic RTCP manager ignores reports about
		// other sources than the local one
		if ( getLocalSSRCNetwork() == RR.ssrc ) {
			getLink(source)->
				setReceiverInfo
				(reinterpret_cast<unsigned char*>(&(RR.blocks[i].rinfo)));
		}
	}
}

void
QueueRTCPManager::updateAvgRTCPSize(size_t len)
{
	size_t newlen = len;
	newlen += lowerHeadersSize;
	rtcpAvgSize = (uint16)(( (15 * rtcpAvgSize) >> 4 ) + ( newlen >> 4));
}

bool
QueueRTCPManager::getBYE(RTCPPacket& pkt, size_t& pointer, size_t)
{
	if ( 0 == pkt.fh.block_count )
		return false;

	char *reason = NULL;

	if ( (sizeof(RTCPFixedHeader) + pkt.fh.block_count * sizeof(uint32))
	     < pkt.getLength() ) {
		uint16 endpointer = (uint16)(pointer + sizeof(RTCPFixedHeader) +
			pkt.fh.block_count * sizeof(uint32));
		uint16 len = rtcpRecvBuffer[endpointer];
		reason = new char[len + 1];
		memcpy(reason,rtcpRecvBuffer + endpointer + 1,len);
		reason[len] = '\0';
	} else { // avoid dangerous conversion of NULL to a C++ string.
		reason = new char[1];
		reason[0] = '\0';
	}

	int i = 0;
	while ( i < pkt.fh.block_count ){
		bool created;
		SyncSourceLink* srcLink =
			getSourceBySSRC(pkt.getSSRC(),created);
		i++;
		if( srcLink->getGoodbye() )
			onGotGoodbye(*(srcLink->getSource()),reason);
		BYESource(pkt.getSSRC());
		setState(*(srcLink->getSource()),SyncSource::stateLeaving);

		reverseReconsideration();
	}

	delete [] reason;
	pointer += pkt.getLength();
	return true;
}

void
QueueRTCPManager::reverseReconsideration()
{
	if ( getMembersCount() < reconsInfo.rtcpPMembers ) {
		timeval inc;

		// reconsider reconsInfo.rtcpTn (time for next RTCP packet)
		microtimeout_t t =
			(reconsInfo.rtcpTn.tv_sec - reconsInfo.rtcpTc.tv_sec) *
			1000000 +
			(reconsInfo.rtcpTn.tv_usec - reconsInfo.rtcpTc.tv_usec);
		t *= getMembersCount();
		t /= reconsInfo.rtcpPMembers;
		inc.tv_usec = t % 1000000;
		inc.tv_sec = t / 1000000;
		timeradd(&(reconsInfo.rtcpTc),&inc,&(reconsInfo.rtcpTn));

		// reconsider tp (time for previous RTCP packet)
		t = (reconsInfo.rtcpTc.tv_sec - reconsInfo.rtcpTp.tv_sec) *
			1000000 +
			(reconsInfo.rtcpTc.tv_usec - reconsInfo.rtcpTp.tv_usec);
		t *= getMembersCount();
		t /= reconsInfo.rtcpPMembers;
		inc.tv_usec = t % 1000000;
		inc.tv_sec = t / 1000000;
		timeradd(&(reconsInfo.rtcpTc),&inc,&(reconsInfo.rtcpTp));
	}
	reconsInfo.rtcpPMembers = getMembersCount();
}

bool
QueueRTCPManager::onGotSDES(SyncSource& source, RTCPPacket& pkt)
{
	// Take into account that length fields in SDES items are
	// 8-bit long, so no ntoh[s|l] is required
	bool cname_found = false;

	ptrdiff_t pointer = reinterpret_cast<unsigned char*>(&pkt) - rtcpRecvBuffer;
	uint16 i = 0;
	do {
		size_t len = pkt.getLength();
		pointer += sizeof(RTCPFixedHeader);
		SDESChunk* chunk = (SDESChunk*)(rtcpRecvBuffer + pointer);

		bool source_created = false;
		// TODO: avoid searching again the source of the first chunk.
		SyncSourceLink* sourceLink =
			getSourceBySSRC(chunk->getSSRC(),
					source_created);
		// TODO: check that there are no two chunks with the
		// same SSRC but different CNAME
		SyncSource& src = *( sourceLink->getSource() );

		if ( onGotSDESChunk(source,*chunk,len) )
			cname_found = true;
		pointer +=len;
		if( sourceLink->getHello() )
			onNewSyncSource(src);
		i++;
	} while ( i < pkt.fh.block_count );
	return cname_found;
}

bool
QueueRTCPManager::onGotSDESChunk(SyncSource& source, SDESChunk& chunk,
				 size_t len)
{
	bool cname_found = false;
	bool end = false;

	SyncSourceLink* srcLink = getLink(source);
	Participant* part = source.getParticipant();

	size_t pointer = sizeof(chunk.ssrc);

	// process chunk items
	while ( (pointer < len) && !end ) {
		SDESItem* item =
			reinterpret_cast<SDESItem*>(size_t(&(chunk)) + pointer);
		if ( item->type > SDESItemTypeEND && item->type <= SDESItemTypeLast) {
			pointer += sizeof(item->type) + sizeof(item->len) +
				item->len;
			if ( NULL == part && SDESItemTypeCNAME == item->type ) {
				const RTPApplication& app = getApplication();
				std::string cname = std::string(item->data,item->len);
				const Participant* p = app.getParticipant(cname);
				if ( p ) {
					part = const_cast<Participant*>(p);
					setParticipant(*(srcLink->getSource()),*part);
				} else {
					part = new Participant("-");
					addParticipant(const_cast<RTPApplication&>(getApplication()),*part);
				}
				setParticipant(*(srcLink->getSource()),*part);
			}

			// support for CNAME updates
			if ( part )
				setSDESItem(part,(SDESItemType)item->type,
					    item->data,item->len);

			if ( item->type == SDESItemTypeCNAME) {
				cname_found = true;
				// note that CNAME must be send in
				// every RTCP compound, so we only
				// trust sources that include it.
				setState(*(srcLink->getSource()),
					 SyncSource::stateActive);
			}
		} else if ( item->type == SDESItemTypeEND) {
			end = true;
			pointer++;
			pointer += (pointer & 0x03); // padding
		} else if ( item->type == SDESItemTypePRIV ){
			ptrdiff_t prevpointer = pointer;
			uint8 plength = *( &(item->len) + 1 );
			pointer += sizeof(item->type) + sizeof(item->len)  + 1;

			if ( part )
				setSDESItem(part,SDESItemTypePRIV,
					    reinterpret_cast<char*>(item + pointer),plength);
			pointer += plength;
			setPRIVPrefix(part,
				      reinterpret_cast<char*>(item + pointer),
				      (item->len - 1 - plength));
			pointer = prevpointer + item->len;
		} else {
			pointer++;
			// TODO: error: SDES unknown
			I( false );
		}
	}
	return cname_found;
}

timeval
QueueRTCPManager::computeRTCPInterval()
{
	float bwfract = controlBwFract * getSessionBandwidth();
	uint32 participants = getMembersCount();
	if ( getSendersCount() > 0 &&
	     ( getSendersCount() < (getMembersCount() * sendControlBwFract) )) {
		// reserve "sendControlBwFract" fraction of the total
		// RTCP bandwith for senders.
		if (rtcpWeSent) {
			// we take the side of active senders
			bwfract *= sendControlBwFract;
			participants = getSendersCount();
		} else {
			// we take the side of passive receivers
			bwfract *= recvControlBwFract;
			participants = getMembersCount() - getSendersCount();
		}
	}

	microtimeout_t min_interval = rtcpMinInterval;
	// be a bit quicker at first
	if ( rtcpInitial )
		min_interval /= 2;
	// this is the real computation:
	microtimeout_t interval = 0;
	if ( bwfract != 0 ) {
		interval = static_cast<microtimeout_t>
			((participants * rtcpAvgSize / bwfract) * 1000000);

		if ( interval < rtcpMinInterval )
			interval = rtcpMinInterval;
	} else {
		// 100 seconds instead of infinite
		interval = 100000000;
	}

	interval = static_cast<microtimeout_t>(interval * ( 0.5 +
						(rand() / (RAND_MAX + 1.0))));

	timeval result;
	result.tv_sec = interval / 1000000;
	result.tv_usec = interval % 1000000;
	return result;
}

size_t
QueueRTCPManager::dispatchBYE(const std::string& reason)
{
	// for this method, see section 6.3.7 in RFC 3550
	// never send a BYE packet if never sent an RTP or RTCP packet
	// before
	if ( !(getSendPacketCount() || getSendRTCPPacketCount()) )
		return 0;

	if ( getMembersCount() > 50) {
		// Usurp the scheduler role and apply a back-off
		// algorithm to avoid BYE floods.
		gettimeofday(&(reconsInfo.rtcpTc),NULL);
		reconsInfo.rtcpTp = reconsInfo.rtcpTc;
		setMembersCount(1);
		setPrevMembersNum(1);
		rtcpInitial = true;
		rtcpWeSent = false;
		rtcpAvgSize = (uint16)(sizeof(RTCPFixedHeader) + sizeof(uint32) +
			strlen(reason.c_str()) +
			(4 - (strlen(reason.c_str()) & 0x03)));
		gettimeofday(&(reconsInfo.rtcpTc),NULL);
		timeval T = computeRTCPInterval();
		timeradd(&(reconsInfo.rtcpTp),&T,&(reconsInfo.rtcpTn));
		while ( timercmp(&(reconsInfo.rtcpTc),&(reconsInfo.rtcpTn),<) ) {
			getOnlyBye();
			if ( timerReconsideration() )
				break;
			gettimeofday(&(reconsInfo.rtcpTc),NULL);
		}
	}


	unsigned char buffer[500];
	// Build an empty RR as first packet in the compound.
        // TODO: provide more information if available. Not really
	// important, since this is the last packet being sent.
	RTCPPacket* pkt = reinterpret_cast<RTCPPacket*>(buffer);
	pkt->fh.version = CCRTP_VERSION;
	pkt->fh.padding = 0;
	pkt->fh.block_count = 0;
	pkt->fh.type = RTCPPacket::tRR;
	pkt->info.RR.ssrc= getLocalSSRCNetwork();
	uint16 len1 = sizeof(RTCPFixedHeader) + sizeof(uint32); // 1st pkt len.
	pkt->fh.length = htons((len1 >> 2) - 1);
	uint16 len = len1; // whole compound len.
	// build a BYE packet
	uint16 padlen = 0;
        pkt = reinterpret_cast<RTCPPacket*>(buffer + len1);
	pkt->fh.version = CCRTP_VERSION;
	pkt->fh.block_count = 1;
	pkt->fh.type = RTCPPacket::tBYE;
	// add the SSRC identifier
	pkt->info.BYE.ssrc = getLocalSSRCNetwork();
	len += sizeof(RTCPFixedHeader) + sizeof(BYEPacket);
	// add the optional reason
	if ( reason.c_str() != NULL ){
		pkt->info.BYE.length = (uint8)strlen(reason.c_str());
		memcpy(buffer + len,reason.c_str(),pkt->info.BYE.length);
		len += pkt->info.BYE.length;
		padlen = 4 - ((len - len1) & 0x03);
		if ( padlen ) {
			memset(buffer + len,0,padlen);
			len += padlen;
		}
	}
	pkt->fh.length = htons(((len - len1) >> 2) - 1);
	pkt->fh.padding = (padlen > 0);

	return sendControlToDestinations(buffer,len);
}

void
QueueRTCPManager::getOnlyBye()
{
	// This method is kind of simplified recvControl
	timeval wait;
	timersub(&(reconsInfo.rtcpTn),&(reconsInfo.rtcpTc),&wait);
	microtimeout_t timer = wait.tv_usec/1000 + wait.tv_sec * 1000;
	// wait up to reconsInfo.rtcpTn
	if ( !isPendingControl(timer) )
		return;

	size_t len = 0;
	InetHostAddress network_address;
	tpport_t transport_port;
	while (	(len = recvControl(rtcpRecvBuffer,getPathMTU(),
				  network_address,transport_port)) ) {
		// Process a <code>len<code> octets long RTCP compound packet
		// Check validity of the header fields of the compound packet
		if ( !RTCPCompoundHandler::checkCompoundRTCPHeader(len) )
			return;

		// TODO: For now, we do nothing with the padding bit
		// in the header.
		uint32 pointer = 0;
		RTCPPacket* pkt;
		while ( pointer < len) {
			pkt = reinterpret_cast<RTCPPacket*>
				(rtcpRecvBuffer + pointer);

			if (pkt->fh.type == RTCPPacket::tBYE ) {
				bool created;
				SyncSourceLink* srcLink =
					getSourceBySSRC(pkt->getSSRC(),
							created);
				if( srcLink->getGoodbye() )
					onGotGoodbye(*(srcLink->getSource()), "");
				BYESource(pkt->getSSRC());
			}
			pointer += pkt->getLength();
		}
	}
}

size_t
QueueRTCPManager::dispatchControlPacket(void)
{
	rtcpInitial = false;
	// Keep in mind: always include a report (in SR or RR) and at
	// least a SDES with the local CNAME. It is mandatory.

	// (A) SR or RR, depending on whether we sent.
	// pkt will point to the packets of the compound

	RTCPPacket* pkt = reinterpret_cast<RTCPPacket*>(rtcpSendBuffer);
	// Fixed header of the first report
	pkt->fh.padding = 0;
	pkt->fh.version = CCRTP_VERSION;
	// length of the RTCP compound packet. It will increase till
	// the end of this routine. Both sender and receiver report
	// carry the general 32-bit long fixed header and a 32-bit
	// long SSRC identifier.
	uint16 len = sizeof(RTCPFixedHeader) + sizeof(uint32);

	// the fields block_count and length will be filled in later
	// now decide whether to send a SR or a SR
	if ( lastSendPacketCount != getSendPacketCount() ) {
		// we have sent rtp packets since last RTCP -> send SR
		lastSendPacketCount = getSendPacketCount();
		pkt->fh.type = RTCPPacket::tSR;
		pkt->info.SR.ssrc = getLocalSSRCNetwork();

		// Fill in sender info block. It would be more
		// accurate if this were done as late as possible.
		timeval now;
		gettimeofday(&now,NULL);
		// NTP MSB and MSB: dependent on current payload type.
		pkt->info.SR.sinfo.NTPMSW = htonl(now.tv_sec + NTP_EPOCH_OFFSET);
		pkt->info.SR.sinfo.NTPLSW = htonl((uint32)(((double)(now.tv_usec)*(uint32)(~0))/1000000.0));
		// RTP timestamp
		uint32 tstamp = now.tv_usec - getInitialTime().tv_usec;
		tstamp *= (getCurrentRTPClockRate()/1000);
		tstamp /= 1000;
		tstamp += (now.tv_sec - getInitialTime().tv_sec) *
			getCurrentRTPClockRate();
		tstamp += getInitialTimestamp();
		pkt->info.SR.sinfo.RTPTimestamp = htonl(tstamp);
		// sender's packet and octet count
		pkt->info.SR.sinfo.packetCount = htonl(getSendPacketCount());
		pkt->info.SR.sinfo.octetCount = htonl(getSendOctetCount());
		len += sizeof(SenderInfo);
	} else {
		// RR
		pkt->fh.type = RTCPPacket::tRR;
		pkt->info.RR.ssrc = getLocalSSRCNetwork();
	}

	// (B) put report blocks
	// After adding report blocks, we have to leave room for at
	// least a CNAME SDES item
	uint16 available = (uint16)(getPathMTU()
		- lowerHeadersSize
		- len
		- (sizeof(RTCPFixedHeader) +
		   2*sizeof(uint8) +
		   getApplication().getSDESItem(SDESItemTypeCNAME).length())
		- 100);

	// if we have to go to a new RR packet
	bool another = false;
	uint16 prevlen = 0;
	RRBlock* reports;
	if ( RTCPPacket::tRR == pkt->fh.type )
		reports = pkt->info.RR.blocks;
	else // ( RTCPPacket::tSR == pkt->fh.type )
		reports = pkt->info.SR.blocks;
	do {
		uint8 blocks = 0;
		pkt->fh.block_count = blocks = packReportBlocks(reports,len,available);
		// the length field specifies 32-bit words
		pkt->fh.length = htons( ((len - prevlen) >> 2) - 1);
		prevlen = len;
		if ( 31 == blocks ) {
			// we would need room for a new RR packet and
			// a CNAME SDES
			if ( len < (available -
			     ( sizeof(RTCPFixedHeader) + sizeof(uint32) +
			       sizeof(RRBlock))) ) {
				another = true;
				// Header for this new packet in the compound
				pkt = reinterpret_cast<RTCPPacket*>
					(rtcpSendBuffer + len);
				pkt->fh.version = CCRTP_VERSION;
				pkt->fh.padding = 0;
				pkt->fh.type = RTCPPacket::tRR;
				pkt->info.RR.ssrc = getLocalSSRCNetwork();
				// appended a new Header and a report block

				len += sizeof(RTCPFixedHeader)+ sizeof(uint32);
				reports = pkt->info.RR.blocks;
			} else {
				another = false;
			}
		} else {
			another = false;
		}
	} while ( (len < available) && another );

	// (C) SDES (CNAME)
	// each SDES chunk must be 32-bit multiple long
	// fill the padding with 0s
	packSDES(len);

	// TODO: virtual for sending APP RTCP packets?

	// actually send the packet.
	size_t count = sendControlToDestinations(rtcpSendBuffer,len);
	ctrlSendCount++;
	// Everything went right, update the RTCP average size
	updateAvgRTCPSize(len);

	return count;
}

void
QueueRTCPManager::packSDES(uint16 &len)
{
	uint16 prevlen = len;
	RTCPPacket* pkt = reinterpret_cast<RTCPPacket*>(rtcpSendBuffer + len);
	// Fill RTCP fixed header. Note fh.length is not set till the
	// end of this routine.
	pkt->fh.version = CCRTP_VERSION;
	pkt->fh.padding = 0;
	pkt->fh.block_count = 1;
	pkt->fh.type = RTCPPacket::tSDES;
	pkt->info.SDES.ssrc = getLocalSSRCNetwork();
	pkt->info.SDES.item.type = SDESItemTypeCNAME;
	// put CNAME
	size_t cnameLen =
		getApplication().getSDESItem(SDESItemTypeCNAME).length();
	const char* cname =
		getApplication().getSDESItem(SDESItemTypeCNAME).c_str();
	pkt->info.SDES.item.len = (uint8)cnameLen;
	len += sizeof(RTCPFixedHeader) + sizeof(pkt->info.SDES.ssrc) +
		sizeof(pkt->info.SDES.item.type) +
		sizeof(pkt->info.SDES.item.len);

	memcpy((rtcpSendBuffer + len),cname,cnameLen);
	len += (uint16)cnameLen;
	// pack items other than CNAME (following priorities
	// stablished inside scheduleSDESItem()).
	SDESItemType nexttype = scheduleSDESItem();
	if ( (nexttype > SDESItemTypeCNAME) &&
	     (nexttype <= SDESItemTypeLast ) ) {
		SDESItem *item = reinterpret_cast<SDESItem *>(rtcpSendBuffer + len);
		item->type = nexttype;
		const char *content =
			getApplication().getSDESItem(nexttype).c_str();
		item->len = (uint8)strlen(content);
		len += 2;
		memcpy(reinterpret_cast<char *>(rtcpSendBuffer + len),
			  content,item->len);
		len += item->len;
	}

	// pack END item (terminate list of items in this chunk)
	rtcpSendBuffer[len] = SDESItemTypeEND;
	len++;

	uint8 padding = len & 0x03;
	if ( padding ) {
		padding = 4 - padding;
		memset((rtcpSendBuffer + len),SDESItemTypeEND,padding);
		len += padding;
	}
	pkt->fh.length = htons((len - prevlen - 1) >>2);
}

uint8
QueueRTCPManager::packReportBlocks(RRBlock* blocks, uint16 &len,
				   uint16& available)
{
	uint8 j = 0;
	// pack as many report blocks as we can
	SyncSourceLink* i = getFirst();
	for ( ;
	      ( ( i != NULL ) &&
		( len < (available - sizeof(RTCPCompoundHandler::RRBlock)) ) &&
		( j < 31 ) );
	      i = i->getNext() ) {
		SyncSourceLink& srcLink = *i;
		// update stats.
		srcLink.computeStats();
		blocks[j].ssrc = htonl(srcLink.getSource()->getID());
		blocks[j].rinfo.fractionLost = srcLink.getFractionLost();
		blocks[j].rinfo.lostMSB =
			(srcLink.getCumulativePacketLost() & 0xFF0000) >> 16;
		blocks[j].rinfo.lostLSW =
			htons(srcLink.getCumulativePacketLost() & 0xFFFF);
		blocks[j].rinfo.highestSeqNum =
			htonl(srcLink.getExtendedMaxSeqNum());
		blocks[j].rinfo.jitter =
			htonl(static_cast<uint32>(srcLink.getJitter()));
		RTCPCompoundHandler::SenderInfo* si =
			reinterpret_cast<RTCPCompoundHandler::SenderInfo*>(srcLink.getSenderInfo());
		if ( NULL == si ) {
			blocks[j].rinfo.lsr = 0;
			blocks[j].rinfo.dlsr = 0;
		} else {
			blocks[j].rinfo.lsr =
				htonl( ((ntohl(si->NTPMSW) & 0x0FFFF) << 16 )+
				       ((ntohl(si->NTPLSW) & 0xFFFF0000) >> 16)
				       );
			timeval now, diff;
			gettimeofday(&now,NULL);
			timeval last = srcLink.getLastRTCPSRTime();
			timersub(&now,&last,&diff);
			blocks[j].rinfo.dlsr =
				htonl(timevalIntervalTo65536(diff));
		}
		len += sizeof(RTCPCompoundHandler::RRBlock);
		j++;
	}
	return j;
}

void
QueueRTCPManager::setSDESItem(Participant* part, SDESItemType type,
				const char* const value, size_t len)
{
	char* buf = new char[len + 1];
	memcpy(buf,value,len);
	buf[len] = '\0';
	ParticipantHandler::setSDESItem(part,type,buf);
	delete [] buf;
}


void
QueueRTCPManager::setPRIVPrefix(Participant* part, const char* const value,
				size_t len)
{
	char *buf = new char[len + 1];
	memcpy(buf,value,len);
	buf[len] = '\0';
	ParticipantHandler::setPRIVPrefix(part,buf);
	delete buf;
}

SDESItemType
QueueRTCPManager::scheduleSDESItem()
{
	uint8 i = 0;
	// TODO: follow, at least, standard priorities
	SDESItemType type = nextScheduledSDESItem;

	while ( (queueApplication.getSDESItem(type).length() <= 0) &&
		i < (lastSchedulable - firstSchedulable) ) {
		i++;
		type = nextSDESType(type);
	}
	bool empty = true;
	if ( queueApplication.getSDESItem(type).length() > 0 )
		empty = false;
	nextScheduledSDESItem = nextSDESType(type);
	if ( empty )
		return SDESItemTypeEND;
	else
		return type;
}

SDESItemType
QueueRTCPManager::nextSDESType(SDESItemType t)
{
	t = static_cast<SDESItemType>( static_cast<int>(t) + 1 );
	if ( t > lastSchedulable )
		t = firstSchedulable;
	return t;
}

size_t
QueueRTCPManager::sendControlToDestinations(unsigned char* buffer, size_t len)
{
	size_t count = 0;
	lockDestinationList();
	if ( isSingleDestination() ) {
		count = sendControl(buffer,len);
	} else {
		// when no destination has been added, NULL == dest.
		for (std::list<TransportAddress*>::iterator i =
			     destList.begin(); destList.end() != i; i++) {
			TransportAddress* dest = *i;
			setControlPeer(dest->getNetworkAddress(),
				       dest->getControlTransportPort());
			count += sendControl(buffer,len);
		}
	}
	unlockDestinationList();

	return count;
}

#ifdef	CCXX_NAMESPACES
}
#endif

/** EMACS **
 * Local variables:
 * mode: c++
 * c-basic-offset: 8
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1