
/*---------------------------------------------------------------------------*/
/* http_digest_hosts.cpp                                                     */
/* copyright (c) innovaphone 2016                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>

#include "sdk/common/interface/time.h"
#include "sdk/common/interface/random.h"

#include "common/ilib/hash.h"
#include "sdk/common/lib/httplib.h"
#include "common/ilib/str.h"

#include "http_auth_digest.h"
#include "http_digest_host.h"

/*---------------------------------------------------------------------------*/
/* class HTTPDigestHost                                                      */
/*---------------------------------------------------------------------------*/
void HTTPDigestHost::MakeMD5String(const byte * in, size_t len, char * out33byte)
{
    byte buf[HASH_SIZE_MD5];
    hash md5;
    md5.init(HASH_MD5);
    md5.update(in, len);
    md5.final(buf);

    str::from_hexmem(buf, HASH_SIZE_MD5, out33byte);
}


HTTPDigestHost::HTTPDigestHost(const char * hostName)
{
    static dword opaqueCnt = 0;

    this->hostName = _strdup(hostName);
    this->serverOpaque[0] = '\0';

    char randStr[256];
    IRandom::GetChars(randStr, sizeof(randStr) - 1, random_char_t::random_chars_url);
    randStr[255] = '\0';

    char buffer[2048];
    size_t len = _snprintf(buffer, sizeof(buffer), "#Joh3:16_%u_%s_%s_%u_", IRandom::GetRandom(), hostName, randStr, ++opaqueCnt);
    if (len >= sizeof(buffer)) len = sizeof(buffer) - 1;
    HTTPDigestHost::MakeMD5String((byte *)buffer, len, this->serverOpaque);

    // Use that line below to set fixed data for a test needed to resolve an actual bug.
    //memcpy(this->serverOpaque, "3a7a7605f96e3d7f9306862ed34a51b4", 32);
}


HTTPDigestHost::~HTTPDigestHost()
{
    free(hostName);

    HTTPDigestNonce * cur = this->nonceList.front();
    while (cur) {
        HTTPDigestNonce * tmp = cur->goNext();
        delete cur;
        cur = tmp;
    }
}


HTTPDigestNonce * HTTPDigestHost::GetNonceObj(const char * nonce)
{
    for (HTTPDigestNonce * n = this->nonceList.front(); n; n = n->goNext()) {
        if (strcmp(n->GetNonce(), nonce) == 0) return n;
    }
    return nullptr;
}


void HTTPDigestHost::CleanUpNonceObjects(dword timeStamp)
{
    HTTPDigestNonce * cur = this->nonceList.front();
    while (cur) {
        HTTPDigestNonce * next = cur->goNext();
        if (timeStamp - cur->GetLastAccess() > DIGEST_HOST_LIFETIME) {
            cur->remove();
            delete cur;
        }
        cur = next;
    }
}


HTTPDigestNonce * HTTPDigestHost::CreateNonce()
{
    static dword nonceCreateCnt = 0;

    HTTPDigestNonce * result = new HTTPDigestNonce();
    result->nonce = static_cast<char *>(malloc(34));

    char randStr[256];
    IRandom::GetChars(randStr, sizeof(randStr) - 1, random_char_t::random_chars_url);
    randStr[255] = '\0';

    char buffer[320];
    size_t len = _snprintf(buffer, sizeof(buffer), "#DopeFish_%s_%u_%s_%u_", this->serverOpaque, IRandom::GetRandom(), randStr, ++nonceCreateCnt);
    if (len >= sizeof(buffer)) len = sizeof(buffer) - 1;
    HTTPDigestHost::MakeMD5String((byte *)buffer, len, result->nonce);

    // Use that line below to set fixed data for a test needed to resolve an actual bug.
    // memcpy(result->nonce, "751554f2677d70267d78bcf1fa838279", 32);

    this->nonceList.push_back(result);
    return result;
}


const char * HTTPDigestHost::GetHostName()
{
    return hostName;
}


const char * HTTPDigestHost::GetServerOpaque()
{
    return serverOpaque;
}


int HTTPDigestHost::btree_compare(void * key)
{
    return strcmp(static_cast<char *>(key), hostName);
}


int HTTPDigestHost::btree_compare(class btree * b)
{
    return this->btree_compare(static_cast<HTTPDigestHost *>(b)->hostName);
}


/*---------------------------------------------------------------------------*/
/* class HTTPDigestHostManager                                               */
/*---------------------------------------------------------------------------*/

HTTPDigestHostManager::HTTPDigestHostManager()
{
    hostList = NULL;
}


HTTPDigestHostManager::~HTTPDigestHostManager()
{
    while (this->hostList != nullptr) {
        HTTPDigestHost * tmp = this->hostList;
        this->hostList = static_cast<HTTPDigestHost *>(this->hostList->btree_get(this->hostList));
        delete tmp;
    }
}


HTTPDigestHost * HTTPDigestHostManager::GetDigestHost(const char * hostName)
{
    if (hostName == nullptr) return nullptr;
    return this->hostList != nullptr ? static_cast<HTTPDigestHost *>(this->hostList->btree_find(hostName)) : nullptr;
}


void HTTPDigestHostManager::RemoveDigestHost(class HTTPDigestHost * dgHost)
{
    this->hostList = static_cast<HTTPDigestHost *>(this->hostList->btree_get(dgHost));
    delete dgHost;
}


void HTTPDigestHostManager::AddDigestHost(class HTTPDigestHost * dgHost)
{
    this->hostList = static_cast<HTTPDigestHost *>(this->hostList->btree_put(dgHost));
}


void HTTPDigestHostManager::CleanUpList(dword curTime)
{
    if (this->hostList == nullptr) return;

    istd::list<HTTPDigestHost> garbage;

    HTTPDigestHost * curHost = static_cast<HTTPDigestHost *>(this->hostList->btree_find_left());
    for (; curHost; curHost = static_cast<HTTPDigestHost *>(this->hostList->btree_find_next_left(curHost->GetHostName()))) {
        curHost->CleanUpNonceObjects(curTime);
        if (!curHost->HasNonceObjects()) garbage.push_back(curHost);
    }

    while(!garbage.empty()) {
        hostList = static_cast<HTTPDigestHost *>(this->hostList->btree_get(garbage.front()));
        delete garbage.front();
    }
}


void HTTPDigestHostManager::CleanUpList()
{
    if (this->hostList == nullptr) return;
    dword curTime = static_cast<dword>(ITime::TimeStampMilliseconds() / 1000);
	this->CleanUpList(curTime);
}


/*---------------------------------------------------------------------------*/
/* class HTTPDigestNonce                                                     */
/*---------------------------------------------------------------------------*/

void HTTPDigestNonce::NotifyOwner()
{
    while (this->ownerList.front() != nullptr) {
        this->ownerList.front()->ServerNonceInvalidated(this);
        this->ownerList.front()->remove();
    }
}


void HTTPDigestNonce::Invalidate()
{
    this->NotifyOwner();
    this->remove();
    delete this;
}

void HTTPDigestNonce::SetOwner(class HTTPAuthDigest * owner)
{
    if (owner->getList() == &this->ownerList) return;
    if (owner->getList() != nullptr) owner->remove();
    this->ownerList.push_back(owner);
}
