/*---------------------------------------------------------------------------*/
/* appsharing.cpp                                                            */
/* copyright (c) innovaphone 2018                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "sdk/common/os/iomux.h"

#include "sdk/common/interface/time.h"
#include "sdk/common/interface/random.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/media.h"
#include "sdk/common/interface/appsharing.h"
#include "sdk/common/interface/webserver_plugin.h"
#include "common/ilib/json.h"
#include "common/ilib/str.h"
#include "appsharing_io.h"

//#define _SIG_TRACE_

#define HEADER_BYTES  83

#define FRAME_BUFFER_CAPTURE_TIMER   67
#define LOCAL_USER_ID 16384

class IAppSharingIo * IAppSharingIo::Create(class IIoMux * const iomux, class IInstanceLog * log, class MediaConfiguration * mediaConfiguration)
{
    return new AppSharingIo(iomux, log, mediaConfiguration);
}

AppSharingIo::AppSharingIo(class IIoMux * const iomux, class IInstanceLog * log, class MediaConfiguration * mediaConfiguration) :
log(log),
appSharingIoUser(NULL),
deviceUser(NULL),
iomux(iomux)
{
    this->mediaConfiguration = mediaConfiguration;
    imageBuffer = NULL;
    seq = 0;
    userId = LOCAL_USER_ID;
    wdisplayname = NULL;
    displayname = NULL;
    execCnt = 0;
    shutDown = false;
    subscribe = false;
    sessionId = 0;
    this->infoTimer = new ITimer(iomux, this);
    this->forwardingTimer = new ITimer(iomux, this);
    this->deleteTimer = new ITimer(iomux, this);

    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::AppSharingIo imageBuffer=%p user=%p bitrate=%d phoneLoad=%d", this, imageBuffer, appSharingIoUser, mediaConfiguration ? mediaConfiguration->appSharingBitrate : 500, mediaConfiguration->phoneLoad);
}

AppSharingIo::~AppSharingIo()
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::~AppSharingIo", this);

    if(imageBuffer) delete imageBuffer; 
    delete infoTimer;
    delete forwardingTimer;
    delete deleteTimer;
}

void AppSharingIo::Close()
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Close", this);

    this->shutDown = true;

    if(imageBuffer) {
        if(subscribe) imageBuffer->UnsubscribeApplications();
        subscribe = false;
        imageBuffer->CloseImageBuffer();
    }
    else if(ioChannels.empty()) appSharingIoUser->CloseAppSharingIoComplete();
}

void AppSharingIo::CloseComplete()
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::CloseComplete", this);

    if(imageBuffer) delete imageBuffer;
    imageBuffer = NULL;
    if(ioChannels.empty()) appSharingIoUser->CloseAppSharingIoComplete();
}

void AppSharingIo::AddRemoteContainer(void * context, void * container, const char * channelId, enum MediaType coder)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::AddRemoteContainer context=%p container=%p type=%d channelId=%s", this, context, container, coder, channelId);

    class AppSharingContainerHelper * c = new class AppSharingContainerHelper(context, container, channelId, coder, false);
    containers.push_back(c);
}

void AppSharingIo::CheckRemoteContainers(void * context, const char * channelId, enum MediaType coder)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::CheckRemoteContainers context=%p type=%d channelId=%s", this, context, coder, channelId);

    if(!containers.empty()) {
        class AppSharingContainerHelper * c = containers.front();
        while(c) {
            if(c->containerId && channelId && !strcmp(c->containerId, channelId) && c->type == coder) {
                c->session = context;
                break;
            }
            c = c->goNext();
        }
    }
}

void AppSharingIo::RemoveRemoteContainer(void * container, enum MediaType type)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::RemoveRemoteContainer container=%p type=%d", this, container, type);

    if(!containers.empty()) {
        class AppSharingContainerHelper * c;
        for(c = containers.front(); c; c = c->goNext()) {
            if((c->container == container) && (c->type == type)) {
                c->remove();
                delete c;
                return;
            }
        }
    }
}

void AppSharingIo::ReportBlockLost(unsigned int appId, int posX, int posY, int dimX, int dimY)
{
    if (!shareApps.empty()) {
        class Application* shareApp;
        for (shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
            if (shareApp->id == appId) {
                imageBuffer->Mark(shareApp->id, posX, posY, dimX, dimY);
            }
        }
    }
}

void AppSharingIo::RequestNewImage(unsigned int appId)
{
    if (!shareApps.empty()) {
        class Application* shareApp;
        for (shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
            if (shareApp->id == appId) {
                imageBuffer->RequestNewImage(shareApp->id);
            }
        }
    }
}

void AppSharingIo::SctpRecv(class AppSharingIoChannel * appSharingIoChannel, void * buf, size_t len)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::SctpRecv(%p) conference=%d len=%d containers=%d channels=%d", this, appSharingIoChannel, !ioChannels.empty(), len, !containers.empty(), !ioChannels.empty());

    // Send always AppSharing to local Remote Containers
    if(!containers.empty()) {
        class AppSharingContainerHelper * c;
        for(c = containers.front(); c; c = c->goNext()) {
            //log->Log(LOG_APPSHARING, "(%p,%p) Find Forward %p,%d,%s == %p,%d,%s", c, appSharingIoChannel, c->session, c->type, c->containerId, appSharingIoChannel->GetContext(), appSharingIoChannel->GetMediaType(), appSharingIoChannel->GetChannelId());
            if(c->session == appSharingIoChannel->GetContext() && c->type == appSharingIoChannel->GetMediaType() && !strcmp(c->containerId, appSharingIoChannel->GetChannelId())) {
                appSharingIoUser->MediaIoAppSharingContainerSend(buf, len, 0, c->container, false, c->type);
                break;
            }
        }
    }
}

#define Image_hdr_length 11
#define Msg_type  0
#define Session_id 1
#define Image_x 2
#define Image_y 3
#define Image_version 4
#define Image_update 5
#define Image_pos_x 6
#define Image_pos_y 7
#define Image_dim_x 8
#define Image_dim_y 9
#define Image_compression 10
#define Mouse_position_x 2
#define Mouse_position_y 4
#define Mouse_type 6
#define Update_hdr_length 6
#define Info_message_seq 2
#define Info_message_length 3
#define Info_message_type 4
#define Mouse_move_token 2
#define Mouse_move_position_x 6
#define Mouse_move_position_y 8

void AppSharingIo::ConferenceSctpRecvAck(class AppSharingIoChannel * appSharingIoChannel, unsigned num)
{
#ifdef _SIG_TRACE_
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::ConferenceSctpRecvAck num=%x", this, num);
#endif
    if(!forwardingSessions.empty()) {
        class ForwardingSession * s;
        for(s = forwardingSessions.front(); s; s = s->goNext()) {
            if(s->get_forwarding_channel() == appSharingIoChannel && s->get_remote_session_id() == ((num & 0xff0000) >> 16)) {
                s->recv_ack(num);
                return;
            }
        }
    }
}

void AppSharingIo::Conference(class AppSharingIoChannel * appSharingIoChannel, unsigned char * buf, size_t len)
{
#ifdef _SIG_TRACE_
    if(buf[Msg_type] != AppSharingMessages::MOUSE_CURSOR && buf[Msg_type] != AppSharingMessages::MOUSE_MOVE && buf[Msg_type] != AppSharingMessages::IMAGE_MESSAGE) log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference ch=%p (msg=%d,sid=%d,seq=%d) l=%d", this, appSharingIoChannel, buf[Msg_type], buf[Session_id], (buf[Msg_type] == AppSharingMessages::INFO_MESSAGE_ACK) ? buf[Info_message_seq] : 0xff, len);
#endif

    switch(buf[Msg_type]) {
    case AppSharingMessages::CREATE_SESSION:
    {
        class SharingSession * session = NULL;
        if(!sharingSessions.empty()) {
            for(class SharingSession * s = sharingSessions.front(); s; s = s->goNext()) {
                if(s->get_sharing_session_id() == buf[Session_id] && s->get_sharing_channel() == appSharingIoChannel) {
                    session = s;
                    break;
                }
            }
        }
        if(session == NULL) {
            char * appName = NULL;
            char * appDesc = NULL;
            char * senderName = NULL;
            char * jsonNameDesc = NULL;
            if(len > 6) {
                jsonNameDesc = (char *)malloc(len + 1);
                memcpy(jsonNameDesc, buf, len);
                jsonNameDesc[len] = 0;
                json_io json(&jsonNameDesc[6]);
                if(json.decode()) {
                    word base = json.get_object(JSON_ID_ROOT, 0);
                    if(json.get_string(base, "name")) {
                        appName = _strdup(json.get_string(base, "name"));
                    }
                    if(json.get_string(base, "description")) {
                        appDesc = _strdup(json.get_string(base, "description"));
                    }
                    if(json.get_string(base, "sender")) {
                        senderName = _strdup(json.get_string(base, "sender"));
                    }
                }
            }
            session = new class SharingSession(appSharingIoChannel, log, buf[Session_id], appName, appDesc, senderName);
            log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference new sharing session (%d,%d) (%s,%s) (%s) session=%d", this, READ_WORD_NET(&buf[2]), READ_WORD_NET(&buf[4]), appName ? appName : "", appDesc ? appDesc : "", senderName ? senderName : "", buf[Session_id]);
            session->update_app_resolution(READ_WORD_NET(&buf[2]), READ_WORD_NET(&buf[4]));
            sharingSessions.push_back(session);
            if(senderName) free(senderName);
            if(appName) free(appName);
            if(appDesc) free(appDesc);
            if(jsonNameDesc) free(jsonNameDesc);
            // Create all active Sessions in remote peers
            if(!ioChannels.empty()) {
                class AppSharingIoChannel * ch = ioChannels.front();
                while(ch) {
                    if(appSharingIoChannel != ch && appSharingIoChannel->GetContext() == ch->GetContext()) {
                        class ForwardingSession * nSession = new class ForwardingSession(iomux, ch, session, session->get_sharing_channel(), ch, GetSessionId(), mediaConfiguration->appSharingBitrate, mediaConfiguration->appSharingWaitMsForAck, session->get_app_name(), session->get_app_desc(), session->get_sender_name(), log);

                        forwardingSessions.push_back(nSession);

                        ch->RegisterForwardingSession(nSession->get_remote_session_id());
                    }
                    ch = ch->goNext();
                }
            }
        }
    }
    break;
    case AppSharingMessages::CREATE_SESSION_ACK:
    {
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * session = forwardingSessions.front(); session; session = session->goNext()) {
                if(session->get_remote_session_id() == (buf[Session_id] - 128)) {
                    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Create_session_ack session_id(%p)=%d", this, session, (buf[Session_id] - 128));
                    session->set_session_state(Session_connected);
                    session->recv_sink((char *)buf, len);
                    // Send participant name if available to corresponding sharing session
                    if(sharingSessions.empty() == false) {
                        class SharingSession * ss = sharingSessions.front();
                        while(ss) {
                            if(ss == session->get_sharing_session() && session->get_participant()) {
                                int infoMessageLen = 4 + 1 + strlen(session->get_participant()) + 1;
                                char * infoMessage = (char*)malloc(infoMessageLen);
                                infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
                                infoMessage[1] = session->get_sharing_session_id() + 128;
                                infoMessage[2] = ss->get_info_seq();
                                infoMessage[3] = 1 + ((char)strlen(session->get_participant()) + 1);
                                infoMessage[4] = 0x0; // Info(AddParticipant)
                                memcpy(&infoMessage[5], session->get_participant(), strlen(session->get_participant()) + 1);

                                class InfoMessage * msg = new class InfoMessage(infoMessage, infoMessageLen, ss->get_sharing_session_id(), infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                if(ss->get_info_message() == NULL) ss->get_sharing_channel()->SendSinkMessage(infoMessage, infoMessageLen, (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                infoTimer->Start(20);
                                ss->add_info_message(msg);
                            }
                            ss = ss->goNext();
                        }
                    }
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::DELETE_SESSION:
    {
        class SharingSession * session = NULL;
        if(!sharingSessions.empty()) {
            for(class SharingSession * s = sharingSessions.front(); s; s = s->goNext()) {
                if(s->get_sharing_session_id() == buf[Session_id] && s->get_sharing_channel() == appSharingIoChannel) {
                    session = s;
                    break;
                }
            }
        }
        // Delete forwarding sessions in the other channels
        if(session) {
            if(!forwardingSessions.empty()) {
                class ForwardingSession * fs = forwardingSessions.front();
                while(fs) {
                    class ForwardingSession * next = fs->goNext();
                    if(fs->get_sharing_session() == session) {
                        unsigned char deleteMessage[2];
                        deleteMessage[Msg_type] = AppSharingMessages::DELETE_SESSION;
                        deleteMessage[Session_id] = fs->get_remote_session_id();
#ifdef _SIG_TRACE_
                        log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference delete_remote_session (sharing_id=%d remote_id=%d)", this, fs->get_sharing_session_id(), deleteMessage[Session_id]);
#endif
                        fs->get_user_sink()->SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[Msg_type] << 24) & 0xff000000) | (((unsigned)deleteMessage[Session_id] << 16) & 0xff0000) | fs->get_next_sequence());
                        fs->set_session_state(Session_closing);
                        fs->unregister_sink();
                        fs->reset_counter();
                        fs->remove();
                        fs->get_forwarding_channel()->UnregisterForwardingSession(fs->get_remote_session_id());
                        deletingSessions.push_back(fs);
                        deleteTimer->Start(20);
                    }
                    fs = next;
                }
            }
            // Delete sharing session
            session->remove();
            delete session;
        }
    }
    break;
    case AppSharingMessages::DELETE_SESSION_ACK:
    {
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * session = forwardingSessions.front(); session; session = session->goNext()) {
                if(session->get_remote_session_id() == (buf[Session_id] - 128)) {
                    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Delete_session_ack forwarding session_id(%p)=%d", this, session, (buf[Session_id] - 128));
                    session->remove();
                    delete session;
                    break;
                }
            }
        }
        if(!deletingSessions.empty()) {
            for(class ForwardingSession * session = deletingSessions.front(); session; session = session->goNext()) {
                if(session->get_remote_session_id() == (buf[Session_id] - 128)) {
                    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Delete_session_ack deleting session_id(%p)=%d", this, session, (buf[Session_id] - 128));
                    session->remove();
                    delete session;
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::UPDATE_SESSION:
    {
        if(!sharingSessions.empty()) {
            for(class SharingSession * s = sharingSessions.front(); s; s = s->goNext()) {
                if(s->get_sharing_session_id() == buf[Session_id] && s->get_sharing_channel() == appSharingIoChannel) {
                    s->update_app_resolution(READ_WORD_NET(&buf[2]), READ_WORD_NET(&buf[4]));
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::UPDATE_SESSION_ACK:
    {
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                if(fs->get_remote_session_id() == (buf[Session_id] - 128)) {
                    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Update_session_ack session_id(%p)=%i", this, fs, (buf[Session_id] - 128));
                    fs->set_session_state(Session_connected);
                    fs->recv_sink((char *)buf, len);
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::INFO_MESSAGE:
    {
        switch(buf[Info_message_type]) {
        case 0x0: // Info(AddParticipant)
            if(buf[Info_message_length] > 1) {
                if(!forwardingSessions.empty()) {
                    for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                        if(fs->get_remote_session_id() == (buf[Session_id] - 128)) {
                            char * participant = (char *)malloc(buf[Info_message_length] - 1);
                            memset(participant, 0x0, buf[Info_message_length] - 1);
                            //debug->hexdump(buf, len);
                            memcpy(participant, &buf[Info_message_type+1], buf[Info_message_length] - 1);
                            if((fs->get_session_state() == Session_connected) && fs->get_participant() && !strcmp(fs->get_participant(), participant)) {
                                free(participant);
                                break;  // InfoMessage already sent
                            }
                            log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Info_message::AddParticipant(%d) (%s) connected=%d", this, buf[Info_message_length] - 1, participant, fs->get_session_state());
                            debug->hexdump(participant, buf[Info_message_length] - 1);
                            fs->add_participant(participant);
                            if(fs->get_session_state() == Session_connected) {
                                // Forward participant name to the sharing session
                                if(!sharingSessions.empty()) {
                                    class SharingSession * ss = sharingSessions.front();
                                    while(ss) {
                                        if(ss == fs->get_sharing_session()) {
                                            int infoMessageLen = 4 + 1 + strlen(participant) + 1;
                                            char * infoMessage = (char *)malloc(infoMessageLen);
                                            infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
                                            infoMessage[1] = fs->get_sharing_session_id() + 128;
                                            infoMessage[2] = ss->get_info_seq();
                                            infoMessage[3] = 1 + ((char)strlen(participant) + 1);
                                            infoMessage[4] = 0x0; // Info(AddParticipant)
                                            memcpy(&infoMessage[5], participant, strlen(participant) + 1);
                                            class InfoMessage * msg = new class InfoMessage(infoMessage, infoMessageLen, ss->get_sharing_session_id(), infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                            if(ss->get_info_message() == NULL) ss->get_sharing_channel()->SendSinkMessage(infoMessage, infoMessageLen, (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                            infoTimer->Start(25);
                                            ss->add_info_message(msg);
                                        }
                                        ss = ss->goNext();
                                    }
                                }
                            }
                            break; 
                        }
                    }
                }
            }
            break;
        case 0x1:  // Info(RemParticipant)
            if(buf[Info_message_length] > 1) {
                if(!forwardingSessions.empty()) {
                    for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                        if(fs->get_remote_session_id() == (buf[Session_id] - 128)) {
                            char * participant = (char *)malloc(buf[Info_message_length] - 1);
                            memset(participant, 0x0, buf[Info_message_length] - 1);
                            memcpy(participant, &buf[Info_message_type + 1], buf[Info_message_length] - 1);
                            log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Info_message::RemParticipant(%i) (%s) connected=%i", this, buf[Info_message_length] - 1, participant, fs->get_session_state());
                            debug->hexdump(participant, buf[Info_message_length] - 1);
                            fs->add_participant(NULL);
                            if(fs->get_session_state() == Session_connected) {
                                // Forward InfoMessage to the sharing session
                                if(!sharingSessions.empty()) {
                                    class SharingSession * ss = sharingSessions.front();
                                    while(ss) {
                                        if(ss == fs->get_sharing_session()) {
                                            int infoMessageLen = 4 + 1 + strlen(participant) + 1;
                                            char * infoMessage = (char *)malloc(infoMessageLen);
                                            infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
                                            infoMessage[1] = fs->get_sharing_session_id() + 128;
                                            infoMessage[2] = ss->get_info_seq();
                                            infoMessage[3] = 1 + ((char)strlen(participant) + 1);
                                            infoMessage[4] = 0x1; // Info(RemParticipant)
                                            memcpy(&infoMessage[5], participant, strlen(participant) + 1);
                                            class InfoMessage * msg = new class InfoMessage(infoMessage, infoMessageLen, ss->get_sharing_session_id(), infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                            if(ss->get_info_message() == NULL) ss->get_sharing_channel()->SendSinkMessage(infoMessage, infoMessageLen, (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                            infoTimer->Start(25);
                                            ss->add_info_message(msg);
                                        }
                                        ss = ss->goNext();
                                    }
                                }
                            }
                            free(participant);
                        }
                    }
                }
            }
            break;
        case 0x2:  // Info(GiveControl) 
            if(buf[Info_message_length] > 6) {
                char * participant = (char *)malloc(len - 9);
                memset(participant, 0x0, len - 9);
                memcpy(participant, &buf[9], len - 9);
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Info_message::GiveControlTo (%s) length=%i", this, participant, len - 5);
                debug->hexdump(participant, len - 9);
                if(!ioChannels.empty()) {
                    class AppSharingIoChannel * ch = ioChannels.front();
                    while (ch) {
                        class AppSharingIoChannel * next = ch->goNext();
                        log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference (%p) Info_message::GiveControlTo (%s) remote(%p)=%s", this, appSharingIoChannel, participant, ch, ch->GetRemoteParticipant() ? ch->GetRemoteParticipant() : "");
                        if(ch != appSharingIoChannel && ch->GetRemoteParticipant() && !strcmp(ch->GetRemoteParticipant(), participant)) {
                            log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Forward GiveControl message to ch=%p", this, ch);

                            if(!forwardingSessions.empty()) {
                                for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                                    if(fs->get_forwarding_channel() == ch && fs->get_sharing_session_id() == (buf[Session_id])) {
                                        char * infoMsg = (char *)malloc(buf[Info_message_length]);
                                        memset(infoMsg, 0x0, buf[Info_message_length]);
                                        memcpy(infoMsg, &buf[Info_message_type], buf[Info_message_length]);
                                        debug->hexdump(infoMsg, buf[Info_message_length]);
                                        ch->ForwardInfoMessage(fs, infoMsg, buf[Info_message_length]);
                                        forwardingTimer->Start(100);
                                    }
                                }
                            }
                            break;
                        }
                        ch = next;
                    }
                }
                free(participant);
            }
            break;
        case 0x3:  // Info(TakeControl)
            if(buf[Info_message_length] > 6) {
                char * participant = (char *)malloc(len - 9);
                memset(participant, 0x0, len - 9);
                memcpy(participant, &buf[9], len - 9);
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Info_message::TakeControl (%s) length=%d,%d", this, participant, len - 5, buf[Info_message_length]);
                debug->hexdump(participant, len - 9);
                if(!ioChannels.empty()) {
                    class AppSharingIoChannel * ch = ioChannels.front();
                    while (ch) {
                        class AppSharingIoChannel * next = ch->goNext();
                        log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference (%p) Info_message::TakeControl (%s) remote(%p)=%s", this, appSharingIoChannel, participant, ch, ch->GetRemoteParticipant() ? ch->GetRemoteParticipant() : "");
                        if(ch != appSharingIoChannel && ch->GetRemoteParticipant() && !strcmp(ch->GetRemoteParticipant(), participant)) {
                            log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Forward TakeControl message to ch=%p", this, ch);

                            if(!forwardingSessions.empty()) {
                                for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                                    if(fs->get_forwarding_channel() == ch && fs->get_sharing_session_id() == (buf[Session_id])) {
                                        char * infoMsg = (char *)malloc(buf[Info_message_length]);
                                        memset(infoMsg, 0x0, buf[Info_message_length]);
                                        memcpy(infoMsg, &buf[Info_message_type], buf[Info_message_length]);
                                        debug->hexdump(infoMsg, buf[Info_message_length]);
                                        ch->ForwardInfoMessage(fs, infoMsg, buf[Info_message_length]);
                                        forwardingTimer->Start(100);
                                    }
                                }
                            }
                            break;
                        }
                        ch = next;
                    }
                }
                free(participant);
            }
            break;
        case 0x4:  // Info(RequestControl)
            if(buf[Info_message_length] > 2) {
                char * participant = (char *)malloc(buf[Info_message_length] - 1);
                memset(participant, 0x0, buf[Info_message_length] - 1);
                memcpy(participant, &buf[5], buf[Info_message_length] - 1);
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Info_message::RequestControl (%s) length=%d,%d", this, participant, len - 5, buf[Info_message_length]);
                debug->hexdump(participant, buf[Info_message_length] - 1);

                if(!forwardingSessions.empty()) {
                    for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                        if(fs->get_remote_session_id() == (buf[Session_id] - 128) && fs->get_session_state() == Session_connected) {
                            if(!sharingSessions.empty()) {
                                class SharingSession * ss = sharingSessions.front();
                                while(ss) {
                                    if(ss == fs->get_sharing_session()) {
                                        log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Forward ch=%p RequestControl message to (%p) length=%d,%d", this, appSharingIoChannel, ss->get_sharing_channel(), len, buf[Info_message_length]);

                                        int infoMessageLen = 4 + buf[Info_message_length];
                                        char * infoMessage = (char *)malloc(infoMessageLen);
                                        infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
                                        infoMessage[1] = ss->get_sharing_session_id() + 128;
                                        infoMessage[2] = ss->get_info_seq();
                                        infoMessage[3] = buf[Info_message_length];
                                        memcpy(&infoMessage[4], &buf[Info_message_type], buf[Info_message_length]);
                                        ss->get_sharing_channel()->SendInfoMessage(ss, infoMessage, infoMessageLen, infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                        infoTimer->Start(100);
                                        break;
                                    }
                                    ss = ss->goNext();
                                }
                            }
                        }
                    }
                }
                free(participant);
            }
            break;
        case 0x5:  // Info(Keyboard)
        case 0x6:  // Info(Mouse)
            if(buf[Info_message_length] > 6) {
                char * jsonMsg = (char *)malloc(len - 9);
                memset(jsonMsg, 0x0, len - 9);
                memcpy(jsonMsg, &buf[9], len - 9);
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Info_message::Mouse/Keyboard (%s) length=%i seq=%i", this, jsonMsg, len - 5, buf[Info_message_seq]);
                debug->hexdump(jsonMsg, len - 9);
                free(jsonMsg);

                if(!forwardingSessions.empty()) {
                    for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                        if(fs->get_remote_session_id() == (buf[Session_id] - 128) && fs->get_session_state() == Session_connected) {
                            if(!sharingSessions.empty()) {
                                class SharingSession * ss = sharingSessions.front();
                                while(ss) {
                                    if(ss == fs->get_sharing_session()) {
                                        log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Forward ch=%p Keyboard/Mouse message to (%p) length=%d,%d", this, appSharingIoChannel, ss->get_sharing_channel(), len, buf[Info_message_length]);

                                        int infoMessageLen = 4 + buf[Info_message_length];
                                        char * infoMessage = (char *)malloc(infoMessageLen);
                                        infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
                                        infoMessage[1] = ss->get_sharing_session_id() + 128;
                                        infoMessage[2] = ss->get_info_seq();
                                        infoMessage[3] = buf[Info_message_length];
                                        memcpy(&infoMessage[4], &buf[Info_message_type], buf[Info_message_length]);
                                        ss->get_sharing_channel()->SendInfoMessage(ss, infoMessage, infoMessageLen, infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                                        infoTimer->Start(100);
                                        break;
                                    }
                                    ss = ss->goNext();
                                }
                            }
                        }
                    }
                }
            }
            break;
        };
        char infoMessageAck[3];
        infoMessageAck[0] = AppSharingMessages::INFO_MESSAGE_ACK;
        infoMessageAck[1] = buf[Session_id] >= 128 ? buf[Session_id] - 128 : buf[Session_id];
        infoMessageAck[2] = buf[Info_message_seq];
        appSharingIoChannel->SendSinkMessage(infoMessageAck, 3, (((unsigned)infoMessageAck[0] << 24) & 0xff000000) | (((unsigned)infoMessageAck[1] << 16) & 0xff0000) | infoMessageAck[2]);
    }
    break;
    case AppSharingMessages::INFO_MESSAGE_ACK:
    {
        if(!sharingSessions.empty()) {
            for(class SharingSession * ss = sharingSessions.front(); ss; ss = ss->goNext()) {
                ss->remove_info_message(buf[Session_id], buf[Info_message_seq]);
            }
        }
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                fs->remove_info_message(buf[Session_id]-128, buf[Info_message_seq]);
            }
        }
    }
    break;
    case AppSharingMessages::IMAGE_MESSAGE:
    {
        if(!sharingSessions.empty()) {
            for(class SharingSession * s = sharingSessions.front(); s; s = s->goNext()) {
                if(s->get_sharing_session_id() == buf[Session_id] && s->get_sharing_channel() == appSharingIoChannel) {
                    byte * img = (byte *)malloc(len-11);
                    memcpy(img, &buf[11], len-11);
                    class IImage * image = new class IImage(img, len-11);
                    image->x = buf[Image_x];
                    image->y = buf[Image_y];
                    image->version = buf[Image_version];
                    image->update = buf[Image_update];
                    image->posX = buf[Image_pos_x];
                    image->posY = buf[Image_pos_y];
                    image->dimX = buf[Image_dim_x];
                    image->dimY = buf[Image_dim_y];
                    image->type = (enum CompressionType)buf[Image_compression];
                    s->add_image(image);
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::LOW_QUALITY_IMAGE_MESSAGE:
    {
        if(!sharingSessions.empty()) {
            for(class SharingSession * s = sharingSessions.front(); s; s = s->goNext()) {
                if(s->get_sharing_session_id() == buf[Session_id] && s->get_sharing_channel() == appSharingIoChannel) {
                    byte * img = (byte *)malloc(len);
                    memcpy(img, buf, len);
                    class ILowQualityImage * image = new class ILowQualityImage(img, len);
                    s->add_low_image(image);
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::DELETE_IMAGE_MESSAGE:
    {
        if(!sharingSessions.empty()) {
            for(class SharingSession * s = sharingSessions.front(); s; s = s->goNext()) {
                if(s->get_sharing_session_id() == buf[Session_id] && s->get_sharing_channel() == appSharingIoChannel) {
                    s->delete_image();
                    break;
                }
            }
        }
    }
    break;
    case AppSharingMessages::DELETE_IMAGE_MESSAGE_ACK:
    {
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * session = forwardingSessions.front(); session; session = session->goNext()) {
                if(session->get_remote_session_id() == (buf[Session_id] - 128)) {
#ifdef SIG_TRACE_
                    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference Delete_image_message_ack session_id(%p)=%d", this, session, (buf[Session_id] - 128));
#endif
                    session->recv_sink((char *)buf, len);
                }
            }
        }
    }
    break;
    case AppSharingMessages::MOUSE_CURSOR:
    {
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                if(fs->get_sharing_session_id() == buf[Session_id] && fs->get_session_state() == Session_connected) {
                    char mouseMessage[7];
                    mouseMessage[0] = AppSharingMessages::MOUSE_CURSOR;
                    mouseMessage[1] = fs->get_remote_session_id();
                    WRITE_WORD_NET(&mouseMessage[2], READ_WORD_NET(&buf[Mouse_position_x]));
                    WRITE_WORD_NET(&mouseMessage[4], READ_WORD_NET(&buf[Mouse_position_y]));
                    mouseMessage[6] = buf[Mouse_type];
                    fs->get_user_sink()->SendSinkMessage(mouseMessage, 7, (((unsigned)mouseMessage[0] << 24) & 0xff000000) | (((unsigned)mouseMessage[1] << 16) & 0xff0000) | 0);
                }
            }
        }
    }
    break;
    case AppSharingMessages::MOUSE_MOVE:
    {
        if(!forwardingSessions.empty()) {
            for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
                if(fs->get_remote_session_id() == (buf[Session_id] - 128) && fs->get_session_state() == Session_connected) {
                    if(!sharingSessions.empty()) {
                        class SharingSession * ss = sharingSessions.front();
                        while(ss) {
                            if(ss == fs->get_sharing_session()) {
                                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Conference ch=%p Forward Mouse move message to %p", this, appSharingIoChannel, ss->get_sharing_channel());

                                char mouseMessage[10];
                                mouseMessage[0] = AppSharingMessages::MOUSE_MOVE;
                                mouseMessage[1] = ss->get_sharing_session_id() + 128;
                                WRITE_DWORD_NET(&mouseMessage[2], READ_DWORD_NET(&buf[Mouse_move_token]));
                                WRITE_WORD_NET(&mouseMessage[6], READ_WORD_NET(&buf[Mouse_move_position_x]));
                                WRITE_WORD_NET(&mouseMessage[8], READ_WORD_NET(&buf[Mouse_move_position_y]));
                                mouseMessage[6] = buf[Mouse_type];
                                ss->get_sharing_channel()->SendSinkMessage(mouseMessage, 10, (((unsigned)mouseMessage[0] << 24) & 0xff000000) | (((unsigned)mouseMessage[1] << 16) & 0xff0000) | 0);
                                break;
                            }
                            ss = ss->goNext();
                        }
                    }
                }
            }
        }
    }
    break;
    };
}

void AppSharingIo::ForceCapture(byte sessionId)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::ForceCapture sharing=%d sessionId=%d", this, !shareApps.empty(), sessionId);

    if (!shareApps.empty()) {
        class Application* shareApp;
        for (shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
            if (shareApp->sessionId == sessionId) {
                shareApp->captureTimer->Cancel();
                imageBuffer->ForceCapture(shareApp->id);
                imageBuffer->Capture(shareApp->id, shareApp->screenBuffer, shareApp->coder);
                shareApp->captureTimer->Start(mediaConfiguration ? mediaConfiguration->appSharingCaptureTimer : FRAME_BUFFER_CAPTURE_TIMER);
                break;
            }
        }
    }
}

bool AppSharingIo::GetWindowPosition(byte sessionId, word * x, word * y, int * aspectRatio)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::GetWindowPosition sharing=%d sessionId=%d", this, !shareApps.empty(), sessionId);

    if (!shareApps.empty()) {
        class Application * shareApp;
        for (shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
            if (shareApp->sessionId == sessionId) {
                return imageBuffer->GetWindowPosition(shareApp->id, x, y, aspectRatio);
            }
        }
    }
    return false;
}

void AppSharingIo::TimerOnTimeout(ITimer * timer)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::TimerOnTimeout %p shareApps %d", this, timer, shareApps.empty());

    if(!shareApps.empty()) {
        class Application * shareApp;
        for(shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
            if(shareApp->captureTimer == timer) {
                imageBuffer->Capture(shareApp->id, shareApp->screenBuffer, shareApp->coder);
                shareApp->captureTimer->Start(mediaConfiguration ? mediaConfiguration->appSharingCaptureTimer : FRAME_BUFFER_CAPTURE_TIMER);
            }
        }
    }
    else if(timer == infoTimer && !sharingSessions.empty()) {
        bool infoSent = false;
        for(class SharingSession * ss = sharingSessions.front(); ss; ss = ss->goNext()) {
            while(ss->get_info_message()) {
                class InfoMessage * msg = ss->get_info_message();
#ifdef _SIG_TRACE_
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::TimerOnTimeout info_message session_id=%d seq=%d num=%u", this, msg->get_session_id(), msg->get_seq(), msg->get_num());
#endif
                msg->increase_counter();
                if(msg->get_counter() == 20) {
                    msg->remove();
                    delete msg;
                }
                else {
                    ss->get_sharing_channel()->SendSinkMessage(msg->get_data(), msg->get_len(), msg->get_num());
                    infoSent = true;
                    break;
                }
            }
        }
        if(infoSent) infoTimer->Start(20);
    }
    else if(timer == forwardingTimer && !forwardingSessions.empty()) {
        bool infoSent = false;
        for(class ForwardingSession * fs = forwardingSessions.front(); fs; fs = fs->goNext()) {
            while(fs->get_info_message()) {
                class InfoMessage * msg = fs->get_info_message();
#ifdef _SIG_TRACE_
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::TimerOnTimeout info_message session_id=%d seq=%d num=%u", this, msg->get_session_id(), msg->get_seq(), msg->get_num());
#endif
                msg->increase_counter();
                if(msg->get_counter() == 20) {
                    msg->remove();
                    delete msg;
                }
                else {
                    fs->get_sharing_channel()->SendSinkMessage(msg->get_data(), msg->get_len(), msg->get_num());
                    infoSent = true;
                    break;
                }
            }
        }
        if(infoSent) forwardingTimer->Start(20);
    }
    else if(timer == deleteTimer && !deletingSessions.empty()) {
        int activeSessions = 0;
        class ForwardingSession * fs = deletingSessions.front();
        while(fs) {
            class ForwardingSession * next = fs->goNext();
            if(fs->get_counter() < 20) {
                unsigned char deleteMessage[2];
                deleteMessage[Msg_type] = AppSharingMessages::DELETE_SESSION;
                deleteMessage[Session_id] = fs->get_remote_session_id();
#ifdef _SIG_TRACE_
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::TimerOnTimeout delete_session (remote_id=%d) cnt=%d", this, deleteMessage[Session_id], fs->get_counter());
#endif
                fs->get_user_sink()->SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[Msg_type] << 24) & 0xff000000) | (((unsigned)deleteMessage[Session_id] << 16) & 0xff0000) | fs->get_next_sequence());

                activeSessions++;
                fs->increase_counter();
            }
            else {
                fs->remove();
                delete fs;
            }
            fs = next;
        }
        if(activeSessions) deleteTimer->Start(25);
    }
}

void AppSharingIo::Initialize(class UAppSharingIo * const user, class UDeviceIo * const deviceIoUser)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Initialize user=%p mediaIoUser=%p", this, user, deviceIoUser);

    this->appSharingIoUser = user;
    this->deviceUser = deviceIoUser;
}

void AppSharingIo::SignalingMessage(void * context, void * buffer, int len)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::SignalingMessage len=%d msg=%u sessionId=%u", this, len, ((unsigned char *)buffer)[0], ((unsigned char *)buffer)[1]);

    if(!containers.empty() && !ioChannels.empty()) {
        class AppSharingContainerHelper * c;
        class AppSharingIoChannel * ch;
        for(c = containers.front(); c; c = c->goNext()) {
            for(ch = ioChannels.front(); ch; ch = ch->goNext()) {
                if(c->container == context && c->session == ch->GetContext()) {
                    ch->SignalingMessage(buffer, len);
                }
            }
        }
    }
}

void AppSharingIo::SubscribeApplications()
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::SubscribeApplications %d", this, subscribe);

    if(!subscribe) {
        if(!imageBuffer) {
            mediaConfiguration->appSharingVideo = false;
            imageBuffer = IImageBuffer::CreateImageBuffer(iomux, this, log, mediaConfiguration);
            imageBuffer->RegisterForClipboardUpdates();
        }
        if(imageBuffer) {
            subscribe = true;
            imageBuffer->SubscribeApplications();
        }
    }
    else {
        class Application * app;
        for (app = apps.front(); app; app = app->goNext()) {
            appSharingIoUser->AddAppSharingApplication(app->id, (char *)app->name, (char *)app->description, (char*)app->hwId, (char *)app->icon, (char *)app->thumbnail, app->isDesktop);
        }
    }
}

void AppSharingIo::UnsubscribeApplications()
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::UnsubscribeApplications %d", this, subscribe);

    if(subscribe && imageBuffer) imageBuffer->UnsubscribeApplications();
    subscribe = false;
}

void AppSharingIo::ReloadApplicationList()
{
    while (!apps.empty()) {
        if (subscribe) {
            class Application* app = apps.front();
            if (appSharingIoUser) appSharingIoUser->RemoveAppSharingApplication(app->id);
        }
        delete apps.front();
    }
    UnsubscribeApplications();
    SubscribeApplications();
}

void AppSharingIo::GiveControlToUser(void * context, unsigned int sessionId)
{
    //log->Log(LOG_APPSHARING, "AppSharingIo(%p)::GiveControlToUser %d ctx=%p", this, sessionId, context);
}

void AppSharingIo::RemoveControlFromUser(void * context, unsigned int sessionId)
{
    //log->Log(LOG_APPSHARING, "AppSharingIo(%p)::RemoveControlFromUser %d ctx=%p", this, sessionId, context);
}

void AppSharingIo::RequestControlFromUser(void * context, unsigned int sessionId)
{
    //log->Log(LOG_APPSHARING, "AppSharingIo(%p)::RequestControlFromUser %d ctx=%p", this, sessionId, context);
}

void AppSharingIo::ShareApplication(void * context, unsigned int id, bool admin)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p,%p)::ShareApplication %d apps=%d", this, imageBuffer, id, apps.empty());

    if(imageBuffer) {
        if(!apps.empty()) {
            class Application * app = NULL;
            for (app = apps.front(); app; app = app->goNext()) {
                if(app->id == id) {
                    class Application * shareApp = NULL;
                    for (shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
                        if(shareApp->id == app->id) {
                            break;
                        }
                    }
                    if(shareApp == NULL) { 
                        shareApp = new class Application(app->id, GetSessionId(), app->name, app->description, app->hwId, app->icon, app->thumbnail, app->handle, context, app->isDesktop);
                        shareApps.push_back(shareApp);
                        shareApp->coder = AppSharingCoder::JRFB;
                        if (ioChannels.empty() == false) {
                            class AppSharingIoChannel* ch = ioChannels.front();
                            while (ch) {
                                if (ch->GetCoder() == AppSharingCoder::JRFB2) {
                                    log->Log(LOG_APPSHARING, "AppSharingIo(%p,%p)::ShareApplication remote control protocol session id=%d", this, imageBuffer, shareApp->sessionId);
                                    shareApp->coder = AppSharingCoder::JRFB2;
                                    break;
                                }
                                ch = ch->goNext();
                            }
                        }
                        shareApp->captureTimer = new ITimer(iomux, this);
                        if(shareApp->coder == AppSharingCoder::JRFB) shareApp->screenBuffer = IScreenBuffer::Create(log);
                        if(ioChannels.empty() == false) {
                            class AppSharingIoChannel * ch = ioChannels.front();
                            while(ch) {
                                if(context == ch->GetContext()) {
                                    if (shareApp->coder == AppSharingCoder::JRFB) ch->CreateScreenSink(shareApp->id, shareApp->sessionId, (char *)shareApp->name, (char *)shareApp->description, shareApp->screenBuffer, mediaConfiguration ? mediaConfiguration->appSharingBitrate : 500, mediaConfiguration ? mediaConfiguration->appSharingWaitMsForAck : 200, displayname);
                                    else if (shareApp->coder == AppSharingCoder::JRFB2) ch->CreateRemoteControlSession(shareApp->id, shareApp->sessionId, (char*)shareApp->name, (char*)shareApp->description, shareApp->screenBuffer, mediaConfiguration ? mediaConfiguration->appSharingBitrate : 500, mediaConfiguration ? mediaConfiguration->appSharingWaitMsForAck : 200, displayname);
                                }
                                ch = ch->goNext();
                            }
                        }
                    }
                    else RequestNewImage(shareApp->id);
                    shareApp->adminCapture = admin;
                    if (shareApp->adminCapture) {
                        if (ioChannels.empty() == false) {
                            class AppSharingIoChannel* ch = ioChannels.front();
                            while (ch) {
                                ch->StartAdminCapture();
                                ch = ch->goNext();
                            }
                        }
                    }
                    imageBuffer->Capture(shareApp->id, shareApp->screenBuffer, shareApp->coder);
                    shareApp->captureTimer->Start(500); // Leave sometime to stablish the session
                    break;
                }
            }
            if(app == NULL) log->Log(LOG_APPSHARING, "AppSharingIo(%p)::ShareApplication App not found %d", this, id);
        }
    }
}

void AppSharingIo::UnshareAllApplications(void * context)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::UnshareAllApplication (%d)", this, !shareApps.empty());

    if(!shareApps.empty()) {
        class Application * shareApp = shareApps.front();
        while(shareApp) {
            class Application * shareAppNext = shareApp->goNext();
            UnshareApplication(context, shareApp->id);
            shareApp = shareAppNext;
        }
    }
}

void AppSharingIo::UnshareApplication(void * context, unsigned int id)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::UnshareApplication %d", this, id);

    if(!shareApps.empty()) {
        class Application * shareApp = NULL;
        for (shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
            if(shareApp->id == id) {
                if(imageBuffer) imageBuffer->Clean(shareApp->id);
                if(ioChannels.empty() == false) {
                    class AppSharingIoChannel * ch = ioChannels.front();
                    while(ch) {
                        if(context == ch->GetContext()) {
                            if(ch->GetCoder() == AppSharingCoder::JRFB) ch->RemoveScreenSink(shareApp->screenBuffer);
                            else if (ch->GetCoder() == AppSharingCoder::JRFB2) ch->RemoveRemoteControlSession(shareApp->id);
                        }
                        ch = ch->goNext();
                    }
                }
                shareApp->captureTimer->Cancel();
                shareApp->remove();
                delete shareApp;
                break;
            }
        }
        if(shareApp == NULL) log->Log(LOG_APPSHARING, "AppSharingIo(%p)::UnshareApplication application not found %d", this, id);
    }
}

void AppSharingIo::MouseWheel(int delta) 
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::MouseWheel delta=%d", this, delta);

    if (imageBuffer) imageBuffer->MouseWheel(delta);
}

void AppSharingIo::MouseButtonDown(enum MOUSE_KEY key)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::MouseButtonDown key=%d", this, key);

    if (imageBuffer) imageBuffer->MouseButtonDown(key);
}

void AppSharingIo::MouseButtonUp(enum MOUSE_KEY key)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::MouseButtonUp key=%d", this, key);

    if (imageBuffer) imageBuffer->MouseButtonUp(key);
}

void AppSharingIo::RxClipboardUpdate(const byte* clipboard, int len)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::RxClipboardUpdate len=%d", this, len);

    if (imageBuffer) imageBuffer->ClipboardUpdate(clipboard, len, true);
}

void AppSharingIo::ClipboardUpdate(const char * clipboard, int len)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::ClipboardUpdate len=%d ch=%d sharing=%d", this, len, ioChannels.front() != NULL, IsSharing());

    // If you are sharing do not send updates to remote side, loop
    //if (IsSharing()) return;

    class AppSharingIoChannel* ch = ioChannels.front();
    while (ch) {
        ch->SendClipboardUpdate(clipboard, len);
        ch = ch->goNext();
    }
}

void AppSharingIo::EndSendRemoteControlData(int appId)
{
    class AppSharingIoChannel* ch = ioChannels.front();
    while (ch) {
        ch->SendImageBlocks((unsigned int)appId);
        ch = ch->goNext();
    }
}

int AppSharingIo::SendRemoteControlData(class AppSharingIoChannel* appSharingIoChannel, int appId)
{
    int dataSent = 0;
    class IImage* img;
    if ((img = imageBuffer->GetImageBlock(appId)) != NULL) {
        class AppSharingIoChannel* ch = ioChannels.front();
        while (ch) {
            ch->ImageBlock(img, appId);
            ch = ch->goNext();
        }
        dataSent = img->len;
        if(!img->ref) delete img;
    }
    return dataSent;
}

void AppSharingIo::Add(class AppSharingIoChannel * appSharingIoChannel)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Add Channel %p", this, appSharingIoChannel);

    ioChannels.push_back(appSharingIoChannel);

    // In case another channel is sharing...
    if(!shareApps.empty()) {
        class Application * shareApp = shareApps.front();
        while(shareApp) {
            if(shareApp->context == appSharingIoChannel->GetContext()) {
                if (shareApp->coder == AppSharingCoder::JRFB2) appSharingIoChannel->CreateRemoteControlSession(shareApp->id, shareApp->sessionId, (char*)shareApp->name, (char*)shareApp->description, shareApp->screenBuffer, mediaConfiguration ? mediaConfiguration->appSharingBitrate : 500, mediaConfiguration ? mediaConfiguration->appSharingWaitMsForAck : 200, displayname);
                else appSharingIoChannel->CreateScreenSink(shareApp->id, shareApp->sessionId, (char *)shareApp->name, (char *)shareApp->description, shareApp->screenBuffer, mediaConfiguration ? mediaConfiguration->appSharingBitrate : 500, mediaConfiguration ? mediaConfiguration->appSharingWaitMsForAck : 200, displayname);
            }
            shareApp = shareApp->goNext();
        }
    }
    // Create all active Sessions in this channel
    if(!sharingSessions.empty()) {
        class SharingSession * ss = sharingSessions.front();
        while(ss) {
            if(ss->get_sharing_channel() && appSharingIoChannel != ss->get_sharing_channel() && appSharingIoChannel->GetContext() == ss->get_sharing_channel()->GetContext()) {
                class ForwardingSession * nSession = new class ForwardingSession(iomux, appSharingIoChannel, ss, ss->get_sharing_channel(), appSharingIoChannel, GetSessionId(), mediaConfiguration->appSharingBitrate, mediaConfiguration->appSharingWaitMsForAck, ss->get_app_name(), ss->get_app_desc(), ss->get_sender_name(), log);

                forwardingSessions.push_back(nSession);

                appSharingIoChannel->RegisterForwardingSession(nSession->get_remote_session_id());
            }
            ss = ss->goNext();
        }
    }
}

void AppSharingIo::Reset()
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Reset (%d) imageBuffer=%p sharing=%d", this, subscribe, imageBuffer, shareApps.empty());

    seq = 0;
    if(!shareApps.empty()) {
        while (shareApps.front()) {
            class Application * a = shareApps.front();
            if (imageBuffer) imageBuffer->Clean(a->id);
            if(ioChannels.empty() == false) {
                class AppSharingIoChannel * ch = ioChannels.front();
                while(ch) {
                    ch->RemoveScreenSink(a->screenBuffer);
                    ch = ch->goNext();
                }
            }
            delete shareApps.front();
        }
    }
    if(wdisplayname) free(wdisplayname);
    wdisplayname = NULL;
    if(displayname) free(displayname);
    displayname = NULL;
    sessionId = 0;
}

void AppSharingIo::Remove(class AppSharingIoChannel * appSharingIoChannel)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Remove %p ch=%d front=%p", this, appSharingIoChannel, ioChannels.empty(), (ioChannels.empty() == false ? ioChannels.front() : NULL));

    if(!ioChannels.empty()) {
        class AppSharingIoChannel * ch = ioChannels.front();
        while (ch) {
            class AppSharingIoChannel * next = ch->goNext();
            if(ch == appSharingIoChannel) {
                ch->remove();
                break;
            }
            ch = next;
        }
    }
    if(ioChannels.empty()) {
        if(this->shutDown && imageBuffer == NULL) {
            appSharingIoUser->CloseAppSharingIoComplete();
            return;
        }
        Reset();
    }
    if(!forwardingSessions.empty()) {
        class ForwardingSession * fs = forwardingSessions.front();
        while(fs) {
            class ForwardingSession * next = fs->goNext();
            if(fs->get_forwarding_channel() == appSharingIoChannel) {
                unsigned char deleteMessage[2];
                deleteMessage[Msg_type] = AppSharingMessages::DELETE_SESSION;
                deleteMessage[Session_id] = fs->get_remote_session_id();
#ifdef _SIG_TRACE_
                log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Remove fs delete_remote_session (sharing_id=%i remote_id=%i)", this, fs->get_sharing_session_id(), deleteMessage[Session_id]);
#endif
                fs->get_user_sink()->SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[Msg_type] << 24) & 0xff000000) | (((unsigned)deleteMessage[Session_id] << 16) & 0xff0000) | fs->get_next_sequence());
                fs->set_session_state(Session_closing);
                fs->unregister_sink();
                fs->reset_counter();
                fs->get_forwarding_channel()->UnregisterForwardingSession(fs->get_remote_session_id());
                // Remove participant name from the corresponding sharing session
                if(!sharingSessions.empty()) {
                    for(class SharingSession * ss = sharingSessions.front(); ss; ss = ss->goNext()) {
                        if(ss->get_sharing_channel() && (ss->get_sharing_channel() != appSharingIoChannel) && (ss->get_sharing_channel()->GetContext() == appSharingIoChannel->GetContext()) && (ss == fs->get_sharing_session()) && fs->get_participant()) {
                            int infoMessageLen = 4 + 1 + strlen(fs->get_participant()) + 1;
                            char * infoMessage = (char *)malloc(infoMessageLen);
                            infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
                            infoMessage[1] = fs->get_sharing_session_id() + 128;
                            infoMessage[2] = ss->get_info_seq();
                            infoMessage[3] = 1 + ((char)strlen(fs->get_participant()) + 1);
                            infoMessage[4] = 0x1; // Info(RemParticipant)
                            memcpy(&infoMessage[5], fs->get_participant(), strlen(fs->get_participant()) + 1);

                            class InfoMessage * msg = new class InfoMessage(infoMessage, infoMessageLen, ss->get_sharing_session_id(), infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                            if(ss->get_info_message() == NULL) ss->get_sharing_channel()->SendSinkMessage(infoMessage, infoMessageLen, (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
                            infoTimer->Start(20);
                            ss->add_info_message(msg);
                        }
                    }
                }
                fs->remove();
                delete fs;
            }
            fs = next;
        }
    }
    if(!sharingSessions.empty()) {
        class SharingSession * ss = sharingSessions.front();
        while(ss) {
            class SharingSession * next = ss->goNext();
            if(ss->get_sharing_channel() == appSharingIoChannel) {
                if(!forwardingSessions.empty()) {
                    class ForwardingSession * fs = forwardingSessions.front();
                    while(fs) {
                        class ForwardingSession * next = fs->goNext();
                        if(fs->get_sharing_session() == ss) {
                            unsigned char deleteMessage[2];
                            deleteMessage[Msg_type] = AppSharingMessages::DELETE_SESSION;
                            deleteMessage[Session_id] = fs->get_remote_session_id();
#ifdef _SIG_TRACE_
                            log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Remove ss delete_remote_session (sharing_id=%d remote_id=%d)", this, fs->get_sharing_session_id(), deleteMessage[Session_id]);
#endif
                            fs->get_user_sink()->SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[Msg_type] << 24) & 0xff000000) | (((unsigned)deleteMessage[Session_id] << 16) & 0xff0000) | fs->get_next_sequence());
                            fs->set_session_state(Session_closing);
                            fs->unregister_sink();
                            fs->reset_counter();
                            fs->remove();
                            fs->get_forwarding_channel()->UnregisterForwardingSession(fs->get_remote_session_id());
                            deletingSessions.push_back(fs);
                            deleteTimer->Start(20);
                        }
                        fs = next;
                    }
                }
                // Remove containers in the UI
                /*unsigned char deleteMessage[2];
                deleteMessage[Msg_type] = AppSharingMessages::DELETE_SESSION;
                deleteMessage[Session_id] = ss->get_sharing_session_id();
                SctpRecv(appSharingIoChannel, deleteMessage, 2);*/
                ss->remove();
                delete ss;
            }
            ss = next;
        }
    }
    if(!deletingSessions.empty()) {
        class ForwardingSession * fs = deletingSessions.front();
        while(fs) {
            class ForwardingSession * next = fs->goNext();
            if(fs->get_forwarding_channel() == appSharingIoChannel) {
                fs->remove();
                delete fs;
            }
            fs = next;
        }
    }
}

