/*
 * FILE:	statistics.c
 * 
 * PROGRAM: BAT
 * 
 * AUTHOR: V.J.Hardman
 * 
 * CREATED: 23/03/95
 * 
 * $Id: statistics.c,v 1.1.1.1 1998/08/15 19:16:48 ucaccsp Exp $
 * 
 * This module houses the routine from the main execution loop that calculates
 * the playout point, and monitors duplicated packets. After this routine,
 * sequence numbers are not used, and all calculations are done on the
 * playout point.
 * 
 * This module also contains irregularly executed stats analysis routines
 * 
 * Input queue: netrx_queue Output Queue: receive_queue Stats Queue: Stats queue
 *
 * Copyright (c) 1995,1996 University College London
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted, for non-commercial use only, 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 Computer Science
 *      Department at University College London
 * 4. Neither the name of the University nor of the Department may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * Use of this software for commercial purposes is explicitly forbidden
 * unless prior written permission is obtained from the authors.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED 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 AUTHORS 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.
 */


#include "bat_include.h"

int
coding_value(int coding)
{
	switch (coding) {
	case L16:
		return (10);
	case WBS:
	case WBS_WITH_STATE:
		return (9);
	case PCM:
		return (8);
	case DVI:
        case DVI_WITH_STATE:
		return (7);
	case GSM:
		return (4);
	case LPC:
		return (2);
	case DUMMY:
                return 0;
	default:
#ifdef DEBUG
                fprintf(stderr, "coding value: coding %d not recognized.\n", coding);
#endif
                return 0;
	}
}

int
unit_size(int coding)
{
	switch (coding) {
	case L16:
		return SAMPLES_PER_UNIT * 2;
	case PCM:
		return SAMPLES_PER_UNIT;
	case DVI:
		return DVI_UNIT_SIZE;
	case GSM:
		return 33;
	case LPC:
		return LPCRECSIZE;
	default:
		return 0;
	}
}

int
state_size(int coding)
{
	switch (coding) {
	case DVI:
		return DVI_STATE_SIZE;
	default:
		return 0;
	}
}

int
type_with_state(int coding)
{
	switch (coding) {
	case WBS:
		return WBS_WITH_STATE;
	case DVI:
		return DVI_WITH_STATE;
	default:
		return coding;
	}
}

static int
split_block(u_int32 playout_pt, int pt, char *data_ptr, int len,
	    rtcp_dbentry *src, rx_queue_struct *unitsrx_queue_ptr,
            int talks, rtp_hdr_t *hdr, session_struct *sp)
{
	int	bpu, ss, units, ul, i, j, k;
	sample	*buf;
	rx_queue_element_struct	*p;

	if ((bpu = unit_size(pt)) <= 0) {
		/* Log as bad encoding... XXX */
		return 0;
	}
	ss = state_size(pt);
	units = (len - ss) / bpu;

	for (i = 0; i < units; i++) {
		if (i == 0) {
			ul = bpu + ss;
		} else {
			ul = bpu;
		}
		buf = (sample *)xmalloc(ul);
		memcpy(buf, data_ptr, ul);
		data_ptr += ul;

		p = (rx_queue_element_struct *)block_alloc(sizeof(rx_queue_element_struct));
		p->next_ptr         = NULL;
		p->prev_ptr         = NULL;
		p->talk_spurt_start = (i == 0 ? talks: FALSE);
		p->talk_spurt_end   = FALSE;
		p->sequence_no      = (int)hdr->seq;
		p->time_stamp       = hdr->ts;
		p->unit_size        = SAMPLES_PER_UNIT;
		p->units_per_pckt   = units;
		p->unit_position    = i + 1;
		p->mixed            = FALSE;
		p->dbe_source[0]    = src;
		for (j=0, k=1; j < hdr->cc; j++) {
			p->dbe_source[k] = rtcp_get_dbentry(sp, ntohl(hdr->csrc[j]));
			if (p->dbe_source[k] != NULL) {
				k++;
			}
		}
		p->dbe_source_count = k;
		p->playoutpt        = playout_pt + (i * SAMPLES_PER_UNIT);
                p->comp_count       = 1;
		p->comp_data[0]     = buf;
		if (i == 0) {
			p->comp_format[0].scheme = type_with_state(pt);
                } else {
			p->comp_format[0].scheme = pt;
                }
		p->decomp_format.scheme = -1;
		p->decomp_data = NULL;
		put_on_rx_queue(p, unitsrx_queue_ptr);
	}

	return units;
}

