/*
  Copyright (C) 2012-2013 Werner Dittmann

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*/

#ifndef _ZRTPSDESSTREAM_H_
#define _ZRTPSDESSTREAM_H_
/**
 * @file ZrtpSdesStream.h
 * @brief The ZRTP main engine
 * @defgroup GNU_ZRTP The GNU ZRTP C++ implementation
 * @{
 *
 * This class implements SDES and provides a simple to use API for applications.
 *
 * This SDES implementation currently supports only two SDES algorithms and it does
 * not support optional parameters such as lifetime or MKI parameters. Also session
 * parameters are not supported. Most applications that use SDES don't use these
 * optional parameters.
 *
 * It is not necessary to explicitly start the SDES stream. The class initiates
 * the SRTP after it created and parsed all necessary SDES crypto strings.
 *
 * Because SDES works together with the signaling protocol, for example SIP, it is
 * important to adhere to a defined flow. The following pseudo code snippet depicts
 * such a flow. Applications shall follow this flow.
 *
 *<pre>
 *
 *     Inviter                           Answerer
 *    (Offerer)
 *
 * ZrtpSdesStream inv;                 ZrtpSdesStream answ;
 *
 * // create/get own SDES data
 * inv.createSdes(...);
 * inv.getCryptoMixAttribute(...)
 *
 * // prepare SIP/SDP offer, send
 * // it to answerer
 *                                    // receive SIP/SDP, get
 *                                    // SDES data, parse/set it
 *                                    answ.setCryptoMixAttribute(...)
 *                                    answ.parseSdes(...)
 *
 *                                    // create/get own SDES data
 *                                    answ.getCryptoMixAttribute(...)
 *                                    answ.createSdes(...)
 *
 *                                    // prepare SIP/SDP answer,
 *                                    // send to offerer
 * // receive SIP/SDP answer, get
 * // SDES data, parse, set mix algo
 * // if availabe
 * inv.setCryptoMixAttribute(...)
 * inv.parseSdes(...)
 *
 * ...                                ...
 *
 * inv.outgoingRtp(...)
 *                                    answ.incomingRtp(...)
 *
 *                                    answ.outgoingRtp(...)
 * inv.incomingRtp(...)
 *</pre>
 *
 * To use SDES without the new crypto mix feature just do not use the crypto mix functions.
 * An application may always send crypto mix attributes. If the answerer does not support this
 * feature it does not send back a selected algorithm and the offerer cannot set an algorithm.
 * Thus the crypto mix feature is not used.
 * 
 * @author Werner Dittmann <Werner.Dittmann@t-online.de>
 */

#include <common/osSpecifics.h>

class CryptoContext;
class CryptoContextCtrl;

/*
 * These functions support 256 bit encryption algorithms.
 */
#define MAX_KEY_LEN           32
#define MAX_SALT_LEN          14
#define MAX_DIGEST_LENGTH     64

/**
 * Maximum length of a raw crypto string.
 */
#define MAX_CRYPT_STRING_LEN 200

class __EXPORT ZrtpSdesStream {

public:

    /**
     * Supported SDES crypto suites.
     */
    typedef enum {
        AES_CM_128_HMAC_SHA1_32 = 0,
        AES_CM_128_HMAC_SHA1_80
    } sdesSuites;

    /**
     * SDES stream state
     */
    typedef enum {
        STREAM_INITALIZED = 1,
        OUT_PROFILE_READY,
        IN_PROFILE_READY,
        SDES_SRTP_ACTIVE
    } sdesZrtpStates;

    typedef enum {
        MIX_NONE = 0,
        MIX_HMAC_SHA,
        MIX_MAC_SKEIN
    } sdesHmacTypeMix;

