/* 
 * 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/rtcp.h>
#include <pjmedia/errno.h>
#include <pjmedia/event.h>
#include <pjmedia/rtcp_fb.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/sock.h>
#include <pj/string.h>

#define THIS_FILE "rtcp.c"

#define RTCP_SR   200
#define RTCP_RR   201
#define RTCP_SDES 202
#define RTCP_BYE  203
#define RTCP_XR   207

/* RTCP Feedbacks */
#define RTCP_RTPFB      205
#define RTCP_PSFB       206

enum {
    RTCP_SDES_NULL  = 0,
    RTCP_SDES_CNAME = 1,
    RTCP_SDES_NAME  = 2,
    RTCP_SDES_EMAIL = 3,
    RTCP_SDES_PHONE = 4,
    RTCP_SDES_LOC   = 5,
    RTCP_SDES_TOOL  = 6,
    RTCP_SDES_NOTE  = 7
};

#if PJ_HAS_HIGH_RES_TIMER==0
#   error "High resolution timer needs to be enabled"
#endif



#if 0
#   define TRACE_(x)    PJ_LOG(3,x)
#else
#   define TRACE_(x)    ;
#endif


/*
 * Get NTP time.
 */
PJ_DEF(pj_status_t) pjmedia_rtcp_get_ntp_time(const pjmedia_rtcp_session *sess,
                                              pjmedia_rtcp_ntp_rec *ntp)
{
/* Seconds between 1900-01-01 to 1970-01-01 */
#define JAN_1970  (2208988800UL)
    pj_timestamp ts;
    pj_status_t status;

    status = pj_get_timestamp(&ts);

    /* Fill up the high 32bit part */
    ntp->hi = (pj_uint32_t)((ts.u64 - sess->ts_base.u64) / sess->ts_freq.u64)
              + sess->tv_base.sec + JAN_1970;

    /* Calculate seconds fractions */
    ts.u64 = (ts.u64 - sess->ts_base.u64) % sess->ts_freq.u64;
    pj_assert(ts.u64 < sess->ts_freq.u64);
    ts.u64 = (ts.u64 << 32) / sess->ts_freq.u64;

    /* Fill up the low 32bit part */
    ntp->lo = ts.u32.lo;


#if (defined(PJ_WIN32) && PJ_WIN32!=0) || \
    (defined(PJ_WIN64) && PJ_WIN64!=0) || \
    (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0)

    /* On Win32, since we use QueryPerformanceCounter() as the backend
     * timestamp API, we need to protect against this bug:
     *   Performance counter value may unexpectedly leap forward
     *   http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323
     */
    {
        /*
         * Compare elapsed time reported by timestamp with actual elapsed 
         * time. If the difference is too excessive, then we use system
         * time instead.
         */

        /* MIN_DIFF needs to be large enough so that "normal" diff caused
         * by system activity or context switch doesn't trigger the time
         * correction.
         */
        enum { MIN_DIFF = 400 };

        pj_time_val ts_time, elapsed, diff;

        pj_gettimeofday(&elapsed);

        ts_time.sec = ntp->hi - sess->tv_base.sec - JAN_1970;
        ts_time.msec = (long)(ntp->lo * 1000.0 / 0xFFFFFFFF);

        PJ_TIME_VAL_SUB(elapsed, sess->tv_base);

        if (PJ_TIME_VAL_LT(ts_time, elapsed)) {
            diff = elapsed;
            PJ_TIME_VAL_SUB(diff, ts_time);
        } else {
            diff = ts_time;
            PJ_TIME_VAL_SUB(diff, elapsed);
        }

        if (PJ_TIME_VAL_MSEC(diff) >= MIN_DIFF) {

            TRACE_((sess->name, "RTCP NTP timestamp corrected by %d ms",
                    PJ_TIME_VAL_MSEC(diff)));


            ntp->hi = elapsed.sec + sess->tv_base.sec + JAN_1970;
            ntp->lo = (elapsed.msec * 65536 / 1000) << 16;
        }

    }
#endif

    return status;
}


/*
 * Initialize RTCP session setting.
 */
PJ_DEF(void) pjmedia_rtcp_session_setting_default(
                                    pjmedia_rtcp_session_setting *settings)
{
    pj_bzero(settings, sizeof(*settings));
}


/*
 * Initialize bidirectional RTCP statistics.
 *
 */
PJ_DEF(void) pjmedia_rtcp_init_stat(pjmedia_rtcp_stat *stat)
{
    pj_time_val now;

    pj_assert(stat);

    pj_bzero(stat, sizeof(pjmedia_rtcp_stat));

    pj_math_stat_init(&stat->rtt);
    pj_math_stat_init(&stat->rx.loss_period);
    pj_math_stat_init(&stat->rx.jitter);
    pj_math_stat_init(&stat->tx.loss_period);
    pj_math_stat_init(&stat->tx.jitter);

#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
    pj_math_stat_init(&stat->rx_ipdv);
#endif

#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
    pj_math_stat_init(&stat->rx_raw_jitter);
#endif

    pj_gettimeofday(&now);
    stat->start = now;
}


