/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright 2009-2016 Pierre Ossman for Cendio AB
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

// Debug output on what the congestion control is up to
#undef CONGESTION_DEBUG

#include <sys/time.h>

#ifdef CONGESTION_DEBUG
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#endif

#include <network/TcpSocket.h>
#include <rfb/VNCSConnectionST.h>
#include <rfb/LogWriter.h>
#include <rfb/Security.h>
#include <rfb/screenTypes.h>
#include <rfb/fenceTypes.h>
#include <rfb/ServerCore.h>
#include <rfb/ComparingUpdateTracker.h>
#include <rfb/KeyRemapper.h>
#include <rfb/Encoder.h>
#define XK_MISCELLANY
#define XK_XKB_KEYS
#include <rfb/keysymdef.h>

using namespace rfb;

static LogWriter vlog("VNCSConnST");

// This window should get us going fairly fast on a decent bandwidth network.
// If it's too high, it will rapidly be reduced and stay low.
static const unsigned INITIAL_WINDOW = 16384;

// TCP's minimal window is 3*MSS. But since we don't know the MSS, we
// make a guess at 4 KiB (it's probably a bit higher).
static const unsigned MINIMUM_WINDOW = 4096;

// The current default maximum window for Linux (4 MiB). Should be a good
// limit for now...
static const unsigned MAXIMUM_WINDOW = 4194304;

struct RTTInfo {
  struct timeval tv;
  int offset;
  unsigned inFlight;
};

VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
                                   bool reverse)
  : sock(s), reverseConnection(reverse),
    queryConnectTimer(this), inProcessMessages(false),
    pendingSyncFence(false), syncFence(false), fenceFlags(0),
    fenceDataLen(0), fenceData(NULL),
    baseRTT(-1), congWindow(0), ackedOffset(0), sentOffset(0),
    minRTT(-1), seenCongestion(false),
    pingCounter(0), congestionTimer(this),
    server(server_), updates(false),
    updateRenderedCursor(false), removeRenderedCursor(false),
    continuousUpdates(false), encodeManager(this), pointerEventTime(0),
    accessRights(AccessDefault), startTime(time(0))
{
  setStreams(&sock->inStream(), &sock->outStream());
  peerEndpoint.buf = sock->getPeerEndpoint();
  VNCServerST::connectionsLog.write(1,"accepted: %s", peerEndpoint.buf);

  // Configure the socket
  setSocketTimeouts();
  lastEventTime = time(0);

  server->clients.push_front(this);
}


VNCSConnectionST::~VNCSConnectionST()
{
  // If we reach here then VNCServerST is deleting us!
  VNCServerST::connectionsLog.write(1,"closed: %s (%s)",
                                    peerEndpoint.buf,
                                    (closeReason.buf) ? closeReason.buf : "");

  // Release any keys the client still had pressed
  std::set<rdr::U32>::iterator i;
  for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++) {
    vlog.debug("Releasing key 0x%x on client disconnect", *i);
    server->desktop->keyEvent(*i, false);
  }
  if (server->pointerClient == this)
    server->pointerClient = 0;

  // Remove this client from the server
  server->clients.remove(this);

  delete [] fenceData;
}


// Methods called from VNCServerST

bool VNCSConnectionST::init()
{
  try {
    initialiseProtocol();
  } catch (rdr::Exception& e) {
    close(e.str());
    return false;
  }
  return true;
}

void VNCSConnectionST::close(const char* reason)
{
  // Log the reason for the close
  if (!closeReason.buf)
    closeReason.buf = strDup(reason);
  else
    vlog.debug("second close: %s (%s)", peerEndpoint.buf, reason);

  if (authenticated()) {
      server->lastDisconnectTime = time(0);
  }

  // Just shutdown the socket and mark our state as closing.  Eventually the
  // calling code will call VNCServerST's removeSocket() method causing us to
  // be deleted.
  sock->shutdown();
  setState(RFBSTATE_CLOSING);
}


