/**
 * @file rtp.c  Real-time Transport Protocol
 *
 * 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_sa.h>
#include <re_sys.h>
#include <re_net.h>
#include <re_udp.h>
#include <re_rtp.h>
#include "rtcp.h"


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


/** Defines an RTP Socket */
struct rtp_sock {
	/** Encode data */
	struct {
		uint16_t seq;   /**< Sequence number       */
		uint32_t ssrc;  /**< Synchronizing source  */
	} enc;
	int proto;              /**< Transport Protocol    */
	void *sock_rtp;         /**< RTP Socket            */
	void *sock_rtcp;        /**< RTCP Socket           */
	struct sa local;        /**< Local RTP Address     */
	struct sa rtcp_peer;    /**< RTCP address of Peer  */
	rtp_recv_h *recvh;      /**< RTP Receive handler   */
	rtcp_recv_h *rtcph;     /**< RTCP Receive handler  */
	void *arg;              /**< Handler argument      */
	struct rtcp_sess *rtcp; /**< RTCP Session          */
	bool rtcp_mux;          /**< RTP/RTCP multiplexing */
};


/**
 * Encode the RTP header into a buffer
 *
 * @param mb  Buffer to encode into
 * @param hdr RTP Header to be encoded
 *
 * @return 0 if success, otherwise errorcode
 */
int rtp_hdr_encode(struct mbuf *mb, const struct rtp_header *hdr)
{
	uint8_t buf[2];
	int err, i;

	if (!mb || !hdr)
		return EINVAL;

	buf[0]  = (hdr->ver & 0x02) << 6;
	buf[0] |= (hdr->pad & 0x01) << 5;
	buf[0] |= (hdr->ext & 0x01) << 4;
	buf[0] |= (hdr->cc  & 0x0f) << 0;
	buf[1]  = (hdr->m   & 0x01) << 7;
	buf[1] |= (hdr->pt  & 0x7f) << 0;

	err  = mbuf_write_mem(mb, buf, sizeof(buf));
	err |= mbuf_write_u16(mb, htons(hdr->seq));
	err |= mbuf_write_u32(mb, htonl(hdr->ts));
	err |= mbuf_write_u32(mb, htonl(hdr->ssrc));

	for (i=0; i<hdr->cc; i++) {
		err |= mbuf_write_u32(mb, htonl(hdr->csrc[i]));
	}

	return err;
}


/**
 * Decode an RTP header from a buffer
 *
 * @param hdr RTP Header to decode into
 * @param mb  Buffer to decode from
 *
 * @return 0 if success, otherwise errorcode
 */
int rtp_hdr_decode(struct rtp_header *hdr, struct mbuf *mb)
{
	uint8_t buf[2];
	int err, i;
	size_t header_len;

	if (!hdr || !mb)
		return EINVAL;

	if (mbuf_get_left(mb) < RTP_HEADER_SIZE)
		return EBADMSG;

	err = mbuf_read_mem(mb, buf, sizeof(buf));
	if (err)
		return err;

	hdr->ver  = (buf[0] >> 6) & 0x03;
	hdr->pad  = (buf[0] >> 5) & 0x01;
	hdr->ext  = (buf[0] >> 4) & 0x01;
	hdr->cc   = (buf[0] >> 0) & 0x0f;
	hdr->m    = (buf[1] >> 7) & 0x01;
	hdr->pt   = (buf[1] >> 0) & 0x7f;

	hdr->seq  = ntohs(mbuf_read_u16(mb));
	hdr->ts   = ntohl(mbuf_read_u32(mb));
	hdr->ssrc = ntohl(mbuf_read_u32(mb));

	header_len = hdr->cc*sizeof(uint32_t);
	if (mbuf_get_left(mb) < header_len)
		return EBADMSG;

	for (i=0; i<hdr->cc; i++) {
		hdr->csrc[i] = ntohl(mbuf_read_u32(mb));
	}

	if (hdr->ext) {
		if (mbuf_get_left(mb) < 4)
			return EBADMSG;

		hdr->x.type = ntohs(mbuf_read_u16(mb));
		hdr->x.len  = ntohs(mbuf_read_u16(mb));

		if (mbuf_get_left(mb) < hdr->x.len*sizeof(uint32_t))
			return EBADMSG;

		mb->pos += hdr->x.len*sizeof(uint32_t);
	}

	return 0;
}


