/*
 * Copyright (C) 2016 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see COPYING file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#ifndef _LC_SYM_H
#define _LC_SYM_H

#include "ext_headers.h"
#include "lc_memset_secure.h"
#include "lc_memory_support.h"
#include "lc_status.h"

#ifdef __cplusplus
extern "C" {
#endif

/// \cond DO_NOT_DOCUMENT
struct lc_sym_state;
struct lc_sym {
	int (*init)(struct lc_sym_state *ctx);
	int (*init_nocheck)(struct lc_sym_state *ctx);
	int (*setkey)(struct lc_sym_state *ctx, const uint8_t *key,
		      size_t keylen);
	int (*setiv)(struct lc_sym_state *ctx, const uint8_t *iv, size_t ivlen);
	int (*getiv)(const struct lc_sym_state *ctx, uint8_t *iv, size_t ivlen);
	int (*encrypt)(struct lc_sym_state *ctx, const uint8_t *in,
		       uint8_t *out, size_t len);
	int (*decrypt)(struct lc_sym_state *ctx, const uint8_t *in,
		       uint8_t *out, size_t len);

	int (*init_iv)(const struct lc_sym_state *ctx, uint8_t *iv,
		       size_t ivlen);
	int (*encrypt_iv)(const struct lc_sym_state *ctx, const uint8_t *in,
			  uint8_t *out, size_t len, uint8_t *iv, size_t ivlen);
	int (*decrypt_iv)(const struct lc_sym_state *ctx, const uint8_t *in,
			  uint8_t *out, size_t len, uint8_t *iv, size_t ivlen);

	uint64_t algorithm_type;
	unsigned int statesize;
	unsigned int blocksize;
};

struct lc_sym_ctx {
	const struct lc_sym *sym;
	struct lc_sym_state *sym_state;
};

/*
 * Align the lc_sym_state structure to 16 bytes boundary irrespective where
 * it is embedded into. This is achieved by adding 15 more bytes than necessary
 * to LC_ALIGNED_SYM_BUFFER and then adjusting the pointer offset in that range
 * accordingly.
 *
 * TODO: make this adjustable with a lc_sym->alignment setting - but the
 * question is which pre-processor macro to use to select the proper
 * LC_ALIGN_PTR_XX macro depending on lc_sym->alignment during compile time.
 */
#ifndef LC_SYM_COMMON_ALIGNMENT
#define LC_SYM_COMMON_ALIGNMENT (16)
#endif
#define LC_SYM_ALIGNMENT(symname) LC_SYM_COMMON_ALIGNMENT
#define LC_SYM_ALIGNMASK(symname) (LC_SYM_ALIGNMENT(symname) - 1)

#define LC_ALIGN_SYM_MASK(p, symname)                                          \
	LC_ALIGN_PTR_64(p, LC_SYM_ALIGNMASK(symname))

/*
 * Maximum size of the symmetric context - sizeof(struct lc_sym_state)
 *
 * Note, there is a separate lc_sym_state per block chaining mode in
 * aes_*_*.c and algorithm (aes vs. chacha20).
 */
#define LC_SYM_INTERNAL_SIZE (792)
#define LC_SYM_STATE_SIZE_NONALIGNED (LC_SYM_INTERNAL_SIZE)
#define LC_SYM_STATE_SIZE                                                      \
	(LC_SYM_STATE_SIZE_NONALIGNED + LC_SYM_COMMON_ALIGNMENT)
#define LC_SYM_CTX_SIZE_NONALIGNED                                             \
	(sizeof(struct lc_sym_ctx) + LC_SYM_STATE_SIZE_NONALIGNED)
#define LC_SYM_CTX_SIZE (sizeof(struct lc_sym_ctx) + LC_SYM_STATE_SIZE)

#define LC_SYM_STATE_SIZE_LEN(len) ((len) + LC_SYM_COMMON_ALIGNMENT)
#define LC_SYM_CTX_SIZE_LEN(len)                                               \
	(sizeof(struct lc_sym_ctx) + LC_SYM_STATE_SIZE_LEN(len))

#define _LC_SYM_SET_CTX(name, symname, ctx, offset)                            \
	name->sym_state = (struct lc_sym_state *)LC_ALIGN_SYM_MASK(            \
		((uint8_t *)(ctx)) + (offset), symname);                       \
	name->sym = symname

#define LC_SYM_SET_CTX(name, symname)                                          \
	_LC_SYM_SET_CTX(name, symname, name, sizeof(struct lc_sym_ctx))
/// \endcond

