/**************************************************************************
*   Copyright (c) 2011, 2023 Cisco Systems, Inc.
*   All Rights Reserved. Cisco Highly Confidential.
***************************************************************************
*
*   File:   PrivateApi.c
*   Date:   01/2011
*
***************************************************************************
*   VPN C API Internal C++ class
***************************************************************************/

#include "PrivateApi.h"
#include "FirewallInfo.h"
#ifdef WIN32
#include "atlconv.h"
#endif

void PrivateApi::StatsCB(VPNStats &stats)
{
    if (m_pCallbacks == NULL || m_pCallbacks->pStatsCB == NULL)
    {
        vpncapi_log(LOGERR, "invalid StatsCB specified");
        return;
    }

    resetCVpnStats();

    m_pCVpnStats = createCVpnStats(stats);
    if (NULL == m_pCVpnStats)
    {
        vpncapi_log(LOGERR, "NULL CVpnStats created from VpnStats");
        return;
    }

    // store SecureRoutes
    std::list<RouteInfo *> routes = stats.getSecureRoutes();
    for (std::list<RouteInfo *>::iterator it = routes.begin();
         it != routes.end();
         it++)
    {
        ROUTE_INFO* pCRoute = createCRouteInfo(*it);
        m_CStatsSecureRoutes.push_back(pCRoute);
    }

    m_pCVpnStats->nSecureRoutes = m_CStatsSecureRoutes.size();
    m_pCVpnStats->ppSecureRoutes = (m_CStatsSecureRoutes.empty()) ? NULL : &m_CStatsSecureRoutes[0];

    // store NonSecureRoutes
    std::list<RouteInfo *> nonsecure_routes = stats.getNonsecureRoutes();
    for (std::list<RouteInfo *>::iterator it = nonsecure_routes.begin();
         it != nonsecure_routes.end();
         it++)
    {
        ROUTE_INFO* pCRoute = createCRouteInfo(*it);
        m_CStatsNonSecureRoutes.push_back(pCRoute);
    }

    m_pCVpnStats->nNonSecureRoutes = m_CStatsNonSecureRoutes.size();
    m_pCVpnStats->ppNonSecureRoutes = (m_CStatsNonSecureRoutes.empty()) ? NULL : &m_CStatsNonSecureRoutes[0];

    // store ProtocolInfo
    std::list<ProtocolInfo *> protocols = stats.getProtocolInfo();
    for (std::list<ProtocolInfo *>::iterator it = protocols.begin();
         it != protocols.end();
         it++)
    {
        PROTOCOL_INFO* pCProtocol = createCProtocolInfo(*it);
        m_CStatsProtocolInfo.push_back(pCProtocol);
    }

    m_pCVpnStats->nProtocolInfo = m_CStatsProtocolInfo.size();
    m_pCVpnStats->ppProtocolInfo = (m_CStatsProtocolInfo.empty()) ? NULL : &m_CStatsProtocolInfo[0];

    // store FirewallInfo
    std::list<FirewallInfo *> fwrules = stats.getFirewallInfo();
    for (std::list<FirewallInfo *>::iterator it = fwrules.begin();
         it != fwrules.end();
         it++)
    {
        FIREWALL_INFO* pRule = createCFirewallInfo(*it);
        m_CStatsFirewallInfo.push_back(pRule);
    }

    m_pCVpnStats->nFirewallInfo = m_CStatsFirewallInfo.size();
    m_pCVpnStats->ppFirewallInfo = (m_CStatsFirewallInfo.empty()) ? NULL : &m_CStatsFirewallInfo[0];

    m_pCallbacks->pStatsCB(m_pCVpnStats);
}

void PrivateApi::StateCB(const VPNState state,
                         const VPNSubState subState,
                         const tstring stateString)
{
    USES_CONVERSION;
    m_strState = T2A(stateString.c_str());
    if (m_pCallbacks != NULL && m_pCallbacks->pStateCB != NULL)
    {
        m_pCallbacks->pStateCB(state, subState, m_strState.c_str());
    }
    else
    {
        vpncapi_log(LOGERR, "invalid StateCB specified");
    }
}

void PrivateApi::BannerCB(const tstring &banner)
{
    USES_CONVERSION;
    m_strBanner = T2A(banner.c_str());
    if (m_pCallbacks != NULL && m_pCallbacks->pBannerCB != NULL)
    {
        m_pCallbacks->pBannerCB(m_strBanner.c_str());
    }
    else
    {
        vpncapi_log(LOGERR, "invalid BannerCB specified");
    }
}

void PrivateApi::PreConnectReminderCB(const tstring &rtstrPreConnectReminder)
{
    USES_CONVERSION;
    m_strPreConnectReminder = T2A(rtstrPreConnectReminder.c_str());
    if (m_pCallbacks != NULL && m_pCallbacks->pPreConnectReminderCB != NULL)
    {
        m_pCallbacks->pPreConnectReminderCB(m_strPreConnectReminder.c_str());
    }
    else
    {
        vpncapi_log(LOGERR, "invalid PreConnectReminderCB specified");
    }
}

void PrivateApi::NoticeCB(const tstring &notice,
                          const MessageType type,
                          const bool bSensitive)
{
    USES_CONVERSION;
    m_strNotice = T2A(notice.c_str());
    if (m_pCallbacks != NULL && m_pCallbacks->pNoticeCB != NULL)
    {
        m_pCallbacks->pNoticeCB(m_strNotice.c_str(),
                                type,
                                static_cast<int>(bSensitive));
    }
    else
    {
        vpncapi_log(LOGERR, "invalid NoticeCB specified");
    }
}

