/*
 * Copyright (C) 2014 Igalia S.L. All rights reserved.
 *
 * 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
 */

#include "config.h"
#include "CryptoDigest.h"

#include <openssl/sha.h>

namespace WebCore {

class CryptoDigestContext {
	WTF_MAKE_FAST_ALLOCATED;
public:
	virtual ~CryptoDigestContext()
	{
	}

	virtual void addBytes(const void* input, size_t length) = 0;
	virtual Vector<uint8_t> computeHash() = 0;

	bool valid() const { return m_valid; }

protected:
	explicit CryptoDigestContext(CryptoDigest::Algorithm algorithm)
	: m_algorithm(algorithm)
	, m_valid(false)
	{
	}

	CryptoDigest::Algorithm m_algorithm;
	bool m_valid;
};

class CryptoDigestContext256 : public CryptoDigestContext
{
public:
	explicit CryptoDigestContext256(CryptoDigest::Algorithm algorithm)
	: CryptoDigestContext(algorithm)
	{
		m_valid = SHA256_Init(&m_sha256Context);
	}

	virtual void addBytes(const void* input, size_t length)
	{
		if(m_valid)
			m_valid = SHA256_Update(&m_sha256Context, (unsigned char*)input, length);
	}

	virtual Vector<uint8_t> computeHash()
	{
		Vector<uint8_t> hash;
		hash.resize(SHA256_DIGEST_LENGTH); // 32 bytes
		if(SHA256_Final(hash.data(), &m_sha256Context))
			return hash;
		return Vector<uint8_t>();
	}

private:
	SHA256_CTX m_sha256Context;
};

class CryptoDigestContext384 : public CryptoDigestContext
{
public:
	explicit CryptoDigestContext384(CryptoDigest::Algorithm algorithm)
	: CryptoDigestContext(algorithm)
	{
		m_valid = SHA384_Init(&m_sha384Context);
	}

	virtual void addBytes(const void* input, size_t length)
	{
		if(m_valid)
			m_valid = SHA384_Update(&m_sha384Context, (unsigned char*)input, length);
	}

	virtual Vector<uint8_t> computeHash()
	{
		Vector<uint8_t> hash;
		hash.resize(SHA384_DIGEST_LENGTH); // 32 bytes
		if(SHA384_Final(hash.data(), &m_sha384Context))
			return hash;
		return Vector<uint8_t>();
	}

private:
	SHA512_CTX m_sha384Context;
};

class CryptoDigestContext512 : public CryptoDigestContext
{
public:
	explicit CryptoDigestContext512(CryptoDigest::Algorithm algorithm)
	: CryptoDigestContext(algorithm)
	{
		m_valid = SHA512_Init(&m_sha512Context);
	}

	virtual void addBytes(const void* input, size_t length)
	{
		if(m_valid)
			m_valid = SHA512_Update(&m_sha512Context, (unsigned char*)input, length);
	}

	virtual Vector<uint8_t> computeHash()
	{
		Vector<uint8_t> hash;
		hash.resize(SHA512_DIGEST_LENGTH); // 32 bytes
		if(SHA512_Final(hash.data(), &m_sha512Context))
			return hash;
		return Vector<uint8_t>();
	}

private:
	SHA512_CTX m_sha512Context;
};

CryptoDigest::CryptoDigest(Algorithm algorithm)
{
	switch(algorithm)
	{
	case Algorithm::SHA_1:
		break;
	case Algorithm::SHA_224:
		break;
	case Algorithm::SHA_256:
	    m_context = adoptPtr(new CryptoDigestContext256(algorithm));
		break;
	case Algorithm::SHA_384:
	    m_context = adoptPtr(new CryptoDigestContext384(algorithm));
		break;
	case Algorithm::SHA_512:
	    m_context = adoptPtr(new CryptoDigestContext512(algorithm));
		break;
	}
}

CryptoDigest::~CryptoDigest()
{
}

PassOwnPtr<CryptoDigest> CryptoDigest::create(CryptoDigest::Algorithm algorithm)
{
	OwnPtr<CryptoDigest> cryptoDigest = adoptPtr(new CryptoDigest(algorithm));
	if(cryptoDigest->m_context->valid())
		return cryptoDigest.release();
	return PassOwnPtr<CryptoDigest>();
}

void CryptoDigest::addBytes(const void* input, size_t length)
{
	if(m_context)
		m_context->addBytes(input, length);
}

Vector<uint8_t> CryptoDigest::computeHash()
{
	if(m_context)
		return m_context->computeHash();
	return Vector<uint8_t>();
}

} // namespace WebCore