#define HALF_TS_CYCLE	0x80000000

/*
 * Compare two timestamps and return TRUE if t1 > t2 Assume that they are
 * close together (less than half a cycle) and handle wraparounds...
 */
int
ts_gt(u_int32 t1, u_int32 t2)
{
	u_int32         diff;

	diff = t1 - t2;
	return (diff < HALF_TS_CYCLE && diff != 0);
}

/*
 * Return the abolute difference of two timestamps. As above assume they are
 * close and handle wraprounds...
 */
u_int32
ts_abs_diff(u_int32 t1, u_int32 t2)
{
	u_int32         diff;

	diff = t1 - t2;
	if (diff > HALF_TS_CYCLE)
		diff = t2 - t1;

	return (diff);
}

static u_int32
adapt_playout(rtp_hdr_t *hdr, int arrival_ts, rtcp_dbentry *src,
	      session_struct *sp, cushion_struct *cushion)
{
	u_int32	playout, var;
	int	delay, diff;

	delay = arrival_ts - hdr->ts;

	if (src->first_pckt_flag == TRUE) {
		diff                 = 0;
		src->delay           = delay;
		src->jitter          = 80;
		src->first_pckt_flag = FALSE;
		src->last_ts         = hdr->ts - 1;
		hdr->m               = TRUE;
	} else {
		/* This gives a smoothed average */
		diff       = abs(delay - src->delay);
		src->delay = delay;

		/* Jitter calculation as in RTP draft 07 */
		src->jitter = src->jitter + (((double) diff - src->jitter) / 16);
		src->last_diff = diff;
	}

	if (ts_gt(hdr->ts, src->last_ts)) {
		/* If TS start then adjust playout delay estimation */
		/* The 8 is the number of units in a 160ms packet (nasty hack!) */
		if ((hdr->m) || src->cont_toged > 4 || (ts_gt(hdr->ts, (src->last_ts + (hdr->seq - src->last_seq) * SAMPLES_PER_UNIT * 8 + 1)))) {
			var = (u_int32) src->jitter * 3;
			if (var > 8000) {
				var = 8000;
			}
			var += cushion->cushion_size;
			if (src->encoding == sp->redundancy_pt) {
				var += SAMPLES_PER_UNIT * src->units_per_packet;
			}
			src->playout = src->delay + var;
			if (var > src->peak_var) {
                                src->peak_var = var;
                        } else {
                                src->peak_var = (15 * src->peak_var + var) / 16;
                        }
		} else {
			/* Do not set encoding on TS start packets as they do not show if redundancy is used...   */
			src->encoding = hdr->pt;
		}
		src->last_ts  = hdr->ts;
		src->last_seq = hdr->seq;
	}

	/* Calculate the playout point in local time for this packet */
	playout = hdr->ts + src->playout;

	return playout;
}