void PrivateApi::ExitNoticeCB(const tstring &tstrNotice,
                          const int returnCode)
{
    USES_CONVERSION;
    m_strExitNotice = T2A(tstrNotice.c_str());
    if (m_pCallbacks != NULL && m_pCallbacks->pExitNoticeCB != NULL)
    {
        m_pCallbacks->pExitNoticeCB(m_strExitNotice.c_str(), returnCode);
    }
    else
    {
        vpncapi_log(LOGERR, "invalid ExitNoticeCB specified");
    }
}

void PrivateApi::ServiceReadyCB()
{
    if (m_pCallbacks != NULL && m_pCallbacks->pServiceReadyCB != NULL)
    {
        m_pCallbacks->pServiceReadyCB();
    }
    else
    {
        vpncapi_log(LOGERR, "invalid ServiceReadyCB specified");
    }
}

void PrivateApi::EventAvailable()
{
    // Need this for back support of C API automation
    VPNCAPI_ACQUIRE_LOCK(&m_EventLock);
    m_bEventAvailable = true;
    VPNCAPI_RELEASE_LOCK(&m_EventLock);

    if (m_pCallbacks != NULL && m_pCallbacks->pEventAvailableCB != NULL)
    {
        m_pCallbacks->pEventAvailableCB();
    }
    else
    {
        vpncapi_log(LOGERR, "invalid EventAvailableCB specified");
    }
}

void PrivateApi::WMHintCB(const WMHint hint, const WMHintReason reason)
{
    if (m_pCallbacks != NULL && m_pCallbacks->pWMHintCB != NULL)
    {
        m_pCallbacks->pWMHintCB(hint, reason);
    }
    else
    {
        vpncapi_log(LOGERR, "invalid WMHintCB specified");
    }
}

void PrivateApi::deliverWebLaunchHostCB(const tstring &activeHost)
{
    USES_CONVERSION;
    m_strWebLaunchHost = T2A(activeHost.c_str());
    if (m_pCallbacks != NULL && m_pCallbacks->pWeblaunchHostCB != NULL)
    {

        m_pCallbacks->pWeblaunchHostCB(m_strWebLaunchHost.c_str());
    }
    else
    {
        vpncapi_log(LOGERR, "invalid WMHintCB specified");
    }
}

/*
 * Save a weak reference to ConnectPromptInfo object owned by API layer
 * Create a duplicate of ConnectPromptInfo as a struct, owned by this object
 * Return reference of duplicate to capi user
 * We're assuming UserPromptCB called by APIUser's main thread
 */
void PrivateApi::UserPromptCB(ConnectPromptInfo &ConnectPrompt)
{
    // update our prompt and promptentry structs
    resetCUserPrompt();

    m_pCPromptInfo = createCPromptInfo(ConnectPrompt);
    if (NULL == m_pCPromptInfo)
    {
        vpncapi_log(LOGERR, "NULL CPromptInfo created from ConnectPromptInfo");
        return;
    }

    std::list<PromptEntry *> PromptEntries = ConnectPrompt.getListPromptEntry();
    for (std::list<PromptEntry *>::const_iterator it = PromptEntries.begin();
         it != PromptEntries.end();
         it++)
    {
        PROMPT_ENTRY* pEntry = createCPromptEntry(*it);
        if (NULL == pEntry)
        {
            vpncapi_log(LOGERR, "NULL PROMPT_ENTRY created from PromptEntry");
            continue;
        }

        m_CPromptEntries.push_back(pEntry);
        m_PromptEntryMap[pEntry->pszName] = pEntry;
        m_CPromptEntryNames.push_back(pEntry->pszName);
    }
    m_pCPromptInfo->ppEntries = (m_CPromptEntries.size() > 0) ? &m_CPromptEntries[0] : NULL;
    m_pCPromptInfo->nEntries = m_CPromptEntries.size();

    // save reference to ConnectPromptInfo owned by API layer
    m_pConnectPrompt = &ConnectPrompt;

    // invoke callback
    if (m_pCallbacks != NULL && m_pCallbacks->pUserPromptCB != NULL)
    {
        m_pCallbacks->pUserPromptCB(m_pCPromptInfo);
    }
    else
    {
        vpncapi_log(LOGERR, "invalid UserPromptCB specified");
    }
}

PrivateApi::PrivateApi(VPNAPI_CALLBACKS* pCallbacks) :
    m_pCallbacks(pCallbacks),
    m_pConnectPrompt(NULL),
    m_pCPromptInfo(NULL),
    m_pCVpnStats(NULL),
    m_uiPreferenceCount(PREFERENCE_COUNT),
    m_bEventAvailable(false)
{
    m_CAllControllablePrefs = new PREFERENCE *[m_uiPreferenceCount];
    for (unsigned int i=0; i < m_uiPreferenceCount; i++)
    {
        m_CAllControllablePrefs[i] = NULL;
    }

    m_CPrefChildren = new std::vector<PREFERENCE*>[m_uiPreferenceCount];

    vpncapi_log(LOGINFO, "PrivateApi %p created", this);
    VPNCAPI_INIT_LOCK(&m_EventLock);
}