/*
 * Initialize RTCP session.
 */
PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *sess, 
                               char *name,
                               unsigned clock_rate,
                               unsigned samples_per_frame,
                               pj_uint32_t ssrc)
{
    pjmedia_rtcp_session_setting settings;

    pjmedia_rtcp_session_setting_default(&settings);
    settings.name = name;
    settings.clock_rate = clock_rate;
    settings.samples_per_frame = samples_per_frame;
    settings.ssrc = ssrc;

    pjmedia_rtcp_init2(sess, &settings);
}


/*
 * Initialize RTCP session.
 */
PJ_DEF(void) pjmedia_rtcp_init2( pjmedia_rtcp_session *sess,
                                 const pjmedia_rtcp_session_setting *settings)
{
    pjmedia_rtcp_sr_pkt *sr_pkt = &sess->rtcp_sr_pkt;
    pj_time_val now;
    
    /* Memset everything */
    pj_bzero(sess, sizeof(pjmedia_rtcp_session));

    /* Last RX timestamp in RTP packet */
    sess->rtp_last_ts = (unsigned)-1;

    /* Name */
    sess->name = settings->name ? settings->name : (char*)THIS_FILE;

    /* Set clock rate */
    sess->clock_rate = settings->clock_rate;
    sess->pkt_size = settings->samples_per_frame;

    /* Init common RTCP SR header */
    sr_pkt->common.version = 2;
    sr_pkt->common.count = 1;
    sr_pkt->common.pt = RTCP_SR;
    sr_pkt->common.length = pj_htons(12);
    sr_pkt->common.ssrc = pj_htonl(settings->ssrc);
    
    /* Copy to RTCP RR header */
    pj_memcpy(&sess->rtcp_rr_pkt.common, &sr_pkt->common, 
              sizeof(pjmedia_rtcp_common));
    sess->rtcp_rr_pkt.common.pt = RTCP_RR;
    sess->rtcp_rr_pkt.common.length = pj_htons(7);

    /* Copy to RTCP FB common header */
    pj_memcpy(&sess->rtcp_fb_com, &sr_pkt->common, 
              sizeof(pjmedia_rtcp_common));
    sess->rtcp_fb_com.ssrc_src = 0;

    /* Get time and timestamp base and frequency */
    pj_gettimeofday(&now);
    sess->tv_base = now;
    pj_get_timestamp(&sess->ts_base);
    pj_get_timestamp_freq(&sess->ts_freq);
    sess->rtp_ts_base = settings->rtp_ts_base;

    /* Initialize statistics states */
    pjmedia_rtcp_init_stat(&sess->stat);

    /* RR will be initialized on receipt of the first RTP packet. */
}


PJ_DEF(void) pjmedia_rtcp_fini(pjmedia_rtcp_session *sess)
{
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    pjmedia_rtcp_xr_fini(&sess->xr_session);
#else
    /* Nothing to do. */
    PJ_UNUSED_ARG(sess);
#endif
}

static void rtcp_init_seq(pjmedia_rtcp_session *sess)
{
    sess->received = 0;
    sess->exp_prior = 0;
    sess->rx_prior = 0;
    sess->transit = 0;
    sess->jitter = 0;
}

PJ_DEF(void) pjmedia_rtcp_rx_rtp( pjmedia_rtcp_session *sess, 
                                  unsigned seq, 
                                  unsigned rtp_ts,
                                  unsigned payload)
{
    pjmedia_rtcp_rx_rtp2(sess, seq, rtp_ts, payload, PJ_FALSE);
}

