/**
 * @file pkt.c  RTCP Packet handling
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <re_types.h>
#include <string.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_sys.h>
#include <re_sa.h>
#include <re_rtp.h>
#include "rtcp.h"


#define DEBUG_MODULE "rtcp_pkt"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


static void rtcp_destructor(void *data)
{
	struct rtcp_msg *msg = data;
	size_t i, j;

	switch (msg->hdr.pt) {

	case RTCP_SR:
		mem_deref(msg->r.sr.rrv);
		break;

	case RTCP_RR:
		mem_deref(msg->r.rr.rrv);
		break;

	case RTCP_SDES:
		if (!msg->r.sdesv)
			break;

		for (i=0; i<msg->hdr.count; i++) {
			struct rtcp_sdes *sdes = &msg->r.sdesv[i];

			for (j=0; j<sdes->n; j++) {

				mem_deref(sdes->itemv[j].data);
			}
			mem_deref(sdes->itemv);
		}
		mem_deref(msg->r.sdesv);
		break;

	case RTCP_BYE:
		mem_deref(msg->r.bye.srcv);
		mem_deref(msg->r.bye.reason);
		break;

	case RTCP_APP:
		mem_deref(msg->r.app.data);
		break;

	case RTCP_RTPFB:
	case RTCP_PSFB:
		mem_deref(msg->r.fb.fci.p);
		break;

	default:
		/* nothing allocated */
		break;
	}
}


/**
 * Encode the RTCP Header
 *
 * @param mb     Buffer to encode into
 * @param count  Number of sub-elemements
 * @param type   RTCP Packet type
 * @param length Packet length in words
 *
 * @return 0 for success, otherwise errorcode
 */
int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type,
		    uint16_t length)
{
	int err;

	if (!mb)
		return EINVAL;

	err  = mbuf_write_u8(mb, RTCP_VERSION<<6 | count);
	err |= mbuf_write_u8(mb, type);
	err |= mbuf_write_u16(mb, htons(length));

	return err;
}


/**
 * Decode the RTCP Header
 *
 * @param mb  Buffer to decode from
 * @param hdr RTCP Header to decode into
 *
 * @return 0 for success, otherwise errorcode
 */
int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr)
{
	uint8_t b;

	if (!hdr)
		return EINVAL;
	if (mbuf_get_left(mb) < RTCP_HDR_SIZE)
		return EBADMSG;

	b = mbuf_read_u8(mb);
	hdr->pt = mbuf_read_u8(mb);
	hdr->length = ntohs(mbuf_read_u16(mb));

	hdr->version = (b >> 6) & 0x3;
	hdr->p       = (b >> 5) & 0x1;
	hdr->count   = (b >> 0) & 0x1f;

	return 0;
}