PrivateApi::~PrivateApi()
{
    resetCPreferences();
    resetCUserPrompt();
    resetCVpnStats();

    for (size_t i=0; i<m_Hostnames.size(); i++)
    {
        free((void*)m_Hostnames[i]);
    }
    m_Hostnames.clear();

    delete[] m_CPrefChildren;
    delete[] m_CAllControllablePrefs;

    vpncapi_log(LOGINFO, "PrivateApi %p deleted", this);
}

bool PrivateApi::CSetPromptEntryValue(const char* pszEntryName, const char* pszEntryValue)
{
    USES_CONVERSION;
    if (m_pConnectPrompt == NULL)
    {
        vpncapi_log(LOGERR, "No active ConnectPromptInfo in which to set EntryValue");
        return false;
    }

    if (m_PromptEntryMap[pszEntryName] == NULL)
    {
        vpncapi_log(LOGERR, "prompt entry %s doesn't exist", pszEntryName);
        return false;
    }

    if (pszEntryValue != m_PromptEntryMap[pszEntryName]->pszValue)
    {
        free((void*)m_PromptEntryMap[pszEntryName]->pszValue);
        m_PromptEntryMap[pszEntryName]->pszValue = strdup_f(pszEntryValue);
    }

    PromptEntry *pEntry = m_pConnectPrompt->getPromptEntry(A2T(pszEntryName));
    if (pEntry == NULL)
    {
        vpncapi_log(LOGWARN, "Unable to retrieve %s from ConnectPromptInfo",
                    pszEntryName);
        return false;
    }

    return pEntry->setValue(A2T(pszEntryValue));
}

void PrivateApi::setEnrollmentCA(bool bEnroll)
{
    if (m_pConnectPrompt == NULL)
    {
        vpncapi_log(LOGWARN, "No active ConnectPromptInfo instance.");
        return;
    }

    m_pConnectPrompt->setUseEnrollmentCA(bEnroll);
}

PREFERENCE_INFO* PrivateApi::CGetPreferences()
{
    USES_CONVERSION;
    resetCPreferences();
    
    std::shared_ptr<PreferenceInfo> spPreferenceInfo = ClientIfc::getPreferences();
    if (!spPreferenceInfo)
    {
        vpncapi_log(LOGERR, "Unable to acquire preference access.");
        return NULL;
    }

    typedef std::pair<Preference *, unsigned int> PrefWithDepth;

    // convert Preferences to struct PREFERENCE objects
    // logic for ordering preferences into sorted vector borrowed from Linux GUI
    const PreferenceVector &topLevelPrefs = spPreferenceInfo->getListPreferences();
    std::list<PrefWithDepth> prefQueue;
    for (PreferenceVector::const_iterator topLevelIt = topLevelPrefs.begin();
         topLevelIt != topLevelPrefs.end();
         ++topLevelIt)
    {
        prefQueue.push_back(PrefWithDepth(*topLevelIt, 0));
    }

    while (prefQueue.size() != 0)
    {
        PrefWithDepth pwd = prefQueue.front();
        prefQueue.pop_front();
        if (NULL != pwd.first)
        {
            PREFERENCE* cpref = createCPreference(pwd.first);
            if (NULL == cpref)
            {
                vpncapi_log(LOGERR, "unexpected NULL PREFERENCE");
                continue;
            }

            cpref->depth = pwd.second;
            // if has parent, insert this into parent's children vector
            PreferenceId parentId = Preference::getParentId(cpref->id);
            if ( Preference::getParentId(cpref->id) != UnknownPreference )
            {
                m_CPrefChildren[parentId].push_back(cpref);
            }

            m_CAllControllablePrefs[cpref->id] = cpref;
            m_CSortedPrefs.push_back(cpref);

            const PreferenceList &childPrefs = pwd.first->getChildren();
            for (PreferenceList::const_iterator childIt = childPrefs.begin();
                 childIt != childPrefs.end();
                 ++childIt)
            {
                prefQueue.push_front(PrefWithDepth(*childIt, pwd.second + 1));
            }
        }
    }
    for (unsigned int i=0; i<m_uiPreferenceCount; i++)
    {
        if (NULL != m_CAllControllablePrefs[i])
        {
            m_CAllControllablePrefs[i]->nChildren = m_CPrefChildren[i].size();
            m_CAllControllablePrefs[i]->ppChildren = m_CPrefChildren[i].empty() ? NULL : &m_CPrefChildren[i][0];
        }
    }

    m_CPreferenceInfo.pszHeading = T2CA(spPreferenceInfo->getPreferenceHeading().c_str());
    m_CPreferenceInfo.nPrefs = m_CSortedPrefs.size();
    m_CPreferenceInfo.ppPrefs = (m_CSortedPrefs.size() > 0) ? &m_CSortedPrefs[0] : NULL;

    m_spPreferenceInfo = spPreferenceInfo;

    return &m_CPreferenceInfo;
}

bool PrivateApi::CSavePreferences()
{
    USES_CONVERSION;
    PreferenceVector allPrefs = m_spPreferenceInfo->getAllPreferences();
    for (PreferenceVector::const_iterator it = allPrefs.begin();
         it != allPrefs.end();
         ++it)
    {
        Preference *pPref = *it;
        if (NULL == pPref)
        {
            vpncapi_log(LOGERR, "unexpected NULL Preference object");
            return false;
        }

        PREFERENCE *cpref = m_CAllControllablePrefs[pPref->getPreferenceId()];
        if (NULL == cpref)
        {
            vpncapi_log(LOGERR, "cannot find mapped PREFERENCE for preference %i", pPref->getPreferenceId());
            return false;
        }

        bool result =
                pPref->getPromptEntry()->setValue(A2T(cpref->promptentry.pszValue));
        if (!result)
        {
            vpncapi_log(LOGERR, "can't set prompt value for preference %i", pPref->getPreferenceId());
            return false;
        }
    }
    return ClientIfc::savePreferences(m_spPreferenceInfo);
}