class Application * AppSharingIo::FindApplicationById(unsigned int appId)
{
    log->Log(LOG_APPSHARING, "AppSharingIo: FindApplication %d", appId);

    if(!shareApps.empty()) {
        class Application *app;
        for(app = shareApps.front(); app; app = app->goNext()) {
            if(app->id == appId) {
                return app;
            }
        }
    }
    return NULL;
}

void AppSharingIo::SetDn(const char * dn)
{
    if(!this->wdisplayname) {
        this->wdisplayname = (word *)malloc((strlen(dn) + 1) << 1);
        memset(this->wdisplayname, 0x0, (strlen(dn) + 1) << 1);
        str::latin1_to_ucs2_n(dn, (word *)this->wdisplayname, strlen(dn) + 1);
    }
    if(!this->displayname) this->displayname = _strdup(dn);
}

void AppSharingIo::Add(unsigned int id, const char * name, const char * description, const char * hwId, const char * icon,  const char * thumbnail, void * handle, bool isDesktop)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Add(%d) %d (%s,%d) hwId=%s handle=%x isDesktop=%d", this, subscribe, id, name, strlen(name), hwId ? hwId : "", handle, isDesktop);
    if(!subscribe) return;

    class Application * app = new class Application(id, 0, name, description, hwId, icon, thumbnail, handle, NULL, isDesktop);
    apps.push_back(app);
    if(appSharingIoUser) appSharingIoUser->AddAppSharingApplication(id, name, description, hwId, icon, thumbnail, isDesktop);
}