PJ_DEF(void) pjmedia_rtcp_rx_rtp2(pjmedia_rtcp_session *sess, 
                                  unsigned seq, 
                                  unsigned rtp_ts,
                                  unsigned payload,
                                  pj_bool_t discarded)
{   
    pj_timestamp ts;
    pj_uint32_t arrival;
    pj_int32_t transit;
    pjmedia_rtp_status seq_st;

#if !defined(PJMEDIA_HAS_RTCP_XR) || (PJMEDIA_HAS_RTCP_XR == 0)
    PJ_UNUSED_ARG(discarded);
#endif

    if (sess->stat.rx.pkt == 0) {
        /* Init sequence for the first time. */
        pjmedia_rtp_seq_init(&sess->seq_ctrl, (pj_uint16_t)seq);
    } 

    sess->stat.rx.pkt++;
    sess->stat.rx.bytes += payload;

    /* Process the RTP packet. */
    pjmedia_rtp_seq_update(&sess->seq_ctrl, (pj_uint16_t)seq, &seq_st);

    if (seq_st.status.flag.restart) {
        rtcp_init_seq(sess);
    }
    
    if (seq_st.status.flag.dup) {
        sess->stat.rx.dup++;
        TRACE_((sess->name, "Duplicate packet detected"));
    }

    if (seq_st.status.flag.outorder && !seq_st.status.flag.probation) {
        sess->stat.rx.reorder++;
        TRACE_((sess->name, "Out-of-order packet detected"));
    }

    if (seq_st.status.flag.bad) {
        sess->stat.rx.discard++;

#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
        pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq, 
                               -1,                               /* lost    */
                               (seq_st.status.flag.dup? 1:0),    /* dup     */
                               (!seq_st.status.flag.dup? 1:-1),  /* discard */
                               -1,                               /* jitter  */
                               -1, 0);                           /* toh     */
#endif

        TRACE_((sess->name, "Bad packet discarded"));
        return;
    }

    /* Only mark "good" packets */
    ++sess->received;

    /* Calculate loss periods. */
    if (seq_st.diff > 1) {
        unsigned count = seq_st.diff - 1;
        unsigned period;

        period = count * sess->pkt_size * 1000 / sess->clock_rate;
        period *= 1000;

        /* Update packet lost. 
         * The packet lost number will also be updated when we're sending
         * outbound RTCP RR.
         */
        sess->stat.rx.loss += (seq_st.diff - 1);
        TRACE_((sess->name, "%d packet(s) lost", seq_st.diff - 1));

        /* Update loss period stat */
        pj_math_stat_update(&sess->stat.rx.loss_period, period);
    }


    /*
     * Calculate jitter only when sequence is good (see RFC 3550 section A.8),
     * AND only when the timestamp is different than the last packet
     * (see RTP FAQ).
     */
    if (seq_st.diff == 1 && rtp_ts != sess->rtp_last_ts) {
        /* Get arrival time and convert timestamp to samples */
        pj_get_timestamp(&ts);
        ts.u64 = ts.u64 * sess->clock_rate / sess->ts_freq.u64;
        arrival = ts.u32.lo;

        transit = arrival - rtp_ts;
    
        /* Ignore the first N packets as they normally have bad jitter
         * due to other threads working to establish the call
         */
        if (sess->transit == 0 || 
            sess->received < PJMEDIA_RTCP_IGNORE_FIRST_PACKETS) 
        {
            sess->transit = transit;
            sess->stat.rx.jitter.min = (unsigned)-1;
        } else {
            pj_int32_t d;
            pj_uint32_t jitter;

            d = transit - sess->transit;
            if (d < 0) 
                d = -d;
            
            sess->jitter += d - ((sess->jitter + 8) >> 4);

            /* Update jitter stat */
            jitter = sess->jitter >> 4;
            
            /* Convert jitter unit from samples to usec */
            if (jitter < 4294)
                jitter = jitter * 1000000 / sess->clock_rate;
            else {
                jitter = jitter * 1000 / sess->clock_rate;
                jitter *= 1000;
            }
            pj_math_stat_update(&sess->stat.rx.jitter, jitter);


#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
            {
                pj_uint32_t raw_jitter;

                /* Convert raw jitter unit from samples to usec */
                if (d < 4294)
                    raw_jitter = d * 1000000 / sess->clock_rate;
                else {
                    raw_jitter = d * 1000 / sess->clock_rate;
                    raw_jitter *= 1000;
                }
                
                /* Update jitter stat */
                pj_math_stat_update(&sess->stat.rx_raw_jitter, raw_jitter);
            }
#endif


#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
            {
                pj_int32_t ipdv;

                ipdv = transit - sess->transit;
                /* Convert IPDV unit from samples to usec */
                if (ipdv > -2147 && ipdv < 2147)
                    ipdv = ipdv * 1000000 / (int)sess->clock_rate;
                else {
                    ipdv = ipdv * 1000 / (int)sess->clock_rate;
                    ipdv *= 1000;
                }
                
                /* Update jitter stat */
                pj_math_stat_update(&sess->stat.rx_ipdv, ipdv);
            }
#endif

#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
            pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq, 
                                   0,                       /* lost    */
                                   0,                       /* dup     */
                                   discarded,               /* discard */
                                   (sess->jitter >> 4),     /* jitter  */
                                   -1, 0);                  /* toh     */
#endif

            /* Update session transit */
            sess->transit = transit;
        }
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    } else if (seq_st.diff > 1) {
        int i;

        /* Report RTCP XR about packet losses */
        for (i=seq_st.diff-1; i>0; --i) {
            pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq - i, 
                                   1,                       /* lost    */
                                   0,                       /* dup     */
                                   0,                       /* discard */
                                   -1,                      /* jitter  */
                                   -1, 0);                  /* toh     */
        }

        /* Report RTCP XR this packet */
        pjmedia_rtcp_xr_rx_rtp(&sess->xr_session, seq, 
                               0,                           /* lost    */
                               0,                           /* dup     */
                               discarded,                   /* discard */
                               -1,                          /* jitter  */
                               -1, 0);                      /* toh     */
#endif
    }

    /* Update timestamp of last RX RTP packet */
    sess->rtp_last_ts = rtp_ts;
}

PJ_DEF(void) pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *sess, 
                                 unsigned bytes_payload_size)
{
    /* Update statistics */
    sess->stat.tx.pkt++;
    sess->stat.tx.bytes += bytes_payload_size;
}


