aboutsummaryrefslogtreecommitdiffstats
Commit message (Expand)AuthorAgeFilesLines
...
* 14.4.0-beta2v14.4.0-beta2Vaadin Bot2020-09-302-2/+2
* Update dependencies for version 14.4.0-beta2Vaadin Bot2020-09-302-2/+2
* 14.4.0-beta1v14.4.0-beta1Vaadin Bot2020-09-222-2/+2
* Update dependencies for version 14.4.0-beta1Vaadin Bot2020-09-222-14/+14
* 14.4.0-alpha1v14.4.0-alpha1Vaadin Bot2020-09-042-2/+2
* Update dependencies for version 14.4.0-alpha1Vaadin Bot2020-09-042-6/+6
* 14.3.0v14.3.0Vaadin Bot2020-07-062-2/+2
* Update dependencies for version 14.3.0Vaadin Bot2020-07-062-2/+2
* 14.3.0-rc1v14.3.0-rc1Vaadin Bot2020-07-032-2/+2
* Update dependencies for version 14.3.0-rc1Vaadin Bot2020-07-032-4/+4
* 14.3.0-beta3v14.3.0-beta3Vaadin Bot2020-06-222-2/+2
* Update dependencies for version 14.3.0-beta3Vaadin Bot2020-06-222-2/+2
* 14.3.0-beta2v14.3.0-beta2Vaadin Bot2020-06-172-2/+2
* Update dependencies for version 14.3.0-beta2Vaadin Bot2020-06-172-2/+2
* 14.3.0-beta1v14.3.0-beta1Vaadin Bot2020-06-172-2/+2
* Update dependencies for version 14.3.0-beta1Vaadin Bot2020-06-172-2/+2
* 14.3.0-alpha1v14.3.0-alpha1Vaadin Bot2020-05-292-2/+2
* Update dependencies for version 14.3.0-alpha1Vaadin Bot2020-05-292-12/+12
* 14.2.0v14.2.0Vaadin Bot2020-05-192-2/+2
* Update dependencies for version 14.2.0Vaadin Bot2020-05-192-2/+2
* 14.2.0-rc1v14.2.0-rc1Vaadin Bot2020-05-122-2/+2
* Update dependencies for version 14.2.0-rc1Vaadin Bot2020-05-122-2/+2
* 14.2.0-beta1v14.2.0-beta1Vaadin Bot2020-04-222-2/+2
* Update dependencies for version 14.2.0-beta1Vaadin Bot2020-04-222-2/+2
* 14.2.0-alpha11v14.2.0-alpha11Vaadin Bot2020-04-142-2/+2
* Update dependencies for version 14.2.0-alpha11Vaadin Bot2020-04-142-2/+2
* 14.2.0-alpha10v14.2.0-alpha10Vaadin Bot2020-04-062-2/+2
* Update dependencies for version 14.2.0-alpha10Vaadin Bot2020-04-062-4/+4
* 14.2.0-alpha9v14.2.0-alpha9Vaadin Bot2020-03-202-2/+2
* Update dependencies for version 14.2.0-alpha9Vaadin Bot2020-03-202-4/+4
* 14.2.0-alpha8v14.2.0-alpha8Vaadin Bot2020-03-172-2/+2
* Update dependencies for version 14.2.0-alpha8Vaadin Bot2020-03-172-2/+2
* 14.2.0-alpha7v14.2.0-alpha7Vaadin Bot2020-03-102-2/+2
* Update dependencies for version 14.2.0-alpha7Vaadin Bot2020-03-102-4/+4
* 14.2.0-alpha6v14.2.0-alpha6Vaadin Bot2020-03-022-2/+2
* Update dependencies for version 14.2.0-alpha6Vaadin Bot2020-03-022-2/+2
* 14.2.0-alpha5v14.2.0-alpha5Vaadin Bot2020-02-252-2/+2
* Update dependencies for version 14.2.0-alpha5Vaadin Bot2020-02-252-2/+2
* 14.2.0-alpha4v14.2.0-alpha4Vaadin Bot2020-02-112-2/+2
* Update dependencies for version 14.2.0-alpha4Vaadin Bot2020-02-112-2/+2
* 14.2.0-alpha3v14.2.0-alpha3Vaadin Bot2020-01-132-2/+2
* Update dependencies for version 14.2.0-alpha3Vaadin Bot2020-01-132-2/+2
* 14.2.0-alpha2v14.2.0-alpha2Vaadin Bot2019-12-202-2/+2
* Update dependencies for version 14.2.0-alpha2Vaadin Bot2019-12-202-2/+2
* 14.2.0-alpha1v14.2.0-alpha1Vaadin Bot2019-12-092-2/+2
* Update dependencies for version 14.2.0-alpha1Vaadin Bot2019-12-092-2/+2
* 14.1.2v14.1.2Vaadin Bot2019-12-092-2/+2
* Update dependencies for version 14.1.2Vaadin Bot2019-12-092-2/+2
* 14.1.1v14.1.1Vaadin Bot2019-12-052-2/+2
* Update dependencies for version 14.1.1Vaadin Bot2019-12-052-3/+3
3 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright (C) 2011-2019 Brian P. Hinz
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