    /**
     * @brief Create and SDES/ZRTP stream.
     *
     * This method creates an SDES stream with capabilities to handle RTP,
     * RTCP, SRTP, and SRTCP packets.
     *
     * @param suite defines which crypto suite to use for this stream. The values are
     *              @c AES_CM_128_HMAC_SHA1_80 or @c AES_CM_128_HMAC_SHA1_32.
     */
    ZrtpSdesStream(const sdesSuites suite =AES_CM_128_HMAC_SHA1_32);

    ~ZrtpSdesStream();

    /**
     * @brief Close an SDES/ZRTP stream.
     *
     * Close the stream and return allocated memory to the pool.
     */
    void close();

    /**
     * @brief Creates an SDES crypto string for the SDES/ZRTP stream.
     *
     * Creates the crypto string that the application can use in the SDP fields of
     * SIP INVITE or SIP 200 OK.
     *
     * An INVITE-ing application shall call this function at the same point when
     * it calls the functions to get the @c zrtp-hash string and shall insert the
     * created crypto string into the SDP.
     *
     * An answering application shall call this function directly @b after it called
     * @c sdesZrtpStreamParseSdes. This usually at the same point when it gets the
     * @c zrtp-hash from the SDP parameters and forwards it to @c libzrtp. The
     * answering application's SRTP environment is now ready.
     *
     * @param cryptoString output buffer that receives the crypto  string in raw
     *                     format, without the any signaling prefix, for example
     *                     @c a=crypto:. The function terminates the crypto string
     *                     with a @c nul byte
     *
     * @param maxLen length of the crypto string buffer. On return it contains the
     *               actual length of the crypto string.
     *
     * @param sipInvite the inviter (offerer) must set this to @c true, the answerer must
     *                  set it to @c false.
     *
     * @return @c true if data could be created, @c false otherwise.
     */
    bool createSdes(char *cryptoString, size_t *maxLen, bool sipInvite);

    /**
     * @brief Parses an SDES crypto string for the SDES/ZRTP stream.
     *
     * Parses a SDES crypto string that the application received in a SIP INVITE
     * or SIP 200 OK.
     *
     * An INVITE-ing (offerer) application shall call this function right after it received
     * the 200 OK from the answering application and must call this function with the
     * @c sipInvite parameter set to @c true. The offerer's SRTP is now ready for use.
     *
     * The answering application calls this function after it received the INVITE and
     * extracted the crypto string from the SDP and must call this function with the
     * @c sipInvite parameter set to @c false.
     *
     * @param cryptoString the received crypto sting in raw format,
     *                     without any signaling prefix, for example @c a=crypto:
     *
     * @param length length of the crypto string to parse. If the length is
     *               @c zero then the function uses @c strlen to compute
     *               the length.
     *
     * @param sipInvite the inviter (offerer) must set this to @c true, the answerer must
     *                  set it to @c false.
     *
     * @return @c true if data could be created, @c false otherwise.
     */
    bool parseSdes(const char *cryptoString, size_t length, bool sipInvite);

    /**
     * @brief Get Crypto Mix attribute string
     *
     * The offerer calls this method to get a string of @b all supported crypto mix algorithms
     * and shall send this list to the answerer.
     *
     * The answerer calls this function only @b after it received the crypto mix string and @b after
     * calling @c setCryptoMixAttribute(...). The method returns only one (the selected)
     * crypto mix algorithm and the answerer must send this to the offerer, for example in 200 OK.
     *
     * @param algoNames buffer to store the nul terminated crypto mix algorithm names.
     *                  The buffer must be long enough to hold at least the name of the mandatory
     *                  algorithm HMAC-SHA-384.
     *
     * @param length length of buffer
     *
     * @return Length of algorithm names (excluding nul byte) or zero if crypto mix not supported or
     *         enabled.
     */
    int getCryptoMixAttribute(char *algoNames, size_t length);