bool PrivateApi::CPreferenceSetValue(const char* pszPreferenceName, const char* pszValue)
{
    USES_CONVERSION;
    if (NULL == pszPreferenceName || NULL == pszValue)
    {
        vpncapi_log(LOGERR, "invalid params");
        return false;
    }

    PreferenceId prefId = Preference::getPreferenceIdFromName(A2T(pszPreferenceName));
    if (NULL == m_CAllControllablePrefs[prefId])
    {
        vpncapi_log(LOGERR, "error: can't find preference %s\n", pszPreferenceName);
        return false;
    }
    if (pszValue != m_CAllControllablePrefs[prefId]->promptentry.pszValue)
    {
        free((void*)m_CAllControllablePrefs[prefId]->promptentry.pszValue);
        m_CAllControllablePrefs[prefId]->promptentry.pszValue = strdup_f(pszValue);
    }
    return true;
}

PREFERENCE* PrivateApi::CGetPreferenceByName(const char* pszPreferenceName)
{
    USES_CONVERSION;
    if (NULL==pszPreferenceName)
    {
        vpncapi_log(LOGERR, "invalid params");
        return NULL;
    }
    PreferenceId prefId = Preference::getPreferenceIdFromName(A2T(pszPreferenceName));
    return m_CAllControllablePrefs[prefId];
}

void PrivateApi::resetCPreferences()
{
    m_CSortedPrefs.clear();
    for (unsigned int i=0; i < m_uiPreferenceCount; i++)
    {
        deleteCPreference(m_CAllControllablePrefs[i]);
        m_CPrefChildren[i].clear();
    }
}

// create new CONNECT_PROMPT_INFO from existing ConnectPromptInfo object
CONNECT_PROMPT_INFO* PrivateApi::createCPromptInfo(const ConnectPromptInfo& ConnectPrompt)
{
    USES_CONVERSION;
    CONNECT_PROMPT_INFO* pCPrompt = new CONNECT_PROMPT_INFO;
    if (NULL == pCPrompt)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    pCPrompt->nEntries = ConnectPrompt.getListPromptEntry().size();
    pCPrompt->eType = ConnectPrompt.getConnectPromptType();
    pCPrompt->pszMessage = strdup_f(T2CA(ConnectPrompt.getMessage().c_str()));
    pCPrompt->hasEnrollmentCA = ConnectPrompt.hasEnrollmentCA();
    pCPrompt->hasAuthenticationError = ConnectPrompt.hasAuthenticationError();
    pCPrompt->ppEntries = NULL;

    return pCPrompt;
}

// create new PROMPT_ENTRY from existing PromptEntry object
PROMPT_ENTRY* PrivateApi::createCPromptEntry(const PromptEntry* pPromptEntry)
{
    USES_CONVERSION;
    PROMPT_ENTRY* pCEntry = new PROMPT_ENTRY;
    if (NULL == pCEntry)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    pCEntry->bIsEnabled = pPromptEntry->isEnabled();
    pCEntry->bIsEntryGroup = pPromptEntry->isEntryGroup();
    pCEntry->bIsVisible = pPromptEntry->isVisible();
    pCEntry->eType = pPromptEntry->getPromptType();
    pCEntry->pszLabel = strdup_f(T2CA(pPromptEntry->getPromptLabel().c_str()));
    pCEntry->pszName = strdup_f(T2CA(pPromptEntry->getPromptName().c_str()));
    pCEntry->pszValue = strdup_f(T2CA(pPromptEntry->getValue().c_str()));

    // Add the value options if this is a Prompt_Combo
    pCEntry->slValueOptions.nStrings = 0;
    pCEntry->slValueOptions.ppszStrings = NULL;

    if (pPromptEntry->getPromptType() == Prompt_Combo)
    {
        const std::list<tstring> options = pPromptEntry->getValueOptions();
        if (options.size() != 0)
        {
            pCEntry->slValueOptions.nStrings = options.size();
            pCEntry->slValueOptions.ppszStrings =
                (const char **) malloc(sizeof(const char *) * options.size());
            int i = 0;
            for (std::list<tstring>::const_iterator it = options.begin();
                 it != options.end();
                 it++)
            {
                tstring opt = *it;
                const char* pszOption = strdup_f(T2CA(opt.c_str()));
                pCEntry->slValueOptions.ppszStrings[i] = pszOption;
                i++;
            }
        }
    }
    return pCEntry;
}

void PrivateApi::deleteCPromptInfo(CONNECT_PROMPT_INFO *&pPromptInfo)
{
    if (NULL != pPromptInfo)
    {
        free((void*)pPromptInfo->pszMessage);
        delete pPromptInfo;
        pPromptInfo=NULL;
    }
}


void PrivateApi::resetCUserPrompt()
{
    deleteCPromptInfo(m_pCPromptInfo);

    for (size_t i=0; i<m_CPromptEntries.size(); i++)
    {
        deleteCPromptEntry(m_CPromptEntries[i]);
    }

    m_CPromptEntries.clear();
    m_PromptEntryMap.clear();
    m_CPromptEntryNames.clear();
}