package com.tigervnc.rfb;

import java.awt.color.*;
import java.awt.image.*;
import java.nio.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

import com.tigervnc.network.*;
import com.tigervnc.rdr.*;

abstract public class CConnection extends CMsgHandler {

  static LogWriter vlog = new LogWriter("CConnection");

  private static final String osName = 
    System.getProperty("os.name").toLowerCase(Locale.ENGLISH);

  public CConnection()
  {
    super();
    csecurity = null;
    supportsLocalCursor = false; supportsDesktopResize = false;
    is = null; os = null; reader_ = null; writer_ = null;
    shared = false;
    state_ = stateEnum.RFBSTATE_UNINITIALISED;
    pendingPFChange = false; preferredEncoding = Encodings.encodingTight;
    compressLevel = 2; qualityLevel = -1;
    formatChange = false; encodingChange = false;
    firstUpdate = true; pendingUpdate = false; continuousUpdates = false;
    forceNonincremental = true;
    framebuffer = null; decoder = new DecodeManager(this);
    security = new SecurityClient();
  }

  // Methods to initialise the connection

  // setServerName() is used to provide a unique(ish) name for the server to
  // which we are connected.  This might be the result of getPeerEndpoint on
  // a TcpSocket, for example, or a host specified by DNS name & port.
  // The serverName is used when verifying the Identity of a host (see RA2).
  public void setServerName(String name_) { serverName = name_; }

  public void setServerPort(int port_) { serverPort = port_; }

  // setStreams() sets the streams to be used for the connection.  These must
  // be set before initialiseProtocol() and processMsg() are called.  The
  // CSecurity object may call setStreams() again to provide alternative
  // streams over which the RFB protocol is sent (i.e. encrypting/decrypting
  // streams).  Ownership of the streams remains with the caller
  // (i.e. SConnection will not delete them).
  public final void setStreams(InStream is_, OutStream os_)
  {
    is = is_;
    os = os_;
  }

  // setShared sets the value of the shared flag which will be sent to the
  // server upon initialisation.
  public final void setShared(boolean s) { shared = s; }

