#ifndef _CRYPTO_H_
#define _CRYPTO_H_

#include <sys/types.h>
#include <glib.h>

#include "compat.h"
#include "str.h"
#include "helpers.h"
#include "containers.h"

#define SRTP_MAX_MASTER_KEY_LEN 32
#define SRTP_MAX_MASTER_SALT_LEN 14
#define SRTP_MAX_SESSION_KEY_LEN 32
#define SRTP_MAX_SESSION_SALT_LEN 14
#define SRTP_MAX_SESSION_AUTH_LEN 20

struct crypto_context;
struct rtp_header;
struct rtcp_packet;

typedef int (*crypto_func_rtp)(struct crypto_context *, struct rtp_header *, str *, uint32_t);
typedef int (*crypto_func_rtcp)(struct crypto_context *, struct rtcp_packet *, str *, uint32_t);
typedef int (*hash_func_rtp)(struct crypto_context *, char *out, str *in, uint32_t);
typedef int (*hash_func_rtcp)(struct crypto_context *, char *out, str *in);
typedef int (*session_key_init_func)(struct crypto_context *);
typedef int (*session_key_cleanup_func)(struct crypto_context *);

struct crypto_suite {
	const char *name;
	const char *dtls_name;
	unsigned int
		master_key_len,
		master_salt_len,
		session_key_len,	/* n_e */
		session_salt_len,	/* n_s */
		srtp_auth_tag,		/* n_a */
		srtcp_auth_tag,
		srtp_auth_key_len,	/* n_a */
		srtcp_auth_key_len;
	unsigned long long int
		srtp_lifetime,
		srtcp_lifetime;
	int kernel_cipher;
	int kernel_hmac;
	crypto_func_rtp encrypt_rtp,
			decrypt_rtp;
	crypto_func_rtcp encrypt_rtcp,
			 decrypt_rtcp;
	hash_func_rtp hash_rtp;
	hash_func_rtcp hash_rtcp;
	session_key_init_func session_key_init;
	session_key_cleanup_func session_key_cleanup;
	//const char *dtls_profile_code; // unused
	const EVP_CIPHER *aes_evp;
	unsigned int idx; // filled in during crypto_init_main()
	str name_str; // same as `name`
	const EVP_CIPHER *(*aead_evp)(void);
};

struct crypto_session_params {
	unsigned int unencrypted_srtcp:1,
	             unencrypted_srtp:1,
	             unauthenticated_srtp:1;
};

struct crypto_params {
	const struct crypto_suite *crypto_suite;
	/* we only support one master key for now */
	unsigned char master_key[SRTP_MAX_MASTER_KEY_LEN];
	unsigned char master_salt[SRTP_MAX_MASTER_SALT_LEN];
	unsigned char *mki;
	unsigned int mki_len;
	struct crypto_session_params session_params;
};

struct crypto_params_sdes {
	struct crypto_params params;
	unsigned int tag;
};

TYPED_GQUEUE(sdes, struct crypto_params_sdes)

struct crypto_context {
	struct crypto_params params;

	char session_key[SRTP_MAX_SESSION_KEY_LEN]; /* k_e */
	char session_salt[SRTP_MAX_SESSION_SALT_LEN]; /* k_s */
	char session_auth_key[SRTP_MAX_SESSION_AUTH_LEN];

	/* XXX replay list */
	/* <from, to>? */

	void *session_key_ctx[2];

	unsigned int have_session_key:1;
};


extern const struct crypto_suite *crypto_suites;
extern const unsigned int num_crypto_suites;


void crypto_init_main(void);

const struct crypto_suite *crypto_find_suite(const str *);
int crypto_gen_session_key(struct crypto_context *, str *, unsigned char, unsigned int);
void crypto_dump_keys(struct crypto_context *in, struct crypto_context *out);
char *crypto_params_sdes_dump(const struct crypto_params_sdes *, char **);

/**
 * A function which compares two crypto suite names in str format.
 * Recommended to be used in combination with:
 * g_queue_find_custom() or g_list_find_custom()
 */
INLINE int crypto_params_sdes_cmp(const struct crypto_params_sdes *cs, gconstpointer b)
{
	return str_cmp_str(&cs->params.crypto_suite->name_str, (str *) b);
}


INLINE int crypto_encrypt_rtp(struct crypto_context *c, struct rtp_header *rtp,
		str *payload, uint32_t index)
{
	return c->params.crypto_suite->encrypt_rtp(c, rtp, payload, index);
}
INLINE int crypto_decrypt_rtp(struct crypto_context *c, struct rtp_header *rtp,
		str *payload, uint32_t index)
{
	return c->params.crypto_suite->decrypt_rtp(c, rtp, payload, index);
}
INLINE int crypto_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *rtcp,
		str *payload, uint32_t index)
{
	return c->params.crypto_suite->encrypt_rtcp(c, rtcp, payload, index);
}
INLINE int crypto_decrypt_rtcp(struct crypto_context *c, struct rtcp_packet *rtcp,
		str *payload, uint32_t index)
{
	return c->params.crypto_suite->decrypt_rtcp(c, rtcp, payload, index);
}
INLINE int crypto_init_session_key(struct crypto_context *c) {
	return c->params.crypto_suite->session_key_init(c);
}
INLINE int crypto_cleanup_session_key(struct crypto_context *c) {
	if (c->params.crypto_suite->session_key_cleanup)
		return c->params.crypto_suite->session_key_cleanup(c);
	return 0;
}

