This commit is contained in:
Pierre Ossman 2023-06-30 18:20:44 +02:00
commit ca6527c1bf
18 changed files with 731 additions and 751 deletions

View File

@ -31,10 +31,7 @@ export default class HextileDecoder {
return false;
}
let rQ = sock.rQ;
let rQi = sock.rQi;
let subencoding = rQ[rQi]; // Peek
let subencoding = sock.rQpeek8();
if (subencoding > 30) { // Raw
throw new Error("Illegal hextile subencoding (subencoding: " +
subencoding + ")");
@ -65,7 +62,7 @@ export default class HextileDecoder {
return false;
}
let subrects = rQ[rQi + bytes - 1]; // Peek
let subrects = sock.rQpeekBytes(bytes).at(-1);
if (subencoding & 0x10) { // SubrectsColoured
bytes += subrects * (4 + 2);
} else {
@ -79,7 +76,7 @@ export default class HextileDecoder {
}
// We know the encoding and have a whole tile
rQi++;
sock.rQshift8();
if (subencoding === 0) {
if (this._lastsubencoding & 0x01) {
// Weird: ignore blanks are RAW
@ -89,42 +86,36 @@ export default class HextileDecoder {
}
} else if (subencoding & 0x01) { // Raw
let pixels = tw * th;
let data = sock.rQshiftBytes(pixels * 4, false);
// Max sure the image is fully opaque
for (let i = 0;i < pixels;i++) {
rQ[rQi + i * 4 + 3] = 255;
data[i * 4 + 3] = 255;
}
display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1;
display.blitImage(tx, ty, tw, th, data, 0);
} else {
if (subencoding & 0x02) { // Background
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
this._background = new Uint8Array(sock.rQshiftBytes(4));
}
if (subencoding & 0x04) { // Foreground
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
this._foreground = new Uint8Array(sock.rQshiftBytes(4));
}
this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi];
rQi++;
let subrects = sock.rQshift8();
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;
color = sock.rQshiftBytes(4);
} else {
color = this._foreground;
}
const xy = rQ[rQi];
rQi++;
const xy = sock.rQshift8();
const sx = (xy >> 4);
const sy = (xy & 0x0f);
const wh = rQ[rQi];
rQi++;
const wh = sock.rQshift8();
const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1;
@ -133,7 +124,6 @@ export default class HextileDecoder {
}
this._finishTile(display);
}
sock.rQi = rQi;
this._lastsubencoding = subencoding;
this._tiles--;
}

View File

@ -11,131 +11,136 @@ export default class JPEGDecoder {
constructor() {
// RealVNC will reuse the quantization tables
// and Huffman tables, so we need to cache them.
this._quantTables = [];
this._huffmanTables = [];
this._cachedQuantTables = [];
this._cachedHuffmanTables = [];
this._jpegLength = 0;
this._segments = [];
}
decodeRect(x, y, width, height, sock, display, depth) {
// A rect of JPEG encodings is simply a JPEG file
if (!this._parseJPEG(sock.rQslice(0))) {
return false;
}
const data = sock.rQshiftBytes(this._jpegLength);
if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
// If there are quantization tables and Huffman tables in the JPEG
// image, we can directly render it.
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
} else {
// Otherwise we need to insert cached tables.
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
let segments = this._segments.slice(0, sofIndex);
segments = segments.concat(this._quantTables.length ?
this._quantTables :
this._cachedQuantTables);
segments.push(this._segments[sofIndex]);
segments = segments.concat(this._huffmanTables.length ?
this._huffmanTables :
this._cachedHuffmanTables,
this._segments.slice(sofIndex + 1));
let length = 0;
for (let i = 0; i < segments.length; i++) {
length += segments[i].length;
}
const data = new Uint8Array(length);
length = 0;
for (let i = 0; i < segments.length; i++) {
data.set(segments[i], length);
length += segments[i].length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
}
}
_parseJPEG(buffer) {
if (this._quantTables.length != 0) {
this._cachedQuantTables = this._quantTables;
}
if (this._huffmanTables.length != 0) {
this._cachedHuffmanTables = this._huffmanTables;
}
this._quantTables = [];
this._huffmanTables = [];
this._segments = [];
let i = 0;
let bufferLength = buffer.length;
while (true) {
let j = i;
if (j + 2 > bufferLength) {
let segment = this._readSegment(sock);
if (segment === null) {
return false;
}
if (buffer[j] != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
buffer[j] + ")");
}
const type = buffer[j+1];
j += 2;
if (type == 0xD9) {
this._jpegLength = j;
this._segments.push(buffer.slice(i, j));
return true;
} else if (type == 0xDA) {
// start of scan
let hasFoundEndOfScan = false;
for (let k = j + 3; k + 1 < bufferLength; k++) {
if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
!(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
j = k;
hasFoundEndOfScan = true;
break;
}
}
if (!hasFoundEndOfScan) {
return false;
}
this._segments.push(buffer.slice(i, j));
i = j;
continue;
} else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
// No length after marker
this._segments.push(buffer.slice(i, j));
i = j;
continue;
}
if (j + 2 > bufferLength) {
return false;
}
const length = (buffer[j] << 8) + buffer[j+1] - 2;
if (length < 0) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
j += 2;
if (j + length > bufferLength) {
return false;
}
j += length;
const segment = buffer.slice(i, j);
if (type == 0xC4) {
// Huffman tables
this._huffmanTables.push(segment);
} else if (type == 0xDB) {
// Quantization tables
this._quantTables.push(segment);
}
this._segments.push(segment);
i = j;
// End of image?
if (segment[1] === 0xD9) {
break;
}
}
let huffmanTables = [];
let quantTables = [];
for (let segment of this._segments) {
let type = segment[1];
if (type === 0xC4) {
// Huffman tables
huffmanTables.push(segment);
} else if (type === 0xDB) {
// Quantization tables
quantTables.push(segment);
}
}
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
if (quantTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedQuantTables);
}
if (huffmanTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedHuffmanTables);
}
let length = 0;
for (let segment of this._segments) {
length += segment.length;
}
let data = new Uint8Array(length);
length = 0;
for (let segment of this._segments) {
data.set(segment, length);
length += segment.length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
if (huffmanTables.length !== 0) {
this._cachedHuffmanTables = huffmanTables;
}
if (quantTables.length !== 0) {
this._cachedQuantTables = quantTables;
}
this._segments = [];
return true;
}
_readSegment(sock) {
if (sock.rQwait("JPEG", 2)) {
return null;
}
let marker = sock.rQshift8();
if (marker != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
marker + ")");
}
let type = sock.rQshift8();
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
// No length after marker
return new Uint8Array([marker, type]);
}
if (sock.rQwait("JPEG", 2, 2)) {
return null;
}
let length = sock.rQshift16();
if (length < 2) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
if (sock.rQwait("JPEG", length-2, 4)) {
return null;
}
let extra = 0;
if (type === 0xDA) {
// start of scan
extra += 2;
while (true) {
if (sock.rQwait("JPEG", length-2+extra, 4)) {
return null;
}
let data = sock.rQpeekBytes(length-2+extra, false);
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
extra -= 2;
break;
}
extra++;
}
}
let segment = new Uint8Array(2 + length + extra);
segment[0] = marker;
segment[1] = type;
segment[2] = length >> 8;
segment[3] = length;
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
return segment;
}
}