void VNCSConnectionST::processMessages()
{
  if (state() == RFBSTATE_CLOSING) return;
  try {
    // - Now set appropriate socket timeouts and process data
    setSocketTimeouts();

    inProcessMessages = true;

    // Get the underlying TCP layer to build large packets if we send
    // multiple small responses.
    network::TcpSocket::cork(sock->getFd(), true);

    while (getInStream()->checkNoWait(1)) {
      if (pendingSyncFence) {
        syncFence = true;
        pendingSyncFence = false;
      }

      processMsg();

      if (syncFence) {
        writer()->writeFence(fenceFlags, fenceDataLen, fenceData);
        syncFence = false;
      }
    }

    // Flush out everything in case we go idle after this.
    network::TcpSocket::cork(sock->getFd(), false);

    inProcessMessages = false;

    // If there were anything requiring an update, try to send it here.
    // We wait until now with this to aggregate responses and to give 
    // higher priority to user actions such as keyboard and pointer events.
    writeFramebufferUpdate();
  } catch (rdr::EndOfStream&) {
    close("Clean disconnection");
  } catch (rdr::Exception &e) {
    close(e.str());
  }
}

void VNCSConnectionST::flushSocket()
{
  if (state() == RFBSTATE_CLOSING) return;
  try {
    setSocketTimeouts();
    sock->outStream().flush();
    // Flushing the socket might release an update that was previously
    // delayed because of congestion.
    if (sock->outStream().bufferUsage() == 0)
      writeFramebufferUpdate();
  } catch (rdr::Exception &e) {
    close(e.str());
  }
}

void VNCSConnectionST::pixelBufferChange()
{
  try {
    if (!authenticated()) return;
    if (cp.width && cp.height && (server->pb->width() != cp.width ||
                                  server->pb->height() != cp.height))
    {
      // We need to clip the next update to the new size, but also add any
      // extra bits if it's bigger.  If we wanted to do this exactly, something
      // like the code below would do it, but at the moment we just update the
      // entire new size.  However, we do need to clip the damagedCursorRegion
      // because that might be added to updates in writeFramebufferUpdate().

      //updates.intersect(server->pb->getRect());
      //
      //if (server->pb->width() > cp.width)
      //  updates.add_changed(Rect(cp.width, 0, server->pb->width(),
      //                           server->pb->height()));
      //if (server->pb->height() > cp.height)
      //  updates.add_changed(Rect(0, cp.height, cp.width,
      //                           server->pb->height()));

      damagedCursorRegion.assign_intersect(server->pb->getRect());

      cp.width = server->pb->width();
      cp.height = server->pb->height();
      cp.screenLayout = server->screenLayout;
      if (state() == RFBSTATE_NORMAL) {
        // We should only send EDS to client asking for both
        if (!writer()->writeExtendedDesktopSize()) {
          if (!writer()->writeSetDesktopSize()) {
            close("Client does not support desktop resize");
            return;
          }
        }
      }
    }
    // Just update the whole screen at the moment because we're too lazy to
    // work out what's actually changed.
    updates.clear();
    updates.add_changed(server->pb->getRect());
    writeFramebufferUpdate();
  } catch(rdr::Exception &e) {
    close(e.str());
  }
}

void VNCSConnectionST::writeFramebufferUpdateOrClose()
{
  try {
    writeFramebufferUpdate();
  } catch(rdr::Exception &e) {
    close(e.str());
  }
}

void VNCSConnectionST::screenLayoutChangeOrClose(rdr::U16 reason)
{
  try {
    screenLayoutChange(reason);
  } catch(rdr::Exception &e) {
    close(e.str());
  }
}

void VNCSConnectionST::bellOrClose()
{
  try {
    if (state() == RFBSTATE_NORMAL) writer()->writeBell();
  } catch(rdr::Exception& e) {
    close(e.str());
  }
}

void VNCSConnectionST::serverCutTextOrClose(const char *str, int len)
{
  try {
    if (!(accessRights & AccessCutText)) return;
    if (!rfb::Server::sendCutText) return;
    if (state() == RFBSTATE_NORMAL)
      writer()->writeServerCutText(str, len);
  } catch(rdr::Exception& e) {
    close(e.str());
  }
}


void VNCSConnectionST::setDesktopNameOrClose(const char *name)
{
  try {
    setDesktopName(name);
  } catch(rdr::Exception& e) {
    close(e.str());
  }
}


void VNCSConnectionST::setCursorOrClose()
{
  try {
    setCursor();
  } catch(rdr::Exception& e) {
    close(e.str());
  }
}


