#ifndef _IPXE_GCM_H
#define _IPXE_GCM_H

/** @file
 *
 * Galois/Counter Mode (GCM)
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <ipxe/crypto.h>

/** A GCM counter */
struct gcm_counter {
	/** Initialisation vector */
	uint8_t iv[12];
	/** Counter value */
	uint32_t value;
} __attribute__ (( packed ));

/** A GCM length pair */
struct gcm_lengths {
	/** Additional data length */
	uint64_t add;
	/** Data length */
	uint64_t data;
} __attribute__ (( packed ));

/** A GCM block */
union gcm_block {
	/** Raw bytes */
	uint8_t byte[16];
	/** Raw words */
	uint16_t word[8];
	/** Raw dwords */
	uint32_t dword[4];
	/** Counter */
	struct gcm_counter ctr;
	/** Lengths */
	struct gcm_lengths len;
} __attribute__ (( packed ));

/** GCM context */
struct gcm_context {
	/** Accumulated hash (X) */
	union gcm_block hash;
	/** Accumulated lengths */
	union gcm_block len;
	/** Counter (Y) */
	union gcm_block ctr;
	/** Hash key (H) */
	union gcm_block key;
	/** Underlying block cipher */
	struct cipher_algorithm *raw_cipher;
	/** Underlying block cipher context */
	uint8_t raw_ctx[0];
};

extern void gcm_tag ( struct gcm_context *context, union gcm_block *tag );
extern int gcm_setkey ( struct gcm_context *context, const void *key,
			size_t keylen, struct cipher_algorithm *raw_cipher );
extern void gcm_setiv ( struct gcm_context *context, const void *iv,
			size_t ivlen );
extern void gcm_encrypt ( struct gcm_context *context, const void *src,
			  void *dst, size_t len );
extern void gcm_decrypt ( struct gcm_context *context, const void *src,
			  void *dst, size_t len );

/**
 * Create a GCM mode of behaviour of an existing cipher
 *
 * @v _cbc_name		Name for the new CBC cipher
 * @v _cbc_cipher	New cipher algorithm
 * @v _raw_cipher	Underlying cipher algorithm
 * @v _raw_context	Context structure for the underlying cipher
 * @v _blocksize	Cipher block size
 */
#define GCM_CIPHER( _gcm_name, _gcm_cipher, _raw_cipher, _raw_context,	\
		    _blocksize )					\
struct _gcm_name ## _context {						\
	/** GCM context */						\
	struct gcm_context gcm;						\
	/** Underlying block cipher context */				\
	_raw_context raw;						\
};									\
static int _gcm_name ## _setkey ( void *ctx, const void *key,		\
				  size_t keylen ) {			\
	struct _gcm_name ## _context *context = ctx;			\
	linker_assert ( _blocksize == sizeof ( context->gcm.key ),	\
			_gcm_name ## _unsupported_blocksize );		\
	linker_assert ( ( ( void * ) &context->gcm ) == ctx,		\
			_gcm_name ## _context_layout_error );		\
	linker_assert ( ( ( void * ) &context->raw ) ==			\
			( ( void * ) context->gcm.raw_ctx ),		\
			_gcm_name ## _context_layout_error );		\
	return gcm_setkey ( &context->gcm, key, keylen, &_raw_cipher );	\
}									\
static void _gcm_name ## _setiv ( void *ctx, const void *iv,		\
				  size_t ivlen ) {			\
	struct _gcm_name ## _context *context = ctx;			\
	gcm_setiv ( &context->gcm, iv, ivlen );				\
}									\
static void _gcm_name ## _encrypt ( void *ctx, const void *src,		\
				    void *dst, size_t len ) {		\
	struct _gcm_name ## _context *context = ctx;			\
	gcm_encrypt ( &context->gcm, src, dst, len );			\
}									\
static void _gcm_name ## _decrypt ( void *ctx, const void *src,		\
				    void *dst, size_t len ) {		\
	struct _gcm_name ## _context *context = ctx;			\
	gcm_decrypt ( &context->gcm, src, dst, len );			\
}									\
static void _gcm_name ## _auth ( void *ctx, void *auth ) {		\
	struct _gcm_name ## _context *context = ctx;			\
	union gcm_block *tag = auth;					\
	gcm_tag ( &context->gcm, tag );					\
}									\
struct cipher_algorithm _gcm_cipher = {					\
	.name		= #_gcm_name,					\
	.ctxsize	= sizeof ( struct _gcm_name ## _context ),	\
	.blocksize	= 1,						\
	.alignsize	= sizeof ( union gcm_block ),			\
	.authsize	= sizeof ( union gcm_block ),			\
	.setkey		= _gcm_name ## _setkey,				\
	.setiv		= _gcm_name ## _setiv,				\
	.encrypt	= _gcm_name ## _encrypt,			\
	.decrypt	= _gcm_name ## _decrypt,			\
	.auth		= _gcm_name ## _auth,				\
};

#endif /* _IPXE_GCM_H */