    /**
     * @brief Set Crypto Mix attribute string
     *
     * The method checks if it the string contains an supported algorithm and selects one algorithm.
     *
     * The offerer calls this method @b after it received the selected algorithm in the answer.
     *
     * The answerer must call this method @b before it calls the @c getCryptoMixAttribute() method.
     *
     * @param algoNames buffer that contains the received crypto mix algorithm names.
     *                  The buffer must be nul terminated.
     *
     * @return @c false if none of the offered algorithms is supported.
     */
    bool setCryptoMixAttribute(const char *algoNames);

    /*
     * ******** Outgoing RTP/RTCP packet handling
     */
    /**
     * @brief Process an outgoing RTP packet
     *
     * This function processes an outgoing RTP packet. Depending on the state
     * the packet is either:
     *  - not encrypted if neither SDES nor ZRTP are active or supported by the
     *    other client. This is the standard case if the stream was just initialized.
     *  - encrypted with SDES provided key data. This is the case if the application
     *    called both @c sdesZrtpStreamCreateSdes and @c sdesZrtpStreamParseSdes
     *    functions to properly setup the SDES key data.
     *
     * @param packet the buffer that contains the RTP packet. After processing, the
     *               encrypted packet is stored in the same buffer. The buffer must
     *               big enough to hold the additional SRTP data, depending on the
     *               SRTP profile these are usually 4 - 20 bytes.
     *
     * @param length length of the RTP packet
     *
     * @param newLength to an integer that get the new length of the packet including SRTP data.
     *
     * @return
     *  - @c true if encryption is successful, app shall send packet to the recipient.
     *  - @c false if there was an error during encryption, don't send the packet.
     */
    bool outgoingRtp(uint8_t *packet, size_t length, size_t *newLength);

    /**
     * @brief Process an outgoing RTCP packet
     *
     * This function works in the same way as @c outgoingRtp.
     *
     * @param packet the buffer that contains the RTCP packet. After processing, the
     *               encrypted packet is stored in the same buffer. The buffer must
     *               big enough to hold the additional SRTP data, depending on the
     *               SRTP profile these are usually 8 - 20 bytes.
     *
     * @param length length of the RTP packet
     *
     * @param newLength to an integer that get the new length of the packet including SRTP data.
     *
     * @return
     *  - @c true if encryption is successful, app shall send packet to the recipient.
     *  - @c false if there was an error during encryption, don't send the packet.
     */
    bool outgoingRtcp(uint8_t *packet, size_t length, size_t *newLength);

    /*
     * ******** Incoming SRTP/SRTCP packet handling
     */
    /**
     * @brief Process an incoming RTP or SRTP packet
     *
     * This function processes an incoming RTP/SRTP packet. Depending on the state
     * the packet is either:
     *  - not decrypted if SDES is not active or supported by the
     *    other client. This is the standard case if the stream was just initialized.
     *  - decrypted with SDES provided key data. This is the case if the application
     *    called both @c sdesZrtpStreamCreateSdes and @c sdesZrtpStreamParseSdes
     *    functions to properly setup the SDES key data.
     *
     * @param packet the buffer that contains the RTP/SRTP packet. After processing,
     *               the decrypted packet is stored in the same buffer.
     *
     * @param length length of the RTP packet
     *
     * @param newLength to an integer that get the new length of the packet excluding SRTCP data.
     *
     * @return
     *       - 1: success,
     *       - -1: SRTP authentication failed,
     *       - -2: SRTP replay check failed
     */
    int incomingRtp(uint8_t *packet, size_t length, size_t *newLength);

    /**
     * @brief Process an incoming RTCP or SRTCP packet
     *
     * This function works in the same way as @c incomingRtp.
     *
     * @param packet the buffer that contains the RTCP/SRTCP packet. After processing,
     *               the decrypted packet is stored in the same buffer.
     *
     * @param length length of the RTCP packet
     *
     * @param newLength to an integer that get the new length of the packet excluding SRTCP data.
     *
     * @return
     *       - 1: success,
     *       - -1: SRTCP authentication failed,
     *       - -2: SRTCP replay check failed
     */
    int incomingSrtcp(uint8_t *packet, size_t length, size_t *newLength);

