
/*---------------------------------------------------------------------------*/
/* appwebsocket.cpp                                                          */
/* copyright (c) innovaphone 2016 - 2020                                     */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "sdk/common/interface/json_api.h"
#include "sdk/common/interface/random.h"
#include "sdk/common/interface/webserver_plugin.h"
#include "common/ilib/json.h"
#include "common/ilib/hash.h"
#include "common/ilib/cipher.h"
#include "common/ilib/str.h"
#include "../lib/appwebsocket.h"

AppWebsocket::AppWebsocket(IWebserverPlugin * plugin, class IInstanceLog * const log, class JsonApiContext * jsonApiContext) :
log(log),
jsonApiContext(jsonApiContext)
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocket", this);
    websocket = 0;
    domain = 0;
    sip = 0;
    guid = 0;
    dn = 0;
    app = 0;
    unlicensed = true;
    pbxConnection = false;
    pbxManagerConnection = false;
    //info = 0;
    challenge[0] = 0;
    info = 0;
    digest = 0;
    memset(key, 0, sizeof(key));
    connected = false;
    fragmented = 0;
    fragmentedLen = 0;
    sendCount = 0;
    unique = 0;
    srcTree = 0;
    if (plugin) {
        plugin->WebsocketAccept(this);
    }
}

AppWebsocket::~AppWebsocket()
{
    free(domain);
    free(sip);
    free(guid);
    free(app);
    free(info);
    free(dn);
    free(fragmented);
    free(digest);
    websocket = 0;

    while (srcTree) {
        class JsonApi * api = (class JsonApi *)srcTree;
        srcTree = srcTree->btree_get(api);
        free(api->src);
        api->src = 0;
        api->JsonApiConnectionClosed();
    }
    while (!apis.empty()) {
        class JsonApi * api = apis.front();
        while (api->responsePending--) api->JsonApiResponseSent();
        apis.pop_front();
        api->JsonApiConnectionClosed();
    }
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::~AppWebsocket", this);
}

void AppWebsocket::WebsocketAcceptComplete(class IWebsocket * websocket)
{
    //printf("AcceptComplete\n");
    this->websocket = websocket;
    websocket->Recv();
}

void AppWebsocket::WebsocketSendResult(class IWebsocket * websocket)
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::WebsocketSendResult() connected=%x sendCount=%u", this, connected, sendCount);
    if (connected) {
        if (sendCount) {
            sendCount--;
        }
        else {
            log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketSendResult()", this);
            if (!apis.empty() && apis.front()->responsePending) {
                class JsonApi * a = apis.front();
                a->responsePending--;
                if (!a->responsePending) {
                    apis.pop_front();
                    apis.push_back(a);
                }
                a->JsonApiResponseSent();
            }
            else {
                AppWebsocketSendResult();
            }
        }
    }
}

void * AppWebsocket::WebsocketRecvBuffer(size_t len)
{
    char * recvBuffer = (char *)malloc(len + 1);
    recvBuffer[len] = 0;
    return recvBuffer;
}

void AppWebsocket::CalculateKey(const char * password)
{
    byte k[HASH_SIZE_SHA256];
    hash h;
    h.init(HASH_SHA256);
    h.update("innovaphoneAppSessionKey:", 25);
    h.update(challenge, strlen(challenge));
    h.update(":", 1);
    if (password) h.update(password, strlen(password));
    h.final(k);
    str::from_hexmem(k, HASH_SIZE_SHA256, key);
}

