/*
  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