// Copyright (C) 2001-2015 Federico Montesino Pouzols <fedemp@altern.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 Lesser General Public License
// along with GNU ccRTP.  If not, see <http://www.gnu.org/licenses/>.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// ccRTP.  If you copy code from other releases into a copy of GNU
// ccRTP, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU ccRTP, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

/**
 * @file cqueue.h
 *
 * @short Generic RTCP control queues.
 **/

#ifndef CCXX_RTP_CQUEUE_H_
#define CCXX_RTP_CQUEUE_H_

#include <ccrtp/ioqueue.h>
#include <ccrtp/CryptoContextCtrl.h>
#include <list>

NAMESPACE_COMMONCPP

/**
 * @defgroup cqueue Generic RTCP control queues.
 * @{
 **/

/**
 * @class QueueRTCPManager
 * @short Adds generic management of RTCP functions to an RTP data
 * queue.
 *
 * Extends an RTP data i/o queue adding management of RTCP functions:
 *
 * Provide feedback on the quality of the data distribution.
 *
 * Convey the CNAME (persistent transport-level identifier) for every
 * RTP source.
 *
 * Control the sending rate of RTCP packets
 *
 * Convey minimal control information about the participants
 *
 * This class implements generic RTCP behaviour (as specified in RFC
 * 1889/draft-ietf-avt-rtp-new) and may be specialized for specific
 * profiles (see AVPQueue) or particular RTCP extensions.
 *
 * @author Federico Montesino Pouzols <fedemp@altern.org>
 **/