VPN_STATS* PrivateApi::createCVpnStats(VPNStats &stats)
{
    USES_CONVERSION;
    if (NULL != m_pCVpnStats)
    {
        deleteCVpnStats(m_pCVpnStats);
    }
    m_pCVpnStats = new VPN_STATS;
    if (NULL == m_pCVpnStats)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    m_pCVpnStats->pszState = strdup_f(T2CA(stats.getStatValue(VPNStats::State).c_str()));
    m_pCVpnStats->pszTimeConnected = strdup_f(T2CA(stats.getStatValue(VPNStats::TimeConnected).c_str()));
    m_pCVpnStats->pszBytesSent = strdup_f(T2CA(stats.getStatValue(VPNStats::BytesSent).c_str()));
    m_pCVpnStats->pszBytesReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::BytesReceived).c_str()));
    m_pCVpnStats->pszPacketsSent = strdup_f(T2CA(stats.getStatValue(VPNStats::PacketsSent).c_str()));
    m_pCVpnStats->pszPacketsReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::PacketsReceived).c_str()));
    m_pCVpnStats->pszControlBytesSent = strdup_f(T2CA(stats.getStatValue(VPNStats::ControlBytesSent).c_str()));
    m_pCVpnStats->pszControlBytesReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::ControlBytesReceived).c_str()));
    m_pCVpnStats->pszControlPacketsSent = strdup_f(T2CA(stats.getStatValue(VPNStats::ControlPacketsSent).c_str()));
    m_pCVpnStats->pszControlPacketsReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::ControlPacketsReceived).c_str()));
    m_pCVpnStats->pszEncryptedBytesSent = strdup_f(T2CA(stats.getStatValue(VPNStats::EncryptedBytesSent).c_str()));
    m_pCVpnStats->pszEncryptedBytesReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::EncryptedBytesReceived).c_str()));
    m_pCVpnStats->pszEncryptedPacketsSent = strdup_f(T2CA(stats.getStatValue(VPNStats::EncryptedPacketsSent).c_str()));
    m_pCVpnStats->pszEncryptedPacketsReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::EncryptedPacketsReceived).c_str()));
    m_pCVpnStats->pszCompressedBytesSent = strdup_f(T2CA(stats.getStatValue(VPNStats::CompressedBytesSent).c_str()));
    m_pCVpnStats->pszCompressedBytesReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::CompressedBytesReceived).c_str()));
    m_pCVpnStats->pszCompressedPacketsSent = strdup_f(T2CA(stats.getStatValue(VPNStats::CompressedPacketsSent).c_str()));
    m_pCVpnStats->pszCompressedPacketsReceived = strdup_f(T2CA(stats.getStatValue(VPNStats::CompressedPacketsReceived).c_str()));
    m_pCVpnStats->pszInboundDiscarded = strdup_f(T2CA(stats.getStatValue(VPNStats::InboundDiscarded).c_str()));
    m_pCVpnStats->pszOutboundDiscarded = strdup_f(T2CA(stats.getStatValue(VPNStats::OutboundDiscarded).c_str()));
    m_pCVpnStats->pszInboundBypassed = strdup_f(T2CA(stats.getStatValue(VPNStats::InboundBypassed).c_str()));
    m_pCVpnStats->pszOutboundBypassed = strdup_f(T2CA(stats.getStatValue(VPNStats::OutboundBypassed).c_str()));
    m_pCVpnStats->pszClientAddress = strdup_f(T2CA(stats.getStatValue(VPNStats::ClientAddress).c_str()));
    m_pCVpnStats->pszServerAddress = strdup_f(T2CA(stats.getStatValue(VPNStats::ServerAddress).c_str()));
    m_pCVpnStats->pszClientAddressV6 = strdup_f(T2CA(stats.getStatValue(VPNStats::ClientAddressV6).c_str()));
    m_pCVpnStats->pszServerHostName = strdup_f(T2CA(stats.getStatValue(VPNStats::ServerHostName).c_str()));
    m_pCVpnStats->pszProxyAddress = strdup_f(T2CA(stats.getStatValue(VPNStats::ProxyAddress).c_str()));
    m_pCVpnStats->pszProxyHostName = strdup_f(T2CA(stats.getStatValue(VPNStats::ProxyHostName).c_str()));
    m_pCVpnStats->pszProxyPort = strdup_f(T2CA(stats.getStatValue(VPNStats::ProxyPort).c_str()));
    m_pCVpnStats->pszTunnelingMode = strdup_f(T2CA(stats.getStatValue(VPNStats::TunnelingMode).c_str()));
    m_pCVpnStats->pszTunnelingModeV6 = strdup_f(T2CA(stats.getStatValue(VPNStats::TunnelingModeV6).c_str()));
    m_pCVpnStats->pszDynamicTunnelExclusion = strdup_f(T2CA(stats.getStatValue(VPNStats::DynamicTunnelExclusion).c_str()));
    m_pCVpnStats->pszDynamicTunnelInclusion = strdup_f(T2CA(stats.getStatValue(VPNStats::DynamicTunnelInclusion).c_str()));
    m_pCVpnStats->pszFipsMode = strdup_f(T2CA(stats.getStatValue(VPNStats::FipsMode).c_str()));
    m_pCVpnStats->pszTrustedNetworkDetectionMode = strdup_f(T2CA(stats.getStatValue(VPNStats::TrustedNetworkDetectionMode).c_str()));
    m_pCVpnStats->pszAlwaysOnMode = strdup_f(T2CA(stats.getStatValue(VPNStats::AlwaysOnMode).c_str()));
    m_pCVpnStats->pszNetworkStatus = strdup_f(T2CA(stats.getStatValue(VPNStats::NetworkStatus).c_str()));
    m_pCVpnStats->pszDAPMessage = strdup_f(T2CA(stats.getStatValue(VPNStats::DAPMessage).c_str()));
    m_pCVpnStats->pszMUSHost = strdup_f(T2CA(stats.getStatValue(VPNStats::MUSHost).c_str()));
    m_pCVpnStats->pszMUSStatus = strdup_f(T2CA(stats.getStatValue(VPNStats::MUSStatus).c_str()));
    m_pCVpnStats->pszEnabled = strdup_f(T2CA(stats.getStatValue(VPNStats::Enabled).c_str()));
    m_pCVpnStats->pszDisabled = strdup_f(T2CA(stats.getStatValue(VPNStats::Disabled).c_str()));

    m_pCVpnStats->ppNonSecureRoutes = NULL;
    m_pCVpnStats->ppProtocolInfo = NULL;
    m_pCVpnStats->ppSecureRoutes = NULL;
    m_pCVpnStats->ppFirewallInfo = NULL;

    return m_pCVpnStats;
}