bool AppWebsocket::CheckHash(const char * app, const char * domain, const char * sip, const char * guid, const char * dn, const char * digest, const char * infoText, size_t infoLen, const char * password, const char * ch)
{
    if (!challenge[0] && !ch) return false;
    class hash hash;
    byte digest_bin[HASH_SIZE_SHA256];
    char digest_check[HASH_SIZE_SHA256 * 2 + 1];
    hash.init(HASH_SHA256);
    hash.update(app, strlen(app));
    hash.update(":", 1);
    hash.update(domain, strlen(domain));
    hash.update(":", 1);
    hash.update(sip, strlen(sip));
    hash.update(":", 1);
    hash.update(guid, strlen(guid));
    hash.update(":", 1);
    hash.update(dn, strlen(dn));
    hash.update(":", 1);
    if (infoLen) {
        hash.update(infoText, infoLen);
        hash.update(":", 1);
    }
    hash.update(ch ? ch : challenge, strlen(ch ? ch : challenge));
    hash.update(":", 1);
    if(password) hash.update(password, strlen(password));
    //debug->printf("CHECK-DIGEST %s:%s:%s:%s:%s%s%s:%s:%s", app, domain, sip, guid, dn, infoLen ? ":" : "", infoLen ? infoText : "", ch ? ch : challenge, password);
    hash.final(digest_bin);
    for (int i = 0; i < HASH_SIZE_SHA256; i++) sprintf(&digest_check[i * 2], "%02x", digest_bin[i]);
    bool ret = !strcmp(digest, digest_check);
#ifdef MODULE_TEST
    if(!ret) {
        debug->printf("correct digest: %s", digest_check);
    }
#endif
    return ret;
}

