/**
 * Copyright 2020 German Tischler-Höhle
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS 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 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **/
#if ! defined(LIBSECRECY_GCMCRYPTBASE_HPP)
#define LIBSECRECY_GCMCRYPTBASE_HPP

#include <libsecrecy/GCMIV.hpp>

namespace libsecrecy
{
	template<typename _encrypt_key_type>
	struct GCMCryptBase
	{
		typedef _encrypt_key_type encrypt_key_type;

		static ::std::size_t const blocksize = encrypt_key_type::blocksize;

		protected:
		std::shared_ptr<encrypt_key_type> pkey;
		encrypt_key_type & key;

		std::shared_ptr<GCMIV> pIV;
		GCMIV const & IV;

		std::shared_ptr<uint8_t[]> sauthdata;
		uint8_t const * authdata;
		::std::size_t const l_authdata;

		LockedMemory<gcm_key> lgcmkey;
		gcm_key * gcmkey;
		LockedMemory<gcm_ctx> lgcmctx;
		gcm_ctx * gcmctx;

		public:
		GCMCryptBase(
			std::shared_ptr<encrypt_key_type> rpkey,
			std::shared_ptr<GCMIV> rpIV,
			std::shared_ptr<uint8_t[]> rsauthdata,
			::std::size_t const rl_authdata,
			uint64_t const countershift
		) : pkey(rpkey), key(*pkey), pIV(rpIV), IV(*pIV), sauthdata(rsauthdata),
		    authdata(sauthdata.get()), l_authdata(rl_authdata),
		    lgcmkey(1), gcmkey(lgcmkey.get()), lgcmctx(1), gcmctx(lgcmctx.get())
		{
			reset(countershift);
		}

		std::string getAuthHexString() const
		{
			std::ostringstream ostr;
			for ( std::size_t i = 0; i < l_authdata; ++i )
			{
				ostr.put(NumToHex::numToHex((authdata[i] >> 4)&0xF));
				ostr.put(NumToHex::numToHex((authdata[i] >> 0)&0xF));
			}
			return ostr.str();
		}

		std::string getIVHexString() const
		{
			return IV.toHexString();
		}

		std::string getKeyHashString() const
		{
			return key.hashToHex();
		}

		std::string getPrefix() const
		{
			return key.getPrefix();
		}

		void reset(uint64_t const countershift)
		{
			gcm_set_key(gcmkey,key.getContext(),encrypt_key_type::getCipherFunction());
			gcm_set_iv(gcmctx,gcmkey,IV.size(),IV.begin());
			gcm_update(gcmctx,gcmkey,l_authdata,authdata);
			addToCounter(countershift);
		}

		void addToCounter(uint64_t const add)
		{
			static ::std::size_t const n = sizeof(gcmctx->ctr.b)/sizeof(gcmctx->ctr.b[0]);
			static_assert ( n == blocksize );
			static std::size_t const numlen = blocksize - GCM_IV_SIZE;
			static_assert ( numlen == sizeof(uint32_t) );

			uint8_t * b = gcmctx->ctr.b;

			// decode current counter
			uint64_t v = 0;
			uint8_t const * b_in = b + n - numlen;

			for ( ::std::size_t i = 0; i < numlen; ++i )
				v = (v<<8) | static_cast<uint64_t>(*(b_in++));

			// add and mod
			v += add;
			v &= 0xFFFFFFFFul;

			// write modified counter
			uint8_t * b_out = b + n;

			for ( ::std::size_t i = 0; i < numlen; ++i )
			{
				*(--b_out) = v&0xFF;
				v >>= 8;
			}
		}

		void printCtr(std::ostream & out) const
		{
			uint8_t const * b = gcmctx->ctr.b;
			::std::size_t const n = sizeof(gcmctx->ctr.b)/sizeof(gcmctx->ctr.b[0]);

			for ( ::std::size_t i = 0; i < n; ++i )
				out << "ctr[" << i << "]=" << static_cast<int>(b[i]) << "\n";
		}

		void printIv(std::ostream & out) const
		{
			uint8_t const * b = gcmctx->iv.b;
			::std::size_t const n = sizeof(gcmctx->ctr.b)/sizeof(gcmctx->ctr.b[0]);

			for ( ::std::size_t i = 0; i < n; ++i )
				out << "iv[" << i << "]=" << static_cast<int>(b[i]) << "\n";
		}
	};
}
#endif