void PrivateApi::resetCVpnStats()
{
    deleteCVpnStats(m_pCVpnStats);

    for (size_t i=0; i<m_CStatsSecureRoutes.size(); i++)
    {
        deleteCRouteInfo(m_CStatsSecureRoutes[i]);
    }

    for (size_t i=0; i<m_CStatsNonSecureRoutes.size(); i++)
    {
        deleteCRouteInfo(m_CStatsNonSecureRoutes[i]);
    }

    for (size_t i=0; i<m_CStatsProtocolInfo.size(); i++)
    {
        deleteCProtocolInfo(m_CStatsProtocolInfo[i]);
    }

    m_CStatsSecureRoutes.clear();
    m_CStatsNonSecureRoutes.clear();
    m_CStatsProtocolInfo.clear();
}

void PrivateApi::deleteCVpnStats(VPN_STATS *&pCStats)
{
    if (NULL != pCStats)
    {
        free((void*)m_pCVpnStats->pszState);
        free((void*)m_pCVpnStats->pszTimeConnected);
        free((void*)m_pCVpnStats->pszBytesSent);
        free((void*)m_pCVpnStats->pszBytesReceived);
        free((void*)m_pCVpnStats->pszPacketsSent);
        free((void*)m_pCVpnStats->pszPacketsReceived);
        free((void*)m_pCVpnStats->pszControlBytesSent);
        free((void*)m_pCVpnStats->pszControlBytesReceived);
        free((void*)m_pCVpnStats->pszControlPacketsSent);
        free((void*)m_pCVpnStats->pszControlPacketsReceived);
        free((void*)m_pCVpnStats->pszEncryptedBytesSent);
        free((void*)m_pCVpnStats->pszEncryptedBytesReceived);
        free((void*)m_pCVpnStats->pszEncryptedPacketsSent);
        free((void*)m_pCVpnStats->pszEncryptedPacketsReceived);
        free((void*)m_pCVpnStats->pszCompressedBytesSent);
        free((void*)m_pCVpnStats->pszCompressedBytesReceived);
        free((void*)m_pCVpnStats->pszCompressedPacketsSent);
        free((void*)m_pCVpnStats->pszCompressedPacketsReceived);
        free((void*)m_pCVpnStats->pszInboundDiscarded);
        free((void*)m_pCVpnStats->pszOutboundDiscarded);
        free((void*)m_pCVpnStats->pszInboundBypassed);
        free((void*)m_pCVpnStats->pszOutboundBypassed);
        free((void*)m_pCVpnStats->pszClientAddress);
        free((void*)m_pCVpnStats->pszServerAddress);
        free((void*)m_pCVpnStats->pszClientAddressV6);
        free((void*)m_pCVpnStats->pszServerHostName);
        free((void*)m_pCVpnStats->pszProxyAddress);
        free((void*)m_pCVpnStats->pszProxyHostName);
        free((void*)m_pCVpnStats->pszProxyPort);
        free((void*)m_pCVpnStats->pszTunnelingMode);
        free((void*)m_pCVpnStats->pszTunnelingModeV6);
        free((void*)m_pCVpnStats->pszDynamicTunnelExclusion);
        free((void*)m_pCVpnStats->pszDynamicTunnelInclusion);
        free((void*)m_pCVpnStats->pszFipsMode);
        free((void*)m_pCVpnStats->pszTrustedNetworkDetectionMode);
        free((void*)m_pCVpnStats->pszAlwaysOnMode);
        free((void*)m_pCVpnStats->pszNetworkStatus);
        free((void*)m_pCVpnStats->pszDAPMessage);
        free((void*)m_pCVpnStats->pszMUSHost);
        free((void*)m_pCVpnStats->pszMUSStatus);
        free((void*)m_pCVpnStats->pszEnabled);
        free((void*)m_pCVpnStats->pszDisabled);

        delete pCStats;
        pCStats = NULL;
    }
}

