/*
 * Copyright (c) 2016-2020 Belledonne Communications SARL.
 *
 * This file is part of bctoolbox.
 *
 * 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 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 BCTBX_VFS_ENCRYPTED_HH
#define BCTBX_VFS_ENCRYPTED_HH

#include "bctoolbox/exception.hh"
#include "bctoolbox/port.h"
#include "bctoolbox/vfs.h"
#include <functional>
#include <memory>
#include <vector>

namespace bctoolbox {

/**
 * @brief This dedicated exception inherits \ref BctoolboxException.
 *
 */
class EvfsException : public BctbxException {
public:
	EvfsException() = default;
	EvfsException(const std::string &message) : BctbxException(message) {
	}
	EvfsException(const char *message) : BctbxException(message) {
	}
	virtual ~EvfsException() throw() {
	}
	EvfsException(const EvfsException &other) : BctbxException(other) {
	}

	template <typename T>
	EvfsException &operator<<(const T &val) {
		BctbxException::operator<<(val);
		return *this;
	}
};

#define EVFS_EXCEPTION EvfsException() << " " << __FILE__ << ":" << __LINE__ << " "

/**
 * Virtual File sytem provided
 */
extern BCTBX_PUBLIC bctbx_vfs_t bcEncryptedVfs;

/**
 * Provided encryption suites
 */
enum class EncryptionSuite : uint16_t {
	unset = 0, /**< no encryption suite selected */
	dummy = 1, /**< a test suite, do not use other than for test */
	aes256gcm128_sha256 =
	    2,         /**< This module encrypts blocks with AES256GCM and authenticate header using HMAC-sha256 */
	plain = 0xFFFF /**< no encryption activated, direct use of standard file system API */
};

/**
 * Helper function returning a string holding the suite name
 * @param[in]	suite	the encryption suite as a c++ enum
 * @return the suite name in a readable string
 */
const std::string encryptionSuiteString(const EncryptionSuite suite) noexcept;

/* complete declaration follows, we need this one to define the callback type */
class VfsEncryption;

/**
 * Define a function prototype to be called at each file opening.
 * This function is a static class property, used to retrieve secretMaterial to encrypt/decrypt the file
 */
using EncryptedVfsOpenCb = std::function<void(VfsEncryption &settings)>;

// forward declare this type, store all the encryption data and functions
class VfsEncryptionModule;

/** Store in the bctbx_vfs_file_t userData field an object specific to encryption */
class VfsEncryption {
	/* Class properties and method */
private:
	static EncryptedVfsOpenCb s_openCallback; /**< a class callback to get secret material at file opening. Implemented
	                                             as static as it is called by constructor */
public:
	/**
	 * at file opening a callback ask for crypto material, it is class property, set it using this class method
	 */
	static void openCallbackSet(const EncryptedVfsOpenCb &cb) noexcept;
	static EncryptedVfsOpenCb openCallbackGet() noexcept;

	/* Object properties and methods */
private:
	uint16_t mVersionNumber; /**< version number of the encryption vfs */
	size_t mChunkSize;       /**< size of the file chunks payload in bytes : default is 4kB */
	size_t rawChunkSizeGet()
	    const noexcept; /** return the size of a chunk including its encryption header, as stored in the raw file */
	std::shared_ptr<VfsEncryptionModule>
	    m_module; /**< one of the available encryption module : if nullptr, assume we deal with regular plain file */
	size_t mHeaderExtensionSize; /**< header extension size */
	const std::string mFilename; /**< the filename as given to the open function */
	uint64_t mFileSize;          /**< size of the plaintext file */

	uint64_t rawFileSizeGet() const noexcept; /**< return the size of the raw file */
	uint32_t
	getChunkIndex(uint64_t offset) const noexcept; /**< return the chunk index where to find the given offset */
	size_t getChunkOffset(
	    uint32_t index) const noexcept; /**< return the offset in the actual file of the begining of the chunk */
	std::vector<uint8_t> r_header;      /**< a cache of the header - without the encryption module data */
	/** flags use to communicate during differents functions involved at file opening **/
	bool mEncryptExistingPlainFile; /**< when opening a plain file, if the callback set an encryption suite and key
	                                   material : migrate the file */
	bool mIntegrityFullCheck;       /**< if the file size given in the header metadata is incorrect, full check the file
	                                   integrity and revrite header */
	int mAccessMode;                /**< the flags used to open the file, filtered on the access mode */

	/**
	 * Parse the header of an encrypted file, check everything seems correct
	 * may perform integrity checking if the encryption module provides it
	 *
	 * @throw a EvfsException if something goes wrong
	 */
	void parseHeader();
	/**
	 * Write the encrypted file header to the actual file
	 * Create the needed structures if the file is actually empty
	 * @param[in] fp	if a file pointer is given write to this one, otherwise use the pFileStp property
	 *
	 * @throw a EvfsException if something goes wrong
	 **/
	void writeHeader(bctbx_vfs_file_t *fp = nullptr);

public:
	bctbx_vfs_file_t *pFileStd; /**< The encrypted vfs encapsulate a standard one */

	VfsEncryption(bctbx_vfs_file_t *stdFp, const std::string &filename, int openFlags, int accessMode);
	~VfsEncryption();

	/***
	 * Plain version of the file related accessors
	 ***/
	/**
	 * @return the size of the plain text file
	 */
	int64_t fileSizeGet() const noexcept;

	/* Read from file at given offset the requested size */
	std::vector<uint8_t> read(size_t offset, size_t count) const;

	/* write to file at given offset the requested size */
	size_t write(const std::vector<uint8_t> &plainData, size_t offset);

	/* Truncate the file to the given size, if given size is greater than current, pad with 0 */
	void truncate(const uint64_t size);

	/**
	 *  Get the filename
	 *  @return a string with the filename as given to the open function
	 */
	std::string filenameGet() const noexcept;

	/***
	 * Encryption related API
	 ***/
	/**
	 * Set an encryption suite.
	 * When called at file creation, select the module to use for this file
	 * When called at the opening of an existing file, check it is the suite used at file creation, throw an exception
	 * if they differs
	 */
	void encryptionSuiteSet(const EncryptionSuite);

	/**
	 * Set the secret Material in the encryption module
	 * This function cannot be called if a encryption suite was not set.
	 */
	void secretMaterialSet(const std::vector<uint8_t> &secretMaterial);

	/**
	 * Returns the encryption suite used for this file
	 * Can be return unset if the file is being created
	 */
	EncryptionSuite encryptionSuiteGet() const noexcept;

	/**
	 * Returns the size of chunks in which the file is divided for encryption
	 */
	size_t chunkSizeGet() const noexcept;
	/**
	 * Set the size, in bytes, of chunks in which the file is divided for encryption
	 * This size must be a multiple of 16, accepted values in range [16, (2^16-1)*16].
	 * If the size is set on an existing file and differs from previous setting, an exception is generated
	 * Default chunk size at file creation is 4kB.
	 * A file holds a maximum of 2^32-1 chunks. 16 bytes chunks - not recommended smallest admissible value - limit the
	 * file size to 64GB
	 */
	void chunkSizeSet(const size_t size);

	/**
	 * Get raw header: encryption module might check integrity on header
	 * This function returns the raw header, without the encryption module part
	 */
	const std::vector<uint8_t> &rawHeaderGet() const noexcept;
};

} // namespace bctoolbox
#endif /* BCTBX_VFS_STANDARD_HH */
