From e64162f67ee16c334903a46f2d3d8d23611d10e6 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 28 May 2020 03:14:08 -0700 Subject: [PATCH] Improved Intel AMT KVM rendering speed. --- public/scripts/amt-desktop-0.0.2.js | 228 ++++++++++++------------- public/scripts/amt-redir-ws-0.1.0.js | 245 +++++++++++---------------- 2 files changed, 200 insertions(+), 273 deletions(-) diff --git a/public/scripts/amt-desktop-0.0.2.js b/public/scripts/amt-desktop-0.0.2.js index 7f28fd8d..e7f4aed7 100644 --- a/public/scripts/amt-desktop-0.0.2.js +++ b/public/scripts/amt-desktop-0.0.2.js @@ -10,10 +10,10 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { obj.canvasid = divid; obj.CanvasId = Q(divid); obj.scrolldiv = scrolldiv; - obj.canvas = Q(divid).getContext("2d"); + obj.canvas = Q(divid).getContext('2d'); obj.protocol = 2; // KVM obj.state = 0; - obj.acc = ""; + obj.acc = null; obj.ScreenWidth = 960; obj.ScreenHeight = 700; obj.width = 0; @@ -25,8 +25,6 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { obj.showmouse = true; obj.buttonmask = 0; obj.localKeyMap = true; - //obj.inbytes = 0; - //obj.outbytes = 0; obj.spare = null; obj.sparew = 0; obj.spareh = 0; @@ -61,9 +59,6 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { obj.inflate = ZLIB.inflateInit(-15); // ###END###{Inflate} - // Private method - obj.Debug = function (msg) { console.log(msg); } - obj.xxStateChange = function (newstate) { if (newstate == 0) { obj.canvas.fillStyle = '#000000'; @@ -76,70 +71,78 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { } } - obj.ProcessData = function (data) { - if (!data) return; - //obj.Debug("KRecv(" + data.length + "): " + rstr2hex(data)); - //obj.inbytes += data.length; - //obj.Debug("KRecv(" + obj.inbytes + ")"); - obj.acc += data; - while (obj.acc.length > 0) { - //obj.Debug("KAcc(" + obj.acc.length + "): " + rstr2hex(obj.acc)); - var cmdsize = 0; - if (obj.state == 0 && obj.acc.length >= 12) { + function arrToStr(arr) { return String.fromCharCode.apply(null, arr); } + function strToArr(str) { var arr = new Uint8Array(str.length); for (var i = 0, j = str.length; i < j; ++i) { arr[i] = str.charCodeAt(i); } return arr } + + obj.ProcessBinaryData = function (data) { + // Append to accumulator + if (obj.acc == null) { + obj.acc = new Uint8Array(data); + } else { + var tmp = new Uint8Array(obj.acc.byteLength + data.byteLength); + tmp.set(obj.acc, 0); + tmp.set(new Uint8Array(data), obj.acc.byteLength); + obj.acc = tmp; + } + + while ((obj.acc != null) && (obj.acc.byteLength > 0)) { + //console.log('KAcc', obj.state, obj.acc); + var cmdsize = 0, accview = new DataView(obj.acc.buffer); + if ((obj.state == 0) && (obj.acc.byteLength >= 12)) { // Getting handshake & version cmdsize = 12; //if (obj.acc.substring(0, 4) != "RFB ") { return obj.Stop(); } //var version = parseFloat(obj.acc.substring(4, 11)); - //obj.Debug("KVersion: " + version); + //console.log("KVersion: " + version); obj.state = 1; - obj.send("RFB 003.008\n"); + obj.send('RFB 003.008\n'); } - else if (obj.state == 1 && obj.acc.length >= 1) { + else if ((obj.state == 1) && (obj.acc.byteLength >= 1)) { // Getting security options - cmdsize = obj.acc.charCodeAt(0) + 1; + cmdsize = obj.acc[0] + 1; obj.send(String.fromCharCode(1)); // Send the "None" security type. Since we already authenticated using redirection digest auth, we don't need to do this again. obj.state = 2; } - else if (obj.state == 2 && obj.acc.length >= 4) { + else if ((obj.state == 2) && (obj.acc.byteLength >= 4)) { // Getting security response cmdsize = 4; - if (ReadInt(obj.acc, 0) != 0) { return obj.Stop(); } + if (accview.getUint32(0) != 0) { return obj.Stop(); } obj.send(String.fromCharCode(1)); // Send share desktop flag obj.state = 3; } - else if (obj.state == 3 && obj.acc.length >= 24) { + else if ((obj.state == 3) && (obj.acc.byteLength >= 24)) { // Getting server init // ###BEGIN###{DesktopRotation} obj.rotation = 0; // We don't currently support screen init while rotated. // ###END###{DesktopRotation} - var namelen = ReadInt(obj.acc, 20); - if (obj.acc.length < 24 + namelen) return; + var namelen = accview.getUint32(20); + if (obj.acc.byteLength < 24 + namelen) return; cmdsize = 24 + namelen; - obj.canvas.canvas.width = obj.rwidth = obj.width = obj.ScreenWidth = ReadShort(obj.acc, 0); - obj.canvas.canvas.height = obj.rheight = obj.height = obj.ScreenHeight = ReadShort(obj.acc, 2); + obj.canvas.canvas.width = obj.rwidth = obj.width = obj.ScreenWidth = accview.getUint16(0); + obj.canvas.canvas.height = obj.rheight = obj.height = obj.ScreenHeight = accview.getUint16(2); // These are all values we don't really need, we are going to only run in RGB565 or RGB332 and not use the flexibility provided by these settings. // Makes the javascript code smaller and maybe a bit faster. /* - obj.xbpp = obj.acc.charCodeAt(4); - obj.depth = obj.acc.charCodeAt(5); - obj.bigend = obj.acc.charCodeAt(6); - obj.truecolor = obj.acc.charCodeAt(7); + obj.xbpp = obj.acc[4]; + obj.depth = obj.acc[5]; + obj.bigend = obj.acc[6]; + obj.truecolor = obj.acc[7]; obj.rmax = ReadShort(obj.acc, 8); obj.gmax = ReadShort(obj.acc, 10); obj.bmax = ReadShort(obj.acc, 12); - obj.rsh = obj.acc.charCodeAt(14); - obj.gsh = obj.acc.charCodeAt(15); - obj.bsh = obj.acc.charCodeAt(16); + obj.rsh = obj.acc[14]; + obj.gsh = obj.acc[15]; + obj.bsh = obj.acc[16]; var name = obj.acc.substring(24, 24 + namelen); - obj.Debug("name: " + name); - obj.Debug("width: " + obj.width + ", height: " + obj.height); - obj.Debug("bits-per-pixel: " + obj.xbpp); - obj.Debug("depth: " + obj.depth); - obj.Debug("big-endian-flag: " + obj.bigend); - obj.Debug("true-colour-flag: " + obj.truecolor); - obj.Debug("rgb max: " + obj.rmax + "," + obj.gmax + "," + obj.bmax); - obj.Debug("rgb shift: " + obj.rsh + "," + obj.gsh + "," + obj.bsh); + console.log("name: " + name); + console.log("width: " + obj.width + ", height: " + obj.height); + console.log("bits-per-pixel: " + obj.xbpp); + console.log("depth: " + obj.depth); + console.log("big-endian-flag: " + obj.bigend); + console.log("true-colour-flag: " + obj.truecolor); + console.log("rgb max: " + obj.rmax + "," + obj.gmax + "," + obj.bmax); + console.log("rgb shift: " + obj.rsh + "," + obj.gsh + "," + obj.bsh); */ // SetEncodings, with AMT we can't omit RAW, must be specified. @@ -170,36 +173,36 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); } } else if (obj.state == 4) { - switch (obj.acc.charCodeAt(0)) { + switch (obj.acc[0]) { case 0: // FramebufferUpdate - if (obj.acc.length < 4) return; - obj.state = 100 + ReadShort(obj.acc, 2); // Read the number of tiles that are going to be sent, add 100 and use that as our protocol state. + if (obj.acc.byteLength < 4) return; + obj.state = 100 + accview.getUint16(2); // Read the number of tiles that are going to be sent, add 100 and use that as our protocol state. cmdsize = 4; break; case 2: // This is the bell, do nothing. cmdsize = 1; break; case 3: // This is ServerCutText - if (obj.acc.length < 8) return; - var len = ReadInt(obj.acc, 4) + 8; - if (obj.acc.length < len) return; - cmdsize = handleServerCutText(obj.acc); + if (obj.acc.byteLength < 8) return; + var len = accview.getUint32(4) + 8; + if (obj.acc.byteLength < len) return; + cmdsize = handleServerCutText(obj.acc, accview); break; } } - else if (obj.state > 100 && obj.acc.length >= 12) { - var x = ReadShort(obj.acc, 0), - y = ReadShort(obj.acc, 2), - width = ReadShort(obj.acc, 4), - height = ReadShort(obj.acc, 6), + else if ((obj.state > 100) && (obj.acc.byteLength >= 12)) { + var x = accview.getUint16(0), + y = accview.getUint16(2), + width = accview.getUint16(4), + height = accview.getUint16(6), s = width * height, - encoding = ReadInt(obj.acc, 8); + encoding = accview.getUint32(8); if (encoding < 17) { - if (width < 1 || width > 64 || height < 1 || height > 64) { console.log("Invalid tile size (" + width + "," + height + "), disconnecting."); return obj.Stop(); } + if ((width < 1) || (width > 64) || (height < 1) || (height > 64)) { console.log("Invalid tile size (" + width + "," + height + "), disconnecting."); return obj.Stop(); } - // Set the spare bitmap to the rigth size if it's not already. This allows us to recycle the spare most if not all the time. - if (obj.sparew != width || obj.spareh != height) { + // Set the spare bitmap to the right size if it's not already. This allows us to recycle the spare most if not all the time. + if ((obj.sparew != width) || (obj.spareh != height)) { obj.sparew = obj.sparew2 = width; obj.spareh = obj.spareh2 = height; // ###BEGIN###{DesktopRotation} @@ -222,51 +225,43 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { obj.send(String.fromCharCode(3, 0, 0, 0, 0, 0) + ShortToStr(obj.width) + ShortToStr(obj.height)); // FramebufferUpdateRequest cmdsize = 12; if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); } - // obj.Debug("New desktop width: " + obj.width + ", height: " + obj.height); - } - else if (encoding == 0) { + //console.log("New desktop width: " + obj.width + ", height: " + obj.height); + } else if (encoding == 0) { // RAW encoding var ptr = 12, cs = 12 + (s * obj.bpp); - if (obj.acc.length < cs) return; // Check we have all the data needed and we can only draw 64x64 tiles. + if (obj.acc.byteLength < cs) return; // Check we have all the data needed and we can only draw 64x64 tiles. cmdsize = cs; // CRITICAL LOOP, optimize this as much as possible if (obj.bpp == 2) { - for (var i = 0; i < s; i++) { _setPixel16(obj.acc.charCodeAt(ptr++) + (obj.acc.charCodeAt(ptr++) << 8), i); } + for (var i = 0; i < s; i++) { _setPixel16(accview.getUint16(ptr, true), i); ptr += 2; } } else { - for (var i = 0; i < s; i++) { _setPixel8(obj.acc.charCodeAt(ptr++), i); } + for (var i = 0; i < s; i++) { _setPixel8(obj.acc[ptr++], i); } } _putImage(obj.spare, x, y); - } - else if (encoding == 16) { + } else if (encoding == 16) { // ZRLE encoding - if (obj.acc.length < 16) return; - var datalen = ReadInt(obj.acc, 12); - if (obj.acc.length < (16 + datalen)) return; - //obj.Debug("RECT ZRLE (" + x + "," + y + "," + width + "," + height + ") LEN = " + datalen); - //obj.Debug("RECT ZRLE LEN: " + ReadShortX(obj.acc, 17) + ", DATA: " + rstr2hex(obj.acc.substring(16))); + if (obj.acc.byteLength < 16) return; + var datalen = accview.getUint32(12); + if (obj.acc.byteLength < (16 + datalen)) return; // Process the ZLib header if this is the first block var ptr = 16, delta = 5, dx = 0; - if (datalen > 5 && obj.acc.charCodeAt(ptr) == 0 && ReadShortX(obj.acc, ptr + 1) == (datalen - delta)) { + if ((datalen > 5) && (obj.acc[ptr] == 0) && (accview.getUint16(ptr + 1, true) == (datalen - delta))) { // This is an uncompressed ZLib data block _decodeLRE(obj.acc, ptr + 5, x, y, width, height, s, datalen); } // ###BEGIN###{Inflate} else { - // This is compressed ZLib data, decompress and process it. - var arr = obj.inflate.inflate(obj.acc.substring(ptr, ptr + datalen - dx)); - if (arr.length > 0) { _decodeLRE(arr, 0, x, y, width, height, s, arr.length); } else { obj.Debug("Invalid deflate data"); } + // This is compressed ZLib data, decompress and process it. (TODO: This need to be optimized, remove str/arr conversions) + var str = obj.inflate.inflate(arrToStr(obj.acc.slice(ptr, ptr + datalen - dx))); + if (str.length > 0) { _decodeLRE(strToArr(str), 0, x, y, width, height, s, str.length); } else { console.log("Invalid deflate data"); } } // ###END###{Inflate} cmdsize = 16 + datalen; - } - else { - obj.Debug("Unknown Encoding: " + encoding); - return obj.Stop(); - } + } else { return obj.Stop(); } if (--obj.state == 100) { obj.state = 4; if (obj.frameRateDelay == 0) { @@ -277,26 +272,26 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { } } + //console.log('cmdsize', cmdsize); if (cmdsize == 0) return; - obj.acc = obj.acc.substring(cmdsize); + if (cmdsize != obj.acc.byteLength) { obj.acc = obj.acc.slice(cmdsize); } else { obj.acc = null; } } } function _decodeLRE(data, ptr, x, y, width, height, s, datalen) { - var subencoding = data.charCodeAt(ptr++), index, v, runlengthdecode, palette = {}, rlecount = 0, runlength = 0, i; - // obj.Debug("RECT RLE (" + (datalen - 5) + ", " + subencoding + "):" + rstr2hex(data.substring(21, 21 + (datalen - 5)))); + var subencoding = data[ptr++], index, v, runlengthdecode, palette = {}, rlecount = 0, runlength = 0, i; if (subencoding == 0) { // RAW encoding if (obj.bpp == 2) { - for (i = 0; i < s; i++) { _setPixel16(data.charCodeAt(ptr++) + (data.charCodeAt(ptr++) << 8), i); } + for (i = 0; i < s; i++) { _setPixel16(data[ptr++] + (data[ptr++] << 8), i); } } else { - for (i = 0; i < s; i++) { _setPixel8(data.charCodeAt(ptr++), i); } + for (i = 0; i < s; i++) { _setPixel8(data[ptr++], i); } } _putImage(obj.spare, x, y); } else if (subencoding == 1) { // Solid color tile - v = data.charCodeAt(ptr++) + ((obj.bpp == 2) ? (data.charCodeAt(ptr++) << 8) : 0); + v = data[ptr++] + ((obj.bpp == 2) ? (data[ptr++] << 8) : 0); obj.canvas.fillStyle = 'rgb(' + ((obj.bpp == 1) ? ((v & 224) + ',' + ((v & 28) << 3) + ',' + _fixColor((v & 3) << 6)) : (((v >> 8) & 248) + ',' + ((v >> 3) & 252) + ',' + ((v & 31) << 3))) + ')'; // ###BEGIN###{DesktopRotation} @@ -311,24 +306,24 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { // Read the palette var br = 4, bm = 15; // br is BitRead and bm is BitMask. By adjusting these two we can support all the variations in this encoding. if (obj.bpp == 2) { - for (i = 0; i < subencoding; i++) { palette[i] = data.charCodeAt(ptr++) + (data.charCodeAt(ptr++) << 8); } + for (i = 0; i < subencoding; i++) { palette[i] = data[ptr++] + (data[ptr++] << 8); } if (subencoding == 2) { br = 1; bm = 1; } else if (subencoding <= 4) { br = 2; bm = 3; } // Compute bits to read & bit mark - while (rlecount < s && ptr < data.length) { v = data.charCodeAt(ptr++); for (i = (8 - br) ; i >= 0; i -= br) { _setPixel16(palette[(v >> i) & bm], rlecount++); } } // Display all the bits + while (rlecount < s && ptr < data.byteLength) { v = data[ptr++]; for (i = (8 - br); i >= 0; i -= br) { _setPixel16(palette[(v >> i) & bm], rlecount++); } } // Display all the bits } else { - for (i = 0; i < subencoding; i++) { palette[i] = data.charCodeAt(ptr++); } + for (i = 0; i < subencoding; i++) { palette[i] = data[ptr++]; } if (subencoding == 2) { br = 1; bm = 1; } else if (subencoding <= 4) { br = 2; bm = 3; } // Compute bits to read & bit mark - while (rlecount < s && ptr < data.length) { v = data.charCodeAt(ptr++); for (i = (8 - br) ; i >= 0; i -= br) { _setPixel8(palette[(v >> i) & bm], rlecount++); } } // Display all the bits + while (rlecount < s && ptr < data.byteLength) { v = data[ptr++]; for (i = (8 - br) ; i >= 0; i -= br) { _setPixel8(palette[(v >> i) & bm], rlecount++); } } // Display all the bits } _putImage(obj.spare, x, y); } else if (subencoding == 128) { // RLE encoded tile if (obj.bpp == 2) { - while (rlecount < s && ptr < data.length) { + while (rlecount < s && ptr < data.byteLength) { // Get the run color - v = data.charCodeAt(ptr++) + (data.charCodeAt(ptr++) << 8); + v = data[ptr++] + (data[ptr++] << 8); // Decode the run length. This is the fastest and most compact way I found to do this. - runlength = 1; do { runlength += (runlengthdecode = data.charCodeAt(ptr++)); } while (runlengthdecode == 255); + runlength = 1; do { runlength += (runlengthdecode = data[ptr++]); } while (runlengthdecode == 255); // Draw a run if (obj.rotation == 0) { @@ -338,12 +333,12 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { } } } else { - while (rlecount < s && ptr < data.length) { + while (rlecount < s && ptr < data.byteLength) { // Get the run color - v = data.charCodeAt(ptr++); + v = data[ptr++]; // Decode the run length. This is the fastest and most compact way I found to do this. - runlength = 1; do { runlength += (runlengthdecode = data.charCodeAt(ptr++)); } while (runlengthdecode == 255); + runlength = 1; do { runlength += (runlengthdecode = data[ptr++]); } while (runlengthdecode == 255); // Draw a run if (obj.rotation == 0) { @@ -358,18 +353,18 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { else if (subencoding > 129) { // Palette RLE encoded tile // Read the palette if (obj.bpp == 2) { - for (i = 0; i < (subencoding - 128) ; i++) { palette[i] = data.charCodeAt(ptr++) + (data.charCodeAt(ptr++) << 8); } + for (i = 0; i < (subencoding - 128) ; i++) { palette[i] = data[ptr++] + (data[ptr++] << 8); } } else { - for (i = 0; i < (subencoding - 128) ; i++) { palette[i] = data.charCodeAt(ptr++); } + for (i = 0; i < (subencoding - 128) ; i++) { palette[i] = data[ptr++]; } } // Decode RLE on palette - while (rlecount < s && ptr < data.length) { + while (rlecount < s && ptr < data.byteLength) { // Setup the run, get the color index and get the color from the palette. - runlength = 1; index = data.charCodeAt(ptr++); v = palette[index % 128]; + runlength = 1; index = data[ptr++]; v = palette[index % 128]; // If the index starts with high order bit 1, this is a run and decode the run length. - if (index > 127) { do { runlength += (runlengthdecode = data.charCodeAt(ptr++)); } while (runlengthdecode == 255); } + if (index > 127) { do { runlength += (runlengthdecode = data[ptr++]); } while (runlengthdecode == 255); } // Draw a run if (obj.rotation == 0) { @@ -582,12 +577,9 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { } obj.Start = function () { - //obj.Debug("KVM-Start"); obj.state = 0; - obj.acc = ""; + obj.acc = null; obj.ZRLEfirst = 1; - //obj.inbytes = 0; - //obj.outbytes = 0; // ###BEGIN###{Inflate} obj.inflate.inflateReset(); // ###END###{Inflate} @@ -599,17 +591,8 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { for (var i in obj.sparecache) { delete obj.sparecache[i]; } } - obj.Stop = function () { - obj.UnGrabMouseInput(); - obj.UnGrabKeyInput(); - if (obj.parent) { obj.parent.Stop(); } - } - - obj.send = function (x) { - //obj.Debug("KSend(" + x.length + "): " + rstr2hex(x)); - //obj.outbytes += x.length; - if (obj.parent) { obj.parent.send(x); } - } + obj.Stop = function () { obj.UnGrabMouseInput(); obj.UnGrabKeyInput(); if (obj.parent) { obj.parent.Stop(); } } + obj.send = function (x) { if (obj.parent) { obj.parent.send(x); } } var convertAmtKeyCodeTable = { "Pause": 19, @@ -748,20 +731,19 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { obj.sendkey = function (k, d) { if (typeof k == 'object') { - //for (var i in k) { obj.sendkey(k[i][0], k[i][1]); } var buf = ''; for (var i in k) { buf += (String.fromCharCode(4, k[i][1], 0, 0) + IntToStr(k[i][0])); } obj.send(buf); } else { obj.send(String.fromCharCode(4, d, 0, 0) + IntToStr(k)); } } - function handleServerCutText(acc) { - if (acc.length < 8) return 0; - var len = ReadInt(obj.acc, 4) + 8; - if (acc.length < len) return 0; + function handleServerCutText(acc, accview) { + if (acc.byteLength < 8) return 0; + var len = accview.getUint32(4) + 8; + if (acc.byteLength < len) return 0; // ###BEGIN###{DesktopInband} if (obj.onKvmData != null) { - var d = acc.substring(8, len); + var d = arrToStr(acc.slice(8, len)); if ((d.length >= 16) && (d.substring(0, 15) == '\0KvmDataChannel')) { if (obj.kvmDataSupported == false) { obj.kvmDataSupported = true; console.log('KVM Data Channel Supported.'); } if (((obj.onKvmDataAck == -1) && (d.length == 16)) || (d.charCodeAt(15) != 0)) { obj.onKvmDataAck = true; } diff --git a/public/scripts/amt-redir-ws-0.1.0.js b/public/scripts/amt-redir-ws-0.1.0.js index d03c387b..c3aba1e2 100644 --- a/public/scripts/amt-redir-ws-0.1.0.js +++ b/public/scripts/amt-redir-ws-0.1.0.js @@ -1,7 +1,7 @@ /** * @description Intel AMT Redirection Transport Module - using websocket relay * @author Ylian Saint-Hilaire -* @version v0.0.1f +* @version v2.0.0 */ // Construct a MeshServer object @@ -17,22 +17,19 @@ var CreateAmtRedirect = function (module, authCookie) { obj.port = 0; obj.user = null; obj.pass = null; - obj.authuri = "/RedirectionService"; + obj.authuri = '/RedirectionService'; obj.tlsv1only = 0; obj.inDataCount = 0; // ###END###{!Mode-Firmware} obj.connectstate = 0; obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER - obj.debugmode = 0; - - obj.amtaccumulator = ""; + obj.acc = null; obj.amtsequence = 1; obj.amtkeepalivetimer = null; - obj.onStateChanged = null; - // Private method - //obj.Debug = function (msg) { console.log(msg); } + function arrToStr(arr) { return String.fromCharCode.apply(null, arr); } + function randomHex(length) { var r = ''; for (var i = 0; i < length; i++) { r += 'abcdef0123456789'.charAt(Math.floor(Math.random() * 16)); } return r; } obj.Start = function (host, port, user, pass, tls) { obj.host = host; @@ -41,9 +38,10 @@ var CreateAmtRedirect = function (module, authCookie) { obj.pass = pass; obj.connectstate = 0; obj.inDataCount = 0; - var url = window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/webrelay.ashx?p=2&host=" + host + "&port=" + port + "&tls=" + tls + ((user == '*') ? "&serverauth=1" : "") + ((typeof pass === "undefined") ? ("&serverauth=1&user=" + user) : ""); // The "p=2" indicates to the relay that this is a REDIRECTION session + var url = window.location.protocol.replace('http', 'ws') + '//' + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/webrelay.ashx?p=2&host=' + host + '&port=' + port + '&tls=' + tls + ((user == '*') ? '&serverauth=1' : '') + ((typeof pass === 'undefined') ? ('&serverauth=1&user=' + user) : ''); // The 'p=2' indicates to the relay that this is a REDIRECTION session if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; } obj.socket = new WebSocket(url); + obj.socket.binaryType = 'arraybuffer'; obj.socket.onopen = obj.xxOnSocketConnected; obj.socket.onmessage = obj.xxOnMessage; obj.socket.onclose = obj.xxOnSocketClosed; @@ -51,80 +49,44 @@ var CreateAmtRedirect = function (module, authCookie) { } obj.xxOnSocketConnected = function () { - //obj.Debug("Redir Socket Connected"); - if (obj.debugmode == 1) { console.log('onSocketConnected'); } obj.xxStateChange(2); - if (obj.protocol == 1) obj.xxSend(obj.RedirectStartSol); // TODO: Put these strings in higher level module to tighten code - if (obj.protocol == 2) obj.xxSend(obj.RedirectStartKvm); // Don't need these is the feature is not compiled-in. - if (obj.protocol == 3) obj.xxSend(obj.RedirectStartIder); - } - - // Setup the file reader - var fileReader = new FileReader(); - var fileReaderInuse = false, fileReaderAcc = []; - if (fileReader.readAsBinaryString) { - // Chrome & Firefox (Draft) - fileReader.onload = function (e) { obj.xxOnSocketData(e.target.result); if (fileReaderAcc.length == 0) { fileReaderInuse = false; } else { fileReader.readAsBinaryString(new Blob([fileReaderAcc.shift()])); } } - } else if (fileReader.readAsArrayBuffer) { - // Chrome & Firefox (Spec) - fileReader.onloadend = function (e) { obj.xxOnSocketData(e.target.result); if (fileReaderAcc.length == 0) { fileReaderInuse = false; } else { fileReader.readAsArrayBuffer(fileReaderAcc.shift()); } } + if (obj.protocol == 1) obj.directSend(new Uint8Array([0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20])); // SOL + if (obj.protocol == 2) obj.directSend(new Uint8Array([0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52])); // KVM + if (obj.protocol == 3) obj.directSend(new Uint8Array([0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52])); // IDER } obj.xxOnMessage = function (e) { - //if (obj.debugmode == 1) { console.log('Recv', e.data); } + if (!e.data || obj.connectstate == -1) return; obj.inDataCount++; - if (typeof e.data == 'object') { - if (fileReaderInuse == true) { fileReaderAcc.push(e.data); return; } - if (fileReader.readAsBinaryString) { - // Chrome & Firefox (Draft) - fileReaderInuse = true; - fileReader.readAsBinaryString(new Blob([e.data])); - } else if (fileReader.readAsArrayBuffer) { - // Chrome & Firefox (Spec) - fileReaderInuse = true; - fileReader.readAsArrayBuffer(e.data); - } else { - // IE10, readAsBinaryString does not exist, use an alternative. - var binary = "", bytes = new Uint8Array(e.data), length = bytes.byteLength; - for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } - obj.xxOnSocketData(binary); - } + + // KVM traffic, forward it directly. + if ((obj.connectstate == 1) && ((obj.protocol == 2) || (obj.protocol == 3))) { + return obj.m.ProcessBinaryData ? obj.m.ProcessBinaryData(e.data) : obj.m.ProcessData(arrToStr(e.data)); + } + + // Append to accumulator + if (obj.acc == null) { + obj.acc = e.data; } else { - // If we get a string object, it maybe the WebRTC confirm. Ignore it. - // obj.debug("MeshDataChannel - OnData - " + typeof e.data + " - " + e.data.length); - obj.xxOnSocketData(e.data); + var tmp = new Uint8Array(obj.acc.byteLength + e.data.byteLength); + tmp.set(new Uint8Array(obj.acc), 0); + tmp.set(new Uint8Array(e.data), obj.acc.byteLength); + obj.acc = tmp.buffer; } - }; - - obj.xxOnSocketData = function (data) { - if (!data || obj.connectstate == -1) return; - if (typeof data === 'object') { - // This is an ArrayBuffer, convert it to a string array (used in IE) - var binary = ""; - var bytes = new Uint8Array(data); - var length = bytes.byteLength; - for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } - data = binary; - } - else if (typeof data !== 'string') { return; } - - if ((obj.protocol == 2 || obj.protocol == 3) && obj.connectstate == 1) { return obj.m.ProcessData(data); } // KVM traffic, forward it directly. - obj.amtaccumulator += data; - //obj.Debug("Redir Recv(" + obj.amtaccumulator.length + "): " + rstr2hex(obj.amtaccumulator)); - while (obj.amtaccumulator.length >= 1) { - var cmdsize = 0; - switch (obj.amtaccumulator.charCodeAt(0)) { + //console.log('Redir Recv', obj.acc); + while ((obj.acc != null) && (obj.acc.byteLength >= 1)) { + var cmdsize = 0, accArray = new Uint8Array(obj.acc); + switch (accArray[0]) { case 0x11: // StartRedirectionSessionReply (17) - if (obj.amtaccumulator.length < 4) return; - var statuscode = obj.amtaccumulator.charCodeAt(1); + if (accArray.byteLength < 4) return; + var statuscode = accArray[1]; switch (statuscode) { case 0: // STATUS_SUCCESS - if (obj.amtaccumulator.length < 13) return; - var oemlen = obj.amtaccumulator.charCodeAt(12); - if (obj.amtaccumulator.length < 13 + oemlen) return; - // Query for available authentication - obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)); // Query authentication support + if (accArray.byteLength < 13) return; + var oemlen = accArray[12]; + if (accArray.byteLength < 13 + oemlen) return; + obj.directSend(new Uint8Array([0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); // Query for available authentication cmdsize = (13 + oemlen); break; default: @@ -133,14 +95,12 @@ var CreateAmtRedirect = function (module, authCookie) { } break; case 0x14: // AuthenticateSessionReply (20) - if (obj.amtaccumulator.length < 9) return; - var authDataLen = ReadIntX(obj.amtaccumulator, 5); - if (obj.amtaccumulator.length < 9 + authDataLen) return; - var status = obj.amtaccumulator.charCodeAt(1); - var authType = obj.amtaccumulator.charCodeAt(4); - var authData = []; - for (i = 0; i < authDataLen; i++) { authData.push(obj.amtaccumulator.charCodeAt(9 + i)); } - var authDataBuf = obj.amtaccumulator.substring(9, 9 + authDataLen); + if (accArray.byteLength < 9) return; + var authDataLen = new DataView(obj.acc).getUint32(5, true); + if (accArray.byteLength < 9 + authDataLen) return; + var status = accArray[1], authType = accArray[4], authData = []; + for (i = 0; i < authDataLen; i++) { authData.push(accArray[9 + i]); } + var authDataBuf = accArray.slice(9, 9 + authDataLen); cmdsize = 9 + authDataLen; if (authType == 0) { // Query @@ -157,65 +117,67 @@ var CreateAmtRedirect = function (module, authCookie) { obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x01) + IntToStrX(obj.user.length + obj.pass.length + 2) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(obj.pass.length) + obj.pass); } else obj.Stop(2); - } - else if ((authType == 3 || authType == 4) && status == 1) { + } else if (((authType == 3) || (authType == 4)) && (status == 1)) { var curptr = 0; // Realm - var realmlen = authDataBuf.charCodeAt(curptr); - var realm = authDataBuf.substring(curptr + 1, curptr + 1 + realmlen); + var realmlen = authDataBuf[curptr]; + var realm = arrToStr(authDataBuf.slice(curptr + 1, curptr + 1 + realmlen)); curptr += (realmlen + 1); // Nonce - var noncelen = authDataBuf.charCodeAt(curptr); - var nonce = authDataBuf.substring(curptr + 1, curptr + 1 + noncelen); + var noncelen = authDataBuf[curptr]; + var nonce = arrToStr(authDataBuf.slice(curptr + 1, curptr + 1 + noncelen)); curptr += (noncelen + 1); // QOP var qoplen = 0; var qop = null; - var cnonce = obj.xxRandomNonce(32); + var cnonce = randomHex(32); var snc = '00000002'; var extra = ''; if (authType == 4) { - qoplen = authDataBuf.charCodeAt(curptr); - qop = authDataBuf.substring(curptr + 1, curptr + 1 + qoplen); + qoplen = authDataBuf[curptr]; + qop = arrToStr(authDataBuf.slice(curptr + 1, curptr + 1 + qoplen)); curptr += (qoplen + 1); - extra = snc + ":" + cnonce + ":" + qop + ":"; + extra = snc + ':' + cnonce + ':' + qop + ':'; } - var digest = hex_md5(hex_md5(obj.user + ":" + realm + ":" + obj.pass) + ":" + nonce + ":" + extra + hex_md5("POST:" + obj.authuri)); + var digest = hex_md5(hex_md5(obj.user + ':' + realm + ':' + obj.pass) + ':' + nonce + ':' + extra + hex_md5('POST:' + obj.authuri)); var totallen = obj.user.length + realm.length + nonce.length + obj.authuri.length + cnonce.length + snc.length + digest.length + 7; if (authType == 4) totallen += (qop.length + 1); var buf = String.fromCharCode(0x13, 0x00, 0x00, 0x00, authType) + IntToStrX(totallen) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(realm.length) + realm + String.fromCharCode(nonce.length) + nonce + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(cnonce.length) + cnonce + String.fromCharCode(snc.length) + snc + String.fromCharCode(digest.length) + digest; if (authType == 4) buf += (String.fromCharCode(qop.length) + qop); obj.xxSend(buf); - } - else - if (status == 0) { // Success - if (obj.protocol == 1) { - // Serial-over-LAN: Send Intel AMT serial settings... - var MaxTxBuffer = 10000; - var TxTimeout = 100; - var TxOverflowTimeout = 0; - var RxTimeout = 10000; - var RxFlushTimeout = 100; - var Heartbeat = 0;//5000; - obj.xxSend(String.fromCharCode(0x20, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(MaxTxBuffer) + ShortToStrX(TxTimeout) + ShortToStrX(TxOverflowTimeout) + ShortToStrX(RxTimeout) + ShortToStrX(RxFlushTimeout) + ShortToStrX(Heartbeat) + IntToStrX(0)); - } - if (obj.protocol == 2) { - // Remote Desktop: Send traffic directly... - obj.xxSend(String.fromCharCode(0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)); - } - if (obj.protocol == 3) { - // Remote IDER: Send traffic directly... - obj.connectstate = 1; - obj.xxStateChange(3); + } else if (status == 0) { // Success + switch (obj.protocol) { + case 1: { + // Serial-over-LAN: Send Intel AMT serial settings... + var MaxTxBuffer = 10000; + var TxTimeout = 100; + var TxOverflowTimeout = 0; + var RxTimeout = 10000; + var RxFlushTimeout = 100; + var Heartbeat = 0;//5000; + obj.xxSend(String.fromCharCode(0x20, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(MaxTxBuffer) + ShortToStrX(TxTimeout) + ShortToStrX(TxOverflowTimeout) + ShortToStrX(RxTimeout) + ShortToStrX(RxFlushTimeout) + ShortToStrX(Heartbeat) + IntToStrX(0)); + break; + } + case 2: { + // Remote Desktop: Send traffic directly... + obj.directSend(new Uint8Array([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); + break; + } + case 3: { + // Remote IDER: Send traffic directly... + obj.connectstate = 1; + obj.xxStateChange(3); + break; + } } } else obj.Stop(3); break; case 0x21: // Response to settings (33) - if (obj.amtaccumulator.length < 23) break; + if (accArray.byteLength < 23) break; cmdsize = 23; obj.xxSend(String.fromCharCode(0x27, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + String.fromCharCode(0x00, 0x00, 0x1B, 0x00, 0x00, 0x00)); if (obj.protocol == 1) { obj.amtkeepalivetimer = setInterval(obj.xxSendAmtKeepAlive, 2000); } @@ -223,27 +185,27 @@ var CreateAmtRedirect = function (module, authCookie) { obj.xxStateChange(3); break; case 0x29: // Serial Settings (41) - if (obj.amtaccumulator.length < 10) break; + if (accArray.byteLength < 10) break; cmdsize = 10; break; case 0x2A: // Incoming display data (42) - if (obj.amtaccumulator.length < 10) break; - var cs = (10 + ((obj.amtaccumulator.charCodeAt(9) & 0xFF) << 8) + (obj.amtaccumulator.charCodeAt(8) & 0xFF)); - if (obj.amtaccumulator.length < cs) break; - obj.m.ProcessData(obj.amtaccumulator.substring(10, cs)); + if (accArray.byteLength < 10) break; + var cs = (10 + (accArray[9] << 8) + accArray[8]); + if (accArray.byteLength < cs) break; + if (obj.m.ProcessBinaryData) { obj.m.ProcessBinaryData(accArray.slice(10, cs)); } else { obj.m.ProcessData(arrToStr(accArray.slice(10, cs))); } cmdsize = cs; break; case 0x2B: // Keep alive message (43) - if (obj.amtaccumulator.length < 8) break; + if (accArray.byteLength < 8) break; cmdsize = 8; break; case 0x41: - if (obj.amtaccumulator.length < 8) break; + if (accArray.byteLength < 8) break; obj.connectstate = 1; obj.m.Start(); // KVM traffic, forward rest of accumulator directly. - if (obj.amtaccumulator.length > 8) { obj.m.ProcessData(obj.amtaccumulator.substring(8)); } - cmdsize = obj.amtaccumulator.length; + if (accArray.byteLength > 8) { obj.m.ProcessData(accArray.slice(8)); } + cmdsize = accArray.byteLength; break; case 0xF0: // console.log('Session is being recorded'); @@ -251,19 +213,19 @@ var CreateAmtRedirect = function (module, authCookie) { cmdsize = 1; break; default: - console.log("Unknown Intel AMT command: " + obj.amtaccumulator.charCodeAt(0) + " acclen=" + obj.amtaccumulator.length); + console.log('Unknown Intel AMT command: ' + accArray[0] + ' acclen=' + accArray.byteLength); obj.Stop(4); return; } if (cmdsize == 0) return; - obj.amtaccumulator = obj.amtaccumulator.substring(cmdsize); + if (cmdsize != obj.acc.byteLength) { obj.acc = obj.acc.slice(cmdsize); } else { obj.acc = null; } } } - + + obj.directSend = function (arr) { try { obj.socket.send(arr.buffer); } catch (ex) { } } + obj.xxSend = function (x) { - //obj.Debug("Redir Send(" + x.length + "): " + rstr2hex(x)); - if (obj.socket != null && obj.socket.readyState == WebSocket.OPEN) { - if (obj.debugmode == 1) { console.log('Send', x); } + if ((obj.socket != null) && (obj.socket.readyState == WebSocket.OPEN)) { var b = new Uint8Array(x.length); for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); } try { obj.socket.send(b.buffer); } catch (ex) { } @@ -275,24 +237,13 @@ var CreateAmtRedirect = function (module, authCookie) { if (obj.protocol == 1) { obj.xxSend(String.fromCharCode(0x28, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(x.length) + x); } else { obj.xxSend(x); } } - obj.xxSendAmtKeepAlive = function () { - if (obj.socket == null) return; - obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++)); - } - - obj.xxRandomNonceX = "abcdef0123456789"; - obj.xxRandomNonce = function (length) { - var r = ""; - for (var i = 0; i < length; i++) { r += obj.xxRandomNonceX.charAt(Math.floor(Math.random() * obj.xxRandomNonceX.length)); } - return r; - } + obj.xxSendAmtKeepAlive = function () { if (obj.socket != null) { obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++)); } } obj.xxOnSocketClosed = function () { - if (obj.debugmode == 1) { console.log('onSocketClosed'); } - //obj.Debug("Redir Socket Closed"); if ((obj.inDataCount == 0) && (obj.tlsv1only == 0)) { obj.tlsv1only = 1; - obj.socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/webrelay.ashx?p=2&host=" + obj.host + "&port=" + obj.port + "&tls=" + obj.tls + "&tls1only=1" + ((obj.user == '*') ? "&serverauth=1" : "") + ((typeof pass === "undefined") ? ("&serverauth=1&user=" + obj.user) : "")); // The "p=2" indicates to the relay that this is a REDIRECTION session + obj.socket = new WebSocket(window.location.protocol.replace('http', 'ws') + '//' + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/webrelay.ashx?p=2&host=' + obj.host + '&port=' + obj.port + '&tls=' + obj.tls + '&tls1only=1' + ((obj.user == '*') ? '&serverauth=1' : '') + ((typeof pass === 'undefined') ? ('&serverauth=1&user=' + obj.user) : '')); // The 'p=2' indicates to the relay that this is a REDIRECTION session + obj.socket.binaryType = 'arraybuffer'; obj.socket.onopen = obj.xxOnSocketConnected; obj.socket.onmessage = obj.xxOnMessage; obj.socket.onclose = obj.xxOnSocketClosed; @@ -301,7 +252,7 @@ var CreateAmtRedirect = function (module, authCookie) { } } - obj.xxStateChange = function(newstate) { + obj.xxStateChange = function (newstate) { if (obj.State == newstate) return; obj.State = newstate; obj.m.xxStateChange(obj.State); @@ -309,18 +260,12 @@ var CreateAmtRedirect = function (module, authCookie) { } obj.Stop = function (x) { - if (obj.debugmode == 1) { console.log('onSocketStop', x); } - //obj.Debug("Redir Socket Stopped"); obj.xxStateChange(0); obj.connectstate = -1; - obj.amtaccumulator = ""; + obj.acc = null; if (obj.socket != null) { obj.socket.close(); obj.socket = null; } if (obj.amtkeepalivetimer != null) { clearInterval(obj.amtkeepalivetimer); obj.amtkeepalivetimer = null; } } - obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20); - obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52); - obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52); - return obj; }