void AppWebsocket::WebsocketRecvResult(class IWebsocket * websocket, void * buffer, size_t len, bool text, bool isFragmented)
{
    if (fragmented) {
        char * b = (char *)malloc(fragmentedLen + len + 1);
        memcpy(b, fragmented, fragmentedLen);
        memcpy(&b[fragmentedLen], buffer, len);
        fragmentedLen += (unsigned int)len;
        free(buffer);
        free(fragmented);
        fragmented = b;
    }
    if (isFragmented) {
        if (!fragmented) {
            fragmented = (char *)buffer;
            fragmentedLen = (unsigned int)len;
        }
        websocket->Recv();
        return;
    }
    if (fragmented) {
        buffer = fragmented;
        len = fragmentedLen;
        fragmented = 0;
        fragmentedLen = 0;
    }
    if (connected) log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketRecvResult %.*s", this, len, buffer);
    if(text) {
        class json_io recv((char *)buffer);
        ((char *)buffer)[len] = 0;
        bool error = true;
        bool doRecv = false;
        if (recv.decode()) {
            word base = recv.get_object(JSON_ID_ROOT, NULL);
            const char * mt = recv.get_string(base, "mt");
            if (mt) {
                char sb[WS_MAX_DATA_SIZE];
                class json_io send(sb);
                word sendBase = JSON_ID_NONE;
                if (!strcmp(mt, "KeepAlive")) {
                    error = false;
                    websocket->Recv();
                }
                else if (!strcmp(mt, "CheckBuild")) {
                    char tmp[32];
                    const char * buildString = AppWebsocketBuildString();
                    const char * buildPath = 0;
                    if (buildString) {
                        _snprintf(tmp, sizeof(tmp), "/%s/", buildString);
                        buildPath = tmp;
                    }
                    else {
                        buildString = _BUILD_STRING_;
                        buildPath = __BUILD_STRING__;
                    }
                    sendBase = send.add_object(JSON_ID_ROOT, NULL);
                    send.add_string(sendBase, "mt", "CheckBuildResult");
                    doRecv = true;
                    error = false;
                    const char * url = recv.get_string(base, "url");
                    if (url) {
                        unsigned buildBegin = 0;
                        unsigned buildEnd = 0;
                        for (unsigned i = 0; url[i] && url[i] != '?'; i++) {
                            if (url[i] == '/') {
                                buildBegin = buildEnd;
                                buildEnd = i;
                            }
                        }
                        if (url[buildBegin + 1] >= '0' && url[buildBegin + 1] <= '9') {
                            if (buildBegin && buildEnd && strncmp(&url[buildBegin], buildPath, buildEnd - buildBegin + 1)) {
                                char * newUrl = (char *)alloca(strlen(url) + strlen(buildString) + 3);
                                sprintf(newUrl, "%.*s/%s/%s", buildBegin, url, buildString, &url[buildEnd + 1]);
                                send.add_string(sendBase, "url", newUrl);
                            }
                        }
                    }
                }
                else if (!strcmp(mt, "AppChallenge")) {
                    const char * usr = recv.get_string(base, "user");
                    const char * appObj = recv.get_string(base, "appObj");
                    bool complete = true;
                    if (usr || appObj) {
                        complete = AppWebsocketChallenge(usr, appObj);
                    }
                    if (complete) {
                        ChallengeComplete(0, 0, send, sendBase);
                        error = false;
                        doRecv = true;
                    }
                    else {
                        free(buffer);
                        return;
                    }
                }
                else if (!strcmp(mt, "AppLogin")) {
                    const char * app = recv.get_string(base, "app");
                    const char * domain = recv.get_string(base, "domain");
                    const char * sip = recv.get_string(base, "sip");
                    const char * guid = recv.get_string(base, "guid");
                    const char * dn = recv.get_string(base, "dn");
                    const char * digest = recv.get_string(base, "digest");
                    bool unlicensed = true;
                    char infoText[10000];
                    word infoLen = 0;
                    word info = recv.get_object(base, "info");
                    if (info != JSON_ID_NONE) {
                        char * p = infoText;
                        infoLen = recv.encode(info, p);
                        unlicensed = recv.get_bool(info, "unlicensed");
                        const char * appobj = recv.get_string(info, "appobj");
                        unsigned l;
                        for (l = 0; appobj && appobj[l] && appobj[l] != '~'; l++);
                        if (appobj && sip && strlen(sip) == l && !strncmp(appobj, sip, l)) pbxConnection = true;
                        if (!appobj && app && !strcmp(app, "-")) pbxManagerConnection = true;
                    }
                    sendBase = send.add_object(JSON_ID_ROOT, NULL);
                    send.add_string(sendBase, "mt", "AppLoginResult");
                    send.add_string(sendBase, "app", app);
                    send.add_string(sendBase, "domain", AppWebsocketInstanceDomain());
                    //printf("app=%s, domain=%s, sip=%s, guid=%s, dn=%s, digest=%s\n", app, domain, sip, guid, dn, digest);

                    if (domain && sip && guid && dn) {
                        bool loginOk = false;
                        free(this->domain);
                        free(this->sip);
                        free(this->guid);
                        free(this->dn);
                        free(this->app);
                        free(this->info);
                        this->domain = _strdup(domain);
                        this->sip = _strdup(sip);
                        this->guid = _strdup(guid);
                        this->dn = _strdup(dn);
                        this->app = _strdup(app);
                        this->info = infoLen > 0 ? _strdup(infoText) : NULL;
                        this->unlicensed = unlicensed;
                        if (app && digest) {
                            const char * password = websocket->IsSysClientAuthenticated() ? "" : AppWebsocketPassword();      // sysclients use an empty password, otherwise the key will be wrong
                            if (password) {
                                // try login with instance password first
                                loginOk = CheckHash(app, domain, sip, guid, dn, digest, infoText, infoLen, password);
                                if (loginOk) CalculateKey(password);
                                if (!loginOk && (password = AppWebsocketPassword(domain)) != NULL) {
                                    // if this login doesn't match AND there is a special password for the domain, try this one
                                    loginOk = CheckHash(app, domain, sip, guid, dn, digest, infoText, infoLen, password);
                                    if (loginOk) CalculateKey(password);
                                }
                                challenge[0] = 0;
                            }
                            else {
                                error = false;
                                sendBase = JSON_ID_NONE;
                                free(this->digest);
                                this->digest = _strdup(digest);
                                AppWebsocketLogin(recv, info);
                            }
                        }

                        if (loginOk) {
                            send.add_bool(sendBase, "ok", true);
                            connected = true;
                            error = false;
                            sendCount++;
                            word len = send.encode();
                            websocket->Send(sb, len, true);
                            sendBase = JSON_ID_NONE;
                            doRecv = AppWebsocketConnectComplete(recv, info);
                            if (doRecv) websocket->Recv();
                        }
                    }
                }
                else if (!strcmp(mt, "AppCheckLogin")) {
                    error = false;
                    websocket->Recv();
                    AppWebsocketCheckLogin(recv.get_string(base, "src"), recv.get_string(base, "app"), recv.get_string(base, "domain"), recv.get_string(base, "challenge"), recv.get_string(base, "digest"));
                }
                else {
                    if (connected) {
                        const char * api = recv.get_string(base, "api");
                        const char * src = recv.get_string(base, "src");
                        if (api) {
                            class JsonApi * jsonApi = 0;
                            if (src) {
                                struct JsonApiSrc search = { api, src };
                                class btree * b = srcTree->btree_find(&search);
                                if (b) jsonApi = (class JsonApi *)b;
                            }
                            if (!jsonApi) {
                                for (std::list<class JsonApi *>::const_iterator iterator = apis.begin(), end = apis.end(); iterator != end; ++iterator) {
                                    if (!strcmp(api, (*iterator)->Name())) {
                                        jsonApi = *iterator;
                                        break;
                                    }
                                }
                            }
                            if (!jsonApi && jsonApiContext) {
                                jsonApi = jsonApiContext->JsonApiRequested(api, this);
                            }
                            if (jsonApi) {
                                jsonApi->Message(recv, base, mt, src);
                            }
                            else {
                                AppWebsocketMessageComplete();
                            }
                        }
                        else if (!strcmp(mt, "AppInfo")) {
                            const char * app = recv.get_string(base, "app");
                            if (!app || !app[0]) app = this->app;
                            if (app && app[0]) {
                                sendBase = send.add_object(JSON_ID_ROOT, NULL);
                                send.add_string(sendBase, "mt", "AppInfoResult");
                                if (src) send.add_string(sendBase, "src", src);
                                if (pbxConnection) {
                                    send.add_string(sendBase, "app", app);
                                    word info = send.add_object(sendBase, "info");
                                    UserAppWebsocketAppInfo(app, send, info);
                                    word service_info = send.add_object(sendBase, "serviceInfo");
                                    UserAppWebsocketServiceInfo(app, send, service_info);
                                    send.add_string(sendBase, "deviceapp", AppWebsocketDeviceAppType(app));
                                }
                                else {
                                    send.add_bool(sendBase, "error", true);
                                    send.add_string(sendBase, "errorText", "not allowed on this connection");
                                }
                                doRecv = true;
                            }
                        }
                        else if (!strcmp(mt, "Test")) {
                            sendBase = send.add_object(JSON_ID_ROOT, NULL);
                            send.add_string(sendBase, "mt", "TestResult");
                            if (src) send.add_string(sendBase, "src", src);
                            sendCount++;
                            doRecv = true;
                        }
                        else if (!strcmp(mt, "PbxInfo") && !pbxConnection) {
                            sendBase = send.add_object(JSON_ID_ROOT, NULL);
                            send.add_string(sendBase, "mt", "PbxInfoResult");
                            if (src) send.add_string(sendBase, "src", src);
                            send.add_bool(sendBase, "error", true);
                            send.add_string(sendBase, "errorText", "not allowed on this connection");
                            sendCount++;
                            doRecv = true;
                        }
                        else {
                            AppWebsocketMessage(recv, base, mt, src);
                        }
                        error = false;
                    }
                }
                if (sendBase != JSON_ID_NONE) {
                    word len = send.encode();
                    websocket->Send(sb, len, true);
                    if (doRecv && !error) {
                        log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketRecv", this);
                        websocket->Recv();
                    }
                }
            }
        }
        free(buffer);
        if (error) {
            log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::Error", this);
            websocket->Close();
        }
    }
    else {
        AppWebsocketBinaryMessage(buffer, (int)len);
        free(buffer);
    }
}