static void parse_rtcp_report( pjmedia_rtcp_session *sess,
                               const void *pkt,
                               pj_size_t size)
{
    pjmedia_rtcp_common *common = (pjmedia_rtcp_common*) pkt;
    const pjmedia_rtcp_rr *rr = NULL;
    const pjmedia_rtcp_sr *sr = NULL;
    pj_uint32_t last_loss, jitter_samp, jitter;

    /* Parse RTCP */
    if (common->pt == RTCP_SR) {
        if (sizeof (pjmedia_rtcp_common) + sizeof (pjmedia_rtcp_sr) > size) {
            TRACE_((sess->name, "Discarding RTCP SR due to truncated size "
                                "%d bytes", size));
            return;
        }
        sr = (pjmedia_rtcp_sr*) (((char*)pkt) + sizeof(pjmedia_rtcp_common));
        if (common->count > 0 && size >= (sizeof(pjmedia_rtcp_sr_pkt))) {
            rr = (pjmedia_rtcp_rr*)(((char*)pkt) + (sizeof(pjmedia_rtcp_common)
                                    + sizeof(pjmedia_rtcp_sr)));
        }
    } else if (common->pt == RTCP_RR && common->count > 0) {
        if (sizeof (pjmedia_rtcp_common) + sizeof (pjmedia_rtcp_rr) > size) {
            TRACE_((sess->name, "Discarding RTCP RR due to truncated size "
                                "%d bytes", size));
            return;
        }
        rr = (pjmedia_rtcp_rr*)(((char*)pkt) + sizeof(pjmedia_rtcp_common));
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    } else if (common->pt == RTCP_XR) {
        if (sess->xr_enabled)
            pjmedia_rtcp_xr_rx_rtcp_xr(&sess->xr_session, pkt, size);

        return;
#endif
    }


    if (sr) {
        /* Save LSR from NTP timestamp of RTCP packet */
        sess->rx_lsr = ((pj_ntohl(sr->ntp_sec) & 0x0000FFFF) << 16) | 
                       ((pj_ntohl(sr->ntp_frac) >> 16) & 0xFFFF);

        /* Calculate SR arrival time for DLSR */
        pj_get_timestamp(&sess->rx_lsr_time);

        TRACE_((sess->name, "Rx RTCP SR: ntp_ts=%p", 
                sess->rx_lsr,
                (pj_uint32_t)(sess->rx_lsr_time.u64*65536/sess->ts_freq.u64)));
    }


    /* Nothing more to do if there's no RR packet */
    if (rr == NULL)
        return;


    last_loss = sess->stat.tx.loss;

    /* Get packet loss */
    sess->stat.tx.loss = (rr->total_lost_2 << 16) +
                         (rr->total_lost_1 << 8) +
                          rr->total_lost_0;

    TRACE_((sess->name, "Rx RTCP RR: total_lost_2=%x, 1=%x, 0=%x, lost=%d", 
            (int)rr->total_lost_2,
            (int)rr->total_lost_1,
            (int)rr->total_lost_0,
            sess->stat.tx.loss));
    
    /* We can't calculate the exact loss period for TX, so just give the
     * best estimation.
     */
    if (sess->stat.tx.loss > last_loss) {
        unsigned period;

        /* Loss period in msec */
        period = (sess->stat.tx.loss - last_loss) * sess->pkt_size *
                 1000 / sess->clock_rate;

        /* Loss period in usec */
        period *= 1000;

        /* Update loss period stat */
        pj_math_stat_update(&sess->stat.tx.loss_period, period);
    }

    /* Get jitter value in usec */
    jitter_samp = pj_ntohl(rr->jitter);
    /* Calculate jitter in usec, avoiding overflows */
    if (jitter_samp <= 4294)
        jitter = jitter_samp * 1000000 / sess->clock_rate;
    else {
        jitter = jitter_samp * 1000 / sess->clock_rate;
        jitter *= 1000;
    }

    /* Update jitter statistics */
    pj_math_stat_update(&sess->stat.tx.jitter, jitter);

    /* Can only calculate if LSR and DLSR is present in RR */
    if (rr->lsr && rr->dlsr) {
        pj_uint32_t lsr, now, dlsr;
        pj_uint64_t eedelay;
        pjmedia_rtcp_ntp_rec ntp;

        /* LSR is the middle 32bit of NTP. It has 1/65536 second 
         * resolution 
         */
        lsr = pj_ntohl(rr->lsr);

        /* DLSR is delay since LSR, also in 1/65536 resolution */
        dlsr = pj_ntohl(rr->dlsr);

        /* Get current time, and convert to 1/65536 resolution */
        pjmedia_rtcp_get_ntp_time(sess, &ntp);
        now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16);

        /* End-to-end delay is (now-lsr-dlsr) */
        eedelay = now - lsr - dlsr;

        /* Convert end to end delay to usec (keeping the calculation in
         * 64bit space)::
         *   sess->ee_delay = (eedelay * 1000) / 65536;
         */
        if (eedelay < 4294) {
            eedelay = (eedelay * 1000000) >> 16;
        } else {
            eedelay = (eedelay * 1000) >> 16;
            eedelay *= 1000;
        }

        TRACE_((sess->name, "Rx RTCP RR: lsr=%p, dlsr=%p (%d:%03dms), "
                           "now=%p, rtt=%p",
                lsr, dlsr, dlsr/65536, (dlsr%65536)*1000/65536,
                now, (pj_uint32_t)eedelay));
        
        /* Only save calculation if "now" is greater than lsr, or
         * otherwise rtt will be invalid 
         */
        if (now-dlsr >= lsr) {
            unsigned rtt = (pj_uint32_t)eedelay;
            
            /* Check that eedelay value really makes sense. 
             * We allow up to 30 seconds RTT!
             */
            if (eedelay > 30 * 1000 * 1000UL) {

                TRACE_((sess->name, "RTT not making any sense, ignored.."));
                goto end_rtt_calc;
            }

#if defined(PJMEDIA_RTCP_NORMALIZE_FACTOR) && PJMEDIA_RTCP_NORMALIZE_FACTOR!=0
            /* "Normalize" rtt value that is exceptionally high. For such
             * values, "normalize" the rtt to be PJMEDIA_RTCP_NORMALIZE_FACTOR
             * times the average value.
             */
            if (rtt > ((unsigned)sess->stat.rtt.mean *
                       PJMEDIA_RTCP_NORMALIZE_FACTOR) && sess->stat.rtt.n!=0)
            {
                unsigned orig_rtt = rtt;
                rtt = sess->stat.rtt.mean * PJMEDIA_RTCP_NORMALIZE_FACTOR;
                PJ_LOG(5,(sess->name,
                          "RTT value %d usec is normalized to %d usec",
                          orig_rtt, rtt));
            }
#endif
            TRACE_((sess->name, "RTCP RTT is set to %d usec", rtt));

            /* Update RTT stat */
            pj_math_stat_update(&sess->stat.rtt, rtt);

        } else {
            PJ_LOG(5, (sess->name, "Internal RTCP NTP clock skew detected: "
                                   "lsr=%p, now=%p, dlsr=%p (%d:%03dms), "
                                   "diff=%d",
                                   lsr, now, dlsr, dlsr/65536,
                                   (dlsr%65536)*1000/65536,
                                   dlsr-(now-lsr)));
        }
    }