void AppSharingIo::Update(unsigned int id, const char * name, const char * description, const char * hwId, const char * icon,  const char * thumbnail)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Update(%d) Application %d (%s) hwId=%s", this, subscribe, id, name, hwId ? hwId : "");
    if(!subscribe) return;

    class Application * app;
    for (app = apps.front(); app; app = app->goNext()) {
        if(app->id == id) {
            app->Update(id, name, description, hwId, icon, thumbnail);
            appSharingIoUser->UpdateAppSharingApplication(id, name, description, hwId, icon, thumbnail);
            return;
        }
    }
}

void AppSharingIo::Remove(unsigned int id)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::Remove(%d) Application %d", this, subscribe, id);

    class Application * app;
    for (app = apps.front(); app; app = app->goNext()) {
        if(app->id == id) {
            class Application * shareApp;
            for(shareApp = shareApps.front(); shareApp; shareApp = shareApp->goNext()) {
                if(shareApp->id == id) {
                    if (imageBuffer) imageBuffer->Clean(app->id);
                    if(ioChannels.empty() == false) {
                        class AppSharingIoChannel * ch = ioChannels.front();
                        while(ch) {
                            ch->RemoveScreenSink(shareApp->screenBuffer);
                            ch = ch->goNext();
                        }
                    }
                    shareApp->remove();
                    delete shareApp;
                    break;
                }
            }
            app->remove();
            delete app;
            if(subscribe) appSharingIoUser->RemoveAppSharingApplication(id);
            return;
        }
    }
}