void AppWebsocket::WebsocketRecvCanceled(class IWebsocket * websocket, void * buffer)
{
    free(buffer);
}

void AppWebsocket::AppWebsocketChallengeComplete(const char * nonce, const char * dn)
{
    class json_io send((char *)alloca(1000));
    word sendBase = JSON_ID_ROOT;
    ChallengeComplete(nonce, dn, send, sendBase);
    word len = send.encode();
    websocket->Send(send.get_buffer(), len, true);
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketRecv", this);
    websocket->Recv();
}

void AppWebsocket::ChallengeComplete(const char * nonce, const char * cdn, class json_io & send, word & sendBase)
{
    sendBase = send.add_object(JSON_ID_ROOT, NULL);
    send.add_string(sendBase, "mt", "AppChallengeResult");
    if (websocket->IsSysClientAuthenticated()) send.add_bool(sendBase, "sysClient", true);
    snprintf(challenge, sizeof(challenge), "%08x%08x", IRandom::GetRandom(), IRandom::GetRandom());
    send.add_string(sendBase, "challenge", challenge);
    send.add_string(sendBase, "nonce", nonce);
    send.add_string(sendBase, "dn", cdn);
    if (sip) free(sip);
    if (domain) free(domain);
    if (guid) free(guid);
    if (dn) free(dn);
    if (app) free(app);
    sip = 0;
    domain = 0;
    guid = 0;
    dn = 0;
    app = 0;
}