  // setFramebuffer configures the PixelBuffer that the CConnection
  // should render all pixel data in to. Note that the CConnection
  // takes ownership of the PixelBuffer and it must not be deleted by
  // anyone else. Call setFramebuffer again with NULL or a different
  // PixelBuffer to delete the previous one.
  public void setFramebuffer(ModifiablePixelBuffer fb)
  {
    decoder.flush();

    if (fb != null) {
      assert(fb.width() == server.width());
      assert(fb.height() == server.height());
    }

    if ((framebuffer != null) && (fb != null)) {
      Rect rect = new Rect();

      Raster data;

      byte[] black = new byte[4];

      // Copy still valid area

      rect.setXYWH(0, 0,
                   Math.min(fb.width(), framebuffer.width()),
                   Math.min(fb.height(), framebuffer.height()));
      data = framebuffer.getBuffer(rect);
      fb.imageRect(framebuffer.getPF(), rect, data);

      // Black out any new areas

      if (fb.width() > framebuffer.width()) {
        rect.setXYWH(framebuffer.width(), 0,
                     fb.width() - framebuffer.width(),
                     fb.height());
        fb.fillRect(rect, black);
      }

      if (fb.height() > framebuffer.height()) {
        rect.setXYWH(0, framebuffer.height(),
                     fb.width(),
                     fb.height() - framebuffer.height());
        fb.fillRect(rect, black);
      }
    }

    framebuffer = fb;
  }

  // initialiseProtocol() should be called once the streams and security
  // types are set.  Subsequently, processMsg() should be called whenever
  // there is data to read on the InStream.
  public final void initialiseProtocol()
  {
    state_ = stateEnum.RFBSTATE_PROTOCOL_VERSION;
  }

  // processMsg() should be called whenever there is data to read on the
  // InStream.  You must have called initialiseProtocol() first.
  public void processMsg()
  {
    switch (state_) {

    case RFBSTATE_PROTOCOL_VERSION: processVersionMsg();        break;
    case RFBSTATE_SECURITY_TYPES:   processSecurityTypesMsg();  break;
    case RFBSTATE_SECURITY:         processSecurityMsg();       break;
    case RFBSTATE_SECURITY_RESULT:  processSecurityResultMsg(); break;
    case RFBSTATE_INITIALISATION:   processInitMsg();           break;
    case RFBSTATE_NORMAL:           reader_.readMsg();          break;
    case RFBSTATE_UNINITIALISED:
      throw new Exception("CConnection.processMsg: not initialised yet?");
    default:
      throw new Exception("CConnection.processMsg: invalid state");
    }
  }

  private void processVersionMsg()
  {
    ByteBuffer verStr = ByteBuffer.allocate(12);
    int majorVersion;
    int minorVersion;

    vlog.debug("reading protocol version");

    if (!is.checkNoWait(12))
      return;

    is.readBytes(verStr, 12);

    if ((new String(verStr.array())).matches("RFB \\d{3}\\.\\d{3}\\n")) {
      majorVersion =
        Integer.parseInt((new String(verStr.array())).substring(4,7));
      minorVersion =
        Integer.parseInt((new String(verStr.array())).substring(8,11));
    } else {
      state_ = stateEnum.RFBSTATE_INVALID;
      throw new Exception("reading version failed: not an RFB server?");
    }

    server.setVersion(majorVersion, minorVersion);

    vlog.info("Server supports RFB protocol version "
              +server.majorVersion+"."+ server.minorVersion);

    // The only official RFB protocol versions are currently 3.3, 3.7 and 3.8
    if (server.beforeVersion(3,3)) {
      String msg = ("Server gave unsupported RFB protocol version "+
                    server.majorVersion+"."+server.minorVersion);
      vlog.error(msg);
      state_ = stateEnum.RFBSTATE_INVALID;
      throw new Exception(msg);
    } else if (server.beforeVersion(3,7)) {
      server.setVersion(3,3);
    } else if (server.afterVersion(3,8)) {
      server.setVersion(3,8);
    }

    verStr.clear();
    verStr.put(String.format("RFB %03d.%03d\n",
               majorVersion, minorVersion).getBytes()).flip();
    os.writeBytes(verStr.array(), 0, 12);
    os.flush();

    state_ = stateEnum.RFBSTATE_SECURITY_TYPES;

    vlog.info("Using RFB protocol version "+
              server.majorVersion+"."+server.minorVersion);
  }