end_rtt_calc:

    pj_gettimeofday(&sess->stat.tx.update);
    sess->stat.tx.update_cnt++;
}


static void parse_rtcp_sdes(pjmedia_rtcp_session *sess,
                            const void *pkt,
                            pj_size_t size)
{
    pjmedia_rtcp_sdes *sdes = &sess->stat.peer_sdes;
    char *p, *p_end;
    char *b, *b_end;

    p = (char*)pkt + 8;
    p_end = (char*)pkt + size;

    pj_bzero(sdes, sizeof(*sdes));
    b = sess->stat.peer_sdes_buf_;
    b_end = b + sizeof(sess->stat.peer_sdes_buf_);

    while (p < p_end) {
        pj_uint8_t sdes_type, sdes_len;
        pj_str_t sdes_value = {NULL, 0};

        sdes_type = *p++;

        /* Check for end of SDES item list */
        if (sdes_type == RTCP_SDES_NULL || p == p_end)
            break;

        sdes_len = *p++;

        /* Check for corrupted SDES packet */
        if (p + sdes_len > p_end)
            break;

        /* Get SDES item */
        if (b + sdes_len < b_end) {
            pj_memcpy(b, p, sdes_len);
            sdes_value.ptr = b;
            sdes_value.slen = sdes_len;
            b += sdes_len;
        } else {
            /* Insufficient SDES buffer */
            PJ_LOG(5, (sess->name,
                    "Unsufficient buffer to save RTCP SDES type %d:%.*s",
                    sdes_type, sdes_len, p));
            p += sdes_len;
            continue;
        }

        switch (sdes_type) {
        case RTCP_SDES_CNAME:
            sdes->cname = sdes_value;
            break;
        case RTCP_SDES_NAME:
            sdes->name = sdes_value;
            break;
        case RTCP_SDES_EMAIL:
            sdes->email = sdes_value;
            break;
        case RTCP_SDES_PHONE:
            sdes->phone = sdes_value;
            break;
        case RTCP_SDES_LOC:
            sdes->loc = sdes_value;
            break;
        case RTCP_SDES_TOOL:
            sdes->tool = sdes_value;
            break;
        case RTCP_SDES_NOTE:
            sdes->note = sdes_value;
            break;
        default:
            TRACE_((sess->name, "Received unknown RTCP SDES type %d:%.*s",
                    sdes_type, sdes_value.slen, sdes_value.ptr));
            break;
        }

        p += sdes_len;
    }
}


static void parse_rtcp_bye(pjmedia_rtcp_session *sess,
                           const void *pkt,
                           pj_size_t size)
{
    pj_str_t reason = {"-", 1};

    /* Check and get BYE reason */
    if (size > 8) {
        /* Make sure the BYE reason does not exceed:
         * - the size of the available buffer
         * - the declared reason's length
         * - the actual packet size
         */
        reason.slen = PJ_MIN(sizeof(sess->stat.peer_sdes_buf_),
                             *((pj_uint8_t*)pkt+8));
        reason.slen = PJ_MIN(reason.slen, (pj_ssize_t)(size-9));

        pj_memcpy(sess->stat.peer_sdes_buf_, ((pj_uint8_t*)pkt+9),
                  reason.slen);
        reason.ptr = sess->stat.peer_sdes_buf_;
    }

    /* Just print RTCP BYE log */
    PJ_LOG(5, (sess->name, "Received RTCP BYE, reason: %.*s",
               reason.slen, reason.ptr));
}