void AppSharingIo::UpdateResolution(int dimX, int dimY, unsigned int appId)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::UpdateResolution dimX=%d dimY=%d appId=%d", this, dimX, dimY, appId);

    if (ioChannels.empty() == false) {
        class AppSharingIoChannel* ch = ioChannels.front();
        while (ch) {
            ch->UpdateResolution(dimX, dimY, appId);
            ch = ch->goNext();
        }
    }
}

void AppSharingIo::MousePosition(int coorX, int coorY, int type, unsigned int appId)
{
    if(ioChannels.empty() == false) {
        class AppSharingIoChannel * ch = ioChannels.front();
        while(ch) {
            ch->MousePosition(coorX, coorY, type, appId);
            ch = ch->goNext();
        }
    }
}

void AppSharingIo::IsSecureDesktop(bool secure)
{
    if (ioChannels.empty() == false) {
        class AppSharingIoChannel* ch = ioChannels.front();
        while (ch) {
            ch->IsSecureDesktop(secure);
            ch = ch->goNext();
        }
    }
}

void AppSharingIo::ForceSendData(int appId)
{
    log->Log(LOG_APPSHARING, "AppSharingIo(%p)::ForceSendData appId=%d", this, appId);

    class IImage* img;
    if ((img = imageBuffer->GetImageBlock(appId)) != NULL) {
        class AppSharingIoChannel* ch = ioChannels.front();
        while (ch) {
            ch->ImageBlock(img, appId);
            ch = ch->goNext();
        }
        if (!img->ref) delete img;
    }
    class AppSharingIoChannel* ch = ioChannels.front();
    while (ch) {
        ch->SendImageBlocks(appId);
        ch = ch->goNext();
    }
}

class IAppSharingIoChannel * IAppSharingIoChannel::Create(class IIoMux * const iomux, class IAppSharingIo * mediaIo, class UMediaIoChannel * const user, class IInstanceLog * log, const char * dn, class MediaConfiguration * mediaConfiguration)
{
    return new AppSharingIoChannel(iomux, mediaIo, user, log, dn, mediaConfiguration);
}

AppSharingIoChannel::AppSharingIoChannel(class IIoMux * const iomux, class IAppSharingIo * mediaIo, class UMediaIoChannel * const user, class IInstanceLog * log, const char * dn, class MediaConfiguration * mediaConfiguration) :
appSharingIo((AppSharingIo *)mediaIo),
user(user),
log(log),
iomux(iomux)
{
    this->open = false;
    this->context = NULL;
    this->channelId = NULL;
    this->type = MediaType::WEBM_VP8_CONTAINER;
    this->coder = AppSharingCoder::JRFB;
    this->numTxProcSamples = 0;
    this->procTxDataTot = 0;
    this->procTxDataPar = 0;
    this->firstTxTimeStamp = 0;
    this->partialTxTimeStamp = 0;
    this->numRxProcSamples = 0;
    this->procRxDataTot = 0;
    this->procRxDataPar = 0;
    this->firstRxTimeStamp = 0;
    this->partialRxTimeStamp = 0;
    this->deleteTimer = new ITimer(iomux, this);
    this->clipboardTimer = new ITimer(iomux, this);
    this->dn = _strdup(dn);
    this->infoMsgSeq = 0;
    this->token = 0; 
    this->lastInfoMessage = 0xff;
    for(int i = 0; i < 256; i++) {
        rxSessionIds[i] = -1;
        txSessionIds[i] = -1;
        fwdSessionIds[i] = -1;
    }
    this->remoteControl = IRemoteControl::Create(iomux, this, log, (AppSharingAppType)mediaConfiguration->remoteControl);
    this->closed = false;
    this->closing = false;
    this->remoteParticipant = NULL;
    this->mediaConfiguration = mediaConfiguration;
    this->lastXCoor = 0;
    this->lastYCoor = 0;
    this->lastMouseType = 0;
    this->initLastProcInfoSequence = false;
    this->lastProcInfoSequence = 65535;
    this->clipboardTxId = 1;
    this->clipboardRx = NULL;
    this->clipboardRxLen = 0;

    appSharingIo->SetDn(dn);
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::AppSharingIoChannel user=%p", this, user);
}

AppSharingIoChannel::~AppSharingIoChannel()
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::~AppSharingIoChannel", this);

    closed = true;

    if (!this->remoteControlSessions.empty()) {
        while (this->remoteControlSessions.front()) {
            delete this->remoteControlSessions.front();
        }
    }

    if (!this->images.empty()) {
        while (this->images.front()) {
            class IImageHelper* helper = images.front();
            helper->img->ref--;
            if (!helper->img->ref) delete helper->img;
            delete helper;
        }
    }

    if(remoteParticipant) free(remoteParticipant);
    if(dn) free(dn);
    delete deleteTimer;
    delete clipboardTimer;

    appSharingIo->Remove(this);
}

void AppSharingIoChannel::Initialize(void * context, const char * channelId, enum AppSharingCoder coder)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::Initialize coder=%d", this, coder);
    this->context = context;
    if(this->channelId) free(this->channelId);
    this->channelId = _strdup(channelId);
    this->type = MediaType::APPSHARING_CONTAINER;
    this->coder = coder;
    appSharingIo->CheckRemoteContainers(context, channelId, MediaType::APPSHARING_CONTAINER);
}

void AppSharingIoChannel::Open()
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::Open channel", this);
    if(open) return;
    open = true;
    appSharingIo->Add(this);
}

void AppSharingIoChannel::SignalingMessage(void * buf, int len)
{
    if (coder == AppSharingCoder::JRFB2) {
        RemoteControlUiMessageRecv(buf, len);
        return;
    }

    byte * b = (byte *)buf;

    debug->hexdump(b, len);

    byte msg = b[0];
    byte sId = b[1];
    // Messages 
    if(sId >= 128) {
        if(txSessionIds[sId - 128] == -1) {
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SignalingMessage not for this channel session=%d sId=%d", this, txSessionIds[sId - 128], sId);
            return;
        }
        byte remoteSessionId = txSessionIds[sId - 128];
        b[1] = remoteSessionId + 128;
    }
    else {
        if(txSessionIds[sId + 128] == -1) {
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SignalingMessage not for this channel session=%d sId=%d", this, txSessionIds[sId + 128], sId);
            return;
        }
        byte remoteSessionId = txSessionIds[sId + 128];
        b[1] = remoteSessionId - 128;
    }

    if(msg == AppSharingMessages::INFO_MESSAGE) {
        if(b[4] == 0x2) {
            token = READ_DWORD_NET(&b[5]);
        }
        else if(b[4] == 0x3) {
            token = 0xffffffff;
        }
    }

    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SignalingMessage msg=%d Sessions %d -> %d token=%x", this, msg, sId, sId >= 128 ? txSessionIds[sId - 128] : sId, token);

    SendSinkMessage(buf, len, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);

    b[1] = sId;
}

void AppSharingIoChannel::StartAdminCapture()
{
    if (remoteControl) remoteControl->StartAdminCapture();
}

void AppSharingIoChannel::CreateScreenSink(int appId, byte sessionId, char * appName, char * appDesc, class IScreenBuffer * screenBuffer, int bitrate, int waitMsForAck, const char * senderName)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::CreateScreenSink screenBuffer=%p", this, screenBuffer);

    class IScreenSink * sink = IScreenSink::Create(iomux, this, screenBuffer, sessionId, bitrate, waitMsForAck, appName, appDesc, senderName, log);
    sessions.push_back(new class AppSharingIoChannelSinkSession(appId, sessionId, sink, screenBuffer));
    screenBuffer->RegisterScreenSink(sink);
}

void AppSharingIoChannel::RemoveScreenSink(class IScreenBuffer * screenBuffer)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoveScreenSink screenBuffer=%p", this, screenBuffer);

    if(!sessions.empty()) {
        class AppSharingIoChannelSinkSession * h;
        for(h = sessions.front(); h; h = h->goNext()) {
            if(h->buffer == screenBuffer) {
                screenBuffer->UnregisterScreenSink(h->sink);
                h->buffer = NULL;
                h->closing = true;
                delete h->sink;
                h->sink = NULL;

                char * deleteMessage = (char *)malloc(2);
                deleteMessage[0] = AppSharingMessages::DELETE_SESSION;
                deleteMessage[1] = h->sessionId;
                SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[0] << 24) & 0xff000000) | (((unsigned)deleteMessage[1] << 16) & 0xff0000) | 0);
                free(deleteMessage);
                deleteTimer->Start(50);
                return;
            }
        }
    }
}

void AppSharingIoChannel::IsSecureDesktop(bool secure)
{
    if (remoteControl) remoteControl->IsSecureDesktop(secure);
}