int VNCSConnectionST::checkIdleTimeout()
{
  int idleTimeout = rfb::Server::idleTimeout;
  if (idleTimeout == 0) return 0;
  if (state() != RFBSTATE_NORMAL && idleTimeout < 15)
    idleTimeout = 15; // minimum of 15 seconds while authenticating
  time_t now = time(0);
  if (now < lastEventTime) {
    // Someone must have set the time backwards.  Set lastEventTime so that the
    // idleTimeout will count from now.
    vlog.info("Time has gone backwards - resetting idle timeout");
    lastEventTime = now;
  }
  int timeLeft = lastEventTime + idleTimeout - now;
  if (timeLeft < -60) {
    // Our callback is over a minute late - someone must have set the time
    // forwards.  Set lastEventTime so that the idleTimeout will count from
    // now.
    vlog.info("Time has gone forwards - resetting idle timeout");
    lastEventTime = now;
    return secsToMillis(idleTimeout);
  }
  if (timeLeft <= 0) {
    close("Idle timeout");
    return 0;
  }
  return secsToMillis(timeLeft);
}


bool VNCSConnectionST::getComparerState()
{
  // We interpret a low compression level as an indication that the client
  // wants to prioritise CPU usage over bandwidth, and hence disable the
  // comparing update tracker.
  return (cp.compressLevel == -1) || (cp.compressLevel > 1);
}


// renderedCursorChange() is called whenever the server-side rendered cursor
// changes shape or position.  It ensures that the next update will clean up
// the old rendered cursor and if necessary draw the new rendered cursor.

void VNCSConnectionST::renderedCursorChange()
{
  if (state() != RFBSTATE_NORMAL) return;
  if (!damagedCursorRegion.is_empty())
    removeRenderedCursor = true;
  if (needRenderedCursor()) {
    updateRenderedCursor = true;
    writeFramebufferUpdateOrClose();
  }
}

// needRenderedCursor() returns true if this client needs the server-side
// rendered cursor.  This may be because it does not support local cursor or
// because the current cursor position has not been set by this client.
// Unfortunately we can't know for sure when the current cursor position has
// been set by this client.  We guess that this is the case when the current
// cursor position is the same as the last pointer event from this client, or
// if it is a very short time since this client's last pointer event (up to a
// second).  [ Ideally we should do finer-grained timing here and make the time
// configurable, but I don't think it's that important. ]

bool VNCSConnectionST::needRenderedCursor()
{
  if (state() != RFBSTATE_NORMAL)
    return false;

  if (!cp.supportsLocalCursor && !cp.supportsLocalXCursor)
    return true;
  if (!server->cursorPos.equals(pointerEventPos) &&
      (time(0) - pointerEventTime) > 0)
    return true;

  return false;
}


void VNCSConnectionST::approveConnectionOrClose(bool accept,
                                                const char* reason)
{
  try {
    approveConnection(accept, reason);
  } catch (rdr::Exception& e) {
    close(e.str());
  }
}



// -=- Callbacks from SConnection

void VNCSConnectionST::authSuccess()
{
  lastEventTime = time(0);

  server->startDesktop();

  // - Set the connection parameters appropriately
  cp.width = server->pb->width();
  cp.height = server->pb->height();
  cp.screenLayout = server->screenLayout;
  cp.setName(server->getName());
  
  // - Set the default pixel format
  cp.setPF(server->pb->getPF());
  char buffer[256];
  cp.pf().print(buffer, 256);
  vlog.info("Server default pixel format %s", buffer);

  // - Mark the entire display as "dirty"
  updates.add_changed(server->pb->getRect());
  startTime = time(0);

  // - Bootstrap the congestion control
  ackedOffset = sock->outStream().length();
  congWindow = INITIAL_WINDOW;
}

void VNCSConnectionST::queryConnection(const char* userName)
{
  // - Authentication succeeded - clear from blacklist
  CharArray name; name.buf = sock->getPeerAddress();
  server->blHosts->clearBlackmark(name.buf);

  // - Special case to provide a more useful error message
  if (rfb::Server::neverShared && !rfb::Server::disconnectClients &&
    server->authClientCount() > 0) {
    approveConnection(false, "The server is already in use");
    return;
  }

  // - Does the client have the right to bypass the query?
  if (reverseConnection ||
      !(rfb::Server::queryConnect || sock->requiresQuery()) ||
      (accessRights & AccessNoQuery))
  {
    approveConnection(true);
    return;
  }

  // - Get the server to display an Accept/Reject dialog, if required
  //   If a dialog is displayed, the result will be PENDING, and the
  //   server will call approveConnection at a later time
  CharArray reason;
  VNCServerST::queryResult qr = server->queryConnection(sock, userName,
                                                        &reason.buf);
  if (qr == VNCServerST::PENDING) {
    queryConnectTimer.start(rfb::Server::queryConnectTimeout * 1000);
    return;
  }

  // - If server returns ACCEPT/REJECT then pass result to SConnection
  approveConnection(qr == VNCServerST::ACCEPT, reason.buf);
}

