diff --git a/README.md b/README.md index 8db4bfb70..b21156b20 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ use a WebSockets to TCP socket proxy. There is a python proxy included * UI and Icons : Chris Gordon * Original Logo : Michael Sersen * tight encoding : Michael Tinglof (Mercuri.ca) - * pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/) + * pixel format conversion and ATEN iKVM support : [Alexander Clouter](http://www.digriz.org.uk/) * Included libraries: * as3crypto : Henri Torgemane (code.google.com/p/as3crypto) diff --git a/include/keysym.js b/include/keysym.js index 58b107c05..2cc268896 100644 --- a/include/keysym.js +++ b/include/keysym.js @@ -376,3 +376,80 @@ XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIA XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */ XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ + +var XK2HID = {}; + +// F{1..12} +for (var i = XK_F1; i <= XK_F12; i++) { + XK2HID[i] = 0x3a + (i - XK_F1); +} +// A-Za-z +for (var i = XK_A; i <= XK_Z; i++) { + XK2HID[i] = 0x04 + (i - XK_A); + XK2HID[i + (XK_a - XK_A)] = 0x04 + (i - XK_A); +} +// 1-9 +for (var i = XK_1; i <= XK_9; i++) { + XK2HID[i] = 0x1e + (i - XK_1); +} + +XK2HID[XK_0] = 0x27; +XK2HID[XK_Return] = 0x28; +XK2HID[XK_Escape] = 0x29; +XK2HID[XK_BackSpace] = 0x2a; +XK2HID[XK_Tab] = 0x2b; +XK2HID[XK_space] = 0x2c; +XK2HID[XK_minus] = 0x2d; +XK2HID[XK_equal] = 0x2e; +XK2HID[XK_bracketleft] = 0x2f; +XK2HID[XK_bracketright] = 0x30; +XK2HID[XK_backslash] = 0x31; +XK2HID[XK_semicolon] = 0x33; +XK2HID[XK_apostrophe] = 0x34; +XK2HID[XK_grave] = 0x35; +XK2HID[XK_comma] = 0x36; +XK2HID[XK_period] = 0x37; +XK2HID[XK_slash] = 0x38; + +XK2HID[XK_Print] = 0x46; +XK2HID[XK_Scroll_Lock] = 0x47; +XK2HID[XK_Pause] = 0x48; +XK2HID[XK_Insert] = 0x49; +XK2HID[XK_Home] = 0x4a; +XK2HID[XK_Page_Up] = 0x4b; +XK2HID[XK_Delete] = 0x4c; +XK2HID[XK_End] = 0x4d; +XK2HID[XK_Page_Down] = 0x4e; +XK2HID[XK_Right] = 0x4f; +XK2HID[XK_Left] = 0x50; +XK2HID[XK_Down] = 0x51; +XK2HID[XK_Up] = 0x52; + +XK2HID[XK_Control_L] = 0xe0; +XK2HID[XK_Control_R] = XK2HID[XK_Control_L]; +XK2HID[XK_Shift_L] = 0xe1; +XK2HID[XK_Shift_R] = XK2HID[XK_Shift_L]; +XK2HID[XK_Alt_L] = 0xe2; +XK2HID[XK_Alt_R] = XK2HID[XK_Alt_L]; +XK2HID[XK_Super_L] = 0xe3; +XK2HID[XK_Super_R] = XK2HID[XK_Super_L]; + +XK2HID[XK_Caps_Lock] = 0x39; + +// locale hardcoded hack +XK2HID[XK_less] = XK2HID[XK_comma]; +XK2HID[XK_greater] = XK2HID[XK_period]; +XK2HID[XK_exclam] = XK2HID[XK_1]; +XK2HID[XK_at] = XK2HID[XK_2]; +XK2HID[XK_numbersign] = XK2HID[XK_3]; +XK2HID[XK_dollar] = XK2HID[XK_4]; +XK2HID[XK_percent] = XK2HID[XK_5]; +XK2HID[XK_asciicircum] = XK2HID[XK_6]; +XK2HID[XK_ampersand] = XK2HID[XK_7]; +XK2HID[XK_asterisk] = XK2HID[XK_8]; +XK2HID[XK_parenleft] = XK2HID[XK_9]; +XK2HID[XK_parenright] = XK2HID[XK_0]; +XK2HID[XK_underscore] = XK2HID[XK_minus]; +XK2HID[XK_bar] = XK2HID[XK_backslash]; +XK2HID[XK_quotedbl] = XK2HID[XK_apostrophe]; +XK2HID[XK_asciitilde] = XK2HID[XK_grave]; diff --git a/include/rfb.js b/include/rfb.js index f26eb4481..87b94c145 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -33,6 +33,7 @@ var RFB; this._rfb_auth_scheme = ''; this._rfb_tightvnc = false; + this._rfb_atenikvm = false; this._rfb_xvp_ver = 0; // In preference order @@ -43,6 +44,7 @@ var RFB; ['HEXTILE', 0x05 ], ['RRE', 0x02 ], ['RAW', 0x00 ], + ['ATEN', 0x59 ], ['DesktopSize', -223 ], ['Cursor', -239 ], @@ -75,6 +77,8 @@ var RFB; subrects: 0, // RRE lines: 0, // RAW tiles: 0, // HEXTILE + aten_len: -1, // ATEN + aten_type: -1, // ATEN bytes: 0, x: 0, y: 0, @@ -129,6 +133,7 @@ var RFB; 'local_cursor': false, // Request locally rendered cursor 'shared': true, // Request shared mode 'view_only': false, // Disable client mouse/keyboard + 'aten_password_sep': ':', // Separator for ATEN iKVM password fields 'xvp_password_sep': '@', // Separator for XVP password fields 'disconnectTimeout': 3, // Time (s) to wait for disconnection 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection @@ -363,9 +368,12 @@ var RFB; this._FBU.lines = 0; // RAW this._FBU.tiles = 0; // HEXTILE this._FBU.zlibs = []; // TIGHT zlib encoders + this._FBU.aten_len = -1; // ATEN + this._FBU.aten_type = -1; // ATEN this._mouse_buttonMask = 0; this._mouse_arr = []; this._rfb_tightvnc = false; + this._rfb_atenikvm = false; // Clear the per connection encoding stats var i; @@ -713,6 +721,36 @@ var RFB; }, // authentication + _negotiate_aten_auth: function () { + var aten_sep = this._aten_password_sep; + var aten_auth = this._rfb_password.split(aten_sep); + if (aten_auth.length < 2) { + this._updateState('password', 'ATEN iKVM credentials required (user' + aten_sep + + 'password) -- got only ' + this._rfb_password); + this._onPasswordRequired(this); + return false; + } + + this._rfb_atenikvm = true; + + if (this._rfb_tightvnc) { + this._rfb_tightvnc = false; + } else { + this._sock.rQskipBytes(4); + } + + this._sock.rQskipBytes(16); + + var username = aten_auth[0]; + username += new Array(24 - username.length+1).join("\x00"); + var password = aten_auth.slice(1).join(aten_sep); + password += new Array(24 - password.length+1).join("\x00"); + + this._sock.send_string(username + password); + this._updateState("SecurityResult"); + return true; + }, + _negotiate_xvp_auth: function () { var xvp_sep = this._xvp_password_sep; var xvp_auth = this._rfb_password.split(xvp_sep); @@ -779,9 +817,12 @@ var RFB; }, _negotiate_tight_auth: function () { + var numTunnels; // NB(directxman12): this is only in scope within the following block, + // or if equal to zero (necessary for ATEN iKVM support) if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation if (this._sock.rQwait("num tunnels", 4)) { return false; } - var numTunnels = this._sock.rQshift32(); + numTunnels = this._sock.rQshift32(); + if (this._rfb_version === 3.8 && (numTunnels & 0xffff0ff0) >>> 0 === 0xaff90fb0) { return this._negotiate_aten_auth(); } if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; } this._rfb_tightvnc = true; @@ -797,6 +838,12 @@ var RFB; var subAuthCount = this._sock.rQshift32(); if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; } + // Newer X10 Supermicro motherboards get here + if (this._rfb_version === 3.8 && numTunnels === 0 && subAuthCount === 0) { + Util.Warn("Newer ATEN iKVM detected, you may get an 'unsupported encoding 87'"); + return this._negotiate_aten_auth(); + } + var clientSupportedTypes = { 'STDVNOAUTH__': 1, 'STDVVNCAUTH_': 2 @@ -883,6 +930,7 @@ var RFB; _negotiate_server_init: function () { if (this._sock.rQwait("server initialization", 24)) { return false; } + if (this._rfb_atenikvm && this._sock.rQwait("ATEN server initialization", 36)) { return false; } /* Screen size */ this._fb_width = this._sock.rQshift16(); @@ -911,6 +959,14 @@ var RFB; if (this._sock.rQwait('server init name', name_length, 24)) { return false; } this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length)); + if (this._rfb_atenikvm) { + this._sock.rQskipBytes(8); // unknown + this._sock.rQskip8(); // IKVMVideoEnable + this._sock.rQskip8(); // IKVMKMEnable + this._sock.rQskip8(); // IKVMKickEnable + this._sock.rQskip8(); // VUSBEnable + } + if (this._rfb_tightvnc) { if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; } // In TightVNC mode, ServerInit message is extended @@ -957,6 +1013,57 @@ var RFB; this._convertColor = true; } + // ATEN 'wisdom' from chicken-aten-ikvm:lens/lens.rb + // tested against the following Supermicro motherboards + // (use 'dmidecode -s baseboard-product-name' for model): + // - X7SPA-F + // - X8DTL + // - X8SIE-F + // - X9SCL/X9SCM + // - X9SCM-F + // - X9DRD-iF + // - X9SRE/X9SRE-3F/X9SRi/X9SRi-3F + // - X9DRL-3F/X9DRL-6F + // - X10SLD + // + // Not supported (uses encoding 87): + // - X10SL7-F + // - X10SLD-F + // - X10SLM-F + // - X10SLE + // + // Simply does not work: + // Hermon (WPMC450) [hangs at login]: + // - X7SB3-F + // - X8DTU-F + // - X8STi-3F + // Peppercon (Raritan/Kira100) [connection refused]: + // - X7SBi + // + // Thanks to Brian Rak and Erik Smit for testing + if (this._rfb_atenikvm) { + // we do not know the resolution till the first fbupdate so go large + // although, not necessary, saves a pointless full screen refresh + this._fb_width = RFB.ATEN_INIT_WIDTH; + this._fb_height = RFB.ATEN_INIT_HEIGHT; + + // lies about what it supports + Util.Warn("ATEN iKVM lies and only does 15 bit depth with RGB555"); + this._convertColor = true; + this._pixelFormat.bpp = 16; + this._pixelFormat.depth = 15; + this._pixelFormat.red_max = (1 << 5) - 1; + this._pixelFormat.green_max = (1 << 5) - 1; + this._pixelFormat.blue_max = (1 << 5) - 1; + this._pixelFormat.red_shift = 10; + this._pixelFormat.green_shift = 5; + this._pixelFormat.blue_shift = 0; + + // changes to protocol format + RFB.messages.keyEvent = RFB.messages.atenKeyEvent; + RFB.messages.pointerEvent = RFB.messages.atenPointerEvent; + } + if (this._convertColor) this._display.set_true_color(this._pixelFormat.true_color); this._display.resize(this._fb_width, this._fb_height); @@ -1118,6 +1225,42 @@ var RFB; msg_type = this._sock.rQshift8(); } + if (this._rfb_atenikvm) { + switch (msg_type) { + case 4: // Front Ground Event + Util.Debug("ATEN iKVM Front Ground Event"); + this._sock.rQskipBytes(20); + return true; + + case 22: // Keep Alive Event + Util.Debug("ATEN iKVM Keep Alive Event"); + this._sock.rQskipBytes(1); + return true; + + case 51: // Video Get Info + Util.Debug("ATEN iKVM Video Get Info"); + this._sock.rQskipBytes(4); + return true; + + case 55: // Mouse Get Info + Util.Debug("ATEN iKVM Mouse Get Info"); + this._sock.rQskipBytes(2); + return true; + + case 57: // Session Message + Util.Debug("ATEN iKVM Session Message"); + this._sock.rQskipBytes(4); // u32 + this._sock.rQskipBytes(4); // u32 + this._sock.rQskipBytes(256); + return true; + + case 60: // Get Viewer Lang + Util.Debug("ATEN iKVM Get Viewer Lang"); + this._sock.rQskipBytes(8); + return true; + } + } + switch (msg_type) { case 0: // FramebufferUpdate var ret = this._framebufferUpdate(); @@ -1191,6 +1334,11 @@ var RFB; this._FBU.encoding); return false; } + + // ATEN uses 0x00 even when it is meant to be 0x59 + if (this._rfb_atenikvm && this._FBU.encoding === 0x00) { + this._FBU.encoding = 0x59; + } } this._timing.last_fbu = (new Date()).getTime(); @@ -1342,6 +1490,7 @@ var RFB; ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor ['shared', 'rw', 'bool'], // Request shared mode ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard + ['aten_password_sep', 'rw', 'str'], // Separator for ATEN iKVM password fields ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection @@ -1378,6 +1527,11 @@ var RFB; RFB.prototype.get_keyboard = function () { return this._keyboard; }; RFB.prototype.get_mouse = function () { return this._mouse; }; + // ATEN iKVM doesn't send the resolution until the first refresh, so start big + // to avoid a repaint + RFB.ATEN_INIT_WIDTH = 10000; + RFB.ATEN_INIT_HEIGHT = 10000; + // Class Methods RFB.messages = { keyEvent: function (sock, keysym, down) { @@ -1398,6 +1552,70 @@ var RFB; sock._sQlen += 8; }, + atenKeyEvent: function (sock, keysym, down) { + var ks = XK2HID[keysym]; + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 4; + buff[offset + 1] = 0; + buff[offset + 2] = down; + + buff[offset + 3] = 0; + buff[offset + 4] = 0; + + buff[offset + 5] = (ks >> 24); + buff[offset + 6] = (ks >> 16); + buff[offset + 7] = (ks >> 8); + buff[offset + 8] = ks; + + buff[offset + 9] = 0; + buff[offset + 10] = 0; + buff[offset + 11] = 0; + buff[offset + 12] = 0; + + buff[offset + 13] = 0; + buff[offset + 14] = 0; + buff[offset + 15] = 0; + buff[offset + 16] = 0; + + buff[offset + 17] = 0; + + sock._sQlen += 18; + }, + + atenPointerEvent: function (sock, x, y, mask) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 5; + buff[offset + 1] = 0; + buff[offset + 2] = mask; + + buff[offset + 3] = x >> 8; + buff[offset + 4] = x; + + buff[offset + 5] = y >> 8; + buff[offset + 6] = y; + + buff[offset + 7] = 0; + buff[offset + 8] = 0; + buff[offset + 9] = 0; + buff[offset + 10] = 0; + + buff[offset + 11] = 0; + buff[offset + 12] = 0; + buff[offset + 13] = 0; + buff[offset + 14] = 0; + + buff[offset + 15] = 0; + buff[offset + 16] = 0; + + buff[offset + 17] = 0; + + sock._sQlen += 18; + }, + pointerEvent: function (sock, x, y, mask) { var buff = sock._sQ; var offset = sock._sQlen; @@ -2214,6 +2432,86 @@ var RFB; compress_lo: function () { Util.Error("Server sent compress level pseudo-encoding"); + }, + + ATEN: function () { + if (this._FBU.aten_len === -1) { + this._FBU.bytes = 8; + if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; } + this._FBU.bytes = 0; + this._sock.rQskipBytes(4); + this._FBU.aten_len = this._sock.rQshift32(); + + if (this._FBU.width === 64896 && this._FBU.height === 65056) { + Util.Info("ATEN iKVM screen is probably off"); + if (this._FBU.aten_len !== 10 && this._FBU.aten_len !== 0) { + Util.Debug(">> ATEN iKVM screen off (aten_len="+this._FBU.aten_len+")"); + this._fail('expected aten_len to be 10 when screen is off'); + } + this._FBU.aten_len = 0; + return true; + } + if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) { + Util.Debug(">> ATEN resize desktop"); + this._fb_width = this._FBU.width; + this._fb_height = this._FBU.height; + this._onFBResize(this, this._fb_width, this._fb_height); + this._display.resize(this._fb_width, this._fb_height); + Util.Debug("<< ATEN resize desktop"); + } + } + + if (this._FBU.aten_type === -1) { + this._FBU.bytes = 10; + if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; } + this._FBU.bytes = 0; + this._FBU.aten_type = this._sock.rQshift8(); + this._sock.rQskip8(); + + this._sock.rQskipBytes(4); // number of subrects + if (this._FBU.aten_len !== this._sock.rQshift32()) { + return this._fail('ATEN RAW len mis-match'); + } + this._FBU.aten_len -= 10; + } + + while (this._FBU.aten_len > 0) { + switch (this._FBU.aten_type) { + case 0: // Subrects + this._FBU.bytes = 6 + (16 * 16 * this._pixelFormat.Bpp); // at least a subrect + if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; } + var a = this._sock.rQshift16(); + var b = this._sock.rQshift16(); + var y = this._sock.rQshift8(); + var x = this._sock.rQshift8(); + this._convert_color(this._destBuff, this._sock.rQshiftBytes(this._FBU.bytes - 6)); + this._display.blitImage(x * 16, y * 16, 16, 16, this._destBuff, 0, true, false); + this._FBU.aten_len -= this._FBU.bytes; + this._FBU.bytes = 0; + break; + case 1: // RAW + var olines = (this._FBU.lines === 0) ? this._FBU.height : this._FBU.lines; + this._encHandlers.RAW(); + this._FBU.aten_len -= (olines - this._FBU.lines) * this._FBU.width * this._pixelFormat.Bpp; + if (this._FBU.bytes > 0) return false; + break; + default: + return this._fail('unknown ATEN type: '+this._FBU.aten_type); + } + } + + if (this._FBU.aten_len < 0) { + this._fail('aten_len dropped below zero'); + } + + if (this._FBU.aten_type === 0) { + this._FBU.rects--; + } + + this._FBU.aten_len = -1; + this._FBU.aten_type = -1; + + return true; } }; })(); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 0fa93f062..c481806e4 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -873,6 +873,51 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_state).to.equal('failed'); }); }); + + describe('ATEN iKVM Authentication Handler', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'Security'; + client._rfb_version = 3.8; + send_security(16, client); + client._sock._websocket._get_sent_data(); // skip the security reply + client._rfb_password = 'test1:test2'; + }); + + var auth = [ + 116, 101, 115, 116, 49, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 116, 101, 115, 116, 50, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0]; + + it('via old style method', function () { + client._sock._websocket._receive_data(new Uint8Array([ + 0xaf, 0xf9, 0x0f, 0xb0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0])); + expect(client._rfb_tightvnc).to.be.false; + expect(client._rfb_atenikvm).to.be.true; + expect(client._sock).to.have.sent(auth); + }); + + it('via new style method', function () { + client._sock._websocket._receive_data(new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0])); + expect(client._rfb_tightvnc).to.be.false; + expect(client._rfb_atenikvm).to.be.true; + expect(client._sock).to.have.sent(auth); + }); + }); }); describe('SecurityResult', function () { @@ -1020,6 +1065,25 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_state).to.equal('normal'); }); + it('should handle an ATEN iKVM server initialization', function () { + RFB.ATEN_INIT_WIDTH = 1; + RFB.ATEN_INIT_HEIGHT = 1; + client._rfb_atenikvm = true; + send_server_init({ true_color: 1, bpp: 32 }, client); + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); + expect(client._pixelFormat.bpp).to.equal(16); + expect(client._pixelFormat.depth).to.equal(15); + expect(client._pixelFormat.red_max).to.equal(31); + expect(client._pixelFormat.green_max).to.equal(31); + expect(client._pixelFormat.blue_max).to.equal(31); + expect(client._pixelFormat.red_shift).to.equal(10); + expect(client._pixelFormat.green_shift).to.equal(5); + expect(client._pixelFormat.blue_shift).to.equal(0); + expect(client._pixelFormat.Bpp).to.equal(2); + expect(client._pixelFormat.Bdepth).to.equal(2); + expect(client._rfb_state).to.equal('normal'); + }); + it('should call the resize callback and resize the display', function () { client.set_onFBResize(sinon.spy()); sinon.spy(client._display, 'resize'); @@ -1562,6 +1626,97 @@ describe('Remote Frame Buffer Protocol Client', function() { // TODO(directxman12): test this }); + describe('the ATEN encoding handler', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client._fb_name = 'some device'; + client._rfb_atenikvm = true; + // start large, then go small + client._fb_width = 10000; + client._fb_height = 10000; + client._display._fb_width = 10000; + client._display._fb_height = 10000; + client._display._viewportLoc.w = 10000; + client._display._viewportLoc.h = 10000; + client._convertColor = true; + client._pixelFormat.Bpp = 2; + client._pixelFormat.big_endian = false; + client._pixelFormat.red_shift = 10; + client._pixelFormat.red_max = 31; + client._pixelFormat.green_shift = 5; + client._pixelFormat.green_max = 31; + client._pixelFormat.blue_shift = 0; + client._pixelFormat.blue_max = 31; + client._destBuff = new Uint8Array(client._fb_width * client._fb_height * 4); + }); + + var aten_target_data_arr = [0xa8, 0xe8, 0xf8, 0xff]; + var aten_target_data; + + before(function () { + for (var i = 0; i < 10; i++) { + aten_target_data_arr = aten_target_data_arr.concat(aten_target_data_arr); + } + aten_target_data = new Uint8Array(aten_target_data_arr); + }); + + it('should handle subtype subrect encoding', function () { + var info = [{ x: 0, y: 0, width: 32, height: 32, encoding: 0x59 }]; + var rect = []; + + rect.push32(0); // padding + rect.push32(2082); // 10 + 32x32x2Bpp + 6*(num of subrects) + + rect.push8(0); // type + rect.push8(0); // padding + + rect.push32(4); // num of subrects (32/16)*(32/16) + rect.push32(2082); // length (again) + + for (var y = 0; y < 2; y++) { + for (var x = 0; x < 2; x++) { + rect.push16(0); // a + rect.push16(0); // b + rect.push8(y); + rect.push8(x); + + for (var i = 0; i < 16*16; i++) { + rect.push16(0xbf57); + } + } + } + + send_fbu_msg(info, [rect], client); + expect(client._display).to.have.displayed(aten_target_data); + }); + + it('should handle subtype RAW encoding', function () { + // do not use encoding=0x59 here, as rfb.js should override it + var info = [{ x: 0, y: 0, width: 32, height: 32, encoding: 0x00 }]; + var rect = []; + + rect.push32(0); // padding + rect.push32(2058); // 10 + 32x32x2Bpp + + rect.push8(1); // type + rect.push8(0); // padding + + rect.push32(0); // padding + rect.push32(2058); // length (again) + + for (var i = 0; i < 32*32; i++) { + rect.push16(0xbf57); + } + + send_fbu_msg(info, [rect], client); + expect(client._display).to.have.displayed(aten_target_data); + }); + }); + it('should handle the DesktopSize pseduo-encoding', function () { client.set_onFBResize(sinon.spy()); sinon.spy(client._display, 'resize');