    /**
     * @brief Process an outgoing ZRTP packet.
     * 
     * Works like @c outgoingRtp, refer to that documentation.
     * 
     * @param packet the buffer that contains the ZRTP packet.
     *
     * @param length length of the ZRTP packet
     *
     * @param newLength to an integer that get the new length of the packet including SRTP data.
     *
     * @return
     *  - @c true if encryption is successful, app shall send packet to the recipient.
     *  - @c false if there was an error during encryption, don't send the packet.
     */
    bool outgoingZrtpTunnel(uint8_t *packet, size_t length, size_t *newLength);

    /**
     * @brief Process an incoming ZRTP packet
     *
     * Works like @c incomingRtp, refer to that documentation.
     *
     * @param packet the buffer that contains the ZRTP/SRTP packet. After processing,
     *               the decrypted packet is stored in the same buffer.
     *
     * @param length length of the RTP packet
     *
     * @param newLength to an integer that get the new length of the packet excluding SRTCP data.
     *
     * @return
     *       - 1: success,
     *       - -1: SRTP authentication failed,
     *       - -2: SRTP replay check failed
     */
    int incomingZrtpTunnel(uint8_t *packet, size_t length, size_t *newLength);

        /**
     * @brief Return state of SDES stream.
     *
     * @return state of stream.
     */
    sdesZrtpStates getState() {return state;}

    /**
     * @brief Return SDES crypto mixer HMAC type.
     *
     * @return HMAC type
     */
    sdesHmacTypeMix getHmacTypeMix() {return cryptoMixHashType;}

    /**
     * @brief Return name of active cipher algorithm.
     *
     * @return point to name of cipher algorithm.
     */
    const char* getCipher();

    /**
     * @brief Return name of active SRTP authentication algorithm.
     *
     * @return point to name of authentication algorithm.
     */
    const char* getAuthAlgo();


    /*
     * ******** Lower layer functions
     */
private:
    /**
     * @brief Create an SRTP crypto context and the according SDES crypto string.
     *
     * This lower layer method creates an SDES crypto string. It selects a valid
     * crypto suite, generates the key and salt data, converts these into base 64
     * and returns the crypto string in raw format without any signaling prefixes.
     *
     * The output string has the following format:
     * @verbatim
     * 1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj
     * @endverbatim
     *
     * Applications usually don't use this method directly. Applications shall
     * use the SDES stream functions.
     *
     * Depending on the crypto suite the overall length of the crypto string
     * is variable. For a normal AES_128_CM suite the minumum lenth is 73
     * characters, a AES_256_CM suite results in 97 characters (not counting
     * any signaling prefixes).
     *
     * @param cryptoString points to a char output buffer that receives the
     *                     crypto  string in the raw format, without the any
     *                     signaling prefix, for example @c a=crypto: in case
     *                     of SDP signaling. The function terminates the
     *                     crypto string with a @c nul byte
     *
     * @param maxLen points to an integer. On input this integer specifies the
     *               length of the output buffer. If @c maxLen is smaller than
     *               the resulting crypto string the function returns an error
     *               conde. On return the functions sets @c maxLen to the
     *               actual length of the resultig crypto string.
     *
     * @param tag the value of the @c tag field in the crypto string. The
     *            answerer must use this input to make sure that the tag value
     *            in the answer matches the value in the offer. See RFC 4568,
     *            section 5.1.2.
     *            If the tag value is @c -1 the function sets the tag to @c 1.
     *
     * @return @c true if data could be created, @c false
     *          otherwise.
     */
    bool createSdesProfile(char *cryptoString, size_t *maxLen);

