/*
 * Copyright 2009 Funambol, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $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();
    if(m_pCrypto == (ICrypto*)NULL)
    {
        LERROR("Failed to CreateCryptoImpl");
    }
    LDUMP("CRYPTO=%p", m_pCrypto);
}


BootstrapMessage::~BootstrapMessage()
{
    if (m_pCrypto != (ICrypto*)NULL)
    {
        m_pCrypto->Release();
        m_pCrypto =(ICrypto*)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 != (ICrypto*)NULL)
    {
        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 != (ICrypto*)NULL) && !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;
}


