/* * KasmVNC: HTML5 VNC client * Copyright (C) 2020 Kasm Technologies * 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._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) this._directDraw = false; //Draw directly to the canvas without ordering this._zlibs = []; for (let i = 0; i < 4; i++) { this._zlibs[i] = new Inflator(); } } decodeRect(x, y, width, height, data, display, depth, frame_id) { let ctl = data[12]; ctl = ctl >> 4; let ret; if (ctl === 0x08) { ret = this._fillRect(x, y, width, height, data, display, depth, frame_id); } else if (ctl === 0x09) { ret = this._jpegRect(x, y, width, height, data, display, depth, frame_id); } else if (ctl === 0x0A) { ret = this._pngRect(x, y, width, height, data, display, depth, frame_id); } else if ((ctl & 0x08) == 0) { ret = this._basicRect(ctl, x, y, width, height, data, display, depth, frame_id); } else if (ctl === 0x0B) { ret = this._webpRect(x, y, width, height, data, display, depth, frame_id); } else { throw new Error("Illegal udp compression received (ctl: " + ctl + ")"); } return ret; } _fillRect(x, y, width, height, data, display, depth, frame_id) { display.fillRect(x, y, width, height, [data[13], data[14], data[15]], frame_id, this._directDraw); return true; } _jpegRect(x, y, width, height, data, display, depth, frame_id) { let img = this._readData(data); if (img === null) { return false; } display.imageRect(x, y, width, height, "image/jpeg", img, frame_id, this._directDraw); return true; } _webpRect(x, y, width, height, data, display, depth, frame_id) { let img = this._readData(data); if (img === null) { return false; } display.imageRect(x, y, width, height, "image/webp", img, frame_id, this._directDraw); return true; } _pngRect(x, y, width, height, data, display, depth, frame_id) { //throw new Error("PNG received in UDP rect"); Log.Error("PNG received in UDP rect"); } _basicRect(ctl, x, y, width, height, data, display, depth, frame_id) { 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.Debug("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 ret = this._copyFilter(streamId, x, y, width, height, data, display, depth, frame_id, data_index); break; case 1: // PaletteFilter ret = this._paletteFilter(streamId, x, y, width, height, data, display, depth, frame_id); break; case 2: // GradientFilter ret = this._gradientFilter(streamId, x, y, width, height, data, display, depth, frame_id); break; default: throw new Error("Illegal tight filter received (ctl: " + this._filter + ")"); } return ret; } _copyFilter(streamId, x, y, width, height, data, display, depth, frame_id, data_index=14) { const uncompressedSize = width * height * 3; if (uncompressedSize === 0) { return true; } if (uncompressedSize < 12) { data = data.slice(data_index, data_index + 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, frame_id, this._directDraw); return true; } _paletteFilter(streamId, x, y, width, height, data, display, depth, frame_id) { 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 = 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 (numColors == 2) { this._monoRect(x, y, width, height, data, palette, display, frame_id); } else { this._paletteRect(x, y, width, height, data, palette, display, frame_id); } return true; } _monoRect(x, y, width, height, data, palette, display, frame_id) { // 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, frame_id, this._directDraw); } _paletteRect(x, y, width, height, data, palette, display, frame_id) { // 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, frame_id, this._directDraw); } _gradientFilter(streamId, x, y, width, height, data, display, depth, frame_id) { 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; } } //TODO: 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); } _getScratchBuffer(size) { if (!this._scratchBuffer || (this._scratchBuffer.length < size)) { this._scratchBuffer = new Uint8Array(size); } return this._scratchBuffer; } }