class __EXPORT QueueRTCPManager : public RTPDataQueue,
    protected RTCPCompoundHandler
{
public:
    /**
     * Get the most recent sender report received from a
     * synchronization source.
     *
     * @param src Synchronization source of the sender info.
     * @return Most recent sender info received from src.
     * @retval NULL when no sender report has been received from
     * the specified source.
     **/
    RTCPSenderInfo* getMRSenderInfo(SyncSource& src);

    /**
     * Ask for the info in the most recent receiver report about
     * the local source received from the source given as
     * parameter.
     *
     * @param srcFrom Source of the receiver info.
     * @return most recent receiver info received from src.
     * @retval NULL when no receiver report has been received from
     * the specified source.
     */
    RTCPReceiverInfo* getMRReceiverInfo(SyncSource& srcFrom);

    /**
     * Set how much time the stack will wait before deleting a
     * synchronization source that has sent an RTCP BYE packet.
     *
     * @param delay delay in microseconds.
     *
     * @note The default delay is 1000000 microseconds
     **/
    void setLeavingDelay(microtimeout_t delay)
    { leavingDelay = delay; }

    /**
     * This method sets the maximum end to end delay allowed. If
     * the processing delay plus the trip time for a packet is
     * greater than the end to end delay, the packet is discarded,
     * and the application cannot get it.
     *
     * This is a way of setting an upper bound to the end to end
     * delay, computed as the elapsed time between the packet
     * timestamping at the sender side, and the picking of the
     * packet at the receiver side.
     *
     * @param t maximum end to end delay allowed. A value of 0
     * implies there is no limit and is the default
     */
    inline void
    setEnd2EndDelay(microtimeout_t t)
        { end2EndDelay = t; }

    inline microtimeout_t
    getDefaultEnd2EndDelay() const
    { return defaultEnd2EndDelay; }

    inline microtimeout_t
    getEnd2EndDelay() const
    { return end2EndDelay; }

    /**
     * Specify the fraction of the total control bandwith to be
     * dedicated to senders reports.
     *
     * @param fraction fraction of bandwidth, must be between 0 an 1.
     *
     * This method sets the fraction of the global control
     * bandwidth that will be dedicated to senders reports. Of
     * course, <code>1 - fraction</code> will be dedicated to
     * receivers reports.
     *
     * @see setControlBandwidth
     */
    inline void
    setSendersControlFraction(float fraction)
    { sendControlBwFract = fraction; recvControlBwFract = 1 - fraction;}

    /**
     * Manually set the minimum interval for sending RTP compound
     * packets
     *
     * @param interval minimum interval between RTCP packets, in
     * microseconds.
     *
     * @see computeRTCPInterval()
     **/
    void
    setMinRTCPInterval(microtimeout_t interval)
    { rtcpMinInterval = interval; }

    /**
     * Get the total number of RTCP packets sent until now
     **/
    inline uint32
    getSendRTCPPacketCount() const
    { return ctrlSendCount; }

    /**
     * Set ouput queue CryptoContext.
     *
     * The endQueue method (provided by RTPQueue) deletes all
     * registered CryptoContexts.
     *
     * @param cc Pointer to initialized CryptoContext.
     */
    void
    setOutQueueCryptoContextCtrl(CryptoContextCtrl* cc);

    /**
     * Remove output queue CryptoContext.
     *
     * The endQueue method (provided by RTPQueue) also deletes all
     * registered CryptoContexts.
     *
     * @param cc Pointer to initialized CryptoContext to remove.
     */
    void
    removeOutQueueCryptoContextCtrl(CryptoContextCtrl* cc);

    /**
     * Get an output queue CryptoContext identified by SSRC
     *
     * @param ssrc Request CryptoContext for this incoming SSRC
     * @return Pointer to CryptoContext of the SSRC of NULL if no context
     * available for this SSRC.
     */
    CryptoContextCtrl*
    getOutQueueCryptoContextCtrl(uint32 ssrc);


    /**
     * Set input queue CryptoContext.
     *
     * The endQueue method (provided by RTPQueue) deletes all
     * registered CryptoContexts.
     *
     * @param cc Pointer to initialized CryptoContext.
     */
    void
    setInQueueCryptoContextCtrl(CryptoContextCtrl* cc);

    /**
     * Remove input queue CryptoContext.
     *
     * The endQueue method (provided by RTPQueue) also deletes all
     * registered CryptoContexts.
     *
     * @param cc
     *     Pointer to initialized CryptoContext to remove. If pointer
     *     if <code>NULL</code> then delete the whole queue
     */
    void
    removeInQueueCryptoContextCtrl(CryptoContextCtrl* cc);

    /**
     * Get an input queue CryptoContext identified by SSRC
     *
     * @param ssrc Request CryptoContext for this incoming SSRC
     * @return Pointer to CryptoContext of the SSRC of NULL if no context
     * available for this SSRC.
     */
    CryptoContextCtrl*
    getInQueueCryptoContextCtrl(uint32 ssrc);

protected:
    QueueRTCPManager(uint32 size = RTPDataQueue::defaultMembersHashSize,
             RTPApplication& app = defaultApplication());

    QueueRTCPManager(uint32 ssrc,
             uint32 size = RTPDataQueue::defaultMembersHashSize,
             RTPApplication& app = defaultApplication());

    virtual
    ~QueueRTCPManager();

    const RTPApplication&
    getApplication()
    { return queueApplication; }

    inline void
    setControlBandwidth(float fraction)
    { controlBwFract = fraction; }

    float
    getControlBandwidth() const
    { return controlBwFract; }

    /**
     * Build and send RTCP packets following timing rules
     * (including the "timer reconsideration" algorithm).
     **/
    void
    controlTransmissionService();

    /**
     * Process incoming RTCP packets pending in the control
     * reception socket.
     **/
    void
    controlReceptionService();

    /**
     * Appy collision and loop detection and correction algorithm
     * when receiving RTCP packets. Follows section 8.2 in
     * draft-ietf-avp-rtp-new.
     *
     * @param sourceLink link to the source object.
     * @param is_new whether the source has been just recorded.
     * @param na RTCP packet network address.
     * @param tp RTCP packet source transport port.
     *
     * @return whether the packet must not be discarded.
     **/
    bool checkSSRCInRTCPPkt(SyncSourceLink& sourceLink, bool is_new,
                InetAddress& na, tpport_t tp);

    void
    endQueueRTCPManager();

    /**
     * Plug-in for processing (acquire information carried in) an
     * incoming RTCP Sender Report. The default implementation in
     * this class only processes the sender information and the
     * receiver report blocks about the local source.
     *
     * @param source Synchronization source this report comes from.
     * @param SR Sender report structure.
     * @param blocks Number of report blocks in the packet.
     **/
    virtual void
    onGotSR(SyncSource& source, SendReport& SR, uint8 blocks);

    /**
     * Plug-in for processing (acquire information carried in) an
     * incoming RTCP Receiver Report. The default implementation
     * in this class only processes the receiver report blocks
     * about the local source.
     *
     * @param source Synchronization source this report comes from.
     * @param RR Receiver report structure
     * @param blocks Number of report blocks in the packet
     **/
    virtual void
    onGotRR(SyncSource& source, RecvReport& RR, uint8 blocks);

    /**
     * @param source Synchronization source of SDES RTCP packet.
     * @param pkt SDES RTCP packet received.
     **/
    bool
    onGotSDES(SyncSource& source, RTCPPacket& pkt);

    /**
     * Plug-in for handling of SDES chunks.
     *
     * @param source Synchronization source of SDES chunk.
     * @param chunk SDES chunk structure.
     * @param len Length of chunk, in octets.
     *
     * @return whether there was a CNAME.
     **/
    virtual bool
    onGotSDESChunk(SyncSource& source, SDESChunk& chunk, size_t len);

    /**
     * Plug-in for handling of APP (application specific) RTCP
     * packets.
     *
     * @param - Synchronization source of this packet.
     * @param - RTCP APP packet struct.
     * @param - Length of the app data packet, including ssrc.
     * name and app. specific data.
     **/
    inline virtual void
    onGotAPP(SyncSource&, RTCPCompoundHandler::APPPacket&,
         size_t)
    { return; }

    inline timeval
    getRTCPCheckInterval()
    { return rtcpCheckInterval; }

    /**
     * Get the number of data packets sent at the time the last SR
     * was generated.
     **/
    uint32
    getLastSendPacketCount() const
    { return lastSendPacketCount; }

    /**
     * @param n Number of members.
     **/
    inline void
    setPrevMembersNum(uint32 n)
    { reconsInfo.rtcpPMembers = n; }

    inline uint32
    getPrevMembersCount() const
    { return reconsInfo.rtcpPMembers; }

    /**
     * This method is used to send an RTCP BYE packet.  An RTCP
     * BYE packet is sent when one of the the following
     * circumstances occur:
     * - when leaving the session
     * - when we have detected that another synchronization source
     * in the same session is using the same SSRC identifier as
     * us.
     *
     * Try to post a BYE message. It will send a BYE packet as
     * long as at least one RTP or RTCP packet has been sent
     * before. If the number of members in the session is more
     * than 50, the algorithm described in section 6.3.7 of
     * RFC 3550 is applied in order to avoid a flood
     * of BYE messages.
     *
     * @param reason reason to specify in the BYE packet.
     **/
    size_t
    dispatchBYE(const std::string& reason);

    size_t
    sendControlToDestinations(unsigned char* buffer, size_t len);

private:
    QueueRTCPManager(const QueueRTCPManager &o);

    QueueRTCPManager&
    operator=(const QueueRTCPManager &o);

    /**
     * Posting of RTCP messages.
     *
     * @return std::size_t number of octets sent
     */
    size_t
    dispatchControlPacket();

    /**
     * For picking up incoming RTCP packets if they are waiting. A
     * timeout for the maximum interval since the last RTCP packet
     * had been received is also returned. This is checked every
     * rtcpCheckInterval seconds.
     *
     * This method decomposes all incoming RTCP compound packets
     * pending in the control socket and processes each RTCP
     * packet.
     *
     **/
    void
    takeInControlPacket();

    /**
     * Computes the interval for sending RTCP compound packets,
     * based on the average size of RTCP packets sent and
     * received, and the current estimated number of participants
     * in the session.
     *
     * @note This currently follows the rules in section 6 of
     *       RFC 3550
     * @todo make it more flexible as recommended in the draft. For now,
     * we have setMinRTCPInterval.
     *
     * @return interval for sending RTCP compound packets
     **/
    virtual timeval
    computeRTCPInterval();

    /**
     * Choose which should be the type of the next SDES item
     * sent. This method is called when packing SDES chunks in a
     * new RTCP packet.
     *
     * @return type of the next SDES item to be sent
     **/
    virtual SDESItemType
    scheduleSDESItem();

    /**
     * Plug-in for SSRC collision handling.
     *
     * @param - previously identified source.
     **/
    inline virtual void
    onSSRCCollision(const SyncSource&)
    { }

    /**
     * Virtual reimplemented from RTPDataQueue
     **/
    virtual bool
    end2EndDelayed(IncomingRTPPktLink& p);

    /**
     * Plug-in for processing of SR/RR RTCP packet
     * profile-specific extensions (third part of SR reports or
     * second part of RR reports).
     *
     * @param - Content of the profile extension.
     * @param - Length of the extension, in octets.
     **/
    inline virtual void
    onGotRRSRExtension(unsigned char*, size_t)
    { return; }

    /**
     * A plugin point for goodbye message.  Called when a BYE RTCP
     * packet has been received from a valid synchronization
     * source.
     *
     * @param - synchronization source from what a BYE RTCP
     * packet has been just received.
     * @param - reason string the source has provided.
     **/
    inline virtual void
    onGotGoodbye(const SyncSource&, const std::string&)
    { return; }

    /**
     * Process a BYE packet just received and identified.
     *
     * @param pkt previously identified RTCP BYE packet
     * @param pointer octet number in the RTCP reception buffer
     *        where the packet is stored
     * @param len total length of the compount RTCP packet the BYE
     *        packet to process is contained
     *
     * @bug if the bye packet contains several SSRCs,
     *      eventSourceLeaving is only called for the last one
     **/
    bool
    getBYE(RTCPPacket &pkt, size_t &pointer, size_t len);

    /**
     * @return number of Report Blocks packed
     **/
    uint8
    packReportBlocks(RRBlock* blocks, uint16& len, uint16& available);

    /**
     * Builds an SDES RTCP packet. Each chunk is built following
     * appendix A.4 in draft-ietf-avt-rtp-new.
     *
     * @param len provisionary length of the RTCP compound packet
     *
     * @return
     **/
    void
    packSDES(uint16& len);

    /**
     * This must be called in order to update the average RTCP compound
     * packet size estimation when:
     *
     * a compoung RTCP packet is received (6.3.3).
     *
     * a compound RTCP packet is transmitted (6.3.6).
     *
     * @param len length in octets of the compound RTCP packet
     * just received/transmitted.
     **/
    void
    updateAvgRTCPSize(size_t len);

    /**
     * Apply reverse reconsideration adjustment to timing
     * parameters when receiving BYE packets and not waiting to
     * send a BYE.
     **/
    void
    reverseReconsideration();

    bool
    timerReconsideration();

    /**
     * Purge sources that do not seem active any more.
     *
     * @note MUST be perform at least every RTCP transmission
     *       interval
     * @todo implement it. It may be dangerous and anyway should
     * be optional.
     **/
    void
    expireSSRCs();

    /**
     * To be executed when whe are leaving the session.
     **/
    void
    getOnlyBye();

    /**
     * Set item value from a string without null termination (as
     * it is transported in RTCP packets).
     **/
    void
    setSDESItem(Participant* part, SDESItemType type,
            const char* const value, size_t len);

    /**
     * Set PRIV item previx value from a string without null
     * termination (as it is transported in RTCP packets).
     **/
    void
    setPRIVPrefix(Participant* part, const char* const value, size_t len);

    /**
     * For certain control calculations in RTCP, the size of the
     * underlying network and transport protocols is needed. This
     * method provides the size of the network level header for
     * the default case of IP (20 octets). In case other protocol
     * with different header size is used, this method should be
     * redefined in a new specialized class.
     *
     * @return size of the headers of the network level. IP (20) by
     *        default.
     **/
    inline virtual uint16
    networkHeaderSize()
    { return 20; }

    /**
     * For certain control calculations in RTCP, the size of the
     * underlying network and transport protocols is needed. This
     * method provides the size of the transport level header for
     * the default case of UDP (8 octets). In case other protocol
     * with different header size is used, this method should be
     * redefined in a new specialized class.
     *
     * return size of the headers of the transport level. UDP (8)
     *        by default
     **/
    inline virtual uint16
    transportHeaderSize()
    { return 8; }


    int32 protect(uint8* pkt, size_t len, CryptoContextCtrl* cc);
    int32 unprotect(uint8* pkt, size_t len, CryptoContextCtrl* cc);
    
    
    SDESItemType
    nextSDESType(SDESItemType t);

    virtual size_t
    sendControl(const unsigned char* const buffer, size_t len) = 0;

    virtual size_t
    recvControl(unsigned char* buffer, size_t len,
            InetHostAddress& na, tpport_t& tp) = 0;

    virtual bool
    isPendingControl(microtimeout_t timeout) = 0;

    // whether the RTCP service is active
    volatile bool controlServiceActive;
    float controlBwFract, sendControlBwFract, recvControlBwFract;
    // number of RTCP packets sent since the beginning
    uint32 ctrlSendCount;

    // Network + transport headers size, typically size of IP +
    // UDP headers
    uint16 lowerHeadersSize;

    SDESItemType nextScheduledSDESItem;
    static const SDESItemType firstSchedulable;
    static const SDESItemType lastSchedulable;

    // state for rtcp timing. Its meaning is defined in
    // draft-ietf-avt-rtp-new, 6.3.

    // Parameters for timer reconsideration algorithm
    struct {
        timeval rtcpTp, rtcpTc, rtcpTn;
        uint32 rtcpPMembers;
    } reconsInfo;
    bool rtcpWeSent;
    uint16 rtcpAvgSize;
    bool rtcpInitial;
    // last time we checked if there were incoming RTCP packets
    timeval rtcpLastCheck;
    // interval to check if there are incoming RTCP packets
    timeval rtcpCheckInterval;
    // next time to check if there are incoming RTCP packets
    timeval rtcpNextCheck;

    // number of RTP data packets sent at the time of the last
    // RTCP packet transmission.
    uint32 lastSendPacketCount;

    // minimum interval for transmission of RTCP packets. The
    // result of computeRTCPInterval will always be >= (times a
    // random number between 0.5 and 1.5).
    microtimeout_t rtcpMinInterval;

    microtimeout_t leavingDelay;
    static const microtimeout_t defaultEnd2EndDelay;
    // Maximum delay allowed between packet timestamping and
    // packet availability for the application.
    microtimeout_t end2EndDelay;
    // Application this queue is bound to.
    RTPApplication& queueApplication;

    // an empty RTPData
    static const uint16 TIMEOUT_MULTIPLIER;
    static const double RECONSIDERATION_COMPENSATION;
    
    mutable Mutex outCryptoMutex;
    std::list<CryptoContextCtrl *> outCryptoContexts;
    uint32 srtcpIndex;

    mutable Mutex inCryptoMutex;
    std::list<CryptoContextCtrl *> inCryptoContexts;

};