INLINE void crypto_params_cleanup(struct crypto_params *p) {
	if (p->mki)
		free(p->mki);
	p->mki = NULL;
}
INLINE void crypto_cleanup(struct crypto_context *c) {
	crypto_params_cleanup(&c->params);
	if (!c->params.crypto_suite)
		return;
	crypto_cleanup_session_key(c);
	c->have_session_key = 0;
	c->params.crypto_suite = NULL;
}
INLINE void crypto_reset(struct crypto_context *c) {
	crypto_cleanup(c);
}
INLINE void crypto_params_copy(struct crypto_params *o, const struct crypto_params *i, int copy_sp) {
	struct crypto_session_params sp;

	crypto_params_cleanup(o);

	if (!copy_sp)
		sp = o->session_params;
	*o = *i;
	if (!copy_sp)
		o->session_params = sp;

	if (o->mki_len > 255)
		o->mki_len = 0;
	if (o->mki_len) {
		o->mki = malloc(i->mki_len);
		memcpy(o->mki, i->mki, i->mki_len);
	}
}
INLINE void crypto_init(struct crypto_context *c, const struct crypto_params *p) {
	crypto_cleanup(c);
	if (p)
		crypto_params_copy(&c->params, p, 1);
}
INLINE int crypto_params_cmp(const struct crypto_params *a, const struct crypto_params *b) {
       if (a->crypto_suite != b->crypto_suite)
               return 1;
       if (!a->crypto_suite)
               return 0;
       if (memcmp(a->master_key, b->master_key, a->crypto_suite->master_key_len))
               return 1;
       if (memcmp(a->master_salt, b->master_salt, a->crypto_suite->master_salt_len))
               return 1;
       if (a->mki_len != b->mki_len)
               return 1;
       if (a->mki_len && memcmp(a->mki, b->mki, a->mki_len))
               return 1;
       if (memcmp(&a->session_params, &b->session_params, sizeof(a->session_params)))
	       return 1;
       return 0;
}
INLINE void crypto_params_sdes_free(struct crypto_params_sdes *cps) {
	crypto_params_cleanup(&cps->params);
	g_free(cps);
}
INLINE void crypto_params_sdes_queue_clear(sdes_q *q) {
	t_queue_clear_full(q, crypto_params_sdes_free);
}
INLINE void crypto_params_sdes_queue_copy(sdes_q *dst, const sdes_q *src) {
	for (auto_iter(l, src->head); l; l = l->next) {
		struct crypto_params_sdes *cps = l->data;
		struct crypto_params_sdes *cpy = g_new(__typeof(*cpy), 1);
		*cpy = *cps;
		cpy->params.mki = NULL;
		crypto_params_copy(&cpy->params, &cps->params, 1);
		t_queue_push_tail(dst, cpy);
	}
}
/**
 * Checks whether to apply policies according to: sdes_no / sdes_only
 * returns: 1 - to not apply / 0 - to apply
 */
INLINE int crypto_params_sdes_check_limitations(str_case_ht sdes_only,
			str_case_ht sdes_no,
			const struct crypto_suite *cps) {

	/* if 'SDES-only-' flag(s) present, then
	 * accept only those SDES suites mentioned in the 'SDES-only-',
	 * all the rest will be dropped / not added.
	 * This takes precedence over 'SDES-no-'.
	 *
	 * We mustn't check the 'flags->sdes_no' at all, if 'flags->sdes_only' is set. */
	if (t_hash_table_is_set(sdes_only))
	{
		if (!t_hash_table_lookup(sdes_only, &cps->name_str))
			return 1;
	}

	/* if 'SDES-no-' flag(s) present, then
		* remove SDES-no suites from offered ones */
	else if (t_hash_table_is_set(sdes_no) &&
		t_hash_table_lookup(sdes_no, &cps->name_str))
	{
		return 1;
	}

	return 0;
}

#include "main.h"
#include "log.h"
#include <inttypes.h>


typedef GString crypto_debug_string;

INLINE crypto_debug_string *crypto_debug_init(bool flag) {
	if (rtpe_config.common.log_levels[log_level_index_srtp] < LOG_DEBUG)
		return NULL;
	if (!flag)
		return NULL;
	return g_string_new("");
}
void __crypto_debug_printf(crypto_debug_string *gs, const char *fmt, ...) __attribute__((format(printf,2,3)));
#define crypto_debug_printf(gs, f, ...) \
	do { \
		if (gs) \
			__crypto_debug_printf(gs, f, ##__VA_ARGS__); \
	} while (0)
INLINE void crypto_debug_dump_raw(crypto_debug_string *gs, const char *b, int len) {
	for (int i = 0; i < len; i++)
		crypto_debug_printf(gs, "%02" PRIx8, (unsigned char) b[i]);
}
INLINE void crypto_debug_dump(crypto_debug_string *gs, const str *s) {
	crypto_debug_dump_raw(gs, s->s, s->len);
}
INLINE void crypto_debug_finish(crypto_debug_string *gs) {
	ilogs(srtp, LOG_NOTICE, "Crypto debug: %.*s", (int) gs->len, gs->str);
	g_string_free(gs, TRUE);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC(crypto_debug_string, crypto_debug_finish);



#endif
