/*
 * Copyright (c) 1995 The Regents of the University of California.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the Network Research
 * 	Group at Lawrence Berkeley National Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
static const char rcsid[] =
    "@(#) $Header: session-vat.cc,v 1.9 96/04/04 03:46:39 van Exp $ (LBL)";

#include "config.h"
#include <math.h>
#include "session.h"
#include "source.h"
#include "rtp.h"
#include "ntp-time.h"

/*
 * A class for backward compat with the old vat protocol
 * (vat-3.x and earlier).  Vat now uses RTPv2.
 */
class VatSessionManager : public AudioSessionManager {
    public:
	virtual void send_bye();
	virtual void send_report();
    protected:
	virtual void transmit(pktbuf* pb);
	virtual void recv(CtrlHandler*);
	virtual void recv(DataHandler*);
};

static class VatSessionMatcher : public Matcher {
    public:
	VatSessionMatcher() : Matcher("session") {}
	TclObject* match(const char* id) {
		if (strcmp(id, "audio/vat") == 0)
			return (new VatSessionManager);
		else
			return (0);
	}
} vat_matcher;

/*
 * this table maps vat format codes to RTP format codes.  Invalid
 * codes are signaled by -1.  The table includes the 2 'must be zero'
 * bits in the vat hdr to merge their check with the fmt code check.
 */
static const signed char rtp_format[128] = {
	RTP_PT_PCMU, RTP_PT_CELP, -1, 	       RTP_PT_GSM,	//  0 - 3
	-1,	     -1, 	  -1,	       -1,		//  4 - 7
	-1,	     -1, 	  -1,	       -1,		//  8 - 11
	-1,	     -1, 	  -1,	       -1,		// 12 - 15
	-1,	     -1, 	  -1,	       -1,		// 16 - 19
	-1,	     -1, 	  -1,	       -1,		// 20 - 23
	-1,	     -1, 	  -1,	       -1,		// 24 - 27
	RTP_PT_LPC,  RTP_PT_LPC,  RTP_PT_DVI,  -1,		// 28 - 31

	// remaining codes are not valid vat formats 
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 48
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, //112
};

static timeval tzero;

void VatSessionManager::transmit(pktbuf* pb)
{
	mh_.msg_iov = pb->iov;
	Network* net = dh_.net();
	/*
	 * Smash the packet for RTPv1 compat.
	 * (put the contents back because it gets looped back above)
	 */
	rtphdr rhs = *(rtphdr*)pb->iov[0].iov_base; // save
	pb->iov[0].iov_base = (caddr_t)pb->iov[0].iov_base + 4;
	pb->iov[0].iov_len -= 4;
	u_char* bp = (u_char*)pb->iov[0].iov_base;
	bp[0] = 0;
	int flags = ((u_char*)&rhs)[1];
	int fmt = flags & 0x7f;
	if (fmt == RTP_PT_LPC)
		fmt = 28;
	else if (fmt == RTP_PT_DVI)
		/*XXX*/
		fmt = 30;
	bp[1] = (flags & 0x80) | fmt;
	bp[2] = confid_ >> 8;
	bp[3] = confid_;
	*(u_int32_t*)&bp[4] = rhs.rh_ts;
	net->sendmsg(mh_);
	pb->iov[0].iov_len += 4;
	pb->iov[0].iov_base = (caddr_t)pb->iov[0].iov_base - 4;
	*(rtphdr*)pb->iov[0].iov_base = rhs;
}

/*
 * Send a vat report packet.
 */
void VatSessionManager::send_report()
{
	SourceManager& sm = SourceManager::instance();
	Source& s = *sm.localsrc();
	s.lts_ctrl(unixtime());

	u_char* bp = pktbuf_;
	bp[0] = 0;
	bp[1] = 1;
	bp[2] = confid_ >> 8;
	bp[3] = confid_;
	const char* name = s.sdes(RTCP_SDES_NAME);
	int n = strlen(name) + 1;
	memcpy(&bp[4], name, n);
	ch_.send(pktbuf_, n + 4);

	/* update the avg control msg size with our msg */
	rtcp_avg_size_ += RTCP_SIZE_GAIN * (double(n + 32) - rtcp_avg_size_);

	/*
	 * compute the time to the next report.  The bandwidth
	 * limit for rtcp traffic was set on startup from the
	 * session bandwidth.  It is the inverse of bandwidth
	 * (ie., ms/byte) to avoid a divide below.
	 */
	int nsrc = sm.nsources();
	double rint = rtcp_avg_size_ * double(nsrc) * rtcp_inv_bw_;
	if (rint < RTCP_MIN_RPT_TIME * 1000.)
		rint = RTCP_MIN_RPT_TIME * 1000.;
	rt_.msched(int(fmod(double(random()), rint) + rint * .5 + .5));
	sm.CheckActiveSources(rint);
}

/*
 * Send a vat bye message.
 */
void VatSessionManager::send_bye()
{
	u_int8_t* bp = pktbuf_;
	bp[0] = 0;
	bp[1] = 2;
	bp[2] = confid_ >> 8;
	bp[3] = confid_;
	ch_.send(pktbuf_, 4);
}

