/*
 * Copyright 2010 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 "serverexchange/session/MD5Session.h"
#include "Logger/LoggerMacroses.h"
#include "Utils.h"
#include <syncml/core/NextNonce.h>

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Communication;
using namespace NS_DM_Client::NS_SyncMLCommand;

static const char * c_LogName = "MD5Session";

MD5Session::MD5Session(ConnectionInfo &info, Funambol::TransportAgent &agent) :
    Session(info, agent), m_credentialsSent(false)
{
}


MD5Session::~MD5Session()
{
}


SStatusCommand* MD5Session::CreateSyncHdrStatus(int code, const char * responseURI)
{
    SStatusCommand *status = new(std::nothrow) SStatusCommand(code);
    if(status == (SStatusCommand*)NULL)
    {
        GDLWARN("new SStatusCommand");
    }
    status->SetMsgRef(m_connectionInfo.LastServerMessageID());
    status->SetCmdRef("0");
    status->SetCmd("SyncHdr");

    if (m_connectionInfo.devinf.get())
    {
        status->SetTargetRef(m_connectionInfo.devinf->getDevID());
        status->SetSourceRef(responseURI);
    }

    Funambol::Meta meta;
    Funambol::NextNonce nonce(m_connectionInfo.acconfig.getServerNonce());
    meta.setType(GetTypeName());
    meta.setFormat("b64");
    meta.setNextNonce(&nonce);
    
    Funambol::Chal chal(&meta);
    status->Prepare();
    status->Internal()->setChal(&chal);

    return status;
}


bool MD5Session::CredentialsSent()
{
    return m_credentialsSent;
}


bool MD5Session::IsServerCredValid(Funambol::Cred *cred)
{
    if (cred && cred->getData() && cred->getFormat() && cred->getType())
    {
        const char *data   = cred->getData();
        
        // check format and data are b64 text
        const char *format = cred->getFormat();
        const char *type   = cred->getType();
        
        if (!strcmp(AUTH_TYPE_MD5, type) && !strcmp("b64", format))
        {
            char *localmd5 = Funambol::MD5CredentialData(m_connectionInfo.acconfig.getServerID(), 
                                                         m_connectionInfo.acconfig.getServerPWD(), 
                                                         m_connectionInfo.acconfig.getServerNonce());
            GDLDEBUG("Local md5 calc: %s", localmd5);
//          GDLDEBUG("on auth data: ['%s' '%s' '%s']", 
//                   m_connectionInfo.acconfig.getServerID(), 
//                   m_connectionInfo.acconfig.getServerPWD(), 
//                   m_connectionInfo.acconfig.getServerNonce());

            bool res = localmd5 && data && !strcmp(localmd5, data);
            SAFE_DELETE_ARR(localmd5);
            return res;
        }
    }
    return false;
}


bool MD5Session::ReadChal(Funambol::Chal &chal)
{
    Funambol::Meta *pMeta  = chal.getMeta();
    if (!pMeta) 
        return false;

    const char *pType = pMeta->getType();

    if (!strcmp(AUTH_TYPE_MD5, pType) || !strcmp(AUTH_TYPE_MAC, pType))
    {
        if (strcmp(pType, GetTypeName()))
            m_connectionInfo.SwitchToSessionType(pType);

        // todo remove
//      m_connectionInfo.SetUseHMAC(!strcmp(AUTH_TYPE_MAC, pType)); // switch to HMAC checking auth scheme

        // Note: b64NextNoce string is allocated with new[] inside of NextNonce class!
        const char *b64NextNoce = NULL;
        if (pMeta->getNextNonce())
        {
            b64NextNoce = pMeta->getNextNonce()->getValueAsBase64();
        }
        else
            return false;
        
        if (!b64NextNoce)
        {
            return false;
        }
        else
        {
            m_connectionInfo.acconfig.setClientNonce(b64NextNoce); // TODO? store NextNonce into tree
            GDLDEBUG("NextNonce '%s' will be used for the next connection to server", b64NextNoce);

            // also set the auth type to the pType - this will auto trigger certain connection type on next tryout
//          m_connectionInfo.acconfig.setClientAuthType(pType); // AUTH_TYPE_MD5 || AUTH_TYPE_MAC
            
/*          if (!strcmp(AUTH_TYPE_MAC, pType))
            {
                // set nonce for server
                Funambol::NextNonce *pNonce = CreateNonce();
                const char *nonce64 = pNonce->getValueAsBase64();
                m_connectionInfo.acconfig.setServerNonce(nonce64);
                GDLDEBUG("server's NextNonce: '%s'", nonce64);
                SAFE_DELETE_ARR(nonce64);
                SAFE_DELETE(pNonce);
            }*/
        }
        
        SAFE_DELETE_ARR(b64NextNoce);
    }
    return true;
}


void MD5Session::insertSessionInitiationCommands(int code)
{
    GDLDEBUG("m_firstMessage %d", m_firstMessage);
    if (m_firstMessage)
    {
        insertAlertReplace(code);
        m_firstMessage = false;
    }
    else
    {
        if (m_connectionInfo.state.repeatAuthentication)
        {
            GDLDEBUG("Post Alert&Replace");
            insertAlertReplace(code);
        }

        if (m_state.ServerAuthenticated)
        {
            GDLDEBUG("ServerAuthenticated, do not send Status with Nonce");
        }
        else
        {
            Funambol::NextNonce *pNonce = CreateNonce();
            const char *nonce64 = pNonce->getValueAsBase64();
            m_connectionInfo.acconfig.setServerNonce(nonce64);
            SAFE_DELETE_ARR(nonce64);
            SAFE_DELETE(pNonce);

            GDLDEBUG("Post status for SyncHdr");
            SStatusCommand *status = 
                CreateSyncHdrStatus(m_state.ClientAuthenticated ? AUTHENTICATION_ACCEPTED : INVALID_CREDENTIALS, 
                                    m_connectionInfo.GetSessionURL());
            if(status == (SStatusCommand*)NULL)
            {
                GDLWARN("status is NULL");
            }
            m_commandsToSend.InsertFirst(SCommandPtr(status));
            m_credentialsSent = true;
        }
    }
}


const char * MD5Session::GetTypeName()
{
    return AUTH_TYPE_MD5;
}