  private void processSecurityTypesMsg()
  {
    vlog.debug("processing security types message");

    int secType = Security.secTypeInvalid;

    List<Integer> secTypes = new ArrayList<Integer>();
    secTypes = security.GetEnabledSecTypes();

    if (server.isVersion(3,3)) {

      // legacy 3.3 server may only offer "vnc authentication" or "none"

      secType = is.readU32();
      if (secType == Security.secTypeInvalid) {
        throwConnFailedException();

      } else if (secType == Security.secTypeNone || secType == Security.secTypeVncAuth) {
        Iterator<Integer> i;
        for (i = secTypes.iterator(); i.hasNext(); ) {
          int refType = (Integer)i.next();
          if (refType == secType) {
            secType = refType;
            break;
          }
        }

        if (!secTypes.contains(secType))
          secType = Security.secTypeInvalid;
      } else {
        vlog.error("Unknown 3.3 security type "+secType);
        throw new Exception("Unknown 3.3 security type");
      }

    } else {

      // 3.7 server will offer us a list

      int nServerSecTypes = is.readU8();
      if (nServerSecTypes == 0)
        throwConnFailedException();

      Iterator<Integer> j;

      for (int i = 0; i < nServerSecTypes; i++) {
        int serverSecType = is.readU8();
        vlog.debug("Server offers security type "+
                   Security.secTypeName(serverSecType)+"("+serverSecType+")");

        /*
        * Use the first type sent by server which matches client's type.
        * It means server's order specifies priority.
        */
        if (secType == Security.secTypeInvalid) {
          for (j = secTypes.iterator(); j.hasNext(); ) {
            int refType = (Integer)j.next();
            if (refType == serverSecType) {
              secType = refType;
              break;
            }
          }
        }
      }

      // Inform the server of our decision
      if (secType != Security.secTypeInvalid) {
        os.writeU8(secType);
        os.flush();
        vlog.debug("Choosing security type "+Security.secTypeName(secType)+
                   "("+secType+")");
      }
    }

    if (secType == Security.secTypeInvalid) {
      state_ = stateEnum.RFBSTATE_INVALID;
      vlog.error("No matching security types");
      throw new Exception("No matching security types");
    }

    state_ = stateEnum.RFBSTATE_SECURITY;
    csecurity = security.GetCSecurity(secType);
    processSecurityMsg();
  }

  private void processSecurityMsg() {
    vlog.debug("processing security message");
    if (csecurity.processMsg(this)) {
      state_ = stateEnum.RFBSTATE_SECURITY_RESULT;
      processSecurityResultMsg();
    }
  }

  private void processSecurityResultMsg() {
    vlog.debug("processing security result message");
    int result;
    if (server.beforeVersion(3,8) && csecurity.getType() == Security.secTypeNone) {
      result = Security.secResultOK;
    } else {
      if (!is.checkNoWait(1)) return;
      result = is.readU32();
    }
    switch (result) {
    case Security.secResultOK:
      securityCompleted();
      return;
    case Security.secResultFailed:
      vlog.debug("auth failed");
      break;
    case Security.secResultTooMany:
      vlog.debug("auth failed - too many tries");
      break;
    default:
      throw new Exception("Unknown security result from server");
    }
    state_ = stateEnum.RFBSTATE_INVALID;
    if (server.beforeVersion(3,8))
      throw new AuthFailureException();
    String reason = is.readString();
    throw new AuthFailureException(reason);
  }

  private void processInitMsg() {
    vlog.debug("reading server initialisation");
    reader_.readServerInit();
  }

  private void throwConnFailedException() {
    state_ = stateEnum.RFBSTATE_INVALID;
    String reason;
    reason = is.readString();
    throw new ConnFailedException(reason);
  }

  private void securityCompleted() {
    state_ = stateEnum.RFBSTATE_INITIALISATION;
    reader_ = new CMsgReader(this, is);
    writer_ = new CMsgWriter(server, os);
    vlog.debug("Authentication success!");
    authSuccess();
    writer_.writeClientInit(shared);
  }