static void destructor(void *data)
{
	struct rtp_sock *rs = data;

	switch (rs->proto) {

	case IPPROTO_UDP:
		udp_handler_set(rs->sock_rtp, NULL, NULL);
		udp_handler_set(rs->sock_rtcp, NULL, NULL);
		break;

	default:
		break;
	}

	/* Destroy RTCP Session now */
	mem_deref(rs->rtcp);

	mem_deref(rs->sock_rtp);
	mem_deref(rs->sock_rtcp);
}


static void rtcp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
{
	struct rtp_sock *rs = arg;
	struct rtcp_msg *msg;

	while (0 == rtcp_decode(&msg, mb)) {

		/* handle internally first */
		rtcp_handler(rs->rtcp, msg);

		/* then relay to application */
		if (rs->rtcph)
			rs->rtcph(src, msg, rs->arg);

		mem_deref(msg);
	}
}


static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
{
	struct rtp_sock *rs = arg;
	struct rtp_header hdr;
	int err;

	/* Handle RTCP multiplexed on RTP-port */
	if (rs->rtcp_mux) {
		uint8_t pt;

		if (mbuf_get_left(mb) < 2)
			return;

		pt = mbuf_buf(mb)[1] & 0x7f;

		if (64 <= pt && pt <= 95) {
			rtcp_recv_handler(src, mb, arg);
			return;
		}
	}

	err = rtp_decode(rs, mb, &hdr);
	if (err)
		return;

	if (rs->rtcp) {
		rtcp_sess_rx_rtp(rs->rtcp, hdr.seq, hdr.ts,
				 hdr.ssrc, mbuf_get_left(mb), src);
	}

	if (rs->recvh)
		rs->recvh(src, &hdr, mb, rs->arg);
}


static int udp_range_listen(struct rtp_sock *rs, const struct sa *ip,
			    uint16_t min_port, uint16_t max_port)
{
	struct sa rtcp;
	int tries = 64;
	int err = 0;

	rs->local = rtcp = *ip;

	/* try hard */
	while (tries--) {
		struct udp_sock *us_rtp, *us_rtcp;
		uint16_t port;

		port = (min_port + (rand_u16() % (max_port - min_port)));
		port &= 0xfffe;

		sa_set_port(&rs->local, port);
		err = udp_listen(&us_rtp, &rs->local, udp_recv_handler, rs);
		if (err)
			continue;

		sa_set_port(&rtcp, port + 1);
		err = udp_listen(&us_rtcp, &rtcp, rtcp_recv_handler, rs);
		if (err) {
			mem_deref(us_rtp);
			continue;
		}

		/* OK */
		rs->sock_rtp = us_rtp;
		rs->sock_rtcp = us_rtcp;
		break;
	}

	return err;
}


/**
 * Allocate a new RTP socket
 *
 * @param rsp Pointer to returned RTP socket
 *
 * @return 0 for success, otherwise errorcode
 */
int rtp_alloc(struct rtp_sock **rsp)
{
	struct rtp_sock *rs;

	if (!rsp)
		return EINVAL;

	rs = mem_zalloc(sizeof(*rs), destructor);
	if (!rs)
		return ENOMEM;

	sa_init(&rs->rtcp_peer, AF_UNSPEC);

	rs->enc.seq  = rand_u16() & 0x7fff;
	rs->enc.ssrc = rand_u32();

	*rsp = rs;

	return 0;
}