int
rtp_header_validation(rtp_hdr_t *hdr, int length, int redundancy_pt, int l16_pt)
{
	/* This function checks the header info to make sure that the packet */
	/* is valid. We return TRUE if the packet is valid, FALSE otherwise. */
	/* This follows from page 52 of RFC1889.            [csp 22-10-1996] */
	int      pt   = 0;
	int      pl   = 0;
	int     *data = 0;
	u_int32  red_hdr;

	/* We only accept RTPv2 packets... */
	if (hdr->type != 2) {
#ifdef DEBUG
		printf("rtp_header_validation: version != 2\n");
#endif
		return FALSE;
	}

	/* Check for valid audio payload types... */
	if (((hdr->pt > 23) && (hdr->pt < 96)) || (hdr->pt > 127)) {
#ifdef DEBUG
		printf("rtp_header_validation: payload-type out of audio range\n");
#endif
		return FALSE;
	}

	/* If padding or header-extension is set, we punt on this one... */
	/* We should really deal with it though...                       */
	if (hdr->x) {
#ifdef DEBUG
		printf("rtp_header_validation: x bit set\n");
#endif
		return FALSE;
	}

	/* Check the length is consistent... We have to check for 20,40,60,80ms packets*/
	pl = (length - 12) - (hdr->cc * 4);	/* Size of the payload... */
	if (hdr->p) {
		int pad = *((unsigned char *)hdr + length - 1);

		if (pad < 1) {
#ifdef DEBUG
			printf("rtp_header_validation: padding but 0 len\n");
#endif
			return FALSE;
		}
		pl -= pad;
		if (pl < 1) {
#ifdef DEBUG
			printf("rtp_header_validation: whole pkt padding\n");
#endif
			return FALSE;
		}
	}
	if (hdr->pt == redundancy_pt) {
		data  = (int *)hdr + 3 + hdr->cc;
		while ((red_hdr = ntohl(*data)) & 0x80000000) {
			pt  = RED_PT(red_hdr);
			pl -= RED_LEN(red_hdr) + 4;
			data++;
		}
		pl -= 1;
		pt  = RED_PT(red_hdr) & 0x7f;
	} else {
		pt = hdr->pt;
	}
	switch (pt) {
	case 0 : /* PCM u-law packet */
		 if ((pl % 160) != 0) {
#ifdef DEBUG
			printf("rtp_header_validation: PCM packet length is wrong\n");
#endif
		 	return FALSE;
		 }
	 	 break;
	case 3 : /* GSM packet */
		 if ((pl % 33) != 0) {
#ifdef DEBUG
			printf("rtp_header_validation: GSM packet length is wrong\n");
#endif
		 	return FALSE;
		 }
	 	 break;
	case 5 : /* DVI4 packet */
	 	 if (((pl - 4) % 80) != 0) {
#ifdef DEBUG
			printf("rtp_header_validation: DVI packet length is wrong\n");
#endif
		 	return FALSE;
		 }
	 	 break;
	case 7 : /* LPC packet */
		 if ((pl % 14) != 0) {
#ifdef DEBUG
			printf("rtp_header_validation: LPC packet length is wrong\n");
#endif
		 	return FALSE;
		 }
	         break;
	default: /* Unknown payload type, so punt on it... */
		 if ((pt == l16_pt) && ((pl % 320) == 0)) return TRUE;
#ifdef DEBUG
		 printf("rtp_header_validation: pt=%d length=%d huh?\n", pt, pl);
#endif
		 return FALSE;
	};
	return TRUE;
}

rtcp_dbentry   *
update_database(session_struct *sp, pckt_queue_element_struct *e_ptr, rtp_hdr_t *hdr, u_int32 cur_time)
{
	rtcp_dbentry   *dbe_source;

	/* This function gets the relevant data base entry */
	dbe_source = rtcp_get_dbentry(sp, hdr->ssrc);
	if (dbe_source == NULL) {
		/* We haven't received an RTCP packet for this source, so we must throw the   */
		/* packets away. This seems a little extreme, but there are actually a couple */
		/* of good reasons for it:                                                    */
		/*   1) If we're receiving encrypted data, but we don't have the decryption   */
		/*      key, then every RTP packet we receive will have a different SSRC      */
		/*      and if we create a new database entry for it, we fill up the database */
		/*      with garbage (which is then displayed in the list of participants...) */
		/*   2) The RTP specification says that we should do it this way (sec 6.2.1)  */
		/*                                                                     [csp]  */
		return NULL;
	}
	/* Used to be RTPTime, now audio_time - VH */
	dbe_source->last_active = cur_time;
	dbe_source->is_sender   = 1;

	sp->db.pckts_received++;

	return dbe_source;
}