void VNCSConnectionST::clientInit(bool shared)
{
  lastEventTime = time(0);
  if (rfb::Server::alwaysShared || reverseConnection) shared = true;
  if (!(accessRights & AccessNonShared)) shared = true;
  if (rfb::Server::neverShared) shared = false;
  if (!shared) {
    if (rfb::Server::disconnectClients && (accessRights & AccessNonShared)) {
      // - Close all the other connected clients
      vlog.debug("non-shared connection - closing clients");
      server->closeClients("Non-shared connection requested", getSock());
    } else {
      // - Refuse this connection if there are existing clients, in addition to
      // this one
      if (server->authClientCount() > 1) {
        close("Server is already in use");
        return;
      }
    }
  }
  SConnection::clientInit(shared);
}

void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
{
  SConnection::setPixelFormat(pf);
  char buffer[256];
  pf.print(buffer, 256);
  vlog.info("Client pixel format %s", buffer);
  setCursor();
}

void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
{
  pointerEventTime = lastEventTime = time(0);
  server->lastUserInputTime = lastEventTime;
  if (!(accessRights & AccessPtrEvents)) return;
  if (!rfb::Server::acceptPointerEvents) return;
  if (!server->pointerClient || server->pointerClient == this) {
    pointerEventPos = pos;
    if (buttonMask)
      server->pointerClient = this;
    else
      server->pointerClient = 0;
    server->desktop->pointerEvent(pointerEventPos, buttonMask);
  }
}


class VNCSConnectionSTShiftPresser {
public:
  VNCSConnectionSTShiftPresser(SDesktop* desktop_)
    : desktop(desktop_), pressed(false) {}
  ~VNCSConnectionSTShiftPresser() {
    if (pressed) {
      vlog.debug("Releasing fake Shift_L");
      desktop->keyEvent(XK_Shift_L, false);
    }
  }
  void press() {
    vlog.debug("Pressing fake Shift_L");
    desktop->keyEvent(XK_Shift_L, true);
    pressed = true;
  }
  SDesktop* desktop;
  bool pressed;
};

// keyEvent() - record in the pressedKeys which keys were pressed.  Allow
// multiple down events (for autorepeat), but only allow a single up event.
void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) {
  lastEventTime = time(0);
  server->lastUserInputTime = lastEventTime;
  if (!(accessRights & AccessKeyEvents)) return;
  if (!rfb::Server::acceptKeyEvents) return;

  if (down)
    vlog.debug("Key pressed: 0x%x", key);
  else
    vlog.debug("Key released: 0x%x", key);

  // Remap the key if required
  if (server->keyRemapper) {
    rdr::U32 newkey;
    newkey = server->keyRemapper->remapKey(key);
    if (newkey != key) {
      vlog.debug("Key remapped to 0x%x", newkey);
      key = newkey;
    }
  }

  // Turn ISO_Left_Tab into shifted Tab.
  VNCSConnectionSTShiftPresser shiftPresser(server->desktop);
  if (key == XK_ISO_Left_Tab) {
    if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() &&
        pressedKeys.find(XK_Shift_R) == pressedKeys.end())
      shiftPresser.press();
    key = XK_Tab;
  }

  if (down) {
    pressedKeys.insert(key);
  } else {
    if (!pressedKeys.erase(key)) return;
  }
  server->desktop->keyEvent(key, down);
}

void VNCSConnectionST::clientCutText(const char* str, int len)
{
  if (!(accessRights & AccessCutText)) return;
  if (!rfb::Server::acceptCutText) return;
  server->desktop->clientCutText(str, len);
}