/**
 * Listen on an RTP/RTCP Socket
 *
 * @param rsp         Pointer to returned RTP socket
 * @param proto       Transport protocol
 * @param ip          Local IP address
 * @param min_port    Minimum port range
 * @param max_port    Maximum port range
 * @param enable_rtcp True to enable RTCP Session
 * @param recvh       RTP Receive handler
 * @param rtcph       RTCP Receive handler
 * @param arg         Handler argument
 *
 * @return 0 for success, otherwise errorcode
 */
int rtp_listen(struct rtp_sock **rsp, int proto, const struct sa *ip,
	       uint16_t min_port, uint16_t max_port, bool enable_rtcp,
	       rtp_recv_h *recvh, rtcp_recv_h *rtcph, void *arg)
{
	struct rtp_sock *rs;
	int err;

	if (!ip || min_port >= max_port || !recvh)
		return EINVAL;

	err = rtp_alloc(&rs);
	if (err)
		return err;

	rs->proto = proto;
	rs->recvh = recvh;
	rs->rtcph = rtcph;
	rs->arg   = arg;

	/* Optional RTCP */
	if (enable_rtcp) {
		err = rtcp_sess_alloc(&rs->rtcp, rs);
		if (err)
			goto out;
	}

	switch (proto) {

	case IPPROTO_UDP:
		err = udp_range_listen(rs, ip, min_port, max_port);
		break;

	default:
		err = EPROTONOSUPPORT;
		break;
	}

 out:
	if (err)
		mem_deref(rs);
	else
		*rsp = rs;

	return err;
}


/**
 * Encode a new RTP header into the beginning of the buffer
 *
 * @param rs     RTP Socket
 * @param marker Marker bit
 * @param pt     Payload type
 * @param ts     Timestamp
 * @param mb     Memory buffer
 *
 * @return 0 for success, otherwise errorcode
 *
 * @note The buffer must have enough space for the RTP header
 */
int rtp_encode(struct rtp_sock *rs, bool marker, uint8_t pt, uint32_t ts,
	       struct mbuf *mb)
{
	struct rtp_header hdr;

	if (!rs || pt&~0x7f || !mb)
		return EINVAL;

	hdr.ver  = RTP_VERSION;
	hdr.pad  = false;
	hdr.ext  = false;
	hdr.cc   = 0;
	hdr.m    = marker ? 1 : 0;
	hdr.pt   = pt;
	hdr.seq  = rs->enc.seq++;
	hdr.ts   = ts;
	hdr.ssrc = rs->enc.ssrc;

	return rtp_hdr_encode(mb, &hdr);
}


/**
 * Decode an RTP packet and return decoded RTP header and payload
 *
 * @param rs     RTP Socket
 * @param mb     Memory buffer containing RTP packet
 * @param hdr    RTP header (set on return)
 *
 * @return 0 for success, otherwise errorcode
 */
int rtp_decode(struct rtp_sock *rs, struct mbuf *mb,
	       struct rtp_header *hdr)
{
	int err;

	if (!rs || !mb || !hdr)
		return EINVAL;

	memset(hdr, 0, sizeof(*hdr));
	err = rtp_hdr_decode(hdr, mb);
	if (err)
		return err;

	if (RTP_VERSION != hdr->ver)
		return EBADMSG;

	return 0;
}


/**
 * Send an RTP packet to a peer
 *
 * @param rs     RTP Socket
 * @param dst    Destination address
 * @param marker Marker bit
 * @param pt     Payload type
 * @param ts     Timestamp
 * @param mb     Payload buffer
 *
 * @return 0 for success, otherwise errorcode
 */