void AppSharingIoChannel::MousePosition(int coorX, int coorY, int type, unsigned int appId)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel::MousePosition (%d,%d) (%d,%d) type=%d", lastXCoor, lastYCoor, coorX, coorY, type);

    if ((ABS(lastXCoor-coorX) <= 1) && (ABS(lastYCoor-coorY) <= 1)) {
        if (lastMouseType != type) {
            if (!sessions.empty()) {
                class AppSharingIoChannelSinkSession* s;
                for (s = sessions.front(); s; s = s->goNext()) {
                    if (s->appId == appId && s->connected) {
                        char* imageMessage = (char*)malloc(3);
                        imageMessage[0] = AppSharingMessages::MOUSE_TYPE_CHANGED;
                        imageMessage[1] = s->sessionId;
                        imageMessage[2] = type;

                        SendSinkMessage(imageMessage, 3, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                        SendSinkMessage(imageMessage, 3, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                        SendSinkMessage(imageMessage, 3, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                        free(imageMessage);
                    }
                }
            }
            if (!remoteControlSessions.empty()) {
                class RemoteControlSession* rc;
                for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
                    if (rc->appId == appId && rc->sessionCreated && !rc->closing) {
                        char* imageMessage = (char*)malloc(7);
                        imageMessage[0] = AppSharingMessages::MOUSE_TYPE_CHANGED;
                        imageMessage[1] = rc->sessionId;
                        imageMessage[2] = type;

                        SendSinkMessage(imageMessage, 3, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                        SendSinkMessage(imageMessage, 3, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                        SendSinkMessage(imageMessage, 3, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                        free(imageMessage);
                    }
                }
            }
        }
        lastXCoor = coorX;
        lastYCoor = coorY;
        lastMouseType = type;
        return;
    }

    lastXCoor = coorX;
    lastYCoor = coorY;

    if(!sessions.empty()) {
        class AppSharingIoChannelSinkSession * s;
        for(s = sessions.front(); s; s = s->goNext()) {
            if(s->appId == appId && s->connected) {
                char * imageMessage = (char *)malloc(7);
                imageMessage[0] = AppSharingMessages::MOUSE_CURSOR;
                imageMessage[1] = s->sessionId;
                WRITE_WORD_NET(&imageMessage[2], coorX);
                WRITE_WORD_NET(&imageMessage[4], coorY);
                imageMessage[6] = type;

                SendSinkMessage(imageMessage, 7, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                free(imageMessage);
            }
        } 
    }
    if (!remoteControlSessions.empty()) {
        class RemoteControlSession* rc;
        for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
            if (rc->appId == appId && rc->sessionCreated && !rc->closing) {
                char* imageMessage = (char*)malloc(7);
                imageMessage[0] = AppSharingMessages::MOUSE_CURSOR;
                imageMessage[1] = rc->sessionId;
                WRITE_WORD_NET(&imageMessage[2], coorX);
                WRITE_WORD_NET(&imageMessage[4], coorY);
                imageMessage[6] = type;

                SendSinkMessage(imageMessage, 7, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | 0);
                free(imageMessage);
            }
        }
    }
}

void AppSharingIoChannel::ReceiveMessage(void * buf, size_t len)
{
    appSharingIo->SctpRecv(this, buf, len);
}

void AppSharingIoChannel::ReportBlockLost(int appId, int posX, int posY, int dimX, int dimY)
{
    appSharingIo->ReportBlockLost(appId, posX, posY, dimX, dimY);
    if (!remoteControlSessions.empty()) {
        class RemoteControlSession* rc;
        for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
            if (rc->appId == appId) {
                rc->ReportBlockLost();
            }
        }
    }
}

void AppSharingIoChannel::RequestNewImage(int appId)
{
    appSharingIo->RequestNewImage(appId);
}

void AppSharingIoChannel::SendSinkMessage(void * buf, size_t len, unsigned num)
{
#ifdef _SIG_TRACE_
    if(num != 0x8000000) log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SendSinkMessage num=%x len=%d closed=%d", this, num, len, closed);
#endif
    if(closed) return;

    ulong64 tm = ITime::TimeStampMilliseconds();
    if(numTxProcSamples) {
        if(((numTxProcSamples & 0x1ff) == 0) && (firstTxTimeStamp != tm)) {
            int timeDiffPar = (int)(tm - partialTxTimeStamp);
            int timeDiffTot = (int)(tm - firstTxTimeStamp);
            int bitratePar = (int)((procTxDataPar * 8) / timeDiffPar);
            int bitrateTot = (int)((procTxDataTot * 8) / timeDiffTot);
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpSend tx-samples:%d Bytes:%d-%d tDiff: %d-%d msec BitRate:%d-%d kbps", this, numTxProcSamples, procTxDataPar, procTxDataTot, timeDiffPar, timeDiffTot, bitratePar, bitrateTot);
            partialTxTimeStamp = tm;
            procTxDataPar = 0;
        }
    }
    else {
        partialTxTimeStamp = tm;
        firstTxTimeStamp = tm;
    }
    procTxDataTot += len + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + DTLS_HEADER_DATA_LEN;
    procTxDataPar += len + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + DTLS_HEADER_DATA_LEN;
    numTxProcSamples++;

    user->MediaIoSctpSend(buf, len, num);
}

void AppSharingIoChannel::SctpRecvAck(unsigned num)
{
    //log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecvAck num=%x sessions=%d coder=%d", this, num, !sessions.empty(), coder);

    if (coder == AppSharingCoder::JRFB2) return;

    if(!sessions.empty()) {
        class AppSharingIoChannelSinkSession * h;
        for(h = sessions.front(); h; h = h->goNext()) {
            if(h->sessionId == ((num & 0xff0000)>>16)) {
                if(h->sink) h->sink->RecvAck(num);
                return;
            }
        }
    }
    appSharingIo->ConferenceSctpRecvAck(this, num);
}

void AppSharingIoChannel::RegisterForwardingSession(byte sessionId)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RegisterForwardingSession sessionId=%d", this, sessionId);
    fwdSessionIds[sessionId] = 1;
}

void AppSharingIoChannel::UnregisterForwardingSession(byte sessionId)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::UnregisterForwardingSession sessionId=%d", this, sessionId);
    fwdSessionIds[sessionId] = -1;
}

void AppSharingIoChannel::SctpRecv(void * buf, size_t len)
{
    if(appSharingIo->IsPhoneLoad()) {
        RecvPhoneLoad(buf, len);
        return; 
    }

    ulong64 tm = ITime::TimeStampMilliseconds();
    if(firstRxTimeStamp) {
        if((firstRxTimeStamp != tm) && (partialRxTimeStamp != tm) && ((numRxProcSamples & 0x1ff) == 0)) {
            int timeDiffPar = (int)(tm - partialRxTimeStamp);
            int timeDiffTot = (int)(tm - firstRxTimeStamp);
            int bitratePar = (int)((procRxDataPar * 8) / timeDiffPar);
            int bitrateTot = (int)((procRxDataTot * 8) / timeDiffTot);
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv rx-samples:%d Bytes: %d,%d tDiff: %d-%d msec BitRate: %d-%d kbps", this, numRxProcSamples, procRxDataPar, procRxDataTot, timeDiffPar, timeDiffTot, bitratePar, bitrateTot);
            partialRxTimeStamp = tm;
            procRxDataPar = 0;
        }
    }
    else {
        partialRxTimeStamp = tm;
        firstRxTimeStamp = tm;
    }
    procRxDataTot += len + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + DTLS_HEADER_DATA_LEN;
    procRxDataPar += len + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + DTLS_HEADER_DATA_LEN;
    numRxProcSamples++;

    if (coder == AppSharingCoder::JRFB2) {
        RemoteControlMessageRecv(buf, len);
        return;
    }

    byte * tbuf = (byte *)buf;

    if(tbuf[0] != AppSharingMessages::MOUSE_MOVE && tbuf[0] != AppSharingMessages::MOUSE_CURSOR && tbuf[0] != AppSharingMessages::IMAGE_MESSAGE) log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv msg=%d sessionId=%d(f=%d) len=%d fwd=%d token=%x", this, tbuf[0], tbuf[1], rxSessionIds[tbuf[1]], len, (tbuf[1] > 128 ? fwdSessionIds[tbuf[1] - 128] : -1), token);

    if((tbuf[1] >= 128) && (fwdSessionIds[tbuf[1] - 128] != -1)) {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv forward message sessionId=%d lastInfoMessage=%d(seq=%d)", this, tbuf[1], lastInfoMessage, (tbuf[0] == AppSharingMessages::INFO_MESSAGE) ? tbuf[2] : -1);
        if(tbuf[0] == AppSharingMessages::INFO_MESSAGE) {
            if(tbuf[4] == 0x0) {  // AddParticipant
                if(remoteParticipant) free(remoteParticipant);
                remoteParticipant = (char *)malloc(tbuf[Info_message_length] - 1);
                memset(remoteParticipant, 0x0, tbuf[Info_message_length] - 1);
                memcpy(remoteParticipant, &tbuf[Info_message_type + 1], tbuf[Info_message_length] - 1);
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv Info_message::RemoteParticipantName(%d) (%s)", this, tbuf[Info_message_length] - 5, remoteParticipant);
                debug->hexdump(remoteParticipant, tbuf[Info_message_length] - 1);
            }
            else if(tbuf[4] == 0x1) {  // RemParticipant
                if(remoteParticipant) free(remoteParticipant);
                remoteParticipant = NULL;
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv Info_message::RemParticipant", this);
            }
            if(lastInfoMessage == tbuf[2]) return;
            lastInfoMessage = tbuf[2];
        }
    }
    else {
        class AppSharingIoChannelSinkSession * h = NULL;

        // need to overwrite sessionId for PhoneApp. There could be a collision in a third party conference.
        // CREATE_SESSION_ACK is needed because appsharing_io uses same sessionId for both channel
        byte rxSessionId = tbuf[1];
        if((tbuf[0] == AppSharingMessages::CREATE_SESSION || tbuf[0] == AppSharingMessages::CREATE_SESSION_ACK) && rxSessionIds[rxSessionId] == -1) {
            byte newSessionId = appSharingIo->GetSessionId();
            newSessionId += (tbuf[0] == AppSharingMessages::CREATE_SESSION_ACK) * 128;
            rxSessionIds[rxSessionId] = newSessionId;
            txSessionIds[newSessionId] = rxSessionId;
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv new forward sessionId (%d->%d->%d)", this, rxSessionId, rxSessionIds[rxSessionId], txSessionIds[newSessionId]);
        }
        bool sentToContainer = true;
        if(tbuf[0] == AppSharingMessages::INFO_MESSAGE) {
            if(tbuf[4] == 0x2 || tbuf[4] == 0x3) {
                const char * jparticipant = NULL;
                char * participant = (char *)malloc(tbuf[Info_message_length] - 5);  // token+length
                memset(participant, 0x0, tbuf[Info_message_length] - 5);
                memcpy(participant, &tbuf[Info_message_type + 5], tbuf[Info_message_length] - 5);
                debug->hexdump(participant, tbuf[Info_message_length] - 5);

                sentToContainer = false;

                json_io json(participant);
                if(json.decode()) {
                    word base = json.get_object(JSON_ID_ROOT, 0);
                    if(json.get_string(base, "participant")) {
                        jparticipant = json.get_string(base, "participant");
                        if(dn && jparticipant && !strcmp(dn, jparticipant)) sentToContainer = true;
                    }
                }
                else log->Log(LOG_APPSHARING, "failed to decoed");
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv INFO_MESSAGE (type=%d) dn=%s remote=%s len=%d", this, tbuf[4], dn ? dn : "", jparticipant ? jparticipant : "", tbuf[Info_message_length]);

                free(participant);
            }
            else if(tbuf[4] == 0x5 || tbuf[4] == 0x6) {
                dword rToken = READ_DWORD_NET(&tbuf[5]);
                if(rToken != token) sentToContainer = false;
            }
        }
        if(sentToContainer) {
            tbuf[1] = rxSessionIds[rxSessionId];
            // Send to container
            appSharingIo->SctpRecv(this, buf, len);
            // Write receiver 
            tbuf[1] = rxSessionId;
        }
        // Ack or message from remote peer to a local sharing session
        if(tbuf[1] >= 128) {
            if(!sessions.empty()) {
                for(h = sessions.front(); h; h = h->goNext()) {
                    if(h->sessionId == (tbuf[1]-128)) {
                        if(h->sink) h->sink->Recv(buf, len);
                        break;
                    }
                }
            }
        }
        switch(tbuf[0]) {
        case AppSharingMessages::DELETE_SESSION:
            //rxSessionIds[rxSessionId] = -1; We cannot do that because DELETE_SESSION may arrive again, tbuf[1] = rxSessionIds[rxSessionId];
            break;
        case AppSharingMessages::CREATE_SESSION_ACK:
            if(h) h->connected = true;
            break;
        case AppSharingMessages::DELETE_SESSION_ACK:
            // Application is unshared and shared again -> SessionId would be identical
            // An old DELETE_SESSION_ACK would delete the actual session!
            if(h && h->closing) {
                h->remove();
                delete h;
            }
            break;
        case AppSharingMessages::MOUSE_MOVE:
            {
                dword rToken = READ_DWORD_NET(&tbuf[2]);
                if(token == rToken) {
                    word coorX = 0;
                    word coorY = 0;
                    int aspectRatio = 0;
                    if(appSharingIo->GetWindowPosition(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], &coorX, &coorY, &aspectRatio)) {
                        int newCoorX = aspectRatio ? (READ_WORD_NET(&tbuf[6]) * 100) / aspectRatio : READ_WORD_NET(&tbuf[6]);
                        int newCoorY = aspectRatio ? (READ_WORD_NET(&tbuf[8]) * 100) / aspectRatio : READ_WORD_NET(&tbuf[8]);
                        lastXCoor = READ_WORD_NET(&tbuf[6]);
                        lastYCoor = READ_WORD_NET(&tbuf[8]);
                        if(appSharingIo->GetImageBuffer()) appSharingIo->GetImageBuffer()->SetMouseAspectRation(aspectRatio);
                        remoteControl->MouseMove(coorX, coorY, READ_WORD_NET(&tbuf[6]), READ_WORD_NET(&tbuf[8]), newCoorX, newCoorY, aspectRatio);
                    }
                }
            }
            break;
        case AppSharingMessages::INFO_MESSAGE:
            {
                class InfoMessage * msg = SearchLastInfoMessage(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1]);
                if (msg && (msg->get_seq() == tbuf[2])) {
                    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv duplicated info message received! (sessionId=%d, seq=%d)", this, tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], tbuf[2]);
                    return; 
                }
                else {
                    if(tbuf[4] == 0x0) {  // AddParticipant
                        if(remoteParticipant) free(remoteParticipant);
                        remoteParticipant = (char *)malloc(tbuf[Info_message_length] - 1);
                        memset(remoteParticipant, 0x0, tbuf[Info_message_length] - 1);
                        memcpy(remoteParticipant, &tbuf[Info_message_type + 1], tbuf[Info_message_length] - 1);
                        log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv Info_message::AddParticipant(%d) (%s)", this, tbuf[Info_message_length] - 1, remoteParticipant);
                        debug->hexdump(remoteParticipant, tbuf[Info_message_length] - 1);
                    }
                    else if(tbuf[4] == 0x1) {  // RemParticipant
                        if(remoteParticipant) free(remoteParticipant);
                        remoteParticipant = NULL;
                        log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SctpRecv Info_message::RemParticipant", this);
                    }
                    else if((tbuf[4] == 0x5 || tbuf[4] == 0x6) && ((len - 9) > 0)) {
                        dword rToken = READ_DWORD_NET(&tbuf[5]);
                        if(rToken == token) {
                            word coorX = 0;
                            word coorY = 0;
                            int aspectRatio = 0;
                            if(appSharingIo->GetWindowPosition(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], &coorX, &coorY, &aspectRatio)) {
                                char * cmd = (char *)malloc(len - 9);
                                memcpy(cmd, &tbuf[9], len - 9);
                                remoteControl->SendCommand(coorX, coorY, aspectRatio, cmd, len - 9);
                                free(cmd);
                            }
                        }
                    }
                    if (msg) delete msg;
                    msg = new class InfoMessage(NULL, 0, tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], tbuf[2], 0);
                    infoMessagesProc.push_back(msg);
                }
            }
            break;
        }
    }
    // In case there is a third party conference
    appSharingIo->Conference(this, (unsigned char *)buf, len);
}

void AppSharingIoChannel::RecvPhoneLoad(void * buf, size_t len)
{
    char * b = (char *)buf;
    byte msg = b[0];
    byte sId = b[1];
    switch(msg) {
    case AppSharingMessages::CREATE_SESSION:
    {
        char sigMsg[2];
        sigMsg[0] = AppSharingMessages::CREATE_SESSION_ACK;
        sigMsg[1] = sId + 128;
        SendSinkMessage(sigMsg, 2, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);
        break;
    }
    case AppSharingMessages::UPDATE_SESSION:
    {
        char sigMsg[6];
        sigMsg[0] = AppSharingMessages::UPDATE_SESSION_ACK;
        sigMsg[1] = sId + 128;
        WRITE_WORD_NET(&sigMsg[2], READ_WORD_NET(&b[2]));
        WRITE_WORD_NET(&sigMsg[4], READ_WORD_NET(&b[4]));
        SendSinkMessage(sigMsg, 6, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);
        break;
    }
    case AppSharingMessages::DELETE_SESSION:
    {
        char sigMsg[2];
        sigMsg[0] = AppSharingMessages::DELETE_SESSION_ACK;
        sigMsg[1] = sId + 128;
        SendSinkMessage(sigMsg, 2, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);
        break;
    }
    case AppSharingMessages::DELETE_IMAGE_MESSAGE:
    {
        char sigMsg[2];
        sigMsg[0] = AppSharingMessages::DELETE_IMAGE_MESSAGE_ACK;
        sigMsg[1] = sId + 128;
        SendSinkMessage(sigMsg, 2, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);
        break;
    }
    case AppSharingMessages::INFO_MESSAGE:
    {
        char sigMsg[3];
        sigMsg[0] = AppSharingMessages::INFO_MESSAGE_ACK;
        sigMsg[1] = sId >= 128 ? sId - 128 : sId + 128;
        sigMsg[2] = b[2];  // Sequence
        SendSinkMessage(sigMsg, 3, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);
        break;
    }
    default: 
        //log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RecvPhoneLoad Discard message msg=%d sId=%d", this, msg, sId); 
        break;
    }
}

