Skip to content

Commit c6c8e5e

Browse files
committed
Add Zlib encoding
1 parent 96c76f7 commit c6c8e5e

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ profits such as:
6666
RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,
6767
UltraVNC's MSLogonII
6868
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,
69-
ZRLE, JPEG
69+
ZRLE, JPEG, Zlib
7070
* Supports scaling, clipping and resizing the desktop
7171
* Local cursor rendering
7272
* Clipboard copy/paste with full Unicode support

core/decoders/zlib.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* noVNC: HTML5 VNC client
3+
* Copyright (C) 2024 The noVNC Authors
4+
* Licensed under MPL 2.0 (see LICENSE.txt)
5+
*
6+
* See README.md for usage and integration instructions.
7+
*
8+
*/
9+
10+
import Inflator from "../inflator.js";
11+
12+
export default class ZlibDecoder {
13+
constructor() {
14+
this._zlib = new Inflator();
15+
this._length = 0;
16+
}
17+
18+
decodeRect(x, y, width, height, sock, display, depth) {
19+
if ((width === 0) || (height === 0)) {
20+
return true;
21+
}
22+
23+
if (this._length === 0) {
24+
if (sock.rQwait("ZLIB", 4)) {
25+
return false;
26+
}
27+
28+
this._length = sock.rQshift32();
29+
}
30+
31+
if (sock.rQwait("ZLIB", this._length)) {
32+
return false;
33+
}
34+
35+
let data = new Uint8Array(sock.rQshiftBytes(this._length, false));
36+
this._length = 0;
37+
38+
this._zlib.setInput(data);
39+
data = this._zlib.inflate(width * height * 4);
40+
this._zlib.setInput(null);
41+
42+
// Max sure the image is fully opaque
43+
for (let i = 0; i < width * height; i++) {
44+
data[i * 4 + 3] = 255;
45+
}
46+
47+
display.blitImage(x, y, width, height, data, 0);
48+
49+
return true;
50+
}
51+
}

core/encodings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const encodings = {
1111
encodingCopyRect: 1,
1212
encodingRRE: 2,
1313
encodingHextile: 5,
14+
encodingZlib: 6,
1415
encodingTight: 7,
1516
encodingZRLE: 16,
1617
encodingTightPNG: -260,
@@ -40,6 +41,7 @@ export function encodingName(num) {
4041
case encodings.encodingCopyRect: return "CopyRect";
4142
case encodings.encodingRRE: return "RRE";
4243
case encodings.encodingHextile: return "Hextile";
44+
case encodings.encodingZlib: return "Zlib";
4345
case encodings.encodingTight: return "Tight";
4446
case encodings.encodingZRLE: return "ZRLE";
4547
case encodings.encodingTightPNG: return "TightPNG";

core/rfb.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import RawDecoder from "./decoders/raw.js";
3131
import CopyRectDecoder from "./decoders/copyrect.js";
3232
import RREDecoder from "./decoders/rre.js";
3333
import HextileDecoder from "./decoders/hextile.js";
34+
import ZlibDecoder from './decoders/zlib.js';
3435
import TightDecoder from "./decoders/tight.js";
3536
import TightPNGDecoder from "./decoders/tightpng.js";
3637
import ZRLEDecoder from "./decoders/zrle.js";
@@ -244,6 +245,7 @@ export default class RFB extends EventTargetMixin {
244245
this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
245246
this._decoders[encodings.encodingRRE] = new RREDecoder();
246247
this._decoders[encodings.encodingHextile] = new HextileDecoder();
248+
this._decoders[encodings.encodingZlib] = new ZlibDecoder();
247249
this._decoders[encodings.encodingTight] = new TightDecoder();
248250
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
249251
this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
@@ -2121,6 +2123,7 @@ export default class RFB extends EventTargetMixin {
21212123
encs.push(encodings.encodingJPEG);
21222124
encs.push(encodings.encodingHextile);
21232125
encs.push(encodings.encodingRRE);
2126+
encs.push(encodings.encodingZlib);
21242127
}
21252128
encs.push(encodings.encodingRaw);
21262129

tests/test.zlib.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import Websock from '../core/websock.js';
2+
import Display from '../core/display.js';
3+
4+
import ZlibDecoder from '../core/decoders/zlib.js';
5+
6+
import FakeWebSocket from './fake.websocket.js';
7+
8+
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
9+
let sock;
10+
let done = false;
11+
12+
sock = new Websock;
13+
sock.open("ws://example.com");
14+
15+
sock.on('message', () => {
16+
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
17+
});
18+
19+
// Empty messages are filtered at multiple layers, so we need to
20+
// do a direct call
21+
if (data.length === 0) {
22+
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
23+
} else {
24+
sock._websocket._receiveData(new Uint8Array(data));
25+
}
26+
27+
display.flip();
28+
29+
return done;
30+
}
31+
32+
describe('Zlib Decoder', function () {
33+
let decoder;
34+
let display;
35+
36+
before(FakeWebSocket.replace);
37+
after(FakeWebSocket.restore);
38+
39+
beforeEach(function () {
40+
decoder = new ZlibDecoder();
41+
display = new Display(document.createElement('canvas'));
42+
display.resize(4, 4);
43+
});
44+
45+
it('should handle the Zlib encoding', function () {
46+
let done;
47+
48+
let zlibData = new Uint8Array([
49+
0x00, 0x00, 0x00, 0x23, /* length */
50+
0x78, 0x01, 0xfa, 0xcf, 0x00, 0x04, 0xff, 0x61, 0x04, 0x90, 0x01, 0x41, 0x50, 0xc1, 0xff, 0x0c,
51+
0xef, 0x40, 0x02, 0xef, 0xfe, 0x33, 0xac, 0x02, 0xe2, 0xd5, 0x40, 0x8c, 0xce, 0x07, 0x00, 0x00,
52+
0x00, 0xff, 0xff,
53+
]);
54+
done = testDecodeRect(decoder, 0, 0, 4, 4, zlibData, display, 24);
55+
expect(done).to.be.true;
56+
57+
let targetData = new Uint8ClampedArray([
58+
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
59+
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
60+
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
61+
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
62+
]);
63+
64+
expect(display).to.have.displayed(targetData);
65+
});
66+
67+
it('should handle empty rects', function () {
68+
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
69+
display.fillRect(2, 0, 2, 2, [0x00, 0xff, 0x00]);
70+
display.fillRect(0, 2, 2, 2, [0x00, 0xff, 0x00]);
71+
72+
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
73+
74+
let targetData = new Uint8Array([
75+
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
76+
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
77+
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
78+
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
79+
]);
80+
81+
expect(done).to.be.true;
82+
expect(display).to.have.displayed(targetData);
83+
});
84+
});

0 commit comments

Comments
 (0)