int rtp_send(struct rtp_sock *rs, const struct sa *dst,
	     bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb)
{
	size_t pos;
	int err;

	if (!rs || !mb)
		return EINVAL;

	if (mb->pos < RTP_HEADER_SIZE) {
		DEBUG_WARNING("rtp_send: buffer must have space for"
			      " rtp header (pos=%u, end=%u)\n",
			      mb->pos, mb->end);
		return EBADMSG;
	}

	mbuf_advance(mb, -RTP_HEADER_SIZE);

	pos = mb->pos;

	err = rtp_encode(rs, marker, pt, ts, mb);
	if (err)
		return err;

	if (rs->rtcp)
		rtcp_sess_tx_rtp(rs->rtcp, ts, mbuf_get_left(mb));

	mb->pos = pos;

	return udp_send(rs->sock_rtp, dst, mb);
}


/**
 * Get the RTP transport socket from an RTP/RTCP Socket
 *
 * @param rs RTP Socket
 *
 * @return Transport socket for RTP
 */
void *rtp_sock(const struct rtp_sock *rs)
{
	return rs ? rs->sock_rtp : NULL;
}


/**
 * Get the RTCP transport socket from an RTP/RTCP Socket
 *
 * @param rs RTP Socket
 *
 * @return Transport socket for RTCP
 */
void *rtcp_sock(const struct rtp_sock *rs)
{
	return rs ? rs->sock_rtcp : NULL;
}


/**
 * Get the local RTP address for an RTP/RTCP Socket
 *
 * @param rs RTP Socket
 *
 * @return Local RTP address
 */
const struct sa *rtp_local(const struct rtp_sock *rs)
{
	return rs ? &rs->local : NULL;
}


/**
 * Get the Synchronizing source for an RTP/RTCP Socket
 *
 * @param rs RTP Socket
 *
 * @return Synchronizing source
 */
uint32_t rtp_sess_ssrc(const struct rtp_sock *rs)
{
	return rs ? rs->enc.ssrc : 0;
}


/**
 * Get the RTCP-Session for an RTP/RTCP Socket
 *
 * @param rs RTP Socket
 *
 * @return RTCP-Session
 */
struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs)
{
	return rs ? rs->rtcp : NULL;
}


/**
 * Start the RTCP Session
 *
 * @param rs    RTP Socket
 * @param cname Canonical Name
 * @param peer  IP-Address of RTCP Peer
 */
void rtcp_start(struct rtp_sock *rs, const char *cname,
		const struct sa *peer)
{
	if (!rs)
		return;

	if (peer)
		rs->rtcp_peer = *peer;

	(void)rtcp_enable(rs->rtcp, peer != NULL, cname);
}


/**
 * Enable RTCP-multiplexing on RTP-port
 *
 * @param rs      RTP Socket
 * @param enabled True to enable, false to disable
 */
void rtcp_enable_mux(struct rtp_sock *rs, bool enabled)
{
	if (!rs)
		return;

	rs->rtcp_mux = enabled;
}


/**
 * Send RTCP packet(s) to the Peer
 *
 * @param rs RTP Socket
 * @param mb Buffer containing the RTCP Packet(s)
 *
 * @return 0 for success, otherwise errorcode
 */
int rtcp_send(struct rtp_sock *rs, struct mbuf *mb)
{
	if (!rs || !rs->sock_rtcp || !sa_isset(&rs->rtcp_peer, SA_ALL))
		return EINVAL;

	return udp_send(rs->rtcp_mux ? rs->sock_rtp : rs->sock_rtcp,
			&rs->rtcp_peer, mb);
}


/**
 * RTP Debug handler, use with fmt %H
 *
 * @param pf Print function
 * @param rs RTP Socket
 *
 * @return 0 if success, otherwise errorcode
 */
int rtp_debug(struct re_printf *pf, const struct rtp_sock *rs)
{
	int err;

	if (!rs || !pf)
		return EINVAL;

	err  = re_hprintf(pf, "RTP debug:\n");
	err |= re_hprintf(pf, " Encode: seq=%u ssrc=0x%lx\n",
			  rs->enc.seq, rs->enc.ssrc);

	if (rs->rtcp)
		err |= rtcp_debug(pf, rs);

	return err;
}