void
statistics(session_struct    *sp,
	   pckt_queue_struct *netrx_pckt_queue,
	   rx_queue_struct   *unitsrx_queue_ptr,
	   cushion_struct    *cushion,
	   u_int32       cur_time)
{
	/*
	 * We expect to take in an RTP packet, and decode it - read fields
	 * etc. This module should do statistics, and keep information on
	 * losses, and re-orderings. Duplicates will be dealt with in the
	 * receive buffer module.
	 * 
	 * Late packets will not be counted as lost. RTP stats reports count
	 * duplicates in with the number of packets correctly received, thus
	 * sometimes invalidating stats reports. We can, if necessary, keep
	 * track of some duplicates, and throw away in the receive module. It
	 * has not yet been decided whether or not loss will be 'indicated'
	 * to later modules - put a dummy unit(s) on the queue for the receive
	 * buffer
	 */

	rtp_hdr_t	*hdr;
	u_char		*hdr_ptr;
	u_char		*data_ptr;
	int		len, block_len;
	rtcp_dbentry	*src;
	u_int32		red_hdr, block_ts;
	u_int32         playout_pt;
	int             blocks;
	pckt_queue_element_struct *e_ptr;

	/* Get a packet to process */
	e_ptr = get_pckt_off_queue(netrx_pckt_queue);
	assert(e_ptr != NULL);

	/* Impose RTP formating on it... */
	hdr = (rtp_hdr_t *) (e_ptr->pckt_ptr);

	if (rtp_header_validation(hdr, e_ptr->len, sp->redundancy_pt, sp->l16_pt) == FALSE) {
#ifdef DEBUG
		printf("RTP Packet failed header validation!\n");
#endif
		free_pckt_queue_element(&e_ptr);
		/* XXX log as bad packet */
		return;
	}
	/* Convert from network byte-order */
	hdr->seq  = ntohs(hdr->seq);
	hdr->ts   = ntohl(hdr->ts);
	hdr->ssrc = ntohl(hdr->ssrc);

	if ((hdr->ssrc == sp->db.myssrc) && sp->filter_loopback) {
		/* Discard loopback packets...unless we have asked for them ;-) */
                goto release;
	}

	/* Get database entry of participant that sent this packet */
	src = update_database(sp, e_ptr, hdr, cur_time);
	/* Uncknown participant */
	if (src == NULL) {
		free_pckt_queue_element(&e_ptr);
		return;
	}

	rtcp_update_seq(src, hdr->seq);

	/* This code is bad: we should first process the headers, and  */
	/* then decode the data. As it stands, we make two passes over */
	/* the headers, which is very wasteful, but for now I don't    */
	/* care, and I'm just trying to make it work!            [csp] */
	hdr_ptr    = (char *)e_ptr->pckt_ptr + 4 * (3 + hdr->cc);
	data_ptr   = hdr_ptr; 
	len        = e_ptr->len - 4 * (3 + hdr->cc);;
	playout_pt = adapt_playout(hdr, e_ptr->arrival_timestamp, src, sp, cushion);
	if (hdr->pt == sp->redundancy_pt) {
		/* Advance data_ptr past the headers... */
		for (blocks = 1; len >= 4 && (*data_ptr & 0x80); blocks++) {
			data_ptr += 4;
			len -= 4;
		}
		data_ptr++;
		len--;
		/* Process the data... */
		for (blocks = 1; *hdr_ptr & 0x80; blocks++) {
			red_hdr = ntohl(*((int *)hdr_ptr));
			src->encs[blocks] = RED_PT(red_hdr);
			/* Set PT for L16 to something the rest of the code understands... */
			if (src->encs[blocks] == sp->l16_pt) {
				src->encs[blocks] = L16;
			}
			block_len = RED_LEN(red_hdr);
			block_ts = hdr->ts - RED_OFF(red_hdr);
			if (block_len > len) {
				printf("Bad redundancy packet!\n");
				free_pckt_queue_element(&e_ptr);
				return;
			}
			split_block(playout_pt - RED_OFF(red_hdr), src->encs[blocks], data_ptr, block_len, src, unitsrx_queue_ptr, FALSE, hdr, sp);
			len      -= block_len;
			data_ptr += block_len;
			hdr_ptr  += 4;
		}
		hdr->pt = *hdr_ptr & 0x7f;
	} else {
		blocks = 1;
	}

	src->encs[0] = hdr->pt;
	/* Set PT for L16 to something the rest of the code understands... */
	if (src->encs[0] == sp->l16_pt) {
		src->encs[0] = L16;
	}
	src->encs[blocks] = -1;
	src->units_per_packet = split_block(playout_pt, src->encs[0], data_ptr, len, src, unitsrx_queue_ptr, hdr->m, hdr, sp);
release:
	free_pckt_queue_element(&e_ptr);
}

