/*
 * Copyright (C) 2022 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see LICENSE 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_HMAC_DRBG_H
#define LC_HMAC_DRBG_H

#include "lc_export.h"
#include "lc_drbg.h"
#include "lc_hmac.h"
#include "lc_rng.h"

#ifdef __cplusplus
extern "C" {
#endif

#if !defined(LC_DRBG_HMAC_STATELEN) || !defined(LC_DRBG_HMAC_BLOCKLEN) ||      \
	!defined(LC_DRBG_HMAC_CORE)
#error "Do not include this header file directly! Use lc_hmac_drbg_<hashtype>.h"
#endif

/// \cond DO_NOT_DOCUMENT
struct lc_drbg_hmac_state {
	struct lc_hmac_ctx hmac_ctx; /* Cipher handle - HMAC_MAX_STATE_SIZE */
	struct lc_hmac_key hmac_key;
	uint8_t *V; /* internal state 10.1.1.1 1a) - DRBG_STATELEN */
	uint8_t *C; /* static value 10.1.1.1 1b) - DRBG_STATELEN */
	unsigned int seeded : 1;
};

#define LC_DRBG_HMAC_STATE_SIZE (2 * LC_DRBG_HMAC_STATELEN)
#define LC_DRBG_HMAC_CTX_SIZE                                                  \
	(LC_DRBG_HMAC_STATE_SIZE + sizeof(struct lc_drbg_hmac_state) +         \
	 sizeof(struct lc_rng_ctx) + LC_HASH_COMMON_ALIGNMENT)

#define _LC_DRBG_HMAC_SET_CTX(name, ctx, offset)                               \
	_LC_HMAC_SET_CTX((&(name)->hmac_ctx), LC_DRBG_HMAC_CORE,               \
			 (&(name)->hmac_key), 0);                              \
	(name)->V = (uint8_t *)((uint8_t *)ctx + offset);                      \
	(name)->C =                                                            \
		(uint8_t *)((uint8_t *)ctx + offset + LC_DRBG_HMAC_STATELEN);  \
	(name)->seeded = 0

#define LC_DRBG_HMAC_SET_CTX(name)                                             \
	_LC_DRBG_HMAC_SET_CTX(name, name, sizeof(struct lc_drbg_hmac_state))

extern LC_DLL_IMPORT const struct lc_rng *lc_hmac_drbg;

#define LC_DRBG_HMAC_RNG_CTX(name)                                             \
	LC_RNG_CTX(name, lc_hmac_drbg, LC_HASH_COMMON_ALIGNMENT);              \
	LC_DRBG_HMAC_SET_CTX((struct lc_drbg_hmac_state *)name->rng_state);    \
	lc_hmac_drbg->zero(name->rng_state)
/// \endcond

/**
 * @brief Allocate stack memory for the Hash DRBG context
 *
 * @param [in] name Name of the stack variable
 *
 * \warning You MUST seed the DRNG!
 */
#define LC_DRBG_HMAC_CTX_ON_STACK(name)                                        \
	_Pragma("GCC diagnostic push") _Pragma(                                \
		"GCC diagnostic ignored \"-Wdeclaration-after-statement\"")    \
		LC_ALIGNED_BUFFER(name##_ctx_buf, LC_DRBG_HMAC_CTX_SIZE,       \
				  LC_HASH_COMMON_ALIGNMENT);                   \
	struct lc_rng_ctx *name = (struct lc_rng_ctx *)name##_ctx_buf;         \
	LC_DRBG_HMAC_RNG_CTX(name);                                            \
	_Pragma("GCC diagnostic pop")

/**
 * @brief Allocate HMAC DRBG context on heap
 *
 * @param [out] drbg Allocated HMAC DRBG context
 *
 * \warning You MUST seed the DRNG!
 *
 * @return: 0 on success, < 0 on error
 */
int lc_drbg_hmac_alloc(struct lc_rng_ctx **drbg);

/**
 * @brief Tests as defined in 11.3.2 in addition to the cipher tests: testing
 *	  of the error handling.
 *
 * @param [in] drbg DRBG state handle that is used solely for the testing. It
 *		    shall not be a production handle unless you call drbg_seed
 *		    on that handle afterwards.
 *
 * Note: testing of failing seed source as defined in 11.3.2 must be handled
 * by the caller.
 *
 * Note 2: There is no sensible way of testing the reseed counter
 * enforcement, so skip it.
 *
 * @return: 0 on success, < 0 on error
 */
int lc_drbg_hmac_healthcheck_sanity(struct lc_rng_ctx *drbg);

#ifdef __cplusplus
}
#endif

#endif /* LC_HMAC_DRBG_H */
