WIP Added udp decoder
This commit is contained in:
parent
ac8dff1c0d
commit
6da3b14d02
|
|
@ -30,8 +30,8 @@ window.updateSetting = (name, value) => {
|
|||
}
|
||||
}
|
||||
|
||||
import "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
||||
//import "core-js/stable";
|
||||
//import "regenerator-runtime/runtime";
|
||||
import * as Log from '../core/util/logging.js';
|
||||
import _, { l10n } from './localization.js';
|
||||
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold, supportsBinaryClipboard, isFirefox, isWindows, isIOS }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as Log from '../util/logging.js';
|
||||
import Inflator from "../inflator.js";
|
||||
|
||||
export default class UDPDecoder {
|
||||
constructor() {
|
||||
this._filter = null;
|
||||
this._numColors = 0;
|
||||
this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
|
||||
|
||||
this._zlibs = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._zlibs[i] = new Inflator();
|
||||
}
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, data, display, depth) {
|
||||
let ctl = data[12];
|
||||
ctl = ctl >> 4;
|
||||
|
||||
let ret;
|
||||
|
||||
if (ctl === 0x08) {
|
||||
Log.Info("Fill Rect");
|
||||
ret = this._fillRect(x, y, width, height,
|
||||
data, display, depth);
|
||||
} else if (ctl === 0x09) {
|
||||
Log.Info("Fill JPEG");
|
||||
ret = this._jpegRect(x, y, width, height,
|
||||
data, display, depth);
|
||||
} else if (ctl === 0x0A) {
|
||||
Log.Info("Fill Png");
|
||||
ret = this._pngRect(x, y, width, height,
|
||||
data, display, depth);
|
||||
} else if ((ctl & 0x08) == 0) {
|
||||
Log.Info("Fill Basic");
|
||||
ret = this._basicRect(ctl, x, y, width, height,
|
||||
data, display, depth);
|
||||
} else if (ctl === 0x0B) {
|
||||
Log.Info("Fill webp");
|
||||
ret = this._webpRect(x, y, width, height,
|
||||
data, display, depth);
|
||||
} else {
|
||||
throw new Error("Illegal udp compression received (ctl: " +
|
||||
ctl + ")");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
_fillRect(x, y, width, height, data, display, depth) {
|
||||
|
||||
display.fillRect(x, y, width, height,
|
||||
[data[13], data[14], data[15]], false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_jpegRect(x, y, width, height, data, display, depth) {
|
||||
let img = this._readData(data);
|
||||
if (img === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/jpeg", img);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_webpRect(x, y, width, height, data, display, depth) {
|
||||
let img = this._readData(data);
|
||||
if (img === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/webp", img);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_pngRect(x, y, width, height, data, display, depth) {
|
||||
//throw new Error("PNG received in UDP rect");
|
||||
Log.Error("PNG received in UDP rect");
|
||||
}
|
||||
|
||||
_basicRect(ctl, x, y, width, height, data, display, depth) {
|
||||
let zlibs_flags = data[12];
|
||||
// Reset streams if the server requests it
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if ((zlibs_flags >> i) & 1) {
|
||||
this._zlibs[i].reset();
|
||||
Log.Info("Reset zlib stream " + i);
|
||||
}
|
||||
}
|
||||
|
||||
let filter = data[13];
|
||||
let data_index = 14;
|
||||
let streamId = ctl & 0x3;
|
||||
if (!(ctl & 0x4)) {
|
||||
// Implicit CopyFilter
|
||||
filter = 0;
|
||||
data_index = 13;
|
||||
}
|
||||
|
||||
let ret;
|
||||
|
||||
switch (filter) {
|
||||
case 0: // CopyFilter
|
||||
Log.Info("Filter Copy");
|
||||
ret = this._copyFilter(streamId, x, y, width, height,
|
||||
data, display, depth, data_index);
|
||||
break;
|
||||
case 1: // PaletteFilter
|
||||
Log.Info("Filter Palette");
|
||||
ret = this._paletteFilter(streamId, x, y, width, height,
|
||||
data, display, depth);
|
||||
break;
|
||||
case 2: // GradientFilter
|
||||
Log.Info("Filter Gradient");
|
||||
ret = this._gradientFilter(streamId, x, y, width, height,
|
||||
data, display, depth);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Illegal tight filter received (ctl: " +
|
||||
this._filter + ")");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
_copyFilter(streamId, x, y, width, height, data, display, depth, data_index=14) {
|
||||
const uncompressedSize = width * height * 3;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
data = data.slice(data_index, data_index + uncompressedSize);
|
||||
//data = sock.rQshiftBytes(uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(data, data_index);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
let rgbx = new Uint8Array(width * height * 4);
|
||||
for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
|
||||
rgbx[i] = data[j];
|
||||
rgbx[i + 1] = data[j + 1];
|
||||
rgbx[i + 2] = data[j + 2];
|
||||
rgbx[i + 3] = 255; // Alpha
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, rgbx, 0, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_paletteFilter(streamId, x, y, width, height, data, display, depth) {
|
||||
const numColors = data[14] + 1;
|
||||
const paletteSize = numColors * 3;
|
||||
let palette = data.slice(15, 15 + paletteSize);
|
||||
|
||||
const bpp = (numColors <= 2) ? 1 : 8;
|
||||
const rowSize = Math.floor((width * bpp + 7) / 8);
|
||||
const uncompressedSize = rowSize * height;
|
||||
let data_i = 15 + paletteSize;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
//data = sock.rQshiftBytes(uncompressedSize);
|
||||
data = data.slice(data_i, data_i + uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(data, data_i);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
if (this._numColors == 2) {
|
||||
this._monoRect(x, y, width, height, data, palette, display);
|
||||
} else {
|
||||
this._paletteRect(x, y, width, height, data, palette, display);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_monoRect(x, y, width, height, data, palette, display) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
// TODO: reduce number of calculations inside loop
|
||||
const dest = this._getScratchBuffer(width * height * 4);
|
||||
const w = Math.floor((width + 7) / 8);
|
||||
const w1 = Math.floor(width / 8);
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
let dp, sp, x;
|
||||
for (x = 0; x < w1; x++) {
|
||||
for (let b = 7; b >= 0; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
for (let b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, dest, 0, false);
|
||||
}
|
||||
|
||||
_paletteRect(x, y, width, height, data, palette, display) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
const dest = this._getScratchBuffer(width * height * 4);
|
||||
const total = width * height * 4;
|
||||
for (let i = 0, j = 0; i < total; i += 4, j++) {
|
||||
const sp = data[j] * 3;
|
||||
dest[i] = palette[sp];
|
||||
dest[i + 1] = palette[sp + 1];
|
||||
dest[i + 2] = palette[sp + 2];
|
||||
dest[i + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, dest, 0, false);
|
||||
}
|
||||
|
||||
_gradientFilter(streamId, x, y, width, height, data, display, depth) {
|
||||
throw new Error("Gradient filter not implemented");
|
||||
}
|
||||
|
||||
_readData(data, len_index = 13) {
|
||||
if (data.length < len_index + 2) {
|
||||
Log.Error("UDP Decoder, readData, invalid data len")
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
let i = len_index;
|
||||
let byte = data[i++];
|
||||
let len = byte & 0x7f;
|
||||
// lenth field is variably sized 1 to 3 bytes long
|
||||
if (byte & 0x80) {
|
||||
byte = data[i++]
|
||||
len |= (byte & 0x7f) << 7;
|
||||
if (byte & 0x80) {
|
||||
byte = data[i++];
|
||||
len |= byte << 14;
|
||||
}
|
||||
}
|
||||
|
||||
// get rid of me
|
||||
if (data.length !== len + i) {
|
||||
console.log('Rect of size ' + len + ' with data size ' + data.length + ' index of ' + i);
|
||||
}
|
||||
|
||||
|
||||
return data.slice(i, data.length - 1);
|
||||
}
|
||||
|
||||
_getScratchBuffer(size) {
|
||||
if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
|
||||
this._scratchBuffer = new Uint8Array(size);
|
||||
}
|
||||
return this._scratchBuffer;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ export const encodings = {
|
|||
encodingHextile: 5,
|
||||
encodingTight: 7,
|
||||
encodingTightPNG: -260,
|
||||
encodingUDP: -261,
|
||||
|
||||
pseudoEncodingQualityLevel9: -23,
|
||||
pseudoEncodingQualityLevel0: -32,
|
||||
|
|
|
|||
137
core/rfb.js
137
core/rfb.js
|
|
@ -33,6 +33,7 @@ import RREDecoder from "./decoders/rre.js";
|
|||
import HextileDecoder from "./decoders/hextile.js";
|
||||
import TightDecoder from "./decoders/tight.js";
|
||||
import TightPNGDecoder from "./decoders/tightpng.js";
|
||||
import UDPDecoder from './decoders/udp.js';
|
||||
|
||||
// How many seconds to wait for a disconnect to finish
|
||||
const DISCONNECT_TIMEOUT = 3;
|
||||
|
|
@ -234,6 +235,7 @@ export default class RFB extends EventTargetMixin {
|
|||
this._decoders[encodings.encodingHextile] = new HextileDecoder();
|
||||
this._decoders[encodings.encodingTight] = new TightDecoder();
|
||||
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
||||
this._decoders[encodings.encodingUDP] = new UDPDecoder();
|
||||
|
||||
// NB: nothing that needs explicit teardown should be done
|
||||
// before this point, since this can throw an exception
|
||||
|
|
@ -979,54 +981,56 @@ export default class RFB extends EventTargetMixin {
|
|||
|
||||
if (pieces == 1) { // Handle it immediately
|
||||
Log.Info("Single Piece recieved");
|
||||
sock._insertIntoMiddle(u8.slice(12));
|
||||
me._handleUdpRect();
|
||||
me._handleUdpRect(u8.slice(12));
|
||||
} else { // Insert into wait array
|
||||
const now = Date.now();
|
||||
|
||||
if (udpBuffer.has(id)) {
|
||||
let item = udpBuffer.get(id);
|
||||
if (!item) {
|
||||
Log.Info("Item Missing id: " + id);
|
||||
return;
|
||||
}
|
||||
item.recieved_pieces += 1;
|
||||
item.data[i] = u8.slice(12);
|
||||
item.total_bytes += item.data[i].length;
|
||||
} else {
|
||||
let item = {
|
||||
total_pieces: pieces, // number of pieces expected
|
||||
arrival: now, //time first piece was recieved
|
||||
recieved_pieces: 1, // current number of pieces in data
|
||||
total_bytes: 0, // total size of all data pieces combined
|
||||
data: new Array(pieces)
|
||||
}
|
||||
item.data[i] = u8.slice(12);
|
||||
item.total_bytes = item.data[i].length;
|
||||
udpBuffer.set(id, item);
|
||||
}
|
||||
let item = udpBuffer.get(id);
|
||||
if (!item) {
|
||||
Log.Info("Item Missing id: " + id);
|
||||
return;
|
||||
}
|
||||
item.recieved_pieces += 1;
|
||||
item.data[i] = u8.slice(12);
|
||||
item.total_bytes += item.data[i].length;
|
||||
|
||||
if (item.total_pieces == item.recieved_pieces) {
|
||||
// Message is complete, combile data into a single array
|
||||
var finaldata = new Uint8Array(item.total_bytes);
|
||||
let z = 0;
|
||||
for (let x = 0; x < item.data.length; x++) {
|
||||
finaldata.set(item.data[x], z);
|
||||
z += item.data[x].length;
|
||||
}
|
||||
Log.Info('Completed message applied: ' + finaldata.length + ' ' + item.total_bytes + ' ' + item.total_pieces);
|
||||
udpBuffer.delete(id);
|
||||
me._handleUdpRect(finaldata);
|
||||
}
|
||||
} else {
|
||||
let item = {
|
||||
total_pieces: pieces, // number of pieces expected
|
||||
arrival: now, //time first piece was recieved
|
||||
recieved_pieces: 1, // current number of pieces in data
|
||||
total_bytes: 0, // total size of all data pieces combined
|
||||
data: new Array(pieces)
|
||||
}
|
||||
item.data[i] = u8.slice(12);
|
||||
item.total_bytes = item.data[i].length;
|
||||
udpBuffer.set(id, item);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this loop is inefficent and likely unneccesary.
|
||||
// perhaps just keep n number of incomplete messages
|
||||
const now = Date.now();
|
||||
for (const [key, value] of udpBuffer.entries()) {
|
||||
// Drop any messages older than 100ms
|
||||
if (now - value.arrival > 100) {
|
||||
Log.Info('Removed id: ' + key);
|
||||
udpBuffer.delete(key);
|
||||
} else if (value.total_pieces == value.recieved_pieces) {
|
||||
// Message is complete, combile data into a single array
|
||||
var finaldata = new Uint8Array(value.total_bytes);
|
||||
let z = 0;
|
||||
for (let x = 0; x < value.data.length; x++) {
|
||||
finaldata.set(value.data[x], z);
|
||||
z += value.data[x].length;
|
||||
}
|
||||
Log.Info('Completed message applied: ' + finaldata.length + ' ' + value.total_bytes + ' ' + value.total_pieces);
|
||||
sock._insertIntoMiddle(finaldata);
|
||||
udpBuffer.delete(key);
|
||||
me._handleUdpRect();
|
||||
}
|
||||
}
|
||||
for (const [key, value] of udpBuffer.entries()) {
|
||||
// Drop any messages older than 100ms
|
||||
if (now - value.arrival > 100) {
|
||||
Log.Info('Removed id: ' + key);
|
||||
udpBuffer.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -2871,32 +2875,39 @@ export default class RFB extends EventTargetMixin {
|
|||
}
|
||||
}
|
||||
|
||||
_handleUdpRect() {
|
||||
if (this._FBU.encoding === null) {
|
||||
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);
|
||||
}
|
||||
_handleUdpRect(data) {
|
||||
let frame = {
|
||||
x: (data[0] << 8) + data[1],
|
||||
y: (data[2] << 8) + data[3],
|
||||
width: (data[4] << 8) + data[5],
|
||||
height: (data[6] << 8) + data[7],
|
||||
encoding: parseInt((data[8] << 24) + (data[9] << 16) +
|
||||
(data[10] << 8) + data[11], 10)
|
||||
};
|
||||
|
||||
if (this._FBU.encoding === encodings.pseudoEncodingLastRect) {
|
||||
} else {
|
||||
if (!this._handleDataRect()) {
|
||||
switch (frame.encoding) {
|
||||
case encodings.pseudoEncodingLastRect:
|
||||
if (document.visibilityState !== "hidden") {
|
||||
this._display.flip();
|
||||
}
|
||||
break;
|
||||
case encodings.encodingTight:
|
||||
let decoder = this._decoders[encodings.encodingUDP];
|
||||
try {
|
||||
decoder.decodeRect(frame.x, frame.y,
|
||||
frame.width, frame.height,
|
||||
data, this._display,
|
||||
this._fbDepth);
|
||||
} catch (err) {
|
||||
this._fail("Error decoding rect: " + err);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.Error("Invalid rect encoding via UDP: " + frame.encoding);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._FBU.encoding === encodings.pseudoEncodingLastRect) {
|
||||
if (document.visibilityState !== "hidden") {
|
||||
this._display.flip();
|
||||
}
|
||||
}
|
||||
|
||||
this._FBU.encoding = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
4
vnc.html
4
vnc.html
|
|
@ -50,7 +50,7 @@
|
|||
<script src="vendor/interact.min.js"></script>
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<!--link rel="stylesheet" href="app/styles/base.css">
|
||||
<link rel="stylesheet" href="app/styles/base.css">
|
||||
|
||||
<script src="app/error-handler.js"></script>
|
||||
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<script type="module" crossorigin="use-credentials" src="app/ui.js"></script-->
|
||||
<script type="module" crossorigin="use-credentials" src="app/ui.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
Loading…
Reference in New Issue