void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
{
  Rect safeRect;

  if (!(accessRights & AccessView)) return;

  SConnection::framebufferUpdateRequest(r, incremental);

  // Check that the client isn't sending crappy requests
  if (!r.enclosed_by(Rect(0, 0, cp.width, cp.height))) {
    vlog.error("FramebufferUpdateRequest %dx%d at %d,%d exceeds framebuffer %dx%d",
               r.width(), r.height(), r.tl.x, r.tl.y, cp.width, cp.height);
    safeRect = r.intersect(Rect(0, 0, cp.width, cp.height));
  } else {
    safeRect = r;
  }

  // Just update the requested region.
  // Framebuffer update will be sent a bit later, see processMessages().
  Region reqRgn(r);
  if (!incremental || !continuousUpdates)
    requested.assign_union(reqRgn);

  if (!incremental) {
    // Non-incremental update - treat as if area requested has changed
    updates.add_changed(reqRgn);

    // And send the screen layout to the client (which, unlike the
    // framebuffer dimensions, the client doesn't get during init)
    writer()->writeExtendedDesktopSize();

    // We do not send a DesktopSize since it only contains the
    // framebuffer size (which the client already should know) and
    // because some clients don't handle extra DesktopSize events
    // very well.
  }
}

void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height,
                                      const ScreenSet& layout)
{
  unsigned int result;

  if (!(accessRights & AccessSetDesktopSize)) return;
  if (!rfb::Server::acceptSetDesktopSize) return;

  // Don't bother the desktop with an invalid configuration
  if (!layout.validate(fb_width, fb_height)) {
    writer()->writeExtendedDesktopSize(reasonClient, resultInvalid,
                                       fb_width, fb_height, layout);
    writeFramebufferUpdate();
    return;
  }

  // FIXME: the desktop will call back to VNCServerST and an extra set
  // of ExtendedDesktopSize messages will be sent. This is okay
  // protocol-wise, but unnecessary.
  result = server->desktop->setScreenLayout(fb_width, fb_height, layout);

  writer()->writeExtendedDesktopSize(reasonClient, result,
                                     fb_width, fb_height, layout);

  // Only notify other clients on success
  if (result == resultSuccess) {
    if (server->screenLayout != layout)
        throw Exception("Desktop configured a different screen layout than requested");
    server->notifyScreenLayoutChange(this);
  }

  // but always send back a reply to the requesting client
  // (do this last as it might throw an exception on socket errors)
  writeFramebufferUpdate();
}

void VNCSConnectionST::fence(rdr::U32 flags, unsigned len, const char data[])
{
  if (flags & fenceFlagRequest) {
    if (flags & fenceFlagSyncNext) {
      pendingSyncFence = true;

      fenceFlags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter | fenceFlagSyncNext);
      fenceDataLen = len;
      delete [] fenceData;
      fenceData = NULL;
      if (len > 0) {
        fenceData = new char[len];
        memcpy(fenceData, data, len);
      }

      return;
    }

    // We handle everything synchronously so we trivially honor these modes
    flags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter);

    writer()->writeFence(flags, len, data);
    return;
  }

  struct RTTInfo rttInfo;

  switch (len) {
  case 0:
    // Initial dummy fence;
    break;
  case sizeof(struct RTTInfo):
    memcpy(&rttInfo, data, sizeof(struct RTTInfo));
    handleRTTPong(rttInfo);
    break;
  default:
    vlog.error("Fence response of unexpected size received");
  }
}

void VNCSConnectionST::enableContinuousUpdates(bool enable,
                                               int x, int y, int w, int h)
{
  Rect rect;

  if (!cp.supportsFence || !cp.supportsContinuousUpdates)
    throw Exception("Client tried to enable continuous updates when not allowed");

  continuousUpdates = enable;

  rect.setXYWH(x, y, w, h);
  cuRegion.reset(rect);

  if (enable) {
    requested.clear();
    writeFramebufferUpdate();
  } else {
    writer()->writeEndOfContinuousUpdates();
  }
}

// supportsLocalCursor() is called whenever the status of
// cp.supportsLocalCursor has changed.  If the client does now support local
// cursor, we make sure that the old server-side rendered cursor is cleaned up
// and the cursor is sent to the client.

void VNCSConnectionST::supportsLocalCursor()
{
  if (cp.supportsLocalCursor || cp.supportsLocalXCursor) {
    if (!damagedCursorRegion.is_empty())
      removeRenderedCursor = true;
    setCursor();
  }
}

void VNCSConnectionST::supportsFence()
{
  writer()->writeFence(fenceFlagRequest, 0, NULL);
}

void VNCSConnectionST::supportsContinuousUpdates()
{
  // We refuse to use continuous updates if we cannot monitor the buffer
  // usage using fences.
  if (!cp.supportsFence)
    return;

  writer()->writeEndOfContinuousUpdates();
}