ROUTE_INFO* PrivateApi::createCRouteInfo(RouteInfo *pRouteInfo)
{
    USES_CONVERSION;
    if (NULL == pRouteInfo)
    {
        vpncapi_log(LOGERR, "invalid param");
        return NULL;
    }

    ROUTE_INFO* pCRoute = new ROUTE_INFO;
    if (NULL == pCRoute)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    pCRoute->pszNetwork = strdup_f(T2CA(pRouteInfo->getNetwork().c_str()));
    pCRoute->pszSubnet = strdup_f(T2CA(pRouteInfo->getSubnet().c_str()));
    pCRoute->pszHostNames = strdup_f(T2CA(pRouteInfo->getHostNames().c_str()));
    return pCRoute;
}

void PrivateApi::deleteCRouteInfo(ROUTE_INFO *&pRouteInfo)
{
    free((void*)pRouteInfo->pszNetwork);
    free((void*)pRouteInfo->pszSubnet);
    free((void*)pRouteInfo->pszHostNames);
    delete pRouteInfo;
    pRouteInfo = NULL;
}

PROTOCOL_INFO* PrivateApi::createCProtocolInfo(ProtocolInfo* pProtocol)
{
    USES_CONVERSION;
    if (NULL == pProtocol)
    {
        vpncapi_log(LOGERR, "invalid param");
        return NULL;
    }

    PROTOCOL_INFO* pCProtocol = new PROTOCOL_INFO;
    if (NULL == pCProtocol)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    pCProtocol->bIsActive = pProtocol->isActive();
    pCProtocol->pszCipher = strdup_f(T2CA(pProtocol->getProtocolValue(ProtocolInfo::Cipher).c_str()));
    pCProtocol->pszCompression = strdup_f(T2CA(pProtocol->getProtocolValue(ProtocolInfo::Compression).c_str()));
    pCProtocol->pszProtocol = strdup_f(T2CA(pProtocol->getProtocolValue(ProtocolInfo::Protocol).c_str()));
    pCProtocol->pszState = strdup_f(T2CA(pProtocol->getProtocolValue(ProtocolInfo::State).c_str()));

    return pCProtocol;
}

void PrivateApi::deleteCProtocolInfo(PROTOCOL_INFO *&pCProtocolInfo)
{
    if (NULL != pCProtocolInfo)
    {
        free((void*)pCProtocolInfo->pszCipher);
        free((void*)pCProtocolInfo->pszCompression);
        free((void*)pCProtocolInfo->pszProtocol);
        free((void*)pCProtocolInfo->pszState);

        delete pCProtocolInfo;
        pCProtocolInfo = NULL;
    }
}

void PrivateApi::deleteCPromptEntry(PROMPT_ENTRY *&pCPromptEntry)
{
    if (NULL != pCPromptEntry)
    {
        free((void*)pCPromptEntry->pszLabel);
        free((void*)pCPromptEntry->pszName);
        free((void*)pCPromptEntry->pszValue);

        if (pCPromptEntry->slValueOptions.ppszStrings != NULL)
        {
            for (unsigned int i = 0; i < pCPromptEntry->slValueOptions.nStrings; i++)
            {
                free((void*)pCPromptEntry->slValueOptions.ppszStrings[i]);
            }
            free((void*)pCPromptEntry->slValueOptions.ppszStrings);
        }

        delete pCPromptEntry;
        pCPromptEntry=NULL;
    }
}

void PrivateApi::deleteCPreference(PREFERENCE *&pCPreference)
{
    if (NULL != pCPreference)
    {
        free((void*)pCPreference->promptentry.pszValue);
        free((void*)pCPreference->promptentry.pszLabel);
        free((void*)pCPreference->promptentry.pszName);

        delete pCPreference;
        pCPreference = NULL;
    }
}

PREFERENCE* PrivateApi::createCPreference(Preference *pPref)
{
    USES_CONVERSION;
    if (NULL == pPref)
    {
        vpncapi_log(LOGERR, "invalid param");
        return NULL;
    }

    PREFERENCE *pCPref= new PREFERENCE;
    if (NULL == pCPref)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    pCPref->id = pPref->getPreferenceId();
    pCPref->nChildren = pPref->getChildren().size();
    pCPref->promptentry.bIsEnabled = pPref->getPromptEntry()->isEnabled();
    pCPref->promptentry.bIsEntryGroup = pPref->getPromptEntry()->isEntryGroup();
    pCPref->promptentry.bIsVisible = pPref->getPromptEntry()->isVisible();
    pCPref->promptentry.pszLabel = strdup_f(T2CA(pPref->getPromptEntry()->getPromptLabel().c_str()));
    pCPref->promptentry.pszName = strdup_f(T2CA(pPref->getPromptEntry()->getPromptName().c_str()));
    pCPref->promptentry.pszValue = strdup_f(T2CA(pPref->getPromptEntry()->getValue().c_str()));
    pCPref->promptentry.eType = pPref->getPromptEntry()->getPromptType();

    return pCPref;
}