bool AppWebsocket::AppWebsocketLoginComplete(const char * password, char * key, unsigned keyLen)
{
    bool ok = false;
    char sb[1000];
    class json_io send(sb);
    word base = send.add_object(JSON_ID_ROOT, NULL);
    send.add_string(base, "mt", "AppLoginResult");
    if (CheckHash(app, domain, sip, guid, dn, digest, info, info ? strlen(info) : 0, password)) {
        send.add_string(base, "domain", AppWebsocketInstanceDomain());
        send.add_bool(base, "ok", true);
        connected = true;
        sendCount++;
        ok = true;
        CalculateKey(password);
        if (key) {
            memcpy(key, this->key, MIN(keyLen, sizeof(this->key)));
        }
    }
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketLoginComplete(%s) ok=%x,  (%s:%s:%s:%s:%s:%.*s:%s:%s)=%s", this, password ? password : "-", ok, app, domain, sip, guid, dn, info ? strlen(info) : 0, info, challenge, password, digest);
    challenge[0] = 0;
    if (websocket) {
        word len = send.encode();
        websocket->Send(sb, len, true);
    }
    return ok;
}

void AppWebsocket::AppWebsocketCheckLoginComplete(const char * src, const char * app, const char * domain, const char * challenge, const char * digest, const char * password)
{
    char sb[1000];
    class json_io send(sb);
    word base = send.add_object(JSON_ID_ROOT, NULL);
    send.add_string(base, "mt", "AppCheckLoginResult");
    send.add_string(base, "src", src);
    if (app && challenge && digest && CheckHash(app, (domain && domain[0]) ? domain : this->domain, "", "", "", digest, "", 0, password, challenge)) {
        send.add_bool(base, "ok", true);
    }
    if (websocket) {
        word len = send.encode();
        websocket->Send(sb, len, true);
    }
}

bool AppWebsocket::AppWebsocketCheckPassword(const char* password)
{
    return CheckHash(app, domain, sip, guid, dn, digest, info, info ? strlen(info) : 0, password);
}

void AppWebsocket::AppWebsocketMessageComplete()
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketMessageComplete connected:%s", this, websocket ? "true" : "false");
    if (websocket) {
        websocket->Recv();
    }
}

void AppWebsocket::AppWebsocketMessageSend(class json_io & msg, char * buffer)
{
    word len = msg.encode();
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketMessageSend %.*s connected:%s", this, len, buffer, websocket ? "true" : "false");
    if (websocket) {
        websocket->Send(buffer, len, true);
    }
}

void AppWebsocket::AppWebsocketMessageSendText(const char * buffer)
{
    size_t len = strlen(buffer);
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketMessageSend %.*s connected:%s", this, len, buffer, websocket ? "true" : "false");
    if (websocket) {
        websocket->Send(buffer, len, true);
    }
}

void AppWebsocket::AppWebsocketMessageSendBinary(char * buffer, int len)
{
    if(websocket) {
        websocket->Send(buffer, len, false);
    }
}

void AppWebsocket::WebsocketCloseComplete(class IWebsocket * websocket, ws_close_reason_t reason)
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::WebsocketCloseComplete", this);
    delete websocket;
    this->websocket = NULL;
    UserAppWebsocketClosed();
}

void AppWebsocket::AppWebsocketClose()
{
    if (websocket) {
        log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketClose", this);
        websocket->Close();
    }
}

void AppWebsocket::AppWebsocketEncrypt(const char * seed, const char * data, char * out, size_t outLen)
{
    ASSERT(outLen > strlen(data) * 2, "AppWebsocketEncrypt - output buffer too small");
    size_t len = strlen(data);
    char * k = (char *)malloc(strlen(seed) + strlen(key) + 2);
    byte * o = (byte *)malloc(outLen);
    sprintf(k, "%s:%s", seed, key);
    Cipher c;
    c.Init(CIPHER_RC4, (const byte *)k, (int)strlen(k), true);
    c.Crypt((const byte*)data, o, (int)len);
    str::from_hexmem(o, (const dword)len, out);
    free(k);
    free(o);
}