  // Methods overridden from CMsgHandler

  // Note: These must be called by any deriving classes

  public void setDesktopSize(int w, int h) {
    decoder.flush();

    super.setDesktopSize(w,h);

    if (continuousUpdates)
      writer().writeEnableContinuousUpdates(true, 0, 0,
                                            server.width(),
                                            server.height());

    resizeFramebuffer();
    assert(framebuffer != null);
    assert(framebuffer.width() == server.width());
    assert(framebuffer.height() == server.height());
  }

  public void setExtendedDesktopSize(int reason,
                                     int result,
                                     int w, int h,
                                     ScreenSet layout) {
    decoder.flush();

    super.setExtendedDesktopSize(reason, result, w, h, layout);

    if (continuousUpdates)
      writer().writeEnableContinuousUpdates(true, 0, 0,
                                            server.width(),
                                            server.height());

    resizeFramebuffer();
    assert(framebuffer != null);
    assert(framebuffer.width() == server.width());
    assert(framebuffer.height() == server.height());
  }

  public void endOfContinuousUpdates()
  {
    super.endOfContinuousUpdates();

    // We've gotten the marker for a format change, so make the pending
    // one active
    if (pendingPFChange) {
      server.setPF(pendingPF);
      pendingPFChange = false;

      // We might have another change pending
      if (formatChange)
        requestNewUpdate();
    }
  }
  // serverInit() is called when the ServerInit message is received.  The
  // derived class must call on to CConnection::serverInit().
  public void serverInit(int width, int height,
                         PixelFormat pf, String name)
  {
    super.serverInit(width, height, pf, name);
    
    state_ = stateEnum.RFBSTATE_NORMAL;
    vlog.debug("initialisation done");

    initDone();
    assert(framebuffer != null);
    // FIXME: even if the client is scaling?
    assert(framebuffer.width() == server.width());
    assert(framebuffer.height() == server.height());

    // We want to make sure we call SetEncodings at least once
    encodingChange = true;

    requestNewUpdate();

    // This initial update request is a bit of a corner case, so we need
    // to help out setting the correct format here.
    if (pendingPFChange) {
      server.setPF(pendingPF);
      pendingPFChange = false;
    }
  }

  public void readAndDecodeRect(Rect r, int encoding,
                                ModifiablePixelBuffer pb)
  {
    decoder.decodeRect(r, encoding, pb);
    decoder.flush();
  }

  public void framebufferUpdateStart()
  {
    super.framebufferUpdateStart();

    assert(framebuffer != null);

    // Note: This might not be true if continuous updates are supported
    pendingUpdate = false;

    requestNewUpdate();
  }

  public void framebufferUpdateEnd()
  {
    decoder.flush();

    super.framebufferUpdateEnd();

    // A format change has been scheduled and we are now past the update
    // with the old format. Time to active the new one.
    if (pendingPFChange && !continuousUpdates) {
      server.setPF(pendingPF);
      pendingPFChange = false;
    }

    if (firstUpdate) {
      if (server.supportsContinuousUpdates) {
        vlog.info("Enabling continuous updates");
        continuousUpdates = true;
        writer().writeEnableContinuousUpdates(true, 0, 0,
                                              server.width(),
                                              server.height());
      }

      firstUpdate = false;
    }
  }

  public void dataRect(Rect r, int encoding)
  {
    decoder.decodeRect(r, encoding, framebuffer);
  }

  // Methods to be overridden in a derived class

  // authSuccess() is called when authentication has succeeded.
  public void authSuccess() { }

  // initDone() is called when the connection is fully established
  // and standard messages can be sent. This is called before the
  // initial FramebufferUpdateRequest giving a derived class the
  // chance to modify pixel format and settings. The derived class
  // must also make sure it has provided a valid framebuffer before
  // returning.
  public void initDone() { }

