/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $Id$ */

#include "NotificationListener/BootstrapMessage.h"
#include <CryptoAPI/ICrypto.h>
#include <Logger/LoggerMacroses.h>
#include <common/Utils.h>

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_NotificationListener;

BootstrapMessage::BootstrapMessage(NS_Logging::Logger& logger)
: m_logger(logger)
{
	m_pCrypto = CreateCryptoImpl();
	LDUMP("CRYPTO=%p", m_pCrypto);
}


BootstrapMessage::~BootstrapMessage()
{
	if (m_pCrypto)
	{
		m_pCrypto->Release();
		m_pCrypto = NULL;
	}
}


void    BootstrapMessage::SetBEK(const void* key, size_t key_size)
{
    m_BEK.resize(key_size);
    memcpy(&m_BEK[0], key, key_size);
}


void    BootstrapMessage::SetEMSK(const void* EMSK, size_t EMSK_len)
{
	m_BEK.clear();
    if (m_pCrypto)
    {
		unsigned char data[] = "bek@wimaxforum.org";
		const int data_len = sizeof(data) - 1;

    	size_t sizeBEKBuf = 64;
		size_t sizeBEK = 16;
    	m_BEK.resize(sizeBEKBuf);
    	if (!m_pCrypto->HMAC_SHA256(data, data_len, (const unsigned char*)EMSK, EMSK_len,
			(unsigned char*)&m_BEK[0], sizeBEKBuf))
    	{
    		sizeBEK = 0;
    	}
		
		if (sizeBEKBuf < sizeBEK)
		{
			sizeBEK = sizeBEKBuf;
		}

    	m_BEK.resize(sizeBEK);
    }
	else
	{
		LWARN("Crypto API is not ready");
	}

}

bool    BootstrapMessage::Decode(const void* msg, size_t msgsize, bool plain, bool cipherHeaderPresent, buffer_t& decoded)
{
    unsigned char*   msg_ptr = static_cast<unsigned char*>(const_cast<void*>(msg));

	// by default (plain message) all data is WBXML
	unsigned char* data = msg_ptr;
	size_t datasize = msgsize;

	unsigned char* nonce = 0;
	size_t nonceLength = 0;
	const size_t c_NonceLength = 13;

	if (!plain || cipherHeaderPresent)
	{
		// cipher header is mandatory for encrypted bootstrap message
		//

#ifdef PLT_LITTLE_ENDIAN
		m_message.resize(msgsize);
		memcpy(&m_message[0], msg, msgsize);
		msg_ptr = reinterpret_cast<unsigned char*>(&m_message[0]);

		HEADER& hdr = reinterpret_cast<HEADER&>(*msg_ptr);
		hdr.ntoh();

#else
		HEADER& hdr = reinterpret_cast<HEADER&>(*msg_ptr);

#endif


		if (hdr.version != e_Version)
		{
			LERROR("The version (code = %d) is not supported", hdr.version);
			return false;
		}

		if (hdr.protocol != e_OMA_DM)
		{
			LERROR("The protocol (code = %d) is not supported", hdr.protocol);
			return false;
		}

		const size_t totalSize = hdr.data_length + sizeof(hdr);
		if (totalSize > msgsize)
		{
			LERROR("Total length (code = %d) is out of message size", totalSize);
			return false;
		}


#ifdef PLT_LITTLE_ENDIAN
		char tlvHdrBuf[sizeof(TLV_HEADER)];
#endif

		msg_ptr += sizeof(hdr);
		// TODO: check msg_pointer out of buffer

		// get 1st TLV (Nonce)
#ifdef PLT_LITTLE_ENDIAN
		memcpy(tlvHdrBuf, msg_ptr, sizeof(tlvHdrBuf));
		TLV_HEADER& firstTLV = *(TLV_HEADER*)tlvHdrBuf;
		firstTLV.ntoh();
#else
		const TLV_HEADER& firstTLV = *msg_ptr;
#endif
		if (firstTLV.type == e_Nonce)
		{
			nonce = msg_ptr + sizeof(TLV_HEADER);
			nonceLength = firstTLV.length;
			if (nonceLength != c_NonceLength)
			{
				LWARN("NONCE length is not as assumed. Actual length is %d.", nonceLength);
			}	
		}

		msg_ptr += firstTLV.length + sizeof(TLV_HEADER);
		// TODO: check msg_pointer out of buffer

		// get 2nd TLV (Bootstrap data)
#ifdef PLT_LITTLE_ENDIAN
		memcpy(tlvHdrBuf, msg_ptr, sizeof(tlvHdrBuf));
		TLV_HEADER& secondTLV = *(TLV_HEADER*)tlvHdrBuf;
		secondTLV.ntoh();
#else
		const TLV_HEADER& secondTLV = *msg_ptr;
#endif
		if (secondTLV.type == e_Ciphertext)
		{
			data = msg_ptr + sizeof(TLV_HEADER);
			datasize = secondTLV.length;

			LDEBUG("Data length is %d.", datasize);
		}		
	}
    // TODO: check msg_pointer/datasize out of buffer

    int res = 0;
    if (plain)
	{
		LDEBUG("Plain bootstrap decodding...");
		decoded.resize(datasize);
		memcpy(&decoded[0], data, decoded.size());
		res = decoded.size();
	}
	else if	(nonce && data && m_pCrypto && !m_BEK.empty())
    {
		LDEBUG("Encrypted bootstrap decodding...");
        decoded.resize(32*1024);
        size_t inoutdata = decoded.size();
        res = m_pCrypto->AES_CCM_Decrypt(
            nonce, nonceLength,
            (unsigned char*)&m_BEK[0], m_BEK.size(),
            data, datasize,
            (unsigned char*)&decoded[0], inoutdata
            );

        decoded.resize(inoutdata);

		if (!res)
		{
			String bek;
			if (!m_BEK.empty())
			{
				Utils::ToAsciiHex(bek, &m_BEK[0], m_BEK.size());
			}
			else
			{
				bek = "";
			}
			LDUMP("Decryption failed. BEK='%s'", bek.c_str());
		}
    }
	else
	{
		LDUMP("CRYPTO=%p, NONCE=%p, DATA=%p, BEK size=%d", m_pCrypto, nonce, data, m_BEK.size());
	}

	LDEBUG("bRes=%d", res != 0);
	return res != 0;
}