int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count,
		 va_list ap)
{
	size_t i, pos;
	uint16_t len;
	const uint8_t *data;
	size_t data_len;
	const uint32_t *srcv;
	const char *reason;
	rtcp_encode_h *ench;
	void *arg;
	int err = 0;

	if (!mb)
		return EINVAL;

	pos = mb->pos;

	/* Skip header - encoded last */
	mb->pos = mb->end = (mb->pos + RTCP_HDR_SIZE);

	switch (type) {

	case RTCP_SR:
		for (i=0; i<6; i++)
			err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		ench = va_arg(ap, rtcp_encode_h *);
		arg = va_arg(ap, void *);
		if (ench)
			err |= ench(mb, arg);
		break;

	case RTCP_RR:
		err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		ench = va_arg(ap, rtcp_encode_h *);
		arg = va_arg(ap, void *);
		if (ench)
			err |= ench(mb, arg);
		break;

	case RTCP_SDES:
		ench = va_arg(ap, rtcp_encode_h *);
		arg = va_arg(ap, void *);
		if (ench)
			err |= ench(mb, arg);
		break;

	case RTCP_BYE:
		srcv   = va_arg(ap, uint32_t *);
		reason = va_arg(ap, char *);
		for (i=0; i<count && !err; i++) {
			err = mbuf_write_u32(mb, htonl(srcv[i]));
		}
		if (reason) {
			err |= mbuf_write_u8(mb, strlen(reason));
			err |= mbuf_write_str(mb, reason);
		}
		break;

	case RTCP_APP:
		err  = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		err |= mbuf_write_mem(mb, va_arg(ap, uint8_t *), 4);
		data = va_arg(ap, const uint8_t *);
		data_len = va_arg(ap, size_t);
		if (data) {
			if (data_len % 4) {
				DEBUG_WARNING("not a multiple of 32bits\n");
				return EBADMSG;
			}
			err |= mbuf_write_mem(mb, data, data_len);
		}
		break;

	case RTCP_FIR:
		err  = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		break;

	case RTCP_NACK:
		err  = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		err |= mbuf_write_u16(mb, htons(va_arg(ap, uint32_t)));
		err |= mbuf_write_u16(mb, htons(va_arg(ap, uint32_t)));
		break;

	case RTCP_RTPFB:
	case RTCP_PSFB:
		err  = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
		ench = va_arg(ap, rtcp_encode_h *);
		arg = va_arg(ap, void *);
		if (ench)
			err |= ench(mb, arg);
		break;

	default:
		return EINVAL;
	}
	if (err)
		return err;

	/* padding to 32 bits */
	while ((mb->end - pos) & 0x3)
		err |= mbuf_write_u8(mb, 0x00);
	if (err)
		return err;

	/* Encode RTCP Header */
	mb->pos = pos;
	len = (mb->end - pos - RTCP_HDR_SIZE)/sizeof(uint32_t);
	err = rtcp_hdr_encode(mb, count, type, len);
	if (err)
		return err;

	mb->pos = mb->end;

	return 0;
}


/**
 * Encode an RTCP Packet into a buffer
 *
 * @param mb    Buffer to encode into
 * @param type  RTCP Packet type
 * @param count Packet-specific count
 *
 * @return 0 for success, otherwise errorcode
 */
int rtcp_encode(struct mbuf *mb, enum rtcp_type type, uint32_t count, ...)
{
	va_list ap;
	int err;

	va_start(ap, count);
	err = rtcp_vencode(mb, type, count, ap);
	va_end(ap);

	return err;
}


/**
 * Decode one RTCP message from a buffer
 *
 * @param msgp Pointer to allocated RTCP Message
 * @param mb   Buffer to decode from
 *
 * @return 0 for success, otherwise errorcode
 */
