diff --git a/.gitmodules b/.gitmodules index 45574aeb..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "include/web-socket-js-project"] - path = include/web-socket-js-project - url = https://github.com/gimite/web-socket-js.git diff --git a/LICENSE.txt b/LICENSE.txt index e896efca..82e8a6a1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -51,14 +51,14 @@ licenses (all MPL 2.0 compatible): include/jsunzip.js : zlib/libpng license - include/web-socket-js/ : New BSD license (3-clause). Source code at - http://github.com/gimite/web-socket-js - include/chrome-app/tcp-stream.js : Apache 2.0 license utils/websockify utils/websocket.py : LGPL 3 + + utils/inflator.partial.js + include/inflator.js : MIT (for pako) The following license texts are included: @@ -70,6 +70,7 @@ The following license texts are included: docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) docs/LICENSE.zlib docs/LICENSE.Apache-2.0 + docs/LICENSE.pako Or alternatively the license texts may be found here: diff --git a/README.md b/README.md index b5679cdd..59a72b10 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,7 @@ See more screenshots h * HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS Safari, Opera 11+, Internet Explorer 9+, etc. -* HTML5 WebSockets: For browsers that do not have builtin - WebSockets support, the project includes - web-socket-js, - a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in - WebSocket support. +* HTML5 WebSockets and Typed Arrays * Fast Javascript Engine: this is not strictly a requirement, but without a fast Javascript engine, noVNC might be painfully slow. @@ -130,9 +126,7 @@ use a WebSockets to TCP socket proxy. There is a python proxy included * tight encoding : Michael Tinglof (Mercuri.ca) * Included libraries: - * web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js) * as3crypto : Henri Torgemane (code.google.com/p/as3crypto) * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net) - * jsunzip : Erik Moller (github.com/operasoftware/jsunzip), - * tinflate : Joergen Ibsen (ibsensoftware.com) * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs) + * Pako : Vitaly Puzrin (https://github.com/nodeca/pako) diff --git a/docs/LICENSE.pako b/docs/LICENSE.pako new file mode 100644 index 00000000..e6c9e5a5 --- /dev/null +++ b/docs/LICENSE.pako @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (C) 2014 by Vitaly Puzrin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/notes b/docs/notes index 9bcc6af3..6ff5ec19 100644 --- a/docs/notes +++ b/docs/notes @@ -1,17 +1,5 @@ -Some implementation notes: +Rebuilding inflator.js -There is an included flash object (web-socket-js) that is used to -emulate websocket support on browsers without websocket support -(currently only Chrome has WebSocket support). - -Javascript doesn't have a bytearray type, so what you get out of -a WebSocket object is just Javascript strings. Javascript has UTF-16 -unicode strings and anything sent through the WebSocket gets converted -to UTF-8 and vice-versa. So, one additional (and necessary) function -of websockify is base64 encoding/decoding what is sent to/from the -browser. - -Building web-socket-js emulator: - -cd include/web-socket-js/flash-src -mxmlc -static-link-runtime-shared-libraries WebSocketMain.as +- Download pako from npm +- Install browserify using npm +- browserify utils/inflator.partial.js -o include/inflator.js diff --git a/include/display.js b/include/display.js index f20a5571..80530022 100644 --- a/include/display.js +++ b/include/display.js @@ -15,6 +15,14 @@ var Display; (function () { "use strict"; + var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false; + try { + new ImageData(new Uint8ClampedArray(1), 1, 1); + SUPPORTS_IMAGEDATA_CONSTRUCTOR = true; + } catch (ex) { + // ignore failure + } + Display = function (defaults) { this._drawCtx = null; this._c_forceCanvas = false; @@ -351,18 +359,41 @@ var Display; this._renderQ = []; }, - fillRect: function (x, y, width, height, color) { - this._setFillColor(color); - this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height); + fillRect: function (x, y, width, height, color, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this.renderQ_push({ + 'type': 'fill', + 'x': x, + 'y': y, + 'width': width, + 'height': height, + 'color': color + }); + } else { + this._setFillColor(color); + this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height); + } }, - copyImage: function (old_x, old_y, new_x, new_y, w, h) { - var x1 = old_x - this._viewportLoc.x; - var y1 = old_y - this._viewportLoc.y; - var x2 = new_x - this._viewportLoc.x; - var y2 = new_y - this._viewportLoc.y; + copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this.renderQ_push({ + 'type': 'copy', + 'old_x': old_x, + 'old_y': old_y, + 'x': new_x, + 'y': new_y, + 'width': w, + 'height': h, + }); + } else { + var x1 = old_x - this._viewportLoc.x; + var y1 = old_y - this._viewportLoc.y; + var x2 = new_x - this._viewportLoc.x; + var y2 = new_y - this._viewportLoc.y; - this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h); + this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h); + } }, // start updating a tile @@ -394,7 +425,7 @@ var Display; data[i + 3] = 255; } } else { - this.fillRect(x, y, width, height, color); + this.fillRect(x, y, width, height, color, true); } }, @@ -425,7 +456,7 @@ var Display; } } } else { - this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color); + this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true); } }, @@ -438,16 +469,34 @@ var Display; // else: No-op -- already done by setSubTile }, - blitImage: function (x, y, width, height, arr, offset) { - if (this._true_color) { + blitImage: function (x, y, width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this.renderQ_push({ + 'type': 'blit', + 'data': arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else if (this._true_color) { this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); } else { this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); } }, - blitRgbImage: function (x, y , width, height, arr, offset) { - if (this._true_color) { + blitRgbImage: function (x, y , width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + this.renderQ_push({ + 'type': 'blitRgb', + 'data': arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else if (this._true_color) { this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); } else { // probably wrong? @@ -455,6 +504,26 @@ var Display; } }, + blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) { + if (this._renderQ.length !== 0 && !from_queue) { + // NB(directxman12): it's technically more performant here to use preallocated arrays, but it + // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, + // this probably isn't getting called *nearly* as much + var new_arr = new Uint8Array(width * height * 4); + new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); + this.renderQ_push({ + 'type': 'blitRgbx', + 'data': new_arr, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }); + } else { + this._rgbxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + } + }, + blitStringImage: function (str, x, y) { var img = new Image(); img.onload = function () { @@ -632,6 +701,18 @@ var Display; this._drawCtx.putImageData(img, x - vx, y - vy); }, + _rgbxImageData: function (x, y, vx, vy, width, height, arr, offset) { + // NB(directxman12): arr must be an Type Array view + var img; + if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) { + img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height); + } else { + img = this._drawCtx.createImageData(width, height); + img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); + } + this._drawCtx.putImageData(img, x - vx, y - vy); + }, + _cmapImageData: function (x, y, vx, vy, width, height, arr, offset) { var img = this._drawCtx.createImageData(width, height); var data = img.data; @@ -652,16 +733,19 @@ var Display; var a = this._renderQ[0]; switch (a.type) { case 'copy': - this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height); + this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true); break; case 'fill': - this.fillRect(a.x, a.y, a.width, a.height, a.color); + this.fillRect(a.x, a.y, a.width, a.height, a.color, true); break; case 'blit': - this.blitImage(a.x, a.y, a.width, a.height, a.data, 0); + this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); break; case 'blitRgb': - this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0); + this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true); + break; + case 'blitRgbx': + this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); break; case 'img': if (a.img.complete) { diff --git a/include/inflator.js b/include/inflator.js new file mode 100644 index 00000000..a9c75a62 --- /dev/null +++ b/include/inflator.js @@ -0,0 +1,2409 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.inflator = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o>> 16) & 0xffff) |0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; +} + + +module.exports = adler32; + +},{}],3:[function(require,module,exports){ +'use strict'; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + + +// Use ordinary array, since untyped makes no boost here +function makeTable() { + var c, table = []; + + for (var n =0; n < 256; n++) { + c = n; + for (var k =0; k < 8; k++) { + c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +} + +// Create table on load. Just 255 signed longs. Not a problem. +var crcTable = makeTable(); + + +function crc32(crc, buf, len, pos) { + var t = crcTable, + end = pos + len; + + crc = crc ^ (-1); + + for (var i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + + +module.exports = crc32; + +},{}],4:[function(require,module,exports){ +'use strict'; + +// See state defs from inflate.js +var BAD = 30; /* got a data error -- remain here until reset */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +module.exports = function inflate_fast(strm, start) { + var state; + var _in; /* local strm.input */ + var last; /* have enough input while in < last */ + var _out; /* local strm.output */ + var beg; /* inflate()'s initial strm.output */ + var end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + var dmax; /* maximum distance from zlib header */ +//#endif + var wsize; /* window size or zero if not using window */ + var whave; /* valid bytes in the window */ + var wnext; /* window write index */ + var window; /* allocated sliding window, if wsize != 0 */ + var hold; /* local strm.hold */ + var bits; /* local strm.bits */ + var lcode; /* local strm.lencode */ + var dcode; /* local strm.distcode */ + var lmask; /* mask for first level of length codes */ + var dmask; /* mask for first level of distance codes */ + var here; /* retrieved table entry */ + var op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + var len; /* match length, unused bytes */ + var dist; /* match distance */ + var from; /* where to copy match from */ + var from_source; + + + var input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } + +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; + +},{}],5:[function(require,module,exports){ +'use strict'; + + +var utils = require('../utils/common'); +var adler32 = require('./adler32'); +var crc32 = require('./crc32'); +var inflate_fast = require('./inffast'); +var inflate_table = require('./inftrees'); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +//var Z_NO_FLUSH = 0; +//var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +//var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + +/* The deflate compression method */ +var Z_DEFLATED = 8; + + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + + +var HEAD = 1; /* i: waiting for magic header */ +var FLAGS = 2; /* i: waiting for method and flags (gzip) */ +var TIME = 3; /* i: waiting for modification time (gzip) */ +var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ +var EXLEN = 5; /* i: waiting for extra length (gzip) */ +var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ +var NAME = 7; /* i: waiting for end of file name (gzip) */ +var COMMENT = 8; /* i: waiting for end of comment (gzip) */ +var HCRC = 9; /* i: waiting for header crc (gzip) */ +var DICTID = 10; /* i: waiting for dictionary check value */ +var DICT = 11; /* waiting for inflateSetDictionary() call */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ +var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ +var STORED = 14; /* i: waiting for stored size (length and complement) */ +var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ +var COPY = 16; /* i/o: waiting for input or output to copy stored block */ +var TABLE = 17; /* i: waiting for dynamic block table lengths */ +var LENLENS = 18; /* i: waiting for code length code lengths */ +var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ +var LEN_ = 20; /* i: same as LEN below, but only first time in */ +var LEN = 21; /* i: waiting for length/lit/eob code */ +var LENEXT = 22; /* i: waiting for length extra bits */ +var DIST = 23; /* i: waiting for distance code */ +var DISTEXT = 24; /* i: waiting for distance extra bits */ +var MATCH = 25; /* o: waiting for output space to copy string */ +var LIT = 26; /* o: waiting for output space to write literal */ +var CHECK = 27; /* i: waiting for 32-bit check value */ +var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ +var DONE = 29; /* finished check, done -- remain here until reset */ +var BAD = 30; /* got a data error -- remain here until reset */ +var MEM = 31; /* got an inflate() memory error -- remain here until reset */ +var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + + + +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_WBITS = MAX_WBITS; + + +function ZSWAP32(q) { + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +} + + +function InflateState() { + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib) */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ + this.work = new utils.Buf16(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + +function inflateResetKeep(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); + state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +function inflateReset(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + +} + +function inflateReset2(strm, windowBits) { + var wrap; + var state; + + /* get the state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +} + +function inflateInit2(strm, windowBits) { + var ret; + var state; + + if (!strm) { return Z_STREAM_ERROR; } + //strm.msg = Z_NULL; /* in case we return an error */ + + state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.window = null/*Z_NULL*/; + ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK) { + strm.state = null/*Z_NULL*/; + } + return ret; +} + +function inflateInit(strm) { + return inflateInit2(strm, DEF_WBITS); +} + + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +var virgin = true; + +var lenfix, distfix; // We have no pointers in JS, so keep tables separate + +function fixedtables(state) { + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + var sym; + + lenfix = new utils.Buf32(512); + distfix = new utils.Buf32(32); + + /* literal/length table */ + sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {bits: 9}); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, {bits: 5}); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +} + + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +function updatewindow(strm, src, end, copy) { + var dist; + var state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new utils.Buf8(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + utils.arraySet(state.window,src, end - state.wsize, state.wsize, 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + utils.arraySet(state.window,src, end - copy, dist, state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + utils.arraySet(state.window,src, end - copy, copy, 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +} + +function inflate(strm, flush) { + var state; + var input, output; // input/output buffers + var next; /* next input INDEX */ + var put; /* next output INDEX */ + var have, left; /* available input and output */ + var hold; /* bit buffer */ + var bits; /* bits in bit buffer */ + var _in, _out; /* save starting available input and output */ + var copy; /* number of stored or match bytes to copy */ + var from; /* where to copy match bytes from */ + var from_source; + var here = 0; /* current decoding table entry */ + var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //var last; /* parent table entry */ + var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + var len; /* length to copy for repeats, bits to drop */ + var ret; /* return code */ + var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ + var opts; + + var n; // temporary var for NEED_BITS + + var order = /* permutation of code lengths */ + [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + + + if (!strm || !strm.state || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + state.flags = 0; /* expect zlib header */ + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + else if (len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + state.dmax = 1 << len; + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if (state.flags & 0x0200) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more conveniend processing later + state.head.extra = new Array(state.head.extra_len); + } + utils.arraySet( + state.head.extra, + input, + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + copy, + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0 /*crc32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = ZSWAP32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + utils.arraySet(output, input, next, copy, put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = {bits: state.lenbits}; + ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = {bits: state.lenbits}; + ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = {bits: state.distbits}; + ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inflate_fast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) -1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) -1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defailts, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' insdead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if (_out) { + strm.adler = state.check = + /*UPDATE(state.check, put - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, ZSWAP32 returns signed too + if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR; + break inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { + state.mode = MEM; + return Z_MEM_ERROR; + } + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if (state.wrap && _out) { + strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { + ret = Z_BUF_ERROR; + } + return ret; +} + +function inflateEnd(strm) { + + if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { + return Z_STREAM_ERROR; + } + + var state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK; +} + +function inflateGetHeader(strm, head) { + var state; + + /* check state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK; +} + + +exports.inflateReset = inflateReset; +exports.inflateReset2 = inflateReset2; +exports.inflateResetKeep = inflateResetKeep; +exports.inflateInit = inflateInit; +exports.inflateInit2 = inflateInit2; +exports.inflate = inflate; +exports.inflateEnd = inflateEnd; +exports.inflateGetHeader = inflateGetHeader; +exports.inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +exports.inflateCopy = inflateCopy; +exports.inflateGetDictionary = inflateGetDictionary; +exports.inflateMark = inflateMark; +exports.inflatePrime = inflatePrime; +exports.inflateSetDictionary = inflateSetDictionary; +exports.inflateSync = inflateSync; +exports.inflateSyncPoint = inflateSyncPoint; +exports.inflateUndermine = inflateUndermine; +*/ + +},{"../utils/common":1,"./adler32":2,"./crc32":3,"./inffast":4,"./inftrees":6}],6:[function(require,module,exports){ +'use strict'; + + +var utils = require('../utils/common'); + +var MAXBITS = 15; +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +var lbase = [ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]; + +var lext = [ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]; + +var dbase = [ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]; + +var dext = [ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]; + +module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) +{ + var bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + var len = 0; /* a code's length in bits */ + var sym = 0; /* index of code symbols */ + var min = 0, max = 0; /* minimum and maximum code lengths */ + var root = 0; /* number of index bits for root table */ + var curr = 0; /* number of index bits for current table */ + var drop = 0; /* code bits to drop for sub-table */ + var left = 0; /* number of prefix codes available */ + var used = 0; /* code entries in table used */ + var huff = 0; /* Huffman code */ + var incr; /* for incrementing code, index */ + var fill; /* index for replicating entries */ + var low; /* low bits for current root entry */ + var mask; /* mask for low root bits */ + var next; /* next available space in table */ + var base = null; /* base value table to use */ + var base_index = 0; +// var shoextra; /* extra bits table to use */ + var end; /* use base and extra for symbol > end */ + var count = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* number of codes of each length */ + var offs = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* offsets in table for each length */ + var extra = null; + var extra_index = 0; + + var here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES) { + base = extra = work; /* dummy value--not used */ + end = 19; + + } else if (type === LENS) { + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + + } else { /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + var i=0; + /* process all codes and make table entries */ + for (;;) { + i++; + /* create table entry */ + here_bits = len - drop; + if (work[sym] < end) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] > end) { + here_op = extra[extra_index + work[sym]]; + here_val = base[base_index + work[sym]]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + +},{"../utils/common":1}],7:[function(require,module,exports){ +'use strict'; + + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +module.exports = ZStream; + +},{}],"/partial_inflator.js":[function(require,module,exports){ +var zlib = require('./lib/zlib/inflate.js'); +var ZStream = require('./lib/zlib/zstream.js'); + +var Inflate = function () { + this.strm = new ZStream(); + this.chunkSize = 1024 * 10 * 10; + this.strm.output = new Uint8Array(this.chunkSize); + this.windowBits = 5; + + zlib.inflateInit(this.strm, this.windowBits); +}; + +Inflate.prototype = { + inflate: function (data, flush) { + this.strm.input = data; + this.strm.avail_in = this.strm.input.length; + this.strm.next_in = 0; + this.strm.next_out = 0; + + this.strm.avail_out = this.chunkSize; + + zlib.inflate(this.strm, flush); + + return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); + }, + + reset: function () { + zlib.inflateReset(this.strm); + } +}; + +module.exports = {Inflate: Inflate}; + +},{"./lib/zlib/inflate.js":5,"./lib/zlib/zstream.js":7}]},{},[])("/partial_inflator.js") +}); diff --git a/include/playback.js b/include/playback.js index 7756529d..203576f6 100644 --- a/include/playback.js +++ b/include/playback.js @@ -12,21 +12,39 @@ var rfb, mode, test_state, frame_idx, frame_length, iteration, iterations, istart_time, // Pre-declarations for jslint - send_array, next_iteration, queue_next_packet, do_packet; + send_array, next_iteration, queue_next_packet, do_packet, enable_test_mode; // Override send_array send_array = function (arr) { // Stub out send_array }; +enable_test_mode = function () { + rfb._sock._mode = VNC_frame_encoding; + rfb._sock.send = send_array; + rfb._sock.close = function () {}; + rfb._sock.flush = function () {}; + rfb._checkEvents = function () {}; + rfb.connect = function (host, port, password, path) { + this._rfb_host = host; + this._rfb_port = port; + this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_path = (path !== undefined) ? path : ""; + this._sock.init('binary', 'ws'); + this._updateState('ProtocolVersion', "Starting VNC handshake"); + }; +}; + next_iteration = function () { + rfb = new RFB({'target': $D('VNC_canvas'), + 'onUpdateState': updateState}); + enable_test_mode(); + if (iteration === 0) { frame_length = VNC_frame_data.length; test_state = 'running'; - } else { - rfb.disconnect(); } - + if (test_state !== 'running') { return; } iteration += 1; @@ -91,9 +109,9 @@ do_packet = function () { for (var i = 0; i < frame.length - start; i++) { u8[i] = frame.charCodeAt(start + i); } - rfb.recv_message({'data' : u8}); + rfb._sock._recv_message({'data' : u8}); } else { - rfb.recv_message({'data' : frame.slice(start)}); + rfb._sock._recv_message({'data' : frame.slice(start)}); } frame_idx += 1; diff --git a/include/rfb.js b/include/rfb.js index a591ca2b..b7a811d5 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -92,6 +92,9 @@ var RFB; this._fb_height = 0; this._fb_name = ""; + this._destBuff = null; + this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) + this._rre_chunk_sz = 100; this._timing = { @@ -128,7 +131,7 @@ var RFB; 'view_only': false, // Disable client mouse/keyboard 'xvp_password_sep': '@', // Separator for XVP password fields 'disconnectTimeout': 3, // Time (s) to wait for disconnection - 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection + 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection 'repeaterID': '', // [UltraVNC] RepeaterID to connect to 'viewportDrag': false, // Move the viewport on mouse drags @@ -217,16 +220,8 @@ var RFB; Util.Info("Using native WebSockets"); this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); } else { - Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version); - if (!Util.Flash || Util.Flash.version < 9) { - this._cleanupSocket('fatal'); - throw new Exception("WebSockets or Adobe Flash is required"); - } else if (document.location.href.substr(0, 7) === 'file://') { - this._cleanupSocket('fatal'); - throw new Exception("'file://' URL is incompatible with Adobe Flash"); - } else { - this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); - } + this._cleanupSocket('fatal'); + throw new Error("WebSocket support is required to use noVNC"); } Util.Debug("<< RFB.constructor"); @@ -264,14 +259,14 @@ var RFB; if (this._rfb_state !== 'normal' || this._view_only) { return false; } Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 0)); - arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 0)); - arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 0)); - this._sock.send(arr); + RFB.messages.keyEvent(this._sock, XK_Control_L, 1); + RFB.messages.keyEvent(this._sock, XK_Alt_L, 1); + RFB.messages.keyEvent(this._sock, XK_Delete, 1); + RFB.messages.keyEvent(this._sock, XK_Delete, 0); + RFB.messages.keyEvent(this._sock, XK_Alt_L, 0); + RFB.messages.keyEvent(this._sock, XK_Control_L, 0); + + this._sock.flush(); }, xvpOp: function (ver, op) { @@ -297,21 +292,22 @@ var RFB; // followed by an up key. sendKey: function (code, down) { if (this._rfb_state !== "normal" || this._view_only) { return false; } - var arr = []; if (typeof down !== 'undefined') { Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); - arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0)); + RFB.messages.keyEvent(this._sock, code, down ? 1 : 0); } else { Util.Info("Sending key code (down + up): " + code); - arr = arr.concat(RFB.messages.keyEvent(code, 1)); - arr = arr.concat(RFB.messages.keyEvent(code, 0)); + RFB.messages.keyEvent(this._sock, code, 1); + RFB.messages.keyEvent(this._sock, code, 0); } - this._sock.send(arr); + + this._sock.flush(); }, clipboardPasteFrom: function (text) { if (this._rfb_state !== 'normal') { return; } - this._sock.send(RFB.messages.clientCutText(text)); + RFB.messages.clientCutText(this._sock, text); + this._sock.flush(); }, setDesktopSize: function (width, height) { @@ -362,8 +358,6 @@ var RFB; _init_vars: function () { // reset state - this._sock.init(); - this._FBU.rects = 0; this._FBU.subrects = 0; // RRE and HEXTILE this._FBU.lines = 0; // RAW @@ -380,8 +374,9 @@ var RFB; } for (i = 0; i < 4; i++) { - this._FBU.zlibs[i] = new TINF(); - this._FBU.zlibs[i].init(); + //this._FBU.zlibs[i] = new TINF(); + //this._FBU.zlibs[i].init(); + this._FBU.zlibs[i] = new inflator.Inflate(); } }, @@ -578,16 +573,10 @@ var RFB; } }, - _checkEvents: function () { - if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) { - this._sock.send(this._mouse_arr); - this._mouse_arr = []; - } - }, - _handleKeyPress: function (keysym, down) { if (this._view_only) { return; } // View only, skip keyboard, events - this._sock.send(RFB.messages.keyEvent(keysym, down)); + RFB.messages.keyEvent(this._sock, keysym, down); + this._sock.flush(); }, _handleMouseButton: function (x, y, down, bmask) { @@ -611,10 +600,8 @@ var RFB; if (this._view_only) { return; } // View only, skip mouse events - this._mouse_arr = this._mouse_arr.concat( - RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); - this._sock.send(this._mouse_arr); - this._mouse_arr = []; + if (this._rfb_state !== "normal") { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); }, _handleMouseMove: function (x, y) { @@ -631,10 +618,8 @@ var RFB; if (this._view_only) { return; } // View only, skip mouse events - this._mouse_arr = this._mouse_arr.concat( - RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); - - this._checkEvents(); + if (this._rfb_state !== "normal") { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); }, // Message Handlers @@ -758,7 +743,8 @@ var RFB; if (this._sock.rQwait("auth challenge", 16)) { return false; } - var challenge = this._sock.rQshiftBytes(16); + // TODO(directxman12): make genDES not require an Array + var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); var response = RFB.genDES(this._rfb_password, challenge); this._sock.send(response); this._updateState("SecurityResult"); @@ -900,6 +886,7 @@ var RFB; /* Screen size */ this._fb_width = this._sock.rQshift16(); this._fb_height = this._sock.rQshift16(); + this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); /* PIXEL_FORMAT */ var bpp = this._sock.rQshift8(); @@ -995,18 +982,13 @@ var RFB; this._fb_depth = 1; } - var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color); - response = response.concat( - RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color)); - response = response.concat( - RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), - this._fb_width, this._fb_height)); + RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color); + RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color); + RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); this._timing.fbu_rt_start = (new Date()).getTime(); this._timing.pixels = 0; - this._sock.send(response); - - this._checkEvents(); + this._sock.flush(); if (this._encrypt) { this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); @@ -1108,8 +1090,8 @@ var RFB; case 0: // FramebufferUpdate var ret = this._framebufferUpdate(); if (ret) { - this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), - this._fb_width, this._fb_height)); + RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); + this._sock.flush(); } return ret; @@ -1181,7 +1163,14 @@ var RFB; this._timing.last_fbu = (new Date()).getTime(); - ret = this._encHandlers[this._FBU.encoding](); + var handler = this._encHandlers[this._FBU.encoding]; + try { + //ret = this._encHandlers[this._FBU.encoding](); + ret = handler(); + } catch (ex) { + console.log("missed " + this._FBU.encoding + ": " + handler); + ret = this._encHandlers[this._FBU.encoding](); + } now = (new Date()).getTime(); this._timing.cur_fbu += (now - this._timing.last_fbu); @@ -1276,64 +1265,111 @@ var RFB; // Class Methods RFB.messages = { - keyEvent: function (keysym, down) { - var arr = [4]; - arr.push8(down); - arr.push16(0); - arr.push32(keysym); - return arr; + keyEvent: function (sock, keysym, down) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 4; // msg-type + buff[offset + 1] = down; + + buff[offset + 2] = 0; + buff[offset + 3] = 0; + + buff[offset + 4] = (keysym >> 24); + buff[offset + 5] = (keysym >> 16); + buff[offset + 6] = (keysym >> 8); + buff[offset + 7] = keysym; + + sock._sQlen += 8; }, - pointerEvent: function (x, y, mask) { - var arr = [5]; // msg-type - arr.push8(mask); - arr.push16(x); - arr.push16(y); - return arr; + pointerEvent: function (sock, x, y, mask) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 5; // msg-type + + buff[offset + 1] = mask; + + buff[offset + 2] = x >> 8; + buff[offset + 3] = x; + + buff[offset + 4] = y >> 8; + buff[offset + 5] = y; + + sock._sQlen += 6; }, // TODO(directxman12): make this unicode compatible? - clientCutText: function (text) { - var arr = [6]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push32(text.length); + clientCutText: function (sock, text) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 6; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + var n = text.length; + + buff[offset + 4] = n >> 24; + buff[offset + 5] = n >> 16; + buff[offset + 6] = n >> 8; + buff[offset + 7] = n; + for (var i = 0; i < n; i++) { - arr.push(text.charCodeAt(i)); + buff[offset + 8 + i] = text.charCodeAt(i); } - return arr; + sock._sQlen += 8 + n; }, - pixelFormat: function (bpp, depth, true_color) { - var arr = [0]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding + pixelFormat: function (sock, bpp, depth, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; - arr.push8(bpp * 8); // bits-per-pixel - arr.push8(depth * 8); // depth - arr.push8(0); // little-endian - arr.push8(true_color ? 1 : 0); // true-color + buff[offset] = 0; // msg-type - arr.push16(255); // red-max - arr.push16(255); // green-max - arr.push16(255); // blue-max - arr.push8(16); // red-shift - arr.push8(8); // green-shift - arr.push8(0); // blue-shift + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - return arr; + buff[offset + 4] = bpp * 8; // bits-per-pixel + buff[offset + 5] = depth * 8; // depth + buff[offset + 6] = 0; // little-endian + buff[offset + 7] = true_color ? 1 : 0; // true-color + + buff[offset + 8] = 0; // red-max + buff[offset + 9] = 255; // red-max + + buff[offset + 10] = 0; // green-max + buff[offset + 11] = 255; // green-max + + buff[offset + 12] = 0; // blue-max + buff[offset + 13] = 255; // blue-max + + buff[offset + 14] = 16; // red-shift + buff[offset + 15] = 8; // green-shift + buff[offset + 16] = 0; // blue-shift + + buff[offset + 17] = 0; // padding + buff[offset + 18] = 0; // padding + buff[offset + 19] = 0; // padding + + sock._sQlen += 20; }, - clientEncodings: function (encodings, local_cursor, true_color) { - var i, encList = []; + clientEncodings: function (sock, encodings, local_cursor, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; + buff[offset] = 2; // msg-type + buff[offset + 1] = 0; // padding + + // offset + 2 and offset + 3 are encoding count + + var i, j = offset + 4, cnt = 0; for (i = 0; i < encodings.length; i++) { if (encodings[i][0] === "Cursor" && !local_cursor) { Util.Debug("Skipping Cursor pseudo-encoding"); @@ -1341,23 +1377,25 @@ var RFB; // TODO: remove this when we have tight+non-true-color Util.Warn("Skipping tight as it is only supported with true color"); } else { - encList.push(encodings[i][1]); + var enc = encodings[i][1]; + buff[j] = enc >> 24; + buff[j + 1] = enc >> 16; + buff[j + 2] = enc >> 8; + buff[j + 3] = enc; + + j += 4; + cnt++; } } - var arr = [2]; // msg-type - arr.push8(0); // padding + buff[offset + 2] = cnt >> 8; + buff[offset + 3] = cnt; - arr.push16(encList.length); // encoding count - for (i = 0; i < encList.length; i++) { - arr.push32(encList[i]); - } - - return arr; + sock._sQlen += j - offset; }, - fbUpdateRequests: function (cleanDirty, fb_width, fb_height) { - var arr = []; + fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) { + var offsetIncrement = 0; var cb = cleanDirty.cleanBox; var w, h; @@ -1365,7 +1403,7 @@ var RFB; w = typeof cb.w === "undefined" ? fb_width : cb.w; h = typeof cb.h === "undefined" ? fb_height : cb.h; // Request incremental for clean box - arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h)); + RFB.messages.fbUpdateRequest(sock, 1, cb.x, cb.y, w, h); } for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) { @@ -1373,24 +1411,33 @@ var RFB; // Force all (non-incremental) for dirty box w = typeof db.w === "undefined" ? fb_width : db.w; h = typeof db.h === "undefined" ? fb_height : db.h; - arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h)); + RFB.messages.fbUpdateRequest(sock, 0, db.x, db.y, w, h); } - - return arr; }, - fbUpdateRequest: function (incremental, x, y, w, h) { + fbUpdateRequest: function (sock, incremental, x, y, w, h) { + var buff = sock._sQ; + var offset = sock._sQlen; + if (typeof(x) === "undefined") { x = 0; } if (typeof(y) === "undefined") { y = 0; } - var arr = [3]; // msg-type - arr.push8(incremental); - arr.push16(x); - arr.push16(y); - arr.push16(w); - arr.push16(h); + buff[offset] = 3; // msg-type + buff[offset + 1] = incremental; - return arr; + buff[offset + 2] = (x >> 8) & 0xFF; + buff[offset + 3] = x & 0xFF; + + buff[offset + 4] = (y >> 8) & 0xFF; + buff[offset + 5] = y & 0xFF; + + buff[offset + 6] = (w >> 8) & 0xFF; + buff[offset + 7] = w & 0xFF; + + buff[offset + 8] = (h >> 8) & 0xFF; + buff[offset + 9] = h & 0xFF; + + sock._sQlen += 10; } }; @@ -1436,15 +1483,10 @@ var RFB; COPYRECT: function () { this._FBU.bytes = 4; if (this._sock.rQwait("COPYRECT", 4)) { return false; } - this._display.renderQ_push({ - 'type': 'copy', - 'old_x': this._sock.rQshift16(), - 'old_y': this._sock.rQshift16(), - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - }); + this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(), + this._FBU.x, this._FBU.y, this._FBU.width, + this._FBU.height); + this._FBU.rects--; this._FBU.bytes = 0; return true; @@ -1549,11 +1591,21 @@ var RFB; rQi += this._FBU.bytes - 1; } else { if (this._FBU.subencoding & 0x02) { // Background - this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp); + if (this._fb_Bpp == 1) { + this._FBU.background = rQ[rQi]; + } else { + // fb_Bpp is 4 + this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; + } rQi += this._fb_Bpp; } if (this._FBU.subencoding & 0x04) { // Foreground - this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp); + if (this._fb_Bpp == 1) { + this._FBU.foreground = rQ[rQi]; + } else { + // this._fb_Bpp is 4 + this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; + } rQi += this._fb_Bpp; } @@ -1565,7 +1617,12 @@ var RFB; for (var s = 0; s < subrects; s++) { var color; if (this._FBU.subencoding & 0x10) { // SubrectsColoured - color = rQ.slice(rQi, rQi + this._fb_Bpp); + if (this._fb_Bpp === 1) { + color = rQ[rQi]; + } else { + // _fb_Bpp is 4 + color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; + } rQi += this._fb_Bpp; } else { color = this._FBU.foreground; @@ -1639,61 +1696,96 @@ var RFB; } } - var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); - if (uncompressed.status !== 0) { + //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); + var uncompressed = this._FBU.zlibs[streamId].inflate(data, true); + /*if (uncompressed.status !== 0) { Util.Error("Invalid data in zlib stream"); - } + }*/ - return uncompressed.data; + //return uncompressed.data; + return uncompressed; }.bind(this); - var indexedToRGB = function (data, numColors, palette, width, height) { + var indexedToRGBX2Color = function (data, palette, width, height) { // Convert indexed (palette based) image data to RGB // TODO: reduce number of calculations inside loop - var dest = []; - var x, y, dp, sp; - if (numColors === 2) { - var w = Math.floor((width + 7) / 8); - var w1 = Math.floor(width / 8); + var dest = this._destBuff; + var w = Math.floor((width + 7) / 8); + var w1 = Math.floor(width / 8); - for (y = 0; y < height; y++) { - var b; - for (x = 0; x < w1; x++) { - for (b = 7; b >= 0; b--) { - dp = (y * width + x * 8 + 7 - b) * 3; - sp = (data[y * w + x] >> b & 1) * 3; - dest[dp] = palette[sp]; - dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; - } + /*for (var y = 0; y < height; y++) { + var b, x, dp, sp; + var yoffset = y * width; + var ybitoffset = y * w; + var xoffset, targetbyte; + for (x = 0; x < w1; x++) { + xoffset = yoffset + x * 8; + targetbyte = data[ybitoffset + x]; + for (b = 7; b >= 0; b--) { + dp = (xoffset + 7 - b) * 3; + sp = (targetbyte >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; } + } - for (b = 7; b >= 8 - width % 8; b--) { - dp = (y * width + x * 8 + 7 - b) * 3; + xoffset = yoffset + x * 8; + targetbyte = data[ybitoffset + x]; + for (b = 7; b >= 8 - width % 8; b--) { + dp = (xoffset + 7 - b) * 3; + sp = (targetbyte >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + } + }*/ + + for (var y = 0; y < height; y++) { + var b, x, dp, sp; + for (x = 0; x < w1; x++) { + for (b = 7; b >= 0; b--) { + dp = (y * width + x * 8 + 7 - b) * 4; sp = (data[y * w + x] >> b & 1) * 3; dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; dest[dp + 2] = palette[sp + 2]; + dest[dp + 3] = 255; } } - } else { - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - dp = (y * width + x) * 3; - sp = data[y * width + x] * 3; - dest[dp] = palette[sp]; - dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; - } + + for (b = 7; b >= 8 - width % 8; b--) { + dp = (y * width + x * 8 + 7 - b) * 4; + sp = (data[y * w + x] >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + dest[dp + 3] = 255; } } return dest; }.bind(this); + var indexedToRGBX = function (data, palette, width, height) { + // Convert indexed (palette based) image data to RGB + var dest = this._destBuff; + var total = width * height * 4; + for (var i = 0, j = 0; i < total; i += 4, j++) { + var sp = data[j] * 3; + dest[i] = palette[sp]; + dest[i + 1] = palette[sp + 1]; + dest[i + 2] = palette[sp + 2]; + dest[i + 3] = 255; + } + + return dest; + }.bind(this); + var rQ = this._sock.get_rQ(); var rQi = this._sock.get_rQi(); - var cmode, clength, data; + var cmode, data; + var cl_header, cl_data; var handlePalette = function () { var numColors = rQ[rQi + 2] + 1; @@ -1706,37 +1798,51 @@ var RFB; var raw = false; if (rowSize * this._FBU.height < 12) { raw = true; - clength = [0, rowSize * this._FBU.height]; + cl_header = 0; + cl_data = rowSize * this._FBU.height; + //clength = [0, rowSize * this._FBU.height]; } else { - clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize, - 3 + paletteSize + 3)); + // begin inline getTightCLength (returning two-item arrays is bad for performance with GC) + var cl_offset = rQi + 3 + paletteSize; + cl_header = 1; + cl_data = 0; + cl_data += rQ[cl_offset] & 0x7f; + if (rQ[cl_offset] & 0x80) { + cl_header++; + cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; + if (rQ[cl_offset + 1] & 0x80) { + cl_header++; + cl_data += rQ[cl_offset + 2] << 14; + } + } + // end inline getTightCLength } - this._FBU.bytes += clength[0] + clength[1]; + this._FBU.bytes += cl_header + cl_data; if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } // Shift ctl, filter id, num colors, palette entries, and clength off this._sock.rQskipBytes(3); - var palette = this._sock.rQshiftBytes(paletteSize); - this._sock.rQskipBytes(clength[0]); + //var palette = this._sock.rQshiftBytes(paletteSize); + this._sock.rQshiftTo(this._paletteBuff, paletteSize); + this._sock.rQskipBytes(cl_header); if (raw) { - data = this._sock.rQshiftBytes(clength[1]); + data = this._sock.rQshiftBytes(cl_data); } else { - data = decompress(this._sock.rQshiftBytes(clength[1])); + data = decompress(this._sock.rQshiftBytes(cl_data)); } // Convert indexed (palette based) image data to RGB - var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height); + var rgbx; + if (numColors == 2) { + rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height); + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); + } else { + rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height); + this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); + } - this._display.renderQ_push({ - 'type': 'blitRgb', - 'data': rgb, - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - }); return true; }.bind(this); @@ -1746,30 +1852,37 @@ var RFB; var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; if (uncompressedSize < 12) { raw = true; - clength = [0, uncompressedSize]; + cl_header = 0; + cl_data = uncompressedSize; } else { - clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); + // begin inline getTightCLength (returning two-item arrays is for peformance with GC) + var cl_offset = rQi + 1; + cl_header = 1; + cl_data = 0; + cl_data += rQ[cl_offset] & 0x7f; + if (rQ[cl_offset] & 0x80) { + cl_header++; + cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; + if (rQ[cl_offset + 1] & 0x80) { + cl_header++; + cl_data += rQ[cl_offset + 2] << 14; + } + } + // end inline getTightCLength } - this._FBU.bytes = 1 + clength[0] + clength[1]; + this._FBU.bytes = 1 + cl_header + cl_data; if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } // Shift ctl, clength off - this._sock.rQshiftBytes(1 + clength[0]); + this._sock.rQshiftBytes(1 + cl_header); if (raw) { - data = this._sock.rQshiftBytes(clength[1]); + data = this._sock.rQshiftBytes(cl_data); } else { - data = decompress(this._sock.rQshiftBytes(clength[1])); + data = decompress(this._sock.rQshiftBytes(cl_data)); } - this._display.renderQ_push({ - 'type': 'blitRgb', - 'data': data, - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height - }); + this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false); return true; }.bind(this); @@ -1817,28 +1930,34 @@ var RFB; // Determine FBU.bytes switch (cmode) { case "fill": - this._sock.rQskip8(); // shift off ctl - var color = this._sock.rQshiftBytes(this._fb_depth); - this._display.renderQ_push({ - 'type': 'fill', - 'x': this._FBU.x, - 'y': this._FBU.y, - 'width': this._FBU.width, - 'height': this._FBU.height, - 'color': [color[2], color[1], color[0]] - }); + // skip ctl byte + this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false); + this._sock.rQskipBytes(4); break; case "png": case "jpeg": - clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); - this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data + // begin inline getTightCLength (returning two-item arrays is for peformance with GC) + var cl_offset = rQi + 1; + cl_header = 1; + cl_data = 0; + cl_data += rQ[cl_offset] & 0x7f; + if (rQ[cl_offset] & 0x80) { + cl_header++; + cl_data += (rQ[cl_offset + 1] & 0x7f) << 7; + if (rQ[cl_offset + 1] & 0x80) { + cl_header++; + cl_data += rQ[cl_offset + 2] << 14; + } + } + // end inline getTightCLength + this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } // We have everything, render it - this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length + this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length var img = new Image(); img.src = "data: image/" + cmode + - RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1])); + RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data)); this._display.renderQ_push({ 'type': 'img', 'img': img, @@ -1881,6 +2000,7 @@ var RFB; handle_FB_resize: function () { this._fb_width = this._FBU.width; this._fb_height = this._FBU.height; + this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); this._display.resize(this._fb_width, this._fb_height); this._onFBResize(this, this._fb_width, this._fb_height); this._timing.fbu_rt_start = (new Date()).getTime(); @@ -1903,9 +2023,9 @@ var RFB; this._sock.rQskipBytes(1); // number-of-screens this._sock.rQskipBytes(3); // padding - for (var i=0; i - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/rfc6455 - -(function() { - - if (window.WEB_SOCKET_FORCE_FLASH) { - // Keeps going. - } else if (window.WebSocket) { - return; - } else if (window.MozWebSocket) { - // Firefox. - window.WebSocket = MozWebSocket; - return; - } - - var logger; - if (window.WEB_SOCKET_LOGGER) { - logger = WEB_SOCKET_LOGGER; - } else if (window.console && window.console.log && window.console.error) { - // In some environment, console is defined but console.log or console.error is missing. - logger = window.console; - } else { - logger = {log: function(){ }, error: function(){ }}; - } - - // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. - if (swfobject.getFlashPlayerVersion().major < 10) { - logger.error("Flash Player >= 10.0.0 is required."); - return; - } - if (location.protocol == "file:") { - logger.error( - "WARNING: web-socket-js doesn't work in file:///... URL " + - "unless you set Flash Security Settings properly. " + - "Open the page via Web server i.e. http://..."); - } - - /** - * Our own implementation of WebSocket class using Flash. - * @param {string} url - * @param {array or string} protocols - * @param {string} proxyHost - * @param {int} proxyPort - * @param {string} headers - */ - window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { - var self = this; - self.__id = WebSocket.__nextId++; - WebSocket.__instances[self.__id] = self; - self.readyState = WebSocket.CONNECTING; - self.bufferedAmount = 0; - self.__events = {}; - if (!protocols) { - protocols = []; - } else if (typeof protocols == "string") { - protocols = [protocols]; - } - // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. - // Otherwise, when onopen fires immediately, onopen is called before it is set. - self.__createTask = setTimeout(function() { - WebSocket.__addTask(function() { - self.__createTask = null; - WebSocket.__flash.create( - self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); - }); - }, 0); - }; - - /** - * Send data to the web socket. - * @param {string} data The data to send to the socket. - * @return {boolean} True for success, false for failure. - */ - WebSocket.prototype.send = function(data) { - if (this.readyState == WebSocket.CONNECTING) { - throw "INVALID_STATE_ERR: Web Socket connection has not been established"; - } - // We use encodeURIComponent() here, because FABridge doesn't work if - // the argument includes some characters. We don't use escape() here - // because of this: - // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions - // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't - // preserve all Unicode characters either e.g. "\uffff" in Firefox. - // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require - // additional testing. - var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); - if (result < 0) { // success - return true; - } else { - this.bufferedAmount += result; - return false; - } - }; - - /** - * Close this web socket gracefully. - */ - WebSocket.prototype.close = function() { - if (this.__createTask) { - clearTimeout(this.__createTask); - this.__createTask = null; - this.readyState = WebSocket.CLOSED; - return; - } - if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { - return; - } - this.readyState = WebSocket.CLOSING; - WebSocket.__flash.close(this.__id); - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void - */ - WebSocket.prototype.addEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) { - this.__events[type] = []; - } - this.__events[type].push(listener); - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void - */ - WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) return; - var events = this.__events[type]; - for (var i = events.length - 1; i >= 0; --i) { - if (events[i] === listener) { - events.splice(i, 1); - break; - } - } - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {Event} event - * @return void - */ - WebSocket.prototype.dispatchEvent = function(event) { - var events = this.__events[event.type] || []; - for (var i = 0; i < events.length; ++i) { - events[i](event); - } - var handler = this["on" + event.type]; - if (handler) handler.apply(this, [event]); - }; - - /** - * Handles an event from Flash. - * @param {Object} flashEvent - */ - WebSocket.prototype.__handleEvent = function(flashEvent) { - - if ("readyState" in flashEvent) { - this.readyState = flashEvent.readyState; - } - if ("protocol" in flashEvent) { - this.protocol = flashEvent.protocol; - } - - var jsEvent; - if (flashEvent.type == "open" || flashEvent.type == "error") { - jsEvent = this.__createSimpleEvent(flashEvent.type); - } else if (flashEvent.type == "close") { - jsEvent = this.__createSimpleEvent("close"); - jsEvent.wasClean = flashEvent.wasClean ? true : false; - jsEvent.code = flashEvent.code; - jsEvent.reason = flashEvent.reason; - } else if (flashEvent.type == "message") { - var data = decodeURIComponent(flashEvent.message); - jsEvent = this.__createMessageEvent("message", data); - } else { - throw "unknown event type: " + flashEvent.type; - } - - this.dispatchEvent(jsEvent); - - }; - - WebSocket.prototype.__createSimpleEvent = function(type) { - if (document.createEvent && window.Event) { - var event = document.createEvent("Event"); - event.initEvent(type, false, false); - return event; - } else { - return {type: type, bubbles: false, cancelable: false}; - } - }; - - WebSocket.prototype.__createMessageEvent = function(type, data) { - if (document.createEvent && window.MessageEvent && !window.opera) { - var event = document.createEvent("MessageEvent"); - event.initMessageEvent("message", false, false, data, null, null, window, null); - return event; - } else { - // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. - return {type: type, data: data, bubbles: false, cancelable: false}; - } - }; - - /** - * Define the WebSocket readyState enumeration. - */ - WebSocket.CONNECTING = 0; - WebSocket.OPEN = 1; - WebSocket.CLOSING = 2; - WebSocket.CLOSED = 3; - - // Field to check implementation of WebSocket. - WebSocket.__isFlashImplementation = true; - WebSocket.__initialized = false; - WebSocket.__flash = null; - WebSocket.__instances = {}; - WebSocket.__tasks = []; - WebSocket.__nextId = 0; - - /** - * Load a new flash security policy file. - * @param {string} url - */ - WebSocket.loadFlashPolicyFile = function(url){ - WebSocket.__addTask(function() { - WebSocket.__flash.loadManualPolicyFile(url); - }); - }; - - /** - * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. - */ - WebSocket.__initialize = function() { - - if (WebSocket.__initialized) return; - WebSocket.__initialized = true; - - if (WebSocket.__swfLocation) { - // For backword compatibility. - window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; - } - if (!window.WEB_SOCKET_SWF_LOCATION) { - logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); - return; - } - if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && - !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && - WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { - var swfHost = RegExp.$1; - if (location.host != swfHost) { - logger.error( - "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + - "('" + location.host + "' != '" + swfHost + "'). " + - "See also 'How to host HTML file and SWF file in different domains' section " + - "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + - "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); - } - } - var container = document.createElement("div"); - container.id = "webSocketContainer"; - // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents - // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). - // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash - // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is - // the best we can do as far as we know now. - container.style.position = "absolute"; - if (WebSocket.__isFlashLite()) { - container.style.left = "0px"; - container.style.top = "0px"; - } else { - container.style.left = "-100px"; - container.style.top = "-100px"; - } - var holder = document.createElement("div"); - holder.id = "webSocketFlash"; - container.appendChild(holder); - document.body.appendChild(container); - // See this article for hasPriority: - // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html - swfobject.embedSWF( - WEB_SOCKET_SWF_LOCATION, - "webSocketFlash", - "1" /* width */, - "1" /* height */, - "10.0.0" /* SWF version */, - null, - null, - {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, - null, - function(e) { - if (!e.success) { - logger.error("[WebSocket] swfobject.embedSWF failed"); - } - } - ); - - }; - - /** - * Called by Flash to notify JS that it's fully loaded and ready - * for communication. - */ - WebSocket.__onFlashInitialized = function() { - // We need to set a timeout here to avoid round-trip calls - // to flash during the initialization process. - setTimeout(function() { - WebSocket.__flash = document.getElementById("webSocketFlash"); - WebSocket.__flash.setCallerUrl(location.href); - WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); - for (var i = 0; i < WebSocket.__tasks.length; ++i) { - WebSocket.__tasks[i](); - } - WebSocket.__tasks = []; - }, 0); - }; - - /** - * Called by Flash to notify WebSockets events are fired. - */ - WebSocket.__onFlashEvent = function() { - setTimeout(function() { - try { - // Gets events using receiveEvents() instead of getting it from event object - // of Flash event. This is to make sure to keep message order. - // It seems sometimes Flash events don't arrive in the same order as they are sent. - var events = WebSocket.__flash.receiveEvents(); - for (var i = 0; i < events.length; ++i) { - WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); - } - } catch (e) { - logger.error(e); - } - }, 0); - return true; - }; - - // Called by Flash. - WebSocket.__log = function(message) { - logger.log(decodeURIComponent(message)); - }; - - // Called by Flash. - WebSocket.__error = function(message) { - logger.error(decodeURIComponent(message)); - }; - - WebSocket.__addTask = function(task) { - if (WebSocket.__flash) { - task(); - } else { - WebSocket.__tasks.push(task); - } - }; - - /** - * Test if the browser is running flash lite. - * @return {boolean} True if flash lite is running, false otherwise. - */ - WebSocket.__isFlashLite = function() { - if (!window.navigator || !window.navigator.mimeTypes) { - return false; - } - var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; - if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { - return false; - } - return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; - }; - - if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { - // NOTE: - // This fires immediately if web_socket.js is dynamically loaded after - // the document is loaded. - swfobject.addDomLoadEvent(function() { - WebSocket.__initialize(); - }); - } - -})(); diff --git a/include/websock.js b/include/websock.js index cc82e5a2..61d94672 100644 --- a/include/websock.js +++ b/include/websock.js @@ -15,7 +15,7 @@ */ /*jslint browser: true, bitwise: true */ -/*global Util, Base64 */ +/*global Util*/ // Load Flash WebSocket emulator if needed @@ -34,29 +34,26 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { /* no builtin WebSocket so load web_socket.js */ Websock_native = false; - (function () { - window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() + - "web-socket-js/WebSocketMain.swf"; - if (Util.Engine.trident) { - Util.Debug("Forcing uncached load of WebSocketMain.swf"); - window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); - } - Util.load_scripts(["web-socket-js/swfobject.js", - "web-socket-js/web_socket.js"]); - })(); } - function Websock() { "use strict"; this._websocket = null; // WebSocket object - this._rQ = []; // Receive queue - this._rQi = 0; // Receive queue index - this._rQmax = 10000; // Max receive queue size before compacting - this._sQ = []; // Send queue - this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64' + this._rQi = 0; // Receive queue index + this._rQlen = 0; // Next write position in the receive queue + this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) + this._rQmax = this._rQbufferSize / 8; + // called in init: this._rQ = new Uint8Array(this._rQbufferSize); + this._rQ = null; // Receive queue + + this._sQbufferSize = 1024 * 10; // 10 KiB + // called in init: this._sQ = new Uint8Array(this._sQbufferSize); + this._sQlen = 0; + this._sQ = null; // Send queue + + this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64' this.maxBufferedAmount = 200; this._eventHandlers = { @@ -69,6 +66,22 @@ function Websock() { (function () { "use strict"; + + var typedArrayToString = (function () { + // This is only for PhantomJS, which doesn't like apply-ing + // with Typed Arrays + try { + var arr = new Uint8Array([1, 2, 3]); + String.fromCharCode.apply(null, arr); + return function (a) { return String.fromCharCode.apply(null, a); }; + } catch (ex) { + return function (a) { + return String.fromCharCode.apply( + null, Array.prototype.slice.call(a)); + }; + } + })(); + Websock.prototype = { // Getters and Setters get_sQ: function () { @@ -89,7 +102,7 @@ function Websock() { // Receive Queue rQlen: function () { - return this._rQ.length - this._rQi; + return this._rQlen - this._rQi; }, rQpeek8: function () { @@ -108,15 +121,7 @@ function Websock() { this._rQi += num; }, - rQunshift8: function (num) { - if (this._rQi === 0) { - this._rQ.unshift(num); - } else { - this._rQi--; - this._rQ[this._rQi] = num; - } - }, - + // TODO(directxman12): test performance with these vs a DataView rQshift16: function () { return (this._rQ[this._rQi++] << 8) + this._rQ[this._rQi++]; @@ -131,22 +136,29 @@ function Websock() { rQshiftStr: function (len) { if (typeof(len) === 'undefined') { len = this.rQlen(); } - var arr = this._rQ.slice(this._rQi, this._rQi + len); + var arr = new Uint8Array(this._rQ.buffer, this._rQi, len); this._rQi += len; - return String.fromCharCode.apply(null, arr); + return typedArrayToString(arr); }, rQshiftBytes: function (len) { if (typeof(len) === 'undefined') { len = this.rQlen(); } this._rQi += len; - return this._rQ.slice(this._rQi - len, this._rQi); + return new Uint8Array(this._rQ.buffer, this._rQi - len, len); + }, + + rQshiftTo: function (target, len) { + if (len === undefined) { len = this.rQlen(); } + // TODO: make this just use set with views when using a ArrayBuffer to store the rQ + target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); + this._rQi += len; }, rQslice: function (start, end) { if (end) { - return this._rQ.slice(this._rQi + start, this._rQi + end); + return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); } else { - return this._rQ.slice(this._rQi + start); + return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start); } }, @@ -154,7 +166,7 @@ function Websock() { // to be available in the receive queue. Return true if we need to // wait (and possibly print a debug message), otherwise false. rQwait: function (msg, num, goback) { - var rQlen = this._rQ.length - this._rQi; // Skip rQlen() function call + var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call if (rQlen < num) { if (goback) { if (this._rQi < goback) { @@ -175,9 +187,9 @@ function Websock() { } if (this._websocket.bufferedAmount < this.maxBufferedAmount) { - if (this._sQ.length > 0) { + if (this._sQlen > 0) { this._websocket.send(this._encode_message()); - this._sQ = []; + this._sQlen = 0; } return true; @@ -189,8 +201,9 @@ function Websock() { }, send: function (arr) { - this._sQ = this._sQ.concat(arr); - return this.flush(); + this._sQ.set(arr, this._sQlen); + this._sQlen += arr.length; + return this.flush(); }, send_string: function (str) { @@ -208,10 +221,14 @@ function Websock() { this._eventHandlers[evt] = handler; }, + _allocate_buffers: function () { + this._rQ = new Uint8Array(this._rQbufferSize); + this._sQ = new Uint8Array(this._sQbufferSize); + }, + init: function (protocols, ws_schema) { - this._rQ = []; + this._allocate_buffers(); this._rQi = 0; - this._sQ = []; this._websocket = null; // Check for full typed array support @@ -238,35 +255,21 @@ function Websock() { // Default protocols if not specified if (typeof(protocols) === "undefined") { - if (wsbt) { - protocols = ['binary', 'base64']; - } else { - protocols = 'base64'; - } + protocols = 'binary'; + } + + if (Array.isArray(protocols) && protocols.indexOf('binary') > -1) { + protocols = 'binary'; } if (!wsbt) { - if (protocols === 'binary') { - throw new Error('WebSocket binary sub-protocol requested but not supported'); - } + throw new Error("noVNC no longer supports base64 WebSockets. " + + "Please use a browser which supports binary WebSockets."); + } - if (typeof(protocols) === 'object') { - var new_protocols = []; - - for (var i = 0; i < protocols.length; i++) { - if (protocols[i] === 'binary') { - Util.Error('Skipping unsupported WebSocket binary sub-protocol'); - } else { - new_protocols.push(protocols[i]); - } - } - - if (new_protocols.length > 0) { - protocols = new_protocols; - } else { - throw new Error("Only WebSocket binary sub-protocol was requested and is not supported."); - } - } + if (protocols != 'binary') { + throw new Error("noVNC no longer supports base64 WebSockets. Please " + + "use the binary subprotocol instead."); } return protocols; @@ -289,9 +292,16 @@ function Websock() { this._mode = this._websocket.protocol; Util.Info("Server choose sub-protocol: " + this._websocket.protocol); } else { - this._mode = 'base64'; + this._mode = 'binary'; Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol); } + + if (this._mode != 'binary') { + throw new Error("noVNC no longer supports base64 WebSockets. Please " + + "use the binary subprotocol instead."); + + } + this._eventHandlers.open(); Util.Debug("<< WebSock.onopen"); }).bind(this); @@ -321,26 +331,16 @@ function Websock() { // private methods _encode_message: function () { - if (this._mode === 'binary') { - // Put in a binary arraybuffer - return (new Uint8Array(this._sQ)).buffer; - } else { - // base64 encode - return Base64.encode(this._sQ); - } + // Put in a binary arraybuffer + // according to the spec, you can send ArrayBufferViews with the send method + return new Uint8Array(this._sQ.buffer, 0, this._sQlen); }, _decode_message: function (data) { - if (this._mode === 'binary') { - // push arraybuffer values onto the end - var u8 = new Uint8Array(data); - for (var i = 0; i < u8.length; i++) { - this._rQ.push(u8[i]); - } - } else { - // base64 decode and concat to end - this._rQ = this._rQ.concat(Base64.decode(data, 0)); - } + // push arraybuffer values onto the end + var u8 = new Uint8Array(data); + this._rQ.set(u8, this._rQlen); + this._rQlen += u8.length; }, _recv_message: function (e) { @@ -349,8 +349,26 @@ function Websock() { if (this.rQlen() > 0) { this._eventHandlers.message(); // Compact the receive queue - if (this._rQ.length > this._rQmax) { - this._rQ = this._rQ.slice(this._rQi); + if (this._rQlen == this._rQi) { + this._rQlen = 0; + this._rQi = 0; + } else if (this._rQlen > this._rQmax) { + if (this._rQlen - this._rQi > 0.5 * this._rQbufferSize) { + var old_rQbuffer = this._rQ.buffer; + this._rQbufferSize *= 2; + this._rQmax = this._rQbufferSize / 8; + this._rQ = new Uint8Array(this._rQbufferSize); + this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi)); + } else { + if (this._rQ.copyWithin) { + // Firefox only, ATM + this._rQ.copyWithin(0, this._rQi); + } else { + this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi)); + } + } + + this._rQlen = this._rQlen - this._rQi; this._rQi = 0; } } else { diff --git a/karma.conf.js b/karma.conf.js index d8b8e905..870b8551 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -119,9 +119,9 @@ module.exports = function(config) { 'include/input.js', 'include/websock.js', 'include/rfb.js', - 'include/jsunzip.js', 'include/des.js', 'include/display.js', + 'include/inflator.js', 'tests/test.*.js' ], diff --git a/tests/assertions.js b/tests/assertions.js index 92b11d1f..930e1460 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -5,7 +5,17 @@ chai.use(function (_chai, utils) { var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data; // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that var data = new Uint8Array(data_cl); - this.assert(utils.eql(data, target_data), + var same = true; + for (var i = 0; i < obj.length; i++) { + if (data[i] != target_data[i]) { + same = false; + break; + } + } + if (!same) { + console.log("expected data: %o, actual data: %o", target_data, data); + } + this.assert(same, "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}", "expected #{this} not to have displayed the image #{act}", target_data, @@ -14,11 +24,70 @@ chai.use(function (_chai, utils) { _chai.Assertion.addMethod('sent', function (target_data) { var obj = this._obj; + obj.inspect = function () { + var res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen), + _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) }; + res.prototype = obj; + return res; + }; var data = obj._websocket._get_sent_data(); - this.assert(utils.eql(data, target_data), + var same = true; + for (var i = 0; i < obj.length; i++) { + if (data[i] != target_data[i]) { + same = false; + break; + } + } + if (!same) { + console.log("expected data: %o, actual data: %o", target_data, data); + } + this.assert(same, "expected #{this} to have sent the data #{exp}, but it actually sent #{act}", "expected #{this} not to have sent the data #{act}", - target_data, - data); + Array.prototype.slice.call(target_data), + Array.prototype.slice.call(data)); + }); + + _chai.Assertion.addProperty('array', function () { + utils.flag(this, 'array', true); + }); + + _chai.Assertion.overwriteMethod('equal', function (_super) { + return function assertArrayEqual(target) { + if (utils.flag(this, 'array')) { + var obj = this._obj; + + var i; + var same = true; + + if (utils.flag(this, 'deep')) { + for (i = 0; i < obj.length; i++) { + if (!utils.eql(obj[i], target[i])) { + same = false; + break; + } + } + + this.assert(same, + "expected #{this} to have elements deeply equal to #{exp}", + "expected #{this} not to have elements deeply equal to #{exp}", + Array.prototype.slice.call(target)); + } else { + for (i = 0; i < obj.length; i++) { + if (obj[i] != target[i]) { + same = false; + break; + } + } + + this.assert(same, + "expected #{this} to have elements equal to #{exp}", + "expected #{this} not to have elements equal to #{exp}", + Array.prototype.slice.call(target)); + } + } else { + _super.apply(this, arguments); + } + }; }); }); diff --git a/tests/fake.websocket.js b/tests/fake.websocket.js index 749c0eaf..21012059 100644 --- a/tests/fake.websocket.js +++ b/tests/fake.websocket.js @@ -51,14 +51,9 @@ var FakeWebSocket; }, _get_sent_data: function () { - var arr = []; - for (var i = 0; i < this.bufferedAmount; i++) { - arr[i] = this._send_queue[i]; - } - + var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); this.bufferedAmount = 0; - - return arr; + return res; }, _open: function (data) { diff --git a/tests/run_from_console.casper.js b/tests/run_from_console.casper.js index 57ed2be2..6a738a3e 100644 --- a/tests/run_from_console.casper.js +++ b/tests/run_from_console.casper.js @@ -15,7 +15,19 @@ var casper_opts = { } }; -var provide_emitter = function(file_paths) { +var provide_emitter = function(file_paths, debug_port) { + if (debug_port) { + casper_opts.child['remote-debugger-port'] = debug_port; + var debug_url = ('https://localhost:' + debug_port + + '/webkit/inspector/inspector.html?page='); + console.info('[remote-debugger] Navigate to ' + debug_url + '1 and ' + + 'run `__run();` in the console to continue loading.' + + '\n[remote-debugger] Navigate to ' + debug_url + '2 to ' + + 'view the actual page source.\n' + + '[remote-debugger] Use the `debugger;` statement to ' + + 'trigger an initial breakpoint.'); + } + var spooky = new Spooky(casper_opts, function(err) { if (err) { if (err.stack) console.warn(err.stack); diff --git a/tests/run_from_console.js b/tests/run_from_console.js index 2a5bb70b..371e861a 100755 --- a/tests/run_from_console.js +++ b/tests/run_from_console.js @@ -20,6 +20,7 @@ program .option('--output-html', 'Instead of running the tests, just output the generated HTML source to STDOUT (should be used with .js tests)') .option('-d, --debug', 'Show debug output (the "console" event) from the provider') .option('-r, --relative', 'Use relative paths in the generated HTML file') + .option('--debugger ', 'Enable the remote debugger for CasperJS') .parse(process.argv); if (program.tests.length === 0) { @@ -202,7 +203,7 @@ if (!program.outputHtml && !program.generateHtml) { .write("\n"); //console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name); - var provider = prov.provide_emitter(file_paths); + var provider = prov.provide_emitter(file_paths, program.debugger); provider.on('test_ready', function(test_json) { console.log(''); @@ -249,6 +250,24 @@ if (!program.outputHtml && !program.generateHtml) { console.log(''); if (test_json.num_fails > 0 || program.printAll) { + var extract_error_lines = function (err) { + // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too + var err_lines = err.split('\n'); + if (err_lines.length == 1) { + return err_lines[0]; + } else { + var ind; + for (ind = 0; ind < err_lines.length; ind++) { + var at_ind = err_lines[ind].trim().indexOf('at '); + if (at_ind === 0) { + break; + } + } + + return err_lines.slice(0, ind).join('\n'); + } + }; + var traverse_tree = function(indentation, node) { if (node.type == 'suite') { if (!node.has_subfailures && !program.printAll) return; @@ -280,7 +299,7 @@ if (!program.outputHtml && !program.generateHtml) { cursor.magenta(); console.log('- failed: '+node.text+test_json.replay); cursor.red(); - console.log(' '+node.error.split("\n")[0]); // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too + console.log(' '+extract_error_lines(node.error)); cursor.reset(); console.log(''); } diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 006b5fa9..961d9eb5 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1,4 +1,4 @@ -// requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, jsunzip, des, display +// requires local modules: util, websock, rfb, keyboard, keysym, keysymdef, input, inflator, des, display // requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; @@ -18,6 +18,27 @@ describe('Remote Frame Buffer Protocol Client', function() { before(FakeWebSocket.replace); after(FakeWebSocket.restore); + before(function () { + this.clock = sinon.useFakeTimers(); + // Use a single set of buffers instead of reallocating to + // speed up tests + var sock = new Websock(); + var _sQ = new Uint8Array(sock._sQbufferSize); + var rQ = new Uint8Array(sock._rQbufferSize); + + Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers; + Websock.prototype._allocate_buffers = function () { + this._sQ = _sQ; + this._rQ = rQ; + }; + + }); + + after(function () { + Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers; + this.clock.restore(); + }); + describe('Public API Basic Behavior', function () { var client; beforeEach(function () { @@ -105,34 +126,34 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { - var expected = []; - expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 0)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 0)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 0)); + var expected = {_sQ: new Uint8Array(48), _sQlen: 0}; + RFB.messages.keyEvent(expected, 0xFFE3, 1); + RFB.messages.keyEvent(expected, 0xFFE9, 1); + RFB.messages.keyEvent(expected, 0xFFFF, 1); + RFB.messages.keyEvent(expected, 0xFFFF, 0); + RFB.messages.keyEvent(expected, 0xFFE9, 0); + RFB.messages.keyEvent(expected, 0xFFE3, 0); client.sendCtrlAltDel(); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the keys if we are not in a normal state', function () { client._rfb_state = "broken"; client.sendCtrlAltDel(); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the keys if we are set as view_only', function () { client._view_only = true; client.sendCtrlAltDel(); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -141,34 +162,36 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should send a single key with the given code and state (down = true)', function () { - var expected = RFB.messages.keyEvent(123, 1); + var expected = {_sQ: new Uint8Array(8), _sQlen: 0}; + RFB.messages.keyEvent(expected, 123, 1); client.sendKey(123, true); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should send both a down and up event if the state is not specified', function () { - var expected = RFB.messages.keyEvent(123, 1); - expected = expected.concat(RFB.messages.keyEvent(123, 0)); + var expected = {_sQ: new Uint8Array(16), _sQlen: 0}; + RFB.messages.keyEvent(expected, 123, 1); + RFB.messages.keyEvent(expected, 123, 0); client.sendKey(123); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the key if we are not in a normal state', function () { client._rfb_state = "broken"; client.sendKey(123); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the key if we are set as view_only', function () { client._view_only = true; client.sendKey(123); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -177,21 +200,22 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should send the given text in a paste event', function () { - var expected = RFB.messages.clientCutText('abc'); + var expected = {_sQ: new Uint8Array(11), _sQlen: 0}; + RFB.messages.clientCutText(expected, 'abc'); client.clipboardPasteFrom('abc'); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the text if we are not in a normal state', function () { client._rfb_state = "broken"; client.clipboardPasteFrom('abc'); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -200,7 +224,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; client._supportsSetDesktopSize = true; @@ -221,19 +245,19 @@ describe('Remote Frame Buffer Protocol Client', function() { expected.push32(0); // flags client.setDesktopSize(1, 2); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () { client._supportsSetDesktopSize = false; client.setDesktopSize(1,2); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the request if we are not in a normal state', function () { client._rfb_state = "broken"; client.setDesktopSize(1,2); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -242,7 +266,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; client._rfb_xvp_ver = 1; @@ -250,27 +274,27 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send the shutdown signal on #xvpShutdown', function () { client.xvpShutdown(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x02]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02])); }); it('should send the reboot signal on #xvpReboot', function () { client.xvpReboot(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x03]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03])); }); it('should send the reset signal on #xvpReset', function () { client.xvpReset(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x04]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04])); }); it('should support sending arbitrary XVP operations via #xvpOp', function () { client.xvpOp(1, 7); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x07]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07])); }); it('should not send XVP operations with higher versions than we support', function () { expect(client.xvpOp(2, 7)).to.be.false; - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); }); @@ -483,7 +507,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); }); it('should interpret version 003.003 as version 3.3', function () { @@ -540,7 +564,7 @@ describe('Remote Frame Buffer Protocol Client', function() { send_ver('000.000', client); expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); expect(sent_data).to.have.length(250); send_ver('003.008', client); @@ -563,7 +587,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expected[i] = expected_str.charCodeAt(i); } - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); it('should transition to the Security state on successful negotiation', function () { @@ -596,7 +620,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var auth_schemes = [2, 1, 2]; client._sock._websocket._receive_data(auth_schemes); expect(client._rfb_auth_scheme).to.equal(2); - expect(client._sock).to.have.sent([2]); + expect(client._sock).to.have.sent(new Uint8Array([2])); }); it('should fail if there are no supported schemes for versions >= 3.7', function () { @@ -702,7 +726,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._receive_data(new Uint8Array(challenge)); var des_pass = RFB.genDES('passwd', challenge); - expect(client._sock).to.have.sent(des_pass); + expect(client._sock).to.have.sent(new Uint8Array(des_pass)); }); it('should transition to SecurityResult immediately after sending the password', function () { @@ -759,7 +783,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var expected = [22, 4, 6]; // auth selection, len user, len target for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); }); @@ -807,14 +831,14 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should choose the notunnel tunnel type', function () { send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client); - expect(client._sock).to.have.sent([0, 0, 0, 0]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0])); }); it('should continue to sub-auth negotiation after tunnel negotiation', function () { send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client); client._sock._websocket._get_sent_data(); // skip the tunnel choice here send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); expect(client._rfb_state).to.equal('SecurityResult'); }); @@ -830,7 +854,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should accept the "no auth" auth type and transition to SecurityResult', function () { client._rfb_tightvnc = true; send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); expect(client._rfb_state).to.equal('SecurityResult'); }); @@ -838,7 +862,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._rfb_tightvnc = true; client._negotiate_std_vnc_auth = sinon.spy(); send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 2]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2])); expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; expect(client._rfb_auth_scheme).to.equal(2); }); @@ -902,13 +926,13 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send 1 if we are in shared mode', function () { client.set_shared(true); client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent([1]); + expect(client._sock).to.have.sent(new Uint8Array([1])); }); it('should send 0 if we are not in shared mode', function () { client.set_shared(false); client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent([0]); + expect(client._sock).to.have.sent(new Uint8Array([0])); }); }); @@ -1045,21 +1069,16 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should reply with the pixel format, client encodings, and initial update request', function () { client.set_true_color(true); client.set_local_cursor(false); - var expected = RFB.messages.pixelFormat(4, 3, true); - expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true)); + // we skip the cursor encoding + var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), _sQlen: 0}; + RFB.messages.pixelFormat(expected, 4, 3, true); + RFB.messages.clientEncodings(expected, client._encodings, false, true); var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] }; - expected = expected.concat(RFB.messages.fbUpdateRequests(expected_cdr, 27, 32)); + RFB.messages.fbUpdateRequests(expected, expected_cdr, 27, 32); send_server_init({ width: 27, height: 32 }, client); - expect(client._sock).to.have.sent(expected); - }); - - it('should check for sending mouse events', function () { - // be lazy with our checking so we don't have to check through the whole sent buffer - sinon.spy(client, '_checkEvents'); - send_server_init({}, client); - expect(client._checkEvents).to.have.been.calledOnce; + expect(client._sock).to.have.sent(expected._sQ); }); it('should transition to the "normal" state', function () { @@ -1142,14 +1161,15 @@ describe('Remote Frame Buffer Protocol Client', function() { } it('should send an update request if there is sufficient data', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0}; var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; - var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); client._framebufferUpdate = function () { return true; }; client._sock._websocket._receive_data(new Uint8Array([0])); - expect(client._sock).to.have.sent(expected_msg); + expect(client._sock).to.have.sent(expected_msg._sQ); }); it('should not send an update request if we need more data', function () { @@ -1158,9 +1178,10 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should resume receiving an update if we previously did not have enough data', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0}; var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; - var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); // just enough to set FBU.rects client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3])); @@ -1169,7 +1190,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._framebufferUpdate = function () { return true; }; // we magically have enough data // 247 should *not* be used as the message type here client._sock._websocket._receive_data(new Uint8Array([247])); - expect(client._sock).to.have.sent(expected_msg); + expect(client._sock).to.have.sent(expected_msg._sQ); }); it('should parse out information from a header before any actual data comes in', function () { @@ -1691,58 +1712,61 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock.send = sinon.spy(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'flush'); client._rfb_state = 'normal'; }); it('should not send button messages in view-only mode', function () { client._view_only = true; client._mouse._onMouseButton(0, 0, 1, 0x001); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._view_only = true; client._mouse._onMouseMove(0, 0); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should send a pointer event on mouse button presses', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a mask of 1 on mousedown', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 0, 10, 12, 0x001); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a mask of 0 on mouseup', function () { client._mouse_buttonMask = 0x001; client._mouse._onMouseButton(10, 12, 0, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x000); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a pointer event on mouse movement', function () { client._mouse._onMouseMove(10, 12); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should set the button mask so that future mouse movements use it', function () { client._mouse._onMouseButton(10, 12, 1, 0x010); - client._sock.send = sinon.spy(); client._mouse._onMouseMove(13, 9); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(13, 9, 0x010); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010); + RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); // NB(directxman12): we don't need to test not sending messages in @@ -1753,13 +1777,13 @@ describe('Remote Frame Buffer Protocol Client', function() { client._viewportDragging = true; client._display.viewportChangePos = sinon.spy(); client._mouse._onMouseMove(13, 9); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send button messages when initiating viewport dragging', function () { client._viewportDrag = true; client._mouse._onMouseButton(13, 9, 0x001); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should be initiate viewport dragging on a button down event, if enabled', function () { @@ -1795,20 +1819,23 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock.send = sinon.spy(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'flush'); }); it('should send a key message on a key press', function () { client._keyboard._onKeyPress(1234, 1); - expect(client._sock.send).to.have.been.calledOnce; - var key_msg = RFB.messages.keyEvent(1234, 1); - expect(client._sock.send).to.have.been.calledWith(key_msg); + var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0}; + RFB.messages.keyEvent(key_msg, 1234, 1); + expect(client._sock).to.have.sent(key_msg._sQ); }); it('should not send messages in view-only mode', function () { client._view_only = true; client._keyboard._onKeyPress(1234, 1); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -1826,7 +1853,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client.connect('host', 8675); client._rfb_state = 'normal'; client._normal_msg = sinon.spy(); - client._sock._websocket._receive_data(Base64.encode([])); + client._sock._websocket._receive_data(new Uint8Array([])); expect(client._normal_msg).to.not.have.been.called; }); @@ -1834,7 +1861,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client.connect('host', 8675); client._rfb_state = 'normal'; client._normal_msg = sinon.spy(); - client._sock._websocket._receive_data(Base64.encode([1, 2, 3])); + client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); expect(client._normal_msg).to.have.been.calledOnce; }); @@ -1842,7 +1869,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client.connect('host', 8675); client._rfb_state = 'ProtocolVersion'; client._init_msg = sinon.spy(); - client._sock._websocket._receive_data(Base64.encode([1, 2, 3])); + client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); expect(client._init_msg).to.have.been.calledOnce; }); diff --git a/tests/test.websock.js b/tests/test.websock.js index 7d242d3e..14d57832 100644 --- a/tests/test.websock.js +++ b/tests/test.websock.js @@ -1,5 +1,5 @@ -// requires local modules: websock, base64, util -// requires test modules: fake.websocket +// requires local modules: websock, util +// requires test modules: fake.websocket, assertions /* jshint expr: true */ var assert = chai.assert; var expect = chai.expect; @@ -9,13 +9,14 @@ describe('Websock', function() { describe('Queue methods', function () { var sock; - var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7]; + var RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); beforeEach(function () { sock = new Websock(); - for (var i = RQ_TEMPLATE.length - 1; i >= 0; i--) { - sock.rQunshift8(RQ_TEMPLATE[i]); - } + // skip init + sock._allocate_buffers(); + sock._rQ.set(RQ_TEMPLATE); + sock._rQlen = RQ_TEMPLATE.length; }); describe('rQlen', function () { it('should return the length of the receive queue', function () { @@ -49,14 +50,6 @@ describe('Websock', function() { }); }); - describe('rQunshift8', function () { - it('should place a byte at the front of the queue', function () { - sock.rQunshift8(255); - expect(sock.rQpeek8()).to.equal(255); - expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length + 1); - }); - }); - describe('rQshift16', function () { it('should pop two bytes from the receive queue and return a single number', function () { var bef_len = sock.rQlen(); @@ -84,7 +77,7 @@ describe('Websock', function() { var bef_rQi = sock.get_rQi(); var shifted = sock.rQshiftStr(3); expect(shifted).to.be.a('string'); - expect(shifted).to.equal(String.fromCharCode.apply(null, RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3))); + expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)))); expect(sock.rQlen()).to.equal(bef_len - 3); }); @@ -99,8 +92,8 @@ describe('Websock', function() { var bef_len = sock.rQlen(); var bef_rQi = sock.get_rQi(); var shifted = sock.rQshiftBytes(3); - expect(shifted).to.be.an.instanceof(Array); - expect(shifted).to.deep.equal(RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3)); + expect(shifted).to.be.an.instanceof(Uint8Array); + expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)); expect(sock.rQlen()).to.equal(bef_len - 3); }); @@ -123,19 +116,19 @@ describe('Websock', function() { it('should return an array containing the given slice of the receive queue', function () { var sl = sock.rQslice(0, 2); - expect(sl).to.be.an.instanceof(Array); - expect(sl).to.deep.equal(RQ_TEMPLATE.slice(0, 2)); + expect(sl).to.be.an.instanceof(Uint8Array); + expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2)); }); it('should use the rest of the receive queue if no end is given', function () { var sl = sock.rQslice(1); expect(sl).to.have.length(RQ_TEMPLATE.length - 1); - expect(sl).to.deep.equal(RQ_TEMPLATE.slice(1)); + expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1)); }); it('should take the current rQi in to account', function () { sock.set_rQi(1); - expect(sock.rQslice(0, 2)).to.deep.equal(RQ_TEMPLATE.slice(1, 3)); + expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2)); }); }); @@ -180,7 +173,8 @@ describe('Websock', function() { it('should actually send on the websocket if the websocket does not have too much buffered', function () { sock.maxBufferedAmount = 10; sock._websocket.bufferedAmount = 8; - sock._sQ = [1, 2, 3]; + sock._sQ = new Uint8Array([1, 2, 3]); + sock._sQlen = 3; var encoded = sock._encode_message(); sock.flush(); @@ -196,7 +190,7 @@ describe('Websock', function() { }); it('should not call send if we do not have anything queued up', function () { - sock._sQ = []; + sock._sQlen = 0; sock.maxBufferedAmount = 10; sock._websocket.bufferedAmount = 8; @@ -222,7 +216,7 @@ describe('Websock', function() { it('should add to the send queue', function () { sock.send([1, 2, 3]); var sq = sock.get_sQ(); - expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]); + expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3])); }); it('should call flush', function () { @@ -257,6 +251,8 @@ describe('Websock', function() { WebSocket.CONNECTING = old_WS.CONNECTING; WebSocket.CLOSING = old_WS.CLOSING; WebSocket.CLOSED = old_WS.CLOSED; + + WebSocket.prototype.binaryType = 'arraybuffer'; }); describe('opening', function () { @@ -265,22 +261,14 @@ describe('Websock', function() { }); it('should open the actual websocket', function () { - sock.open('ws://localhost:8675', 'base64'); - expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64'); + sock.open('ws://localhost:8675', 'binary'); + expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary'); }); - it('should fail if we try to use binary but do not support it', function () { - expect(function () { sock.open('ws:///', 'binary'); }).to.throw(Error); + it('should fail if we specify a protocol besides binary', function () { + expect(function () { sock.open('ws:///', 'base64'); }).to.throw(Error); }); - it('should fail if we specified an array with only binary and we do not support it', function () { - expect(function () { sock.open('ws:///', ['binary']); }).to.throw(Error); - }); - - it('should skip binary if we have multiple options for encoding and do not support binary', function () { - sock.open('ws:///', ['binary', 'base64']); - expect(WebSocket).to.have.been.calledWith('ws:///', ['base64']); - }); // it('should initialize the event handlers')? }); @@ -340,16 +328,15 @@ describe('Websock', function() { expect(sock._recv_message).to.have.been.calledOnce; }); - it('should copy the mode over upon opening', function () { - sock._websocket.protocol = 'cheese'; - sock._websocket.onopen(); - expect(sock._mode).to.equal('cheese'); + it('should fail if a protocol besides binary is requested', function () { + sock._websocket.protocol = 'base64'; + expect(sock._websocket.onopen).to.throw(Error); }); - it('should assume base64 if no protocol was available on opening', function () { + it('should assume binary if no protocol was available on opening', function () { sock._websocket.protocol = null; sock._websocket.onopen(); - expect(sock._mode).to.equal('base64'); + expect(sock._mode).to.equal('binary'); }); it('should call the open event handler on opening', function () { @@ -377,13 +364,7 @@ describe('Websock', function() { var sock; beforeEach(function () { sock = new Websock(); - }); - - it('should support decoding base64 string data to add it to the receive queue', function () { - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; - sock._recv_message(msg); - expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); + sock._allocate_buffers(); }); it('should support adding binary Uint8Array data to the receive queue', function () { @@ -395,16 +376,16 @@ describe('Websock', function() { it('should call the message event handler if present', function () { sock._eventHandlers.message = sinon.spy(); - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([1, 2, 3]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); expect(sock._eventHandlers.message).to.have.been.calledOnce; }); it('should not call the message event handler if there is nothing in the receive queue', function () { sock._eventHandlers.message = sinon.spy(); - var msg = { data: Base64.encode([]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); expect(sock._eventHandlers.message).not.to.have.been.called; }); @@ -412,21 +393,22 @@ describe('Websock', function() { it('should compact the receive queue', function () { // NB(sross): while this is an internal implementation detail, it's important to // test, otherwise the receive queue could become very large very quickly - sock._rQ = [0, 1, 2, 3, 4, 5]; + sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]); + sock._rQlen = 6; sock.set_rQi(6); sock._rQmax = 3; - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([1, 2, 3]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); - expect(sock._rQ.length).to.equal(3); + expect(sock._rQlen).to.equal(3); expect(sock.get_rQi()).to.equal(0); }); it('should call the error event handler on an exception', function () { sock._eventHandlers.error = sinon.spy(); sock._eventHandlers.message = sinon.stub().throws(); - var msg = { data: Base64.encode([1, 2, 3]) }; - sock._mode = 'base64'; + var msg = { data: new Uint8Array([1, 2, 3]).buffer }; + sock._mode = 'binary'; sock._recv_message(msg); expect(sock._eventHandlers.error).to.have.been.calledOnce; }); @@ -444,37 +426,17 @@ describe('Websock', function() { sock._websocket._open(); }); - it('should convert the send queue into an ArrayBuffer', function () { - sock._sQ = [1, 2, 3]; - var res = sock._encode_message(); // An ArrayBuffer - expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res)); + it('should only send the send queue up to the send queue length', function () { + sock._sQ = new Uint8Array([1, 2, 3, 4, 5]); + sock._sQlen = 3; + var res = sock._encode_message(); + expect(res).to.array.equal(new Uint8Array([1, 2, 3])); }); it('should properly pass the encoded data off to the actual WebSocket', function () { sock.send([1, 2, 3]); - expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); + expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3])); }); }); - - describe('as Base64 data', function () { - var sock; - beforeEach(function () { - sock = new Websock(); - sock.open('ws://', 'base64'); - sock._websocket._open(); - }); - - it('should convert the send queue into a Base64-encoded string', function () { - sock._sQ = [1, 2, 3]; - expect(sock._encode_message()).to.equal(Base64.encode([1, 2, 3])); - }); - - it('should properly pass the encoded data off to the actual WebSocket', function () { - sock.send([1, 2, 3]); - expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); - }); - - }); - }); }); diff --git a/tests/vnc_playback.html b/tests/vnc_playback.html index b5faf93c..f36f1e65 100644 --- a/tests/vnc_playback.html +++ b/tests/vnc_playback.html @@ -59,9 +59,9 @@ if (fname) { message("Loading " + fname); // Load supporting scripts - Util.load_scripts(["base64.js", "websock.js", "des.js", + Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js", "keysymdef.js", "keyboard.js", "input.js", "display.js", - "jsunzip.js", "rfb.js", "playback.js", fname]); + "rfb.js", "playback.js", "inflator.js", fname]); } else { message("Must specify data=FOO in query string."); @@ -75,7 +75,6 @@ test_state = 'failed'; break; case 'loaded': - $D('startButton').disabled = false; break; } if (typeof msg !== 'undefined') { @@ -99,7 +98,8 @@ mode = 'realtime'; } - recv_message = rfb.testMode(send_array, VNC_frame_encoding); + //recv_message = rfb.testMode(send_array, VNC_frame_encoding); + next_iteration(); } @@ -130,9 +130,8 @@ } if (fname) { message("VNC_frame_data.length: " + VNC_frame_data.length); - rfb = new RFB({'target': $D('VNC_canvas'), - 'onUpdateState': updateState}); } + $D('startButton').disabled = false; } diff --git a/utils/inflator.partial.js b/utils/inflator.partial.js new file mode 100644 index 00000000..2522d781 --- /dev/null +++ b/utils/inflator.partial.js @@ -0,0 +1,32 @@ +var zlib = require('./lib/zlib/inflate.js'); +var ZStream = require('./lib/zlib/zstream.js'); + +var Inflate = function () { + this.strm = new ZStream(); + this.chunkSize = 1024 * 10 * 10; + this.strm.output = new Uint8Array(this.chunkSize); + this.windowBits = 5; + + zlib.inflateInit(this.strm, this.windowBits); +}; + +Inflate.prototype = { + inflate: function (data, flush) { + this.strm.input = data; + this.strm.avail_in = this.strm.input.length; + this.strm.next_in = 0; + this.strm.next_out = 0; + + this.strm.avail_out = this.chunkSize; + + zlib.inflate(this.strm, flush); + + return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); + }, + + reset: function () { + zlib.inflateReset(this.strm); + } +}; + +module.exports = {Inflate: Inflate}; diff --git a/vnc_auto.html b/vnc_auto.html index 4361a5d5..86cfde75 100644 --- a/vnc_auto.html +++ b/vnc_auto.html @@ -77,7 +77,7 @@ // Load supporting scripts Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js", "keysymdef.js", "keyboard.js", "input.js", "display.js", - "jsunzip.js", "rfb.js", "keysym.js"]); + "inflator.js", "rfb.js", "keysym.js"]); var rfb; var resizeTimeout;