  // resizeFramebuffer() is called whenever the framebuffer
  // dimensions or the screen layout changes. A subclass must make
  // sure the pixel buffer has been updated once this call returns.
  public void resizeFramebuffer()
  {
    assert(false);
  }

  // refreshFramebuffer() forces a complete refresh of the entire
  // framebuffer
  public void refreshFramebuffer()
  {
    forceNonincremental = true;

    // Without fences, we cannot safely trigger an update request directly
    // but must wait for the next update to arrive.
    if (continuousUpdates)
      requestNewUpdate();
  }

  // setPreferredEncoding()/getPreferredEncoding() adjusts which
  // encoding is listed first as a hint to the server that it is the
  // preferred one
  public void setPreferredEncoding(int encoding)
  {
    if (preferredEncoding == encoding)
      return;
  
    preferredEncoding = encoding;
    encodingChange = true;
  }
  
  public int getPreferredEncoding()
  {
    return preferredEncoding;
  }
  
  // setCompressLevel()/setQualityLevel() controls the encoding hints
  // sent to the server
  public void setCompressLevel(int level)
  {
    if (compressLevel == level)
      return;
  
    compressLevel = level;
    encodingChange = true;
  }
  
  public void setQualityLevel(int level)
  {
    if (qualityLevel == level)
      return;
  
    qualityLevel = level;
    encodingChange = true;
  }

  // setPF() controls the pixel format requested from the server.
  // server.pf() will automatically be adjusted once the new format
  // is active.
  public void setPF(PixelFormat pf)
  {
    if (server.pf().equal(pf) && !formatChange)
      return;

    nextPF = pf;
    formatChange = true;
  }

  public CMsgReader reader() { return reader_; }
  public CMsgWriter writer() { return writer_; }

  public InStream getInStream() { return is; }
  public OutStream getOutStream() { return os; }

  // Access method used by SSecurity implementations that can verify servers'
  // Identities, to determine the unique(ish) name of the server.
  public String getServerName() { return serverName; }
  public int getServerPort() { return serverPort; }

  boolean isSecure() { return csecurity != null ? csecurity.isSecure() : false; }

  public enum stateEnum {
    RFBSTATE_UNINITIALISED,
    RFBSTATE_PROTOCOL_VERSION,
    RFBSTATE_SECURITY_TYPES,
    RFBSTATE_SECURITY,
    RFBSTATE_SECURITY_RESULT,
    RFBSTATE_INITIALISATION,
    RFBSTATE_NORMAL,
    RFBSTATE_INVALID
  };

  public stateEnum state() { return state_; }

  protected void setState(stateEnum s) { state_ = s; }

  protected void setReader(CMsgReader r) { reader_ = r; }
  protected void setWriter(CMsgWriter w) { writer_ = w; }

  protected ModifiablePixelBuffer getFramebuffer() { return framebuffer; }

  public void fence(int flags, int len, byte[] data)
  {
    super.fence(flags, len, data);

    if ((flags & fenceTypes.fenceFlagRequest) != 0)
      return;

    // We cannot guarantee any synchronisation at this level
    flags = 0;

    writer().writeFence(flags, len, data);
  }

  // requestNewUpdate() requests an update from the server, having set the
  // format and encoding appropriately.
  private void requestNewUpdate()
  {
    if (formatChange && !pendingPFChange) {
      /* Catch incorrect requestNewUpdate calls */
      assert(!pendingUpdate || continuousUpdates);

      // We have to make sure we switch the internal format at a safe
      // time. For continuous updates we temporarily disable updates and
      // look for a EndOfContinuousUpdates message to see when to switch.
      // For classical updates we just got a new update right before this
      // function was called, so we need to make sure we finish that
      // update before we can switch.

      pendingPFChange = true;
      pendingPF = nextPF;

      if (continuousUpdates)
        writer().writeEnableContinuousUpdates(false, 0, 0, 0, 0);

      writer().writeSetPixelFormat(pendingPF);

      if (continuousUpdates)
        writer().writeEnableContinuousUpdates(true, 0, 0,
                                              server.width(),
                                              server.height());
      formatChange = false;
    }

    if (encodingChange) {
      updateEncodings();
      encodingChange = false;
    }

    if (forceNonincremental || !continuousUpdates) {
      pendingUpdate = true;
      writer().writeFramebufferUpdateRequest(new Rect(0, 0,
                                                      server.width(),
                                                      server.height()),
                                             !forceNonincremental);
    }

    forceNonincremental = false;
  }