void AppSharingIoChannel::SendInfoMessage(class SharingSession * sharingSession, char * message, int len, byte seq, unsigned num)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::SendInfoMessage session_id=%i seq=%i num=%x", this, sharingSession->get_sharing_session_id(), seq, num);

    class InfoMessage * msg = new class InfoMessage(message, len, sharingSession->get_sharing_session_id(), seq, num);
    if(sharingSession->get_info_message() == NULL) SendSinkMessage(message, len, num);
    sharingSession->add_info_message(msg);
}

void AppSharingIoChannel::ForwardInfoMessage(class ForwardingSession * fs, char * message, int len)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::ForwardInfoMessage message len=%d type=%d q=%p", this, len, message[0], fs->get_info_message());

    int infoMessageLen = 4 + len;
    char * infoMessage = (char *)malloc(infoMessageLen);
    infoMessage[0] = AppSharingMessages::INFO_MESSAGE;
    infoMessage[1] = fs->get_remote_session_id();
    infoMessage[2] = fs->get_info_seq();
    infoMessage[3] = len;
    memcpy(&infoMessage[4], message, len);

    class InfoMessage * msg = new class InfoMessage(infoMessage, infoMessageLen, infoMessage[1], infoMessage[2], (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
    if(fs->get_info_message() == NULL) SendSinkMessage(infoMessage, infoMessageLen, (((unsigned)infoMessage[0] << 24) & 0xff000000) | (((unsigned)infoMessage[1] << 16) & 0xff0000) | infoMessage[2]);
    fs->add_info_message(msg);
}

class InfoMessage * AppSharingIoChannel::SearchLastInfoMessage(byte sessionId)
{
    if (!infoMessagesProc.empty()) {
        class InfoMessage * msg = infoMessagesProc.front();
        while (msg) {
            if (msg) {
                if (msg->get_session_id() == sessionId) return msg;
            }
            msg = msg->goNext();
        }
    }
    return NULL;
}

void AppSharingIoChannel::DeleteInfoMessages(byte sessionId)
{
    if (!infoMessagesProc.empty()) {
        class InfoMessage* msg = infoMessagesProc.front();
        while (msg) {
            class InfoMessage* next = msg->goNext();
            if (msg->get_session_id() == sessionId) {
                msg->remove();
                delete msg;
            }
            msg = next;
        }
    }
}

void AppSharingIoChannel::TimerOnTimeout(ITimer * timer)
{
    if(timer == deleteTimer) {
        if(!sessions.empty()) {
            bool startTimer = false;
            class AppSharingIoChannelSinkSession * h;
            for(h = sessions.front(); h; h = h->goNext()) {
                if(h->closing) {
                    char * deleteMessage = (char *)malloc(2);
                    deleteMessage[0] = AppSharingMessages::DELETE_SESSION;
                    deleteMessage[1] = h->sessionId;
                    SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[0] << 24) & 0xff000000) | (((unsigned)deleteMessage[1] << 16) & 0xff0000) | 0);
                    free(deleteMessage);
                    startTimer = true;
                }
            }
            if(startTimer) deleteTimer->Start(50);
        }
        if (!remoteControlSessions.empty()) {
            bool startTimer = false;
            class RemoteControlSession* rc;
            for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
                if (rc->closing) {
                    char* deleteMessage = (char*)malloc(2);
                    deleteMessage[0] = AppSharingMessages::DELETE_SESSION;
                    deleteMessage[1] = rc->sessionId;
                    SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[0] << 24) & 0xff000000) | (((unsigned)deleteMessage[1] << 16) & 0xff0000) | 0);
                    free(deleteMessage);
                    startTimer = true;
                }
            }
            if (startTimer) deleteTimer->Start(50);
        }
    }
    else if (timer == clipboardTimer) {
        if (!clipboardChunks.empty()) {
            class ClipboardChunk* front = clipboardChunks.front();
            user->MediaIoSctpSend(front->chunk, front->len, (((unsigned)front->chunk[0] << 24) & 0xff000000) | (((unsigned)front->chunk[1] << 16) & 0xff0000) | READ_WORD_NET(&front->chunk[2]));
            clipboardTimer->Start(100);
        }
    }
}

void AppSharingIoChannel::MouseWheel(int delta)
{
    appSharingIo->MouseWheel(delta);
}

void AppSharingIoChannel::MouseButtonDown(enum MOUSE_KEY key)
{
    appSharingIo->MouseButtonDown(key);
}

void AppSharingIoChannel::MouseButtonUp(enum MOUSE_KEY key)
{
    appSharingIo->MouseButtonUp(key);
}

void AppSharingIoChannel::RemoteControlCloseComplete()
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlCloseComplete", this);

    delete remoteControl;
    remoteControl = NULL;
    if(closing) iomux->SetExec(this, NULL);
}

void AppSharingIoChannel::Close()
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::Close channel", this);

    closing = true;

    for(int i = 0; i < 128; i++) {
        if(rxSessionIds[i] != -1) {
            char * deleteMessage = (char *)malloc(2);
            deleteMessage[0] = AppSharingMessages::DELETE_SESSION;
            deleteMessage[1] = rxSessionIds[i];
            appSharingIo->SctpRecv(this, deleteMessage, 2);
            free(deleteMessage);
        }
    }

    deleteTimer->Cancel();

    if(!sessions.empty()) {
        while(sessions.front()) {
            class AppSharingIoChannelSinkSession * h = sessions.front();
            h->remove();
            if(h->sink) {
                if(h->buffer) h->buffer->UnregisterScreenSink(h->sink);
                delete h->sink;
            }
            delete h;
        }
    }
    if (remoteControl) remoteControl->Close();
    else iomux->SetExec(this, NULL);

    if(mediaConfiguration) mediaConfiguration->isCapturingApplications = false;
}

void AppSharingIoChannel::IoExec(void* execContext)
{
    user->MediaIoCloseComplete(this);
}

void AppSharingIoChannel::CreateRemoteControlSession(int appId, byte sessionId, char* appName, char* appDesc, class IScreenBuffer* screenBuffer, int bitrate, int waitMsForAck, const char* senderName)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::CreateRemoteControlSession", this);

    remoteControlSessions.push_back(new class RemoteControlSession(iomux, sessionId, appId, appName, appDesc, senderName, log, this, mediaConfiguration));
}

void AppSharingIoChannel::RemoveRemoteControlSession(int appId)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoveRemoteControlSession appId=%d", this, appId);

    if (!remoteControlSessions.empty()) {
        class RemoteControlSession* rc;
        for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
            if (rc->appId == appId) {
                rc->closing = true;

                char* deleteMessage = (char*)malloc(2);
                deleteMessage[0] = AppSharingMessages::DELETE_SESSION;
                deleteMessage[1] = rc->sessionId;
                SendSinkMessage(deleteMessage, 2, (((unsigned)deleteMessage[0] << 24) & 0xff000000) | (((unsigned)deleteMessage[1] << 16) & 0xff0000) | 0);
                free(deleteMessage);
                deleteTimer->Start(50);
                return;
            }
        }
    }
}

void AppSharingIoChannel::UpdateResolution(int dimX, int dimY, unsigned int appId)
{
    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::UpdateResolution dimX=%d dimY=%d appId=%d", this, dimX, dimY, appId);

    if (!remoteControlSessions.empty()) {
        class RemoteControlSession* rc;
        for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
            if (rc->appId == appId) {
                rc->UpdateResolution(dimX, dimY);
                break;
            }
        }
    }
}

void AppSharingIoChannel::RemoteControlUiMessageRecv(void* buf, size_t len)
{
    byte* b = (byte*)buf;

    byte msg = b[0];
    byte sId = b[1];

    if (msg != AppSharingMessages::MOUSE_MOVE) {
        if (log->LogFlagSet(LOG_APPSHARING)) {
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlUiMessageRecv msg=%d sId=%d", this, msg, sId);
            debug->hexdump(b, len);
        }
    }

    if (msg == AppSharingMessages::INFO_MESSAGE) {
        if (b[4] == AppSharingInfoMessages::GIVE_CONTROL) {         // GiveControl, token provided by the UI
            token = READ_DWORD_NET(&b[5]);
        }
        else if (b[4] == AppSharingInfoMessages::TAKE_CONTROL) {    // RemoveControl, unset token
            token = 0xffffffff;
        }
        if ((b[4] == AppSharingInfoMessages::MOUSE || b[4] == AppSharingInfoMessages::KEYBOARD) && ((len - 9) > 0)) {
            debug->hexdump(b, len);
        }
    }
    else if (msg == AppSharingMessages::INFO_KEYBOARD_MESSAGE) {
        debug->hexdump(b, len);
    }

    user->MediaIoSctpSend(buf, len, (((unsigned)msg << 24) & 0xff000000) | (((unsigned)sId << 16) & 0xff0000) | 0);
}