View File

@ -24,41 +24,34 @@ export default class RawDecoder {
const pixelSize = depth == 8 ? 1 : 4;
const bytesPerLine = width * pixelSize;
if (sock.rQwait("RAW", bytesPerLine)) {
return false;
}
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ;
let index = sock.rQi;
// Convert data if needed
if (depth == 8) {
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 + 3] = 255;
while (this._lines > 0) {
if (sock.rQwait("RAW", bytesPerLine)) {
return false;
}
data = newdata;
index = 0;
}
// Max sure the image is fully opaque
for (let i = 0; i < pixels; i++) {
data[index + i * 4 + 3] = 255;
}
const curY = y + (height - this._lines);
display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight;
if (this._lines > 0) {
return false;
let data = sock.rQshiftBytes(bytesPerLine, false);
// Convert data if needed
if (depth == 8) {
const newdata = new Uint8Array(width * 4);
for (let i = 0; i < width; i++) {
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
}
data = newdata;
}
// Max sure the image is fully opaque
for (let i = 0; i < width; i++) {
data[i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, 1, data, 0);
this._lines--;
}
return true;

View File

@ -76,12 +76,8 @@ export default class TightDecoder {
return false;
}
const rQi = sock.rQi;
const rQ = sock.rQ;
display.fillRect(x, y, width, height,
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
sock.rQskipBytes(3);
let pixel = sock.rQshiftBytes(3);
display.fillRect(x, y, width, height, pixel, false);
return true;
}
@ -316,7 +312,7 @@ export default class TightDecoder {
return null;
}
let data = sock.rQshiftBytes(this._len);
let data = sock.rQshiftBytes(this._len, false);
this._len = 0;
return data;

View File

@ -32,7 +32,7 @@ export default class ZRLEDecoder {
return false;
}
const data = sock.rQshiftBytes(this._length);
const data = sock.rQshiftBytes(this._length, false);
this._inflator.setInput(data);

View File

@ -15,7 +15,7 @@ export default class Display {
this._drawCtx = null;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
this._flushPromise = null;
// the full frame buffer (logical canvas) size
this._fbWidth = 0;
@ -61,10 +61,6 @@ export default class Display {
this._scale = 1.0;
this._clipViewport = false;
// ===== EVENT HANDLERS =====
this.onflush = () => {}; // A flush request has finished
}
// ===== PROPERTIES =====
@ -306,9 +302,14 @@ export default class Display {
flush() {
if (this._renderQ.length === 0) {
this.onflush();
return Promise.resolve();
} else {
this._flushing = true;
if (this._flushPromise === null) {
this._flushPromise = new Promise((resolve) => {
this._flushResolve = resolve;
});
}
return this._flushPromise;
}
}
@ -517,9 +518,11 @@ export default class Display {
}
}
if (this._renderQ.length === 0 && this._flushing) {
this._flushing = false;
this.onflush();
if (this._renderQ.length === 0 &&
this._flushPromise !== null) {
this._flushResolve();
this._flushPromise = null;
this._flushResolve = null;
}
}
}

View File

@ -139,7 +139,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
this._hasStarted = true;
// 1: Receive server public key
await this._waitSockAsync(4);
const serverKeyLengthBuffer = this._sock.rQslice(0, 4);
const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
const serverKeyLength = this._sock.rQshift32();
if (serverKeyLength < 1024) {
throw new Error("RA2: server public key is too short: " + serverKeyLength);
@ -182,7 +182,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientPublicKey[3] = clientKeyLength & 0xff;
clientPublicKey.set(clientN, 4);
clientPublicKey.set(clientE, 4 + clientKeyBytes);
this._sock.send(clientPublicKey);
this._sock.sQpushBytes(clientPublicKey);
this._sock.flush();
// 3: Send client random
const clientRandom = new Uint8Array(16);
@ -193,7 +194,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
clientRandomMessage[1] = serverKeyBytes & 0xff;
clientRandomMessage.set(clientEncryptedRandom, 2);
this._sock.send(clientRandomMessage);
this._sock.sQpushBytes(clientRandomMessage);
this._sock.flush();
// 4: Receive server random
await this._waitSockAsync(2);
@ -234,7 +236,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
serverHash = new Uint8Array(serverHash);
clientHash = new Uint8Array(clientHash);
this._sock.send(await clientCipher.makeMessage(clientHash));
this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
this._sock.flush();
await this._waitSockAsync(2 + 20 + 16);
if (this._sock.rQshift16() !== 20) {
throw new Error("RA2: wrong server hash");
@ -295,7 +298,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
for (let i = 0; i < password.length; i++) {
credentials[username.length + 2 + i] = password.charCodeAt(i);
}
this._sock.send(await clientCipher.makeMessage(credentials));
this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
this._sock.flush();
}
get hasStarted() {

View File

@ -257,7 +257,6 @@ export default class RFB extends EventTargetMixin {
Log.Error("Display exception: " + exc);
throw exc;
}
this._display.onflush = this._onFlush.bind(this);
this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
@ -959,7 +958,7 @@ export default class RFB extends EventTargetMixin {
}
_handleMessage() {
if (this._sock.rQlen === 0) {
if (this._sock.rQwait("message", 1)) {
Log.Warn("handleMessage called on an empty receive queue");
return;
}
@ -976,7 +975,7 @@ export default class RFB extends EventTargetMixin {
if (!this._normalMsg()) {
break;
}
if (this._sock.rQlen === 0) {
if (this._sock.rQwait("message", 1)) {
break;
}
}
@ -1382,7 +1381,8 @@ export default class RFB extends EventTargetMixin {
while (repeaterID.length < 250) {
repeaterID += "\0";
}
this._sock.sendString(repeaterID);
this._sock.sQpushString(repeaterID);
this._sock.flush();
return true;
}
@ -1392,7 +1392,8 @@ export default class RFB extends EventTargetMixin {
const cversion = "00" + parseInt(this._rfbVersion, 10) +
".00" + ((this._rfbVersion * 10) % 10);
this._sock.sendString("RFB " + cversion + "\n");
this._sock.sQpushString("RFB " + cversion + "\n");
this._sock.flush();
Log.Debug('Sent ProtocolVersion: ' + cversion);
this._rfbInitState = 'Security';
@ -1444,7 +1445,8 @@ export default class RFB extends EventTargetMixin {
return this._fail("Unsupported security types (types: " + types + ")");
}
this._sock.send([this._rfbAuthScheme]);
this._sock.sQpush8(this._rfbAuthScheme);
this._sock.flush();
} else {
// Server decides
if (this._sock.rQwait("security scheme", 4)) { return false; }
@ -1506,12 +1508,15 @@ export default class RFB extends EventTargetMixin {
return false;
}
const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) +
String.fromCharCode(this._rfbCredentials.target.length) +
this._rfbCredentials.username +
this._rfbCredentials.target;
this._sock.sendString(xvpAuthStr);
this._sock.sQpush8(this._rfbCredentials.username.length);
this._sock.sQpush8(this._rfbCredentials.target.length);
this._sock.sQpushString(this._rfbCredentials.username);
this._sock.sQpushString(this._rfbCredentials.target);
this._sock.flush();
this._rfbAuthScheme = securityTypeVNCAuth;
return this._negotiateAuthentication();
}
@ -1529,7 +1534,9 @@ export default class RFB extends EventTargetMixin {
return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
}
this._sock.send([0, 2]);
this._sock.sQpush8(0);
this._sock.sQpush8(2);
this._sock.flush();
this._rfbVeNCryptState = 1;
}
@ -1588,10 +1595,8 @@ export default class RFB extends EventTargetMixin {
return this._fail("Unsupported security types (types: " + subtypes + ")");
}
this._sock.send([this._rfbAuthScheme >> 24,
this._rfbAuthScheme >> 16,
this._rfbAuthScheme >> 8,
this._rfbAuthScheme]);
this._sock.sQpush32(this._rfbAuthScheme);
this._sock.flush();
this._rfbVeNCryptState = 4;
return true;
@ -1610,20 +1615,11 @@ export default class RFB extends EventTargetMixin {
const user = encodeUTF8(this._rfbCredentials.username);
const pass = encodeUTF8(this._rfbCredentials.password);
this._sock.send([
(user.length >> 24) & 0xFF,
(user.length >> 16) & 0xFF,
(user.length >> 8) & 0xFF,
user.length & 0xFF
]);
this._sock.send([
(pass.length >> 24) & 0xFF,
(pass.length >> 16) & 0xFF,
(pass.length >> 8) & 0xFF,
pass.length & 0xFF
]);
this._sock.sendString(user);
this._sock.sendString(pass);
this._sock.sQpush32(user.length);
this._sock.sQpush32(pass.length);
this._sock.sQpushString(user);
this._sock.sQpushString(pass);
this._sock.flush();
this._rfbInitState = "SecurityResult";
return true;
@ -1642,7 +1638,8 @@ export default class RFB extends EventTargetMixin {
// TODO(directxman12): make genDES not require an Array
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
const response = RFB.genDES(this._rfbCredentials.password, challenge);
this._sock.send(response);
this._sock.sQpushBytes(response);
this._sock.flush();
this._rfbInitState = "SecurityResult";
return true;
}
@ -1660,8 +1657,9 @@ export default class RFB extends EventTargetMixin {
if (this._rfbCredentials.ardPublicKey != undefined &&
this._rfbCredentials.ardCredentials != undefined) {
// if the async web crypto is done return the results
this._sock.send(this._rfbCredentials.ardCredentials);
this._sock.send(this._rfbCredentials.ardPublicKey);
this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
this._sock.flush();
this._rfbCredentials.ardCredentials = null;
this._rfbCredentials.ardPublicKey = null;
this._rfbInitState = "SecurityResult";
@ -1725,10 +1723,12 @@ export default class RFB extends EventTargetMixin {
return false;
}
this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
this._sock.sendString(this._rfbCredentials.username);
this._sock.sendString(this._rfbCredentials.password);
this._sock.sQpush32(this._rfbCredentials.username.length);
this._sock.sQpush32(this._rfbCredentials.password.length);
this._sock.sQpushString(this._rfbCredentials.username);
this._sock.sQpushString(this._rfbCredentials.password);
this._sock.flush();
this._rfbInitState = "SecurityResult";
return true;
}
@ -1766,7 +1766,8 @@ export default class RFB extends EventTargetMixin {
"vendor or signature");
}
Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
this._sock.sQpush32(0); // use NOTUNNEL
this._sock.flush();
return false; // wait until we receive the sub auth count to continue
} else {
return this._fail("Server wanted tunnels, but doesn't support " +
@ -1816,7 +1817,8 @@ export default class RFB extends EventTargetMixin {
for (let authType in clientSupportedTypes) {
if (serverSupportedTypes.indexOf(authType) != -1) {
this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
this._sock.sQpush32(clientSupportedTypes[authType]);
this._sock.flush();
Log.Debug("Selected authentication type: " + authType);
switch (authType) {
@ -1912,9 +1914,10 @@ export default class RFB extends EventTargetMixin {
passwordBytes[password.length] = 0;
usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
this._sock.send(B);
this._sock.send(usernameBytes);
this._sock.send(passwordBytes);
this._sock.sQpushBytes(B);
this._sock.sQpushBytes(usernameBytes);
this._sock.sQpushBytes(passwordBytes);
this._sock.flush();
this._rfbInitState = "SecurityResult";
return true;
}
@ -2142,7 +2145,8 @@ export default class RFB extends EventTargetMixin {
return this._handleSecurityReason();
case 'ClientInitialisation':
this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
this._sock.flush();
this._rfbInitState = 'ServerInitialisation';
return true;
@ -2455,19 +2459,11 @@ export default class RFB extends EventTargetMixin {
default:
this._fail("Unexpected server message (type " + msgType + ")");
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
return true;
}
}
_onFlush() {
this._flushing = false;
// Resume processing
if (this._sock.rQlen > 0) {
this._handleMessage();
}
}
_framebufferUpdate() {
if (this._FBU.rects === 0) {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
@ -2478,7 +2474,14 @@ export default class RFB extends EventTargetMixin {
// to avoid building up an excessive queue
if (this._display.pending()) {
this._flushing = true;
this._display.flush();
this._display.flush()
.then(() => {
this._flushing = false;
// Resume processing
if (!this._sock.rQwait("message", 1)) {
this._handleMessage();
}
});
return false;
}
}
@ -2488,13 +2491,13 @@ export default class RFB extends EventTargetMixin {
if (this._sock.rQwait("rect header", 12)) { return false; }
/* New FramebufferUpdate */
const hdr = this._sock.rQshiftBytes(12);
this._FBU.x = (hdr[0] << 8) + hdr[1];
this._FBU.y = (hdr[2] << 8) + hdr[3];
this._FBU.width = (hdr[4] << 8) + hdr[5];
this._FBU.height = (hdr[6] << 8) + hdr[7];
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
(hdr[10] << 8) + hdr[11], 10);
this._FBU.x = this._sock.rQshift16();
this._FBU.y = this._sock.rQshift16();
this._FBU.width = this._sock.rQshift16();
this._FBU.height = this._sock.rQshift16();
this._FBU.encoding = this._sock.rQshift32();
/* Encodings are signed */
this._FBU.encoding >>= 0;
}
if (!this._handleRect()) {
@ -2728,26 +2731,18 @@ export default class RFB extends EventTargetMixin {
const firstUpdate = !this._supportsSetDesktopSize;
this._supportsSetDesktopSize = true;
// Normally we only apply the current resize mode after a
// window resize event. However there is no such trigger on the
// initial connect. And we don't know if the server supports
// resizing until we've gotten here.
if (firstUpdate) {
this._requestRemoteResize();
}
this._sock.rQskipBytes(1); // number-of-screens
this._sock.rQskipBytes(3); // padding
for (let i = 0; i < numberOfScreens; i += 1) {
// Save the id and flags of the first screen
if (i === 0) {
this._screenID = this._sock.rQshiftBytes(4); // id
this._sock.rQskipBytes(2); // x-position
this._sock.rQskipBytes(2); // y-position
this._sock.rQskipBytes(2); // width
this._sock.rQskipBytes(2); // height
this._screenFlags = this._sock.rQshiftBytes(4); // flags
this._screenID = this._sock.rQshift32(); // id
this._sock.rQskipBytes(2); // x-position
this._sock.rQskipBytes(2); // y-position
this._sock.rQskipBytes(2); // width
this._sock.rQskipBytes(2); // height
this._screenFlags = this._sock.rQshift32(); // flags
} else {
this._sock.rQskipBytes(16);
}
@ -2785,6 +2780,14 @@ export default class RFB extends EventTargetMixin {
this._resize(this._FBU.width, this._FBU.height);
}
// Normally we only apply the current resize mode after a
// window resize event. However there is no such trigger on the
// initial connect. And we don't know if the server supports
// resizing until we've gotten here.
if (firstUpdate) {
this._requestRemoteResize();
}
return true;
}
@ -2889,21 +2892,13 @@ export default class RFB extends EventTargetMixin {
// Class Methods
RFB.messages = {
keyEvent(sock, keysym, down) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(4); // msg-type
sock.sQpush8(down);
buff[offset] = 4; // msg-type
buff[offset + 1] = down;
sock.sQpush16(0);
buff[offset + 2] = 0;
buff[offset + 3] = 0;
sock.sQpush32(keysym);
buff[offset + 4] = (keysym >> 24);
buff[offset + 5] = (keysym >> 16);
buff[offset + 6] = (keysym >> 8);
buff[offset + 7] = keysym;
sock._sQlen += 8;
sock.flush();
},
@ -2917,46 +2912,28 @@ RFB.messages = {
return xtScanCode;
}
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(255); // msg-type
sock.sQpush8(0); // sub msg-type
buff[offset] = 255; // msg-type
buff[offset + 1] = 0; // sub msg-type
sock.sQpush16(down);
buff[offset + 2] = (down >> 8);
buff[offset + 3] = down;
buff[offset + 4] = (keysym >> 24);
buff[offset + 5] = (keysym >> 16);
buff[offset + 6] = (keysym >> 8);
buff[offset + 7] = keysym;
sock.sQpush32(keysym);
const RFBkeycode = getRFBkeycode(keycode);
buff[offset + 8] = (RFBkeycode >> 24);
buff[offset + 9] = (RFBkeycode >> 16);
buff[offset + 10] = (RFBkeycode >> 8);
buff[offset + 11] = RFBkeycode;
sock.sQpush32(RFBkeycode);
sock._sQlen += 12;
sock.flush();
},
pointerEvent(sock, x, y, mask) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(5); // msg-type
buff[offset] = 5; // msg-type
sock.sQpush8(mask);
buff[offset + 1] = mask;
sock.sQpush16(x);
sock.sQpush16(y);
buff[offset + 2] = x >> 8;
buff[offset + 3] = x;
buff[offset + 4] = y >> 8;
buff[offset + 5] = y;
sock._sQlen += 6;
sock.flush();
},
@ -3056,14 +3033,11 @@ RFB.messages = {
},
clientCutText(sock, data, extended = false) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(6); // msg-type
buff[offset] = 6; // msg-type
buff[offset + 1] = 0; // padding
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
let length;
if (extended) {
@ -3072,121 +3046,63 @@ RFB.messages = {
length = data.length;
}
buff[offset + 4] = length >> 24;
buff[offset + 5] = length >> 16;
buff[offset + 6] = length >> 8;
buff[offset + 7] = length;
sock._sQlen += 8;
// We have to keep track of from where in the data we begin creating the
// buffer for the flush in the next iteration.
let dataOffset = 0;
let remaining = data.length;
while (remaining > 0) {
let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
for (let i = 0; i < flushSize; i++) {
buff[sock._sQlen + i] = data[dataOffset + i];
}
sock._sQlen += flushSize;
sock.flush();
remaining -= flushSize;
dataOffset += flushSize;
}
sock.sQpush32(length);
sock.sQpushBytes(data);
sock.flush();
},
setDesktopSize(sock, width, height, id, flags) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(251); // msg-type
buff[offset] = 251; // msg-type
buff[offset + 1] = 0; // padding
buff[offset + 2] = width >> 8; // width
buff[offset + 3] = width;
buff[offset + 4] = height >> 8; // height
buff[offset + 5] = height;
sock.sQpush8(0); // padding
buff[offset + 6] = 1; // number-of-screens
buff[offset + 7] = 0; // padding
sock.sQpush16(width);
sock.sQpush16(height);
sock.sQpush8(1); // number-of-screens
sock.sQpush8(0); // padding
// screen array
buff[offset + 8] = id >> 24; // id
buff[offset + 9] = id >> 16;
buff[offset + 10] = id >> 8;
buff[offset + 11] = id;
buff[offset + 12] = 0; // x-position
buff[offset + 13] = 0;
buff[offset + 14] = 0; // y-position
buff[offset + 15] = 0;
buff[offset + 16] = width >> 8; // width
buff[offset + 17] = width;
buff[offset + 18] = height >> 8; // height
buff[offset + 19] = height;
buff[offset + 20] = flags >> 24; // flags
buff[offset + 21] = flags >> 16;
buff[offset + 22] = flags >> 8;
buff[offset + 23] = flags;
sock.sQpush32(id);
sock.sQpush16(0); // x-position
sock.sQpush16(0); // y-position
sock.sQpush16(width);
sock.sQpush16(height);
sock.sQpush32(flags);
sock._sQlen += 24;
sock.flush();
},
clientFence(sock, flags, payload) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(248); // msg-type
buff[offset] = 248; // msg-type
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
buff[offset + 1] = 0; // padding
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
sock.sQpush32(flags);
buff[offset + 4] = flags >> 24; // flags
buff[offset + 5] = flags >> 16;
buff[offset + 6] = flags >> 8;
buff[offset + 7] = flags;
sock.sQpush8(payload.length);
sock.sQpushString(payload);
const n = payload.length;
buff[offset + 8] = n; // length
for (let i = 0; i < n; i++) {
buff[offset + 9 + i] = payload.charCodeAt(i);
}
sock._sQlen += 9 + n;
sock.flush();
},
enableContinuousUpdates(sock, enable, x, y, width, height) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(150); // msg-type
buff[offset] = 150; // msg-type
buff[offset + 1] = enable; // enable-flag
sock.sQpush8(enable);
buff[offset + 2] = x >> 8; // x
buff[offset + 3] = x;
buff[offset + 4] = y >> 8; // y
buff[offset + 5] = y;
buff[offset + 6] = width >> 8; // width
buff[offset + 7] = width;
buff[offset + 8] = height >> 8; // height
buff[offset + 9] = height;
sock.sQpush16(x);
sock.sQpush16(y);
sock.sQpush16(width);
sock.sQpush16(height);
sock._sQlen += 10;
sock.flush();
},
pixelFormat(sock, depth, trueColor) {
const buff = sock._sQ;
const offset = sock._sQlen;
let bpp;
if (depth > 16) {
@ -3199,100 +3115,69 @@ RFB.messages = {
const bits = Math.floor(depth/3);
buff[offset] = 0; // msg-type
sock.sQpush8(0); // msg-type
buff[offset + 1] = 0; // padding
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
buff[offset + 4] = bpp; // bits-per-pixel
buff[offset + 5] = depth; // depth
buff[offset + 6] = 0; // little-endian
buff[offset + 7] = trueColor ? 1 : 0; // true-color
sock.sQpush8(bpp);
sock.sQpush8(depth);
sock.sQpush8(0); // little-endian
sock.sQpush8(trueColor ? 1 : 0);
buff[offset + 8] = 0; // red-max
buff[offset + 9] = (1 << bits) - 1; // red-max
sock.sQpush16((1 << bits) - 1); // red-max
sock.sQpush16((1 << bits) - 1); // green-max
sock.sQpush16((1 << bits) - 1); // blue-max
buff[offset + 10] = 0; // green-max
buff[offset + 11] = (1 << bits) - 1; // green-max
sock.sQpush8(bits * 0); // red-shift
sock.sQpush8(bits * 1); // green-shift
sock.sQpush8(bits * 2); // blue-shift
buff[offset + 12] = 0; // blue-max
buff[offset + 13] = (1 << bits) - 1; // blue-max
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
sock.sQpush8(0); // padding
buff[offset + 14] = bits * 0; // red-shift
buff[offset + 15] = bits * 1; // green-shift
buff[offset + 16] = bits * 2; // blue-shift
buff[offset + 17] = 0; // padding
buff[offset + 18] = 0; // padding
buff[offset + 19] = 0; // padding
sock._sQlen += 20;
sock.flush();
},
clientEncodings(sock, encodings) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(2); // msg-type
buff[offset] = 2; // msg-type
buff[offset + 1] = 0; // padding
sock.sQpush8(0); // padding
buff[offset + 2] = encodings.length >> 8;
buff[offset + 3] = encodings.length;
let j = offset + 4;
sock.sQpush16(encodings.length);
for (let i = 0; i < encodings.length; i++) {
const enc = encodings[i];
buff[j] = enc >> 24;
buff[j + 1] = enc >> 16;
buff[j + 2] = enc >> 8;
buff[j + 3] = enc;
j += 4;
sock.sQpush32(encodings[i]);
}
sock._sQlen += j - offset;
sock.flush();
},
fbUpdateRequest(sock, incremental, x, y, w, h) {
const buff = sock._sQ;
const offset = sock._sQlen;
if (typeof(x) === "undefined") { x = 0; }
if (typeof(y) === "undefined") { y = 0; }
buff[offset] = 3; // msg-type
buff[offset + 1] = incremental ? 1 : 0;
sock.sQpush8(3); // msg-type
buff[offset + 2] = (x >> 8) & 0xFF;
buff[offset + 3] = x & 0xFF;
sock.sQpush8(incremental ? 1 : 0);
buff[offset + 4] = (y >> 8) & 0xFF;
buff[offset + 5] = y & 0xFF;
sock.sQpush16(x);
sock.sQpush16(y);
sock.sQpush16(w);
sock.sQpush16(h);
buff[offset + 6] = (w >> 8) & 0xFF;
buff[offset + 7] = w & 0xFF;
buff[offset + 8] = (h >> 8) & 0xFF;
buff[offset + 9] = h & 0xFF;
sock._sQlen += 10;
sock.flush();
},
xvpOp(sock, ver, op) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.sQpush8(250); // msg-type
buff[offset] = 250; // msg-type
buff[offset + 1] = 0; // padding
sock.sQpush8(0); // padding
buff[offset + 2] = ver;
buff[offset + 3] = op;
sock.sQpush8(ver);
sock.sQpush8(op);
sock._sQlen += 4;
sock.flush();
}
};

View File

@ -94,27 +94,7 @@ export default class Websock {
return "unknown";
}
get sQ() {
return this._sQ;
}
get rQ() {
return this._rQ;
}
get rQi() {
return this._rQi;
}
set rQi(val) {
this._rQi = val;
}
// Receive Queue
get rQlen() {
return this._rQlen - this._rQi;
}
rQpeek8() {
return this._rQ[this._rQi];
}
@ -141,42 +121,47 @@ export default class Websock {
for (let byte = bytes - 1; byte >= 0; byte--) {
res += this._rQ[this._rQi++] << (byte * 8);
}
return res;
return res >>> 0;
}
rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
let str = "";
// Handle large arrays in steps to avoid long strings on the stack
for (let i = 0; i < len; i += 4096) {
let part = this.rQshiftBytes(Math.min(4096, len - i));
let part = this.rQshiftBytes(Math.min(4096, len - i), false);
str += String.fromCharCode.apply(null, part);
}
return str;
}
rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
rQshiftBytes(len, copy=true) {
this._rQi += len;
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
if (copy) {
return this._rQ.slice(this._rQi - len, this._rQi);
} else {
return this._rQ.subarray(this._rQi - len, this._rQi);
}
}
rQshiftTo(target, len) {
if (len === undefined) { len = this.rQlen; }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len;
}
rQslice(start, end = this.rQlen) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
rQpeekBytes(len, copy=true) {
if (copy) {
return this._rQ.slice(this._rQi, this._rQi + len);
} else {
return this._rQ.subarray(this._rQi, this._rQi + len);
}
}
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
rQwait(msg, num, goback) {
if (this.rQlen < num) {
if (this._rQlen - this._rQi < num) {
if (goback) {
if (this._rQi < goback) {
throw new Error("rQwait cannot backup " + goback + " bytes");
@ -190,21 +175,56 @@ export default class Websock {
// Send Queue
sQpush8(num) {
this._sQensureSpace(1);
this._sQ[this._sQlen++] = num;
}
sQpush16(num) {
this._sQensureSpace(2);
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpush32(num) {
this._sQensureSpace(4);
this._sQ[this._sQlen++] = (num >> 24) & 0xff;
this._sQ[this._sQlen++] = (num >> 16) & 0xff;
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpushString(str) {
let bytes = str.split('').map(chr => chr.charCodeAt(0));
this.sQpushBytes(new Uint8Array(bytes));
}
sQpushBytes(bytes) {
for (let offset = 0;offset < bytes.length;) {
this._sQensureSpace(1);
let chunkSize = this._sQbufferSize - this._sQlen;
if (chunkSize > bytes.length - offset) {
chunkSize = bytes.length - offset;
}
this._sQ.set(bytes.subarray(offset, chunkSize), this._sQlen);
this._sQlen += chunkSize;
offset += chunkSize;
}
}
flush() {
if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encodeMessage());
this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
this._sQlen = 0;
}
}
send(arr) {
this._sQ.set(arr, this._sQlen);
this._sQlen += arr.length;
this.flush();
}
sendString(str) {
this.send(str.split('').map(chr => chr.charCodeAt(0)));
_sQensureSpace(bytes) {
if (this._sQbufferSize - this._sQlen < bytes) {
this.flush();
}
}
// Event Handlers
@ -283,11 +303,6 @@ export default class Websock {
}
// private methods
_encodeMessage() {
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
}
// We want to move all the unread data to the start of the queue,
// e.g. compacting.
@ -309,7 +324,7 @@ export default class Websock {
// we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
this._rQbufferSize = MAX_RQ_GROW_SIZE;
if (this._rQbufferSize - this.rQlen < minFit) {
if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
}
}
@ -327,25 +342,22 @@ export default class Websock {
}
// push arraybuffer values onto the end of the receive que
_DecodeMessage(data) {
const u8 = new Uint8Array(data);
_recvMessage(e) {
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
const u8 = new Uint8Array(e.data);
if (u8.length > this._rQbufferSize - this._rQlen) {
this._expandCompactRQ(u8.length);
}
this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length;
}
_recvMessage(e) {
this._DecodeMessage(e.data);
if (this.rQlen > 0) {
if (this._rQlen - this._rQi > 0) {
this._eventHandlers.message();
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
} else {
Log.Debug("Ignoring empty message");
}

View File

@ -81,9 +81,3 @@ None
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
| drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display
### 2.2.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing

View File

@ -55,11 +55,15 @@ export default class FakeWebSocket {
}
_receiveData(data) {
// Break apart the data to expose bugs where we assume data is
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.slice(i, i+1);
this.onmessage(new MessageEvent("message", { 'data': buf.buffer }));
if (data.length < 4096) {
// Break apart the data to expose bugs where we assume data is
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.slice(i, i+1);
this.onmessage(new MessageEvent("message", { 'data': buf.buffer }));
}
} else {
this.onmessage(new MessageEvent("message", { 'data': data.buffer }));
}
}
}

View File

@ -131,12 +131,10 @@ export default class RecordingPlayer {
_doPacket() {
// Avoid having excessive queue buildup in non-realtime mode
if (this._trafficManagement && this._rfb._flushing) {
const orig = this._rfb._display.onflush;
this._rfb._display.onflush = () => {
this._rfb._display.onflush = orig;
this._rfb._onFlush();
this._doPacket();
};
this._rfb.flush()
.then(() => {
this._doPacket();
});
return;
}
@ -150,13 +148,8 @@ export default class RecordingPlayer {
_finish() {
if (this._rfb._display.pending()) {
this._rfb._display.onflush = () => {
if (this._rfb._flushing) {
this._rfb._onFlush();
}
this._finish();
};
this._rfb._display.flush();
this._rfb._display.flush()
.then(() => { this._finish(); });
} else {
this._running = false;
this._ws.onclose({code: 1000, reason: ""});

View File

@ -298,14 +298,11 @@ describe('Display/Canvas Helper', function () {
expect(display).to.have.displayed(checkedData);
});
it('should support drawing images via #imageRect', function (done) {
it('should support drawing images via #imageRect', async function () {
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
display.flip();
display.onflush = () => {
expect(display).to.have.displayed(checkedData);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(checkedData);
});
it('should support blit images with true color via #blitImage', function () {
@ -360,12 +357,11 @@ describe('Display/Canvas Helper', function () {
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should call callback when queue is flushed', function () {
display.onflush = sinon.spy();
it('should resolve promise when queue is flushed', async function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
expect(display.onflush).to.not.have.been.called;
display.flush();
expect(display.onflush).to.have.been.calledOnce;
let promise = display.flush();
expect(promise).to.be.an.instanceOf(Promise);
await promise;
});
it('should draw a blit image on type "blit"', function () {

View File

@ -44,7 +44,7 @@ describe('JPEG Decoder', function () {
display.resize(4, 4);
});
it('should handle JPEG rects', function (done) {
it('should handle JPEG rects', async function () {
let data = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
@ -151,14 +151,11 @@ describe('JPEG Decoder', function () {
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
it('should handle JPEG rects without Huffman and quantification tables', function (done) {
it('should handle JPEG rects without Huffman and quantification tables', async function () {
let data1 = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
@ -289,10 +286,7 @@ describe('JPEG Decoder', function () {
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
});

View File

@ -814,10 +814,32 @@ describe('Remote Frame Buffer Protocol Client', function () {
let client;
beforeEach(function () {
client = makeRFB();
client._supportsSetDesktopSize = true;
client.resizeSession = true;
container.style.width = '70px';
container.style.height = '80px';
const incoming = [ 0x00, // msg-type=FBU
0x00, // padding
0x00, 0x01, // number of rects = 1
0x00, 0x00, // reason = server initialized
0x00, 0x00, // status = no error
0x00, 0x04, // new width = 4
0x00, 0x04, // new height = 4
0xff, 0xff,
0xfe, 0xcc, // enc = (-308) ExtendedDesktopSize
0x01, // number of screens = 1
0x00, 0x00,
0x00, // padding
0x78, 0x90,
0xab, 0xcd, // screen id = 0
0x00, 0x00, // screen x = 0
0x00, 0x00, // screen y = 0
0x00, 0x04, // screen width = 4
0x00, 0x04, // screen height = 4
0x12, 0x34,
0x56, 0x78]; // screen flags
client._sock._websocket._receiveData(new Uint8Array(incoming));
sinon.spy(RFB.messages, "setDesktopSize");
});
@ -833,6 +855,13 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
it('should request a resize when initially connecting', function () {
// Create a new object that hasn't yet seen a
// ExtendedDesktopSize rect
client = makeRFB();
client.resizeSession = true;
container.style.width = '70px';
container.style.height = '80px';
// Simple ExtendedDesktopSize FBU message
const incoming = [ 0x00, // msg-type=FBU
0x00, // padding
@ -846,17 +875,14 @@ describe('Remote Frame Buffer Protocol Client', function () {
0x01, // number of screens = 1
0x00, 0x00,
0x00, // padding
0x00, 0x00,
0x00, 0x00, // screen id = 0
0x78, 0x90,
0xab, 0xcd, // screen id = 0
0x00, 0x00, // screen x = 0
0x00, 0x00, // screen y = 0
0x00, 0x04, // screen width = 4
0x00, 0x04, // screen height = 4
0x00, 0x00,
0x00, 0x00]; // screen flags
// This property is indirectly used as a marker for the first update
client._supportsSetDesktopSize = false;
0x12, 0x34,
0x56, 0x78]; // screen flags
// First message should trigger a resize
@ -866,7 +892,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
// not the reported size from the server
expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 70, 80, 0, 0);
sinon.match.object, 70, 80, 0x7890abcd, 0x12345678);
RFB.messages.setDesktopSize.resetHistory();
@ -884,7 +910,8 @@ describe('Remote Frame Buffer Protocol Client', function () {
clock.tick(1000);
expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);
});
it('should not request the same size twice', function () {
@ -895,7 +922,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 40, 50, 0, 0);
sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);
// Server responds with the requested size 40x50
const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
@ -934,7 +961,8 @@ describe('Remote Frame Buffer Protocol Client', function () {
clock.tick(200);
expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);
});
it('should not resize when resize is disabled', function () {
@ -974,9 +1002,9 @@ describe('Remote Frame Buffer Protocol Client', function () {
// Simple ExtendedDesktopSize FBU message, new size: 100x100
const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x64, 0x00, 0x64, 0xff, 0xff, 0xfe, 0xcc,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xab, 0xab, 0xab, 0xab,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00 ];
0x11, 0x22, 0x33, 0x44 ];
// Note that this will cause the browser to display scrollbars
// since the framebuffer is 100x100 and the container is 70x80.
@ -996,8 +1024,8 @@ describe('Remote Frame Buffer Protocol Client', function () {
clock.tick(1000);
expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
expect(RFB.messages.setDesktopSize.firstCall.args[1]).to.equal(120);
expect(RFB.messages.setDesktopSize.firstCall.args[2]).to.equal(130);
expect(RFB.messages.setDesktopSize).to.have.been.calledWith(
sinon.match.object, 120, 130, 0xabababab, 0x11223344);
});
});

View File

@ -295,7 +295,7 @@ describe('Tight Decoder', function () {
expect(display).to.have.displayed(targetData);
});
it('should handle JPEG rects', function (done) {
it('should handle JPEG rects', async function () {
let data = [
// Control bytes
0x90, 0xd6, 0x05,
@ -410,10 +410,7 @@ describe('Tight Decoder', function () {
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
});

View File

@ -44,7 +44,7 @@ describe('TightPng Decoder', function () {
display.resize(4, 4);
});
it('should handle the TightPng encoding', function (done) {
it('should handle the TightPng encoding', async function () {
let data = [
// Control bytes
0xa0, 0xb4, 0x04,
@ -139,10 +139,7 @@ describe('TightPng Decoder', function () {
return diff < 30;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
});

View File

@ -7,91 +7,63 @@ describe('Websock', function () {
"use strict";
describe('Receive queue methods', function () {
let sock;
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
let sock, websock;
beforeEach(function () {
sock = new Websock();
// skip init
sock._allocateBuffers();
sock._rQ.set(RQ_TEMPLATE);
sock._rQlen = RQ_TEMPLATE.length;
});
describe('rQlen', function () {
it('should return the length of the receive queue', function () {
sock.rQi = 0;
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length);
});
it("should return the proper length if we read some from the receive queue", function () {
sock.rQi = 1;
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1);
});
websock = new FakeWebSocket();
websock._open();
sock.attach(websock);
});
describe('rQpeek8', function () {
it('should peek at the next byte without poping it off the queue', function () {
const befLen = sock.rQlen;
const peek = sock.rQpeek8();
expect(sock.rQpeek8()).to.equal(peek);
expect(sock.rQlen).to.equal(befLen);
websock._receiveData(new Uint8Array([0xab, 0xcd]));
expect(sock.rQpeek8()).to.equal(0xab);
expect(sock.rQpeek8()).to.equal(0xab);
});
});
describe('rQshift8()', function () {
it('should pop a single byte from the receive queue', function () {
const peek = sock.rQpeek8();
const befLen = sock.rQlen;
expect(sock.rQshift8()).to.equal(peek);
expect(sock.rQlen).to.equal(befLen - 1);
websock._receiveData(new Uint8Array([0xab, 0xcd]));
expect(sock.rQshift8()).to.equal(0xab);
expect(sock.rQshift8()).to.equal(0xcd);
});
});
describe('rQshift16()', function () {
it('should pop two bytes from the receive queue and return a single number', function () {
const befLen = sock.rQlen;
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
expect(sock.rQshift16()).to.equal(expected);
expect(sock.rQlen).to.equal(befLen - 2);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQshift16()).to.equal(0xabcd);
expect(sock.rQshift16()).to.equal(0x1234);
});
});
describe('rQshift32()', function () {
it('should pop four bytes from the receive queue and return a single number', function () {
const befLen = sock.rQlen;
const expected = (RQ_TEMPLATE[0] << 24) +
(RQ_TEMPLATE[1] << 16) +
(RQ_TEMPLATE[2] << 8) +
RQ_TEMPLATE[3];
expect(sock.rQshift32()).to.equal(expected);
expect(sock.rQlen).to.equal(befLen - 4);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQshift32()).to.equal(0x88ee1133);
});
});
describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', function () {
const befLen = sock.rQlen;
const befRQi = sock.rQi;
const shifted = sock.rQshiftStr(3);
expect(shifted).to.be.a('string');
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3))));
expect(sock.rQlen).to.equal(befLen - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftStr();
expect(sock.rQlen).to.equal(0);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQshiftStr(4)).to.equal('\xab\xcd\x12\x34');
expect(sock.rQshiftStr(4)).to.equal('\x88\xee\x11\x33');
});
it('should be able to handle very large strings', function () {
const BIG_LEN = 500000;
const RQ_BIG = new Uint8Array(BIG_LEN);
const incoming = new Uint8Array(BIG_LEN);
let expected = "";
let letterCode = 'a'.charCodeAt(0);
for (let i = 0; i < BIG_LEN; i++) {
RQ_BIG[i] = letterCode;
incoming[i] = letterCode;
expected += String.fromCharCode(letterCode);
if (letterCode < 'z'.charCodeAt(0)) {
@ -100,89 +72,77 @@ describe('Websock', function () {
letterCode = 'a'.charCodeAt(0);
}
}
sock._rQ.set(RQ_BIG);
sock._rQlen = RQ_BIG.length;
websock._receiveData(incoming);
const shifted = sock.rQshiftStr();
const shifted = sock.rQshiftStr(BIG_LEN);
expect(shifted).to.be.equal(expected);
expect(sock.rQlen).to.equal(0);
});
});
describe('rQshiftBytes', function () {
it('should shift the given number of bytes of the receive queue and return an array', function () {
const befLen = sock.rQlen;
const befRQi = sock.rQi;
const shifted = sock.rQshiftBytes(3);
expect(shifted).to.be.an.instanceof(Uint8Array);
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3));
expect(sock.rQlen).to.equal(befLen - 3);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0x88, 0xee, 0x11, 0x33]));
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftBytes();
expect(sock.rQlen).to.equal(0);
it('should return a shared array if requested', function () {
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
const bytes = sock.rQshiftBytes(4, false);
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
});
});
describe('rQslice', function () {
beforeEach(function () {
sock.rQi = 0;
});
describe('rQpeekBytes', function () {
it('should not modify the receive queue', function () {
const befLen = sock.rQlen;
sock.rQslice(0, 2);
expect(sock.rQlen).to.equal(befLen);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
});
it('should return an array containing the given slice of the receive queue', function () {
const sl = sock.rQslice(0, 2);
expect(sl).to.be.an.instanceof(Uint8Array);
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
});
it('should use the rest of the receive queue if no end is given', function () {
const sl = sock.rQslice(1);
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
});
it('should take the current rQi in to account', function () {
sock.rQi = 1;
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
it('should return a shared array if requested', function () {
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
const bytes = sock.rQpeekBytes(4, false);
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
});
});
describe('rQwait', function () {
beforeEach(function () {
sock.rQi = 0;
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
});
it('should return true if there are not enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
expect(sock.rQwait('hi', 9)).to.be.true;
});
it('should return false if there are enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
expect(sock.rQwait('hi', 8)).to.be.false;
});
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
sock.rQi = 5;
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
expect(sock.rQi).to.equal(1);
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQwait('hi', 8, 2)).to.be.true;
expect(sock.rQshift32()).to.equal(0x123488ee);
});
it('should raise an error if we try to go back more than possible', function () {
sock.rQi = 5;
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(() => sock.rQwait('hi', 8, 6)).to.throw(Error);
});
it('should not reduce rQi if there are enough bytes', function () {
sock.rQi = 5;
sock.rQwait('hi', 1, 6);
expect(sock.rQi).to.equal(5);
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQwait('hi', 4, 2)).to.be.false;
expect(sock.rQshift32()).to.equal(0x88ee1133);
});
});
});
@ -190,6 +150,8 @@ describe('Websock', function () {
describe('Send queue methods', function () {
let sock;
const bufferSize = 10 * 1024;
beforeEach(function () {
let websock = new FakeWebSocket();
websock._open();
@ -197,14 +159,187 @@ describe('Websock', function () {
sock.attach(websock);
});
describe('sQpush8()', function () {
it('should send a single byte', function () {
sock.sQpush8(42);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([42]));
});
it('should not send any data until flushing', function () {
sock.sQpush8(42);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize;i++) {
sock.sQpush8(42);
}
let expected = [];
for (let i = 0;i < bufferSize;i++) {
expected.push(42);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpush16()', function () {
it('should send a number as two bytes', function () {
sock.sQpush16(420);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([1, 164]));
});
it('should not send any data until flushing', function () {
sock.sQpush16(420);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/2;i++) {
sock.sQpush16(420);
}
let expected = [];
for (let i = 0;i < bufferSize/2;i++) {
expected.push(1);
expected.push(164);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpush32()', function () {
it('should send a number as two bytes', function () {
sock.sQpush32(420420);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0, 6, 106, 68]));
});
it('should not send any data until flushing', function () {
sock.sQpush32(420420);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/4;i++) {
sock.sQpush32(420420);
}
let expected = [];
for (let i = 0;i < bufferSize/4;i++) {
expected.push(0);
expected.push(6);
expected.push(106);
expected.push(68);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpushString()', function () {
it('should send a string buffer', function () {
sock.sQpushString('\x12\x34\x56\x78\x90');
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
});
it('should not send any data until flushing', function () {
sock.sQpushString('\x12\x34\x56\x78\x90');
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/5;i++) {
sock.sQpushString('\x12\x34\x56\x78\x90');
}
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
it('should implicitly split a large buffer', function () {
let str = '';
for (let i = 0;i <= bufferSize/5;i++) {
str += '\x12\x34\x56\x78\x90';
}
sock.sQpushString(str);
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpushBytes()', function () {
it('should send a byte buffer', function () {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
});
it('should not send any data until flushing', function () {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/5;i++) {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
}
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
it('should implicitly split a large buffer', function () {
let buffer = [];
for (let i = 0;i <= bufferSize/5;i++) {
buffer.push(0x12);
buffer.push(0x34);
buffer.push(0x56);
buffer.push(0x78);
buffer.push(0x90);
}
sock.sQpushBytes(new Uint8Array(buffer));
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('flush', function () {
it('should actually send on the websocket', function () {
sock._sQ = new Uint8Array([1, 2, 3]);
sock._sQlen = 3;
const encoded = sock._encodeMessage();
sock.flush();
expect(sock).to.have.sent(encoded);
expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
});
it('should not call send if we do not have anything queued up', function () {
@ -215,20 +350,6 @@ describe('Websock', function () {
expect(sock).to.have.sent(new Uint8Array([]));
});
});
describe('send', function () {
it('should send the given data immediately', function () {
sock.send([1, 2, 3]);
expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
});
});
describe('sendString', function () {
it('should send after converting the string to an array', function () {
sock.sendString("\x01\x02\x03");
expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
});
});
});
describe('lifecycle methods', function () {
@ -437,9 +558,8 @@ describe('Websock', function () {
sock._allocateBuffers();
});
it('should support adding binary Uint8Array data to the receive queue', function () {
it('should support adding data to the receive queue', function () {
const msg = { data: new Uint8Array([1, 2, 3]) };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
});
@ -460,80 +580,49 @@ describe('Websock', function () {
expect(sock._eventHandlers.message).not.to.have.been.called;
});
it('should compact the receive queue when a message handler empties it', function () {
sock._eventHandlers.message = () => { sock.rQi = sock._rQlen; };
it('should compact the receive queue when fully read', function () {
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
sock._rQlen = 6;
sock.rQi = 6;
sock._rQi = 6;
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(0);
expect(sock.rQi).to.equal(0);
expect(sock._rQlen).to.equal(3);
expect(sock._rQi).to.equal(0);
});
it('should compact the receive queue when we reach the end of the buffer', function () {
sock._rQ = new Uint8Array(20);
sock._rQbufferSize = 20;
sock._rQlen = 20;
sock.rQi = 10;
sock._rQi = 10;
const msg = { data: new Uint8Array([1, 2]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(12);
expect(sock.rQi).to.equal(0);
expect(sock._rQi).to.equal(0);
});
it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {
sock._rQ = new Uint8Array(20);
sock._rQlen = 0;
sock.rQi = 0;
sock._rQi = 0;
sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(30).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(30);
expect(sock.rQi).to.equal(0);
expect(sock._rQi).to.equal(0);
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
});
it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {
sock._rQ = new Uint8Array(20);
sock._rQlen = 16;
sock.rQi = 16;
sock._rQi = 15;
sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(6).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(6);
expect(sock.rQi).to.equal(0);
expect(sock._rQ.length).to.equal(48);
});
});
describe('Data encoding', function () {
before(function () { FakeWebSocket.replace(); });
after(function () { FakeWebSocket.restore(); });
describe('as binary data', function () {
let sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'binary');
sock._websocket._open();
});
it('should only send the send queue up to the send queue length', function () {
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
sock._sQlen = 3;
const res = sock._encodeMessage();
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
});
expect(sock._rQlen).to.equal(7);
expect(sock._rQi).to.equal(0);
expect(sock._rQ.length).to.equal(56);
});
});
});