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