FIREWALL_INFO* PrivateApi::createCFirewallInfo(FirewallInfo* pFirewallInfo)
{
    USES_CONVERSION;
    if (NULL == pFirewallInfo)
    {
        vpncapi_log(LOGERR, "invalid param");
        return NULL;
    }

    FIREWALL_INFO *pCFirewall = new FIREWALL_INFO;
    if (NULL == pCFirewall)
    {
        vpncapi_log(LOGERR, "out of memory");
        return NULL;
    }

    pCFirewall->pszPermission = strdup_f(T2CA(pFirewallInfo->getProtocol().c_str())) ;
    pCFirewall->pszProtocol = strdup_f(T2CA(pFirewallInfo->getPermission().c_str()));
    pCFirewall->pszInterface = strdup_f(T2CA(pFirewallInfo->getInterface().c_str()));
    pCFirewall->pszSrcPortRange = strdup_f(T2CA(pFirewallInfo->getSrcPortRange().c_str()));
    pCFirewall->pszDstPortRange = strdup_f(T2CA(pFirewallInfo->getDstPortRange().c_str()));
    pCFirewall->pszDstAddr = strdup_f(T2CA(pFirewallInfo->getDstAddr().c_str()));

    return pCFirewall;
}
void PrivateApi::deleteCFirewallInfo(FIREWALL_INFO *&pCFirewall)
{
    if (NULL != pCFirewall)
    {
        free((void*)pCFirewall->pszPermission);
        free((void*)pCFirewall->pszProtocol);
        free((void*)pCFirewall->pszInterface);
        free((void*)pCFirewall->pszSrcPortRange);
        free((void*)pCFirewall->pszDstPortRange);
        free((void*)pCFirewall->pszDstAddr);

        delete pCFirewall;
        pCFirewall = NULL;
    }
}

const char* PrivateApi::CGetDefaultHostname()
{
    USES_CONVERSION;
    m_strDefaultHostname = T2A(getDefaultHostName().c_str());
    return m_strDefaultHostname.c_str();
}
STRING_LIST PrivateApi::CGetHostnames()
{
    USES_CONVERSION;
    for (size_t i=0; i<m_Hostnames.size(); i++)
    {
        free((void*)m_Hostnames[i]);
    }
    m_Hostnames.clear();

    std::list<tstring> hostnames = getHostNames();
    for (std::list<tstring>::iterator it = hostnames.begin();
         it != hostnames.end();
         it++)
    {
        tstring hostname = *it;
        const char* pszHostname = strdup_f(T2CA(hostname.c_str()));
        m_Hostnames.push_back(pszHostname);
    }
    STRING_LIST out;
    out.nStrings = m_Hostnames.size();
    out.ppszStrings = (m_Hostnames.size() > 0) ? &m_Hostnames[0] : NULL;
    return out;
}

STRING_LIST PrivateApi::CGetPromptEntryNames()
{
    STRING_LIST out;
    out.nStrings = m_CPromptEntryNames.size();
    out.ppszStrings = (m_CPromptEntryNames.size() > 0) ? &m_CPromptEntryNames[0] : NULL;
    return out;
}

bool PrivateApi::CIsEventAvailable()
{
    VPNCAPI_ACQUIRE_LOCK(&m_EventLock);
    bool ret = m_bEventAvailable;
    m_bEventAvailable = false;
    VPNCAPI_RELEASE_LOCK(&m_EventLock);

    return ret;
}

void PrivateApi::setCanceled(bool bIsCanceled)
{
    if (m_pConnectPrompt == NULL)
    {
        vpncapi_log(LOGWARN, "No active ConnectPromptInfo instance.");
        return;
    }

    m_pConnectPrompt->setCanceled(bIsCanceled);
}

void PrivateApi::UserSubmit()
{
    m_pConnectPrompt = NULL;
    ClientIfc::UserSubmit();
}

#if defined(PLATFORM_ANDROID)
void PrivateApi::ClientCertificateCB(std::vector< std::pair<uint32_t, uint8_t*> > certList)
{
}
#endif

void vpncapi_log2(vpncapi_log_sev sev, std::string pszMessage)
{
    std::string pszSeverity;
    switch(sev)
    {
    case LOGERR:
        pszSeverity = "error";
        break;
    case LOGWARN:
        pszSeverity = "warn";
        break;
    case LOGINFO:
        pszSeverity = "info";
        break;
    default:
        pszSeverity = "unknown";
        break;
    }
    printf("%s: %s\n", pszSeverity.c_str(), pszMessage.c_str());
}

void PrivateApi::CertBlockedCB(const tstring &rtstrUntrustedServer)
{
    if (m_pCallbacks != NULL && m_pCallbacks->pCertBlockedCB != NULL)
    {
        USES_CONVERSION;
        m_pCallbacks->pCertBlockedCB(T2A(rtstrUntrustedServer.c_str()));
    }
    else
    {
        vpncapi_log(LOGERR, "invalid CertBlockedCB specified");
    }
}

void PrivateApi::CertWarningCB(const tstring &rtstrUntrustedServer,
                               const std::list<tstring> &rltstrCertErrors,
                               bool bAllowImport)
{
    USES_CONVERSION;
    std::vector<const char*> vCertErrors;
    for (std::list<tstring>::const_iterator it = rltstrCertErrors.begin();
         it != rltstrCertErrors.end();
         it++)
    {
        tstring certError = *it;
        vCertErrors.push_back(T2CA(certError.c_str()));
    }

    STRING_LIST out;
    out.nStrings = vCertErrors.size();
    out.ppszStrings = (vCertErrors.size() > 0) ? &vCertErrors[0] : NULL;

    if (m_pCallbacks != NULL && m_pCallbacks->pCertWarningCB != NULL)
    {
        m_pCallbacks->pCertWarningCB(T2A(rtstrUntrustedServer.c_str()),
                                     out,
                                     bAllowImport ? 1 : 0);
    }
    else
    {
        vpncapi_log(LOGERR, "invalid CertWarningCB specified");
    }
}