/** @defgroup Symmetric Symmetric Unauthenticated Encryption Algorithms
 *
 * Concept of symmetric algorithms in leancrypto
 *
 * All symmetric can be used with the API calls documented below. However,
 * the allocation part is symmetric-algorithm-specific. Thus, perform the
 * following steps
 *
 * 1. Allocation: Use the stack or heap allocation functions documented in
 *    lc_aes.h, lc_chacha20.h.
 *
 * 2. Use the returned cipher handle with the API calls below.
 *
 * Concept of parallel use of cipher contexts
 *
 * In threaded environments the caller may want to reuse one cipher handle
 * to be called parallel by different threads. This is achieved by the
 * consideration that
 *
 * 1. The cipher handle will only hold the key (schedule) which is static
 *    for all context invocations.
 *
 * 2. The "volatile" data are the plain/ciphertext and the IV. That is now
 *    given on a per-call basis.
 *
 * Therefore, perform the following steps:
 *
 * Prior to parallel use:
 * allocate algorithm
 * lc_sym_init();
 * lc_sym_setkey();
 *
 * Parallel use, invoke for each thread:
 * lc_sym_init_iv();
 * lc_sym_[en|de]crypt_iv();
 *
 * After parallel use:
 * lc_sym_zero[_free]();
 */

/**
 * @ingroup Symmetric
 * @brief Initialize symmetric context
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   perform sym calculation with.
 *
 * The caller must provide an allocated \p ctx.
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_init(struct lc_sym_ctx *ctx);

/**
 * @ingroup Symmetric
 * @brief Set key
 *
 * \note For XTS, it is required that this function call MUST come BEFORE the
 * \p lc_sym_setiv invocation as the tweak value is immediately calculated from
 * the IV using the XTS key.
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   set the key.
 * @param [in] key Key to be set
 * @param [in] keylen Key length to be set
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_setkey(struct lc_sym_ctx *ctx, const uint8_t *key, size_t keylen);

/**
 * @ingroup Symmetric
 * @brief Set IV
 *
 * \note For XTS, it is required that this function call MUST come AFTER the
 * \p lc_sym_setkey invocation as the tweak value is immediately calculated from
 * the IV using the XTS key.
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   set the IV.
 * @param [in] iv IV to be set
 * @param [in] ivlen IV length to be set
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_setiv(struct lc_sym_ctx *ctx, const uint8_t *iv, size_t ivlen);

/**
 * @ingroup Symmetric
 * @brief Get IV
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   set the IV.
 * @param [in] iv buffer to be filled with IV
 * @param [in] ivlen length of buffer
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_getiv(const struct lc_sym_ctx *ctx, uint8_t *iv, size_t ivlen);

/**
 * @ingroup Symmetric
 * @brief Symmetric encryption
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   perform sym calculation with.
 * @param [in] in Plaintext to be encrypted
 * @param [out] out Ciphertext resulting of the encryption
 * @param [in] len Size of the input / output buffer
 *
 * The plaintext and the ciphertext buffer may be identical to support
 * in-place cryptographic operations.
 *
 * \note This call is allowed to be called multiple times to operate in
 * stream mode. However, the caller must enforce the following:
 * 1. All invocations except for the last data block must contain data that
 *    is always a multiple of 16 bytes.
 * 2. Only the last invocation is allowed to be not a multiple of 16 bytes for
 *    algorithms that allow it (e.g. XTS, CTR)
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_encrypt(struct lc_sym_ctx *ctx, const uint8_t *in, uint8_t *out,
		   size_t len);

/**
 * @ingroup Symmetric
 * @brief Symmetric decryption
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   perform sym calculation with.
 * @param [in] in Ciphertext to be decrypted
 * @param [out] out Plaintext resulting of the decryption
 * @param [in] len Size of the input / output buffer
 *
 * The plaintext and the ciphertext buffer may be identical to support
 * in-place cryptographic operations.
 *
 * \note This call is allowed to be called multiple times to operate in
 * stream mode. However, the caller must enforce the following:
 * 1. All invocations except for the last data block must contain data that
 *    is always a multiple of 16 bytes.
 * 2. Only the last invocation is allowed to be not a multiple of 16 bytes for
 *    algorithms that allow it (e.g. XTS, CTR)
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_decrypt(struct lc_sym_ctx *ctx, const uint8_t *in, uint8_t *out,
		   size_t len);

/**
 * @ingroup Symmetric
 * @brief Symmetric encryption with externally stored IV
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   perform sym calculation with.
 * @param [in] in Plaintext to be encrypted
 * @param [out] out Ciphertext resulting of the encryption
 * @param [in] len Size of the input / output buffer
 * @param [in,out] iv IV that is input and returned from the operation
 * @param [in] ivlen Size of the IV
 *
 * The plaintext and the ciphertext buffer may be identical to support
 * in-place cryptographic operations.
 *
 * The buffer for the IV is held by the caller. In this case, \p ctx can be used
 * at the same time by multiple callers.
 *
 * \note This call is allowed to be called multiple times to operate in
 * stream mode. However, the caller must enforce the following:
 * 1. All invocations except for the last data block must contain data that
 *    is always a multiple of 16 bytes.
 * 2. Only the last invocation is allowed to be not a multiple of 16 bytes for
 *    algorithms that allow it (e.g. XTS, CTR)
 *
 * \note The IV must be initialized with \p lc_sym_init_iv before the first use.
 *
 * \note If the IV buffer is aligned at least at 64-bit the implementation will
 * use a fast path which prevents 2 memcpy operations of the IV per invocation
 * in some cases.
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_encrypt_iv(const struct lc_sym_ctx *ctx, const uint8_t *in,
		      uint8_t *out, size_t len, uint8_t *iv, size_t ivlen);

/**
 * @ingroup Symmetric
 * @brief Symmetric decryption with externally stored IV
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   perform sym calculation with.
 * @param [in] in Ciphertext to be decrypted
 * @param [out] out Plaintext resulting of the decryption
 * @param [in] len Size of the input / output buffer
 * @param [in,out] iv IV that is input and returned from the operation
 * @param [in] ivlen Size of the IV
 *
 * The plaintext and the ciphertext buffer may be identical to support
 * in-place cryptographic operations.
 *
 * The buffer for the IV is held by the caller. In this case, \p ctx can be used
 * at the same time by multiple callers.
 *
 * \note This call is allowed to be called multiple times to operate in
 * stream mode. However, the caller must enforce the following:
 * 1. All invocations except for the last data block must contain data that
 *    is always a multiple of 16 bytes.
 * 2. Only the last invocation is allowed to be not a multiple of 16 bytes for
 *    algorithms that allow it (e.g. XTS, CTR)
 *
 * \note The IV must be initialized with \p lc_sym_init_iv before the first use.
 *
 * \note If the IV buffer is aligned at least at 64-bit the implementation will
 * use a fast path which prevents 2 memcpy operations of the IV per invocation
 * in some cases.
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_decrypt_iv(const struct lc_sym_ctx *ctx, const uint8_t *in,
		      uint8_t *out, size_t len, uint8_t *iv, size_t ivlen);

/**
 * @ingroup Symmetric
 * @brief Initialize externally stored IV
 *
 * Pre-process the externally held IV before using it with \p lc_sym_encrypt_iv
 * or \p lc_sym_decrypt_iv.
 *
 * @param [in] ctx Reference to sym context implementation to be used to
 *		   perform sym calculation with.
 * @param [in,out] iv IV that is input and returned from the operation
 * @param [in] ivlen Size of the IV
 *
 * The buffer for the IV is held by the caller. In this case, \p ctx can be used
 * at the same time by multiple callers.
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_init_iv(const struct lc_sym_ctx *ctx, uint8_t *iv, size_t ivlen);

/**
 * @ingroup Symmetric
 * @brief Zeroize symmetric context allocated with either LC_SYM_CTX_ON_STACK
 *	  or lc_sym_alloc
 *
 * @param [in] ctx Symmetric context to be zeroized
 */
