/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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 
 */
#include <pjmedia/rtp.h>
#include <pjmedia/errno.h>
#include <pj/log.h>
#include <pj/sock.h>    /* pj_htonx, pj_htonx */
#include <pj/assert.h>
#include <pj/rand.h>
#include <pj/string.h>


#define THIS_FILE   "rtp.c"

#define RTP_VERSION     2

#define RTP_SEQ_MOD     (1 << 16)
#define MAX_DROPOUT     ((pj_int16_t)3000)
#define MAX_MISORDER    ((pj_int16_t)100)
#define MIN_SEQUENTIAL  ((pj_int16_t)2)

static void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *seq_ctrl, 
                                    pj_uint16_t seq);


PJ_DEF(pj_status_t) pjmedia_rtp_session_init( pjmedia_rtp_session *ses,
                                              int default_pt, 
                                              pj_uint32_t sender_ssrc )
{
    PJ_LOG(5, (THIS_FILE, 
               "pjmedia_rtp_session_init: ses=%p, default_pt=%d, ssrc=0x%x",
               ses, default_pt, sender_ssrc));

    /* Check RTP header packing. */
    if (sizeof(struct pjmedia_rtp_hdr) != 12) {
        pj_assert(!"Wrong RTP header packing!");
        return PJMEDIA_RTP_EINPACK;
    }

    /* If sender_ssrc is not specified, create from random value. */
    if (sender_ssrc == 0 || sender_ssrc == (pj_uint32_t)-1) {
        sender_ssrc = pj_htonl(pj_rand());
    } else {
        sender_ssrc = pj_htonl(sender_ssrc);
    }

    /* Initialize session. */
    pj_bzero(ses, sizeof(*ses));

    /* Initial sequence number SHOULD be random, according to RFC 3550. */
    /* According to RFC 3711, it should be random within 2^15 bit */
    ses->out_extseq = pj_rand() & 0x7FFF;
    ses->peer_ssrc = 0;
    
    /* Build default header for outgoing RTP packet. */
    ses->out_hdr.v = RTP_VERSION;
    ses->out_hdr.p = 0;
    ses->out_hdr.x = 0;
    ses->out_hdr.cc = 0;
    ses->out_hdr.m = 0;
    ses->out_hdr.pt = (pj_uint8_t) default_pt;
    ses->out_hdr.seq = (pj_uint16_t) pj_htons( (pj_uint16_t)ses->out_extseq );
    ses->out_hdr.ts = 0;
    ses->out_hdr.ssrc = sender_ssrc;

    /* Keep some arguments as session defaults. */
    ses->out_pt = (pj_uint16_t) default_pt;

    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pjmedia_rtp_session_init2( 
                                    pjmedia_rtp_session *ses,
                                    pjmedia_rtp_session_setting settings)
{
    pj_status_t status;
    int          pt = 0;
    pj_uint32_t  sender_ssrc = 0;

    if (settings.flags & 1)
        pt = settings.default_pt;
    if (settings.flags & 2)
        sender_ssrc = settings.sender_ssrc;

    status = pjmedia_rtp_session_init(ses, pt, sender_ssrc);
    if (status != PJ_SUCCESS)
        return status;

    if (settings.flags & 4) {
        ses->out_extseq = settings.seq;
        ses->out_hdr.seq = pj_htons((pj_uint16_t)ses->out_extseq);
    }
    if (settings.flags & 8)
        ses->out_hdr.ts = pj_htonl(settings.ts);
    if (settings.flags & 16) {
        ses->has_peer_ssrc = PJ_TRUE;
        ses->peer_ssrc = settings.peer_ssrc;
    }

    PJ_LOG(5, (THIS_FILE,
               "pjmedia_rtp_session_init2: ses=%p, seq=%d, ts=%u, peer_ssrc=%d",
               ses, pj_ntohs(ses->out_hdr.seq), pj_ntohl(ses->out_hdr.ts),
               ses->has_peer_ssrc? ses->peer_ssrc : 0));

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_rtp_encode_rtp( pjmedia_rtp_session *ses, 
                                            int pt, int m,
                                            int payload_len, int ts_len,
                                            const void **rtphdr, int *hdrlen )
{
    /* Update timestamp */
    ses->out_hdr.ts = pj_htonl(pj_ntohl(ses->out_hdr.ts)+ts_len);

    /* If payload_len is zero, bail out.
     * This is a clock frame; we're not really transmitting anything.
     */
    if (payload_len == 0)
        return PJ_SUCCESS;

    /* Update session. */
    ses->out_extseq++;

    /* Create outgoing header. */
    ses->out_hdr.pt = (pj_uint8_t) ((pt == -1) ? ses->out_pt : pt);
    ses->out_hdr.m = (pj_uint16_t) m;
    ses->out_hdr.seq = pj_htons( (pj_uint16_t) ses->out_extseq);

    /* Return values */
    *rtphdr = &ses->out_hdr;
    *hdrlen = sizeof(pjmedia_rtp_hdr);

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp( pjmedia_rtp_session *ses, 
                                            const void *pkt, int pkt_len,
                                            const pjmedia_rtp_hdr **hdr,
                                            const void **payload,
                                            unsigned *payloadlen)
{
    pjmedia_rtp_dec_hdr dec_hdr;

    return pjmedia_rtp_decode_rtp2(ses, pkt, pkt_len, hdr, &dec_hdr, 
                                   payload, payloadlen);
}


PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp2(
                                            pjmedia_rtp_session *ses,
                                            const void *pkt, int pkt_len,
                                            const pjmedia_rtp_hdr **hdr,
                                            pjmedia_rtp_dec_hdr *dec_hdr,
                                            const void **payload,
                                            unsigned *payloadlen)
{
    int offset;

    PJ_UNUSED_ARG(ses);

    /* Assume RTP header at the start of packet. We'll verify this later. */
    *hdr = (pjmedia_rtp_hdr*)pkt;

    /* Check RTP header sanity. */
    if ((*hdr)->v != RTP_VERSION) {
        return PJMEDIA_RTP_EINVER;
    }

    /* Payload is located right after header plus CSRC */
    offset = sizeof(pjmedia_rtp_hdr) + ((*hdr)->cc * sizeof(pj_uint32_t));

    /* Decode RTP extension. */
    if ((*hdr)->x) {
        if (offset + sizeof (pjmedia_rtp_ext_hdr) > (unsigned)pkt_len)
            return PJMEDIA_RTP_EINLEN;
        dec_hdr->ext_hdr = (pjmedia_rtp_ext_hdr*)(((pj_uint8_t*)pkt) + offset);
        dec_hdr->ext = (pj_uint32_t*)(dec_hdr->ext_hdr + 1);
        dec_hdr->ext_len = pj_ntohs((dec_hdr->ext_hdr)->length);
        offset += ((dec_hdr->ext_len + 1) * sizeof(pj_uint32_t));
    } else {
        dec_hdr->ext_hdr = NULL;
        dec_hdr->ext = NULL;
        dec_hdr->ext_len = 0;
    }

    /* Check that offset is not greater than packet size */
    if (offset > pkt_len) {
        return PJMEDIA_RTP_EINLEN;
    }

    /* Find and set payload. */
    *payload = offset==pkt_len? NULL : ((pj_uint8_t*)pkt) + offset;
    *payloadlen = pkt_len - offset;
 
    /* Remove payload padding if any */
    if ((*hdr)->p && *payloadlen > 0) {
        pj_uint8_t pad_len;

        pad_len = ((pj_uint8_t*)(*payload))[*payloadlen - 1];
        if (pad_len <= *payloadlen)
            *payloadlen -= pad_len;
    }

    return PJ_SUCCESS;
}


PJ_DEF(void) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, 
                                         const pjmedia_rtp_hdr *hdr,
                                         pjmedia_rtp_status *p_seq_st)
{
    pjmedia_rtp_session_update2(ses, hdr, p_seq_st, PJ_TRUE);
}

PJ_DEF(void) pjmedia_rtp_session_update2( pjmedia_rtp_session *ses, 
                                          const pjmedia_rtp_hdr *hdr,
                                          pjmedia_rtp_status *p_seq_st,
                                          pj_bool_t check_pt)
{
    pjmedia_rtp_status seq_st;

    /* for now check_pt MUST be either PJ_TRUE or PJ_FALSE.
     * In the future we might change check_pt from boolean to 
     * unsigned integer to accommodate more flags.
     */
    pj_assert(check_pt==PJ_TRUE || check_pt==PJ_FALSE);

    /* Init status */
    seq_st.status.value = 0;
    seq_st.diff = 0;

    /* Check SSRC. */
    if (!ses->has_peer_ssrc && ses->peer_ssrc == 0)
        ses->peer_ssrc = pj_ntohl(hdr->ssrc);

    if (pj_ntohl(hdr->ssrc) != ses->peer_ssrc) {
        seq_st.status.flag.badssrc = -1;
        if (!ses->has_peer_ssrc)
            ses->peer_ssrc = pj_ntohl(hdr->ssrc);
    }

    /* Check payload type. */
    if (check_pt && hdr->pt != ses->out_pt) {
        if (p_seq_st) {
            p_seq_st->status.value = seq_st.status.value;
            p_seq_st->status.flag.bad = -1;
            p_seq_st->status.flag.badpt = -1;
        }
        return;
    }

    /* Initialize sequence number on first packet received. */
    if (ses->received == 0)
        pjmedia_rtp_seq_init( &ses->seq_ctrl, pj_ntohs(hdr->seq) );

    /* Check sequence number to see if remote session has been restarted. */
    pjmedia_rtp_seq_update( &ses->seq_ctrl, pj_ntohs(hdr->seq), &seq_st);
    if (seq_st.status.flag.restart) {
        ++ses->received;

    } else if (!seq_st.status.flag.bad) {
        ++ses->received;
    }

    if (p_seq_st) {
        p_seq_st->status.value = seq_st.status.value;
        p_seq_st->diff = seq_st.diff;
    }
}



void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *sess, pj_uint16_t seq)
{
    sess->base_seq = seq;
    sess->max_seq = seq;
    sess->bad_seq = RTP_SEQ_MOD + 1;
    sess->cycles = 0;
}


void pjmedia_rtp_seq_init(pjmedia_rtp_seq_session *sess, pj_uint16_t seq)
{
    pjmedia_rtp_seq_restart(sess, seq);

    sess->max_seq = (pj_uint16_t) (seq - 1);
    sess->probation = MIN_SEQUENTIAL;
}


void pjmedia_rtp_seq_update( pjmedia_rtp_seq_session *sess, 
                             pj_uint16_t seq,
                             pjmedia_rtp_status *seq_status)
{
    pj_uint16_t udelta = (pj_uint16_t) (seq - sess->max_seq);
    pjmedia_rtp_status st;
    
    /* Init status */
    st.status.value = 0;
    st.status.flag.probation = 0;
    st.diff = 0;

    /*
     * Source is not valid until MIN_SEQUENTIAL packets with
     * sequential sequence numbers have been received.
     */
    if (sess->probation) {

        st.status.flag.probation = -1;

        if (seq == (pj_uint16_t)(sess->max_seq + 1)) {
            /* packet is in sequence */
            st.diff = 1;
            sess->probation--;
            sess->max_seq = seq;
            if (sess->probation == 0) {
                /* Init base seq, as per the RFC 3550. */
                sess->base_seq = seq;
            }
        } else {

            st.diff = 0;

            st.status.flag.bad = -1;
            if (seq == sess->max_seq)
                st.status.flag.dup = -1;
            else
                st.status.flag.outorder = -1;

            sess->probation = MIN_SEQUENTIAL - 1;
            sess->max_seq = seq;
        }


    } else if (udelta == 0) {

        st.status.flag.dup = -1;

    } else if (udelta < MAX_DROPOUT) {
        /* in order, with permissible gap */
        if (seq < sess->max_seq) {
            /* Sequence number wrapped - count another 64K cycle. */
            sess->cycles += RTP_SEQ_MOD;
        }
        sess->max_seq = seq;

        st.diff = udelta;

    } else if (udelta <= (RTP_SEQ_MOD - MAX_MISORDER)) {
        /* the sequence number made a very large jump */
        if (seq == sess->bad_seq) {
            /*
             * Two sequential packets -- assume that the other side
             * restarted without telling us so just re-sync
             * (i.e., pretend this was the first packet).
             */
            pjmedia_rtp_seq_restart(sess, seq);
            st.status.flag.restart = -1;
            st.status.flag.probation = -1;
            st.diff = 1;
        }
        else {
            sess->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
            st.status.flag.bad = -1;
            st.status.flag.outorder = -1;
        }
    } else {
        /* old duplicate or reordered packet.
         * Not necessarily bad packet (?)
         */
        st.status.flag.outorder = -1;
    }


    if (seq_status) {
        seq_status->diff = st.diff;
        seq_status->status.value = st.status.value;
    }
}
