Home | History | Annotate | Download | only in include
      1 /*
      2  * noVNC: HTML5 VNC client
      3  * Copyright (C) 2011 Joel Martin
      4  * Licensed under LGPL-3 (see LICENSE.txt)
      5  *
      6  * See README.md for usage and integration instructions.
      7  *
      8  * TIGHT decoder portion:
      9  * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
     10  */
     11 
     12 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
     13 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
     14 
     15 
     16 function RFB(defaults) {
     17 "use strict";
     18 
     19 var that           = {},  // Public API methods
     20     conf           = {},  // Configuration attributes
     21 
     22     // Pre-declare private functions used before definitions (jslint)
     23     init_vars, updateState, fail, handle_message,
     24     init_msg, normal_msg, framebufferUpdate, print_stats,
     25 
     26     pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
     27     keyEvent, pointerEvent, clientCutText,
     28 
     29     getTightCLength, extract_data_uri, scan_tight_imgQ,
     30     keyPress, mouseButton, mouseMove,
     31 
     32     checkEvents,  // Overridable for testing
     33 
     34 
     35     //
     36     // Private RFB namespace variables
     37     //
     38     rfb_host       = '',
     39     rfb_port       = 5900,
     40     rfb_password   = '',
     41     rfb_path       = '',
     42 
     43     rfb_state      = 'disconnected',
     44     rfb_version    = 0,
     45     rfb_max_version= 3.8,
     46     rfb_auth_scheme= '',
     47 
     48 
     49     // In preference order
     50     encodings      = [
     51         ['COPYRECT',         0x01 ],
     52         ['TIGHT',            0x07 ],
     53         ['TIGHT_PNG',        -260 ],
     54         ['HEXTILE',          0x05 ],
     55         ['RRE',              0x02 ],
     56         ['RAW',              0x00 ],
     57         ['DesktopSize',      -223 ],
     58         ['Cursor',           -239 ],
     59 
     60         // Psuedo-encoding settings
     61         //['JPEG_quality_lo',   -32 ],
     62         ['JPEG_quality_med',    -26 ],
     63         //['JPEG_quality_hi',   -23 ],
     64         //['compress_lo',      -255 ],
     65         ['compress_hi',        -247 ],
     66         ['last_rect',          -224 ]
     67         ],
     68 
     69     encHandlers    = {},
     70     encNames       = {},
     71     encStats       = {},     // [rectCnt, rectCntTot]
     72 
     73     ws             = null,   // Websock object
     74     display        = null,   // Display object
     75     keyboard       = null,   // Keyboard input handler object
     76     mouse          = null,   // Mouse input handler object
     77     sendTimer      = null,   // Send Queue check timer
     78     connTimer      = null,   // connection timer
     79     disconnTimer   = null,   // disconnection timer
     80     msgTimer       = null,   // queued handle_message timer
     81 
     82     // Frame buffer update state
     83     FBU            = {
     84         rects          : 0,
     85         subrects       : 0,  // RRE
     86         lines          : 0,  // RAW
     87         tiles          : 0,  // HEXTILE
     88         bytes          : 0,
     89         x              : 0,
     90         y              : 0,
     91         width          : 0,
     92         height         : 0,
     93         encoding       : 0,
     94         subencoding    : -1,
     95         background     : null,
     96         imgQ           : [],  // TIGHT_PNG image queue
     97         zlibs          : []   // TIGHT zlib streams
     98     },
     99 
    100     fb_Bpp         = 4,
    101     fb_depth       = 3,
    102     fb_width       = 0,
    103     fb_height      = 0,
    104     fb_name        = "",
    105 
    106     scan_imgQ_rate = 40, // 25 times per second or so
    107     last_req_time  = 0,
    108     rre_chunk_sz   = 100,
    109 
    110     timing         = {
    111         last_fbu       : 0,
    112         fbu_total      : 0,
    113         fbu_total_cnt  : 0,
    114         full_fbu_total : 0,
    115         full_fbu_cnt   : 0,
    116 
    117         fbu_rt_start   : 0,
    118         fbu_rt_total   : 0,
    119         fbu_rt_cnt     : 0,
    120         pixels         : 0
    121     },
    122 
    123     test_mode        = false,
    124 
    125     def_con_timeout  = Websock_native ? 2 : 5,
    126 
    127     /* Mouse state */
    128     mouse_buttonMask = 0,
    129     mouse_arr        = [],
    130     viewportDragging = false,
    131     viewportDragPos  = {};
    132 
    133 // Configuration attributes
    134 Util.conf_defaults(conf, that, defaults, [
    135     ['target',             'wo', 'dom', null, 'VNC display rendering Canvas object'],
    136     ['focusContainer',     'wo', 'dom', document, 'DOM element that captures keyboard input'],
    137 
    138     ['encrypt',            'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
    139     ['true_color',         'rw', 'bool', true,  'Request true color pixel data'],
    140     ['local_cursor',       'rw', 'bool', false, 'Request locally rendered cursor'],
    141     ['shared',             'rw', 'bool', true,  'Request shared mode'],
    142     ['view_only',          'rw', 'bool', false, 'Disable client mouse/keyboard'],
    143 
    144     ['connectTimeout',     'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
    145     ['disconnectTimeout',  'rw', 'int', 3,    'Time (s) to wait for disconnection'],
    146 
    147     ['viewportDrag',       'rw', 'bool', false, 'Move the viewport on mouse drags'],
    148 
    149     ['check_rate',         'rw', 'int', 217,  'Timing (ms) of send/receive check'],
    150     ['fbu_req_rate',       'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
    151 
    152     // Callback functions
    153     ['onUpdateState',      'rw', 'func', function() { },
    154         'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
    155     ['onPasswordRequired', 'rw', 'func', function() { },
    156         'onPasswordRequired(rfb): VNC password is required '],
    157     ['onClipboard',        'rw', 'func', function() { },
    158         'onClipboard(rfb, text): RFB clipboard contents received'],
    159     ['onBell',             'rw', 'func', function() { },
    160         'onBell(rfb): RFB Bell message received '],
    161     ['onFBUReceive',       'rw', 'func', function() { },
    162         'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
    163     ['onFBUComplete',      'rw', 'func', function() { },
    164         'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
    165 
    166     // These callback names are deprecated
    167     ['updateState',        'rw', 'func', function() { },
    168         'obsolete, use onUpdateState'],
    169     ['clipboardReceive',   'rw', 'func', function() { },
    170         'obsolete, use onClipboard']
    171     ]);
    172 
    173 
    174 // Override/add some specific configuration getters/setters
    175 that.set_local_cursor = function(cursor) {
    176     if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
    177         conf.local_cursor = false;
    178     } else {
    179         if (display.get_cursor_uri()) {
    180             conf.local_cursor = true;
    181         } else {
    182             Util.Warn("Browser does not support local cursor");
    183         }
    184     }
    185 };
    186 
    187 // These are fake configuration getters
    188 that.get_display = function() { return display; };
    189 
    190 that.get_keyboard = function() { return keyboard; };
    191 
    192 that.get_mouse = function() { return mouse; };
    193 
    194 
    195 
    196 //
    197 // Setup routines
    198 //
    199 
    200 // Create the public API interface and initialize values that stay
    201 // constant across connect/disconnect
    202 function constructor() {
    203     var i, rmode;
    204     Util.Debug(">> RFB.constructor");
    205 
    206     // Create lookup tables based encoding number
    207     for (i=0; i < encodings.length; i+=1) {
    208         encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
    209         encNames[encodings[i][1]] = encodings[i][0];
    210         encStats[encodings[i][1]] = [0, 0];
    211     }
    212     // Initialize display, mouse, keyboard, and websock
    213     try {
    214         display   = new Display({'target': conf.target});
    215     } catch (exc) {
    216         Util.Error("Display exception: " + exc);
    217         updateState('fatal', "No working Display");
    218     }
    219     keyboard = new Keyboard({'target': conf.focusContainer,
    220                                 'onKeyPress': keyPress});
    221     mouse    = new Mouse({'target': conf.target,
    222                             'onMouseButton': mouseButton,
    223                             'onMouseMove': mouseMove});
    224 
    225     rmode = display.get_render_mode();
    226 
    227     ws = new Websock();
    228     ws.on('message', handle_message);
    229     ws.on('open', function() {
    230         if (rfb_state === "connect") {
    231             updateState('ProtocolVersion', "Starting VNC handshake");
    232         } else {
    233             fail("Got unexpected WebSockets connection");
    234         }
    235     });
    236     ws.on('close', function(e) {
    237         if (e.code) {
    238             Util.Info("Close code: " + e.code + ", reason: " + e.reason + ", wasClean: " + e.wasClean);
    239         }
    240         if (rfb_state === 'disconnect') {
    241             updateState('disconnected', 'VNC disconnected');
    242         } else if (rfb_state === 'ProtocolVersion') {
    243             fail('Failed to connect to server');
    244         } else if (rfb_state in {'failed':1, 'disconnected':1}) {
    245             Util.Error("Received onclose while disconnected");
    246         } else  {
    247             fail('Server disconnected');
    248         }
    249     });
    250     ws.on('error', function(e) {
    251         fail("WebSock error: " + e);
    252     });
    253 
    254 
    255     init_vars();
    256 
    257     /* Check web-socket-js if no builtin WebSocket support */
    258     if (Websock_native) {
    259         Util.Info("Using native WebSockets");
    260         updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
    261     } else {
    262         Util.Warn("Using web-socket-js bridge. Flash version: " +
    263                   Util.Flash.version);
    264         if ((! Util.Flash) ||
    265             (Util.Flash.version < 9)) {
    266             updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
    267         } else if (document.location.href.substr(0, 7) === "file://") {
    268             updateState('fatal',
    269                     "'file://' URL is incompatible with Adobe Flash");
    270         } else {
    271             updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
    272         }
    273     }
    274 
    275     Util.Debug("<< RFB.constructor");
    276     return that;  // Return the public API interface
    277 }
    278 
    279 function connect() {
    280     Util.Debug(">> RFB.connect");
    281     var uri;
    282 
    283     if (typeof UsingSocketIO !== "undefined") {
    284         uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
    285     } else {
    286         if (conf.encrypt) {
    287             uri = "wss://";
    288         } else {
    289             uri = "ws://";
    290         }
    291         uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
    292     }
    293     Util.Info("connecting to " + uri);
    294     ws.open(uri);
    295 
    296     Util.Debug("<< RFB.connect");
    297 }
    298 
    299 // Initialize variables that are reset before each connection
    300 init_vars = function() {
    301     var i;
    302 
    303     /* Reset state */
    304     ws.init();
    305 
    306     FBU.rects        = 0;
    307     FBU.subrects     = 0;  // RRE and HEXTILE
    308     FBU.lines        = 0;  // RAW
    309     FBU.tiles        = 0;  // HEXTILE
    310     FBU.imgQ         = []; // TIGHT_PNG image queue
    311     FBU.zlibs        = []; // TIGHT zlib encoders
    312     mouse_buttonMask = 0;
    313     mouse_arr        = [];
    314 
    315     // Clear the per connection encoding stats
    316     for (i=0; i < encodings.length; i+=1) {
    317         encStats[encodings[i][1]][0] = 0;
    318     }
    319 
    320     for (i=0; i < 4; i++) {
    321         //FBU.zlibs[i] = new InflateStream();
    322         FBU.zlibs[i] = new TINF();
    323         FBU.zlibs[i].init();
    324     }
    325 };
    326 
    327 // Print statistics
    328 print_stats = function() {
    329     var i, s;
    330     Util.Info("Encoding stats for this connection:");
    331     for (i=0; i < encodings.length; i+=1) {
    332         s = encStats[encodings[i][1]];
    333         if ((s[0] + s[1]) > 0) {
    334             Util.Info("    " + encodings[i][0] + ": " +
    335                       s[0] + " rects");
    336         }
    337     }
    338     Util.Info("Encoding stats since page load:");
    339     for (i=0; i < encodings.length; i+=1) {
    340         s = encStats[encodings[i][1]];
    341         if ((s[0] + s[1]) > 0) {
    342             Util.Info("    " + encodings[i][0] + ": " +
    343                       s[1] + " rects");
    344         }
    345     }
    346 };
    347 
    348 //
    349 // Utility routines
    350 //
    351 
    352 
    353 /*
    354  * Page states:
    355  *   loaded       - page load, equivalent to disconnected
    356  *   disconnected - idle state
    357  *   connect      - starting to connect (to ProtocolVersion)
    358  *   normal       - connected
    359  *   disconnect   - starting to disconnect
    360  *   failed       - abnormal disconnect
    361  *   fatal        - failed to load page, or fatal error
    362  *
    363  * RFB protocol initialization states:
    364  *   ProtocolVersion
    365  *   Security
    366  *   Authentication
    367  *   password     - waiting for password, not part of RFB
    368  *   SecurityResult
    369  *   ClientInitialization - not triggered by server message
    370  *   ServerInitialization (to normal)
    371  */
    372 updateState = function(state, statusMsg) {
    373     var func, cmsg, oldstate = rfb_state;
    374 
    375     if (state === oldstate) {
    376         /* Already here, ignore */
    377         Util.Debug("Already in state '" + state + "', ignoring.");
    378         return;
    379     }
    380 
    381     /*
    382      * These are disconnected states. A previous connect may
    383      * asynchronously cause a connection so make sure we are closed.
    384      */
    385     if (state in {'disconnected':1, 'loaded':1, 'connect':1,
    386                   'disconnect':1, 'failed':1, 'fatal':1}) {
    387         if (sendTimer) {
    388             clearInterval(sendTimer);
    389             sendTimer = null;
    390         }
    391 
    392         if (msgTimer) {
    393             clearInterval(msgTimer);
    394             msgTimer = null;
    395         }
    396 
    397         if (display && display.get_context()) {
    398             keyboard.ungrab();
    399             mouse.ungrab();
    400             display.defaultCursor();
    401             if ((Util.get_logging() !== 'debug') ||
    402                 (state === 'loaded')) {
    403                 // Show noVNC logo on load and when disconnected if
    404                 // debug is off
    405                 display.clear();
    406             }
    407         }
    408 
    409         ws.close();
    410     }
    411 
    412     if (oldstate === 'fatal') {
    413         Util.Error("Fatal error, cannot continue");
    414     }
    415 
    416     if ((state === 'failed') || (state === 'fatal')) {
    417         func = Util.Error;
    418     } else {
    419         func = Util.Warn;
    420     }
    421 
    422     if ((oldstate === 'failed') && (state === 'disconnected')) {
    423         // Do disconnect action, but stay in failed state.
    424         rfb_state = 'failed';
    425     } else {
    426         rfb_state = state;
    427     }
    428 
    429     cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
    430     func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
    431 
    432     if (connTimer && (rfb_state !== 'connect')) {
    433         Util.Debug("Clearing connect timer");
    434         clearInterval(connTimer);
    435         connTimer = null;
    436     }
    437 
    438     if (disconnTimer && (rfb_state !== 'disconnect')) {
    439         Util.Debug("Clearing disconnect timer");
    440         clearInterval(disconnTimer);
    441         disconnTimer = null;
    442     }
    443 
    444     switch (state) {
    445     case 'normal':
    446         if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
    447             Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
    448         }
    449 
    450         break;
    451 
    452 
    453     case 'connect':
    454 
    455         connTimer = setTimeout(function () {
    456                 fail("Connect timeout");
    457             }, conf.connectTimeout * 1000);
    458 
    459         init_vars();
    460         connect();
    461 
    462         // WebSocket.onopen transitions to 'ProtocolVersion'
    463         break;
    464 
    465 
    466     case 'disconnect':
    467 
    468         if (! test_mode) {
    469             disconnTimer = setTimeout(function () {
    470                     fail("Disconnect timeout");
    471                 }, conf.disconnectTimeout * 1000);
    472         }
    473 
    474         print_stats();
    475 
    476         // WebSocket.onclose transitions to 'disconnected'
    477         break;
    478 
    479 
    480     case 'failed':
    481         if (oldstate === 'disconnected') {
    482             Util.Error("Invalid transition from 'disconnected' to 'failed'");
    483         }
    484         if (oldstate === 'normal') {
    485             Util.Error("Error while connected.");
    486         }
    487         if (oldstate === 'init') {
    488             Util.Error("Error while initializing.");
    489         }
    490 
    491         // Make sure we transition to disconnected
    492         setTimeout(function() { updateState('disconnected'); }, 50);
    493 
    494         break;
    495 
    496 
    497     default:
    498         // No state change action to take
    499 
    500     }
    501 
    502     if ((oldstate === 'failed') && (state === 'disconnected')) {
    503         // Leave the failed message
    504         conf.updateState(that, state, oldstate); // Obsolete
    505         conf.onUpdateState(that, state, oldstate);
    506     } else {
    507         conf.updateState(that, state, oldstate, statusMsg); // Obsolete
    508         conf.onUpdateState(that, state, oldstate, statusMsg);
    509     }
    510 };
    511 
    512 fail = function(msg) {
    513     updateState('failed', msg);
    514     return false;
    515 };
    516 
    517 handle_message = function() {
    518     //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
    519     //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
    520     if (ws.rQlen() === 0) {
    521         Util.Warn("handle_message called on empty receive queue");
    522         return;
    523     }
    524     switch (rfb_state) {
    525     case 'disconnected':
    526     case 'failed':
    527         Util.Error("Got data while disconnected");
    528         break;
    529     case 'normal':
    530         if (normal_msg() && ws.rQlen() > 0) {
    531             // true means we can continue processing
    532             // Give other events a chance to run
    533             if (msgTimer === null) {
    534                 Util.Debug("More data to process, creating timer");
    535                 msgTimer = setTimeout(function () {
    536                             msgTimer = null;
    537                             handle_message();
    538                         }, 10);
    539             } else {
    540                 Util.Debug("More data to process, existing timer");
    541             }
    542         }
    543         break;
    544     default:
    545         init_msg();
    546         break;
    547     }
    548 };
    549 
    550 
    551 function genDES(password, challenge) {
    552     var i, passwd = [];
    553     for (i=0; i < password.length; i += 1) {
    554         passwd.push(password.charCodeAt(i));
    555     }
    556     return (new DES(passwd)).encrypt(challenge);
    557 }
    558 
    559 function flushClient() {
    560     if (mouse_arr.length > 0) {
    561         //send(mouse_arr.concat(fbUpdateRequests()));
    562         ws.send(mouse_arr);
    563         setTimeout(function() {
    564                 ws.send(fbUpdateRequests());
    565             }, 50);
    566 
    567         mouse_arr = [];
    568         return true;
    569     } else {
    570         return false;
    571     }
    572 }
    573 
    574 // overridable for testing
    575 checkEvents = function() {
    576     var now;
    577     if (rfb_state === 'normal' && !viewportDragging) {
    578         if (! flushClient()) {
    579             now = new Date().getTime();
    580             if (now > last_req_time + conf.fbu_req_rate) {
    581                 last_req_time = now;
    582                 ws.send(fbUpdateRequests());
    583             }
    584         }
    585     }
    586     setTimeout(checkEvents, conf.check_rate);
    587 };
    588 
    589 keyPress = function(keysym, down) {
    590     var arr;
    591 
    592     if (conf.view_only) { return; } // View only, skip keyboard events
    593 
    594     arr = keyEvent(keysym, down);
    595     arr = arr.concat(fbUpdateRequests());
    596     ws.send(arr);
    597 };
    598 
    599 mouseButton = function(x, y, down, bmask) {
    600     if (down) {
    601         mouse_buttonMask |= bmask;
    602     } else {
    603         mouse_buttonMask ^= bmask;
    604     }
    605 
    606     if (conf.viewportDrag) {
    607         if (down && !viewportDragging) {
    608             viewportDragging = true;
    609             viewportDragPos = {'x': x, 'y': y};
    610 
    611             // Skip sending mouse events
    612             return;
    613         } else {
    614             viewportDragging = false;
    615             ws.send(fbUpdateRequests()); // Force immediate redraw
    616         }
    617     }
    618 
    619     if (conf.view_only) { return; } // View only, skip mouse events
    620 
    621     mouse_arr = mouse_arr.concat(
    622             pointerEvent(display.absX(x), display.absY(y)) );
    623     flushClient();
    624 };
    625 
    626 mouseMove = function(x, y) {
    627     //Util.Debug('>> mouseMove ' + x + "," + y);
    628     var deltaX, deltaY;
    629 
    630     if (viewportDragging) {
    631         //deltaX = x - viewportDragPos.x; // drag viewport
    632         deltaX = viewportDragPos.x - x; // drag frame buffer
    633         //deltaY = y - viewportDragPos.y; // drag viewport
    634         deltaY = viewportDragPos.y - y; // drag frame buffer
    635         viewportDragPos = {'x': x, 'y': y};
    636 
    637         display.viewportChange(deltaX, deltaY);
    638 
    639         // Skip sending mouse events
    640         return;
    641     }
    642 
    643     if (conf.view_only) { return; } // View only, skip mouse events
    644 
    645     mouse_arr = mouse_arr.concat(
    646             pointerEvent(display.absX(x), display.absY(y)) );
    647 };
    648 
    649 
    650 //
    651 // Server message handlers
    652 //
    653 
    654 // RFB/VNC initialisation message handler
    655 init_msg = function() {
    656     //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
    657 
    658     var strlen, reason, length, sversion, cversion,
    659         i, types, num_types, challenge, response, bpp, depth,
    660         big_endian, red_max, green_max, blue_max, red_shift,
    661         green_shift, blue_shift, true_color, name_length;
    662 
    663     //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
    664     switch (rfb_state) {
    665 
    666     case 'ProtocolVersion' :
    667         if (ws.rQlen() < 12) {
    668             return fail("Incomplete protocol version");
    669         }
    670         sversion = ws.rQshiftStr(12).substr(4,7);
    671         Util.Info("Server ProtocolVersion: " + sversion);
    672         switch (sversion) {
    673             case "003.003": rfb_version = 3.3; break;
    674             case "003.006": rfb_version = 3.3; break;  // UltraVNC
    675             case "003.889": rfb_version = 3.3; break;  // Apple Remote Desktop
    676             case "003.007": rfb_version = 3.7; break;
    677             case "003.008": rfb_version = 3.8; break;
    678             case "004.000": rfb_version = 3.8; break;  // Intel AMT KVM
    679             default:
    680                 return fail("Invalid server version " + sversion);
    681         }
    682         if (rfb_version > rfb_max_version) {
    683             rfb_version = rfb_max_version;
    684         }
    685 
    686         if (! test_mode) {
    687             sendTimer = setInterval(function() {
    688                     // Send updates either at a rate of one update
    689                     // every 50ms, or whatever slower rate the network
    690                     // can handle.
    691                     ws.flush();
    692                 }, 50);
    693         }
    694 
    695         cversion = "00" + parseInt(rfb_version,10) +
    696                    ".00" + ((rfb_version * 10) % 10);
    697         ws.send_string("RFB " + cversion + "\n");
    698         updateState('Security', "Sent ProtocolVersion: " + cversion);
    699         break;
    700 
    701     case 'Security' :
    702         if (rfb_version >= 3.7) {
    703             // Server sends supported list, client decides
    704             num_types = ws.rQshift8();
    705             if (ws.rQwait("security type", num_types, 1)) { return false; }
    706             if (num_types === 0) {
    707                 strlen = ws.rQshift32();
    708                 reason = ws.rQshiftStr(strlen);
    709                 return fail("Security failure: " + reason);
    710             }
    711             rfb_auth_scheme = 0;
    712             types = ws.rQshiftBytes(num_types);
    713             Util.Debug("Server security types: " + types);
    714             for (i=0; i < types.length; i+=1) {
    715                 if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
    716                     rfb_auth_scheme = types[i];
    717                 }
    718             }
    719             if (rfb_auth_scheme === 0) {
    720                 return fail("Unsupported security types: " + types);
    721             }
    722 
    723             ws.send([rfb_auth_scheme]);
    724         } else {
    725             // Server decides
    726             if (ws.rQwait("security scheme", 4)) { return false; }
    727             rfb_auth_scheme = ws.rQshift32();
    728         }
    729         updateState('Authentication',
    730                 "Authenticating using scheme: " + rfb_auth_scheme);
    731         init_msg();  // Recursive fallthrough (workaround JSLint complaint)
    732         break;
    733 
    734     // Triggered by fallthough, not by server message
    735     case 'Authentication' :
    736         //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
    737         switch (rfb_auth_scheme) {
    738             case 0:  // connection failed
    739                 if (ws.rQwait("auth reason", 4)) { return false; }
    740                 strlen = ws.rQshift32();
    741                 reason = ws.rQshiftStr(strlen);
    742                 return fail("Auth failure: " + reason);
    743             case 1:  // no authentication
    744                 if (rfb_version >= 3.8) {
    745                     updateState('SecurityResult');
    746                     return;
    747                 }
    748                 // Fall through to ClientInitialisation
    749                 break;
    750             case 2:  // VNC authentication
    751                 if (rfb_password.length === 0) {
    752                     // Notify via both callbacks since it is kind of
    753                     // a RFB state change and a UI interface issue.
    754                     updateState('password', "Password Required");
    755                     conf.onPasswordRequired(that);
    756                     return;
    757                 }
    758                 if (ws.rQwait("auth challenge", 16)) { return false; }
    759                 challenge = ws.rQshiftBytes(16);
    760                 //Util.Debug("Password: " + rfb_password);
    761                 //Util.Debug("Challenge: " + challenge +
    762                 //           " (" + challenge.length + ")");
    763                 response = genDES(rfb_password, challenge);
    764                 //Util.Debug("Response: " + response +
    765                 //           " (" + response.length + ")");
    766 
    767                 //Util.Debug("Sending DES encrypted auth response");
    768                 ws.send(response);
    769                 updateState('SecurityResult');
    770                 return;
    771             default:
    772                 fail("Unsupported auth scheme: " + rfb_auth_scheme);
    773                 return;
    774         }
    775         updateState('ClientInitialisation', "No auth required");
    776         init_msg();  // Recursive fallthrough (workaround JSLint complaint)
    777         break;
    778 
    779     case 'SecurityResult' :
    780         if (ws.rQwait("VNC auth response ", 4)) { return false; }
    781         switch (ws.rQshift32()) {
    782             case 0:  // OK
    783                 // Fall through to ClientInitialisation
    784                 break;
    785             case 1:  // failed
    786                 if (rfb_version >= 3.8) {
    787                     length = ws.rQshift32();
    788                     if (ws.rQwait("SecurityResult reason", length, 8)) {
    789                         return false;
    790                     }
    791                     reason = ws.rQshiftStr(length);
    792                     fail(reason);
    793                 } else {
    794                     fail("Authentication failed");
    795                 }
    796                 return;
    797             case 2:  // too-many
    798                 return fail("Too many auth attempts");
    799         }
    800         updateState('ClientInitialisation', "Authentication OK");
    801         init_msg();  // Recursive fallthrough (workaround JSLint complaint)
    802         break;
    803 
    804     // Triggered by fallthough, not by server message
    805     case 'ClientInitialisation' :
    806         ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
    807         updateState('ServerInitialisation', "Authentication OK");
    808         break;
    809 
    810     case 'ServerInitialisation' :
    811         if (ws.rQwait("server initialization", 24)) { return false; }
    812 
    813         /* Screen size */
    814         fb_width  = ws.rQshift16();
    815         fb_height = ws.rQshift16();
    816 
    817         /* PIXEL_FORMAT */
    818         bpp            = ws.rQshift8();
    819         depth          = ws.rQshift8();
    820         big_endian     = ws.rQshift8();
    821         true_color     = ws.rQshift8();
    822 
    823         red_max        = ws.rQshift16();
    824         green_max      = ws.rQshift16();
    825         blue_max       = ws.rQshift16();
    826         red_shift      = ws.rQshift8();
    827         green_shift    = ws.rQshift8();
    828         blue_shift     = ws.rQshift8();
    829         ws.rQshiftStr(3); // padding
    830 
    831         Util.Info("Screen: " + fb_width + "x" + fb_height +
    832                   ", bpp: " + bpp + ", depth: " + depth +
    833                   ", big_endian: " + big_endian +
    834                   ", true_color: " + true_color +
    835                   ", red_max: " + red_max +
    836                   ", green_max: " + green_max +
    837                   ", blue_max: " + blue_max +
    838                   ", red_shift: " + red_shift +
    839                   ", green_shift: " + green_shift +
    840                   ", blue_shift: " + blue_shift);
    841 
    842         if (big_endian !== 0) {
    843             Util.Warn("Server native endian is not little endian");
    844         }
    845         if (red_shift !== 16) {
    846             Util.Warn("Server native red-shift is not 16");
    847         }
    848         if (blue_shift !== 0) {
    849             Util.Warn("Server native blue-shift is not 0");
    850         }
    851 
    852         /* Connection name/title */
    853         name_length   = ws.rQshift32();
    854         fb_name = ws.rQshiftStr(name_length);
    855 
    856         if (conf.true_color && fb_name === "Intel(r) AMT KVM")
    857         {
    858             Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
    859             conf.true_color = false;
    860         }
    861 
    862         display.set_true_color(conf.true_color);
    863         display.resize(fb_width, fb_height);
    864         keyboard.grab();
    865         mouse.grab();
    866 
    867         if (conf.true_color) {
    868             fb_Bpp           = 4;
    869             fb_depth         = 3;
    870         } else {
    871             fb_Bpp           = 1;
    872             fb_depth         = 1;
    873         }
    874 
    875         response = pixelFormat();
    876         response = response.concat(clientEncodings());
    877         response = response.concat(fbUpdateRequests());
    878         timing.fbu_rt_start = (new Date()).getTime();
    879         ws.send(response);
    880 
    881         /* Start pushing/polling */
    882         setTimeout(checkEvents, conf.check_rate);
    883         setTimeout(scan_tight_imgQ, scan_imgQ_rate);
    884 
    885         if (conf.encrypt) {
    886             updateState('normal', "Connected (encrypted) to: " + fb_name);
    887         } else {
    888             updateState('normal', "Connected (unencrypted) to: " + fb_name);
    889         }
    890         break;
    891     }
    892     //Util.Debug("<< init_msg");
    893 };
    894 
    895 
    896 /* Normal RFB/VNC server message handler */
    897 normal_msg = function() {
    898     //Util.Debug(">> normal_msg");
    899 
    900     var ret = true, msg_type, length, text,
    901         c, first_colour, num_colours, red, green, blue;
    902 
    903     if (FBU.rects > 0) {
    904         msg_type = 0;
    905     } else {
    906         msg_type = ws.rQshift8();
    907     }
    908     switch (msg_type) {
    909     case 0:  // FramebufferUpdate
    910         ret = framebufferUpdate(); // false means need more data
    911         break;
    912     case 1:  // SetColourMapEntries
    913         Util.Debug("SetColourMapEntries");
    914         ws.rQshift8();  // Padding
    915         first_colour = ws.rQshift16(); // First colour
    916         num_colours = ws.rQshift16();
    917         if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
    918 
    919         for (c=0; c < num_colours; c+=1) {
    920             red = ws.rQshift16();
    921             //Util.Debug("red before: " + red);
    922             red = parseInt(red / 256, 10);
    923             //Util.Debug("red after: " + red);
    924             green = parseInt(ws.rQshift16() / 256, 10);
    925             blue = parseInt(ws.rQshift16() / 256, 10);
    926             display.set_colourMap([blue, green, red], first_colour + c);
    927         }
    928         Util.Debug("colourMap: " + display.get_colourMap());
    929         Util.Info("Registered " + num_colours + " colourMap entries");
    930         //Util.Debug("colourMap: " + display.get_colourMap());
    931         break;
    932     case 2:  // Bell
    933         Util.Debug("Bell");
    934         conf.onBell(that);
    935         break;
    936     case 3:  // ServerCutText
    937         Util.Debug("ServerCutText");
    938         if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
    939         ws.rQshiftBytes(3);  // Padding
    940         length = ws.rQshift32();
    941         if (ws.rQwait("ServerCutText", length, 8)) { return false; }
    942 
    943         text = ws.rQshiftStr(length);
    944         conf.clipboardReceive(that, text); // Obsolete
    945         conf.onClipboard(that, text);
    946         break;
    947     default:
    948         fail("Disconnected: illegal server message type " + msg_type);
    949         Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
    950         break;
    951     }
    952     //Util.Debug("<< normal_msg");
    953     return ret;
    954 };
    955 
    956 framebufferUpdate = function() {
    957     var now, hdr, fbu_rt_diff, ret = true;
    958 
    959     if (FBU.rects === 0) {
    960         //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
    961         if (ws.rQwait("FBU header", 3)) {
    962             ws.rQunshift8(0);  // FBU msg_type
    963             return false;
    964         }
    965         ws.rQshift8();  // padding
    966         FBU.rects = ws.rQshift16();
    967         //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
    968         FBU.bytes = 0;
    969         timing.cur_fbu = 0;
    970         if (timing.fbu_rt_start > 0) {
    971             now = (new Date()).getTime();
    972             Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
    973         }
    974     }
    975 
    976     while (FBU.rects > 0) {
    977         if (rfb_state !== "normal") {
    978             return false;
    979         }
    980         if (ws.rQwait("FBU", FBU.bytes)) { return false; }
    981         if (FBU.bytes === 0) {
    982             if (ws.rQwait("rect header", 12)) { return false; }
    983             /* New FramebufferUpdate */
    984 
    985             hdr = ws.rQshiftBytes(12);
    986             FBU.x      = (hdr[0] << 8) + hdr[1];
    987             FBU.y      = (hdr[2] << 8) + hdr[3];
    988             FBU.width  = (hdr[4] << 8) + hdr[5];
    989             FBU.height = (hdr[6] << 8) + hdr[7];
    990             FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
    991                                     (hdr[10] << 8) +  hdr[11], 10);
    992 
    993             conf.onFBUReceive(that,
    994                     {'x': FBU.x, 'y': FBU.y,
    995                      'width': FBU.width, 'height': FBU.height,
    996                      'encoding': FBU.encoding,
    997                      'encodingName': encNames[FBU.encoding]});
    998 
    999             if (encNames[FBU.encoding]) {
   1000                 // Debug:
   1001                 /*
   1002                 var msg =  "FramebufferUpdate rects:" + FBU.rects;
   1003                 msg += " x: " + FBU.x + " y: " + FBU.y;
   1004                 msg += " width: " + FBU.width + " height: " + FBU.height;
   1005                 msg += " encoding:" + FBU.encoding;
   1006                 msg += "(" + encNames[FBU.encoding] + ")";
   1007                 msg += ", ws.rQlen(): " + ws.rQlen();
   1008                 Util.Debug(msg);
   1009                 */
   1010             } else {
   1011                 fail("Disconnected: unsupported encoding " +
   1012                     FBU.encoding);
   1013                 return false;
   1014             }
   1015         }
   1016 
   1017         timing.last_fbu = (new Date()).getTime();
   1018 
   1019         ret = encHandlers[FBU.encoding]();
   1020 
   1021         now = (new Date()).getTime();
   1022         timing.cur_fbu += (now - timing.last_fbu);
   1023 
   1024         if (ret) {
   1025             encStats[FBU.encoding][0] += 1;
   1026             encStats[FBU.encoding][1] += 1;
   1027             timing.pixels += FBU.width * FBU.height;
   1028         }
   1029 
   1030         if (FBU.rects === 0 || (timing.pixels >= (fb_width * fb_height))) {
   1031             if (((FBU.width === fb_width) &&
   1032                         (FBU.height === fb_height)) ||
   1033                     (timing.fbu_rt_start > 0)) {
   1034                 timing.full_fbu_total += timing.cur_fbu;
   1035                 timing.full_fbu_cnt += 1;
   1036                 Util.Info("Timing of full FBU, cur: " +
   1037                           timing.cur_fbu + ", total: " +
   1038                           timing.full_fbu_total + ", cnt: " +
   1039                           timing.full_fbu_cnt + ", avg: " +
   1040                           (timing.full_fbu_total /
   1041                               timing.full_fbu_cnt));
   1042             }
   1043             if (timing.fbu_rt_start > 0) {
   1044                 fbu_rt_diff = now - timing.fbu_rt_start;
   1045                 timing.fbu_rt_total += fbu_rt_diff;
   1046                 timing.fbu_rt_cnt += 1;
   1047                 Util.Info("full FBU round-trip, cur: " +
   1048                           fbu_rt_diff + ", total: " +
   1049                           timing.fbu_rt_total + ", cnt: " +
   1050                           timing.fbu_rt_cnt + ", avg: " +
   1051                           (timing.fbu_rt_total /
   1052                               timing.fbu_rt_cnt));
   1053                 timing.fbu_rt_start = 0;
   1054             }
   1055         }
   1056         if (! ret) {
   1057             return ret; // false ret means need more data
   1058         }
   1059     }
   1060 
   1061     conf.onFBUComplete(that,
   1062             {'x': FBU.x, 'y': FBU.y,
   1063                 'width': FBU.width, 'height': FBU.height,
   1064                 'encoding': FBU.encoding,
   1065                 'encodingName': encNames[FBU.encoding]});
   1066 
   1067     return true; // We finished this FBU
   1068 };
   1069 
   1070 //
   1071 // FramebufferUpdate encodings
   1072 //
   1073 
   1074 encHandlers.RAW = function display_raw() {
   1075     //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
   1076 
   1077     var cur_y, cur_height;
   1078 
   1079     if (FBU.lines === 0) {
   1080         FBU.lines = FBU.height;
   1081     }
   1082     FBU.bytes = FBU.width * fb_Bpp; // At least a line
   1083     if (ws.rQwait("RAW", FBU.bytes)) { return false; }
   1084     cur_y = FBU.y + (FBU.height - FBU.lines);
   1085     cur_height = Math.min(FBU.lines,
   1086                           Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
   1087     display.blitImage(FBU.x, cur_y, FBU.width, cur_height,
   1088             ws.get_rQ(), ws.get_rQi());
   1089     ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);
   1090     FBU.lines -= cur_height;
   1091 
   1092     if (FBU.lines > 0) {
   1093         FBU.bytes = FBU.width * fb_Bpp; // At least another line
   1094     } else {
   1095         FBU.rects -= 1;
   1096         FBU.bytes = 0;
   1097     }
   1098     //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
   1099     return true;
   1100 };
   1101 
   1102 encHandlers.COPYRECT = function display_copy_rect() {
   1103     //Util.Debug(">> display_copy_rect");
   1104 
   1105     var old_x, old_y;
   1106 
   1107     if (ws.rQwait("COPYRECT", 4)) { return false; }
   1108     old_x = ws.rQshift16();
   1109     old_y = ws.rQshift16();
   1110     display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
   1111     FBU.rects -= 1;
   1112     FBU.bytes = 0;
   1113     return true;
   1114 };
   1115 
   1116 encHandlers.RRE = function display_rre() {
   1117     //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
   1118     var color, x, y, width, height, chunk;
   1119 
   1120     if (FBU.subrects === 0) {
   1121         if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
   1122         FBU.subrects = ws.rQshift32();
   1123         color = ws.rQshiftBytes(fb_Bpp); // Background
   1124         display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
   1125     }
   1126     while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {
   1127         color = ws.rQshiftBytes(fb_Bpp);
   1128         x = ws.rQshift16();
   1129         y = ws.rQshift16();
   1130         width = ws.rQshift16();
   1131         height = ws.rQshift16();
   1132         display.fillRect(FBU.x + x, FBU.y + y, width, height, color);
   1133         FBU.subrects -= 1;
   1134     }
   1135     //Util.Debug("   display_rre: rects: " + FBU.rects +
   1136     //           ", FBU.subrects: " + FBU.subrects);
   1137 
   1138     if (FBU.subrects > 0) {
   1139         chunk = Math.min(rre_chunk_sz, FBU.subrects);
   1140         FBU.bytes = (fb_Bpp + 8) * chunk;
   1141     } else {
   1142         FBU.rects -= 1;
   1143         FBU.bytes = 0;
   1144     }
   1145     //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
   1146     return true;
   1147 };
   1148 
   1149 encHandlers.HEXTILE = function display_hextile() {
   1150     //Util.Debug(">> display_hextile");
   1151     var subencoding, subrects, color, cur_tile,
   1152         tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
   1153         rQ = ws.get_rQ(), rQi = ws.get_rQi();
   1154 
   1155     if (FBU.tiles === 0) {
   1156         FBU.tiles_x = Math.ceil(FBU.width/16);
   1157         FBU.tiles_y = Math.ceil(FBU.height/16);
   1158         FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
   1159         FBU.tiles = FBU.total_tiles;
   1160     }
   1161 
   1162     /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
   1163     while (FBU.tiles > 0) {
   1164         FBU.bytes = 1;
   1165         if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
   1166         subencoding = rQ[rQi];  // Peek
   1167         if (subencoding > 30) { // Raw
   1168             fail("Disconnected: illegal hextile subencoding " + subencoding);
   1169             //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
   1170             return false;
   1171         }
   1172         subrects = 0;
   1173         cur_tile = FBU.total_tiles - FBU.tiles;
   1174         tile_x = cur_tile % FBU.tiles_x;
   1175         tile_y = Math.floor(cur_tile / FBU.tiles_x);
   1176         x = FBU.x + tile_x * 16;
   1177         y = FBU.y + tile_y * 16;
   1178         w = Math.min(16, (FBU.x + FBU.width) - x);
   1179         h = Math.min(16, (FBU.y + FBU.height) - y);
   1180 
   1181         /* Figure out how much we are expecting */
   1182         if (subencoding & 0x01) { // Raw
   1183             //Util.Debug("   Raw subencoding");
   1184             FBU.bytes += w * h * fb_Bpp;
   1185         } else {
   1186             if (subencoding & 0x02) { // Background
   1187                 FBU.bytes += fb_Bpp;
   1188             }
   1189             if (subencoding & 0x04) { // Foreground
   1190                 FBU.bytes += fb_Bpp;
   1191             }
   1192             if (subencoding & 0x08) { // AnySubrects
   1193                 FBU.bytes += 1;   // Since we aren't shifting it off
   1194                 if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
   1195                 subrects = rQ[rQi + FBU.bytes-1]; // Peek
   1196                 if (subencoding & 0x10) { // SubrectsColoured
   1197                     FBU.bytes += subrects * (fb_Bpp + 2);
   1198                 } else {
   1199                     FBU.bytes += subrects * 2;
   1200                 }
   1201             }
   1202         }
   1203 
   1204         /*
   1205         Util.Debug("   tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
   1206               " (" + tile_x + "," + tile_y + ")" +
   1207               " [" + x + "," + y + "]@" + w + "x" + h +
   1208               ", subenc:" + subencoding +
   1209               "(last: " + FBU.lastsubencoding + "), subrects:" +
   1210               subrects +
   1211               ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
   1212               " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
   1213               " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
   1214         */
   1215         if (ws.rQwait("hextile", FBU.bytes)) { return false; }
   1216 
   1217         /* We know the encoding and have a whole tile */
   1218         FBU.subencoding = rQ[rQi];
   1219         rQi += 1;
   1220         if (FBU.subencoding === 0) {
   1221             if (FBU.lastsubencoding & 0x01) {
   1222                 /* Weird: ignore blanks after RAW */
   1223                 Util.Debug("     Ignoring blank after RAW");
   1224             } else {
   1225                 display.fillRect(x, y, w, h, FBU.background);
   1226             }
   1227         } else if (FBU.subencoding & 0x01) { // Raw
   1228             display.blitImage(x, y, w, h, rQ, rQi);
   1229             rQi += FBU.bytes - 1;
   1230         } else {
   1231             if (FBU.subencoding & 0x02) { // Background
   1232                 FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
   1233                 rQi += fb_Bpp;
   1234             }
   1235             if (FBU.subencoding & 0x04) { // Foreground
   1236                 FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
   1237                 rQi += fb_Bpp;
   1238             }
   1239 
   1240             display.startTile(x, y, w, h, FBU.background);
   1241             if (FBU.subencoding & 0x08) { // AnySubrects
   1242                 subrects = rQ[rQi];
   1243                 rQi += 1;
   1244                 for (s = 0; s < subrects; s += 1) {
   1245                     if (FBU.subencoding & 0x10) { // SubrectsColoured
   1246                         color = rQ.slice(rQi, rQi + fb_Bpp);
   1247                         rQi += fb_Bpp;
   1248                     } else {
   1249                         color = FBU.foreground;
   1250                     }
   1251                     xy = rQ[rQi];
   1252                     rQi += 1;
   1253                     sx = (xy >> 4);
   1254                     sy = (xy & 0x0f);
   1255 
   1256                     wh = rQ[rQi];
   1257                     rQi += 1;
   1258                     sw = (wh >> 4)   + 1;
   1259                     sh = (wh & 0x0f) + 1;
   1260 
   1261                     display.subTile(sx, sy, sw, sh, color);
   1262                 }
   1263             }
   1264             display.finishTile();
   1265         }
   1266         ws.set_rQi(rQi);
   1267         FBU.lastsubencoding = FBU.subencoding;
   1268         FBU.bytes = 0;
   1269         FBU.tiles -= 1;
   1270     }
   1271 
   1272     if (FBU.tiles === 0) {
   1273         FBU.rects -= 1;
   1274     }
   1275 
   1276     //Util.Debug("<< display_hextile");
   1277     return true;
   1278 };
   1279 
   1280 
   1281 // Get 'compact length' header and data size
   1282 getTightCLength = function (arr) {
   1283     var header = 1, data = 0;
   1284     data += arr[0] & 0x7f;
   1285     if (arr[0] & 0x80) {
   1286         header += 1;
   1287         data += (arr[1] & 0x7f) << 7;
   1288         if (arr[1] & 0x80) {
   1289             header += 1;
   1290             data += arr[2] << 14;
   1291         }
   1292     }
   1293     return [header, data];
   1294 };
   1295 
   1296 function display_tight(isTightPNG) {
   1297     //Util.Debug(">> display_tight");
   1298 
   1299     if (fb_depth === 1) {
   1300         fail("Tight protocol handler only implements true color mode");
   1301     }
   1302 
   1303     var ctl, cmode, clength, color, img, data;
   1304     var filterId = -1, resetStreams = 0, streamId = -1;
   1305     var rQ = ws.get_rQ(), rQi = ws.get_rQi();
   1306 
   1307     FBU.bytes = 1; // compression-control byte
   1308     if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
   1309 
   1310     var checksum = function(data) {
   1311         var sum=0, i;
   1312         for (i=0; i<data.length;i++) {
   1313             sum += data[i];
   1314             if (sum > 65536) sum -= 65536;
   1315         }
   1316         return sum;
   1317     }
   1318 
   1319     var decompress = function(data) {
   1320         for (var i=0; i<4; i++) {
   1321             if ((resetStreams >> i) & 1) {
   1322                 FBU.zlibs[i].reset();
   1323                 Util.Info("Reset zlib stream " + i);
   1324             }
   1325         }
   1326         var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
   1327         if (uncompressed.status !== 0) {
   1328             Util.Error("Invalid data in zlib stream");
   1329         }
   1330         //Util.Warn("Decompressed " + data.length + " to " +
   1331         //    uncompressed.data.length + " checksums " +
   1332         //    checksum(data) + ":" + checksum(uncompressed.data));
   1333 
   1334         return uncompressed.data;
   1335     }
   1336 
   1337     var handlePalette = function() {
   1338         var numColors = rQ[rQi + 2] + 1;
   1339         var paletteSize = numColors * fb_depth;
   1340         FBU.bytes += paletteSize;
   1341         if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
   1342 
   1343         var bpp = (numColors <= 2) ? 1 : 8;
   1344         var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
   1345         var raw = false;
   1346         if (rowSize * FBU.height < 12) {
   1347             raw = true;
   1348             clength = [0, rowSize * FBU.height];
   1349         } else {
   1350             clength = getTightCLength(ws.rQslice(3 + paletteSize,
   1351                                                  3 + paletteSize + 3));
   1352         }
   1353         FBU.bytes += clength[0] + clength[1];
   1354         if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
   1355 
   1356         // Shift ctl, filter id, num colors, palette entries, and clength off
   1357         ws.rQshiftBytes(3);
   1358         var palette = ws.rQshiftBytes(paletteSize);
   1359         ws.rQshiftBytes(clength[0]);
   1360 
   1361         if (raw) {
   1362             data = ws.rQshiftBytes(clength[1]);
   1363         } else {
   1364             data = decompress(ws.rQshiftBytes(clength[1]));
   1365         }
   1366 
   1367         // Convert indexed (palette based) image data to RGB
   1368         // TODO: reduce number of calculations inside loop
   1369         var dest = [];
   1370         var x, y, b, w, w1, dp, sp;
   1371         if (numColors === 2) {
   1372             w = Math.floor((FBU.width + 7) / 8);
   1373             w1 = Math.floor(FBU.width / 8);
   1374             for (y = 0; y < FBU.height; y++) {
   1375                 for (x = 0; x < w1; x++) {
   1376                     for (b = 7; b >= 0; b--) {
   1377                         dp = (y*FBU.width + x*8 + 7-b) * 3;
   1378                         sp = (data[y*w + x] >> b & 1) * 3;
   1379                         dest[dp  ] = palette[sp  ];
   1380                         dest[dp+1] = palette[sp+1];
   1381                         dest[dp+2] = palette[sp+2];
   1382                     }
   1383                 }
   1384                 for (b = 7; b >= 8 - FBU.width % 8; b--) {
   1385                     dp = (y*FBU.width + x*8 + 7-b) * 3;
   1386                     sp = (data[y*w + x] >> b & 1) * 3;
   1387                     dest[dp  ] = palette[sp  ];
   1388                     dest[dp+1] = palette[sp+1];
   1389                     dest[dp+2] = palette[sp+2];
   1390                 }
   1391             }
   1392         } else {
   1393             for (y = 0; y < FBU.height; y++) {
   1394                 for (x = 0; x < FBU.width; x++) {
   1395                     dp = (y*FBU.width + x) * 3;
   1396                     sp = data[y*FBU.width + x] * 3;
   1397                     dest[dp  ] = palette[sp  ];
   1398                     dest[dp+1] = palette[sp+1];
   1399                     dest[dp+2] = palette[sp+2];
   1400                 }
   1401             }
   1402         }
   1403 
   1404         FBU.imgQ.push({
   1405                 'type': 'rgb',
   1406                 'img':  {'complete': true, 'data': dest},
   1407                 'x': FBU.x,
   1408                 'y': FBU.y,
   1409                 'width': FBU.width,
   1410                 'height': FBU.height});
   1411         return true;
   1412     }
   1413 
   1414     var handleCopy = function() {
   1415         var raw = false;
   1416         var uncompressedSize = FBU.width * FBU.height * fb_depth;
   1417         if (uncompressedSize < 12) {
   1418             raw = true;
   1419             clength = [0, uncompressedSize];
   1420         } else {
   1421             clength = getTightCLength(ws.rQslice(1, 4));
   1422         }
   1423         FBU.bytes = 1 + clength[0] + clength[1];
   1424         if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
   1425 
   1426         // Shift ctl, clength off
   1427         ws.rQshiftBytes(1 + clength[0]);
   1428 
   1429         if (raw) {
   1430             data = ws.rQshiftBytes(clength[1]);
   1431         } else {
   1432             data = decompress(ws.rQshiftBytes(clength[1]));
   1433         }
   1434 
   1435         FBU.imgQ.push({
   1436                 'type': 'rgb',
   1437                 'img':  {'complete': true, 'data': data},
   1438                 'x': FBU.x,
   1439                 'y': FBU.y,
   1440                 'width': FBU.width,
   1441                 'height': FBU.height});
   1442         return true;
   1443     }
   1444 
   1445     ctl = ws.rQpeek8();
   1446 
   1447     // Keep tight reset bits
   1448     resetStreams = ctl & 0xF;
   1449 
   1450     // Figure out filter
   1451     ctl = ctl >> 4;
   1452     streamId = ctl & 0x3;
   1453 
   1454     if (ctl === 0x08)      cmode = "fill";
   1455     else if (ctl === 0x09) cmode = "jpeg";
   1456     else if (ctl === 0x0A) cmode = "png";
   1457     else if (ctl & 0x04)   cmode = "filter";
   1458     else if (ctl < 0x04)   cmode = "copy";
   1459     else throw("Illegal tight compression received, ctl: " + ctl);
   1460 
   1461     if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
   1462         throw("filter/copy received in tightPNG mode");
   1463     }
   1464 
   1465     switch (cmode) {
   1466         // fill uses fb_depth because TPIXELs drop the padding byte
   1467         case "fill":   FBU.bytes += fb_depth; break; // TPIXEL
   1468         case "jpeg":   FBU.bytes += 3;        break; // max clength
   1469         case "png":    FBU.bytes += 3;        break; // max clength
   1470         case "filter": FBU.bytes += 2;        break; // filter id + num colors if palette
   1471         case "copy":                          break;
   1472     }
   1473 
   1474     if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
   1475 
   1476     //Util.Debug("   ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
   1477     //Util.Debug("   cmode: " + cmode);
   1478 
   1479     // Determine FBU.bytes
   1480     switch (cmode) {
   1481     case "fill":
   1482         ws.rQshift8(); // shift off ctl
   1483         color = ws.rQshiftBytes(fb_depth);
   1484         FBU.imgQ.push({
   1485                 'type': 'fill',
   1486                 'img': {'complete': true},
   1487                 'x': FBU.x,
   1488                 'y': FBU.y,
   1489                 'width': FBU.width,
   1490                 'height': FBU.height,
   1491                 'color': [color[2], color[1], color[0]] });
   1492         break;
   1493     case "png":
   1494     case "jpeg":
   1495         clength = getTightCLength(ws.rQslice(1, 4));
   1496         FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
   1497         if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
   1498 
   1499         // We have everything, render it
   1500         //Util.Debug("   jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
   1501         //           clength[0] + ", clength[1]: " + clength[1]);
   1502         ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
   1503         img = new Image();
   1504         //img.onload = scan_tight_imgQ;
   1505         FBU.imgQ.push({
   1506                 'type': 'img',
   1507                 'img': img,
   1508                 'x': FBU.x,
   1509                 'y': FBU.y});
   1510         img.src = "data:image/" + cmode +
   1511             extract_data_uri(ws.rQshiftBytes(clength[1]));
   1512         img = null;
   1513         break;
   1514     case "filter":
   1515         filterId = rQ[rQi + 1];
   1516         if (filterId === 1) {
   1517             if (!handlePalette()) { return false; }
   1518         } else {
   1519             // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
   1520             // Filter 2, Gradient is valid but not used if jpeg is enabled
   1521             throw("Unsupported tight subencoding received, filter: " + filterId);
   1522         }
   1523         break;
   1524     case "copy":
   1525         if (!handleCopy()) { return false; }
   1526         break;
   1527     }
   1528 
   1529     FBU.bytes = 0;
   1530     FBU.rects -= 1;
   1531     //Util.Debug("   ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
   1532     //Util.Debug("<< display_tight_png");
   1533     return true;
   1534 }
   1535 
   1536 extract_data_uri = function(arr) {
   1537     //var i, stra = [];
   1538     //for (i=0; i< arr.length; i += 1) {
   1539     //    stra.push(String.fromCharCode(arr[i]));
   1540     //}
   1541     //return "," + escape(stra.join(''));
   1542     return ";base64," + Base64.encode(arr);
   1543 };
   1544 
   1545 scan_tight_imgQ = function() {
   1546     var data, imgQ, ctx;
   1547     ctx = display.get_context();
   1548     if (rfb_state === 'normal') {
   1549         imgQ = FBU.imgQ;
   1550         while ((imgQ.length > 0) && (imgQ[0].img.complete)) {
   1551             data = imgQ.shift();
   1552             if (data.type === 'fill') {
   1553                 display.fillRect(data.x, data.y, data.width, data.height, data.color);
   1554             } else if (data.type === 'rgb') {
   1555                 display.blitRgbImage(data.x, data.y, data.width, data.height, data.img.data, 0);
   1556             } else {
   1557                 ctx.drawImage(data.img, data.x, data.y);
   1558             }
   1559         }
   1560         setTimeout(scan_tight_imgQ, scan_imgQ_rate);
   1561     }
   1562 };
   1563 
   1564 encHandlers.TIGHT = function () { return display_tight(false); };
   1565 encHandlers.TIGHT_PNG = function () { return display_tight(true); };
   1566 
   1567 encHandlers.last_rect = function last_rect() {
   1568     Util.Debug(">> set_desktopsize");
   1569     FBU.rects = 0;
   1570     Util.Debug("<< set_desktopsize");
   1571     return true;
   1572 };
   1573 
   1574 encHandlers.DesktopSize = function set_desktopsize() {
   1575     Util.Debug(">> set_desktopsize");
   1576     fb_width = FBU.width;
   1577     fb_height = FBU.height;
   1578     display.resize(fb_width, fb_height);
   1579     timing.fbu_rt_start = (new Date()).getTime();
   1580     // Send a new non-incremental request
   1581     ws.send(fbUpdateRequests());
   1582 
   1583     FBU.bytes = 0;
   1584     FBU.rects -= 1;
   1585 
   1586     Util.Debug("<< set_desktopsize");
   1587     return true;
   1588 };
   1589 
   1590 encHandlers.Cursor = function set_cursor() {
   1591     var x, y, w, h, pixelslength, masklength;
   1592     //Util.Debug(">> set_cursor");
   1593     x = FBU.x;  // hotspot-x
   1594     y = FBU.y;  // hotspot-y
   1595     w = FBU.width;
   1596     h = FBU.height;
   1597 
   1598     pixelslength = w * h * fb_Bpp;
   1599     masklength = Math.floor((w + 7) / 8) * h;
   1600 
   1601     FBU.bytes = pixelslength + masklength;
   1602     if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
   1603 
   1604     //Util.Debug("   set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
   1605 
   1606     display.changeCursor(ws.rQshiftBytes(pixelslength),
   1607                             ws.rQshiftBytes(masklength),
   1608                             x, y, w, h);
   1609 
   1610     FBU.bytes = 0;
   1611     FBU.rects -= 1;
   1612 
   1613     //Util.Debug("<< set_cursor");
   1614     return true;
   1615 };
   1616 
   1617 encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
   1618     Util.Error("Server sent jpeg_quality pseudo-encoding");
   1619 };
   1620 
   1621 encHandlers.compress_lo = function set_compress_level() {
   1622     Util.Error("Server sent compress level pseudo-encoding");
   1623 };
   1624 
   1625 /*
   1626  * Client message routines
   1627  */
   1628 
   1629 pixelFormat = function() {
   1630     //Util.Debug(">> pixelFormat");
   1631     var arr;
   1632     arr = [0];     // msg-type
   1633     arr.push8(0);  // padding
   1634     arr.push8(0);  // padding
   1635     arr.push8(0);  // padding
   1636 
   1637     arr.push8(fb_Bpp * 8); // bits-per-pixel
   1638     arr.push8(fb_depth * 8); // depth
   1639     arr.push8(0);  // little-endian
   1640     arr.push8(conf.true_color ? 1 : 0);  // true-color
   1641 
   1642     arr.push16(255);  // red-max
   1643     arr.push16(255);  // green-max
   1644     arr.push16(255);  // blue-max
   1645     arr.push8(16);    // red-shift
   1646     arr.push8(8);     // green-shift
   1647     arr.push8(0);     // blue-shift
   1648 
   1649     arr.push8(0);     // padding
   1650     arr.push8(0);     // padding
   1651     arr.push8(0);     // padding
   1652     //Util.Debug("<< pixelFormat");
   1653     return arr;
   1654 };
   1655 
   1656 clientEncodings = function() {
   1657     //Util.Debug(">> clientEncodings");
   1658     var arr, i, encList = [];
   1659 
   1660     for (i=0; i<encodings.length; i += 1) {
   1661         if ((encodings[i][0] === "Cursor") &&
   1662             (! conf.local_cursor)) {
   1663             Util.Debug("Skipping Cursor pseudo-encoding");
   1664         } else {
   1665             //Util.Debug("Adding encoding: " + encodings[i][0]);
   1666             encList.push(encodings[i][1]);
   1667         }
   1668     }
   1669 
   1670     arr = [2];     // msg-type
   1671     arr.push8(0);  // padding
   1672 
   1673     arr.push16(encList.length); // encoding count
   1674     for (i=0; i < encList.length; i += 1) {
   1675         arr.push32(encList[i]);
   1676     }
   1677     //Util.Debug("<< clientEncodings: " + arr);
   1678     return arr;
   1679 };
   1680 
   1681 fbUpdateRequest = function(incremental, x, y, xw, yw) {
   1682     //Util.Debug(">> fbUpdateRequest");
   1683     if (typeof(x) === "undefined") { x = 0; }
   1684     if (typeof(y) === "undefined") { y = 0; }
   1685     if (typeof(xw) === "undefined") { xw = fb_width; }
   1686     if (typeof(yw) === "undefined") { yw = fb_height; }
   1687     var arr;
   1688     arr = [3];  // msg-type
   1689     arr.push8(incremental);
   1690     arr.push16(x);
   1691     arr.push16(y);
   1692     arr.push16(xw);
   1693     arr.push16(yw);
   1694     //Util.Debug("<< fbUpdateRequest");
   1695     return arr;
   1696 };
   1697 
   1698 // Based on clean/dirty areas, generate requests to send
   1699 fbUpdateRequests = function() {
   1700     var cleanDirty = display.getCleanDirtyReset(),
   1701         arr = [], i, cb, db;
   1702 
   1703     cb = cleanDirty.cleanBox;
   1704     if (cb.w > 0 && cb.h > 0) {
   1705         // Request incremental for clean box
   1706         arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
   1707     }
   1708     for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
   1709         db = cleanDirty.dirtyBoxes[i];
   1710         // Force all (non-incremental for dirty box
   1711         arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
   1712     }
   1713     return arr;
   1714 };
   1715 
   1716 
   1717 
   1718 keyEvent = function(keysym, down) {
   1719     //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
   1720     var arr;
   1721     arr = [4];  // msg-type
   1722     arr.push8(down);
   1723     arr.push16(0);
   1724     arr.push32(keysym);
   1725     //Util.Debug("<< keyEvent");
   1726     return arr;
   1727 };
   1728 
   1729 pointerEvent = function(x, y) {
   1730     //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
   1731     //           " , mask: " + mouse_buttonMask);
   1732     var arr;
   1733     arr = [5];  // msg-type
   1734     arr.push8(mouse_buttonMask);
   1735     arr.push16(x);
   1736     arr.push16(y);
   1737     //Util.Debug("<< pointerEvent");
   1738     return arr;
   1739 };
   1740 
   1741 clientCutText = function(text) {
   1742     //Util.Debug(">> clientCutText");
   1743     var arr, i, n;
   1744     arr = [6];     // msg-type
   1745     arr.push8(0);  // padding
   1746     arr.push8(0);  // padding
   1747     arr.push8(0);  // padding
   1748     arr.push32(text.length);
   1749     n = text.length;
   1750     for (i=0; i < n; i+=1) {
   1751         arr.push(text.charCodeAt(i));
   1752     }
   1753     //Util.Debug("<< clientCutText:" + arr);
   1754     return arr;
   1755 };
   1756 
   1757 
   1758 
   1759 //
   1760 // Public API interface functions
   1761 //
   1762 
   1763 that.connect = function(host, port, password, path) {
   1764     //Util.Debug(">> connect");
   1765 
   1766     rfb_host       = host;
   1767     rfb_port       = port;
   1768     rfb_password   = (password !== undefined)   ? password : "";
   1769     rfb_path       = (path !== undefined) ? path : "";
   1770 
   1771     if ((!rfb_host) || (!rfb_port)) {
   1772         return fail("Must set host and port");
   1773     }
   1774 
   1775     updateState('connect');
   1776     //Util.Debug("<< connect");
   1777 
   1778 };
   1779 
   1780 that.disconnect = function() {
   1781     //Util.Debug(">> disconnect");
   1782     updateState('disconnect', 'Disconnecting');
   1783     //Util.Debug("<< disconnect");
   1784 };
   1785 
   1786 that.sendPassword = function(passwd) {
   1787     rfb_password = passwd;
   1788     rfb_state = "Authentication";
   1789     setTimeout(init_msg, 1);
   1790 };
   1791 
   1792 that.sendCtrlAltDel = function() {
   1793     if (rfb_state !== "normal" || conf.view_only) { return false; }
   1794     Util.Info("Sending Ctrl-Alt-Del");
   1795     var arr = [];
   1796     arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
   1797     arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
   1798     arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
   1799     arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
   1800     arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
   1801     arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
   1802     arr = arr.concat(fbUpdateRequests());
   1803     ws.send(arr);
   1804 };
   1805 
   1806 // Send a key press. If 'down' is not specified then send a down key
   1807 // followed by an up key.
   1808 that.sendKey = function(code, down) {
   1809     if (rfb_state !== "normal" || conf.view_only) { return false; }
   1810     var arr = [];
   1811     if (typeof down !== 'undefined') {
   1812         Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
   1813         arr = arr.concat(keyEvent(code, down ? 1 : 0));
   1814     } else {
   1815         Util.Info("Sending key code (down + up): " + code);
   1816         arr = arr.concat(keyEvent(code, 1));
   1817         arr = arr.concat(keyEvent(code, 0));
   1818     }
   1819     arr = arr.concat(fbUpdateRequests());
   1820     ws.send(arr);
   1821 };
   1822 
   1823 that.clipboardPasteFrom = function(text) {
   1824     if (rfb_state !== "normal") { return; }
   1825     //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
   1826     ws.send(clientCutText(text));
   1827     //Util.Debug("<< clipboardPasteFrom");
   1828 };
   1829 
   1830 // Override internal functions for testing
   1831 that.testMode = function(override_send) {
   1832     test_mode = true;
   1833     that.recv_message = ws.testMode(override_send);
   1834 
   1835     checkEvents = function () { /* Stub Out */ };
   1836     that.connect = function(host, port, password) {
   1837             rfb_host = host;
   1838             rfb_port = port;
   1839             rfb_password = password;
   1840             updateState('ProtocolVersion', "Starting VNC handshake");
   1841         };
   1842 };
   1843 
   1844 
   1845 return constructor();  // Return the public API interface
   1846 
   1847 }  // End of RFB()
   1848