bool VNCSConnectionST::handleTimeout(Timer* t)
{
  try {
    if (t == &congestionTimer)
      updateCongestion();
    else if (t == &queryConnectTimer) {
      if (state() == RFBSTATE_QUERYING)
        approveConnection(false, "The attempt to prompt the user to accept the connection failed");
    }
  } catch (rdr::Exception& e) {
    close(e.str());
  }

  return false;
}


void VNCSConnectionST::writeRTTPing()
{
  struct RTTInfo rttInfo;

  if (!cp.supportsFence)
    return;

  memset(&rttInfo, 0, sizeof(struct RTTInfo));

  gettimeofday(&rttInfo.tv, NULL);
  rttInfo.offset = sock->outStream().length();
  rttInfo.inFlight = rttInfo.offset - ackedOffset;

  // We need to make sure any old update are already processed by the
  // time we get the response back. This allows us to reliably throttle
  // back on client overload, as well as network overload.
  writer()->writeFence(fenceFlagRequest | fenceFlagBlockBefore,
                       sizeof(struct RTTInfo), (const char*)&rttInfo);

  pingCounter++;

  sentOffset = rttInfo.offset;

  // Let some data flow before we adjust the settings
  if (!congestionTimer.isStarted())
    congestionTimer.start(__rfbmin(baseRTT * 2, 100));
}

void VNCSConnectionST::handleRTTPong(const struct RTTInfo &rttInfo)
{
  unsigned rtt, delay;

  pingCounter--;

  rtt = msSince(&rttInfo.tv);
  if (rtt < 1)
    rtt = 1;

  ackedOffset = rttInfo.offset;

  // Try to estimate wire latency by tracking lowest seen latency
  if (rtt < baseRTT)
    baseRTT = rtt;

  if (rttInfo.inFlight > congWindow) {
    seenCongestion = true;

    // Estimate added delay because of overtaxed buffers
    delay = (rttInfo.inFlight - congWindow) * baseRTT / congWindow;

    if (delay < rtt)
      rtt -= delay;
    else
      rtt = 1;

    // If we underestimate the congestion window, then we'll get a latency
    // that's less than the wire latency, which will confuse other portions
    // of the code.
    if (rtt < baseRTT)
      rtt = baseRTT;
  }

  // We only keep track of the minimum latency seen (for a given interval)
  // on the basis that we want to avoid continuous buffer issue, but don't
  // mind (or even approve of) bursts.
  if (rtt < minRTT)
    minRTT = rtt;
}

bool VNCSConnectionST::isCongested()
{
  int offset;

  // Stuff still waiting in the send buffer?
  sock->outStream().flush();
  if (sock->outStream().bufferUsage() > 0)
    return true;

  if (!cp.supportsFence)
    return false;

  // Idle for too long? (and no data on the wire)
  //
  // FIXME: This should really just be one baseRTT, but we're getting
  //        problems with triggering the idle timeout on each update.
  //        Maybe we need to use a moving average for the wire latency
  //        instead of baseRTT.
  if ((sentOffset == ackedOffset) &&
      (sock->outStream().getIdleTime() > 2 * baseRTT)) {

#ifdef CONGESTION_DEBUG
    if (congWindow > INITIAL_WINDOW)
      fprintf(stderr, "Reverting to initial window (%d KiB) after %d ms\n",
              INITIAL_WINDOW / 1024, sock->outStream().getIdleTime());
#endif

    // Close congestion window and allow a transfer
    // FIXME: Reset baseRTT like Linux Vegas?
    congWindow = __rfbmin(INITIAL_WINDOW, congWindow);

    return false;
  }

  offset = sock->outStream().length();

  // FIXME: Should we compensate for non-update data?
  //        (i.e. use sentOffset instead of offset)
  if ((offset - ackedOffset) < congWindow)
    return false;

  // If we just have one outstanding "ping", that means the client has
  // started receiving our update. In order to not regress compared to
  // before we had congestion avoidance, we allow another update here.
  // This could further clog up the tubes, but congestion control isn't
  // really working properly right now anyway as the wire would otherwise
  // be idle for at least RTT/2.
  if (pingCounter == 1)
    return false;

  return true;
}