    /**
     * @brief Parse and check an offered SDES crypto string and create SRTP crypto context.
     *
     * The method parses an offered SDES crypto string and checks if it is
     * valid. Next it checks if the string contains a supported crypto suite
     * and if the key and salt lengths match the selected crypto suite.
     *
     * Applications usually don't use this method directly. Applications shall
     * use the SDES stream functions.
     *
     * @b NOTE: This function does not support the optional parameters lifetime,
     * MKI, and session parameters. While it can parse liftime and MKI theiy are
     * not evaluated and used. If these parameters are used in the input crypto
     * string the function return @c false.
     *
     * @param cryptoString points to the crypto sting in raw format,
     *        without any signaling prefix, for example @c a=crypto: in case of
     *        SDP signaling.
     *
     * @param length length of the crypto string to parse. If the length is
     *        @c zero then the function uses @c strlen to compute the length.
     *
     * @param parsedSuite the function sets this to the @c sdesSuites enumerator of
     *        the parsed crypto suite. The answerer shall use this as input to
     *        @c createSdesProfile to make sure that it creates the same crypto suite.
     *        See RFC 4568, section 5.1.2
     *
     * @param tag the function sets this to the @c tag value of the parsed crypto
     *        string. The answerer must use this as input to @c createSdesProfile
     *        to make sure that it creates the correct tag in the crypto string.
     *        See RFC 4568, section 5.1.2
     *
     * @return @c true if checks were ok, @c false
     *          otherwise.
     */
    bool parseCreateSdesProfile(const char *cryptoString, size_t length, sdesSuites *parsedSuite, int32_t *tag);

    /**
     * @brief Create the SRTP contexts after all SDES creation and parsing is done.
     * 
     * @param sipInvite if this is set to @c true (not zero) then the method
     *                  computes the key data for the inviting SIP application (offerer) and
     *                  for the answerer otherwise.
     */
    void createSrtpContexts(bool sipInvite);

    /**
     * @brief Compute the mixed keys if SDES mixing attribute is set.
     *
     * The method takes the parsed or created SDES key material and computes the mixed keys and salt.
     * It replaces the existing key material with the new data.
     *
     * @param sipInvite if this is set to @c true (not zero) then the method
     *                  computes the key data for the inviting SIP application (offerer) and
     *                  for the answerer otherwise.
     */
    void computeMixedKeys(bool sipInvite);


    sdesZrtpStates state;
    sdesSuites     suite;
    int32_t        tag;
    CryptoContext     *recvSrtp;           //!< The SRTP context for this stream
    CryptoContextCtrl *recvSrtcp;          //!< The SRTCP context for this stream
    CryptoContext     *sendSrtp;           //!< The SRTP context for this stream
    CryptoContextCtrl *sendSrtcp;          //!< The SRTCP context for this stream
    uint32_t srtcpIndex;                   //!< the local SRTCP index

    CryptoContext     *recvZrtpTunnel;     //!< The SRTP context for sender ZRTP tunnel
    CryptoContext     *sendZrtpTunnel;     //!< The SRTP context for receiver ZRTP tunnel

    int32_t cryptoMixHashLength;
    sdesHmacTypeMix cryptoMixHashType;

    // Variables for crypto that this client creates and sends to the other client, filled during SDES create
    uint8_t localKeySalt[((MAX_KEY_LEN + MAX_SALT_LEN + 3)/4)*4];  //!< Some buffer for key and salt, multiple of 4
    int localKeyLenBytes;
    int localSaltLenBytes;
    int localCipher;
    int localAuthn;
    int localAuthKeyLen;
    int localTagLength;

    // Variables for crypto that this client receives from the other client, filled during SDES parse
    uint8_t remoteKeySalt[((MAX_KEY_LEN + MAX_SALT_LEN + 3)/4)*4];  //!< Some buffer for key and salt, multiple of 4
    int remoteKeyLenBytes;
    int remoteSaltLenBytes;
    int remoteCipher;
    int remoteAuthn;
    int remoteAuthKeyLen;
    int remoteTagLength;
};
#endif