rebased tests from upstream, added readme. (#37)

Co-authored-by: matt <matt@kasmweb.com>
This commit is contained in:
Matt McClaskey 2022-10-11 15:41:02 -04:00 committed by GitHub
parent 79a7dd0ccf
commit 08233e6926
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1535 additions and 419 deletions

43
tests/README.md Normal file
View File

@ -0,0 +1,43 @@
# KasmVNC Client Tests
The page `/tests/vnc_playback.html` can be used to playback KasmVNC session recordings. The playbacks can be ran in realtime or as fast as possible for performance testing.
## Creating new recordings
In order to create a new recording, you will need to disable KasmVNC's built-in web server, enable the legacy VNC TCP port, and disable authentication.
```bash
sudo apt-get install websockify
vncserver -noWebsocket -disableBasicAuth
websockify --web /usr/share/kasmvnc/www --record=/home/ubuntu/record.bin 8444 localhost:5901
```
Websockify automatically adds a number to the end of the filename, so the above example might be record.bin.8. After you are finished recording, Ctrl+C the running websockify process and mv the file to the noVNC www directory.
```bash
sudo mkdir /usr/share/kasmvnc/www/recordings
mv /home/ubuntu/record.bin.8 /usr/share/kasmvnc/www/recordings
```
## Playing Back Recordings
Place recordings on the KasmVNC server in the /usr/share/kasmvnc/www/recordings directory, you may need to create this directory. Then navigate to https://server-ip:8444/tests/vnc_playback.html?data=record.bin.8 where record.bin.8 is the name of the playback file you placed in the recordings directory.
## Kasm Provided Recordings
The following recordings are used by Kasm Technologies to provide repeatable performance statisitics using different rendering settings.
| Name | Description | URL|
|------|-------|----|
| newyork.1 | Default 'Static' preset mode. | https://kasm-static-content.s3.amazonaws.com/kasmvnc/playbacktests/newyork.1 |
| losangeles.1 | Default static preset mode with webp disabled | https://kasm-static-content.s3.amazonaws.com/kasmvnc/playbacktests/losangeles.1 |
## Historical Statistics
This table keeps track of performance of pre-defined recordings, defined in the previous section, on static hardware that can be replicated over time to track performance improvements.
| File | Hardware | OS | Browser | Webpacked | Result Avg |
|------|---------|----|---------|-------|---------|
| newyork.1 | Macbook M1 Pro, 32GB RAM | macOS 12.2 | Chrome 106 | False | 2446ms |
| losangeles.1 | Macbook M1 Pro, 32GB RAM | macOS 12.2 | Chrome 106 | False | 2272ms |

View File

@ -42,6 +42,24 @@ if (window.setImmediate === undefined) {
});
}
class FakeWebSocket {
constructor() {
this.binaryType = "arraybuffer";
this.protocol = "";
this.readyState = "open";
this.onerror = () => {};
this.onmessage = () => {};
this.onopen = () => {};
}
send() {
}
close() {
}
}
export default class RecordingPlayer {
constructor(frames, disconnected) {
this._frames = frames;
@ -63,13 +81,13 @@ export default class RecordingPlayer {
run(realtime, trafficManagement) {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
this._ws = new FakeWebSocket();
this._rfb = new RFB(document.getElementById('VNC_screen'), document.getElementById('noVNC_keyboardinput'), this._ws);
this._rfb.viewOnly = true;
this._rfb.addEventListener("disconnect",
this._handleDisconnect.bind(this));
this._rfb.addEventListener("credentialsrequired",
this._handleCredentials.bind(this));
this._enablePlaybackMode();
// reset the frame index and timer
this._frameIndex = 0;
@ -79,19 +97,7 @@ export default class RecordingPlayer {
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
this._running = true;
}
// _enablePlaybackMode mocks out things not required for running playback
_enablePlaybackMode() {
const self = this;
this._rfb._sock.send = () => {};
this._rfb._sock.close = () => {};
this._rfb._sock.flush = () => {};
this._rfb._sock.open = function () {
this.init();
this._eventHandlers.open();
self._queueNextPacket();
};
this._queueNextPacket();
}
_queueNextPacket() {
@ -136,7 +142,7 @@ export default class RecordingPlayer {
const frame = this._frames[this._frameIndex];
this._rfb._sock._recvMessage({'data': frame.data});
this._ws.onmessage({'data': frame.data});
this._frameIndex++;
this._queueNextPacket();
@ -153,7 +159,7 @@ export default class RecordingPlayer {
this._rfb._display.flush();
} else {
this._running = false;
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
this._ws.onclose({code: 1000, reason: ""});
delete this._rfb;
this.onfinish((new Date()).getTime() - this._startTime);
}

113
tests/test.inflator.js Normal file
View File

@ -0,0 +1,113 @@
/* eslint-disable no-console */
const expect = chai.expect;
import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
import Inflator from "../core/inflator.js";
function _deflator(data) {
let strm = new ZStream();
deflateInit(strm, 5);
/* eslint-disable camelcase */
strm.input = data;
strm.avail_in = strm.input.length;
strm.next_in = 0;
/* eslint-enable camelcase */
let chunks = [];
let totalLen = 0;
while (strm.avail_in > 0) {
/* eslint-disable camelcase */
strm.output = new Uint8Array(1024 * 10 * 10);
strm.avail_out = strm.output.length;
strm.next_out = 0;
/* eslint-enable camelcase */
let ret = deflate(strm, Z_FULL_FLUSH);
// Check that return code is not an error
expect(ret).to.be.greaterThan(-1);
let chunk = new Uint8Array(strm.output.buffer, 0, strm.next_out);
totalLen += chunk.length;
chunks.push(chunk);
}
// Combine chunks into a single data
let outData = new Uint8Array(totalLen);
let offset = 0;
for (let i = 0; i < chunks.length; i++) {
outData.set(chunks[i], offset);
offset += chunks[i].length;
}
return outData;
}
describe('Inflate data', function () {
it('should be able to inflate messages', function () {
let inflator = new Inflator();
let text = "123asdf";
let preText = new Uint8Array(text.length);
for (let i = 0; i < preText.length; i++) {
preText[i] = text.charCodeAt(i);
}
let compText = _deflator(preText);
inflator.setInput(compText);
let inflatedText = inflator.inflate(preText.length);
expect(inflatedText).to.array.equal(preText);
});
it('should be able to inflate large messages', function () {
let inflator = new Inflator();
/* Generate a big string with random characters. Used because
repetition of letters might be deflated more effectively than
random ones. */
let text = "";
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 300000; i++) {
text += characters.charAt(Math.floor(Math.random() * characters.length));
}
let preText = new Uint8Array(text.length);
for (let i = 0; i < preText.length; i++) {
preText[i] = text.charCodeAt(i);
}
let compText = _deflator(preText);
//Check that the compressed size is expected size
expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);
inflator.setInput(compText);
let inflatedText = inflator.inflate(preText.length);
expect(inflatedText).to.array.equal(preText);
});
it('should throw an error on insufficient data', function () {
let inflator = new Inflator();
let text = "123asdf";
let preText = new Uint8Array(text.length);
for (let i = 0; i < preText.length; i++) {
preText[i] = text.charCodeAt(i);
}
let compText = _deflator(preText);
inflator.setInput(compText);
expect(() => inflator.inflate(preText.length * 2)).to.throw();
});
});

288
tests/test.jpeg.js Normal file
View File

@ -0,0 +1,288 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import JPEGDecoder from '../core/decoders/jpeg.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
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) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('JPEG Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new JPEGDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle JPEG rects', function (done) {
let data = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,
0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,
0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,
0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,
0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,
0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,
0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,
0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,
0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,
0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,
0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,
0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,
0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,
0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,
0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,
0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,
0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,
0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,
0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,
0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,
0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,
0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,
0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,
0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,
0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,
0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,
0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,
0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,
0xff, 0xd9,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 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, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Browsers have rounding errors, so we need an approximate
// comparing function
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
it('should handle JPEG rects without Huffman and quantification tables', function (done) {
let data1 = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,
0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,
0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,
0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,
0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,
0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,
0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,
0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,
0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,
0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,
0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,
0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,
0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,
0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,
0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,
0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,
0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,
0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,
0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,
0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,
0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,
0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,
0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,
0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,
0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,
0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,
0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,
0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,
0xff, 0xd9,
];
testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
let data2 = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
0x01, 0x2c, 0x00, 0x73, 0xff, 0xc0, 0x00, 0x11,
0x08, 0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11,
0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff,
0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11,
0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9, 0xf7, 0xfb,
0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8, 0x3f, 0xf0,
0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d, 0x7e, 0x6f,
0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a, 0x8f, 0xfe,
0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd, 0xa7, 0xff,
0x00, 0x10, 0x77, 0x0d, 0xff, 0x00, 0x43, 0xec,
0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,
];
testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 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, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Browsers have rounding errors, so we need an approximate
// comparing function
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
});

357
tests/test.ra2.js Normal file
View File

@ -0,0 +1,357 @@
const expect = chai.expect;
import RFB from '../core/rfb.js';
import FakeWebSocket from './fake.websocket.js';
function fakeGetRandomValues(arr) {
if (arr.length === 16) {
arr.set(new Uint8Array([
0x1c, 0x08, 0xfe, 0x21, 0x78, 0xef, 0x4e, 0xf9,
0x3f, 0x05, 0xec, 0xea, 0xd4, 0x6b, 0xa5, 0xd5,
]));
} else {
arr.set(new Uint8Array([
0xee, 0xe2, 0xf1, 0x5a, 0x3c, 0xa7, 0xbe, 0x95,
0x6f, 0x2a, 0x75, 0xfd, 0x62, 0x01, 0xcb, 0xbf,
0x43, 0x74, 0xca, 0x47, 0x4d, 0xfb, 0x0f, 0xcf,
0x3a, 0x6d, 0x55, 0x6b, 0x59, 0x3a, 0xf6, 0x87,
0xcb, 0x03, 0xb7, 0x28, 0x35, 0x7b, 0x15, 0x8e,
0xb6, 0xc8, 0x8f, 0x2d, 0x5e, 0x7b, 0x1c, 0x9a,
0x32, 0x55, 0xe7, 0x64, 0x36, 0x25, 0x7b, 0xa3,
0xe9, 0x4f, 0x6f, 0x97, 0xdc, 0xa4, 0xd4, 0x62,
0x6d, 0x7f, 0xab, 0x02, 0x6b, 0x13, 0x56, 0x69,
0xfb, 0xd0, 0xd4, 0x13, 0x76, 0xcd, 0x0d, 0xd0,
0x1f, 0xd1, 0x0c, 0x63, 0x3a, 0x34, 0x20, 0x6c,
0xbb, 0x60, 0x45, 0x82, 0x23, 0xfd, 0x7c, 0x77,
0x6d, 0xcc, 0x5e, 0xaa, 0xc3, 0x0c, 0x43, 0xb7,
0x8d, 0xc0, 0x27, 0x6e, 0xeb, 0x1d, 0x6c, 0x5f,
0xd8, 0x1c, 0x3c, 0x1c, 0x60, 0x2e, 0x82, 0x15,
0xfd, 0x2e, 0x5f, 0x3a, 0x15, 0x53, 0x14, 0x70,
0x4f, 0xe1, 0x65, 0x68, 0x35, 0x6d, 0xc7, 0x64,
0xdb, 0xdd, 0x09, 0x31, 0x4f, 0x7b, 0x6d, 0x6c,
0x77, 0x59, 0x5e, 0x1e, 0xfa, 0x4b, 0x06, 0x14,
0xbe, 0xdc, 0x9c, 0x3d, 0x7b, 0xed, 0xf3, 0x2b,
0x19, 0x26, 0x11, 0x8e, 0x3f, 0xab, 0x73, 0x9a,
0x0a, 0x3a, 0xaa, 0x85, 0x06, 0xd5, 0xca, 0x3f,
0xc3, 0xe2, 0x33, 0x7f, 0x97, 0x74, 0x98, 0x8f,
0x2f, 0xa5, 0xfc, 0x7e, 0xb1, 0x77, 0x71, 0x58,
0xf0, 0xbc, 0x04, 0x59, 0xbb, 0xb4, 0xc6, 0xcc,
0x0f, 0x06, 0xcd, 0xa2, 0xd5, 0x01, 0x2f, 0xb2,
0x22, 0x0b, 0xfc, 0x1e, 0x59, 0x9f, 0xd3, 0x4f,
0x30, 0x95, 0xc6, 0x80, 0x0f, 0x69, 0xf3, 0x4a,
0xd4, 0x36, 0xb6, 0x5a, 0x0b, 0x16, 0x0d, 0x81,
0x31, 0xb0, 0x69, 0xd4, 0x4e,
]));
}
}
async function fakeGeneratekey() {
let key = JSON.parse('{"alg":"RSA-OAEP-256","d":"B7QR2yI8sXjo8vQhJpX9odqqR\
6wIuPrTM1B1JJEKVeSrr7OYcc1FRJ52Vap9LIAU-ezigs9QDvWMxknB8motLnG69Wck37nt9_z4s8l\
FQp0nROA-oaR92HW34KNL1b2fEVWGI0N86h730MvTJC5O2cmKeMezIG-oNqbbfFyP8AW-WLdDlgZm1\
1-FjzhbVpb0Bc7nRSgBPSV-EY6Sl-LuglxDx4LaTdQW7QE_WXoRUt-GYGfTseuFQQK5WeoyX3yBtQy\
dpauW6rrgyWdtP4hDFIoZsX6w1i-UMWMMwlIB5FdnUSi26igVGADGpV_vGMP36bv-EHp0bY-Qp0gpI\
fLfgQ","dp":"Z1v5UceFfV2bhmbG19eGYb30jFxqoRBq36PKNY7IunMs1keYy0FpLbyGhtgMZ1Ymm\
c8wEzGYsCPEP-ykcun_rlyu7YxmcnyC9YQqTqLyqvO-7rUqDvk9TMfdqWFP6heADRhKZmEbmcau6_m\
2MwwK9kOkMKWvpqp8_TpJMnAH7zE","dq":"OBacRE15aY3NtCR4cvP5os3sT70JbDdDLHT3IHZM6r\
E35CYNpLDia2chm_wnMcYvKFW9zC2ajRZ15i9c_VXQzS7ZlTaQYBFyMt7kVhxMEMFsPv1crD6t3uEI\
j0LNuNYyy0jkon_LPZKQFK654CiL-L2YaNXOH4HbHP02dWeVQIE","e":"AQAB","ext":true,"ke\
y_ops":["decrypt"],"kty":"RSA","n":"m1c92ZFk9ZI6l_O4YFiNxbv0Ng94SB3yThy1P_mcqr\
GDQkRiGVdcTxAk38T9PgLztmspF-6U5TAHO-gSmmW88AC9m6f1Mspps6r7zl-M_OG-TwvGzf3BDz8z\
Eg1FPbZV7whO1M4TCAZ0PqwG7qCc6nK1WiAhaKrSpzuPdL1igfNBsX7qu5wgw4ZTTGSLbVC_LfULQ5\
FADgFTRXUSaxm1F8C_Lwy6a2e4nTcXilmtN2IHUjHegzm-Tq2HizmR3ARdWJpESYIW5-AXoiqj29tD\
rqCmu2WPkB2psVp83IzZfaQNQzjNfvA8GpimkcDCkP5VMRrtKCcG4ZAFnO-A3NBX_Q","p":"2Q_lN\
L7vCOBzAppYzCZo3WSh0hX-MOZyPUznks5U2TjmfdNZoL6_FJRiGyyLvwSiZFdEAAvpAyESFfFigng\
AqMLSf448nPg15VUGj533CotsEM0WpoEr1JCgqdUbgDAfJQIBcwOmegBqd7lWm7uzEnRCvouB70ybk\
JfpdprhkVE","q":"tzTt-F3g2u_3Ctj26Ho9iN_wC_W0lXGzslLt5nLmss8JqdLoDDrijjU-gjeRh\
7lgiuHdUc3dorfFKbaMNOjoW3QKqt9oZ1JM0HKeRw0X2PnWW_0WK6DK5ASWDTXbMq2sUZqJvYEyL74\
H2Zrt0RPAux7XQLEVgND6ROdXnMJ70O0","qi":"qfl4cXQkz4BNqa2De0-PfdU-8d1w3onnaGqx1D\
s2fHzD_SJ4cNghn2TksoT9Qo64b3pUjH9igi2pyEjomk6D12N6FG0e10u7vFKv3W5YqUOgTpYdbcWH\
dZ2qZWJU0XQZIrF8jLGTOO4GYP6_9sJ5R7Wk_0MdqQy8qvixWD4zLcY"}');
key = await window.crypto.subtle.importKey("jwk", key, {
name: "RSA-OAEP",
hash: {name: "SHA-256"}
}, true, ["decrypt"]);
return {privateKey: key};
}
const receiveData = new Uint8Array([
// server public key
0x00, 0x00, 0x08, 0x00, 0xac, 0x1a, 0xbc, 0x42,
0x8a, 0x2a, 0x69, 0x65, 0x54, 0xf8, 0x9a, 0xe6,
0x43, 0xaa, 0xf7, 0x27, 0xf6, 0x2a, 0xf8, 0x8f,
0x36, 0xd4, 0xae, 0x54, 0x0f, 0x16, 0x28, 0x08,
0xc2, 0x5b, 0xca, 0x23, 0xdc, 0x27, 0x88, 0x1a,
0x12, 0x82, 0xa8, 0x54, 0xea, 0x00, 0x99, 0x8d,
0x02, 0x1d, 0x77, 0x4a, 0xeb, 0xd0, 0x93, 0x40,
0x79, 0x86, 0xcb, 0x37, 0xd4, 0xb2, 0xc7, 0xcd,
0x93, 0xe1, 0x00, 0x4d, 0x86, 0xff, 0x97, 0x33,
0x0c, 0xad, 0x51, 0x47, 0x45, 0x85, 0x56, 0x07,
0x65, 0x21, 0x7c, 0x57, 0x6d, 0x68, 0x7d, 0xd7,
0x00, 0x43, 0x0c, 0x9d, 0x3b, 0xa1, 0x5a, 0x11,
0xed, 0x51, 0x77, 0xf9, 0xd1, 0x5b, 0x33, 0xd7,
0x1a, 0xeb, 0x65, 0x57, 0xc0, 0x01, 0x51, 0xff,
0x9b, 0x82, 0xb3, 0xeb, 0x82, 0xc2, 0x1f, 0xca,
0x47, 0xc0, 0x6a, 0x09, 0xe0, 0xf7, 0xda, 0x39,
0x85, 0x12, 0xe7, 0x45, 0x8d, 0xb4, 0x1a, 0xda,
0xcb, 0x86, 0x58, 0x52, 0x37, 0x66, 0x9d, 0x8a,
0xce, 0xf2, 0x18, 0x78, 0x7d, 0x7f, 0xf0, 0x07,
0x94, 0x8e, 0x6b, 0x17, 0xd9, 0x00, 0x2a, 0x3a,
0xb9, 0xd4, 0x77, 0xde, 0x70, 0x85, 0xc4, 0x3a,
0x62, 0x10, 0x02, 0xee, 0xba, 0xd8, 0xc0, 0x62,
0xd0, 0x8e, 0xc1, 0x98, 0x19, 0x8e, 0x39, 0x0f,
0x3e, 0x1d, 0x61, 0xb1, 0x93, 0x13, 0x59, 0x39,
0xcb, 0x96, 0xf2, 0x17, 0xc9, 0xe1, 0x41, 0xd3,
0x20, 0xdd, 0x62, 0x5e, 0x7d, 0x53, 0xd6, 0xb7,
0x1d, 0xfe, 0x02, 0x18, 0x1f, 0xe0, 0xef, 0x3d,
0x94, 0xe3, 0x0a, 0x9c, 0x59, 0x54, 0xd8, 0x98,
0x16, 0x9c, 0x31, 0xda, 0x41, 0x0f, 0x2e, 0x71,
0x68, 0xe0, 0xa2, 0x62, 0x3e, 0xe5, 0x25, 0x31,
0xcf, 0xfc, 0x67, 0x63, 0xc3, 0xb0, 0xda, 0x3f,
0x7b, 0x59, 0xbe, 0x7e, 0x9e, 0xa8, 0xd0, 0x01,
0x4f, 0x43, 0x7f, 0x8d, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01,
// server random
0x01, 0x00, 0x5b, 0x58, 0x2a, 0x96, 0x2d, 0xbb,
0x88, 0xec, 0xc3, 0x54, 0x00, 0xf3, 0xbb, 0xbe,
0x17, 0xa3, 0x84, 0xd3, 0xef, 0xd8, 0x4a, 0x31,
0x09, 0x20, 0xdd, 0xbc, 0x16, 0x9d, 0xc9, 0x5b,
0x99, 0x62, 0x86, 0xfe, 0x0b, 0x28, 0x4b, 0xfe,
0x5b, 0x56, 0x2d, 0xcb, 0x6e, 0x6f, 0xec, 0xf0,
0x53, 0x0c, 0x33, 0x84, 0x93, 0xc9, 0xbf, 0x79,
0xde, 0xb3, 0xb9, 0x29, 0x60, 0x78, 0xde, 0xe6,
0x1d, 0xa7, 0x89, 0x48, 0x3f, 0xd1, 0x58, 0x66,
0x27, 0x9c, 0xd4, 0x6e, 0x72, 0x9c, 0x6e, 0x4a,
0xc0, 0x69, 0x79, 0x6f, 0x79, 0x0f, 0x13, 0xc4,
0x20, 0xcf, 0xa6, 0xbb, 0xce, 0x18, 0x6d, 0xd5,
0x9e, 0xd9, 0x67, 0xbe, 0x61, 0x43, 0x67, 0x11,
0x76, 0x2f, 0xfd, 0x78, 0x75, 0x2b, 0x89, 0x35,
0xdd, 0x0f, 0x13, 0x7f, 0xee, 0x78, 0xad, 0x32,
0x56, 0x21, 0x81, 0x08, 0x1f, 0xcf, 0x4c, 0x29,
0xa3, 0xeb, 0x89, 0x2d, 0xbe, 0xba, 0x8d, 0xe4,
0x69, 0x28, 0xba, 0x53, 0x82, 0xce, 0x5c, 0xf6,
0x5e, 0x5e, 0xa5, 0xb3, 0x88, 0xd8, 0x3d, 0xab,
0xf4, 0x24, 0x9e, 0x3f, 0x04, 0xaf, 0xdc, 0x48,
0x90, 0x53, 0x37, 0xe6, 0x82, 0x1d, 0xe0, 0x15,
0x91, 0xa1, 0xc6, 0xa9, 0x54, 0xe5, 0x2a, 0xb5,
0x64, 0x2d, 0x93, 0xc0, 0xc0, 0xe1, 0x0f, 0x6a,
0x4b, 0xdb, 0x77, 0xf8, 0x4a, 0x0f, 0x83, 0x36,
0xdd, 0x5e, 0x1e, 0xdd, 0x39, 0x65, 0xa2, 0x11,
0xc2, 0xcf, 0x56, 0x1e, 0xa1, 0x29, 0xae, 0x11,
0x9f, 0x3a, 0x82, 0xc7, 0xbd, 0x89, 0x6e, 0x59,
0xb8, 0x59, 0x17, 0xcb, 0x65, 0xa0, 0x4b, 0x4d,
0xbe, 0x33, 0x32, 0x85, 0x9c, 0xca, 0x5e, 0x95,
0xc2, 0x5a, 0xd0, 0xc9, 0x8b, 0xf1, 0xf5, 0x14,
0xcf, 0x76, 0x80, 0xc2, 0x24, 0x0a, 0x39, 0x7e,
0x60, 0x64, 0xce, 0xd9, 0xb8, 0xad, 0x24, 0xa8,
0xdf, 0xcb,
// server hash
0x00, 0x14, 0x39, 0x30, 0x66, 0xb5, 0x66, 0x8a,
0xcd, 0xb9, 0xda, 0xe0, 0xde, 0xcb, 0xf6, 0x47,
0x5f, 0x54, 0x66, 0xe0, 0xbc, 0x49, 0x37, 0x01,
0xf2, 0x9e, 0xef, 0xcc, 0xcd, 0x4d, 0x6c, 0x0e,
0xc6, 0xab, 0x28, 0xd4, 0x7b, 0x13,
// subtype
0x00, 0x01, 0x30, 0x2a, 0xc3, 0x0b, 0xc2, 0x1c,
0xeb, 0x02, 0x44, 0x92, 0x5d, 0xfd, 0xf9, 0xa7,
0x94, 0xd0, 0x19,
]);
const sendData = new Uint8Array([
// client public key
0x00, 0x00, 0x08, 0x00, 0x9b, 0x57, 0x3d, 0xd9,
0x91, 0x64, 0xf5, 0x92, 0x3a, 0x97, 0xf3, 0xb8,
0x60, 0x58, 0x8d, 0xc5, 0xbb, 0xf4, 0x36, 0x0f,
0x78, 0x48, 0x1d, 0xf2, 0x4e, 0x1c, 0xb5, 0x3f,
0xf9, 0x9c, 0xaa, 0xb1, 0x83, 0x42, 0x44, 0x62,
0x19, 0x57, 0x5c, 0x4f, 0x10, 0x24, 0xdf, 0xc4,
0xfd, 0x3e, 0x02, 0xf3, 0xb6, 0x6b, 0x29, 0x17,
0xee, 0x94, 0xe5, 0x30, 0x07, 0x3b, 0xe8, 0x12,
0x9a, 0x65, 0xbc, 0xf0, 0x00, 0xbd, 0x9b, 0xa7,
0xf5, 0x32, 0xca, 0x69, 0xb3, 0xaa, 0xfb, 0xce,
0x5f, 0x8c, 0xfc, 0xe1, 0xbe, 0x4f, 0x0b, 0xc6,
0xcd, 0xfd, 0xc1, 0x0f, 0x3f, 0x33, 0x12, 0x0d,
0x45, 0x3d, 0xb6, 0x55, 0xef, 0x08, 0x4e, 0xd4,
0xce, 0x13, 0x08, 0x06, 0x74, 0x3e, 0xac, 0x06,
0xee, 0xa0, 0x9c, 0xea, 0x72, 0xb5, 0x5a, 0x20,
0x21, 0x68, 0xaa, 0xd2, 0xa7, 0x3b, 0x8f, 0x74,
0xbd, 0x62, 0x81, 0xf3, 0x41, 0xb1, 0x7e, 0xea,
0xbb, 0x9c, 0x20, 0xc3, 0x86, 0x53, 0x4c, 0x64,
0x8b, 0x6d, 0x50, 0xbf, 0x2d, 0xf5, 0x0b, 0x43,
0x91, 0x40, 0x0e, 0x01, 0x53, 0x45, 0x75, 0x12,
0x6b, 0x19, 0xb5, 0x17, 0xc0, 0xbf, 0x2f, 0x0c,
0xba, 0x6b, 0x67, 0xb8, 0x9d, 0x37, 0x17, 0x8a,
0x59, 0xad, 0x37, 0x62, 0x07, 0x52, 0x31, 0xde,
0x83, 0x39, 0xbe, 0x4e, 0xad, 0x87, 0x8b, 0x39,
0x91, 0xdc, 0x04, 0x5d, 0x58, 0x9a, 0x44, 0x49,
0x82, 0x16, 0xe7, 0xe0, 0x17, 0xa2, 0x2a, 0xa3,
0xdb, 0xdb, 0x43, 0xae, 0xa0, 0xa6, 0xbb, 0x65,
0x8f, 0x90, 0x1d, 0xa9, 0xb1, 0x5a, 0x7c, 0xdc,
0x8c, 0xd9, 0x7d, 0xa4, 0x0d, 0x43, 0x38, 0xcd,
0x7e, 0xf0, 0x3c, 0x1a, 0x98, 0xa6, 0x91, 0xc0,
0xc2, 0x90, 0xfe, 0x55, 0x31, 0x1a, 0xed, 0x28,
0x27, 0x06, 0xe1, 0x90, 0x05, 0x9c, 0xef, 0x80,
0xdc, 0xd0, 0x57, 0xfd, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01,
// client random
0x01, 0x00, 0x84, 0x7f, 0x26, 0x54, 0x74, 0xf6,
0x47, 0xaf, 0x33, 0x64, 0x0d, 0xa6, 0xe5, 0x30,
0xba, 0xe6, 0xe4, 0x8e, 0x50, 0x40, 0x71, 0x1c,
0x0e, 0x06, 0x63, 0xf5, 0x07, 0x2a, 0x26, 0x68,
0xd6, 0xcf, 0xa6, 0x80, 0x84, 0x5e, 0x64, 0xd4,
0x5e, 0x62, 0x31, 0xfe, 0x44, 0x51, 0x0b, 0x7c,
0x4d, 0x55, 0xc5, 0x4a, 0x7e, 0x0d, 0x4d, 0x9b,
0x84, 0xb4, 0x32, 0x2b, 0x4d, 0x8a, 0x34, 0x8d,
0xc8, 0xcf, 0x19, 0x3b, 0x64, 0x82, 0x27, 0x9e,
0xa7, 0x70, 0x2a, 0xc1, 0xb8, 0xf3, 0x6a, 0x3a,
0xf2, 0x75, 0x6e, 0x1d, 0xeb, 0xb6, 0x70, 0x7a,
0x15, 0x18, 0x38, 0x00, 0xb4, 0x4f, 0x55, 0xb5,
0xd8, 0x03, 0x4e, 0xb8, 0x53, 0xff, 0x80, 0x62,
0xf1, 0x9d, 0x27, 0xe8, 0x2a, 0x3d, 0x98, 0x19,
0x32, 0x09, 0x7e, 0x9a, 0xb0, 0xc7, 0x46, 0x23,
0x10, 0x85, 0x35, 0x00, 0x96, 0xce, 0xb3, 0x2c,
0x84, 0x8d, 0xf4, 0x9e, 0xa8, 0x42, 0x67, 0xed,
0x09, 0xa6, 0x09, 0x97, 0xb3, 0x64, 0x26, 0xfb,
0x71, 0x11, 0x9b, 0x3f, 0xbb, 0x57, 0xb8, 0x5b,
0x2e, 0xc5, 0x2d, 0x8c, 0x5c, 0xf7, 0xef, 0x27,
0x25, 0x88, 0x42, 0x45, 0x43, 0xa4, 0xe7, 0xde,
0xea, 0xf9, 0x15, 0x7b, 0x5d, 0x66, 0x24, 0xce,
0xf7, 0xc8, 0x2f, 0xc5, 0xc0, 0x3d, 0xcd, 0xf2,
0x62, 0xfc, 0x1a, 0x5e, 0xec, 0xff, 0xf1, 0x1b,
0xc8, 0xdb, 0xc1, 0x0f, 0x54, 0x66, 0x9e, 0xfd,
0x99, 0x9b, 0x23, 0x70, 0x62, 0x37, 0x80, 0xad,
0x91, 0x6b, 0x84, 0x85, 0x6a, 0x4c, 0x80, 0x9e,
0x60, 0x8a, 0x93, 0xa3, 0xc8, 0x8e, 0xc4, 0x4b,
0x4d, 0xb4, 0x8e, 0x3e, 0xaf, 0xce, 0xcd, 0x83,
0xe5, 0x21, 0x90, 0x95, 0x20, 0x3c, 0x82, 0xb4,
0x7c, 0xab, 0x63, 0x9c, 0xae, 0xc3, 0xc9, 0x71,
0x1a, 0xec, 0x34, 0x18, 0x47, 0xec, 0x5c, 0x4d,
0xed, 0x84,
// client hash
0x00, 0x14, 0x9c, 0x91, 0x9e, 0x76, 0xcf, 0x1e,
0x66, 0x87, 0x5e, 0x29, 0xf1, 0x13, 0x80, 0xea,
0x7d, 0xec, 0xae, 0xf9, 0x60, 0x01, 0xd3, 0x6f,
0xb7, 0x9e, 0xb2, 0xcd, 0x2d, 0xc8, 0xf8, 0x84,
0xb2, 0x9f, 0xc3, 0x7e, 0xb4, 0xbe,
// credentials
0x00, 0x08, 0x9d, 0xc8, 0x3a, 0xb8, 0x80, 0x4f,
0xe3, 0x52, 0xdb, 0x62, 0x9e, 0x97, 0x64, 0x82,
0xa8, 0xa1, 0x6b, 0x7e, 0x4d, 0x68, 0x8c, 0x29,
0x91, 0x38,
]);
describe('RA2 handshake', function () {
let sock;
let rfb;
let sentData;
before(() => {
FakeWebSocket.replace();
sinon.stub(window.crypto, "getRandomValues").callsFake(fakeGetRandomValues);
sinon.stub(window.crypto.subtle, "generateKey").callsFake(fakeGeneratekey);
});
after(() => {
FakeWebSocket.restore();
window.crypto.getRandomValues.restore();
window.crypto.subtle.generateKey.restore();
});
it('should fire the serververification event', function (done) {
sentData = new Uint8Array();
rfb = new RFB(document.createElement('div'), "ws://example.com");
sock = rfb._sock;
sock.send = (data) => {
let res = new Uint8Array(sentData.length + data.length);
res.set(sentData);
res.set(data, sentData.length);
sentData = res;
};
rfb._rfbInitState = "Security";
rfb._rfbVersion = 3.8;
sock._websocket._receiveData(new Uint8Array([1, 6]));
rfb.addEventListener("serververification", (e) => {
expect(e.detail.publickey).to.eql(receiveData.slice(0, 516));
done();
});
sock._websocket._receiveData(receiveData);
});
it('should handle approveServer and fire the credentialsrequired event', function (done) {
rfb.addEventListener("credentialsrequired", (e) => {
expect(e.detail.types).to.eql(["password"]);
done();
});
rfb.approveServer();
});
it('should match sendData after sending credentials', function (done) {
rfb.addEventListener("securityresult", (event) => {
expect(sentData.slice(1)).to.eql(sendData);
done();
});
rfb.sendCredentials({ "password": "123456" });
});
});

File diff suppressed because it is too large Load Diff

View File

@ -270,6 +270,14 @@ describe('Websock', function () {
// it('should initialize the event handlers')?
});
describe('attaching', function () {
it('should attach to an existing websocket', function () {
let ws = new FakeWebSocket('ws://localhost:8675');
sock.attach(ws);
expect(WebSocket).to.not.have.been.called;
});
});
describe('closing', function () {
beforeEach(function () {
sock.open('ws://localhost');
@ -342,6 +350,93 @@ describe('Websock', function () {
});
});
describe('ready state', function () {
it('should be "unused" after construction', function () {
let sock = new Websock();
expect(sock.readyState).to.equal('unused');
});
it('should be "connecting" if WebSocket is connecting', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CONNECTING;
sock.attach(ws);
expect(sock.readyState).to.equal('connecting');
});
it('should be "open" if WebSocket is open', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.OPEN;
sock.attach(ws);
expect(sock.readyState).to.equal('open');
});
it('should be "closing" if WebSocket is closing', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CLOSING;
sock.attach(ws);
expect(sock.readyState).to.equal('closing');
});
it('should be "closed" if WebSocket is closed', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CLOSED;
sock.attach(ws);
expect(sock.readyState).to.equal('closed');
});
it('should be "unknown" if WebSocket state is unknown', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 666;
sock.attach(ws);
expect(sock.readyState).to.equal('unknown');
});
it('should be "connecting" if RTCDataChannel is connecting', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'connecting';
sock.attach(ws);
expect(sock.readyState).to.equal('connecting');
});
it('should be "open" if RTCDataChannel is open', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'open';
sock.attach(ws);
expect(sock.readyState).to.equal('open');
});
it('should be "closing" if RTCDataChannel is closing', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'closing';
sock.attach(ws);
expect(sock.readyState).to.equal('closing');
});
it('should be "closed" if RTCDataChannel is closed', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'closed';
sock.attach(ws);
expect(sock.readyState).to.equal('closed');
});
it('should be "unknown" if RTCDataChannel state is unknown', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'foobar';
sock.attach(ws);
expect(sock.readyState).to.equal('unknown');
});
});
after(function () {
// eslint-disable-next-line no-global-assign
WebSocket = oldWS;

View File

@ -7,6 +7,48 @@ import * as WebUtil from '../app/webutil.js';
describe('WebUtil', function () {
"use strict";
describe('config variables', function () {
it('should parse query string variables', function () {
// history.pushState() will not cause the browser to attempt loading
// the URL, this is exactly what we want here for the tests.
history.pushState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
});
it('should return default value when no query match', function () {
history.pushState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
});
it('should handle no query match and no default value', function () {
history.pushState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
});
it('should parse fragment variables', function () {
history.pushState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
});
it('should return default value when no fragment match', function () {
history.pushState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
});
it('should handle no fragment match and no default value', function () {
history.pushState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
});
it('should handle both query and fragment', function () {
history.pushState({}, '', "test?myquery=1#myhash=2");
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
});
it('should prioritize fragment if both provide same var', function () {
history.pushState({}, '', "test?myvar=1#myvar=2");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
});
});
describe('cookies', function () {
// TODO
});
describe('settings', function () {
describe('localStorage', function () {

124
tests/test.zrle.js Normal file
View File

@ -0,0 +1,124 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import ZRLEDecoder from '../core/decoders/zrle.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
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) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('ZRLE Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new ZRLEDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the Raw subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e, 0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(display).to.have.displayed(targetData);
});
it('should handle the Solid subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(display).to.have.displayed(targetData);
});
it('should handle the Palette Tile subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x12, 0x78, 0x5E, 0x62, 0x62, 0x60, 248, 0xff, 0x9F, 0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff
]);
expect(display).to.have.displayed(targetData);
});
it('should handle the RLE Tile subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e, 0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(display).to.have.displayed(targetData);
});
it('should handle the RLE Palette Tile subencoding', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x11, 0x78, 0x5e, 0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f, 0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(display).to.have.displayed(targetData);
});
it('should fail on an invalid subencoding', function () {
let data = [0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x6a, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff];
expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();
});
});

View File

@ -19,6 +19,16 @@
<br><br>
<!-- This is where the RFB elements will attach -->
<div id="noVNC_container">
<!-- Note that Google Chrome on Android doesn't respect any of these,
html attributes which attempt to disable text suggestions on the
on-screen keyboard. Let's hope Chrome implements the ime-mode
style for example -->
<textarea id="noVNC_keyboardinput" autocapitalize="off"
autocomplete="off" spellcheck="false" tabindex="-1"></textarea>
</div>
<div id="VNC_screen">
<div id="VNC_status">Loading</div>
</div>