void VNCSConnectionST::updateCongestion()
{
  unsigned diff;

  if (!seenCongestion)
    return;

  diff = minRTT - baseRTT;

  if (diff > __rfbmin(100, baseRTT)) {
    // Way too fast
    congWindow = congWindow * baseRTT / minRTT;
  } else if (diff > __rfbmin(50, baseRTT/2)) {
    // Slightly too fast
    congWindow -= 4096;
  } else if (diff < 5) {
    // Way too slow
    congWindow += 8192;
  } else if (diff < 25) {
    // Too slow
    congWindow += 4096;
  }

  if (congWindow < MINIMUM_WINDOW)
    congWindow = MINIMUM_WINDOW;
  if (congWindow > MAXIMUM_WINDOW)
    congWindow = MAXIMUM_WINDOW;

#ifdef CONGESTION_DEBUG
  fprintf(stderr, "RTT: %d ms (%d ms), Window: %d KiB, Bandwidth: %g Mbps\n",
          minRTT, baseRTT, congWindow / 1024,
          congWindow * 8.0 / baseRTT / 1000.0);

#ifdef TCP_INFO
  struct tcp_info tcp_info;
  socklen_t tcp_info_length;

  tcp_info_length = sizeof(tcp_info);
  if (getsockopt(sock->getFd(), SOL_TCP, TCP_INFO,
                 (void *)&tcp_info, &tcp_info_length) == 0) {
    fprintf(stderr, "Socket: RTT: %d ms (+/- %d ms) Window %d KiB\n",
            tcp_info.tcpi_rtt / 1000, tcp_info.tcpi_rttvar / 1000,
            tcp_info.tcpi_snd_mss * tcp_info.tcpi_snd_cwnd / 1024);
  }
#endif

#endif

  minRTT = -1;
  seenCongestion = false;
}


void VNCSConnectionST::writeFramebufferUpdate()
{
  // We're in the middle of processing a command that's supposed to be
  // synchronised. Allowing an update to slip out right now might violate
  // that synchronisation.
  if (syncFence)
    return;

  // We try to aggregate responses, so don't send out anything whilst we
  // still have incoming messages. processMessages() will give us another
  // chance to run once things are idle.
  if (inProcessMessages)
    return;

  if (state() != RFBSTATE_NORMAL)
    return;
  if (requested.is_empty() && !continuousUpdates)
    return;

  // Check that we actually have some space on the link and retry in a
  // bit if things are congested.
  if (isCongested())
    return;

  // Updates often consists of many small writes, and in continuous
  // mode, we will also have small fence messages around the update. We
  // need to aggregate these in order to not clog up TCP's congestion
  // window.
  network::TcpSocket::cork(sock->getFd(), true);

  // First take care of any updates that cannot contain framebuffer data
  // changes.
  writeNoDataUpdate();

  // Then real data (if possible)
  writeDataUpdate();

  network::TcpSocket::cork(sock->getFd(), false);
}

void VNCSConnectionST::writeNoDataUpdate()
{
  if (!writer()->needNoDataUpdate())
    return;

  writer()->writeNoDataUpdate();

  // Make sure no data update is sent until next request
  requested.clear();
}