/**
 * This class, an RTP/RTCP queue, adds audio/video profile (AVP)
 * specific methods to the generic RTCP service queue
 * (QueueRTCPManager).
 *
 * @author Federico Montesino Pouzols <fedemp@altern.org>
 **/
class __EXPORT AVPQueue : public QueueRTCPManager
{
public:
    /**
     * Specify the bandwith available for control (RTCP) packets.
     * This method sets the global control bandwidth for both
     * sender and receiver reports. As recommended in RFC 1890,
     * 1/4 of the total control bandwidth is dedicated to senders,
     * whereas 3/4 are dedicated to receivers.
     *
     * @param fraction fraction of the session bandwidth, between
     * 0 and 1
     *
     * @note If this method is not called, it is assumed that the
     * control bandwidth is equal to 5% of the session
     * bandwidth. Note also that the RFC RECOMMENDS the 5%.
     *
     **/
    inline void
    setControlBandwidth(float fraction)
    { QueueRTCPManager::setControlBandwidth(fraction); }

    float
    getControlBandwidth() const
    { return QueueRTCPManager::getControlBandwidth(); }

protected:
    AVPQueue(uint32 size = RTPDataQueue::defaultMembersHashSize,
         RTPApplication& app = defaultApplication()) :
        QueueRTCPManager(size,app)
    { }

    /**
     * Local SSRC is given instead of computed by the queue.
     **/
    AVPQueue(uint32 ssrc, uint32 size =
         RTPDataQueue::defaultMembersHashSize,
         RTPApplication& app = defaultApplication()) :
        QueueRTCPManager(ssrc,size,app)
    { }
    inline virtual ~AVPQueue()
    { }
};

/** @}*/ // cqueue

END_NAMESPACE

#endif  //CCXX_RTP_CQUEUE_H_

/** EMACS **
 * Local variables:
 * mode: c++
 * c-basic-offset: 8
 * End:
 */