void AppWebsocket::AppWebsocketDecrypt(const char * seed, const char * data, char * out, size_t outLen)
{
    ASSERT(outLen > strlen(data) / 2, "AppWebsocketDecrypt - output buffer too small");
    size_t len = strlen(data);
    char * k = (char *)malloc(strlen(seed) + strlen(key) + 2);
    byte * i = (byte *)malloc(outLen);
    len = str::to_hexmem(data, i, (dword)outLen);
    sprintf(k, "%s:%s", seed, key);
    Cipher c;
    c.Init(CIPHER_RC4, (const byte *)k, (int)strlen(k), false);
    c.Crypt(i, (byte*)out, (int)len);
    out[len] = '\0';
    free(k);
    free(i);
}

void AppWebsocket::AppWebsocketHash(const char * seed, const char * data, char * out, size_t outLen)
{
    ASSERT(outLen > HASH_SIZE_SHA256 * 2, "AppWebsocketDecrypt - output buffer too small");
    byte buf[HASH_SIZE_SHA256];
    hash h;
    h.init(HASH_SHA256);
    if (seed) h.update(seed, strlen(seed));
    h.update(":", 1);
    h.update(key, strlen(key));
    h.update(":", 1);
    if (data) h.update(data, strlen(data));
    h.final(buf);
    str::from_hexmem(buf, HASH_SIZE_SHA256, out);
}

char * AppWebsocket::UserAppWebsocketPassword()
{
    char * password = AppWebsocketPassword();
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketPassword password:%08x", this, password);
    return password;
}

void AppWebsocket::UserAppWebsocketAppInfo(const char * app, class json_io & msg, word base)
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketAppInfo app:%s", this, app);
    AppWebsocketAppInfo(app, msg, base);
}

void AppWebsocket::UserAppWebsocketServiceInfo(const char * app, class json_io & msg, word base)
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketServiceInfo app:%s", this, app);
    AppWebsocketServiceInfo(app, msg, base);
}

const char * AppWebsocket::UserAppWebsocketDeviceAppType(const char * app)
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketDeviceAppType app:%s", this, app);
    return AppWebsocketDeviceAppType(app);
}

void AppWebsocket::UserAppWebsocketClosed()
{
    log->Log(LOG_APP_WEBSOCKET, "AppWebsocket(%08x)::AppWebsocketClosed", this);
    AppWebsocketClosed();
}

void AppWebsocket::RegisterJsonApi(class JsonApi * api, bool uniqueSrc)
{
    if (api->src) {
        srcTree = srcTree->btree_get(api);
        free(api->src);
        api->src = 0;
    }
    else {
        apis.remove(api);
    }
    if (uniqueSrc) {
        char b[32];
        isprintf(b, "src.%llu", unique++);
        api->src = _strdup(b);
        srcTree = srcTree->btree_put(api);
    }
    else {
        apis.push_back(api);
    }
}

void AppWebsocket::UnRegisterJsonApi(class JsonApi * api)
{
    apis.remove(api);
    if (api->src) {
        srcTree = srcTree->btree_get(api);
        free(api->src);
        api->src = 0;
    }
}

void AppWebsocket::JsonApiMessage(class json_io & msg, char * buffer, JsonApi * response)
{
    AppWebsocketMessageSend(msg, buffer);
    if (response) {
        QueueJsonApiResponseSent(response);
    }
    else {
        sendCount++;
    }
}

void AppWebsocket::JsonApiMessageText(const char * buffer, JsonApi * response)
{
    AppWebsocketMessageSendText(buffer);
    if (response) {
        QueueJsonApiResponseSent(response);
    }
    else {
        sendCount++;
    }
}

void AppWebsocket::QueueJsonApiResponseSent(JsonApi * response)
{
    if (!response->responsePending) {
        apis.remove(response);
        if (apis.empty()) {
            apis.push_front(response);
        }
        else {
            std::list<class JsonApi *>::iterator i = apis.begin();
            while (i != apis.end() && (*i)->responsePending) i++;
            apis.insert(i, response);
        }
    }
    response->responsePending++;
}

void AppWebsocket::JsonApiMessageComplete()
{
    AppWebsocketMessageComplete();
}