void VatSessionManager::recv(DataHandler* dh)
{
	u_int32_t addr;
	u_char* bp = &pktbuf_[4];
	int cc = dh->recv(bp, 2 * RTP_MTU - 4, addr);
	if (cc <= 8) {
		++nrunt_;
		return;
	}
	int flags = ntohs(*(u_int16_t*)bp);
	if ((flags & 0xc000) != 0 || ntohs(*(u_int16_t*)(bp + 2)) != confid_) {
		++badversion_;
		return;
	}
	int fmt = rtp_format[flags & 0x7f];
	if (fmt < 0) {
		++badfmt_;
		return;
	}

	u_int32_t ts = *(u_int32_t*)(bp + 4);
	SourceManager& sm = SourceManager::instance();
	Source* s = sm.lookup(addr, addr, addr);
	timeval now = unixtime();

	/* skip over vat header */
	cc -= 8;
	bp += 8;
	rtphdr* rh = (rtphdr*)(bp - 12);

	s->np(1);
	s->nb(cc + sizeof(*rh));
	if (s->checkseq(ntohl(ts) / FRAMESIZE))
		/* this is a dup */
		return;

	int cnt = (flags >> 8) & 0x3f;
	if (cnt > 0) {
		if (cc <= cnt * 4) {
			++badoptions_; /*XXX*/
			return;
		}
		s->ismixer(1);
		rh = (rtphdr*)((char*)rh + (cnt << 2));
		while (--cnt >= 0) {
			u_int32_t csrc = *(u_int32_t*)bp;
			bp += 4;
			cc -= 4;
			Source* cs = sm.lookup(csrc, addr, addr);
			cs->lts_data(now);
			cs->sts_data(ts);
			cs->action();
		}
	} else {
		s->lts_data(now);
		s->sts_data(ts);
		/*
		 * because of bugs in the vat-2/3 'mixer' code, we
		 * occasionally get packets from a 'mixer' that's not
		 * really active.  suppress marking a mixer active
		 * unless we got a name from it.
		 */
		if (s->ismixer() == 0 || s->sdes(0))
			s->action();
	}

	/* fill in stuff decoders want */
	flags = (flags & 0x80) | fmt;
	rh->rh_flags = ntohs(flags);
	rh->rh_ts = ts;
	if (flags & RTP_M)
		s->nf(1);

	/*
	 * This is a data packet.  If the source needs activation,
	 * or the packet format has changed, deal with this.
	 * Then, hand the packet off to the packet handler.
	 * XXX might want to be careful about flip-flopping
	 * here when format changes due to misordered packets
	 * (easy solution -- keep rtp seqno of last fmt change).
	 */
	PacketHandler* h = s->handler();
	if (h == 0)
		h = s->activate(fmt);
	else if (s->format() != fmt)
		h = s->change_format(fmt);

	int hlen = h->hdrlen();
	cc -= hlen;
	if (cc < 0) {
		s->runt(1);
		return;
	}
	if (!s->mute())
		h->recv(rh, bp + hlen, cc);
}

void VatSessionManager::recv(CtrlHandler* ch)
{
	/*
	 * handle in incoming vat session packet
	 */
	u_int8_t* bp = pktbuf_;
	u_int32_t addr;
	int cc = ch->recv(bp, 2 * RTP_MTU, addr);
	if (cc <= 0)
		return;
	if (cc < 4) {
		++nrunt_;
		return;
	}
	if ((bp[0] & 0xc0) != 0 || ntohs(*(u_int16_t*)(bp + 2)) != confid_) {
		++badversion_;
		return;
	}
	rtcp_avg_size_ += RTCP_SIZE_GAIN * (double(cc + 28) - rtcp_avg_size_);

	SourceManager& sm = SourceManager::instance();
	Source* s = sm.lookup(addr, addr, addr);

	u_int8_t* ep = &bp[cc];
	*ep = 0;
	int i;
	timeval now = unixtime();
	switch (bp[1]) {

	case 1: /* ID */
		s->sdes(RTCP_SDES_NAME, (char*)&bp[4]);
		if (s->lts_done().tv_sec &&
		    int(now.tv_sec - s->lts_done().tv_sec) > 2) {
			/*
			 * the site said it was done but then came back -
			 * reactivate it.  (the 2 second delay is to
			 * account for possible packet reordering.)
			 */
			s->lts_done(tzero);
			s->lost(0);
		}
		break;

	 case 2: /* BYE */
		s->lts_done(now);
		break;

	case 3: /* IDLIST */
		i = bp[4];
		bp += 8;
		while (--i >= 0 && bp < ep) {
			u_int32_t id = *(u_int32_t*)bp;
			bp += 4;
			/* 
			 * Due to a bug in vat-3, mixers are
			 * identified with id 0.  We conveniently
			 * use this fact to filter out mixer
			 * names, since the rest of vat assumes
			 * that a mixer usually won't send their
			 * name and uses this as a hint to
			 * not display them in the UI.  (In RTP,
			 * if a mixer does send it's name, then
			 * we go ahead and display it.)
			 */
			if (id != 0) {
				s = sm.lookup(id, addr, addr);
				s->sdes(RTCP_SDES_NAME, (char*)bp);
				s->lts_ctrl(now);
			}
			int n = strlen((char*)bp);
			n += 4 - (n & 3);
			bp += n;
		}
		break;
	}
	s->lts_ctrl(now);
}