void VNCSConnectionST::writeDataUpdate()
{
  Region req;
  UpdateInfo ui;
  bool needNewUpdateInfo;
  const RenderedCursor *cursor;

  updates.enable_copyrect(cp.useCopyRect);

  // See if we are allowed to send anything right now (the framebuffer
  // might have changed in ways we haven't yet been informed of).
  if (!server->checkUpdate())
    return;

  // See what the client has requested (if anything)
  if (continuousUpdates)
    req = cuRegion.union_(requested);
  else
    req = requested;

  if (req.is_empty())
    return;

  // Get the lists of updates. Prior to exporting the data to the `ui' object,
  // getUpdateInfo() will normalize the `updates' object such way that its
  // `changed' and `copied' regions would not intersect.
  updates.getUpdateInfo(&ui, req);
  needNewUpdateInfo = false;

  // If the previous position of the rendered cursor overlaps the source of the
  // copy, then when the copy happens the corresponding rectangle in the
  // destination will be wrong, so add it to the changed region.

  if (!ui.copied.is_empty() && !damagedCursorRegion.is_empty()) {
    Region bogusCopiedCursor;

    bogusCopiedCursor.copyFrom(damagedCursorRegion);
    bogusCopiedCursor.translate(ui.copy_delta);
    bogusCopiedCursor.assign_intersect(server->pb->getRect());
    if (!ui.copied.intersect(bogusCopiedCursor).is_empty()) {
      updates.add_changed(bogusCopiedCursor);
      needNewUpdateInfo = true;
    }
  }

  // If we need to remove the old rendered cursor, just add the region to
  // the changed region.

  if (removeRenderedCursor) {
    updates.add_changed(damagedCursorRegion);
    needNewUpdateInfo = true;
    damagedCursorRegion.clear();
    removeRenderedCursor = false;
  }

  // Return if there is nothing to send the client.

  if (updates.is_empty() && !writer()->needFakeUpdate() && !updateRenderedCursor)
    return;

  // The `updates' object could change, make sure we have valid update info.

  if (needNewUpdateInfo)
    updates.getUpdateInfo(&ui, req);

  // If the client needs a server-side rendered cursor, work out the cursor
  // rectangle.  If it's empty then don't bother drawing it, but if it overlaps
  // with the update region, we need to draw the rendered cursor regardless of
  // whether it has changed.

  cursor = NULL;
  if (needRenderedCursor()) {
    Rect renderedCursorRect;

    cursor = server->getRenderedCursor();

    renderedCursorRect
      = cursor->getEffectiveRect().intersect(req.get_bounding_rect());

    if (renderedCursorRect.is_empty()) {
      cursor = NULL;
    } else if (!updateRenderedCursor &&
               ui.changed.union_(ui.copied)
               .intersect(renderedCursorRect).is_empty()) {
      cursor = NULL;
    }

    if (cursor) {
      updates.subtract(renderedCursorRect);
      updates.getUpdateInfo(&ui, req);
    }

    damagedCursorRegion.assign_union(renderedCursorRect);
    updateRenderedCursor = false;
  }

  if (ui.is_empty() && !writer()->needFakeUpdate() && !cursor)
    return;

  writeRTTPing();

  encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor);

  writeRTTPing();

  // The request might be for just part of the screen, so we cannot
  // just clear the entire update tracker.
  updates.subtract(req);

  requested.clear();
}


void VNCSConnectionST::screenLayoutChange(rdr::U16 reason)
{
  if (!authenticated())
    return;

  cp.screenLayout = server->screenLayout;

  if (state() != RFBSTATE_NORMAL)
    return;

  writer()->writeExtendedDesktopSize(reason, 0, cp.width, cp.height,
                                     cp.screenLayout);
  writeFramebufferUpdate();
}


// setCursor() is called whenever the cursor has changed shape or pixel format.
// If the client supports local cursor then it will arrange for the cursor to
// be sent to the client.

void VNCSConnectionST::setCursor()
{
  if (state() != RFBSTATE_NORMAL)
    return;

  cp.setCursor(*server->cursor);

  if (!writer()->writeSetCursorWithAlpha()) {
    if (!writer()->writeSetCursor()) {
      if (!writer()->writeSetXCursor()) {
        // No client support
        return;
      }
    }
  }

  writeFramebufferUpdate();
}

void VNCSConnectionST::setDesktopName(const char *name)
{
  cp.setName(name);

  if (state() != RFBSTATE_NORMAL)
    return;

  if (!writer()->writeSetDesktopName()) {
    fprintf(stderr, "Client does not support desktop rename\n");
    return;
  }

  writeFramebufferUpdate();
}

void VNCSConnectionST::setSocketTimeouts()
{
  int timeoutms = rfb::Server::clientWaitTimeMillis;
  soonestTimeout(&timeoutms, secsToMillis(rfb::Server::idleTimeout));
  if (timeoutms == 0)
    timeoutms = -1;
  sock->inStream().setTimeout(timeoutms);
  sock->outStream().setTimeout(timeoutms);
}

char* VNCSConnectionST::getStartTime()
{
  char* result = ctime(&startTime);
  result[24] = '\0';
  return result; 
}

void VNCSConnectionST::setStatus(int status)
{
  switch (status) {
  case 0:
    accessRights = accessRights | AccessPtrEvents | AccessKeyEvents | AccessView;
    break;
  case 1:
    accessRights = (accessRights & ~(AccessPtrEvents | AccessKeyEvents)) | AccessView;
    break;
  case 2:
    accessRights = accessRights & ~(AccessPtrEvents | AccessKeyEvents | AccessView);
    break;
  }
  framebufferUpdateRequest(server->pb->getRect(), false);
}
int VNCSConnectionST::getStatus()
{
  if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0007)
    return 0;
  if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0001)
    return 1;
  if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0000)
    return 2;
  return 4;
}

