Skip to content

Commit

Permalink
Add Zlib encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
markpeek committed Aug 16, 2024
1 parent 96c76f7 commit c6c8e5e
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ profits such as:
RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,
UltraVNC's MSLogonII
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,
ZRLE, JPEG
ZRLE, JPEG, Zlib
* Supports scaling, clipping and resizing the desktop
* Local cursor rendering
* Clipboard copy/paste with full Unicode support
Expand Down
51 changes: 51 additions & 0 deletions core/decoders/zlib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2024 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/

import Inflator from "../inflator.js";

export default class ZlibDecoder {
constructor() {
this._zlib = new Inflator();
this._length = 0;
}

decodeRect(x, y, width, height, sock, display, depth) {
if ((width === 0) || (height === 0)) {
return true;
}

if (this._length === 0) {
if (sock.rQwait("ZLIB", 4)) {
return false;
}

this._length = sock.rQshift32();
}

if (sock.rQwait("ZLIB", this._length)) {
return false;
}

let data = new Uint8Array(sock.rQshiftBytes(this._length, false));
this._length = 0;

this._zlib.setInput(data);
data = this._zlib.inflate(width * height * 4);
this._zlib.setInput(null);

// Max sure the image is fully opaque
for (let i = 0; i < width * height; i++) {
data[i * 4 + 3] = 255;
}

display.blitImage(x, y, width, height, data, 0);

return true;
}
}
2 changes: 2 additions & 0 deletions core/encodings.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const encodings = {
encodingCopyRect: 1,
encodingRRE: 2,
encodingHextile: 5,
encodingZlib: 6,
encodingTight: 7,
encodingZRLE: 16,
encodingTightPNG: -260,
Expand Down Expand Up @@ -40,6 +41,7 @@ export function encodingName(num) {
case encodings.encodingCopyRect: return "CopyRect";
case encodings.encodingRRE: return "RRE";
case encodings.encodingHextile: return "Hextile";
case encodings.encodingZlib: return "Zlib";
case encodings.encodingTight: return "Tight";
case encodings.encodingZRLE: return "ZRLE";
case encodings.encodingTightPNG: return "TightPNG";
Expand Down
3 changes: 3 additions & 0 deletions core/rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ 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 ZlibDecoder from './decoders/zlib.js';
import TightDecoder from "./decoders/tight.js";
import TightPNGDecoder from "./decoders/tightpng.js";
import ZRLEDecoder from "./decoders/zrle.js";
Expand Down Expand Up @@ -244,6 +245,7 @@ export default class RFB extends EventTargetMixin {
this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
this._decoders[encodings.encodingRRE] = new RREDecoder();
this._decoders[encodings.encodingHextile] = new HextileDecoder();
this._decoders[encodings.encodingZlib] = new ZlibDecoder();
this._decoders[encodings.encodingTight] = new TightDecoder();
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
Expand Down Expand Up @@ -2121,6 +2123,7 @@ export default class RFB extends EventTargetMixin {
encs.push(encodings.encodingJPEG);
encs.push(encodings.encodingHextile);
encs.push(encodings.encodingRRE);
encs.push(encodings.encodingZlib);
}
encs.push(encodings.encodingRaw);

Expand Down
84 changes: 84 additions & 0 deletions tests/test.zlib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Websock from '../core/websock.js';
import Display from '../core/display.js';

import ZlibDecoder from '../core/decoders/zlib.js';

import FakeWebSocket from './fake.websocket.js';

function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;

sock = new Websock;
sock.open("ws://example.com");

sock.on('message', () => {
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});

// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}

display.flip();

return done;
}

describe('Zlib Decoder', function () {
let decoder;
let display;

before(FakeWebSocket.replace);
after(FakeWebSocket.restore);

beforeEach(function () {
decoder = new ZlibDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});

it('should handle the Zlib encoding', function () {
let done;

let zlibData = new Uint8Array([
0x00, 0x00, 0x00, 0x23, /* length */
0x78, 0x01, 0xfa, 0xcf, 0x00, 0x04, 0xff, 0x61, 0x04, 0x90, 0x01, 0x41, 0x50, 0xc1, 0xff, 0x0c,
0xef, 0x40, 0x02, 0xef, 0xfe, 0x33, 0xac, 0x02, 0xe2, 0xd5, 0x40, 0x8c, 0xce, 0x07, 0x00, 0x00,
0x00, 0xff, 0xff,
]);
done = testDecodeRect(decoder, 0, 0, 4, 4, zlibData, display, 24);
expect(done).to.be.true;

let targetData = new Uint8ClampedArray([
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
]);

expect(display).to.have.displayed(targetData);
});

it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
display.fillRect(2, 0, 2, 2, [0x00, 0xff, 0x00]);
display.fillRect(0, 2, 2, 2, [0x00, 0xff, 0x00]);

let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);

let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);

expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
});

0 comments on commit c6c8e5e

Please sign in to comment.