void AppSharingIoChannel::RemoteControlMessageRecv(void* buf, size_t len)
{
    byte* tbuf = (byte*)buf;

    if (len > WS_MAX_DATA_SIZE) {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel::RemoteControlMessageRecv image size too big for Websocket");
        return;
    }

    //if (tbuf[0] != AppSharingMessages::MOUSE_MOVE && tbuf[0] != AppSharingMessages::MOUSE_CURSOR && tbuf[0] != AppSharingMessages::IMAGE_MESSAGE)
    if (tbuf[0] != AppSharingMessages::MOUSE_MOVE) log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv msg=%d sessionId=%d", this, tbuf[0], tbuf[1]);

    class RemoteControlSession* rc = NULL;

    bool forwardToUi = true;
    if (tbuf[0] == AppSharingMessages::INFO_MESSAGE) {
        // GiveControl/RemoveControl
        if (tbuf[4] == 0x2 || tbuf[4] == 0x3) {
            const char* jparticipant = NULL;
            char* participant = (char*)malloc(tbuf[Info_message_length] - 5);  // token+length
            memset(participant, 0x0, tbuf[Info_message_length] - 5);
            memcpy(participant, &tbuf[Info_message_type + 5], tbuf[Info_message_length] - 5);
            debug->hexdump(participant, tbuf[Info_message_length] - 5);

            forwardToUi = false;

            json_io json(participant);
            if (json.decode()) {
                word base = json.get_object(JSON_ID_ROOT, 0);
                if (json.get_string(base, "participant")) {
                    jparticipant = json.get_string(base, "participant");
                    if (dn && jparticipant && !strcmp(dn, jparticipant)) forwardToUi = true;
                }
            }
            else log->Log(LOG_APPSHARING, "failed to decode");
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv INFO_MESSAGE (type=%d) dn=%s remote=%s len=%d", this, tbuf[4], dn ? dn : "", jparticipant ? jparticipant : "", tbuf[Info_message_length]);

            free(participant);
        }
        // Mouse clicks/Keyboard events
        else if (tbuf[4] == AppSharingInfoMessages::KEYBOARD || tbuf[4] == AppSharingInfoMessages::MOUSE) {
            dword rToken = READ_DWORD_NET(&tbuf[5]);
            if (rToken != token) forwardToUi = false;
        }
        else if (tbuf[4] == AppSharingInfoMessages::RCP_LOST_SEQUENCE) {
            forwardToUi = false;
        }
        else if (tbuf[4] == AppSharingInfoMessages::RCP_NEW_PICTURE) {
            forwardToUi = false;
        }
    }
    if ((tbuf[0] == AppSharingMessages::RCP_CREATE_SESSION) && mediaConfiguration) mediaConfiguration->isCapturingApplications = true;
    if (tbuf[0] == AppSharingMessages::INFO_KEYBOARD_MESSAGE || tbuf[0] == AppSharingMessages::CLIPBOARD_UPDATE || tbuf[0] == AppSharingMessages::CLIPBOARD_UPDATE_ACK) forwardToUi = false;
    if (forwardToUi) appSharingIo->SctpRecv(this, buf, len);

    // Ack or message from remote peer to a local remote control session
    if (tbuf[1] >= 128 && tbuf[0] != AppSharingMessages::CLIPBOARD_UPDATE_ACK) {
        if (!remoteControlSessions.empty()) {
            for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
                if (rc->sessionId == (tbuf[1] - 128)) {
                    // Do not pass INFO_MESSAGE to session before checking if this is a duplicate
                    if(tbuf[0] != AppSharingMessages::INFO_MESSAGE && tbuf[0] != AppSharingMessages::INFO_KEYBOARD_MESSAGE) rc->Recv(buf, len);
                    break;
                }
            }
        }
        if (!rc) {
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv sessionId=%d not found!", this, (tbuf[1] - 128));
            return;
        }
    }
    switch (tbuf[0]) {
    case AppSharingMessages::DELETE_SESSION:
        //rxSessionIds[rxSessionId] = -1; We cannot do that because DELETE_SESSION may arrive again, tbuf[1] = rxSessionIds[rxSessionId];
        break;
    case AppSharingMessages::CREATE_SESSION_ACK:
        if (rc) {
            rc->sessionCreated = true;
            char* ackedMessage = (char*)malloc(3);
            ackedMessage[0] = AppSharingMessages::RCP_CREATE_SESSION_ACK;
            ackedMessage[1] = rc->sessionId;
            ackedMessage[2] = rc->appId;
            appSharingIo->SctpRecv(this, ackedMessage, 3);
            free(ackedMessage);
        }
        break;
    case AppSharingMessages::DELETE_SESSION_ACK:
        if (rc && rc->closing) {
            DeleteInfoMessages(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1]);
            if (!this->images.empty()) {
                class IImageHelper* helper = images.front();
                while (helper) {
                    class IImageHelper* next = helper->goNext();
                    if (helper->appId == (unsigned int)(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1])) {
                        helper->img->ref--;
                        if (!helper->img->ref) delete helper->img;
                        delete helper;
                    }
                    helper = next;
                }
            }
            rc->remove();
            delete rc;
        }
        if (remoteControlSessions.empty()) {
            initLastProcInfoSequence = false;
            mediaConfiguration->appSharingBitrate2 = mediaConfiguration->appSharingBitrate;
        }
        break;
    case AppSharingMessages::MOUSE_MOVE:
    {
        dword rToken = READ_DWORD_NET(&tbuf[2]);
        if (token == rToken) {
            word coorX = 0;
            word coorY = 0;
            int aspectRatio = 0;
            if (appSharingIo->GetWindowPosition(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], &coorX, &coorY, &aspectRatio)) {
                int newCoorX = aspectRatio ? (READ_WORD_NET(&tbuf[6]) * 100) / aspectRatio : READ_WORD_NET(&tbuf[6]);
                int newCoorY = aspectRatio ? (READ_WORD_NET(&tbuf[8]) * 100) / aspectRatio : READ_WORD_NET(&tbuf[8]);
                lastXCoor = READ_WORD_NET(&tbuf[6]);
                lastYCoor = READ_WORD_NET(&tbuf[8]);
                if (appSharingIo->GetImageBuffer()) appSharingIo->GetImageBuffer()->SetMouseAspectRation(aspectRatio);
                remoteControl->MouseMove(coorX, coorY, READ_WORD_NET(&tbuf[6]), READ_WORD_NET(&tbuf[8]), newCoorX, newCoorY, aspectRatio);
            }
        }
    }
    break;
    case AppSharingMessages::INFO_MESSAGE:
    {
        class InfoMessage* msg = SearchLastInfoMessage(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1]);
        if (msg && (msg->get_seq() == tbuf[2])) {
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv duplicated info message received! (sessionId=%d, seq=%d) forwardToUi=%d", this, tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], tbuf[2], forwardToUi);
            if (forwardToUi == false) {
                byte infoMsgAck[3];
                infoMsgAck[0] = AppSharingMessages::INFO_MESSAGE_ACK;
                infoMsgAck[1] = tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1] + 128;
                infoMsgAck[2] = tbuf[2];  // Sequence
                user->MediaIoSctpSend(infoMsgAck, 3, (((unsigned)infoMsgAck[0] << 24) & 0xff000000) | (((unsigned)infoMsgAck[1] << 16) & 0xff0000) | 0);

                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv ack info message (sessionId=%d, seq=%d)", this, infoMsgAck[1], infoMsgAck[2]);
            }
        }
        else {
            if (tbuf[4] == AppSharingInfoMessages::ADD_PARTICIPANT) {
                if (remoteParticipant) free(remoteParticipant);
                remoteParticipant = (char*)malloc(tbuf[Info_message_length] - 1);
                memset(remoteParticipant, 0x0, tbuf[Info_message_length] - 1);
                memcpy(remoteParticipant, &tbuf[Info_message_type + 1], tbuf[Info_message_length] - 1);
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv Info_message::AddParticipant(%d) (%s)", this, tbuf[Info_message_length] - 1, remoteParticipant);
                debug->hexdump(remoteParticipant, tbuf[Info_message_length] - 1);
            }
            else if (tbuf[4] == AppSharingInfoMessages::REMOVE_PARTICIPANT) {
                if (remoteParticipant) free(remoteParticipant);
                remoteParticipant = NULL;
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv Info_message::RemParticipant", this);
            }
            else if ((tbuf[4] == AppSharingInfoMessages::MOUSE || tbuf[4] == AppSharingInfoMessages::KEYBOARD) && ((len - 9) > 0)) {
                dword rToken = READ_DWORD_NET(&tbuf[5]);
                if (rToken == token) {
                    word coorX = 0;
                    word coorY = 0;
                    int aspectRatio = 0;
                    if (appSharingIo->GetWindowPosition(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], &coorX, &coorY, &aspectRatio)) {
                        char* cmd = (char*)malloc(len - 9);
                        memcpy(cmd, &tbuf[9], len - 9);
                        bool forceCapture = strstr(cmd, "keyboard") != NULL;
                        remoteControl->SendCommand(coorX, coorY, aspectRatio, cmd, len - 9);
                        if (forceCapture) appSharingIo->ForceCapture(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1]);
                        free(cmd);
                    }
                }
            }
            else if (tbuf[4] == AppSharingInfoMessages::INFO_KEYBOARD_MESSAGE_SEQ) {
                if (!initLastProcInfoSequence) {
                    initLastProcInfoSequence = true;
                    lastProcInfoSequence = (word)((READ_WORD_NET(&tbuf[5]) - 1) & 0xffff);
                    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv Info_message::INFO_KEYBOARD_MESSAGE_SEQ seq=%d", this, lastProcInfoSequence);
                }
            }
            else if (tbuf[4] == AppSharingInfoMessages::RCP_IGNORE_INFO_MESSAGE_SEQ) {
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv RCP_IGNORE_INFO_MESSAGE_SEQ seq=%d", this, tbuf[5]);
            }
            else if (tbuf[4] == AppSharingInfoMessages::RCP_IGNORE_INFO_KEYBOARD_MESSAGE_SEQ) {
                log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv RCP_IGNORE_INFO_KEYBOARD_MESSAGE_SEQ seq=%d", this, READ_WORD_NET(&tbuf[5]));
            }
            if(rc) rc->Recv(buf, len);
            // Delete last info message if any
            if (msg) delete msg;
            msg = new class InfoMessage(NULL, 0, tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], tbuf[2], 0);
            infoMessagesProc.push_back(msg);
        }
        if (forwardToUi == false) {
            byte infoMsgAck[3];
            infoMsgAck[0] = AppSharingMessages::INFO_MESSAGE_ACK;
            infoMsgAck[1] = tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1] + 128;
            infoMsgAck[2] = tbuf[2];  // Sequence
            user->MediaIoSctpSend(infoMsgAck, 3, (((unsigned)infoMsgAck[0] << 24) & 0xff000000) | (((unsigned)infoMsgAck[1] << 16) & 0xff0000) | 0);

            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv ack info message (sessionId=%d, seq=%d)", this, infoMsgAck[1], infoMsgAck[2]);
        }
    }
    break;
    case AppSharingMessages::CLIPBOARD_UPDATE:
    {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel::RemoteControlMessageRecv CLIPBOARD_UPDATE id=%d ind=%d sharing=%d", tbuf[2], tbuf[3], appSharingIo->IsSharing());

        byte clipboardAck[4];
        clipboardAck[0] = AppSharingMessages::CLIPBOARD_UPDATE_ACK;
        clipboardAck[1] = tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1] + 128;
        clipboardAck[2] = tbuf[2];  // clipboardTxId
        clipboardAck[3] = tbuf[3];  // sequence
        user->MediaIoSctpSend(clipboardAck, 4, (((unsigned)clipboardAck[0] << 24) & 0xff000000) | (((unsigned)clipboardAck[1] << 16) & 0xff0000) | 0);

        //if (!appSharingIo->IsSharing()) return;

        if (tbuf[2] & 0x40) { // First block
            clipboardRxLen = READ_DWORD_NET(&tbuf[4]);
            log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv clipboard id=%d len=%d offset=%d last=%d", this, clipboardRxLen, READ_WORD_NET(&tbuf[8]), (tbuf[2] & 0x80));
            if (clipboardRx) free(clipboardRx);
            clipboardRx = (byte*)malloc(clipboardRxLen);
        }
        if (clipboardRx) {
            memcpy(&clipboardRx[READ_DWORD_NET(&tbuf[8])], &tbuf[16], READ_DWORD_NET(&tbuf[12]));
            if (tbuf[2] & 0x80) {
                appSharingIo->RxClipboardUpdate(clipboardRx, clipboardRxLen);
                free(clipboardRx);
                clipboardRx = NULL;
            }
        }
    }
    break;
    case AppSharingMessages::CLIPBOARD_UPDATE_ACK:
    {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel::RemoteControlMessageRecv CLIPBOARD_UPDATE_ACK id=%d ind=%d", tbuf[2], tbuf[3]);

        if (!clipboardChunks.empty()) {
            class ClipboardChunk * chunk = clipboardChunks.front();
            while (chunk) {
                if (chunk->chunk[2] == tbuf[2] && chunk->chunk[3] == tbuf[3]) {
                    delete chunk;
                    break;
                }
                chunk = chunk->goNext();
            }
        }
        if (!clipboardChunks.empty()) {
            class ClipboardChunk* front = clipboardChunks.front();
            user->MediaIoSctpSend(front->chunk, front->len, (((unsigned)front->chunk[0] << 24) & 0xff000000) | (((unsigned)front->chunk[1] << 16) & 0xff0000) | READ_WORD_NET(&front->chunk[2]));
        }
        else clipboardTimer->Cancel();
    }
    break;
    case AppSharingMessages::INFO_KEYBOARD_MESSAGE:
    {
        word rxSeq = READ_WORD_NET(&tbuf[2]);

        if (initLastProcInfoSequence) {
            // Old packet? would fail if more than 32768 packets were lost
            if (rxSeq <= lastProcInfoSequence && (lastProcInfoSequence - rxSeq < 32768)) {
                log->Log(LOG_APPSHARING, "AppSharingMessages(%p)::RemoteControlMessageRecv drop old info keyboard sequence=%d lastProcSequence=%d ", this, rxSeq, lastProcInfoSequence);

                byte infoMsgAck[4];
                infoMsgAck[0] = AppSharingMessages::INFO_KEYBOARD_MESSAGE_ACK;
                infoMsgAck[1] = tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1] + 128;
                WRITE_WORD_NET(&infoMsgAck[2], rxSeq);
                user->MediaIoSctpSend(infoMsgAck, 4, (((unsigned)infoMsgAck[0] << 24) & 0xff000000) | (((unsigned)infoMsgAck[1] << 16) & 0xff0000) | 0);
                return;
            }
        }
        else {
            log->Log(LOG_APPSHARING, "AppSharingMessages(%p)::RemoteControlMessageRecv init message seq not received yet!", this);
            return;
        }

        int diff = (int)((rxSeq - (lastProcInfoSequence + 1)) & 0xffff);
        if (diff) {
            log->Log(LOG_APPSHARING, "AppSharingMessages(%p)::RemoteControlMessageRecv missing keyboard sequence=%d lastProcInfoSequence=%d diff=%d len=%d", this, rxSeq, lastProcInfoSequence, diff, len);

            byte infoMsgAck[4];
            infoMsgAck[0] = AppSharingMessages::INFO_KEYBOARD_MESSAGE_ACK;
            infoMsgAck[1] = tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1] + 128;
            WRITE_WORD_NET(&infoMsgAck[2], rxSeq);
            user->MediaIoSctpSend(infoMsgAck, 4, (((unsigned)infoMsgAck[0] << 24) & 0xff000000) | (((unsigned)infoMsgAck[1] << 16) & 0xff0000) | 0);

            if (!infoKeyboardMessagesProc.empty()) {
                class InfoKeyboardMessage* msg = infoKeyboardMessagesProc.front();
                while (msg) {
                    if (msg->get_seq() == rxSeq) {
                        log->Log(LOG_APPSHARING, "AppSharingMessages(%p)::RemoteControlMessageRecv already on the list", this);
                        return;
                    }
                    msg = msg->goNext();
                }
            }

            char* infoMsg = (char*)malloc(len);
            memcpy(infoMsg, buf, len);
            class InfoKeyboardMessage* msg = new class InfoKeyboardMessage(infoMsg, len, tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], rxSeq);
            infoKeyboardMessagesProc.push_back(msg);

            return;
        }

        dword rToken = READ_DWORD_NET(&tbuf[5]);
        if (rToken == token) {
            word coorX = 0;
            word coorY = 0;
            int aspectRatio = 0;
            if (appSharingIo->GetWindowPosition(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1], &coorX, &coorY, &aspectRatio)) {
                char* cmd = (char*)malloc(len - 9);
                memcpy(cmd, &tbuf[9], len - 9);
                bool forceCapture = strstr(cmd, "keyboard") != NULL;
                remoteControl->SendCommand(coorX, coorY, aspectRatio, cmd, len - 9);
                if(forceCapture) appSharingIo->ForceCapture(tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1]);
                free(cmd);
            }
        }

        byte infoMsgAck[4];
        infoMsgAck[0] = AppSharingMessages::INFO_KEYBOARD_MESSAGE_ACK;
        infoMsgAck[1] = tbuf[1] >= 128 ? tbuf[1] - 128 : tbuf[1] + 128;
        WRITE_WORD_NET(&infoMsgAck[2], rxSeq);
        user->MediaIoSctpSend(infoMsgAck, 4, (((unsigned)infoMsgAck[0] << 24) & 0xff000000) | (((unsigned)infoMsgAck[1] << 16) & 0xff0000) | 0);

        log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv ack info keyboard message (sessionId=%d, seq=%d)", this, infoMsgAck[1], rxSeq);

        lastProcInfoSequence = rxSeq;

        if (!infoKeyboardMessagesProc.empty()) {
            class InfoKeyboardMessage* msg = infoKeyboardMessagesProc.front();
            while (msg) {
                class InfoKeyboardMessage* n = msg->goNext();
                word rxSeq = msg->get_seq();
                char* infoData = msg->get_data();
                int diff = (int)((rxSeq - (lastProcInfoSequence + 1)) & 0xffff);
                if (diff) break;
                dword rToken = READ_DWORD_NET(&infoData[5]);
                if (rToken == token) {
                    log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv proc queued info keyboard message (seq=%d)", this, rxSeq);
                    word coorX = 0;
                    word coorY = 0;
                    int tLen = msg->get_len();
                    int aspectRatio = 0;
                    if (appSharingIo->GetWindowPosition(msg->get_session_id(), &coorX, &coorY, &aspectRatio)) {
                        char* cmd = (char*)malloc(tLen - 9);
                        memcpy(cmd, &infoData[9], tLen - 9);
                        bool forceCapture = strstr(cmd, "keyboard") != NULL;
                        remoteControl->SendCommand(coorX, coorY, aspectRatio, cmd, tLen - 9);
                        if(forceCapture) appSharingIo->ForceCapture(msg->get_session_id());
                        free(cmd);
                    }
                    else log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv GetWindowPosition failed", this);
                }
                else log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::RemoteControlMessageRecv proc queued info keyboard message (seq=%d) wrong token (%x != %x)", this, rxSeq, token, rToken);
                lastProcInfoSequence = rxSeq;
                delete msg;
                msg = n;
            }
        }
    }
    break;
    }
}

bool AppSharingIoChannel::IsRemoteControlSessionCreated(int appId, byte sessionId)
{
    if (closed) return false;

    class RemoteControlSession* rc;
    for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
        if (rc->appId == appId && rc->sessionId == sessionId) {
            return rc->sessionCreated;
        }
    }
    return false;
}

void AppSharingIoChannel::ImageBlock(class IImage* img, unsigned int appId)
{
    if (closed) {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel::ImageBlock closed!");
        return;
    }

    img->ref++;
    images.push_back(new class IImageHelper(img, appId));
}

void AppSharingIoChannel::SendImageBlocks(unsigned int appId)
{
    class RemoteControlSession* rc;
    for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
        if ((rc->appId == appId) && rc->sessionCreated && !rc->closing) break;
    }

    if (!rc) {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel::SendImageBlocks no session available for %d", appId);
        return;
    }

    while (!images.empty()) {
        int numImages = 0;
        int dataToSend = 0;
        istd::list<class IImageHelper> sendImages;

        //log->Log(LOG_APPSHARING, "AppSharingIoChannel::SendImageBlocks images=%d", !images.empty());

        class IImageHelper* helper = images.front();
        while (helper) {
            class IImageHelper* next = helper->goNext();
            if (helper->appId == appId) {
                if (dataToSend) {
                    if ((dataToSend + (21 + helper->img->len)) >= MAX_SCTP_MTU) break;
                }
                dataToSend += (21 + helper->img->len);
                helper->remove();
                sendImages.push_back(helper);
                numImages++;
            }
            helper = next;
        }

        if (!dataToSend || sendImages.empty()) {
            log->Log(LOG_APPSHARING, "AppSharingIoChannel::SendImageBlocks no data to send (%d) or no images for %d", dataToSend, appId);
            return;
        }

        byte* imageMessage = (byte*)malloc(dataToSend);
        int offsetImg = 0;
        while (!sendImages.empty()) {
            class IImageHelper * helper = sendImages.pop_front();
            class IImage* img = helper->img;
            imageMessage[offsetImg + 0] = (numImages > 1) ? AppSharingMessages::RCP_MULTI_IMAGE_MESSAGE : AppSharingMessages::RCP_IMAGE_MESSAGE; // img->type == CompressionType::PNG ? AppSharingMessages::IMAGE_MESSAGE_PNG : img->type == CompressionType::JPEG ? AppSharingMessages::IMAGE_MESSAGE_JPEG : AppSharingMessages::IMAGE_MESSAGE_PLAIN;
            imageMessage[offsetImg + 1] = rc->sessionId;
            WRITE_WORD_NET(&imageMessage[offsetImg + 2], rc->startSequence);
            WRITE_WORD_NET(&imageMessage[offsetImg + 4], img->posX);
            WRITE_WORD_NET(&imageMessage[offsetImg + 6], img->posY);
            WRITE_WORD_NET(&imageMessage[offsetImg + 8], img->dimX);
            WRITE_WORD_NET(&imageMessage[offsetImg + 10], img->dimY);
            imageMessage[offsetImg + 12] = img->type;
            imageMessage[offsetImg + 14] = 0;
            if (img->first) imageMessage[offsetImg + 14] |= 0x1;
            if (img->last) imageMessage[offsetImg + 14] |= 0x2;
            if (img->moveBlocks) imageMessage[offsetImg + 14] |= 0x4;
            if (img->doNotRedraw) imageMessage[offsetImg + 14] |= 0x8;
            WRITE_WORD_NET(&imageMessage[offsetImg + 15], img->picId);
            int len = 21;
            if (img->type == CompressionType::PLAIN) {
                //dword rawValue = ((dword)img->buf[0] << 24) & 0xff000000 | ((dword)img->buf[1] << 16) & 0xff0000 | ((dword)img->buf[2] << 8) & 0xff00 | ((dword)img->buf[3] & 0xff);
                imageMessage[offsetImg + 13] = 0;
                WRITE_DWORD_NET(&imageMessage[offsetImg + 17], READ_DWORD_NET(&img->buf[0]));
            }
            else if (img->type == CompressionType::MOVE) {
                imageMessage[offsetImg + 13] = 0;
                WRITE_WORD_NET(&imageMessage[offsetImg + 17], img->movePosX);
                WRITE_WORD_NET(&imageMessage[offsetImg + 19], img->movePosY);
                //debug->printf("CompressionType::MOVE(id=%d) (%d,%d) (%d,%d) -> (%d,%d)", img->picId, READ_WORD_NET(&imageMessage[offsetImg + 17]), READ_WORD_NET(&imageMessage[offsetImg + 19]), img->movePosX, img->movePosY, img->posX, img->posY);
            }
            else if (numImages > 1) {
                len = 21 + img->len;
                imageMessage[offsetImg + 13] = img->numEqual;
                WRITE_DWORD_NET(&imageMessage[offsetImg + 17], img->len);
                memcpy(&imageMessage[offsetImg + 21], img->buf, img->len);
            }
            else {
                len = 17 + img->len;
                imageMessage[offsetImg + 13] = img->numEqual;
                memcpy(&imageMessage[offsetImg + 17], img->buf, img->len);
            }
            rc->AddSentBlock(rc->startSequence, img->posX, img->posY, img->dimX, img->dimY);

            log->Log(LOG_APPSHARING, "AppSharingIoChannel::SendImageBlocks %d,%d %d,%d offsetImg=%d len=%d id=%d (%d,%d,%d,%d)", img->posX, img->posY, img->dimX, img->dimY, offsetImg, len, img->picId, img->first, img->last, img->moveBlocks, img->doNotRedraw);

            offsetImg += len;
            rc->startSequence++;
            img->ref--;
            if (!img->ref) delete img;
            delete helper;
        }

        user->MediaIoSctpSend(imageMessage, offsetImg, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | READ_WORD_NET(&imageMessage[2]));
        free(imageMessage);
    }