int rtcp_decode(struct rtcp_msg **msgp, struct mbuf *mb)
{
	struct rtcp_msg *msg = NULL;
	size_t start, i, sz, count, rem;
	int err;

	if (!msgp)
		return EINVAL;
	if (mbuf_get_left(mb) < RTCP_HDR_SIZE)
		return EBADMSG;

	msg = mem_zalloc(sizeof(*msg), rtcp_destructor);
	if (!msg)
		return ENOMEM;

	start = mb->pos;

	/* decode and check header */
	err = rtcp_hdr_decode(mb, &msg->hdr);
	if (err)
		goto out;

	if (msg->hdr.version != RTCP_VERSION)
		goto badmsg;

	/* check length and remaining */
	rem = msg->hdr.length * sizeof(uint32_t);
	if (mbuf_get_left(mb) < rem)
		goto badmsg;

	count = msg->hdr.count;

	switch (msg->hdr.pt) {

	case RTCP_SR:
		if (mbuf_get_left(mb) < (RTCP_SRC_SIZE + RTCP_SR_SIZE))
			goto badmsg;
		msg->r.sr.ssrc     = ntohl(mbuf_read_u32(mb));
		msg->r.sr.ntp_sec  = ntohl(mbuf_read_u32(mb));
		msg->r.sr.ntp_frac = ntohl(mbuf_read_u32(mb));
		msg->r.sr.rtp_ts   = ntohl(mbuf_read_u32(mb));
		msg->r.sr.psent    = ntohl(mbuf_read_u32(mb));
		msg->r.sr.osent    = ntohl(mbuf_read_u32(mb));

		err = rtcp_rr_alloc(&msg->r.sr.rrv, count);
		if (err)
			goto out;
		for (i=0; i<count && !err; i++)
			err = rtcp_rr_decode(mb, &msg->r.sr.rrv[i]);
		break;

	case RTCP_RR:
		if (mbuf_get_left(mb) < RTCP_SRC_SIZE)
			goto badmsg;
		msg->r.rr.ssrc = ntohl(mbuf_read_u32(mb));

		err = rtcp_rr_alloc(&msg->r.rr.rrv, count);
		if (err)
			goto out;
		for (i=0; i<count && !err; i++)
			err = rtcp_rr_decode(mb, &msg->r.rr.rrv[i]);
		break;

	case RTCP_SDES:
		if (count == 0)
			break;

		sz = count * sizeof(*msg->r.sdesv);
		msg->r.sdesv = mem_zalloc(sz, NULL);
		if (!msg->r.sdesv) {
			err = ENOMEM;
			goto out;
		}

		for (i=0; i<msg->hdr.count && !err; i++)
			err = rtcp_sdes_decode(mb, &msg->r.sdesv[i]);
		break;

	case RTCP_BYE:
		sz = count * sizeof(*msg->r.bye.srcv);
		msg->r.bye.srcv = mem_alloc(sz, NULL);
		if (!msg->r.bye.srcv) {
			err = ENOMEM;
			goto out;
		}
		if (mbuf_get_left(mb) < sz)
			goto badmsg;
		for (i=0; i<count; i++)
			msg->r.bye.srcv[i] = ntohl(mbuf_read_u32(mb));

		/* decode reason (optional) */
		if (rem > count*sizeof(uint32_t)) {
			const size_t len = mbuf_read_u8(mb);
			if (mbuf_get_left(mb) < len)
				goto badmsg;

			err = mbuf_strdup(mb, &msg->r.bye.reason, len);
		}
		break;

	case RTCP_APP:
		if (mbuf_get_left(mb) < RTCP_APP_SIZE)
			goto badmsg;
		msg->r.app.src = ntohl(mbuf_read_u32(mb));
		(void)mbuf_read_mem(mb, (uint8_t *)msg->r.app.name,
				    sizeof(msg->r.app.name));
		if (rem > RTCP_APP_SIZE) {
			msg->r.app.data_len = rem - RTCP_APP_SIZE;
			msg->r.app.data = mem_alloc(msg->r.app.data_len, NULL);
			if (!msg->r.app.data) {
				err = ENOMEM;
				goto out;
			}
			if (mbuf_get_left(mb) < msg->r.app.data_len)
				goto badmsg;
			(void)mbuf_read_mem(mb, msg->r.app.data,
					    msg->r.app.data_len);
		}
		break;

	case RTCP_FIR:
		if (mbuf_get_left(mb) < RTCP_FIR_SIZE)
			goto badmsg;
		msg->r.fir.ssrc = ntohl(mbuf_read_u32(mb));
		break;

	case RTCP_NACK:
		if (mbuf_get_left(mb) < RTCP_NACK_SIZE)
			goto badmsg;
		msg->r.nack.ssrc = ntohl(mbuf_read_u32(mb));
		msg->r.nack.fsn  = ntohs(mbuf_read_u16(mb));
		msg->r.nack.blp  = ntohs(mbuf_read_u16(mb));
		break;

	case RTCP_RTPFB:
		if (mbuf_get_left(mb) < RTCP_FB_SIZE)
			goto badmsg;

		if (msg->hdr.length < 2)
			goto badmsg;

		msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb));
		msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb));
		msg->r.fb.n = msg->hdr.length - 2;

		err = rtcp_rtpfb_decode(mb, msg);
		break;

	case RTCP_PSFB:
		if (mbuf_get_left(mb) < RTCP_FB_SIZE)
			goto badmsg;

		if (msg->hdr.length < 2)
			goto badmsg;

		msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb));
		msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb));
		msg->r.fb.n = msg->hdr.length - 2;

		err = rtcp_psfb_decode(mb, msg);
		break;

	default:
		/* unknown message type */
		mbuf_advance(mb, rem);
		break;
	}
	if (err)
		goto out;

	/* slurp padding */
	while ((mb->pos - start) & 0x3 && mbuf_get_left(mb))
		++mb->pos;

 out:
	if (err)
		mem_deref(msg);
	else
		*msgp = msg;

	return err;

 badmsg:
	mem_deref(msg);
	return EBADMSG;
}