void lc_sym_zero(struct lc_sym_ctx *ctx);

/**
 * @ingroup Symmetric
 * @brief Allocate symmetric algorithm context on heap
 *
 * @param [in] sym Symmetric algorithm implementation of type struct lc_sym
 * @param [out] ctx Allocated symmetrc algorithm context
 *
 * @return 0 on success, < 0 on error
 */
int lc_sym_alloc(const struct lc_sym *sym, struct lc_sym_ctx **ctx);

/**
 * @ingroup Symmetric
 * @brief Symmetric algorithm deallocation and properly zeroization function to
 *	  frees all buffers and the cipher handle
 *
 * @param [in] ctx Symmtric algorithm context handle
 */
void lc_sym_zero_free(struct lc_sym_ctx *ctx);

/**
 * @ingroup Symmetric
 * @brief Obtain algorithm status
 *
 * @param [in] sym AEAD algorithm instance
 *
 * @return algorithm status
 */
enum lc_alg_status_val lc_sym_alg_status(const struct lc_sym *sym);

/**
 * @ingroup Symmetric
 * @brief Obtain algorithm status
 *
 * @param [in] ctx Symmetric context handle
 *
 * @return algorithm status
 */
enum lc_alg_status_val lc_sym_ctx_alg_status(const struct lc_sym_ctx *ctx);

/**
 * @ingroup Symmetric
 * @brief Allocate stack memory for the sym context
 *
 * @param [in] name Name of the stack variable
 * @param [in] symname Pointer of type struct sym referencing the sym
 *		       implementation to be used
 */
#define LC_SYM_CTX_ON_STACK(name, symname)                                     \
	_Pragma("GCC diagnostic push") _Pragma(                                \
		"GCC diagnostic ignored \"-Wdeclaration-after-statement\"")    \
		_Pragma("GCC diagnostic ignored \"-Wcast-align\"")             \
			LC_ALIGNED_BUFFER(name##_ctx_buf, LC_SYM_CTX_SIZE,     \
					  LC_SYM_COMMON_ALIGNMENT);            \
	struct lc_sym_ctx *name = (struct lc_sym_ctx *)name##_ctx_buf;         \
	LC_SYM_SET_CTX(name, symname);                                         \
	lc_sym_zero(name);                                                     \
	_Pragma("GCC diagnostic pop")

#ifdef __cplusplus
}
#endif

#endif /* _LC_SYM_H */
