summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2018-07-20 16:05:16 +0200
committerPierre Ossman <ossman@cendio.se>2018-08-22 15:12:34 +0200
commit923cd22083bf9c9cc5f460c863fedfbcce5c4907 (patch)
tree631943db45b90543ee47ef858c7568f65d2535ed
parent11309f32434bdd56dcea380ed7abd98abe9e9fae (diff)
downloadnovnc-923cd22083bf9c9cc5f460c863fedfbcce5c4907.tar.gz
Move decoders to separate classes
Makes things a lot clearer by letting each encoding handle its own details and state.
-rw-r--r--core/decoders/copyrect.js24
-rw-r--r--core/decoders/hextile.js139
-rw-r--r--core/decoders/raw.js58
-rw-r--r--core/decoders/rre.js46
-rw-r--r--core/decoders/tight.js319
-rw-r--r--core/decoders/tightpng.js29
-rw-r--r--core/rfb.js582
7 files changed, 649 insertions, 548 deletions
diff --git a/core/decoders/copyrect.js b/core/decoders/copyrect.js
new file mode 100644
index 0000000..a78ded7
--- /dev/null
+++ b/core/decoders/copyrect.js
@@ -0,0 +1,24 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+export default class CopyRectDecoder {
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (sock.rQwait("COPYRECT", 4)) {
+ return false;
+ }
+
+ let deltaX = sock.rQshift16();
+ let deltaY = sock.rQshift16();
+ display.copyImage(deltaX, deltaY, x, y, width, height);
+
+ return true;
+ }
+}
diff --git a/core/decoders/hextile.js b/core/decoders/hextile.js
new file mode 100644
index 0000000..91141aa
--- /dev/null
+++ b/core/decoders/hextile.js
@@ -0,0 +1,139 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import * as Log from '../util/logging.js';
+
+export default class HextileDecoder {
+ constructor() {
+ this._tiles = 0;
+ this._lastsubencoding = 0;
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._tiles === 0) {
+ this._tiles_x = Math.ceil(width / 16);
+ this._tiles_y = Math.ceil(height / 16);
+ this._total_tiles = this._tiles_x * this._tiles_y;
+ this._tiles = this._total_tiles;
+ }
+
+ while (this._tiles > 0) {
+ let bytes = 1;
+
+ if (sock.rQwait("HEXTILE", bytes)) {
+ return false;
+ }
+
+ let rQ = sock.get_rQ();
+ let rQi = sock.get_rQi();
+
+ let subencoding = rQ[rQi]; // Peek
+ if (subencoding > 30) { // Raw
+ throw Error("Illegal hextile subencoding (subencoding: " +
+ subencoding + ")");
+ }
+
+ const curr_tile = this._total_tiles - this._tiles;
+ const tile_x = curr_tile % this._tiles_x;
+ const tile_y = Math.floor(curr_tile / this._tiles_x);
+ const tx = x + tile_x * 16;
+ const ty = y + tile_y * 16;
+ const tw = Math.min(16, (x + width) - tx);
+ const th = Math.min(16, (y + height) - ty);
+
+ // Figure out how much we are expecting
+ if (subencoding & 0x01) { // Raw
+ bytes += tw * th * 4;
+ } else {
+ if (subencoding & 0x02) { // Background
+ bytes += 4;
+ }
+ if (subencoding & 0x04) { // Foreground
+ bytes += 4;
+ }
+ if (subencoding & 0x08) { // AnySubrects
+ bytes++; // Since we aren't shifting it off
+
+ if (sock.rQwait("HEXTILE", bytes)) {
+ return false;
+ }
+
+ let subrects = rQ[rQi + bytes - 1]; // Peek
+ if (subencoding & 0x10) { // SubrectsColoured
+ bytes += subrects * (4 + 2);
+ } else {
+ bytes += subrects * 2;
+ }
+ }
+ }
+
+ if (sock.rQwait("HEXTILE", bytes)) {
+ return false;
+ }
+
+ // We know the encoding and have a whole tile
+ rQi++;
+ if (subencoding === 0) {
+ if (this._lastsubencoding & 0x01) {
+ // Weird: ignore blanks are RAW
+ Log.Debug(" Ignoring blank after RAW");
+ } else {
+ display.fillRect(tx, ty, tw, th, this._background);
+ }
+ } else if (subencoding & 0x01) { // Raw
+ display.blitImage(tx, ty, tw, th, rQ, rQi);
+ rQi += bytes - 1;
+ } else {
+ if (subencoding & 0x02) { // Background
+ this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ rQi += 4;
+ }
+ if (subencoding & 0x04) { // Foreground
+ this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ rQi += 4;
+ }
+
+ display.startTile(tx, ty, tw, th, this._background);
+ if (subencoding & 0x08) { // AnySubrects
+ let subrects = rQ[rQi];
+ rQi++;
+
+ for (let s = 0; s < subrects; s++) {
+ let color;
+ if (subencoding & 0x10) { // SubrectsColoured
+ color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ rQi += 4;
+ } else {
+ color = this._foreground;
+ }
+ const xy = rQ[rQi];
+ rQi++;
+ const sx = (xy >> 4);
+ const sy = (xy & 0x0f);
+
+ const wh = rQ[rQi];
+ rQi++;
+ const sw = (wh >> 4) + 1;
+ const sh = (wh & 0x0f) + 1;
+
+ display.subTile(sx, sy, sw, sh, color);
+ }
+ }
+ display.finishTile();
+ }
+ sock.set_rQi(rQi);
+ this._lastsubencoding = subencoding;
+ this._tiles--;
+ }
+
+ return true;
+ }
+}
diff --git a/core/decoders/raw.js b/core/decoders/raw.js
new file mode 100644
index 0000000..282ab45
--- /dev/null
+++ b/core/decoders/raw.js
@@ -0,0 +1,58 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+export default class RawDecoder {
+ constructor() {
+ this._lines = 0;
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._lines === 0) {
+ this._lines = height;
+ }
+
+ const pixelSize = depth == 8 ? 1 : 4;
+ const bytesPerLine = width * pixelSize;
+
+ if (sock.rQwait("RAW", bytesPerLine)) {
+ return false;
+ }
+
+ const cur_y = y + (height - this._lines);
+ const curr_height = Math.min(this._lines,
+ Math.floor(sock.rQlen() / bytesPerLine));
+ let data = sock.get_rQ();
+ let index = sock.get_rQi();
+
+ // Convert data if needed
+ if (depth == 8) {
+ const pixels = width * curr_height
+ const newdata = new Uint8Array(pixels * 4);
+ for (let i = 0; i < pixels; i++) {
+ newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
+ newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
+ newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
+ newdata[i * 4 + 4] = 0;
+ }
+ data = newdata;
+ index = 0;
+ }
+
+ display.blitImage(x, cur_y, width, curr_height, data, index);
+ sock.rQskipBytes(curr_height * bytesPerLine);
+ this._lines -= curr_height;
+ if (this._lines > 0) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/core/decoders/rre.js b/core/decoders/rre.js
new file mode 100644
index 0000000..57414a0
--- /dev/null
+++ b/core/decoders/rre.js
@@ -0,0 +1,46 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+export default class RREDecoder {
+ constructor() {
+ this._subrects = 0;
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._subrects === 0) {
+ if (sock.rQwait("RRE", 4 + 4)) {
+ return false;
+ }
+
+ this._subrects = sock.rQshift32();
+
+ let color = sock.rQshiftBytes(4); // Background
+ display.fillRect(x, y, width, height, color);
+ }
+
+ while (this._subrects > 0) {
+ if (sock.rQwait("RRE", 4 + 8)) {
+ return false;
+ }
+
+ let color = sock.rQshiftBytes(4);
+ let sx = sock.rQshift16();
+ let sy = sock.rQshift16();
+ let swidth = sock.rQshift16();
+ let sheight = sock.rQshift16();
+ display.fillRect(x + sx, y + sy, swidth, sheight, color);
+
+ this._subrects--;
+ }
+
+ return true;
+ }
+}
diff --git a/core/decoders/tight.js b/core/decoders/tight.js
new file mode 100644
index 0000000..fce341e
--- /dev/null
+++ b/core/decoders/tight.js
@@ -0,0 +1,319 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import * as Log from '../util/logging.js';
+import Inflator from "../inflator.js";
+
+export default class TightDecoder {
+ constructor() {
+ this._ctl = null;
+ this._filter = null;
+ this._numColors = 0;
+ this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
+ this._len = 0;
+
+ this._zlibs = [];
+ for (let i = 0; i < 4; i++) {
+ this._zlibs[i] = new Inflator();
+ }
+ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._ctl === null) {
+ if (sock.rQwait("TIGHT compression-control", 1)) {
+ return false;
+ }
+
+ this._ctl = sock.rQshift8();
+
+ // Reset streams if the server requests it
+ for (let i = 0; i < 4; i++) {
+ if ((this._ctl >> i) & 1) {
+ this._zlibs[i].reset();
+ Log.Info("Reset zlib stream " + i);
+ }
+ }
+
+ // Figure out filter
+ this._ctl = this._ctl >> 4;
+ }
+
+ let ret;
+
+ if (this._ctl === 0x08) {
+ ret = this._fillRect(x, y, width, height,
+ sock, display, depth);
+ } else if (this._ctl === 0x09) {
+ ret = this._jpegRect(x, y, width, height,
+ sock, display, depth);
+ } else if (this._ctl === 0x0A) {
+ ret = this._pngRect(x, y, width, height,
+ sock, display, depth);
+ } else if ((this._ctl & 0x80) == 0) {
+ ret = this._basicRect(this._ctl, x, y, width, height,
+ sock, display, depth);
+ } else {
+ throw Error("Illegal tight compression received (ctl: " +
+ this._ctl + ")");
+ }
+
+ if (ret) {
+ this._ctl = null;
+ }
+
+ return ret;
+ }
+
+ _fillRect(x, y, width, height, sock, display, depth) {
+ if (sock.rQwait("TIGHT", 3)) {
+ return false;
+ }
+
+ const rQi = sock.get_rQi();
+ const rQ = sock.rQwhole();
+
+ display.fillRect(x, y, width, height,
+ [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
+ sock.rQskipBytes(3);
+
+ return true;
+ }
+
+ _jpegRect(x, y, width, height, sock, display, depth) {
+ let data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ display.imageRect(x, y, "image/jpeg", data);
+
+ return true;
+ }
+
+ _pngRect(x, y, width, height, sock, display, depth) {
+ throw Error("PNG received in standard Tight rect");
+ }
+
+ _basicRect(ctl, x, y, width, height, sock, display, depth) {
+ if (this._filter === null) {
+ if (ctl & 0x4) {
+ if (sock.rQwait("TIGHT", 1)) {
+ return false;
+ }
+
+ this._filter = sock.rQshift8();
+ } else {
+ // Implicit CopyFilter
+ this._filter = 0;
+ }
+ }
+
+ let streamId = ctl & 0x3;
+
+ let ret;
+
+ switch (this._filter) {
+ case 0: // CopyFilter
+ ret = this._copyFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ case 1: // PaletteFilter
+ ret = this._paletteFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ case 2: // GradientFilter
+ ret = this._gradientFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ default:
+ throw Error("Illegal tight filter received (ctl: " +
+ this._filter + ")");
+ }
+
+ if (ret) {
+ this._filter = null;
+ }
+
+ return ret;
+ }
+
+ _copyFilter(streamId, x, y, width, height, sock, display, depth) {
+ const uncompressedSize = width * height * 3;
+ let data;
+
+ if (uncompressedSize < 12) {
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
+ return false;
+ }
+
+ data = sock.rQshiftBytes(uncompressedSize);
+ } else {
+ data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
+ if (data.length != uncompressedSize) {
+ throw Error("Incomplete zlib block");
+ }
+ }
+
+ display.blitRgbImage(x, y, width, height, data, 0, false);
+
+ return true;
+ }
+
+ _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
+ if (this._numColors === 0) {
+ if (sock.rQwait("TIGHT palette", 1)) {
+ return false;
+ }
+
+ const numColors = sock.rQpeek8() + 1;
+ const paletteSize = numColors * 3;
+
+ if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
+ return false;
+ }
+
+ this._numColors = numColors;
+ sock.rQskipBytes(1);
+
+ sock.rQshiftTo(this._palette, paletteSize);
+ }
+
+ const bpp = (this._numColors <= 2) ? 1 : 8;
+ const rowSize = Math.floor((width * bpp + 7) / 8);
+ const uncompressedSize = rowSize * height;
+
+ let data;
+
+ if (uncompressedSize < 12) {
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
+ return false;
+ }
+
+ data = sock.rQshiftBytes(uncompressedSize);
+ } else {
+ data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
+ if (data.length != uncompressedSize) {
+ throw Error("Incomplete zlib block");
+ }
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ if (this._numColors == 2) {
+ this._monoRect(x, y, width, height, data, this._palette, display);
+ } else {
+ this._paletteRect(x, y, width, height, data, this._palette, display);
+ }
+
+ this._numColors = 0;
+
+ return true;
+ }
+
+ _monoRect(x, y, width, height, data, palette, display) {
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ const dest = this._getScratchBuffer(width * height * 4);
+ const w = Math.floor((width + 7) / 8);
+ const w1 = Math.floor(width / 8);
+
+ for (let y = 0; y < height; y++) {
+ let dp, sp, x;
+ for (x = 0; x < w1; x++) {
+ for (let 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;
+ }
+ }
+
+ for (let 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;
+ }
+ }
+
+ display.blitRgbxImage(x, y, width, height, dest, 0, false);
+ }
+
+ _paletteRect(x, y, width, height, data, palette, display) {
+ // Convert indexed (palette based) image data to RGB
+ const dest = this._getScratchBuffer(width * height * 4);
+ const total = width * height * 4;
+ for (let i = 0, j = 0; i < total; i += 4, j++) {
+ const sp = data[j] * 3;
+ dest[i] = palette[sp];
+ dest[i + 1] = palette[sp + 1];
+ dest[i + 2] = palette[sp + 2];
+ dest[i + 3] = 255;
+ }
+
+ display.blitRgbxImage(x, y, width, height, dest, 0, false);
+ }
+
+ _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
+ throw Error("Gradient filter not implemented");
+ }
+
+ _readData(sock) {
+ if (this._len === 0) {
+ if (sock.rQwait("TIGHT", 3)) {
+ return null;
+ }
+
+ let byte;
+
+ byte = sock.rQshift8();
+ this._len = byte & 0x7f;
+ if (byte & 0x80) {
+ byte = sock.rQshift8();
+ this._len |= (byte & 0x7f) << 7;
+ if (byte & 0x80) {
+ byte = sock.rQshift8();
+ this._len |= byte << 14;
+ }
+ }
+ }
+
+ if (sock.rQwait("TIGHT", this._len)) {
+ return null;
+ }
+
+ let data = sock.rQshiftBytes(this._len);
+ this._len = 0;
+
+ return data;
+ }
+
+ _getScratchBuffer(size) {
+ if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
+ this._scratchBuffer = new Uint8Array(size);
+ }
+ return this._scratchBuffer;
+ }
+}
diff --git a/core/decoders/tightpng.js b/core/decoders/tightpng.js
new file mode 100644
index 0000000..2d91746
--- /dev/null
+++ b/core/decoders/tightpng.js
@@ -0,0 +1,29 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import TightDecoder from './tight.js';
+
+export default class TightPNGDecoder extends TightDecoder {
+ _pngRect(x, y, width, height, sock, display, depth) {
+ let data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ display.imageRect(x, y, "image/png", data);
+
+ return true;
+ }
+
+ _basicRect(ctl, x, y, width, height, sock, display, depth) {
+ throw Error("BasicCompression received in TightPNG rect");
+ }
+}
diff --git a/core/rfb.js b/core/rfb.js
index 1bfb282..42d682b 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -7,8 +7,6 @@
*
* See README.md for usage and integration instructions.
*
- * TIGHT decoder portion:
- * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
*/
import * as Log from './util/logging.js';
@@ -23,10 +21,16 @@ import Websock from "./websock.js";
import DES from "./des.js";
import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
-import Inflator from "./inflator.js";
import { encodings } from "./encodings.js";
import "./util/polyfill.js";
+import RawDecoder from "./decoders/raw.js";
+import CopyRectDecoder from "./decoders/copyrect.js";
+import RREDecoder from "./decoders/rre.js";
+import HextileDecoder from "./decoders/hextile.js";
+import TightDecoder from "./decoders/tight.js";
+import TightPNGDecoder from "./decoders/tightpng.js";
+
// How many seconds to wait for a disconnect to finish
const DISCONNECT_TIMEOUT = 3;
@@ -92,33 +96,17 @@ export default class RFB extends EventTargetMixin {
this._resizeTimeout = null; // resize rate limiting
// Decoder states
- this._encHandlers = {};
+ this._decoders = {};
this._FBU = {
rects: 0,
- subrects: 0, // RRE and HEXTILE
- lines: 0, // RAW
- tiles: 0, // HEXTILE
- bytes: 0,
x: 0,
y: 0,
width: 0,
height: 0,
- encoding: 0,
- subencoding: -1,
- background: null,
- zlibs: [] // TIGHT zlib streams
+ encoding: null,
};
- for (let i = 0; i < 4; i++) {
- this._FBU.zlibs[i] = new Inflator();
- }
-
- this._destBuff = null;
- this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
-
- this._rre_chunk_sz = 100;
-
// Mouse state
this._mouse_buttonMask = 0;
this._mouse_arr = [];
@@ -155,13 +143,13 @@ export default class RFB extends EventTargetMixin {
this._cursor = new Cursor();
- // populate encHandlers with bound versions
- this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
- this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
- this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this);
- this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this);
- this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this, false);
- this._encHandlers[encodings.encodingTightPNG] = RFB.encodingHandlers.TIGHT.bind(this, true);
+ // populate decoder array with objects
+ this._decoders[encodings.encodingRaw] = new RawDecoder();
+ this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
+ this._decoders[encodings.encodingRRE] = new RREDecoder();
+ this._decoders[encodings.encodingHextile] = new HextileDecoder();
+ this._decoders[encodings.encodingTight] = new TightDecoder();
+ this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
// NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception
@@ -1446,7 +1434,6 @@ export default class RFB extends EventTargetMixin {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
this._sock.rQskip8(); // Padding
this._FBU.rects = this._sock.rQshift16();
- this._FBU.bytes = 0;
// Make sure the previous frame is fully rendered first
// to avoid building up an excessive queue
@@ -1458,10 +1445,7 @@ export default class RFB extends EventTargetMixin {
}
while (this._FBU.rects > 0) {
- if (this._rfb_connection_state !== 'connected') { return false; }
-
- if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
- if (this._FBU.bytes === 0) {
+ if (this._FBU.encoding === null) {
if (this._sock.rQwait("rect header", 12)) { return false; }
/* New FramebufferUpdate */
@@ -1479,6 +1463,7 @@ export default class RFB extends EventTargetMixin {
}
this._FBU.rects--;
+ this._FBU.encoding = null;
}
this._display.flip();
@@ -1528,8 +1513,8 @@ export default class RFB extends EventTargetMixin {
const pixelslength = w * h * 4;
const masklength = Math.floor((w + 7) / 8) * h;
- this._FBU.bytes = pixelslength + masklength;
- if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) {
+ let bytes = pixelslength + masklength;
+ if (this._sock.rQwait("cursor encoding", bytes)) {
return false;
}
@@ -1537,21 +1522,18 @@ export default class RFB extends EventTargetMixin {
this._sock.rQshiftBytes(masklength),
x, y, w, h);
- this._FBU.bytes = 0;
-
return true;
}
_handleExtendedDesktopSize() {
- this._FBU.bytes = 4;
- if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) {
+ if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
return false;
}
const number_of_screens = this._sock.rQpeek8();
- this._FBU.bytes += number_of_screens * 16;
- if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) {
+ let bytes = 4 + (number_of_screens * 16);
+ if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
return false;
}
@@ -1615,20 +1597,26 @@ export default class RFB extends EventTargetMixin {
this._resize(this._FBU.width, this._FBU.height);
}
- this._FBU.bytes = 0;
-
return true;
}
_handleDataRect() {
- let handler = this._encHandlers[this._FBU.encoding];
- if (!handler) {
+ let decoder = this._decoders[this._FBU.encoding];
+ if (!decoder) {
this._fail("Unsupported encoding (encoding: " +
this._FBU.encoding + ")");
return false;
}
- return handler();
+ try {
+ return decoder.decodeRect(this._FBU.x, this._FBU.y,
+ this._FBU.width, this._FBU.height,
+ this._sock, this._display,
+ this._fb_depth);
+ } catch (err) {
+ this._fail("Error decoding rect: " + err);
+ return false;
+ }
}
_updateContinuousUpdates() {
@@ -1642,8 +1630,6 @@ export default class RFB extends EventTargetMixin {
this._fb_width = width;
this._fb_height = height;
- this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
-
this._display.resize(this._fb_width, this._fb_height);
// Adjust the visible viewport based on the new dimensions
@@ -1983,503 +1969,3 @@ RFB.messages = {
sock.flush();
}
};
-
-
-RFB.encodingHandlers = {
- RAW() {
- if (this._FBU.lines === 0) {
- this._FBU.lines = this._FBU.height;
- }
-
- const pixelSize = this._fb_depth == 8 ? 1 : 4;
- this._FBU.bytes = this._FBU.width * pixelSize; // at least a line
- if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
- const cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
- const curr_height = Math.min(this._FBU.lines,
- Math.floor(this._sock.rQlen() / (this._FBU.width * pixelSize)));
- let data = this._sock.get_rQ();
- let index = this._sock.get_rQi();
- if (this._fb_depth == 8) {
- const pixels = this._FBU.width * curr_height
- const newdata = new Uint8Array(pixels * 4);
- for (let i = 0; i < pixels; i++) {
- newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
- newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
- newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
- newdata[i * 4 + 4] = 0;
- }
- data = newdata;
- index = 0;
- }
- this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
- curr_height, data, index);
- this._sock.rQskipBytes(this._FBU.width * curr_height * pixelSize);
- this._FBU.lines -= curr_height;
-
- if (this._FBU.lines > 0) {
- this._FBU.bytes = this._FBU.width * pixelSize; // At least another line
- } else {
- this._FBU.bytes = 0;
- }
-
- return true;
- },
-
- COPYRECT() {
- this._FBU.bytes = 4;
- if (this._sock.rQwait("COPYRECT", 4)) { return false; }
- this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
- this._FBU.x, this._FBU.y, this._FBU.width,
- this._FBU.height);
-
- this._FBU.bytes = 0;
- return true;
- },
-
- RRE() {
- let color;
- if (this._FBU.subrects === 0) {
- this._FBU.bytes = 4 + 4;
- if (this._sock.rQwait("RRE", 4 + 4)) { return false; }
- this._FBU.subrects = this._sock.rQshift32();
- color = this._sock.rQshiftBytes(4); // Background
- this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
- }
-
- while (this._FBU.subrects > 0 && this._sock.rQlen() >= (4 + 8)) {
- color = this._sock.rQshiftBytes(4);
- const x = this._sock.rQshift16();
- const y = this._sock.rQshift16();
- const width = this._sock.rQshift16();
- const height = this._sock.rQshift16();
- this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
- this._FBU.subrects--;
- }
-
- if (this._FBU.subrects > 0) {
- const chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
- this._FBU.bytes = (4 + 8) * chunk;
- } else {
- this._FBU.bytes = 0;
- }
-
- return true;
- },
-
- HEXTILE() {
- const rQ = this._sock.get_rQ();
- let rQi = this._sock.get_rQi();
-
- if (this._FBU.tiles === 0) {
- this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
- this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
- this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
- this._FBU.tiles = this._FBU.total_tiles;
- }
-
- while (this._FBU.tiles > 0) {
- this._FBU.bytes = 1;
- if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
- const subencoding = rQ[rQi]; // Peek
- if (subencoding > 30) { // Raw
- this._fail("Illegal hextile subencoding (subencoding: " +
- subencoding + ")");
- return false;
- }
-
- let subrects = 0;
- const curr_tile = this._FBU.total_tiles - this._FBU.tiles;
- const tile_x = curr_tile % this._FBU.tiles_x;
- const tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
- const x = this._FBU.x + tile_x * 16;
- const y = this._FBU.y + tile_y * 16;
- const w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
- const h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
-
- // Figure out how much we are expecting
- if (subencoding & 0x01) { // Raw
- this._FBU.bytes += w * h * 4;
- } else {
- if (subencoding & 0x02) { // Background
- this._FBU.bytes += 4;
- }
- if (subencoding & 0x04) { // Foreground
- this._FBU.bytes += 4;
- }
- if (subencoding & 0x08) { // AnySubrects
- this._FBU.bytes++; // Since we aren't shifting it off
- if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
- subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
- if (subencoding & 0x10) { // SubrectsColoured
- this._FBU.bytes += subrects * (4 + 2);
- } else {
- this._FBU.bytes += subrects * 2;
- }
- }
- }
-
- if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
-
- // We know the encoding and have a whole tile
- this._FBU.subencoding = rQ[rQi];
- rQi++;
- if (this._FBU.subencoding === 0) {
- if (this._FBU.lastsubencoding & 0x01) {
- // Weird: ignore blanks are RAW
- Log.Debug(" Ignoring blank after RAW");
- } else {
- this._display.fillRect(x, y, w, h, this._FBU.background);
- }
- } else if (this._FBU.subencoding & 0x01) { // Raw
- this._display.blitImage(x, y, w, h, rQ, rQi);
- rQi += this._FBU.bytes - 1;
- } else {
- if (this._FBU.subencoding & 0x02) { // Background
- this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
- rQi += 4;
- }
- if (this._FBU.subencoding & 0x04) { // Foreground
- this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
- rQi += 4;
- }
-
- this._display.startTile(x, y, w, h, this._FBU.background);
- if (this._FBU.subencoding & 0x08) { // AnySubrects
- subrects = rQ[rQi];
- rQi++;
-
- for (let s = 0; s < subrects; s++) {
- let color;
- if (this._FBU.subencoding & 0x10) { // SubrectsColoured
- color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
- rQi += 4;
- } else {
- color = this._FBU.foreground;
- }
- const xy = rQ[rQi];
- rQi++;
- const sx = (xy >> 4);
- const sy = (xy & 0x0f);
-
- const wh = rQ[rQi];
- rQi++;
- const sw = (wh >> 4) + 1;
- const sh = (wh & 0x0f) + 1;
-
- this._display.subTile(sx, sy, sw, sh, color);
- }
- }
- this._display.finishTile();
- }
- this._sock.set_rQi(rQi);
- this._FBU.lastsubencoding = this._FBU.subencoding;
- this._FBU.bytes = 0;
- this._FBU.tiles--;
- }
-
- return true;
- },
-
- TIGHT(isTightPNG) {
- this._FBU.bytes = 1; // compression-control byte
- if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
-
- let resetStreams = 0;
- let streamId = -1;
- const decompress = (data, expected) => {
- for (let i = 0; i < 4; i++) {
- if ((resetStreams >> i) & 1) {
- this._FBU.zlibs[i].reset();
- Log.Info("Reset zlib stream " + i);
- }
- }
-
- //const uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
- const uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
- /*if (uncompressed.status !== 0) {
- Log.Error("Invalid data in zlib stream");
- }*/
-
- //return uncompressed.data;
- return uncompressed;
- };
-
- const indexedToRGBX2Color = (data, palette, width, height) => {
- // Convert indexed (palette based) image data to RGB
- // TODO: reduce number of calculations inside loop
- const dest = this._destBuff;
- const w = Math.floor((width + 7) / 8);
- const w1 = Math.floor(width / 8);
-
- /*for (let y = 0; y < height; y++) {
- let b, x, dp, sp;
- const yoffset = y * width;
- const ybitoffset = y * w;
- let 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];
- }
- }
-
- 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 (let y = 0; y < height; y++) {
- let dp, sp, x;
- for (x = 0; x < w1; x++) {
- for (let 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;
- }
- }
-
- for (let 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;
- };
-
- const indexedToRGBX = (data, palette, width, height) => {
- // Convert indexed (palette based) image data to RGB
- const dest = this._destBuff;
- const total = width * height * 4;
- for (let i = 0, j = 0; i < total; i += 4, j++) {
- const 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;
- };
-
- const rQi = this._sock.get_rQi();
- const rQ = this._sock.rQwhole();
- let cmode, data;
- let cl_header, cl_data;
-
- const handlePalette = () => {
- const numColors = rQ[rQi + 2] + 1;
- const paletteSize = numColors * 3;
- this._FBU.bytes += paletteSize;
- if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
-
- const bpp = (numColors <= 2) ? 1 : 8;
- const rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
- let raw = false;
- if (rowSize * this._FBU.height < 12) {
- raw = true;
- cl_header = 0;
- cl_data = rowSize * this._FBU.height;
- //clength = [0, rowSize * this._FBU.height];
- } else {
- // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
- const 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 += 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);
- //const palette = this._sock.rQshiftBytes(paletteSize);
- this._sock.rQshiftTo(this._paletteBuff, paletteSize);
- this._sock.rQskipBytes(cl_header);
-
- if (raw) {
- data = this._sock.rQshiftBytes(cl_data);
- } else {
- data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
- }
-
- // Convert indexed (palette based) image data to RGB
- let rgbx;
- if (numColors == 2) {
- rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
- } 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);
-
-
- return true;
- };
-
- const handleCopy = () => {
- let raw = false;
- const uncompressedSize = this._FBU.width * this._FBU.height * 3;
- if (uncompressedSize < 12) {
- raw = true;
- cl_header = 0;
- cl_data = uncompressedSize;
- } else {
- // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
- const 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;
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
-
- // Shift ctl, clength off
- this._sock.rQshiftBytes(1 + cl_header);
-
- if (raw) {
- data = this._sock.rQshiftBytes(cl_data);
- } else {
- data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
- }
-
- this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
-
- return true;
- };
-
- let ctl = this._sock.rQpeek8();
-
- // Keep tight reset bits
- resetStreams = ctl & 0xF;
-
- // Figure out filter
- ctl = ctl >> 4;
- streamId = ctl & 0x3;
-
- if (ctl === 0x08) cmode = "fill";
- else if (ctl === 0x09) cmode = "jpeg";
- else if (ctl === 0x0A) cmode = "png";
- else if (ctl & 0x04) cmode = "filter";
- else if (ctl < 0x04) cmode = "copy";
- else return this._fail("Illegal tight compression received (ctl: " +
- ctl + ")");
-
- if (isTightPNG && (ctl < 0x08)) {
- return this._fail("BasicCompression received in TightPNG rect");
- }
- if (!isTightPNG && (ctl === 0x0A)) {
- return this._fail("PNG received in standard Tight rect");
- }
-
- switch (cmode) {
- // fill use depth because TPIXELs drop the padding byte
- case "fill": // TPIXEL
- this._FBU.bytes += 3;
- break;
- case "jpeg": // max clength
- this._FBU.bytes += 3;
- break;
- case "png": // max clength
- this._FBU.bytes += 3;
- break;
- case "filter": // filter id + num colors if palette
- this._FBU.bytes += 2;
- break;
- case "copy":
- break;
- }
-
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
-
- // Determine FBU.bytes
- let cl_offset, filterId;
- switch (cmode) {
- case "fill":
- // 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":
- // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
- 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 + cl_header); // shift off clt + compact length
- data = this._sock.rQshiftBytes(cl_data);
- this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data);
- break;
- case "filter":
- filterId = rQ[rQi + 1];
- if (filterId === 1) {
- if (!handlePalette()) { return false; }
- } else {
- // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
- // Filter 2, Gradient is valid but not use if jpeg is enabled
- this._fail("Unsupported tight subencoding received " +
- "(filter: " + filterId + ")");
- }
- break;
- case "copy":
- if (!handleCopy()) { return false; }
- break;
- }
-
-
- this._FBU.bytes = 0;
-
- return true;
- },
-}