static void parse_rtcp_fb(pjmedia_rtcp_session *sess,
                          const void *pkt,
                          pj_size_t size)
{
    unsigned cnt = 1;
    pjmedia_rtcp_fb_nack nack[1];
    //pjmedia_rtcp_fb_sli sli[1];
    //pjmedia_rtcp_fb_rpsi rpsi;
    pjmedia_event ev;
    pj_timestamp ts_now;

    if (size < sizeof(pjmedia_rtcp_fb_common)) {
        PJ_PERROR(3, (THIS_FILE, PJ_ETOOSMALL,
                      "Failed parsing RTCP FB, invalid packet length"));
        return;
    }    

    pj_get_timestamp(&ts_now);

    if (pjmedia_rtcp_fb_parse_nack(pkt, size, &cnt, nack)==PJ_SUCCESS)
    {
        pjmedia_event_init(&ev, PJMEDIA_EVENT_RX_RTCP_FB, &ts_now, sess);
        ev.data.rx_rtcp_fb.cap.type = PJMEDIA_RTCP_FB_NACK;
        ev.data.rx_rtcp_fb.msg.nack = nack[0];
        pjmedia_event_publish(NULL, sess, &ev, 0);

    } else if (pjmedia_rtcp_fb_parse_pli(pkt, size)==PJ_SUCCESS)
    {
        pjmedia_event_init(&ev, PJMEDIA_EVENT_RX_RTCP_FB, &ts_now, sess);
        ev.data.rx_rtcp_fb.cap.type = PJMEDIA_RTCP_FB_NACK;
        pj_strset2(&ev.data.rx_rtcp_fb.cap.param, (char*)"pli");
        pjmedia_event_publish(NULL, sess, &ev, 0);

        /*  For other FB type implementations later
    } else if (pjmedia_rtcp_fb_parse_sli(pkt, size, &cnt, sli)==PJ_SUCCESS)
    {
    } else if (pjmedia_rtcp_fb_parse_rpsi(pkt, size, &rpsi)==PJ_SUCCESS)
    {
        */
    } else {
        /* Ignore unknown RTCP Feedback */
        TRACE_((sess->name, "Received unknown RTCP feedback"));
    }
}


PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess,
                                   const void *pkt,
                                   pj_size_t size)
{
    pj_uint8_t *p, *p_end;

    p = (pj_uint8_t*)pkt;
    p_end = p + size;
    while (p < p_end) {
        pjmedia_rtcp_common *common;
        unsigned len;

        if (p + sizeof(pjmedia_rtcp_common) > p_end) {
            TRACE_((sess->name, "Receiving truncated RTCP packet (1)"));
            break;
        }
        common = (pjmedia_rtcp_common*)p;

        len = (pj_ntohs((pj_uint16_t)common->length)+1) * 4;
        if (p + len > p_end) {
            TRACE_((sess->name, "Receiving truncated RTCP packet (2)"));
            break;
        }

        switch(common->pt) {
        case RTCP_SR:
        case RTCP_RR:
        case RTCP_XR:
            parse_rtcp_report(sess, p, len);
            break;
        case RTCP_SDES:
            parse_rtcp_sdes(sess, p, len);
            break;
        case RTCP_BYE:
            parse_rtcp_bye(sess, p, len);
            break;
        case RTCP_RTPFB:
        case RTCP_PSFB:
            parse_rtcp_fb(sess, p, len);
            break;
        default:
            /* Ignore unknown RTCP */
            TRACE_((sess->name, "Received unknown RTCP packet type=%d",
                    common->pt));
            break;
        }

        p += len;
    }
}


PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *sess, 
                                     void **ret_p_pkt, int *len)
{
    pj_uint32_t expected, expected_interval, received_interval, lost_interval;
    pjmedia_rtcp_sr *sr;
    pjmedia_rtcp_rr *rr;
    pj_timestamp ts_now;
    pjmedia_rtcp_ntp_rec ntp;

    /* Get current NTP time. */
    pj_get_timestamp(&ts_now);
    pjmedia_rtcp_get_ntp_time(sess, &ntp);


    /* See if we have transmitted RTP packets since last time we
     * sent RTCP SR.
     */
    if (sess->stat.tx.pkt != pj_ntohl(sess->rtcp_sr_pkt.sr.sender_pcount)) {
        pj_time_val ts_time;
        pj_uint32_t rtp_ts;

        /* So we should send RTCP SR */
        *ret_p_pkt = (void*) &sess->rtcp_sr_pkt;
        *len = sizeof(pjmedia_rtcp_sr_pkt);
        rr = &sess->rtcp_sr_pkt.rr;
        sr = &sess->rtcp_sr_pkt.sr;

        /* Update packet count */
        sr->sender_pcount = pj_htonl(sess->stat.tx.pkt);

        /* Update octets count */
        sr->sender_bcount = pj_htonl(sess->stat.tx.bytes);

        /* Fill in NTP timestamp in SR. */
        sr->ntp_sec = pj_htonl(ntp.hi);
        sr->ntp_frac = pj_htonl(ntp.lo);

        /* Fill in RTP timestamp (corresponds to NTP timestamp) in SR. */
        ts_time.sec = ntp.hi - sess->tv_base.sec - JAN_1970;
        ts_time.msec = (long)(ntp.lo * 1000.0 / 0xFFFFFFFF);
        rtp_ts = sess->rtp_ts_base +
                 (pj_uint32_t)(sess->clock_rate*ts_time.sec) +
                 (pj_uint32_t)(sess->clock_rate*ts_time.msec/1000);
        sr->rtp_ts = pj_htonl(rtp_ts);

        TRACE_((sess->name, "TX RTCP SR: ntp_ts=%p", 
                           ((ntp.hi & 0xFFFF) << 16) + ((ntp.lo & 0xFFFF0000) 
                                >> 16)));


    } else {
        /* We should send RTCP RR then */
        *ret_p_pkt = (void*) &sess->rtcp_rr_pkt;
        *len = sizeof(pjmedia_rtcp_rr_pkt);
        rr = &sess->rtcp_rr_pkt.rr;
        sr = NULL;
    }
    
    /* SSRC and last_seq */
    rr->ssrc = pj_htonl(sess->peer_ssrc);
    rr->last_seq = (sess->seq_ctrl.cycles & 0xFFFF0000L);
    /* Since this is an "+=" operation, make sure we update last_seq on
     * both RR and SR.
     */
    sess->rtcp_sr_pkt.rr.last_seq += sess->seq_ctrl.max_seq;
    sess->rtcp_rr_pkt.rr.last_seq += sess->seq_ctrl.max_seq;
    rr->last_seq = pj_htonl(rr->last_seq);


    /* Jitter */
    rr->jitter = pj_htonl(sess->jitter >> 4);
    
    
    /* Total lost. */
    expected = pj_ntohl(rr->last_seq) - sess->seq_ctrl.base_seq;

    /* This is bug: total lost already calculated on each incoming RTP!
    if (expected >= sess->received)
        sess->stat.rx.loss = expected - sess->received;
    else
        sess->stat.rx.loss = 0;
    */

    rr->total_lost_2 = (sess->stat.rx.loss >> 16) & 0xFF;
    rr->total_lost_1 = (sess->stat.rx.loss >> 8) & 0xFF;
    rr->total_lost_0 = (sess->stat.rx.loss & 0xFF);

    /* Fraction lost calculation */
    expected_interval = expected - sess->exp_prior;
    sess->exp_prior = expected;
    
    received_interval = sess->received - sess->rx_prior;
    sess->rx_prior = sess->received;
    
    if (expected_interval >= received_interval)
        lost_interval = expected_interval - received_interval;
    else
        lost_interval = 0;
    
    if (expected_interval==0 || lost_interval == 0) {
        rr->fract_lost = 0;
    } else {
        rr->fract_lost = (lost_interval << 8) / expected_interval;
    }
    
    if (sess->rx_lsr_time.u64 == 0 || sess->rx_lsr == 0) {
        rr->lsr = 0;
        rr->dlsr = 0;
    } else {
        pj_timestamp ts;
        pj_uint32_t lsr = sess->rx_lsr;
        pj_uint64_t lsr_time = sess->rx_lsr_time.u64;
        pj_uint32_t dlsr;
        
        /* Convert LSR time to 1/65536 seconds resolution */
        lsr_time = (lsr_time << 16) / sess->ts_freq.u64;

        /* Fill in LSR.
           LSR is the middle 32bit of the last SR NTP time received.
         */
        rr->lsr = pj_htonl(lsr);
        
        /* Fill in DLSR.
           DLSR is Delay since Last SR, in 1/65536 seconds.
         */
        ts.u64 = ts_now.u64;

        /* Convert interval to 1/65536 seconds value */
        ts.u64 = (ts.u64 << 16) / sess->ts_freq.u64;

        /* Get DLSR */
        dlsr = (pj_uint32_t)(ts.u64 - lsr_time);
        rr->dlsr = pj_htonl(dlsr);

        TRACE_((sess->name,"Tx RTCP RR: lsr=%p, lsr_time=%p, now=%p, dlsr=%p"
                           "(%ds:%03dms)",
                           lsr, 
                           (pj_uint32_t)lsr_time,
                           (pj_uint32_t)ts.u64, 
                           dlsr,
                           dlsr/65536,
                           (dlsr%65536)*1000/65536 ));
    }
    
    /* Update counter */
    pj_gettimeofday(&sess->stat.rx.update);
    sess->stat.rx.update_cnt++;
}


PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes(
                                            pjmedia_rtcp_session *session, 
                                            void *buf,
                                            pj_size_t *length,
                                            const pjmedia_rtcp_sdes *sdes)
{
    pjmedia_rtcp_common *hdr;
    pj_uint8_t *p;
    pj_size_t len;

    PJ_ASSERT_RETURN(session && buf && length && sdes, PJ_EINVAL);

    /* Verify SDES item length */
    if (sdes->cname.slen > 255 || sdes->name.slen  > 255 ||
        sdes->email.slen > 255 || sdes->phone.slen > 255 ||
        sdes->loc.slen   > 255 || sdes->tool.slen  > 255 ||
        sdes->note.slen  > 255)
    {
        return PJ_EINVAL;
    }

    /* Verify buffer length */
    len = sizeof(*hdr);
    if (sdes->cname.slen) len += sdes->cname.slen + 2;
    if (sdes->name.slen)  len += sdes->name.slen  + 2;
    if (sdes->email.slen) len += sdes->email.slen + 2;
    if (sdes->phone.slen) len += sdes->phone.slen + 2;
    if (sdes->loc.slen)   len += sdes->loc.slen   + 2;
    if (sdes->tool.slen)  len += sdes->tool.slen  + 2;
    if (sdes->note.slen)  len += sdes->note.slen  + 2;
    len++; /* null termination */
    len = ((len+3)/4) * 4;
    if (len > *length)
        return PJ_ETOOSMALL;

    /* Build RTCP SDES header */
    hdr = (pjmedia_rtcp_common*)buf;
    pj_memcpy(hdr, &session->rtcp_sr_pkt.common,  sizeof(*hdr));
    hdr->pt = RTCP_SDES;
    hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));

    /* Build RTCP SDES items */
    p = (pj_uint8_t*)hdr + sizeof(*hdr);
#define BUILD_SDES_ITEM(SDES_NAME, SDES_TYPE) \
    if (sdes->SDES_NAME.slen) { \
        *p++ = SDES_TYPE; \
        *p++ = (pj_uint8_t)sdes->SDES_NAME.slen; \
        pj_memcpy(p, sdes->SDES_NAME.ptr, sdes->SDES_NAME.slen); \
        p += sdes->SDES_NAME.slen; \
    }
    BUILD_SDES_ITEM(cname, RTCP_SDES_CNAME);
    BUILD_SDES_ITEM(name,  RTCP_SDES_NAME);
    BUILD_SDES_ITEM(email, RTCP_SDES_EMAIL);
    BUILD_SDES_ITEM(phone, RTCP_SDES_PHONE);
    BUILD_SDES_ITEM(loc,   RTCP_SDES_LOC);
    BUILD_SDES_ITEM(tool,  RTCP_SDES_TOOL);
    BUILD_SDES_ITEM(note,  RTCP_SDES_NOTE);
#undef BUILD_SDES_ITEM

    /* Null termination */
    *p++ = 0;

    /* Pad to 32bit */
    while ((p-(pj_uint8_t*)buf) % 4)
        *p++ = 0;

    /* Finally */
    pj_assert((int)len == p-(pj_uint8_t*)buf);
    *length = len;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_bye(pjmedia_rtcp_session *session,
                                                void *buf,
                                                pj_size_t *length,
                                                const pj_str_t *reason)
{
    pjmedia_rtcp_common *hdr;
    pj_uint8_t *p;
    pj_size_t len;

    PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL);

    /* Verify BYE reason length */
    if (reason && reason->slen > 255)
        return PJ_EINVAL;

    /* Verify buffer length */
    len = sizeof(*hdr);
    if (reason && reason->slen) len += reason->slen + 1;
    len = ((len+3)/4) * 4;
    if (len > *length)
        return PJ_ETOOSMALL;

    /* Build RTCP BYE header */
    hdr = (pjmedia_rtcp_common*)buf;
    pj_memcpy(hdr, &session->rtcp_sr_pkt.common,  sizeof(*hdr));
    hdr->pt = RTCP_BYE;
    hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));

    /* Write RTCP BYE reason */
    p = (pj_uint8_t*)hdr + sizeof(*hdr);
    if (reason && reason->slen) {
        *p++ = (pj_uint8_t)reason->slen;
        pj_memcpy(p, reason->ptr, reason->slen);
        p += reason->slen;
    }

    /* Pad to 32bit */
    while ((p-(pj_uint8_t*)buf) % 4)
        *p++ = 0;

    pj_assert((int)len == p-(pj_uint8_t*)buf);
    *length = len;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_rtcp_enable_xr( pjmedia_rtcp_session *sess, 
                                            pj_bool_t enable)
{
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)

    /* Check if request won't change anything */
    if (!(enable ^ sess->xr_enabled))
        return PJ_SUCCESS;

    if (!enable) {
        sess->xr_enabled = PJ_FALSE;
        return PJ_SUCCESS;
    }

    pjmedia_rtcp_xr_init(&sess->xr_session, sess, 0, 1);
    sess->xr_enabled = PJ_TRUE;

    return PJ_SUCCESS;

#else

    PJ_UNUSED_ARG(sess);
    PJ_UNUSED_ARG(enable);
    return PJ_ENOTSUP;

#endif
}