  // Ask for encodings based on which decoders are supported.  Assumes higher
  // encoding numbers are more desirable.

  private void updateEncodings()
  {
    List<Integer> encodings = new ArrayList<Integer>();

    if (server.supportsLocalCursor) {
      // JRE on Windows does not support alpha cursor
      if (!osName.contains("windows"))
        encodings.add(Encodings.pseudoEncodingCursorWithAlpha);
      encodings.add(Encodings.pseudoEncodingVMwareCursor);
      encodings.add(Encodings.pseudoEncodingCursor);
      encodings.add(Encodings.pseudoEncodingXCursor);
    }
    if (server.supportsDesktopResize) {
      encodings.add(Encodings.pseudoEncodingDesktopSize);
      encodings.add(Encodings.pseudoEncodingExtendedDesktopSize);
    }
    if (server.supportsClientRedirect)
      encodings.add(Encodings.pseudoEncodingClientRedirect);

    encodings.add(Encodings.pseudoEncodingDesktopName);
    encodings.add(Encodings.pseudoEncodingLastRect);
    encodings.add(Encodings.pseudoEncodingContinuousUpdates);
    encodings.add(Encodings.pseudoEncodingFence);

    if (Decoder.supported(preferredEncoding)) {
      encodings.add(preferredEncoding);
    }

    encodings.add(Encodings.encodingCopyRect);

    for (int i = Encodings.encodingMax; i >= 0; i--) {
      if ((i != preferredEncoding) && Decoder.supported(i))
        encodings.add(i);
    }

    if (compressLevel >= 0 && compressLevel <= 9)
      encodings.add(Encodings.pseudoEncodingCompressLevel0 + compressLevel);
    if (qualityLevel >= 0 && qualityLevel <= 9)
      encodings.add(Encodings.pseudoEncodingQualityLevel0 + qualityLevel);

    writer().writeSetEncodings(encodings);
  }

  private void throwAuthFailureException() {
    String reason;
    vlog.debug("state="+state()+", ver="+server.majorVersion+"."+server.minorVersion);
    if (state() == stateEnum.RFBSTATE_SECURITY_RESULT && !server.beforeVersion(3,8)) {
      reason = is.readString();
    } else {
      reason = "Authentication failure";
    }
    state_ = stateEnum.RFBSTATE_INVALID;
    vlog.error(reason);
    throw new AuthFailureException(reason);
  }

  public CSecurity csecurity;
  public SecurityClient security;

  protected boolean supportsLocalCursor;
  protected boolean supportsDesktopResize;

  private InStream is;
  private OutStream os;
  private CMsgReader reader_;
  private CMsgWriter writer_;
  private boolean deleteStreamsWhenDone;
  private boolean shared;
  private stateEnum state_;

  private String serverName;
  private int serverPort;

  private boolean pendingPFChange;
  private PixelFormat pendingPF;

  private int preferredEncoding;
  private int compressLevel;
  private int qualityLevel;

  private boolean formatChange;
  private PixelFormat nextPF;
  private boolean encodingChange;

  private boolean firstUpdate;
  private boolean pendingUpdate;
  private boolean continuousUpdates;

  private boolean forceNonincremental;

  protected ModifiablePixelBuffer framebuffer;
  private DecodeManager decoder;
}