#if 0
        class RemoteControlSession* rc;
        for (rc = remoteControlSessions.front(); rc; rc = rc->goNext()) {
            if ((rc->appId == appId) && rc->sessionCreated && !rc->closing) {
                byte* imageMessage = (byte*)malloc(21 + img->len);
                imageMessage[0] = AppSharingMessages::RCP_IMAGE_MESSAGE; // img->type == CompressionType::PNG ? AppSharingMessages::IMAGE_MESSAGE_PNG : img->type == CompressionType::JPEG ? AppSharingMessages::IMAGE_MESSAGE_JPEG : AppSharingMessages::IMAGE_MESSAGE_PLAIN;
                imageMessage[1] = rc->sessionId;
                WRITE_WORD_NET(&imageMessage[2], rc->startSequence);
                WRITE_WORD_NET(&imageMessage[4], img->posX);
                WRITE_WORD_NET(&imageMessage[6], img->posY);
                WRITE_WORD_NET(&imageMessage[8], img->dimX);
                WRITE_WORD_NET(&imageMessage[10], img->dimY);
                imageMessage[12] = img->type;
                imageMessage[14] = 0;
                if (img->first) imageMessage[14] |= 0x1;
                if (img->last) imageMessage[14] |= 0x2;
                WRITE_WORD_NET(&imageMessage[15], img->picId);
                int len = 21;
                if (img->type == CompressionType::PLAIN) {
                    //dword rawValue = ((dword)img->buf[0] << 24) & 0xff000000 | ((dword)img->buf[1] << 16) & 0xff0000 | ((dword)img->buf[2] << 8) & 0xff00 | ((dword)img->buf[3] & 0xff);
                    imageMessage[13] = 0;
                    WRITE_DWORD_NET(&imageMessage[17], READ_DWORD_NET(&img->buf[0]));
                }
                else {
                    len = 17 + img->len;
                    imageMessage[13] = img->numEqual;
                    memcpy(&imageMessage[17], img->buf, img->len);
                }
                rc->AddSentBlock(rc->startSequence, img->posX, img->posY, img->dimX, img->dimY);

                user->MediaIoSctpSend(imageMessage, len, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | READ_WORD_NET(&imageMessage[2]));
                free(imageMessage);
                rc->startSequence++;

                ulong64 tm = ITime::TimeStampMilliseconds();
                if (numTxProcSamples) {
                    if (((numTxProcSamples & 0x1ff) == 0) && (firstTxTimeStamp != tm)) {
                        int timeDiffPar = (int)(tm - partialTxTimeStamp);
                        int timeDiffTot = (int)(tm - firstTxTimeStamp);
                        int bitratePar = (int)((procTxDataPar * 8) / timeDiffPar);
                        int bitrateTot = (int)((procTxDataTot * 8) / timeDiffTot);
                        log->Log(LOG_APPSHARING, "AppSharingIoChannel(%p)::ImageBlock tx-samples:%d Bytes:%d-%d tDiff: %d-%d msec BitRate:%d-%d kbps", this, numTxProcSamples, procTxDataPar, procTxDataTot, timeDiffPar, timeDiffTot, bitratePar, bitrateTot);
                        partialTxTimeStamp = tm;
                        procTxDataPar = 0;
                    }
                }
                else {
                    partialTxTimeStamp = tm;
                    firstTxTimeStamp = tm;
                }
                procTxDataTot += len + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + DTLS_HEADER_DATA_LEN;
                procTxDataPar += len + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + DTLS_HEADER_DATA_LEN;
                numTxProcSamples++;
            }
            else log->Log(LOG_APPSHARING, "AppSharingIoChannel::ImageBlock not created or wrong appId! rc=%p id=(%d != %d) created=(%d) closing=(%d)", rc, rc->appId, appId, rc->sessionCreated, rc->closing);
        }
    }
#endif
}

void AppSharingIoChannel::SendClipboardUpdate(const char * clipboard, int len)
{
    if (len > 65536) {
        log->Log(LOG_APPSHARING, "AppSharingIoChannel::SendClipboardUpdate clipboard too large %d", len);
        return;
    }

    // delete old clipboard
    while (!clipboardChunks.empty()) delete clipboardChunks.pop_front();
    if(this->clipboardTimer->IsRunning()) this->clipboardTimer->Cancel();

    int offsetData = 16;
    int offsetClipboard = 0;
    int totalLen = len;
    int indexChunk = 0;
    while (totalLen > 0) {
        int clipboardDataLen = totalLen >= (MAX_SCTP_MTU - offsetData) ? (MAX_SCTP_MTU - offsetData) : totalLen;
        byte * clipboardMessage = (byte *)malloc(clipboardDataLen + offsetData);

        clipboardMessage[0] = AppSharingMessages::CLIPBOARD_UPDATE;
        clipboardMessage[1] = 0;
        clipboardMessage[2] = this->clipboardTxId & 0x3f;
        clipboardMessage[3] = indexChunk;
        WRITE_DWORD_NET(&clipboardMessage[4], len);
        WRITE_DWORD_NET(&clipboardMessage[8], offsetClipboard);
        WRITE_DWORD_NET(&clipboardMessage[12], clipboardDataLen);

        memcpy(&clipboardMessage[offsetData], &clipboard[offsetClipboard], clipboardDataLen);

        totalLen -= clipboardDataLen;

        if(!offsetClipboard) clipboardMessage[2] |= 0x40;
        if(!totalLen) clipboardMessage[2] |= 0x80;

        log->Log(LOG_APPSHARING, "AppSharingIoChannel::SendClipboardUpdate id=%d chunk=%d totalLen=%d", clipboardMessage[2], offsetData + clipboardDataLen, totalLen);

        if (!offsetClipboard) user->MediaIoSctpSend(clipboardMessage, offsetData + clipboardDataLen, (((unsigned)clipboardMessage[0] << 24) & 0xff000000) | (((unsigned)clipboardMessage[1] << 16) & 0xff0000) | READ_WORD_NET(&clipboardMessage[2]));
        clipboardChunks.push_back(new class ClipboardChunk(clipboardMessage, offsetData + clipboardDataLen));

        offsetClipboard += clipboardDataLen;
        indexChunk++;
    }
    this->clipboardTxId++;
    this->clipboardTimer->Start(100);
}

#define SESSION_TIMER 100
#define MAX_SEQUENCE_SINK 1024

RemoteControlSession::RemoteControlSession(class IIoMux* const iomux, byte sessionId, byte appId, const char* appName, const char* appDesc, const char* senderName, class IInstanceLog* const log, class AppSharingIoChannel* channel, class MediaConfiguration* mediaConfiguration) :
    log(log),
    iomux(iomux),
    channel(channel)
{
    this->w = 0;
    this->h = 0;
    this->seq = 0;
    this->appId = appId;
    this->sessionId = sessionId;
    this->sessionCreated = false;
    this->waitUpdateSessionAck = false;
    this->resolutionUpdated = false;
    this->expectedWidth = 0;
    this->expectedHeight = 0;
    this->sessionTimer = new ITimer(iomux, this);
    this->sessionTimer->Start(500);
    this->createSession = NULL;
    this->appName = _strdup(appName);
    this->appDesc = _strdup(appDesc);
    this->senderName = _strdup(senderName);
    this->closing = false;
    this->startSequence = 0;
    this->totDataSent = 0;
    this->indexBitrate = 0;
    this->bitrateTimer = new ITimer(iomux, this);
    this->bitrateTimer->Start(BITRATE_TIMER);
    this->mediaConfiguration = mediaConfiguration;
    this->appSharingBitrate = mediaConfiguration ? mediaConfiguration->appSharingBitrate : 2000;
    this->maxSendBytes = (this->appSharingBitrate * 1000) / 8;
    this->maxChunkBytes = this->maxSendBytes / BITRATE_SLOTS;
    this->numBlockMessages = 0;
    this->numPacketLost = 0;

    log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::RemoteControlSession appId=%d sessionId=%d ch=%p appName=(%s) appDesc=(%s) senderName=(%s)", this, appId, sessionId, channel, this->appName ? (char*)this->appName : "", this->appDesc ? (char*)this->appDesc : "", this->senderName ? (char*)this->senderName : "");
}

RemoteControlSession::~RemoteControlSession()
{
    log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::~RemoteControlSession", this);

    if (this->appName) free(this->appName);
    if (this->appDesc) free(this->appDesc);
    if (this->senderName) free(this->senderName);
    if (this->createSession) free(this->createSession);

    if (this->sessionTimer) delete this->sessionTimer;
    if (this->bitrateTimer) delete this->bitrateTimer;
}

void RemoteControlSession::TimerOnTimeout(ITimer* timer)
{
    if (timer == sessionTimer) {
        log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::TimerOnTimeout s=%d closing=%d resolutionUpdated=%d", this, sessionCreated, closing, resolutionUpdated);

        if (!resolutionUpdated) {
            channel->RequestNewImage(appId);
            sessionTimer->Start(500);
            return;
        }
        else if (waitUpdateSessionAck) {
            channel->SendSinkMessage(startSessionMsg, 6, (((unsigned)startSessionMsg[0] << 24) & 0xff000000) | (((unsigned)startSessionMsg[1] << 16) & 0xff0000) | seq);
            seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
        }
        else if (!sessionCreated) {
            channel->SendSinkMessage(createSession, createSessionLen, (((unsigned)createSession[0] << 24) & 0xff000000) | (((unsigned)createSession[1] << 16) & 0xff0000) | seq);
            seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
        }
        sessionTimer->Start(SESSION_TIMER);
    }
    else if (timer == bitrateTimer) {
        bitrateTimer->Start(BITRATE_TIMER);

        if (sessionCreated && !closing) {
            indexBitrate = ((indexBitrate + 1) == BITRATE_SLOTS) ? 0 : indexBitrate + 1;
            bitrateSlots[indexBitrate] = 0;
            bool dataSent = false;
            while (CalculateBitrate()) {
                int msgLen = channel->SendRemoteControlData(appId);
                if (msgLen) {
                    //log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::TimerOnTimeout appId=%d ind=%d slot=%d msgLen=%d maxChunkBytes=%d", this, appId, indexBitrate, bitrateSlots[indexBitrate], msgLen, maxChunkBytes);
                    bitrateSlots[indexBitrate] += msgLen;
                    dataSent = true;
                }
                if (!msgLen || bitrateSlots[indexBitrate] > maxChunkBytes) break;
            }
            if (dataSent) channel->EndSendRemoteControlData(appId);
            if (indexBitrate == 0) {
                debug->printf("RemoteControlSession(%p)::TimerOnTimeout appId=%d ind=%d slot=%d bitrate=%d maxChunkBytes=%d lost=%d", this, appId, indexBitrate, bitrateSlots[indexBitrate], this->appSharingBitrate, this->maxChunkBytes, this->numPacketLost);
                int tmp = (this->appSharingBitrate * 5) / 100;
                if (this->numPacketLost) {
                    this->appSharingBitrate = MAX(1000, this->appSharingBitrate - tmp);
                    this->numPacketLost = 0;
                }
                else this->appSharingBitrate = MIN(10000, this->appSharingBitrate + tmp);
                this->maxSendBytes = (this->appSharingBitrate * 1000) / 8;
                this->maxChunkBytes = this->maxSendBytes / BITRATE_SLOTS;
                if(this->mediaConfiguration) this->mediaConfiguration->appSharingBitrate2 = this->appSharingBitrate;
            }
        }
    }
}

void RemoteControlSession::UpdateResolution(int w, int h)
{
    log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::UpdateResolution (%d) (%d,%d, %d,%d) -> (%d,%d)", this, sessionCreated, this->w, this->h, expectedWidth, expectedHeight, w, h);

    resolutionUpdated = true;

    sessionTimer->Cancel();

    this->w = w;
    this->h = h;

    if (sessionCreated) {
        waitUpdateSessionAck = true;

        startSessionMsg[0] = AppSharingMessages::UPDATE_SESSION;
        startSessionMsg[1] = sessionId;
        WRITE_WORD_NET(&startSessionMsg[2], this->w);
        WRITE_WORD_NET(&startSessionMsg[4], this->h);
        channel->SendSinkMessage(startSessionMsg, 6, (((unsigned)startSessionMsg[0] << 24) & 0xff000000) | (((unsigned)startSessionMsg[1] << 16) & 0xff0000) | seq);
        seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
    }
    else {
        if (appName && appDesc)
            createSession = (char*)malloc((strlen(appName) + strlen(appDesc) + 128) << 1);
        else
            createSession = (char*)malloc(8);
        createSession[0] = AppSharingMessages::RCP_CREATE_SESSION;
        createSession[1] = sessionId;
        WRITE_WORD_NET(&createSession[2], this->w);
        WRITE_WORD_NET(&createSession[4], this->h);
        WRITE_WORD_NET(&createSession[6], startSequence);
        createSessionLen = 8;
        if (appName && appDesc && senderName) {
            json_io json(0);
            word base = json.add_object(JSON_ID_ROOT, 0);
            json.add_string(base, "name", appName);
            json.add_string(base, "description", appDesc);
            json.add_string(base, "sender", senderName);
            createSessionLen += json.encode(base, &createSession[createSessionLen]);
        }
        channel->SendSinkMessage(createSession, createSessionLen, (((unsigned)createSession[0] << 24) & 0xff000000) | (((unsigned)createSession[1] << 16) & 0xff0000) | seq);
        seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
        debug->hexdump(createSession, createSessionLen);
    }

    expectedWidth = this->w;
    expectedHeight = this->h;

    sessionTimer->Start(SESSION_TIMER);
}

void RemoteControlSession::Recv(void* buf, int len)
{
    char* b = (char*)buf;

    if(b[0] != AppSharingMessages::MOUSE_MOVE) log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::Recv len=%d msg=%d", this, len, b[0]);

    switch (b[0]) {
    case AppSharingMessages::CREATE_SESSION_ACK:
        sessionCreated = true;
        sessionTimer->Cancel();
        break;
    case AppSharingMessages::UPDATE_SESSION_ACK:
    {
        word w = READ_WORD_NET(&b[2]);
        word h = READ_WORD_NET(&b[4]);
        if (w == expectedWidth && h == expectedHeight) {
            waitUpdateSessionAck = false;
            sessionTimer->Cancel();
        }
        log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::Recv update session ack %dx%d == %dx%d", this, w, h, expectedWidth, expectedHeight);
    }
    break;
    case AppSharingMessages::INFO_MESSAGE:
    {
        if (b[4] == AppSharingInfoMessages::RCP_LOST_SEQUENCE) {
            word lostSeq = READ_WORD_NET(&b[5]);
            log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::RemoteControlMessageRecv RCP_LOST_SEQUENCE seq=%d num=%d", this, lostSeq, b[7]);
            for (int i = 0; i < b[7]; i++) {
                word seq = lostSeq + i;
                class BlockMessage* block = sentBlocks.front();
                while (block) {
                    if (block->seq == seq) {
                        channel->ReportBlockLost(appId, block->posX, block->posY, block->dimX, block->dimY);
                        break;
                    }
                    block = block->goNext();
                }
            }
        }
        else if (b[4] == AppSharingInfoMessages::RCP_NEW_PICTURE) {
            log->Log(LOG_APPSHARING, "RemoteControlSession(%p)::RemoteControlMessageRecv RCP_NEW_PICTURE", this);
            channel->RequestNewImage(appId);
        }
    }
    break;
    }
}

bool RemoteControlSession::CalculateBitrate()
{
    totDataSent = 0;
    for (int i = 0; i < BITRATE_SLOTS; i++) {
        totDataSent += bitrateSlots[i];
    }
    //log->Log(LOG_APPSHARING, "ImageBuffer(%p)::CalculateBitrate[%d] sent=%u max=%u", this, indexBitrate, totDataSent, maxSendBytes);
    if (totDataSent > maxSendBytes) return false;
    return true;
}

void RemoteControlSession::ReportBlockLost()
{
    this->numPacketLost++;
}