/*
  Copyright 2021 Grégory Soutadé

  This file is part of libgourou.

  libgourou 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.

  libgourou 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _DRMPROCESSORCLIENT_H_
#define _DRMPROCESSORCLIENT_H_

#include <string>
#include <bytearray.h>

namespace gourou
{
    /**
     * @brief All fucntions that must be implemented by a client
     * This allow libgourou to have only few external libraries dependencies
     * and improve code portability
     */

    class DigestInterface
    {
    public:
        /**
         * @brief Create a digest handler
         *
         * @param digestName      Digest name to instanciate
         */
        virtual void* createDigest(const std::string& digestName) = 0;

        /**
         * @brief Update digest engine with new data
         *
         * @param handler         Digest handler
         * @param data            Data to digest
         * @param length          Length of data
         */
        virtual void digestUpdate(void* handler, unsigned char* data, unsigned int length) = 0;

        /**
         * @brief Finalize digest with remained buffered data and destroy handler
         *
         * @param handler         Digest handler
         * @param digestOut       Digest result (buffer must be pre allocated with right size)
         */
        virtual void digestFinalize(void* handler, unsigned char* digestOut) = 0;

        /**
         * @brief Global digest function
         *
         * @param digestName      Digest name to instanciate
         * @param data            Data to digest
         * @param length          Length of data
         * @param digestOut       Digest result (buffer must be pre allocated with right size)
         */
        virtual void digest(const std::string& digestName, unsigned char* data, unsigned int length, unsigned char* digestOut) = 0;
    };

    class RandomInterface
    {
    public:
        /**
         * @brief Generate random bytes
         *
         * @param bytesOut        Buffer to fill with random bytes
         * @param length          Length of bytesOut
         */
        virtual void randBytes(unsigned char* bytesOut, unsigned int length) = 0;
    };

    class HTTPInterface
    {
    public:

        /**
         * @brief Send HTTP (GET or POST) request
         *
         * @param URL             HTTP URL
         * @param POSTData        POST data if needed, if not set, a GET request is done
         * @param contentType     Optional content type of POST Data
         * @param responseHeaders Optional Response headers of HTTP request
         * @param fd              Optional file descriptor to write request result
         * @param resume          false if target file should be truncated, true to try resume download (works only in combination with a valid fd)
         *
         * @return data of HTTP response
         */
        virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false) = 0;
    };

    class RSAInterface
    {
    public:
        enum RSA_KEY_TYPE {
            RSA_KEY_PKCS12 = 0,
            RSA_KEY_PKCS8,
            RSA_KEY_X509
        };

        /**
         * @brief Encrypt data with RSA private key. Data is padded using PKCS1.5
         *
         * @param RSAKey         RSA key in binary form
         * @param RSAKeyLength   RSA key length
         * @param keyType        Key type
         * @param password       Optional password for RSA PKCS12 certificate
         * @param data           Data to encrypt
         * @param dataLength     Data length
         * @param res            Encryption result (pre allocated buffer)
         */
        virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
                                       const RSA_KEY_TYPE keyType, const std::string& password,
                                       const unsigned char* data, unsigned dataLength,
                                       unsigned char* res) = 0;

        /**
         * @brief Decrypt data with RSA private key. Data is padded using PKCS1.5
         *
         * @param RSAKey         RSA key in binary form
         * @param RSAKeyLength   RSA key length
         * @param keyType        Key type
         * @param password       Optional password for RSA PKCS12 certificate
         * @param data           Data to encrypt
         * @param dataLength     Data length
         * @param res            Encryption result (pre allocated buffer)
         */
        virtual void RSAPrivateDecrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
                                       const RSA_KEY_TYPE keyType, const std::string& password,
                                       const unsigned char* data, unsigned dataLength,
                                       unsigned char* res) = 0;

        /**
         * @brief Encrypt data with RSA public key. Data is padded using PKCS1.5
         *
         * @param RSAKey         RSA key in binary form
         * @param RSAKeyLength   RSA key length
         * @param keyType        Key type
         * @param password       Optional password for RSA PKCS12 certificate
         * @param data           Data to encrypt
         * @param dataLength     Data length
         * @param res            Encryption result (pre allocated buffer)
         */
        virtual void RSAPublicEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
                                      const RSA_KEY_TYPE keyType,
                                      const unsigned char* data, unsigned dataLength,
                                      unsigned char* res) = 0;

        /**
         * @brief Generate RSA key. Expnonent is fixed (65537 / 0x10001)
         *
         * @param keyLengthBits  Length of key (in bits) to generate
         *
         * @return generatedKey
         */
        virtual void* generateRSAKey(int keyLengthBits) = 0;

        /**
         * @brief Destroy key previously generated
         *
         * @param handler        Key to destroy
         */
        virtual void destroyRSAHandler(void* handler) = 0;

        /**
         * @brief Extract public key (big number) from RSA handler
         *
         * @param handler        RSA handler (generated key)
         * @param keyOut         Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed)
         * @param keyOutLength   Length of result
         */
        virtual void extractRSAPublicKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;

        /**
         * @brief Extract private key (big number) from RSA handler
         *
         * @param handler        RSA handler (generated key)
         * @param keyOut         Pre allocated buffer (if *keyOut != 0). If *keyOut is 0, memory is internally allocated (must be freed)
         * @param keyOutLength   Length of result
         */
        virtual void extractRSAPrivateKey(void* handler, unsigned char** keyOut, unsigned int* keyOutLength) = 0;

        /**
         * @brief Extract certificate from PKCS12 blob
         *
         * @param RSAKey         RSA key in binary form
         * @param RSAKeyLength   RSA key length
         * @param keyType        Key type
         * @param password       Optional password for RSA PKCS12 certificate
         * @param certOut        Result certificate
         * @param certOutLength  Result certificate length
         */
        virtual void extractCertificate(const unsigned char* RSAKey, unsigned int RSAKeyLength,
                                        const RSA_KEY_TYPE keyType, const std::string& password,
                                        unsigned char** certOut, unsigned int* certOutLength) = 0;
    };

    class CryptoInterface
    {
    public:
        enum CRYPTO_ALGO {
            ALGO_AES=0,
            ALGO_RC4
        };

        enum CHAINING_MODE {
            CHAIN_ECB=0,
            CHAIN_CBC
        };

        /**
         * @brief Do encryption. If length of data is not multiple of block size, PKCS#5 padding is done
         *
         * @param algo           Algorithm to use
         * @param chaining       Chaining mode
         * @param key            AES key
         * @param keyLength      AES key length
         * @param iv             IV key
         * @param ivLength       IV key length
         * @param dataIn         Data to encrypt
         * @param dataInLength   Data length
         * @param dataOut        Encrypted data
         * @param dataOutLength  Length of encrypted data
         */
        virtual void encrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
                             const unsigned char* key, unsigned int keyLength,
                             const unsigned char* iv, unsigned int ivLength,
                             const unsigned char* dataIn, unsigned int dataInLength,
                             unsigned char* dataOut, unsigned int* dataOutLength) = 0;

        /**
         * @brief Init encryption
         *
         * @param chaining       Chaining mode
         * @param key            Key
         * @param keyLength      Key length
         * @param iv             Optional IV key
         * @param ivLength       Optional IV key length
         *
         * @return AES handler
         */
        virtual void* encryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
                                  const unsigned char* key, unsigned int keyLength,
                                  const unsigned char* iv=0, unsigned int ivLength=0) = 0;

        /**
         * @brief Encrypt data
         *
         * @param handler        Crypto handler
         * @param dataIn         Data to encrypt
         * @param dataInLength   Data length
         * @param dataOut        Encrypted data
         * @param dataOutLength  Length of encrypted data
         */
        virtual void encryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
                                   unsigned char* dataOut, unsigned int* dataOutLength) = 0;

        /**
         * @brief Finalize encryption (pad and encrypt last block if needed)
         * Destroy handler at the end
         *
         * @param handler        Crypto handler
         * @param dataOut        Last block of encrypted data
         * @param dataOutLength  Length of encrypted data
         */
        virtual void encryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;

        /**
         * @brief Do decryption. If length of data is not multiple of block size, PKCS#5 padding is done
         *
         * @param algo           Algorithm to use
         * @param chaining       Chaining mode
         * @param key            AES key
         * @param keyLength      AES key length
         * @param iv             IV key
         * @param ivLength       IV key length
         * @param dataIn         Data to encrypt
         * @param dataInLength   Data length
         * @param dataOut        Encrypted data
         * @param dataOutLength  Length of encrypted data
         */
        virtual void decrypt(CRYPTO_ALGO algo, CHAINING_MODE chaining,
                             const unsigned char* key, unsigned int keyLength,
                             const unsigned char* iv, unsigned int ivLength,
                             const unsigned char* dataIn, unsigned int dataInLength,
                             unsigned char* dataOut, unsigned int* dataOutLength) = 0;

        /**
         * @brief Init decryption
         *
         * @param chaining       Chaining mode
         * @param key            Key
         * @param keyLength      Key length
         * @param iv             IV key
         * @param ivLength       IV key length
         *
         * @return AES handler
         */
        virtual void* decryptInit(CRYPTO_ALGO algo, CHAINING_MODE chaining,
                                  const unsigned char* key, unsigned int keyLength,
                                  const unsigned char* iv=0, unsigned int ivLength=0) = 0;

        /**
         * @brief Decrypt data
         *
         * @param handler        Crypto handler
         * @param dataIn         Data to decrypt
         * @param dataInLength   Data length
         * @param dataOut        Decrypted data
         * @param dataOutLength  Length of decrypted data
         */
        virtual void decryptUpdate(void* handler, const unsigned char* dataIn, unsigned int dataInLength,
                                   unsigned char* dataOut, unsigned int* dataOutLength) = 0;
        /**
         * @brief Finalize decryption (decrypt last block and remove padding if it is set).
         * Destroy handler at the end
         *
         * @param handler        Crypto handler
         * @param dataOut        Last block decrypted data
         * @param dataOutLength  Length of decrypted data
         */
        virtual void decryptFinalize(void* handler, unsigned char* dataOut, unsigned int* dataOutLength) = 0;
    };


    class ZIPInterface
    {
    public:
        /**
         * @brief Open a zip file and return an handler
         *
         * @param path           Path of zip file
         *
         * @return ZIP file handler
         */
        virtual void* zipOpen(const std::string& path) = 0;

        /**
         * @brief Read zip internal file
         *
         * @param handler        ZIP file handler
         * @param path           Internal path inside zip file
         * @param result         Result buffer
         * @param decompress     If false, don't decompress read data
         */
        virtual void zipReadFile(void* handler, const std::string& path, ByteArray& result, bool decompress=true) = 0;

        /**
         * @brief Write zip internal file
         *
         * @param handler        ZIP file handler
         * @param path           Internal path inside zip file
         * @param content        File content
         */
        virtual void zipWriteFile(void* handler, const std::string& path, ByteArray& content) = 0;

        /**
         * @brief Delete zip internal file
         *
         * @param handler        ZIP file handler
         * @param path           Internal path inside zip file
         */
        virtual void zipDeleteFile(void* handler, const std::string& path) = 0;

        /**
         * @brief Close ZIP file handler
         *
         * @param handler        ZIP file handler
         */
        virtual void zipClose(void* handler) = 0;

        /**
         * @brief Inflate algorithm
         *
         * @param data           Data to inflate
         * @param result         Zipped data
         * @param wbits          Window bits value for libz
         */
        virtual void inflate(gourou::ByteArray& data, gourou::ByteArray& result,
                             int wbits=-15) = 0;

        /**
         * @brief Deflate algorithm
         *
         * @param data           Data to deflate
         * @param result         Unzipped data
         * @param wbits          Window bits value for libz
         * @param compressionLevel Compression level for libz
         */
        virtual void deflate(gourou::ByteArray& data, gourou::ByteArray& result,
                             int wbits=-15, int compressionLevel=8) = 0;
    };

    class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \
                              public RSAInterface, public CryptoInterface, public ZIPInterface
    {};
}
#endif
