diff --git a/agents/MeshCmd-signed.exe b/agents/MeshCmd-signed.exe index 5a43721e..827823b1 100644 Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ diff --git a/agents/MeshCmd64-signed.exe b/agents/MeshCmd64-signed.exe index 67502c5f..d390124b 100644 Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ diff --git a/agents/MeshService-signed.exe b/agents/MeshService-signed.exe index d95330f8..a6ca63ad 100644 Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ diff --git a/agents/MeshService.exe b/agents/MeshService.exe index d300c71e..051bafc8 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64-signed.exe b/agents/MeshService64-signed.exe index f0357e1e..5c329013 100644 Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index 27dea3f6..6961ca2f 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/meshagent_osx-x86-64 b/agents/meshagent_osx-x86-64 index 766198f5..bc4220e8 100644 Binary files a/agents/meshagent_osx-x86-64 and b/agents/meshagent_osx-x86-64 differ diff --git a/agents/meshcore.js b/agents/meshcore.js index 1976521a..a7d03814 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -19,6 +19,7 @@ process.on('uncaughtException', function (ex) { require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException1: " + ex }); }); +//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); }); function createMeshCore(agent) { var obj = {}; @@ -1526,6 +1527,7 @@ function createMeshCore(agent) { obj.setupMeiOsAdmin = function (func, state) { if ((amtMei == null) || (amtMeiConnected != 2)) { return; } // If there is no MEI, don't bother with this. amtMei.getLocalSystemAccount(function (x) { + if (x == null) return; var transport = require('amt-wsman-duk'); var wsman = require('amt-wsman'); var amt = require('amt'); @@ -1537,6 +1539,7 @@ function createMeshCore(agent) { //************************************* // Setup KVM data channel if this is Intel AMT 12 or above amtMei.getVersion(function (x) { + if (x == null) return; var amtver = null; try { for (var i in x.Versions) { if (x.Versions[i].Description == 'AMT') amtver = parseInt(x.Versions[i].Version.split('.')[0]); } } catch (e) { } if ((amtver != null) && (amtver >= 12)) { diff --git a/agents/modules_meshcmd/user-sessions.js b/agents/modules_meshcmd/user-sessions.js index bbe1d613..f882fb7c 100644 --- a/agents/modules_meshcmd/user-sessions.js +++ b/agents/modules_meshcmd/user-sessions.js @@ -215,7 +215,7 @@ function UserSessions() self.parent._user32.ACDC_H = self.parent._user32.RegisterPowerSettingNotification(self.parent.hwnd, GUID_ACDC_POWER_SOURCE, 0); self.parent._user32.BATT_H = self.parent._user32.RegisterPowerSettingNotification(self.parent.hwnd, GUID_BATTERY_PERCENTAGE_REMAINING, 0); self.parent._user32.DISP_H = self.parent._user32.RegisterPowerSettingNotification(self.parent.hwnd, GUID_CONSOLE_DISPLAY_STATE, 0); - console.log(self.parent._user32.ACDC_H.Val, self.parent._user32.BATT_H.Val, self.parent._user32.DISP_H.Val); + //console.log(self.parent._user32.ACDC_H.Val, self.parent._user32.BATT_H.Val, self.parent._user32.DISP_H.Val); }, this); }); this._messagepump.on('message', function (msg) diff --git a/agents/modules_meshcore/amt-lme.js b/agents/modules_meshcore/amt-lme.js index 725cfcda..63ff1bdd 100644 --- a/agents/modules_meshcore/amt-lme.js +++ b/agents/modules_meshcore/amt-lme.js @@ -172,11 +172,14 @@ function lme_heci(options) { { // Bind a new server socket if not already present this[name][port] = require('net').createServer(); this[name][port].HECI = this; - if (lme_port_offset == 0) { - this[name][port].listen({ port: port, host: '127.0.0.1' }); // Normal mode - } else { - this[name][port].listen({ port: (port + lme_port_offset) }); // Debug mode - } + + try { + if (lme_port_offset == 0) { + this[name][port].listen({ port: port, host: '127.0.0.1' }); // Normal mode + } else { + this[name][port].listen({ port: (port + lme_port_offset) }); // Debug mode + } + } catch (ex) { console.log('Binding error, LMS port ' + (port + lme_port_offset) + ': ' + ex) } // TODO: We can't bind this[name][port].on('connection', function (socket) { //console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort); this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort - lme_port_offset); diff --git a/agents/modules_meshcore/duktape-debugger.jsx b/agents/modules_meshcore/duktape-debugger.jsx new file mode 100644 index 00000000..98568a1f --- /dev/null +++ b/agents/modules_meshcore/duktape-debugger.jsx @@ -0,0 +1,2163 @@ +/* + * + * =============== + * Duktape license + * =============== + * + * (http://opensource.org/licenses/MIT) + * + * Copyright (c) 2013-2017 by Duktape authors (see AUTHORS.rst) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* AUTHORS.rst */ +/* + * =============== + * Duktape authors + * =============== + * + * Copyright + * ========= + * + * Duktape copyrights are held by its authors. Each author has a copyright + * to their contribution, and agrees to irrevocably license the contribution + * under the Duktape ``LICENSE.txt``. + * + * Authors + * ======= + * + * Please include an e-mail address, a link to your GitHub profile, or something + * similar to allow your contribution to be identified accurately. + * + * The following people have contributed code, website contents, or Wiki contents, + * and agreed to irrevocably license their contributions under the Duktape + * ``LICENSE.txt`` (in order of appearance): + * + * * Sami Vaarala + * * Niki Dobrev + * * Andreas \u00d6man + * * L\u00e1szl\u00f3 Lang\u00f3 + * * Legimet + * * Karl Skomski + * * Bruce Pascoe + * * Ren\u00e9 Hollander + * * Julien Hamaide (https://github.com/crazyjul) + * * Sebastian G\u00f6tte (https://github.com/jaseg) + * * Tomasz Magulski (https://github.com/magul) + * * \D. Bohdan (https://github.com/dbohdan) + * * Ond\u0159ej Jirman (https://github.com/megous) + * * Sa\u00fal Ibarra Corretg\u00e9 + * * Jeremy HU + * * Ole Andr\u00e9 Vadla Ravn\u00e5s (https://github.com/oleavr) + * * Harold Brenes (https://github.com/harold-b) + * * Oliver Crow (https://github.com/ocrow) + * * Jakub Ch\u0142api\u0144ski (https://github.com/jchlapinski) + * * Brett Vickers (https://github.com/beevik) + * * Dominik Okwieka (https://github.com/okitec) + * * Remko Tron\u00e7on (https://el-tramo.be) + * * Romero Malaquias (rbsm@ic.ufal.br) + * * Michael Drake + * * Steven Don (https://github.com/shdon) + * * Simon Stone (https://github.com/sstone1) + * * \J. McC. (https://github.com/jmhmccr) + * + * Other contributions + * =================== + * + * The following people have contributed something other than code (e.g. reported + * bugs, provided ideas, etc; roughly in order of appearance): + * + * * Greg Burns + * * Anthony Rabine + * * Carlos Costa + * * Aur\u00e9lien Bouilland + * * Preet Desai (Pris Matic) + * * judofyr (http://www.reddit.com/user/judofyr) + * * Jason Woofenden + * * Micha\u0142 Przyby\u015b + * * Anthony Howe + * * Conrad Pankoff + * * Jim Schimpf + * * Rajaran Gaunker (https://github.com/zimbabao) + * * Andreas \u00d6man + * * Doug Sanden + * * Josh Engebretson (https://github.com/JoshEngebretson) + * * Remo Eichenberger (https://github.com/remoe) + * * Mamod Mehyar (https://github.com/mamod) + * * David Demelier (https://github.com/markand) + * * Tim Caswell (https://github.com/creationix) + * * Mitchell Blank Jr (https://github.com/mitchblank) + * * https://github.com/yushli + * * Seo Sanghyeon (https://github.com/sanxiyn) + * * Han ChoongWoo (https://github.com/tunz) + * * Joshua Peek (https://github.com/josh) + * * Bruce E. Pascoe (https://github.com/fatcerberus) + * * https://github.com/Kelledin + * * https://github.com/sstruchtrup + * * Michael Drake (https://github.com/tlsa) + * * https://github.com/chris-y + * * Laurent Zubiaur (https://github.com/lzubiaur) + * * Neil Kolban (https://github.com/nkolban) + * + * If you are accidentally missing from this list, send me an e-mail + * (``sami.vaarala@iki.fi``) and I'll fix the omission. + * + * + * ================================================================= + * Modifications made by Bryan Roe, Copyright 2018 Intel Corporation + * ================================================================= + * + * + */ + + +var events = require('events'); +var Promise = require('promise'); +var dbgHTML = '77u/PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBYSFRNTCAxLjAgVHJhbnNpdGlvbmFsLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSL3hodG1sMS9EVEQveGh0bWwxLXRyYW5zaXRpb25hbC5kdGQiPg0KPGh0bWw+DQo8aGVhZD4NCjxtZXRhIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiAvPg0KPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1FREdFIiAvPg0KPG1ldGEgbmFtZT0iZm9ybWF0LWRldGVjdGlvbiIgY29udGVudD0idGVsZXBob25lPW5vIiAvPg0KPHRpdGxlPldlYlJUQyBEZWJ1ZzwvdGl0bGU+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KYm9keSANCnsNCiAgICBtYXJnaW46IDA7DQoJcGFkZGluZzogMDsNCglib3JkZXI6IDA7DQoJY29sb3I6IGJsYWNrOw0KCWZvbnQtc2l6ZTogMTNweDsNCglmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsIEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7DQoJYmFja2dyb3VuZC1jb2xvcjogI2QzZDlkNjsNCn0NCiNjb250YWluZXIgew0KCWJhY2tncm91bmQtY29sb3I6ICNmZmY7DQoJd2lkdGg6IDk2MHB4Ow0KCW1hcmdpbjogMCBhdXRvOw0KCWJvcmRlci10b3A6IDA7DQoJYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2I3YjdiNzsNCglib3JkZXItYm90dG9tOiAwOw0KCWJvcmRlci1sZWZ0OiAxcHggc29saWQgI2I3YjdiNzsNCglwYWRkaW5nOiAwOw0KfQ0KI21hc3RoZWFkIHsNCgl3aWR0aDogYXV0bzsNCgltYXJnaW46IDA7DQoJcGFkZGluZzogMDsNCglvdmVyZmxvdzogYXV0bzsNCgl0ZXh0LWFsaWduOiByaWdodDsNCgliYWNrZ3JvdW5kLWNvbG9yOiAjMDM2Ow0KCXdpZHRoOiA5NjBweDsNCg0KICAgIGJhY2tncm91bmQ6IHJnYig0NSw4NiwxMzcpOyAvKiBPbGQgYnJvd3NlcnMgKi8NCiAgICBiYWNrZ3JvdW5kOiAtbW96LWxpbmVhci1ncmFkaWVudChsZWZ0LCAgcmdiYSg0NSw4NiwxMzcsMSkgMCUsIHJnYmEoMCw1MSwxMDIsMSkgMjklKTsgLyogRkYzLjYrICovDQogICAgYmFja2dyb3VuZDogLXdlYmtpdC1ncmFkaWVudChsaW5lYXIsIGxlZnQgdG9wLCByaWdodCB0b3AsIGNvbG9yLXN0b3AoMCUscmdiYSg0NSw4NiwxMzcsMSkpLCBjb2xvci1zdG9wKDI5JSxyZ2JhKDAsNTEsMTAyLDEpKSk7IC8qIENocm9tZSxTYWZhcmk0KyAqLw0KICAgIGJhY2tncm91bmQ6IC13ZWJraXQtbGluZWFyLWdyYWRpZW50KGxlZnQsICByZ2JhKDQ1LDg2LDEzNywxKSAwJSxyZ2JhKDAsNTEsMTAyLDEpIDI5JSk7IC8qIENocm9tZTEwKyxTYWZhcmk1LjErICovDQogICAgYmFja2dyb3VuZDogLW8tbGluZWFyLWdyYWRpZW50KGxlZnQsICByZ2JhKDQ1LDg2LDEzNywxKSAwJSxyZ2JhKDAsNTEsMTAyLDEpIDI5JSk7IC8qIE9wZXJhIDExLjEwKyAqLw0KICAgIGJhY2tncm91bmQ6IC1tcy1saW5lYXItZ3JhZGllbnQobGVmdCwgIHJnYmEoNDUsODYsMTM3LDEpIDAlLHJnYmEoMCw1MSwxMDIsMSkgMjklKTsgLyogSUUxMCsgKi8NCiAgICBiYWNrZ3JvdW5kOiBsaW5lYXItZ3JhZGllbnQodG8gcmlnaHQsICByZ2JhKDQ1LDg2LDEzNywxKSAwJSxyZ2JhKDAsNTEsMTAyLDEpIDI5JSk7IC8qIFczQyAqLw0KICAgIGZpbHRlcjogcHJvZ2lkOkRYSW1hZ2VUcmFuc2Zvcm0uTWljcm9zb2Z0LmdyYWRpZW50KCBzdGFydENvbG9yc3RyPScjMmQ1Njg5JywgZW5kQ29sb3JzdHI9JyMwMDMzNjYnLEdyYWRpZW50VHlwZT0xICk7IC8qIElFNi05ICovDQp9DQojY29sdW1uX2wgew0KCXBvc2l0aW9uOiByZWxhdGl2ZTsNCglmbG9hdDogbGVmdDsNCgl3aWR0aDogOTMwcHg7DQoJbWFyZ2luOiAwOw0KCXBhZGRpbmc6IDAgMTVweDsNCgliYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOw0KfQ0KI2Zvb3RlciB7DQoJY2xlYXI6IGJvdGg7DQoJb3ZlcmZsb3c6IGF1dG87DQoJd2lkdGg6IDk2MHB4Ow0KCXRleHQtYWxpZ246IGNlbnRlcjsNCgliYWNrZ3JvdW5kLWNvbG9yOiAjMTEzOTYyOw0KCXBhZGRpbmctdG9wOiA1cHg7DQoJcGFkZGluZy1ib3R0b206IDVweDsNCn0NCiNmb290ZXIgYSB7DQoJY29sb3I6ICNmZmY7DQoJdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7DQp9DQojZm9vdGVyIGE6aG92ZXIgew0KCWNvbG9yOiAjZmZmOw0KCXRleHQtZGVjb3JhdGlvbjogbm9uZTsNCn0NCmEgew0KCWNvbG9yOiAjMDM2Ow0KCXRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOw0KfQ0KLnN0eWxlMyB7DQoJdGV4dC1hbGlnbjogY2VudGVyOw0KCWNvbG9yOiB3aGl0ZTsNCgliYWNrZ3JvdW5kLWNvbG9yOiAjODA4MDgwOw0KCWZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KLnN0eWxlNiB7DQogICAgcGFkZGluZy10b3A6IDJweDsNCiAgICBwYWRkaW5nLWJvdHRvbTogMnB4Ow0KCWJhY2tncm91bmQtY29sb3I6ICNDMEMwQzA7DQp9DQoueHNlY3Rpb24gew0KICAgIGJhY2tncm91bmQtY29sb3I6ICNFMEUwRTA7DQogICAgcGFkZGluZzogNXB4IDIwcHggMjVweCAyMHB4Ow0KICAgIGJvcmRlci1yYWRpdXM6IDEwcHg7DQp9DQouZml4ZWRmb250IHsNCiAgICBmb250LWZhbWlseTpjb3VyaWVyLCAiY291cmllciBuZXciLCBtb25vc3BhY2U7DQp9DQouaW5mb2VudHJ5IHsNCiAgICBwYWRkaW5nLXRvcDoxcHg7DQp9DQouR0VORVJJQ19NT0RVTEV7DQogICAgY29sb3I6YmxhY2s7DQp9DQouVkFSUVVFUll7DQogICAgY29sb3I6YmxhY2s7DQp9DQouVkFSUkVTVUxUew0KICAgIGNvbG9yOmRhcmtncmVlbjsNCn0NCi5CUkVBS1BPSU5Uew0KICAgIGNvbG9yOmRhcmt2aW9sZXQNCn0NCi5CUkVBS1BPSU5UOmhvdmVyIA0Kew0KCWJhY2tncm91bmQtY29sb3I6ICNFRUU7DQp9DQouU1RBVEVDSEFOR0V7DQogICAgY29sb3I6ZGFya3Zpb2xldA0KfQ0KLlNPVVJDRUNPREVQQVJFTlQgDQp7DQogICAgd2lkdGg6MTAwMDBweA0KfQ0KLlNPVVJDRUNPREVQQVJFTlQ6aG92ZXIgDQp7DQoJYmFja2dyb3VuZC1jb2xvcjogI0VFRTsNCn0NCi5TT1VSQ0VDT0RFew0KICAgIGNvbG9yOmRhcmtncmVlbjsNCiAgICBwYWRkaW5nLWxlZnQ6MTBweDsNCiAgICBkaXNwbGF5OmlubGluZS1ibG9jazsNCn0NCi5TT1VSQ0VDT0RFTElORQ0Kew0KICAgIHdpZHRoOjMwcHg7DQogICAgdGV4dC1hbGlnbjpyaWdodDsNCiAgICBjb2xvcjpncmF5Ow0KICAgIGRpc3BsYXk6aW5saW5lLWJsb2NrOw0KICAgIC13ZWJraXQtdXNlci1zZWxlY3Q6bm9uZTsNCiAgICAta2h0bWwtdXNlci1zZWxlY3Q6bm9uZTsNCiAgICAtbW96LXVzZXItc2VsZWN0Om5vbmU7DQogICAgLW1zLXVzZXItc2VsZWN0Om5vbmU7DQogICAgLW8tdXNlci1zZWxlY3Q6bm9uZTsNCiAgICAtdXNlci1zZWxlY3Q6bm9uZTsNCn0NCi5DQUxMU1RBQ0sNCnsNCiAgICBjb2xvcjpzYWRkbGVicm93bjsNCiAgICBmb250LXdlaWdodDpub3JtYWw7DQogICAgLXdlYmtpdC11c2VyLXNlbGVjdDpub25lOw0KICAgIC1raHRtbC11c2VyLXNlbGVjdDpub25lOw0KICAgIC1tb3otdXNlci1zZWxlY3Q6bm9uZTsNCiAgICAtbXMtdXNlci1zZWxlY3Q6bm9uZTsNCiAgICAtby11c2VyLXNlbGVjdDpub25lOw0KICAgIC11c2VyLXNlbGVjdDpub25lOw0KfQ0KLkNBTExTVEFDSzpob3Zlcg0Kew0KCWJhY2tncm91bmQtY29sb3I6ICNFRUU7DQp9DQouRVJST1J7DQogICAgY29sb3I6cmVkOw0KfQ0KLlVOS05PV05fTU9EVUxFew0KICAgIGNvbG9yOnJlZDsNCn0NCjwvc3R5bGU+DQo8L2hlYWQ+DQo8Ym9keSBvbmxvYWQ9ImlmICh0eXBlb2Yoc3RhcnR1cCkgIT09ICd1bmRlZmluZWQnKSBzdGFydHVwKCk7Ij4NCgk8ZGl2IGlkPSJjb250YWluZXIiPg0KCQk8ZGl2IGlkPSJtYXN0aGVhZCIgc3R5bGU9ImhlaWdodDogNjZweDsgd2lkdGg6IDEwMCU7IG92ZXJmbG93OmhpZGRlbiI+DQoJICAgICAgICA8ZGl2IHN0eWxlPSJmbG9hdDpsZWZ0OyBoZWlnaHQ6IDY2cHg7IGNvbG9yOiNjOGM4Yzg7IHBhZGRpbmctbGVmdDoyMHB4OyBwYWRkaW5nLXRvcDo4cHgiPjxzdHJvbmc+PGZvbnQgc3R5bGU9ImZvbnQtc2l6ZTo0NnB4OyBmb250LWZhbWlseTogQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjsiPkJ1aWx0LWluIEpTIERlYnVnZ2VyPC9mb250Pjwvc3Ryb25nPjwvZGl2Pg0KCSAgICAgICAgPGRpdiBzdHlsZT0iZmxvYXQ6bGVmdDsgaGVpZ2h0OiA2NnB4OyBjb2xvcjojYzhjOGM4OyBwYWRkaW5nLWxlZnQ6NXB4OyBwYWRkaW5nLXRvcDoxNHB4Ij48c3Ryb25nPjxmb250IHN0eWxlPSJmb250LXNpemU6MTRweDsgZm9udC1mYW1pbHk6IEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7Ij48c3BhbiBpZD0idGl0bGVob3N0Ij48L3NwYW4+PC9mb250Pjwvc3Ryb25nPjwvZGl2Pg0KCQk8L2Rpdj4NCgkJPGRpdiBpZD0idG9wYmFyIj4NCgkJCTx0YWJsZSBzdHlsZT0id2lkdGg6IDEwMCU7IGhlaWdodDogMjJweDsiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9InN0eWxlMSI+DQoJCQkJPHRyPg0KCQkJCQk8dGQgaWQ9IkF0dGFjaEJ1dHRvbiIgc3R5bGU9IndpZHRoOiAxMDBweDsgaGVpZ2h0OiAyNHB4OyBjdXJzb3I6ZGVmYXVsdDsiIG9uY2xpY2s9ImF0dGFjaERlYnVnZ2VyKCkiIGNsYXNzPSJzdHlsZTMiPkFUVEFDSDwvdGQ+DQoJCQkJCTx0ZCBpZD0iUGF1c2VSZXN1bWVCdXR0b24iIHN0eWxlPSJ3aWR0aDogMTAwcHg7IGhlaWdodDogMjRweDsgY3Vyc29yOmRlZmF1bHQ7IiBvbmNsaWNrPSJwYXVzZVJlc3VtZSgpIiBjbGFzcz0ic3R5bGUzIj5QQVVTRTwvdGQ+DQogICAgICAgICAgICAgICAgICAgIDx0ZCBpZD0iU3RlcE92ZXJCdXR0b24iIHN0eWxlPSJ3aWR0aDogMTAwcHg7IGhlaWdodDogMjRweDsgY3Vyc29yOmRlZmF1bHQ7IiBvbmNsaWNrPSJzdGVwT3ZlcigpIiBjbGFzcz0ic3R5bGUzIj5TdGVwLU92ZXI8L3RkPg0KICAgICAgICAgICAgICAgICAgICA8dGQgaWQ9IlN0ZXBJbnRvQnV0dG9uIiBzdHlsZT0id2lkdGg6IDEwMHB4OyBoZWlnaHQ6IDI0cHg7IGN1cnNvcjpkZWZhdWx0OyIgb25jbGljaz0ic3RlcEludG8oKSIgY2xhc3M9InN0eWxlMyI+U3RlcC1JbnRvPC90ZD4NCiAgICAgICAgICAgICAgICAgICAgPHRkIGlkPSJTdGVwT3V0QnV0dG9uIiBzdHlsZT0id2lkdGg6IDEwMHB4OyBoZWlnaHQ6IDI0cHg7IGN1cnNvcjpkZWZhdWx0OyIgb25jbGljaz0ic3RlcE91dCgpIiBjbGFzcz0ic3R5bGUzIj5TdGVwLU91dDwvdGQ+DQogICAgICAgICAgICAgICAgICAgIDwhLS08dGQgaWQ9IkhlYXBCdXR0b24iIHN0eWxlPSJ3aWR0aDogMTAwcHg7IGhlaWdodDogMjRweDsgY3Vyc29yOmRlZmF1bHQ7IiBvbmNsaWNrPSJvbkR1bXBIZWFwKCkiIGNsYXNzPSJzdHlsZTMiPlZpZXcgSGVhcDwvdGQ+IC0tPg0KCQkJCTwvdHI+DQoJCQk8L3RhYmxlPg0KICAgICAgICAgICAgPGRpdiBpZD0iY29sdW1uX2wiPg0KICAgICAgICAgICAgICAgIDxkaXYgaWQ9InN0YXR1c3RleHQiIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJoZWlnaHQ6MjBweDsgZm9udC1zaXplOjE1cHgiPg0KICAgICAgICAgICAgICAgIDwvZGl2Pg0KICAgICAgICAgICAgPC9kaXY+DQoJCTwvZGl2Pg0KCQk8ZGl2IGlkPSJwYWdlX2NvbnRlbnQiPg0KICAgICAgICAgICAgPGRpdiBzdHlsZT0id2lkdGg6IDc1JTsgZmxvYXQ6bGVmdCI+DQogICAgICAgICAgICAgICAgPGRpdiBpZD0iQ2FsbHN0YWNrV2luZG93IiBzdHlsZT0id2lkdGg6IDEwMCU7aGVpZ2h0OjEwMHB4O292ZXJmbG93LXk6c2Nyb2xsIj4NCiAgICAgICAgICAgICAgICA8L2Rpdj4JDQogICAgICAgICAgICAgICAgPGRpdiBpZD0iTG9nV2luZG93IiBzdHlsZT0id2lkdGg6IDEwMCU7aGVpZ2h0OjUwMHB4O292ZXJmbG93LXk6c2Nyb2xsIj4NCiAgICAgICAgICAgICAgICA8L2Rpdj4JDQogICAgICAgICAgICA8L2Rpdj4NCiAgICAgICAgICAgIDxkaXYgc3R5bGU9IndpZHRoOiAyNSU7IGZsb2F0OnJpZ2h0Ij4NCiAgICAgICAgICAgICAgICA8ZGl2IGlkPSJMb2dXaW5kb3cyIiBzdHlsZT0id2lkdGg6IDEwMCU7aGVpZ2h0OjYwMHB4O292ZXJmbG93LXk6c2Nyb2xsIj4NCiAgICAgICAgICAgICAgICAgICAgPHRleHRhcmVhIGlkPSJsb2FkU291cmNlVGV4dCIgZGlzYWJsZWQgc3R5bGU9IndpZHRoOjk1JTtoZWlnaHQ6MzBweCI+PC90ZXh0YXJlYT4NCiAgICAgICAgICAgICAgICAgICAgPGlucHV0IGlkPSJsb2FkU291cmNlQnV0dG9uIiB0eXBlPSJidXR0b24iIHZhbHVlPSJMb2FkIFNvdXJjZSIgZGlzYWJsZWQgb25jbGljaz0ibWFudWFsbHlMb2FkU291cmNlKCkiIC8+DQogICAgICAgICAgICAgICAgICAgIDxwLz4NCiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0nR0VORVJJQ19NT0RVTEUnPkJyZWFrcG9pbnRzPC9kaXY+DQogICAgICAgICAgICAgICAgICAgIDxkaXYgaWQ9IkN1cnJlbnRCcmVha3BvaW50cyIgc3R5bGU9IndpZHRoOiA5NSU7aGVpZ2h0OjIwMHB4O292ZXJmbG93LXk6c2Nyb2xsIj4NCiAgICAgICAgICAgICAgICAgICAgPC9kaXY+DQogICAgICAgICAgICAgICAgICAgIDxpbnB1dCBpZD0iZGVsQnJlYWtwb2ludEJ1dHRvbiIgdHlwZT0iYnV0dG9uIiB2YWx1ZT0iRGVsIEJyZWFrcG9pbnQiIGRpc2FibGVkIG9uY2xpY2s9ImRlbEJyZWFrcG9pbnQoKSIgLz4NCiAgICAgICAgICAgICAgICAgICAgPHAgLz4NCiAgICAgICAgICAgICAgICAgICAgPHRleHRhcmVhIGlkPSJxdWVyeVRleHQiIGRpc2FibGVkIHN0eWxlPSJ3aWR0aDo5NSU7aGVpZ2h0OjE1cHgiPjwvdGV4dGFyZWE+DQogICAgICAgICAgICAgICAgICAgIDxpbnB1dCBpZD0icXVlcnlCdXR0b24iIHR5cGU9ImJ1dHRvbiIgdmFsdWU9IlF1ZXJ5IiBkaXNhYmxlZCBvbmNsaWNrPSJxdWVyeVZhbCgpIiAvPg0KICAgICAgICAgICAgICAgICAgICA8aW5wdXQgaWQ9ImV2YWxCdXR0b24iIHR5cGU9ImJ1dHRvbiIgdmFsdWU9IkV2YWwiIGRpc2FibGVkIG9uY2xpY2s9ImV2YWxTdHJpbmcoKSIgLz4NCiAgICAgICAgICAgICAgICAgICAgPGlucHV0IGlkPSJjbGVhckJ1dHRvbiIgdHlwZT0iYnV0dG9uIiB2YWx1ZT0iQ2xlYXIiIGRpc2FibGVkIG9uY2xpY2s9ImNsZWFyUXVlcnkoKSIgLz4NCiAgICAgICAgICAgICAgICAgICAgPGlucHV0IGlkPSJsb2NhbHNCdXR0b24iIHR5cGU9ImJ1dHRvbiIgdmFsdWU9IkxvY2FscyIgZGlzYWJsZWQgb25jbGljaz0ibG9jYWxzUXVlcnkoKSIgLz4NCiAgICAgICAgICAgICAgICAgICAgPHAgLz4NCiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0nR0VORVJJQ19NT0RVTEUnPlJlc3VsdHM8L2Rpdj4NCiAgICAgICAgICAgICAgICAgICAgPGRpdiBpZD0icXVlcnlSZXN1bHRzIiBzdHlsZT0id2lkdGg6IDk1JTtoZWlnaHQ6MjAwcHg7b3ZlcmZsb3cteTpzY3JvbGwiPg0KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4JDQoNCiAgICAgICAgICAgICAgICA8L2Rpdj4JDQogICAgICAgICAgICA8L2Rpdj4NCg0KICAgICAgICAgICAgPGRpdiBpZD0iZm9vdGVyIj4NCiAgICAgICAgICAgICAgICA8dGFibGUgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIxMCIgc3R5bGU9IndpZHRoOiAxMDAlIj4NCiAgICAgICAgICAgICAgICAgICAgPHRyPg0KICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGlkPSd4Zm9vdGVyJyBzdHlsZT0idGV4dC1hbGlnbjpsZWZ0OyBkaXNwbGF5Om5vbmUiPjwvdGQ+DQogICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9InRleHQtYWxpZ246Y2VudGVyO2NvbG9yOmxpZ2h0Z3JheSI+VG90YWwgTWVtb3J5IEFsbG9jYXRpb246IDxiIGlkPSJtZW11c2FnZSIgc3R5bGU9ImNvbG9yOnllbGxvdyI+PC9iPjwvdGQ+DQogICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGlucHV0IGlkPSJnY0J1dHRvbiIgdHlwZT0iYnV0dG9uIiB2YWx1ZT0iR0MiIG9uY2xpY2s9ImZvcmNlR0MoKSIgLz48L3RkPg0KICAgICAgICAgICAgICAgICAgICAgPC90cj4NCiAgICAgICAgICAgICAgICA8L3RhYmxlPg0KICAgICAgICAgICAgPC9kaXY+DQogICAgICAgPC9kaXY+DQoNCiAgICA8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+DSAgICAgICAgdmFyIGJyZWFrcG9pbnRJRCA9IDA7DSAgICAgICAgdmFyIHNlbGVjdGVkQnJlYWtwb2ludCA9ICIiOw0gICAgICAgIHZhciB3c29ja2V0Ow0gICAgICAgIHZhciBjb25uZWN0ZWQgPSBmYWxzZTsNICAgICAgICB2YXIgY3VycmVudExpbmUgPSAwOw0gICAgICAgIHZhciBjdXJyZW50TW9kdWxlID0gJyc7DSAgICAgICAgdmFyIGN1cnJlbnRNb2R1bGVUb2tlbnMgPSBudWxsOw0gICAgICAgIHZhciBzb3VyY2VIaWdobGlnaHQgPSAneWVsbG93JzsNICAgICAgICB2YXIgZXhjZXB0aW9uSGlnaGxpZ2h0ID0gJ2RlZXBwaW5rJzsNICAgICAgICB2YXIgYnBDb2xvciA9ICdibHVlJzsNICAgICAgICB2YXIgZXhjZXB0aW9uTWVzc2FnZSA9IG51bGw7DSAgICAgICAgdmFyIGhpZ2hsaWdodCA9IHNvdXJjZUhpZ2hsaWdodDsNICAgICAgICB2YXIgY2FsbHN0YWNrX3Njb3BlID0gJ3JveWFsYmx1ZSc7DSAgICAgICAgdmFyIGNhbGxzdGFja19kZWZhdWx0ID0gJ3NhZGRsZWJyb3duJzsNICAgICAgICB2YXIgY3VycmVudENhbGxzdGFjayA9IFtdOw0gICAgICAgIHZhciBpc05hdGl2ZSA9IGZhbHNlOw0NICAgICAgICBmdW5jdGlvbiBRKHgpIHsgcmV0dXJuIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHgpOyB9IC8vICJRIg0KICAgICAgICBmdW5jdGlvbiBRQyh4LCB5KSB7IFFTKHgpWyJjdXJzb3IiXSA9IHk7fQ0KICAgICAgICBmdW5jdGlvbiBRUyh4KSB7IHJldHVybiBRKHgpLnN0eWxlOyB9ICAgICAgICAgICAgICAgIC8vICJRIiBzdHlsZQ0KICAgICAgICBmdW5jdGlvbiBRRSh4LCB5KSB7IFEoeCkuZGlzYWJsZWQgPSAheTsgfSAgICAgICAgICAgIC8vICJRIiBlbmFibGUNCiAgICAgICAgZnVuY3Rpb24gUVYoeCwgeSkgeyBRUyh4KS5kaXNwbGF5ID0gKHkgPyAnJyA6ICdub25lJyk7IH0gLy8gIlEiIHZpc2libGUNCiAgICAgICAgZnVuY3Rpb24gUUEoeCwgeSkgeyBRKHgpLmlubmVySFRNTCArPSB5OyB9ICAgICAgICAgICAvLyAiUSIgYXBwZW5kDQogICAgICAgIGZ1bmN0aW9uIFFIKHgsIHkpIHsgUSh4KS5pbm5lckhUTUwgPSB5OyB9ICAgICAgICAgICAgLy8gIlEiIGh0bWwNCiAgICAgICAgZnVuY3Rpb24gUUhWQUwoeCkgeyByZXR1cm4gKFEoeCkuaW5uZXJIVE1MKTsgfQ0KICAgICAgICBmdW5jdGlvbiBRVkFMKHgsIHkpIHsgaWYgKHkgPT0gdW5kZWZpbmVkKSB7IHJldHVybiAoUSh4KS52YWx1ZSk7IH0gZWxzZSB7IFEoeCkudmFsdWUgPSB5OyB9IH0NCiAgICAgICAgZnVuY3Rpb24gUUJDKHgsIHkpIHsgUVMoeClbImJhY2tncm91bmQtY29sb3IiXSA9IHk7IH0NCiAgICAgICAgZnVuY3Rpb24gUUNISUxEUkVOKHgpIHsgcmV0dXJuIChRKHgpLmdldEVsZW1lbnRzQnlUYWdOYW1lKCdkaXYnKSk7IH0NDSAgICAgICAgZnVuY3Rpb24gSW50MzJUb1N0cih2KSB7IHJldHVybiBTdHJpbmcuZnJvbUNoYXJDb2RlKCh2ID4+IDI0KSAmIDB4RkYsICh2ID4+IDE2KSAmIDB4RkYsICh2ID4+IDgpICYgMHhGRiwgdiAmIDB4RkYpOyB9DQogICAgICAgIGZ1bmN0aW9uIEludDE2VG9TdHIodikgeyByZXR1cm4gU3RyaW5nLmZyb21DaGFyQ29kZSgodiA+PiA4KSAmIDB4RkYsIHYgJiAweEZGKTsgfQ0KICAgICAgICBmdW5jdGlvbiBSZWFkU2hvcnQoZGF0YSwgcHRyKSB7IHJldHVybiAoZGF0YS5jaGFyQ29kZUF0KHB0cikgPDwgOCkgKyBkYXRhLmNoYXJDb2RlQXQocHRyICsgMSk7IH0NCiAgICAgICAgZnVuY3Rpb24gUmVhZEludChkYXRhLCBwdHIpIHsgcmV0dXJuIChkYXRhLmNoYXJDb2RlQXQocHRyKSAqIDE2Nzc3MjE2KSArIChkYXRhLmNoYXJDb2RlQXQocHRyICsgMSkgKiA2NTUzNikgKyAoZGF0YS5jaGFyQ29kZUF0KHB0ciArIDIpICogMjU2KSArIGRhdGEuY2hhckNvZGVBdChwdHIgKyAzKTsgfQ0KICAgICAgICBmdW5jdGlvbiBSZWFkTG9uZyhkYXRhLCBwdHIpIHsgcmV0dXJuICgoZGF0YS5jaGFyQ29kZUF0KHB0cikgKiA3MjA1NzU5NDAzNzkyNzkzNikgKyAoZGF0YS5jaGFyQ29kZUF0KHB0ciArIDEpICogMjgxNDc0OTc2NzEwNjU2KSArIChkYXRhLmNoYXJDb2RlQXQocHRyICsgMikgKiAxMDk5NTExNjI3Nzc2KSArIChkYXRhLmNoYXJDb2RlQXQocHRyICsgMykgKiA0Mjk0OTY3Mjk2KSArIGRhdGEuY2hhckNvZGVBdChwdHIgKyA0KSAqIDE2Nzc3MjE2KSArIChkYXRhLmNoYXJDb2RlQXQocHRyICsgNSkgKiA2NTUzNikgKyAoZGF0YS5jaGFyQ29kZUF0KHB0ciArIDYpICogMjU2KSArIGRhdGEuY2hhckNvZGVBdChwdHIgKyA3KTsgfQ0KDSAgICAgICAgZnVuY3Rpb24gU2VuZENvbW1hbmQoY21kLCBkYXRhKQ0KICAgICAgICB7DQogICAgICAgICAgICB3c29ja2V0LnNlbmQoSW50MTZUb1N0cihjbWQpICsgZGF0YSk7DQogICAgICAgIH0NDSAgICAgICAgZnVuY3Rpb24gQWRkTG9nKGNvZGUsIG1zZykNCiAgICAgICAgew0KICAgICAgICAgICAgdmFyIGRjbGFzcyA9ICIiOw0KICAgICAgICAgICAgaWYgKG1zZyA9PSB1bmRlZmluZWQpIHsNCiAgICAgICAgICAgICAgICBRQSgiTG9nV2luZG93IiwgIjxkaXYgY2xhc3M9J0dFTkVSSUNfTU9EVUxFJyA+ICIgKyBjb2RlICsgIjwvYnI+PC9kaXY+Iik7DQogICAgICAgICAgICAgICAgcmV0dXJuOw0KICAgICAgICAgICAgfQ0KDQogICAgICAgICAgICBkY2xhc3MgPSBjb2RlOw0KDQogICAgICAgICAgICBRQSgiTG9nV2luZG93IiwgIjxkaXYgY2xhc3M9JyIgKyBkY2xhc3MgKyAiJyA+ICIgKyBtc2cgKyAiPC9icj48L2Rpdj4iKTsNCiAgICAgICAgfQ0gICAgICAgIGZ1bmN0aW9uIHN0YXJ0dXAoKQ0KICAgICAgICB7DQogICAgICAgICAgICBkaXNwbGF5U3RhdHVzKCJFc3RhYmxpc2hpbmcgY29ubmVjdGlvbi4uLiIpOyAgICAgICAgICANCiAgICAgICAgICAgIHdzb2NrZXQgPSBuZXcgV2ViU29ja2V0KCJ3czovLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAiOiIgKyB3aW5kb3cubG9jYXRpb24ucG9ydCArICIvIik7DQogICAgICAgICAgICB3c29ja2V0LmJpbmFyeVR5cGUgPSAiYXJyYXlidWZmZXIiOw0KICAgICAgICAgICAgd3NvY2tldC5vbm9wZW4gPSBmdW5jdGlvbiAoZXZ0KQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIGNvbm5lY3RlZCA9IHRydWU7IGRpc3BsYXlTdGF0dXMoIkRlYnVnIENsaWVudCBDb25uZWN0ZWQuLi4iKTsNCiAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiQXR0YWNoQnV0dG9uIiksIDEpOw0KICAgICAgICAgICAgICAgIGF0dGFjaERlYnVnZ2VyKCk7DQogICAgICAgICAgICB9DQogICAgICAgICAgICB3c29ja2V0Lm9uY2xvc2UgPSBmdW5jdGlvbiAoZXZ0KQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIGNvbm5lY3RlZCA9IGZhbHNlOyBkaXNwbGF5U3RhdHVzKCJEZWJ1ZyBDbGllbnQgQ29ubmVjdGlvbiBsb3N0Li4uIFJldHJ5aW5nLi4uIik7DQogICAgICAgICAgICAgICAgdG9nZ2xlQ29sb3IoUVMoIkF0dGFjaEJ1dHRvbiIpLCAwKTsNCiAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcE92ZXJCdXR0b24iKSwgMCk7DQogICAgICAgICAgICAgICAgdG9nZ2xlQ29sb3IoUVMoIlN0ZXBJbnRvQnV0dG9uIiksIDApOw0KICAgICAgICAgICAgICAgIHRvZ2dsZUNvbG9yKFFTKCJTdGVwT3V0QnV0dG9uIiksIDApOw0KICAgICAgICAgICAgICAgIHRvZ2dsZUNvbG9yKFFTKCJQYXVzZVJlc3VtZUJ1dHRvbiIpLCAwKTsNCiAgICAgICAgICAgICAgICBRRSgibG9hZFNvdXJjZUJ1dHRvbiIsIDApOw0KICAgICAgICAgICAgICAgIFFFKCJsb2FkU291cmNlVGV4dCIsIDApOw0KICAgICAgICAgICAgICAgIFFFKCdxdWVyeUJ1dHRvbicsIDApOw0KICAgICAgICAgICAgICAgIFFFKCdjbGVhckJ1dHRvbicsIDApOw0KICAgICAgICAgICAgICAgIFFFKCdsb2NhbHNCdXR0b24nLCAwKTsNCiAgICAgICAgICAgICAgICBRRSgnZXZhbEJ1dHRvbicsIDApOw0KICAgICAgICAgICAgICAgIFFWKCdwYWdlX2NvbnRlbnQnLCBmYWxzZSk7DQogICAgICAgICAgICAgICAgc2V0VGltZW91dChzZXJ2ZXJQb2xsLCAxMDAwKTsNCiAgICAgICAgICAgICAgICBjdXJyZW50TW9kdWxlID0gJyc7DQogICAgICAgICAgICB9DQogICAgICAgICAgICB3c29ja2V0Lm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChldnQpDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgdmFyIG1zZyA9IEpTT04ucGFyc2UoZXZ0LmRhdGEpOw0KDQogICAgICAgICAgICAgICAgc3dpdGNoKG1zZy5jbWQpDQogICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICBjYXNlICdNRU1PUlknOg0KICAgICAgICAgICAgICAgICAgICAgICAgUUgoJ21lbXVzYWdlJywgTWF0aC5yb3VuZChtc2cudG90YWwvMTAyNCkgKyAnIGtiJyk7DQogICAgICAgICAgICAgICAgICAgICAgICBicmVhazsNCiAgICAgICAgICAgICAgICAgICAgY2FzZSAnQVRUQUNIJzoNCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc3BsYXlTdGF0dXMoJ0F0dGFjaGVkLi4uJyk7DQogICAgICAgICAgICAgICAgICAgICAgICBRSCgiQXR0YWNoQnV0dG9uIiwgIkRFVEFDSCIpOw0KICAgICAgICAgICAgICAgICAgICAgICAgdG9nZ2xlQ29sb3IoUVMoIlBhdXNlUmVzdW1lQnV0dG9uIiksIDEpOw0KICAgICAgICAgICAgICAgICAgICAgICAgUUUoImxvYWRTb3VyY2VCdXR0b24iLCAxKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCJsb2FkU291cmNlVGV4dCIsIDEpOw0KICAgICAgICAgICAgICAgICAgICAgICAgUVYoJ3BhZ2VfY29udGVudCcsIHRydWUpOw0KICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICAgICAgICAgIGNhc2UgJ1BBVVNFJzoNCiAgICAgICAgICAgICAgICAgICAgICAgIGlzTmF0aXZlID0gbXNnLm5hdGl2ZTsNCg0KICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGhpZ2hsaWdodCA9PSBleGNlcHRpb25IaWdobGlnaHQpDQogICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgUUgoJ3N0YXR1c3RleHQnLCAnPGRpdiBjbGFzcz0iU1RBVEVDSEFOR0UiPicgKyBleGNlcHRpb25NZXNzYWdlICsgJyBpbiAnICsgbXNnLmZpbGUgKyAnOicgKyBtc2cubGluZSArICc8L2Rpdj4nKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UNCiAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAobXNnLm5hdGl2ZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFFIKCdzdGF0dXN0ZXh0JywgJzxkaXYgY2xhc3M9IlNUQVRFQ0hBTkdFIj5QQVVTRUQgaW4gW05BVElWRSBDT0RFXTwvZGl2PicpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBRSCgnTG9nV2luZG93JywgJycpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBRSCgnc3RhdHVzdGV4dCcsICc8ZGl2IGNsYXNzPSJTVEFURUNIQU5HRSI+UEFVU0VEIGluICcgKyBtc2cuZmlsZSArICc6JyArIG1zZy5saW5lICsgJzwvZGl2PicpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCg0KICAgICAgICAgICAgICAgICAgICAgICAgdG9nZ2xlQ29sb3IoUVMoIlN0ZXBPdmVyQnV0dG9uIiksIDEpOw0KICAgICAgICAgICAgICAgICAgICAgICAgdG9nZ2xlQ29sb3IoUVMoIlN0ZXBJbnRvQnV0dG9uIiksIDEpOw0KICAgICAgICAgICAgICAgICAgICAgICAgdG9nZ2xlQ29sb3IoUVMoIlN0ZXBPdXRCdXR0b24iKSwgMSk7DQogICAgICAgICAgICAgICAgICAgICAgICBRSCgiUGF1c2VSZXN1bWVCdXR0b24iLCAiUkVTVU1FIik7DQogICAgICAgICAgICAgICAgICAgICAgICBRRSgncXVlcnlUZXh0JywgMSk7DQogICAgICAgICAgICAgICAgICAgICAgICBRRSgncXVlcnlCdXR0b24nLCAxKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCdjbGVhckJ1dHRvbicsIDEpOw0KICAgICAgICAgICAgICAgICAgICAgICAgUUUoJ2xvY2Fsc0J1dHRvbicsIDEpOw0KICAgICAgICAgICAgICAgICAgICAgICAgUUUoJ2V2YWxCdXR0b24nLCAxKTsNCg0KICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1zZy5maWxlID09IGN1cnJlbnRNb2R1bGUpDQogICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgUUJDKHNvdXJjZUxpbmVEaXYobXNnLmxpbmUpLCBoaWdobGlnaHQpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnRMaW5lID0gbXNnLmxpbmU7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgUShzb3VyY2VMaW5lRGl2KGN1cnJlbnRMaW5lKSkuc2Nyb2xsSW50b1ZpZXcoe2Jsb2NrOiAnY2VudGVyJ30pOw0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgZWxzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnRMaW5lID0gbXNnLmxpbmU7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGpqID0geyBjbWQ6ICdTT1VSQ0UnLCBuYW1lOiBtc2cuZmlsZSB9Ow0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdzb2NrZXQuc2VuZChKU09OLnN0cmluZ2lmeShqaikpOw0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICAgICAgICAgIGNhc2UgJ1JVTk5JTkcnOg0KICAgICAgICAgICAgICAgICAgICAgICAgUUgoJ3N0YXR1c3RleHQnLCAnPGRpdiBjbGFzcz0iU1RBVEVDSEFOR0UiPlJVTk5JTkcgJyArIG1zZy5maWxlICsgJzwvZGl2PicpOw0KICAgICAgICAgICAgICAgICAgICAgICAgUUgoIlBhdXNlUmVzdW1lQnV0dG9uIiwgIlBBVVNFIik7DQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcE92ZXJCdXR0b24iKSwgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcEludG9CdXR0b24iKSwgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcE91dEJ1dHRvbiIpLCAwKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCdxdWVyeVRleHQnLCAwKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCdxdWVyeUJ1dHRvbicsIDApOw0KICAgICAgICAgICAgICAgICAgICAgICAgUUUoJ2NsZWFyQnV0dG9uJywgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICBRRSgnbG9jYWxzQnV0dG9uJywgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICBRRSgnZXZhbEJ1dHRvbicsIDApOw0KDQoNCiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOw0KICAgICAgICAgICAgICAgICAgICBjYXNlICdUSFJPVyc6DQogICAgICAgICAgICAgICAgICAgICAgICBoaWdobGlnaHQgPSBleGNlcHRpb25IaWdobGlnaHQ7DQogICAgICAgICAgICAgICAgICAgICAgICBleGNlcHRpb25NZXNzYWdlID0gJyoqICcgKyBtc2cubXNnICsgJyAqKic7DQogICAgICAgICAgICAgICAgICAgICAgICBicmVhazsNCiAgICAgICAgICAgICAgICAgICAgY2FzZSAnREVUQUNIJzoNCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc3BsYXlTdGF0dXMoIkRlYnVnZ2VyIERldGFjaGVkLi4uIik7DQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiUGF1c2VSZXN1bWVCdXR0b24iKSwgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICBRSCgiQXR0YWNoQnV0dG9uIiwgIkFUVEFDSCIpOw0KDQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcE92ZXJCdXR0b24iKSwgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcEludG9CdXR0b24iKSwgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICB0b2dnbGVDb2xvcihRUygiU3RlcE91dEJ1dHRvbiIpLCAwKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCJsb2FkU291cmNlQnV0dG9uIiwgMCk7DQogICAgICAgICAgICAgICAgICAgICAgICBRRSgibG9hZFNvdXJjZVRleHQiLCAwKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCdxdWVyeVRleHQnLCAwKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFFKCdxdWVyeUJ1dHRvbicsIDApOw0KICAgICAgICAgICAgICAgICAgICAgICAgY3VycmVudE1vZHVsZSA9ICcnOw0KICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICAgICAgICAgIGNhc2UgJ1FVRVJZJzoNCiAgICAgICAgICAgICAgICAgICAgICAgIFFBKCJxdWVyeVJlc3VsdHMiLCAnPGRpdiBjbGFzcz0iVkFSUVVFUlkiPicgKyBtc2cudmFyICsgJz08L2Rpdj4nKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIFFBKCJxdWVyeVJlc3VsdHMiLCAnPGRpdiBjbGFzcz0iVkFSUkVTVUxUIj4gICAnICsgbXNnLnZhbCArICc8L2Rpdj4nKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOw0KICAgICAgICAgICAgICAgICAgICBjYXNlICdFVkFMJzoNCiAgICAgICAgICAgICAgICAgICAgICAgIFFBKCJxdWVyeVJlc3VsdHMiLCAnPGRpdiBjbGFzcz0iVkFSUVVFUlkiPicgKyBtc2cuZXZhbCArICc9PC9kaXY+Jyk7DQogICAgICAgICAgICAgICAgICAgICAgICBRQSgicXVlcnlSZXN1bHRzIiwgJzxkaXYgY2xhc3M9IlZBUlJFU1VMVCI+ICAgJyArIG1zZy52YWwgKyAnPC9kaXY+Jyk7DQogICAgICAgICAgICAgICAgICAgICAgICBicmVhazsNCiAgICAgICAgICAgICAgICAgICAgY2FzZSAnU09VUkNFJzoNCiAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnRNb2R1bGUgPSBtc2cubmFtZTsNCiAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnRNb2R1bGVUb2tlbnMgPSBtc2cuc291cmNlLnNwbGl0KCdccicpLmpvaW4oJycpLnNwbGl0KCcgJykuam9pbignJm5ic3A7Jykuc3BsaXQoJ1x0Jykuam9pbignJm5ic3A7Jm5ic3A7Jm5ic3A7Jykuc3BsaXQoJ1xuJyk7DQogICAgICAgICAgICAgICAgICAgICAgICBRSCgnTG9nV2luZG93JywgJycpOw0KICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGxpbmVzID0gW107DQogICAgICAgICAgICAgICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGN1cnJlbnRNb2R1bGVUb2tlbnMubGVuZ3RoOysraSkNCiAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5lcy5wdXNoKCc8ZGl2IGNsYXNzPSJTT1VSQ0VDT0RFUEFSRU5UIiBvbmRibGNsaWNrPSJvblRvZ2dsZUJyZWFrcG9pbnQoJyArIChpKzEpICsgJykiPicpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzLnB1c2goJzxkaXYgY2xhc3M9IlNPVVJDRUNPREVMSU5FIiBpZD0iTCcgKyBzb3VyY2VMaW5lRGl2KGkgKyAxKSArICciPicgKyAoaSArIDEpICsgJzwvZGl2PicpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzLnB1c2goJzxkaXYgY2xhc3M9IlNPVVJDRUNPREUiIGlkPSInICsgc291cmNlTGluZURpdihpICsgMSkgKyAnIj48Y29kZSBzdHlsZT0id2hpdGUtc3BhY2U6bm9ybWFsIj4nICsgY3VycmVudE1vZHVsZVRva2Vuc1tpXSArICc8L2NvZGU+PC9kaXY+Jyk7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZXMucHVzaCgnPC9kaXY+Jyk7DQogICAgICAgICAgICAgICAgICAgICAgICB9DQoNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjbGluZSA9IGdldFNvdXJjZUxpbmUoKTsNCg0KICAgICAgICAgICAgICAgICAgICAgICAgUUEoJ0xvZ1dpbmRvdycsIGxpbmVzLmpvaW4oJycpKTsNCg0KICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNsaW5lID49IDApDQogICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgUUJDKHNvdXJjZUxpbmVEaXYoY2xpbmUpLCBoaWdobGlnaHQpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIFEoc291cmNlTGluZURpdihjbGluZSkpLnNjcm9sbEludG9WaWV3KHsgYmxvY2s6ICdjZW50ZXInIH0pOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIFEoJ0wnICsgc291cmNlTGluZURpdihjbGluZSkpLnNjcm9sbEludG9WaWV3KHsgaW5saW5lOiAnc3RhcnQnIH0pOw0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KDQogICAgICAgICAgICAgICAgICAgICAgICB2YXIgZGl2cyA9IFFDSElMRFJFTigiQ3VycmVudEJyZWFrcG9pbnRzIik7DQogICAgICAgICAgICAgICAgICAgICAgICBmb3IgKHZhciBpIGluIGRpdnMpDQogICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYoY3VycmVudE1vZHVsZS50b0xvd2VyQ2FzZSgpLmVuZHNXaXRoKCcuanMnKSB8fCBjdXJyZW50TW9kdWxlID09ICc8PE5BVElWRT4+JykNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmKGRpdnNbaV0uaWQuc3RhcnRzV2l0aCgnX2JwX18nKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUUJDKCdMJyArIHNvdXJjZUxpbmVEaXYoZGl2c1tpXS5pZC5zbGljZSg1KSksIGJwQ29sb3IpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkaXZzW2ldLmlkLnN0YXJ0c1dpdGgoJ19icF8nICsgY3VycmVudE1vZHVsZSArICdfJykpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFFCQygnTCcgKyBzb3VyY2VMaW5lRGl2KGRpdnNbaV0uaWQuc2xpY2UoNSArIGN1cnJlbnRNb2R1bGUubGVuZ3RoKSksIGJwQ29sb3IpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICAgICAgICAgIGNhc2UgJ0JSRUFLUE9JTlQnOg0KICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGl0ZW1zID0gW107DQogICAgICAgICAgICAgICAgICAgICAgICBmb3IgKHZhciBpIGluIG1zZy5saXN0KQ0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBmbiA9IG1zZy5saXN0W2ldLmZpbGVOYW1lOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBpZDsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZihmbi50b0xvd2VyQ2FzZSgpLmVuZHNXaXRoKCcuanMnKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gJ19icF9fJyArIG1zZy5saXN0W2ldLmxpbmVOdW1iZXI7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZW1zLnB1c2goJzxkaXYgaWQ9IicgKyBpZCArICciIGNsYXNzPSJCUkVBS1BPSU5UIiBvbmNsaWNrPSJvbkJyZWFrcG9pbnRDbGljayhcJycgKyBpZCArICdcJykiPjonICsgbXNnLmxpc3RbaV0ubGluZU51bWJlciArICc8L2Rpdj4nKTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAnX2JwXycgKyBtc2cubGlzdFtpXS5maWxlTmFtZSArICdfJyArIG1zZy5saXN0W2ldLmxpbmVOdW1iZXI7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZW1zLnB1c2goJzxkaXYgaWQ9IicgKyBpZCArICciIGNsYXNzPSJCUkVBS1BPSU5UIiBvbmNsaWNrPSJvbkJyZWFrcG9pbnRDbGljayhcJycgKyBpZCArICdcJykiPicgKyBtc2cubGlzdFtpXS5maWxlTmFtZSArICc6JyArIG1zZy5saXN0W2ldLmxpbmVOdW1iZXIgKyAnPC9kaXY+Jyk7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgUUgoIkN1cnJlbnRCcmVha3BvaW50cyIsIGl0ZW1zLmpvaW4oJycpKTsNCiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOw0KICAgICAgICAgICAgICAgICAgICBjYXNlICdDQUxMU1RBQ0snOg0KICAgICAgICAgICAgICAgICAgICAgICAgY3VycmVudENhbGxzdGFjayA9IG1zZy5jYWxsc3RhY2s7DQogICAgICAgICAgICAgICAgICAgICAgICBkaXNwbGF5Q2FsbHN0YWNrKCk7DQogICAgICAgICAgICAgICAgICAgICAgICBicmVhaw0KICAgICAgICAgICAgICAgICAgICBjYXNlICdMT0NBTFMnOg0KICAgICAgICAgICAgICAgICAgICAgICAgZm9yICh2YXIgdiBpbiBtc2cudmFsKQ0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChtc2cudmFsW3ZdLnR5cGUgPT0gJ3VuZGVmaW5lZCcpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBRQSgicXVlcnlSZXN1bHRzIiwgJzxkaXYgY2xhc3M9IlZBUlFVRVJZIj4nICsgbXNnLnZhbFt2XS5rZXkgKyAnPTwvZGl2PicpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3c29ja2V0LnNlbmQoJ3siY21kIjogIlFVRVJZIiwgInZhciI6ICInICsgbXNnLnZhbFt2XS5rZXkgKyAnIiwgImxldmVsIjogJyArIGdldENhbGxzdGFja0xldmVsKCkgKyAnfScpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOw0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIH0NCiAgICAgICAgfQ0NICAgICAgICBmdW5jdGlvbiBnZXRTb3VyY2VMaW5lKCkNICAgICAgICB7DSAgICAgICAgICAgIGZvcih2YXIgaSA9IDA7IGkgPCBjdXJyZW50Q2FsbHN0YWNrLmxlbmd0aDsgKytpKQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIGlmIChRUygnX3N0YWNrXycgKyBpKS5jb2xvciA9PSBjYWxsc3RhY2tfc2NvcGUgJiYgY3VycmVudENhbGxzdGFja1tpXS5maWxlTmFtZSA9PSBjdXJyZW50TW9kdWxlKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIChjdXJyZW50Q2FsbHN0YWNrW2ldLmxpbmVOdW1iZXIpOw0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIHJldHVybiAoLTEpOw0KICAgICAgICB9DQ0gICAgICAgIGZ1bmN0aW9uIHRvZ2dsZUNvbG9yKGlzdHlsZSwgc3RhdGUpDQogICAgICAgIHsNCiAgICAgICAgICAgIGlzdHlsZVsiYmFja2dyb3VuZC1jb2xvciJdID0gc3RhdGUgPyAnYmx1ZScgOiAnZ3JleSc7DQogICAgICAgICAgICBpc3R5bGVbImN1cnNvciJdID0gc3RhdGUgPyAncG9pbnRlcicgOiAnZGVmYXVsdCc7DQogICAgICAgIH0NDSAgICAgICAgZnVuY3Rpb24gYXR0YWNoRGVidWdnZXIoKQ0KICAgICAgICB7DQogICAgICAgICAgICBzd2l0Y2goUSgiQXR0YWNoQnV0dG9uIikuaW5uZXJIVE1MKQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIGNhc2UgJ0FUVEFDSCc6DQogICAgICAgICAgICAgICAgICAgIGRpc3BsYXlTdGF0dXMoIkF0dGFjaGluZyBkZWJ1Z2dlci4uLiIpOw0KICAgICAgICAgICAgICAgICAgICB3c29ja2V0LnNlbmQoJ3siY21kIjogIkFUVEFDSCJ9Jyk7DQogICAgICAgICAgICAgICAgICAgIGJyZWFrOw0KICAgICAgICAgICAgICAgIGNhc2UgJ0RFVEFDSCc6DQogICAgICAgICAgICAgICAgICAgIGN1cnJlbnRDYWxsc3RhY2sgPSBbXTsNCiAgICAgICAgICAgICAgICAgICAgZGlzcGxheUNhbGxzdGFjaygpOw0KDQogICAgICAgICAgICAgICAgICAgIGRpc3BsYXlTdGF0dXMoIkRldGFjaGluZyBkZWJ1Z2dlci4uLiIpOw0KICAgICAgICAgICAgICAgICAgICBjdXJyZW50TW9kdWxlID0gJyc7DQogICAgICAgICAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiREVUQUNIIn0nKTsNCiAgICAgICAgICAgICAgICAgICAgUVYoJ3BhZ2VfY29udGVudCcsIGZhbHNlKTsNCiAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICB9DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBwYXVzZVJlc3VtZSgpDQogICAgICAgIHsNCiAgICAgICAgICAgIHN3aXRjaChRKCJQYXVzZVJlc3VtZUJ1dHRvbiIpLmlubmVySFRNTCkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBjYXNlICdQQVVTRSc6DQogICAgICAgICAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiUEFVU0UifScpOw0KICAgICAgICAgICAgICAgICAgICBicmVhazsNCiAgICAgICAgICAgICAgICBjYXNlICdSRVNVTUUnOg0KICAgICAgICAgICAgICAgICAgICBoaWdobGlnaHQgPSBzb3VyY2VIaWdobGlnaHQ7DQogICAgICAgICAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiUkVTVU1FIn0nKTsNCiAgICAgICAgICAgICAgICAgICAgUUJDKHNvdXJjZUxpbmVEaXYoY3VycmVudExpbmUpLCAnd2hpdGUnKTsNCg0KICAgICAgICAgICAgICAgICAgICBjdXJyZW50Q2FsbHN0YWNrID0gW107DQogICAgICAgICAgICAgICAgICAgIGRpc3BsYXlDYWxsc3RhY2soKTsNCiAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICB9DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBzdGVwT3ZlcigpDQogICAgICAgIHsNCiAgICAgICAgICAgIGhpZ2hsaWdodCA9IHNvdXJjZUhpZ2hsaWdodDsNCiAgICAgICAgICAgIFFCQyhzb3VyY2VMaW5lRGl2KGN1cnJlbnRMaW5lKSwgJ3doaXRlJyk7DQogICAgICAgICAgICB3c29ja2V0LnNlbmQoJ3siY21kIjogIlNURVBPVkVSIn0nKTsNCiAgICAgICAgfQ0gICAgICAgIGZ1bmN0aW9uIHN0ZXBJbnRvKCkNICAgICAgICB7DQogICAgICAgICAgICBoaWdobGlnaHQgPSBzb3VyY2VIaWdobGlnaHQ7DQogICAgICAgICAgICBRQkMoc291cmNlTGluZURpdihjdXJyZW50TGluZSksICd3aGl0ZScpOw0KICAgICAgICAgICAgd3NvY2tldC5zZW5kKCd7ImNtZCI6ICJTVEVQSU5UTyJ9Jyk7DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBzdGVwT3V0KCkNCiAgICAgICAgew0KICAgICAgICAgICAgaGlnaGxpZ2h0ID0gc291cmNlSGlnaGxpZ2h0Ow0KICAgICAgICAgICAgUUJDKHNvdXJjZUxpbmVEaXYoY3VycmVudExpbmUpLCAnd2hpdGUnKTsNCiAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiU1RFUE9VVCJ9Jyk7DQogICAgICAgIH0NICAgICAgICANICAgICAgICBmdW5jdGlvbiBkZWxCcmVha3BvaW50KCkNICAgICAgICB7DQogICAgICAgICAgICBRVihzZWxlY3RlZEJyZWFrcG9pbnQsIDApOw0KICAgICAgICAgICAgc2VsZWN0ZWRCcmVha3BvaW50ID0gIiI7DQogICAgICAgICAgICBRRSgiZGVsQnJlYWtwb2ludEJ1dHRvbiIsIDApOw0KICAgICAgICB9DSAgICAgICAgZnVuY3Rpb24gb25CcmVha3BvaW50Q2xpY2soaWQpDSAgICAgICAgew0KICAgICAgICAgICAgaWYgKHNlbGVjdGVkQnJlYWtwb2ludCA9PSBpZCkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBRUyhpZClbImJhY2tncm91bmQtY29sb3IiXSA9ICd3aGl0ZSc7DQogICAgICAgICAgICAgICAgc2VsZWN0ZWRCcmVha3BvaW50ID0gIiI7DQogICAgICAgICAgICAgICAgUUUoImRlbEJyZWFrcG9pbnRCdXR0b24iLCAwKTsNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIGVsc2UNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBpZiAoc2VsZWN0ZWRCcmVha3BvaW50ICE9ICIiKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgUVMoc2VsZWN0ZWRCcmVha3BvaW50KVsiYmFja2dyb3VuZC1jb2xvciJdID0gJ3doaXRlJzsNCiAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgUVMoaWQpWyJiYWNrZ3JvdW5kLWNvbG9yIl0gPSAneWVsbG93JzsNCiAgICAgICAgICAgICAgICBzZWxlY3RlZEJyZWFrcG9pbnQgPSBpZDsNCiAgICAgICAgICAgICAgICBRRSgiZGVsQnJlYWtwb2ludEJ1dHRvbiIsIDEpOw0KICAgICAgICAgICAgfQ0KDQogICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGN1cnJlbnRDYWxsc3RhY2subGVuZ3RoOyArK2kpDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgUVMoJ19zdGFja18nICsgaSkuY29sb3IgPSBjYWxsc3RhY2tfZGVmYXVsdDsNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIHZhciBicCA9IFFIVkFMKGlkKS5zcGxpdCgnOicpWzBdOw0KICAgICAgICAgICAgdmFyIGpqID0geyBjbWQ6ICdTT1VSQ0UnLCBuYW1lOiBicCA9PSAnJyA/ICc8PE5BVElWRT4+JyA6IGJwIH07DQogICAgICAgICAgICB3c29ja2V0LnNlbmQoSlNPTi5zdHJpbmdpZnkoamopKTsgICAgICAgIA0KICAgICAgICB9DSAgICAgICAgZnVuY3Rpb24gZ2V0Q2FsbHN0YWNrTGV2ZWwoKQ0gICAgICAgIHsNCiAgICAgICAgICAgIC8vIERldGVybWluZSB0aGUgQ2FsbHN0YWNrIExldmVsDQogICAgICAgICAgICB2YXIgbGV2ZWwgPSAtMTsNCiAgICAgICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY3VycmVudENhbGxzdGFjay5sZW5ndGg7ICsraSkgew0KICAgICAgICAgICAgICAgIGlmIChRUygnX3N0YWNrXycgKyBpKS5jb2xvciA9PSBjYWxsc3RhY2tfc2NvcGUpIHsNCiAgICAgICAgICAgICAgICAgICAgbGV2ZWwgPSAwIC0gKGkgKyAxKTsNCiAgICAgICAgICAgICAgICAgICAgYnJlYWs7DQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgcmV0dXJuIChsZXZlbCk7DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBxdWVyeVZhbCgpDSAgICAgICAgew0KICAgICAgICAgICAgLy9RQSgicXVlcnlSZXN1bHRzIiwgJzxkaXYgY2xhc3M9IlZBUlFVRVJZIj4nICsgUVZBTCgncXVlcnlUZXh0JykgKyAnPTwvZGl2PicpOw0KICAgICAgICAgICAgd3NvY2tldC5zZW5kKCd7ImNtZCI6ICJRVUVSWSIsICJ2YXIiOiAiJyArIFFWQUwoJ3F1ZXJ5VGV4dCcpICsgJyIsICJsZXZlbCI6ICcgKyBnZXRDYWxsc3RhY2tMZXZlbCgpICsgJ30nKTsNCiAgICAgICAgICAgIFFWQUwoJ3F1ZXJ5VGV4dCcsICIiKTsNCiAgICAgICAgfQ0gICAgICAgIGZ1bmN0aW9uIGV2YWxTdHJpbmcoKQ0gICAgICAgIHsNCiAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiRVZBTCIsICJldmFsIjogIicgKyBRVkFMKCdxdWVyeVRleHQnKSArICciLCAibGV2ZWwiOiAnICsgZ2V0Q2FsbHN0YWNrTGV2ZWwoKSArICd9Jyk7DQogICAgICAgICAgICBRVkFMKCdxdWVyeVRleHQnLCAiIik7DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBsb2NhbHNRdWVyeSgpDSAgICAgICAgew0KICAgICAgICAgICAgUUEoInF1ZXJ5UmVzdWx0cyIsICc8ZGl2IGNsYXNzPSJWQVJRVUVSWSI+TG9jYWxzOiZuYnNwOzwvZGl2PicpOw0KICAgICAgICAgICAgd3NvY2tldC5zZW5kKCd7ImNtZCI6ICJMT0NBTFMiLCAibGV2ZWwiOiAnICsgZ2V0Q2FsbHN0YWNrTGV2ZWwoKSArICd9Jyk7DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBjbGVhclF1ZXJ5KCkNICAgICAgICB7DQogICAgICAgICAgICBRSCgncXVlcnlSZXN1bHRzJywgJycpOw0KICAgICAgICB9DSAgICAgICAgZnVuY3Rpb24gZGlzcGxheVN0YXR1cyhtc2csIG1zZ0NsYXNzKQ0gICAgICAgIHsNCiAgICAgICAgICAgIFFIKCdzdGF0dXN0ZXh0JywgJzxkaXYgY2xhc3M9IicgKyAobXNnQ2xhc3M/bXNnQ2xhc3M6J0dFTkVSSUNfTU9EVUxFJykgKyAnIj4nICsgbXNnICsgJzwvZGl2PicpOw0KICAgICAgICB9DSAgICAgICAgZnVuY3Rpb24gc291cmNlTGluZURpdihsaW5lKQ0gICAgICAgIHsNCiAgICAgICAgICAgIHJldHVybiAoJ19fc3JjJyArIGxpbmUpOw0KICAgICAgICB9DQ0gICAgICAgIGZ1bmN0aW9uIG9uVG9nZ2xlQnJlYWtwb2ludChsaW5lKQ0gICAgICAgIHsNCiAgICAgICAgICAgIFFCQygnTCcgKyBzb3VyY2VMaW5lRGl2KGxpbmUpLCBicENvbG9yKTsNCiAgICAgICAgICAgIHZhciBmbiA9IGN1cnJlbnRNb2R1bGU7DQoNCiAgICAgICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY3VycmVudENhbGxzdGFjay5sZW5ndGg7ICsraSkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBpZiAoUVMoJ19zdGFja18nICsgaSkuY29sb3IgPT0gY2FsbHN0YWNrX3Njb3BlKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgZm4gPSBjdXJyZW50Q2FsbHN0YWNrW2ldLmZpbGVOYW1lLnNwbGl0KCdcXCcpLmpvaW4oJ1xcXFwnKTsNCiAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICB9DQoNCiAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiQlJFQUtQT0lOVCIsICJmaWxlIjoiJyArIGZuICsgJyIsICJsaW5lIjonICsgbGluZSArICcsICJtb2RlIjoiYWRkIn0nKTsNCiAgICAgICAgfQ0gICAgICAgIGZ1bmN0aW9uIG9uRHVtcEhlYXAoKQ0gICAgICAgIHsNCiAgICAgICAgICAgIHdzb2NrZXQuc2VuZCgneyJjbWQiOiAiSEVBUCJ9Jyk7DQogICAgICAgIH0NICAgICAgICBmdW5jdGlvbiBmb3JjZUdDKCkNICAgICAgICB7DQogICAgICAgICAgICB3c29ja2V0LnNlbmQoJ3siY21kIjogIkdDIn0nKTsNCiAgICAgICAgfQ0gICAgICAgIGZ1bmN0aW9uIGRpc3BsYXlDYWxsc3RhY2soKQ0gICAgICAgIHsNCiAgICAgICAgICAgIHZhciBkaXNwbGF5ID0gW107DQogICAgICAgICAgICB2YXIgaW5kZW50ID0gJy0tPiZuYnNwOyc7DQogICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGN1cnJlbnRDYWxsc3RhY2subGVuZ3RoOyArK2kpDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgaWYgKGN1cnJlbnRDYWxsc3RhY2tbaV0uZnVuY05hbWUgPT0gJycpIHsgY3VycmVudENhbGxzdGFja1tpXS5mdW5jTmFtZSA9ICcoYW5vbnltb3VzKSc7IH0NCiAgICAgICAgICAgICAgICB2YXIgZm5hbWUgPSBjdXJyZW50Q2FsbHN0YWNrW2ldLmZpbGVOYW1lLnNwbGl0KCdcXCcpLmpvaW4oJ1xcXFwnKTsNCg0KICAgICAgICAgICAgICAgIGRpc3BsYXkucHVzaCgnPGRpdiBkZXB0aD0iJyArIChpKzEpICsgJyIgaWQ9Il9zdGFja18nICsgaSArICciIGNsYXNzPSJDQUxMU1RBQ0siIG9uY2xpY2s9Im9uQ2FsbHN0YWNrQ2xpY2soXCcnICsgY3VycmVudENhbGxzdGFjay5sZW5ndGggKyAnXCcsIFwnX3N0YWNrXycgKyBpICsgJ1wnLCBcJycgKyBmbmFtZSArICdcJykiPicgKyBpbmRlbnQgKyBjdXJyZW50Q2FsbHN0YWNrW2ldLmZ1bmNOYW1lICsgJzonICsgY3VycmVudENhbGxzdGFja1tpXS5saW5lTnVtYmVyICsgJzwvZGl2PicpOw0KICAgICAgICAgICAgICAgIGluZGVudCA9ICcmbmJzcDsmbmJzcDsmbmJzcDsnICsgaW5kZW50Ow0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgUUgoJ0NhbGxzdGFja1dpbmRvdycsIGRpc3BsYXkuam9pbignJykpOw0KDQogICAgICAgICAgICBpZiAoY3VycmVudENhbGxzdGFjay5sZW5ndGggPiAwKQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIFFTKCdfc3RhY2tfMCcpLmNvbG9yID0gY2FsbHN0YWNrX3Njb3BlOw0KICAgICAgICAgICAgfQ0KICAgICAgICB9DSAgICAgICAgZnVuY3Rpb24gb25DYWxsc3RhY2tDbGljayhsZW4sIGlkLCBmaWxlbmFtZSkNICAgICAgICB7DQogICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbjsgKytpKQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIFFTKCdfc3RhY2tfJyArIGkpLmNvbG9yID0gY2FsbHN0YWNrX2RlZmF1bHQ7DQogICAgICAgICAgICB9DQogICAgICAgICAgICBRUyhpZCkuY29sb3IgPSBjYWxsc3RhY2tfc2NvcGU7DQogICAgICAgICAgICB2YXIgamogPSB7IGNtZDogJ1NPVVJDRScsIG5hbWU6IGZpbGVuYW1lIH07DQogICAgICAgICAgICB3c29ja2V0LnNlbmQoSlNPTi5zdHJpbmdpZnkoamopKTsNCiAgICAgICAgfQ0gICAgICAgIGZ1bmN0aW9uIG1hbnVhbGx5TG9hZFNvdXJjZSgpDSAgICAgICAgew0KICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjdXJyZW50Q2FsbHN0YWNrLmxlbmd0aDsgKytpKQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIFFTKCdfc3RhY2tfJyArIGkpLmNvbG9yID0gY2FsbHN0YWNrX2RlZmF1bHQ7DQogICAgICAgICAgICB9DQogICAgICAgICAgICBRSCgnTG9nV2luZG93JywgJycpOw0KDQogICAgICAgICAgICB2YXIgamogPSB7IGNtZDogJ1NPVVJDRScsIG5hbWU6IFFWQUwoJ2xvYWRTb3VyY2VUZXh0JykgPT0gJyc/Jzw8TkFUSVZFPj4nOlFWQUwoJ2xvYWRTb3VyY2VUZXh0JykgfTsNCiAgICAgICAgICAgIHdzb2NrZXQuc2VuZChKU09OLnN0cmluZ2lmeShqaikpOw0KICAgICAgICB9DSAgICAgICAgLy8gUG9sbCB0aGUgc2VydmVyLCBpZiBpdCByZXNwb25kcywgcmVmcmVzaCB0aGUgcGFnZS4NCiAgICAgICAgZnVuY3Rpb24gc2VydmVyUG9sbCgpDQogICAgICAgIHsNCiAgICAgICAgICAgIHhkciA9IG51bGw7DQogICAgICAgICAgICB0cnkgeyB4ZHIgPSBuZXcgWERvbWFpblJlcXVlc3QoKTsgfSBjYXRjaCAoZSkgeyB9DQogICAgICAgICAgICBpZiAoIXhkcikgeGRyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7DQogICAgICAgICAgICB4ZHIub3BlbigiSEVBRCIsIHdpbmRvdy5sb2NhdGlvbi5ocmVmKTsNCiAgICAgICAgICAgIHhkci50aW1lb3V0ID0gMTUwMDA7DQogICAgICAgICAgICB4ZHIub25sb2FkID0gZnVuY3Rpb24gKCkgeyByZWxvYWQoKTsgfTsNCiAgICAgICAgICAgIHhkci5vbmVycm9yID0geGRyLm9udGltZW91dCA9IGZ1bmN0aW9uICgpIHsgc2V0VGltZW91dChzZXJ2ZXJQb2xsLCAyMDAwKTsgfTsNCiAgICAgICAgICAgIHhkci5zZW5kKCk7DQogICAgICAgIH0NCg0KICAgICAgICBmdW5jdGlvbiByZWxvYWQoKSB7IHdpbmRvdy5sb2NhdGlvbi5ocmVmID0gd2luZG93LmxvY2F0aW9uLmhyZWY7IH0NCg0gICAgPC9zY3JpcHQ+DQo8L2JvZHk+DQo8L2h0bWw+DQo='; + +var optTargetHost = '127.0.0.1'; +var optTargetPort = 9091; +var optHttpPort = 9092; +var optJsonProxyPort = 9093; +var optJsonProxy = false; +var optSourceSearchDirs = ['../tests/ecmascript']; +var optDumpDebugRead = null; +var optDumpDebugWrite = null; +var optDumpDebugPretty = null; +var optLogMessages = true; + +// Marker objects for special protocol values +var DVAL_EOM = { type: 'eom' }; +var DVAL_REQ = { type: 'req' }; +var DVAL_REP = { type: 'rep' }; +var DVAL_ERR = { type: 'err' }; +var DVAL_NFY = { type: 'nfy' }; + + +// Commands initiated by Duktape +var CMD_STATUS = 0x01; +var CMD_UNUSED_2 = 0x02; // Duktape 1.x: print notify +var CMD_UNUSED_3 = 0x03; // Duktape 1.x: alert notify +var CMD_UNUSED_4 = 0x04; // Duktape 1.x: log notify +var CMD_THROW = 0x05; +var CMD_DETACHING = 0x06; +var CMD_APPNOTIFY = 0x07; + +// Commands initiated by the debug client (= us) +var CMD_BASICINFO = 0x10; +var CMD_TRIGGERSTATUS = 0x11; +var CMD_PAUSE = 0x12; +var CMD_RESUME = 0x13; +var CMD_STEPINTO = 0x14; +var CMD_STEPOVER = 0x15; +var CMD_STEPOUT = 0x16; +var CMD_LISTBREAK = 0x17; +var CMD_ADDBREAK = 0x18; +var CMD_DELBREAK = 0x19; +var CMD_GETVAR = 0x1a; +var CMD_PUTVAR = 0x1b; +var CMD_GETCALLSTACK = 0x1c; +var CMD_GETLOCALS = 0x1d; +var CMD_EVAL = 0x1e; +var CMD_DETACH = 0x1f; +var CMD_DUMPHEAP = 0x20; +var CMD_GETBYTECODE = 0x21; + +// Constants +var UI_MESSAGE_CLIPLEN = 128; +var LOCALS_CLIPLEN = 64; +var EVAL_CLIPLEN = 4096; +var GETVAR_CLIPLEN = 4096; +var SUPPORTED_DEBUG_PROTOCOL_VERSION = 2; + + +var debugCommandNames = {}; +debugCommandNames['0'] = 'Reserved_0'; +debugCommandNames['1'] = 'Status'; +debugCommandNames['2'] = 'Reserved_2'; +debugCommandNames['3'] = 'Reserved_3'; +debugCommandNames['4'] = 'Reserved_4'; +debugCommandNames['5'] = 'Throw'; +debugCommandNames['6'] = 'Detaching'; +debugCommandNames['7'] = 'AppNotify'; +debugCommandNames['8'] = 'Reserved_8'; +debugCommandNames['9'] = 'Reserved_9'; +debugCommandNames['10'] = 'Reserved_10'; +debugCommandNames['11'] = 'Reserved_11'; +debugCommandNames['12'] = 'Reserved_12'; +debugCommandNames['13'] = 'Reserved_13'; +debugCommandNames['14'] = 'Reserved_14'; +debugCommandNames['15'] = 'Reserved_15'; +debugCommandNames['16'] = 'BasicInfo'; +debugCommandNames['17'] = 'TriggerStatus'; +debugCommandNames['18'] = 'Pause'; +debugCommandNames['19'] = 'Resume'; +debugCommandNames['20'] = 'StepInto'; +debugCommandNames['21'] = 'StepOver'; +debugCommandNames['22'] = 'StepOut'; +debugCommandNames['23'] = 'ListBreak'; +debugCommandNames['24'] = 'AddBreak'; +debugCommandNames['25'] = 'DelBreak'; +debugCommandNames['26'] = 'GetVar'; +debugCommandNames['27'] = 'PutVar'; +debugCommandNames['28'] = 'GetCallStack'; +debugCommandNames['29'] = 'GetLocals'; +debugCommandNames['30'] = 'Eval'; +debugCommandNames['31'] = 'Detach'; +debugCommandNames['32'] = 'DumpHeap'; +debugCommandNames['33'] = 'GetBytecode'; +debugCommandNames['34'] = 'AppRequest'; +debugCommandNames['35'] = 'GetHeapObjInfo'; +debugCommandNames['36'] = 'GetObjPropDesc'; +debugCommandNames['37'] = 'GetObjPropDescRange'; + +// Duktape heaphdr type constants, must match C headers +var DUK_HTYPE_STRING = 0; +var DUK_HTYPE_OBJECT = 1; +var DUK_HTYPE_BUFFER = 2; + +/* + * Debugger output formatting + */ + +function formatDebugValue(v) { + var buf, dec, len; + + // See doc/debugger.rst for format description. + + if (typeof v === 'object' && v !== null) { + // Note: typeof null === 'object', so null special case explicitly + if (v.type === 'eom') { + return new Buffer([0x00]); + } else if (v.type === 'req') { + return new Buffer([0x01]); + } else if (v.type === 'rep') { + return new Buffer([0x02]); + } else if (v.type === 'err') { + return new Buffer([0x03]); + } else if (v.type === 'nfy') { + return new Buffer([0x04]); + } else if (v.type === 'unused') { + return new Buffer([0x15]); + } else if (v.type === 'undefined') { + return new Buffer([0x16]); + } else if (v.type === 'number') { + dec = new Buffer(v.data, 'hex'); + len = dec.length; + if (len !== 8) { + throw new TypeError('value cannot be converted to dvalue: ' + JSON.stringify(v)); + } + buf = Buffer.alloc(1 + len); + buf[0] = 0x1a; + dec.copy(buf, 1); + return buf; + } else if (v.type === 'buffer') { + dec = new Buffer(v.data, 'hex'); + len = dec.length; + if (len <= 0xffff) { + buf = Buffer.alloc(3 + len); + buf[0] = 0x14; + buf[1] = (len >> 8) & 0xff; + buf[2] = (len >> 0) & 0xff; + dec.copy(buf, 3); + return buf; + } else { + buf = Buffer.alloc(5 + len); + buf[0] = 0x13; + buf[1] = (len >> 24) & 0xff; + buf[2] = (len >> 16) & 0xff; + buf[3] = (len >> 8) & 0xff; + buf[4] = (len >> 0) & 0xff; + dec.copy(buf, 5); + return buf; + } + } else if (v.type === 'object') { + dec = new Buffer(v.pointer, 'hex'); + len = dec.length; + buf = Buffer.alloc(3 + len); + buf[0] = 0x1b; + buf[1] = v.class; + buf[2] = len; + dec.copy(buf, 3); + return buf; + } else if (v.type === 'pointer') { + dec = new Buffer(v.pointer, 'hex'); + len = dec.length; + buf = Buffer.alloc(2 + len); + buf[0] = 0x1c; + buf[1] = len; + dec.copy(buf, 2); + return buf; + } else if (v.type === 'lightfunc') { + dec = new Buffer(v.pointer, 'hex'); + len = dec.length; + buf = Buffer.alloc(4 + len); + buf[0] = 0x1d; + buf[1] = (v.flags >> 8) & 0xff; + buf[2] = v.flags & 0xff; + buf[3] = len; + dec.copy(buf, 4); + return buf; + } else if (v.type === 'heapptr') { + dec = new Buffer(v.pointer, 'hex'); + len = dec.length; + buf = Buffer.alloc(2 + len); + buf[0] = 0x1e; + buf[1] = len; + dec.copy(buf, 2); + return buf; + } + } else if (v === null) { + return new Buffer([0x17]); + } else if (typeof v === 'boolean') { + return new Buffer([v ? 0x18 : 0x19]); + } else if (typeof v === 'number') { + if (Math.floor(v) === v && /* whole */ + (v !== 0 || 1 / v > 0) && /* not negative zero */ + v >= -0x80000000 && v <= 0x7fffffff) { + // Represented signed 32-bit integers as plain integers. + // Debugger code expects this for all fields that are not + // duk_tval representations (e.g. command numbers and such). + if (v >= 0x00 && v <= 0x3f) { + return new Buffer([0x80 + v]); + } else if (v >= 0x0000 && v <= 0x3fff) { + return new Buffer([0xc0 + (v >> 8), v & 0xff]); + } else if (v >= -0x80000000 && v <= 0x7fffffff) { + return new Buffer([0x10, + (v >> 24) & 0xff, + (v >> 16) & 0xff, + (v >> 8) & 0xff, + (v >> 0) & 0xff]); + } else { + throw new Error('internal error when encoding integer to dvalue: ' + v); + } + } else { + // Represent non-integers as IEEE double dvalues + buf = Buffer.alloc(1 + 8); + buf[0] = 0x1a; + buf.writeDoubleBE(v, 1); + return buf; + } + } else if (typeof v === 'string') { + if (v.length < 0 || v.length > 0xffffffff) { + // Not possible in practice. + throw new TypeError('cannot convert to dvalue, invalid string length: ' + v.length); + } + if (v.length <= 0x1f) { + buf = Buffer.alloc(1 + v.length); + buf[0] = 0x60 + v.length; + writeDebugStringToBuffer(v, buf, 1); + return buf; + } else if (v.length <= 0xffff) { + buf = Buffer.alloc(3 + v.length); + buf[0] = 0x12; + buf[1] = (v.length >> 8) & 0xff; + buf[2] = (v.length >> 0) & 0xff; + writeDebugStringToBuffer(v, buf, 3); + return buf; + } else { + buf = Buffer.alloc(5 + v.length); + buf[0] = 0x11; + buf[1] = (v.length >> 24) & 0xff; + buf[2] = (v.length >> 16) & 0xff; + buf[3] = (v.length >> 8) & 0xff; + buf[4] = (v.length >> 0) & 0xff; + writeDebugStringToBuffer(v, buf, 5); + return buf; + } + } + + // Shouldn't come here. + throw new TypeError('value cannot be converted to dvalue: ' + JSON.stringify(v)); +} + + +function DebugProtocolParser(inputStream, + protocolVersion, + rawDumpFileName, + textDumpFileName, + textDumpFilePrefix, + hexDumpConsolePrefix, + textDumpConsolePrefix) +{ + var _this = this; + this._ObjectID = 'DebugProtocolParser'; + this.inputStream = inputStream; + this.closed = false; // stream is closed/broken, don't parse anymore + this.bytes = 0; + this.dvalues = 0; + this.messages = 0; + this.requests = 0; + this.prevBytes = 0; + this.bytesPerSec = 0; + this.statsTimer = null; + this.readableNumberValue = true; + + events.EventEmitter.call(this); + + var buf = Buffer.alloc(0); // accumulate data + var msg = []; // accumulated message until EOM + var versionIdentification; + + var statsInterval = 2000; + var statsIntervalSec = statsInterval / 1000; + this.statsTimer = setInterval(function () { + _this.bytesPerSec = (_this.bytes - _this.prevBytes) / statsIntervalSec; + _this.prevBytes = _this.bytes; + _this.emit('stats-update'); + }, statsInterval); + + function consume(n) { + var tmp = Buffer.alloc(buf.length - n); + buf.copy(tmp, 0, n); + buf = tmp; + } + + inputStream.on('data', function (data) { + var i, n, x, v, gotValue, len, t, tmpbuf, verstr; + var prettyMsg; + + if (_this.closed || !_this.inputStream) + { + console.error('Ignoring incoming data from closed input stream, len ' + data.length); + return; + } + + _this.bytes += data.length; + if (rawDumpFileName) { + fs.appendFileSync(rawDumpFileName, data); + } + if (hexDumpConsolePrefix) { + console.info1(hexDumpConsolePrefix + data.toString('hex')); + } + + buf = Buffer.concat([buf, data]); + + // Protocol version handling. When dumping an output stream, the + // caller gives a non-null protocolVersion so we don't read one here. + + if (protocolVersion == null) { + if (buf.length > 1024) { + _this.emit('transport-error', 'Parse error (version identification too long), dropping connection'); + _this.close(); + return; + } + + + for (i = 0, n = buf.length; i < n; i++) + { + if (buf[i] == 0x0a) + { + tmpbuf = Buffer.alloc(i); + buf.copy(tmpbuf, 0, 0, i); + consume(i + 1); + verstr = tmpbuf.toString(); + t = verstr.split(' '); + protocolVersion = Number(t[0]); + versionIdentification = verstr; + + _this.emit('protocol-version', { + protocolVersion: protocolVersion, + versionIdentification: versionIdentification + }); + break; + } + } + + if (protocolVersion == null) + { + // Still waiting for version identification to complete. + return; + } + } + + // Parse complete dvalues (quite inefficient now) by trial parsing. + // Consume a value only when it's fully present in 'buf'. + // See doc/debugger.rst for format description. + + while (buf.length > 0) { + x = buf[0]; + v = undefined; + gotValue = false; // used to flag special values like undefined + + if (x >= 0xc0) { + // 0xc0...0xff: integers 0-16383 + if (buf.length >= 2) { + v = ((x - 0xc0) << 8) + buf[1]; + consume(2); + } + } else if (x >= 0x80) { + // 0x80...0xbf: integers 0-63 + v = x - 0x80; + consume(1); + } else if (x >= 0x60) { + // 0x60...0x7f: strings with length 0-31 + len = x - 0x60; + if (buf.length >= 1 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 1, 1 + len); + v = bufferToDebugString(v); + consume(1 + len); + } + } else { + switch (x) { + case 0x00: v = DVAL_EOM; consume(1); break; + case 0x01: v = DVAL_REQ; consume(1); break; + case 0x02: v = DVAL_REP; consume(1); break; + case 0x03: v = DVAL_ERR; consume(1); break; + case 0x04: v = DVAL_NFY; consume(1); break; + case 0x10: // 4-byte signed integer + if (buf.length >= 5) { + v = buf.readInt32BE(1); + consume(5); + } + break; + case 0x11: // 4-byte string + if (buf.length >= 5) { + len = buf.readUInt32BE(1); + if (buf.length >= 5 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 5, 5 + len); + v = bufferToDebugString(v); + consume(5 + len); + } + } + break; + case 0x12: // 2-byte string + if (buf.length >= 3) { + len = buf.readUInt16BE(1); + if (buf.length >= 3 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 3, 3 + len); + v = bufferToDebugString(v); + consume(3 + len); + } + } + break; + case 0x13: // 4-byte buffer + if (buf.length >= 5) { + len = buf.readUInt32BE(1); + if (buf.length >= 5 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 5, 5 + len); + v = { type: 'buffer', data: v.toString('hex') }; + consume(5 + len); + // Value could be a Node.js buffer directly, but + // we prefer all dvalues to be JSON compatible + } + } + break; + case 0x14: // 2-byte buffer + if (buf.length >= 3) { + len = buf.readUInt16BE(1); + if (buf.length >= 3 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 3, 3 + len); + v = { type: 'buffer', data: v.toString('hex') }; + consume(3 + len); + // Value could be a Node.js buffer directly, but + // we prefer all dvalues to be JSON compatible + } + } + break; + case 0x15: // unused/none + v = { type: 'unused' }; + consume(1); + break; + case 0x16: // undefined + v = { type: 'undefined' }; + gotValue = true; // indicate 'v' is actually set + consume(1); + break; + case 0x17: // null + v = null; + gotValue = true; // indicate 'v' is actually set + consume(1); + break; + case 0x18: // true + v = true; + consume(1); + break; + case 0x19: // false + v = false; + consume(1); + break; + case 0x1a: // number (IEEE double), big endian + if (buf.length >= 9) { + v = Buffer.alloc(8); + buf.copy(v, 0, 1, 9); + v = { type: 'number', data: v.toString('hex') }; + + if (_this.readableNumberValue) { + // The value key should not be used programmatically, + // it is just there to make the dumps more readable. + v.value = buf.readDoubleBE(1); + } + consume(9); + } + break; + case 0x1b: // object + if (buf.length >= 3) { + len = buf[2]; + if (buf.length >= 3 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 3, 3 + len); + v = { type: 'object', 'class': buf[1], pointer: v.toString('hex') }; + consume(3 + len); + } + } + break; + case 0x1c: // pointer + if (buf.length >= 2) { + len = buf[1]; + if (buf.length >= 2 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 2, 2 + len); + v = { type: 'pointer', pointer: v.toString('hex') }; + consume(2 + len); + } + } + break; + case 0x1d: // lightfunc + if (buf.length >= 4) { + len = buf[3]; + if (buf.length >= 4 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 4, 4 + len); + v = { type: 'lightfunc', flags: buf.readUInt16BE(1), pointer: v.toString('hex') }; + consume(4 + len); + } + } + break; + case 0x1e: // heapptr + if (buf.length >= 2) { + len = buf[1]; + if (buf.length >= 2 + len) { + v = Buffer.alloc(len); + buf.copy(v, 0, 2, 2 + len); + v = { type: 'heapptr', pointer: v.toString('hex') }; + consume(2 + len); + } + } + break; + default: + _this.emit('transport-error', 'Parse error, dropping connection'); + _this.close(); + } + } + + if (typeof v === 'undefined' && !gotValue) { + break; + } + msg.push(v); + _this.dvalues++; + + // Could emit a 'debug-value' event here, but that's not necessary + // because the receiver will just collect statistics which can also + // be done using the finished message. + + if (v === DVAL_EOM) + { + _this.messages++; + + if (textDumpFileName || textDumpConsolePrefix) { + prettyMsg = prettyDebugMessage(msg); + if (textDumpFileName) { + fs.appendFileSync(textDumpFileName, (textDumpFilePrefix || '') + prettyMsg + '\n'); + } + if (textDumpConsolePrefix) { + console.info1(textDumpConsolePrefix + prettyMsg); + } + } + + _this.emit('debug-message', msg); + msg = []; // new object, old may be in circulation for a while + } + } + }); + + // Not all streams will emit this. + inputStream.on('error', function (err) { + _this.emit('transport-error', err); + _this.close(); + }); + + // Not all streams will emit this. + inputStream.on('close', function () { + _this.close(); + }); +} + +DebugProtocolParser.prototype.close = function () { + // Although the underlying transport may not have a close() or destroy() + // method or even a 'close' event, this method is always available and + // will generate a 'transport-close'. + // + // The caller is responsible for closing the underlying stream if that + // is necessary. + + if (this.closed) { return; } + + this.closed = true; + if (this.statsTimer) { + clearInterval(this.statsTimer); + this.statsTimer = null; + } + this.emit('transport-close'); +}; + + +/* + * Debugger implementation + * + * A debugger instance communicates with the debug target and maintains + * persistent debug state so that the current state can be resent to the + * socket.io client (web UI) if it reconnects. Whenever the debugger state + * is changed an event is generated. The socket.io handler will listen to + * state change events and push the necessary updates to the web UI, often + * in a rate limited fashion or using a client pull to ensure the web UI + * is not overloaded. + * + * The debugger instance assumes that if the debug protocol connection is + * re-established, it is always to the same target. There is no separate + * abstraction for a debugger session. + */ + +function Debugger() +{ + events.EventEmitter.call(this); + this._ObjectID = 'Debugger'; + this.web = null; // web UI singleton + this.targetStream = null; // transport connection to target + this.outputPassThroughStream = null; // dummy passthrough for message dumping + this.inputParser = null; // parser for incoming debug messages + this.outputParser = null; // parser for outgoing debug messages (stats, dumping) + this.protocolVersion = null; + this.dukVersion = null; + this.dukGitDescribe = null; + this.targetInfo = null; + this.debugger_attached = false; + this.handshook = false; + this.reqQueue = null; + this.stats = { // stats for current debug connection + rxBytes: 0, rxDvalues: 0, rxMessages: 0, rxBytesPerSec: 0, + txBytes: 0, txDvalues: 0, txMessages: 0, txBytesPerSec: 0 + }; + this.execStatus = { + attached: false, + state: 'detached', + fileName: '', + funcName: '', + line: 0, + pc: 0 + }; + this.breakpoints = []; + this.callstack = []; + this.locals = []; + this.messageLines = []; + this.messageScrollBack = 100; + + this.on('attached', function OnAttached() + { + this.websocket.write({ cmd: 'ATTACH' }); + console.info1('Debug transport connected'); + + this.debugger_attached = true; + this.reqQueue = []; + this.uiMessage('debugger-info', 'Debug transport connected'); + + this.sendStatusRequest().then(function () { statusPending = false; console.info1('finally'); }, console.info1); + hostCooperate(); + }); +} + + +Debugger.prototype.decodeBytecodeFromBuffer = function (buf, consts, funcs) +{ + var i, j, n, m, ins, pc; + var res = []; + var op, str, args, comments; + + // XXX: add constants inline to preformatted output (e.g. for strings, + // add a short escaped snippet as a comment on the line after the + // compact argument list). + + for (i = 0, n = buf.length; i < n; i += 4) { + pc = i / 4; + + // shift forces unsigned + if (this.endianness === 'little') { + ins = buf.readInt32LE(i) >>> 0; + } else { + ins = buf.readInt32BE(i) >>> 0; + } + + op = dukOpcodes.opcodes[ins & 0xff]; + + args = []; + comments = []; + if (op.args) { + for (j = 0, m = op.args.length; j < m; j++) { + var A = (ins >>> 8) & 0xff; + var B = (ins >>> 16) & 0xff; + var C = (ins >>> 24) & 0xff; + var BC = (ins >>> 16) & 0xffff; + var ABC = (ins >>> 8) & 0xffffff; + var Bconst = op & 0x01; + var Cconst = op & 0x02; + + switch (op.args[j]) { + case 'A_R': args.push('r' + A); break; + case 'A_RI': args.push('r' + A + '(indirect)'); break; + case 'A_C': args.push('c' + A); break; + case 'A_H': args.push('0x' + A.toString(16)); break; + case 'A_I': args.push(A.toString(10)); break; + case 'A_B': args.push(A ? 'true' : 'false'); break; + case 'B_RC': args.push((Bconst ? 'c' : 'r') + B); break; + case 'B_R': args.push('r' + B); break; + case 'B_RI': args.push('r' + B + '(indirect)'); break; + case 'B_C': args.push('c' + B); break; + case 'B_H': args.push('0x' + B.toString(16)); break; + case 'B_I': args.push(B.toString(10)); break; + case 'C_RC': args.push((Cconst ? 'c' : 'r') + C); break; + case 'C_R': args.push('r' + C); break; + case 'C_RI': args.push('r' + C + '(indirect)'); break; + case 'C_C': args.push('c' + C); break; + case 'C_H': args.push('0x' + C.toString(16)); break; + case 'C_I': args.push(C.toString(10)); break; + case 'BC_R': args.push('r' + BC); break; + case 'BC_C': args.push('c' + BC); break; + case 'BC_H': args.push('0x' + BC.toString(16)); break; + case 'BC_I': args.push(BC.toString(10)); break; + case 'ABC_H': args.push(ABC.toString(16)); break; + case 'ABC_I': args.push(ABC.toString(10)); break; + case 'BC_LDINT': args.push(BC - (1 << 15)); break; + case 'BC_LDINTX': args.push(BC - 0); break; // no bias in LDINTX + case 'ABC_JUMP': { + var pc_add = ABC - (1 << 23) + 1; // pc is preincremented before adding + var pc_dst = pc + pc_add; + args.push(pc_dst + ' (' + (pc_add >= 0 ? '+' : '') + pc_add + ')'); + break; + } + default: args.push('?'); break; + } + } + } + if (op.flags) { + for (j = 0, m = op.flags.length; j < m; j++) { + if (ins & op.flags[j].mask) { + comments.push(op.flags[j].name); + } + } + } + + if (args.length > 0) { + str = sprintf('%05d %08x %-10s %s', pc, ins, op.name, args.join(', ')); + } else { + str = sprintf('%05d %08x %-10s', pc, ins, op.name); + } + if (comments.length > 0) { + str = sprintf('%-44s ; %s', str, comments.join(', ')); + } + + res.push({ + str: str, + ins: ins + }); + } + + return res; +}; + +Debugger.prototype.uiMessage = function (type, val) +{ + var msg; + if (typeof type === 'object') { + msg = type; + } else if (typeof type === 'string') { + msg = { type: type, message: val }; + } else { + throw new TypeError('invalid ui message: ' + type); + } + this.messageLines.push(msg); + while (this.messageLines.length > this.messageScrollBack) { + this.messageLines.shift(); + } + this.emit('ui-message-update'); // just trigger a sync, gets rate limited +}; + +Debugger.prototype.sendRequest = function (msg) +{ + var _this = this; + return new Promise(function (resolve, reject) { + var dvals = []; + var dval; + var data; + var i; + + if (!_this.debugger_attached || !_this.handshook || !_this.reqQueue || !_this.targetStream) + { + console.error(_this.debugger_attached, _this.handshook, _this.reqQueue); + throw new Error('invalid state for sendRequest'); + } + + for (i = 0; i < msg.length; i++) { + try { + dval = formatDebugValue(msg[i]); + } catch (e) { + console.error('Failed to format dvalue, dropping connection: ' + e); + console.error(e.stack || e); + _this.targetStream.destroy(); + throw new Error('failed to format dvalue'); + } + dvals.push(dval); + } + + data = Buffer.concat(dvals); + + //console.log('Writing: ' + data.toString('hex')); + + _this.targetStream.write(data); + + if (_this.outputPassThroughStream) { _this.outputPassThroughStream.write(data); } // stats and dumping + if (optLogMessages) { + console.info1('Request ' + prettyDebugCommand(msg[1]) + ': ' + prettyDebugMessage(msg)); + } + + if (!_this.reqQueue) { + throw new Error('no reqQueue'); + } + _this.reqQueue.push({ + reqMsg: msg, + reqCmd: msg[1], + resolveCb: resolve, + rejectCb: reject + }); + }); +}; + +Debugger.prototype.sendBasicInfoRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_BASICINFO, DVAL_EOM]).then(function (msg) { + _this.dukVersion = msg[1]; + _this.dukGitDescribe = msg[2]; + _this.targetInfo = msg[3]; + _this.endianness = { 1: 'little', 2: 'mixed', 3: 'big' }[msg[4]] || 'unknown'; + _this.emit('basic-info-update'); + return msg; + }); +}; + +Debugger.prototype.sendGetVarRequest = function (varname, level) { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_GETVAR, (typeof level === 'number' ? level : -1), varname, DVAL_EOM]).then(function (msg) { + return { found: msg[1] === 1, value: msg[2] }; + }); +}; + +Debugger.prototype.sendPutVarRequest = function (varname, varvalue, level) { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_PUTVAR, (typeof level === 'number' ? level : -1), varname, varvalue, DVAL_EOM]); +}; + +Debugger.prototype.sendInvalidCommandTestRequest = function () { + // Intentional invalid command + var _this = this; + return this.sendRequest([DVAL_REQ, 0xdeadbeef, DVAL_EOM]); +}; + +Debugger.prototype.sendStatusRequest = function () { + // Send a status request to trigger a status notify, result is ignored: + // target sends a status notify instead of a meaningful reply + //console.log('this', this.sendRequest); + return (this.sendRequest([DVAL_REQ, CMD_TRIGGERSTATUS, DVAL_EOM])); +}; + +Debugger.prototype.sendBreakpointListRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_LISTBREAK, DVAL_EOM]).then(function (msg) { + var i, n; + var breakpts = []; + + for (i = 1, n = msg.length - 1; i < n; i += 2) { + breakpts.push({ fileName: msg[i], lineNumber: msg[i + 1] }); + } + + _this.breakpoints = breakpts; + _this.emit('breakpoints-update', breakpts); + return msg; + }); +}; + +Debugger.prototype.sendGetLocalsRequest = function (level) { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_GETLOCALS, (typeof level === 'number' ? level : -1), DVAL_EOM]).then(function (msg) { + var i; + var locals = []; + + for (i = 1; i <= msg.length - 2; i += 2) + { + // XXX: do pretty printing in debug client for now + locals.push({ key: msg[i], value: prettyUiDebugValue(msg[i + 1], LOCALS_CLIPLEN), type: (msg[i+1]?msg[i+1].type:'undefined') }); + } + + _this.locals = locals; + _this.emit('locals-update'); + return locals; + }); +}; + +Debugger.prototype.sendGetCallStackRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_GETCALLSTACK, DVAL_EOM]).then(function (msg) { + var i; + var stack = []; + + for (i = 1; i + 3 <= msg.length - 1; i += 4) + { + stack.push({ + fileName: msg[i], + funcName: msg[i + 1], + lineNumber: msg[i + 2], + pc: msg[i + 3] + }); + } + + _this.callstack = stack; + _this.emit('callstack-update'); + return msg; + }); +}; + +Debugger.prototype.sendStepIntoRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_STEPINTO, DVAL_EOM]); +}; + +Debugger.prototype.sendStepOverRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_STEPOVER, DVAL_EOM]); +}; + +Debugger.prototype.sendStepOutRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_STEPOUT, DVAL_EOM]); +}; + +Debugger.prototype.sendPauseRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_PAUSE, DVAL_EOM]); +}; + +Debugger.prototype.sendResumeRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_RESUME, DVAL_EOM]); +}; + +Debugger.prototype.sendEvalRequest = function (evalInput, level) { + var _this = this; + // Use explicit level if given. If no level is given, use null if the call + // stack is empty, -1 otherwise. This works well when we're paused and the + // callstack information is not liable to change before we do an Eval. + console.info1('levelType = ' + typeof (level)); + if (typeof level !== 'number') { + level = this.callstack && this.callstack.length > 0 ? -1 : null; + } + console.info1('level=' + level); + return this.sendRequest([DVAL_REQ, CMD_EVAL, level, evalInput, DVAL_EOM]).then(function (msg) { + return { error: msg[1] === 1 /*error*/, value: msg[2] }; + }); +}; + +Debugger.prototype.sendDetachRequest = function () { + var _this = this; + return this.sendRequest([DVAL_REQ, CMD_DETACH, DVAL_EOM]); +}; + +Debugger.prototype.sendDumpHeapRequest = function () { + var _this = this; + + return this.sendRequest([DVAL_REQ, CMD_DUMPHEAP, DVAL_EOM]).then(function (msg) { + var res = {}; + var objs = []; + var i, j, n, m, o, prop; + + res.type = 'heapDump'; + res.heapObjects = objs; + + for (i = 1, n = msg.length - 1; i < n; /*nop*/) { + o = {}; + o.ptr = msg[i++]; + o.type = msg[i++]; + o.flags = msg[i++] >>> 0; /* unsigned */ + o.refc = msg[i++]; + + if (o.type === DUK_HTYPE_STRING) { + o.blen = msg[i++]; + o.clen = msg[i++]; + o.hash = msg[i++] >>> 0; /* unsigned */ + o.data = msg[i++]; + } else if (o.type === DUK_HTYPE_BUFFER) { + o.len = msg[i++]; + o.data = msg[i++]; + } else if (o.type === DUK_HTYPE_OBJECT) { + o['class'] = msg[i++]; + o.proto = msg[i++]; + o.esize = msg[i++]; + o.enext = msg[i++]; + o.asize = msg[i++]; + o.hsize = msg[i++]; + o.props = []; + for (j = 0, m = o.enext; j < m; j++) { + prop = {}; + prop.flags = msg[i++]; + prop.key = msg[i++]; + prop.accessor = (msg[i++] == 1); + if (prop.accessor) { + prop.getter = msg[i++]; + prop.setter = msg[i++]; + } else { + prop.value = msg[i++]; + } + o.props.push(prop); + } + o.array = []; + for (j = 0, m = o.asize; j < m; j++) { + prop = {}; + prop.value = msg[i++]; + o.array.push(prop); + } + } else { + console.error('invalid htype: ' + o.type + ', disconnect'); + _this.disconnectDebugger(); + throw new Error('invalid htype'); + return; + } + + objs.push(o); + } + + return res; + }); +}; + +Debugger.prototype.sendGetBytecodeRequest = function () { + var _this = this; + + return this.sendRequest([DVAL_REQ, CMD_GETBYTECODE, -1 /* level; could be other than -1 too */, DVAL_EOM]).then(function (msg) { + var idx = 1; + var nconst; + var nfunc; + var val; + var buf; + var i, n; + var consts = []; + var funcs = []; + var bcode; + var preformatted; + var ret; + var idxPreformattedInstructions; + + //console.log(JSON.stringify(msg)); + + nconst = msg[idx++]; + for (i = 0; i < nconst; i++) { + val = msg[idx++]; + consts.push(val); + } + + nfunc = msg[idx++]; + for (i = 0; i < nfunc; i++) { + val = msg[idx++]; + funcs.push(val); + } + val = msg[idx++]; + + // Right now bytecode is a string containing a direct dump of the + // bytecode in target endianness. Decode here so that the web UI + // doesn't need to. + + buf = Buffer.alloc(val.length); + writeDebugStringToBuffer(val, buf, 0); + bcode = _this.decodeBytecodeFromBuffer(buf, consts, funcs); + + preformatted = []; + consts.forEach(function (v, i) { + preformatted.push('; c' + i + ' ' + JSON.stringify(v)); + }); + preformatted.push(''); + idxPreformattedInstructions = preformatted.length; + bcode.forEach(function (v) { + preformatted.push(v.str); + }); + preformatted = preformatted.join('\n') + '\n'; + + ret = { + constants: consts, + functions: funcs, + bytecode: bcode, + preformatted: preformatted, + idxPreformattedInstructions: idxPreformattedInstructions + }; + + return ret; + }); +}; + +Debugger.prototype.changeBreakpoint = function (fileName, lineNumber, mode) { + var _this = this; + + return this.sendRequest([DVAL_REQ, CMD_LISTBREAK, DVAL_EOM]).then(function (msg) { + var i, n; + var breakpts = []; + var deleted = false; + + // Up-to-date list of breakpoints on target + for (i = 1, n = msg.length - 1; i < n; i += 2) { + breakpts.push({ fileName: msg[i], lineNumber: msg[i + 1] }); + } + + // Delete matching breakpoints in reverse order so that indices + // remain valid. We do this for all operations so that duplicates + // are eliminated if present. + for (i = breakpts.length - 1; i >= 0; i--) { + var bp = breakpts[i]; + if (mode === 'deleteall' || (bp.fileName === fileName && bp.lineNumber === lineNumber)) + { + deleted = true; + _this.sendRequest([DVAL_REQ, CMD_DELBREAK, i, DVAL_EOM], function (msg) + { + // nop + }, function (err) { + // nop + }); + } + } + + // Technically we should wait for each delbreak reply but because + // target processes the requests in order, it doesn't matter. + if ((mode === 'add') || (mode === 'toggle' && !deleted)) { + _this.sendRequest([DVAL_REQ, CMD_ADDBREAK, fileName, lineNumber, DVAL_EOM], function (msg) + { + // nop + }, function (err) { + _this.uiMessage('debugger-info', 'Failed to add breakpoint: ' + err); + }); + } + + // Read final, effective breakpoints from the target + _this.sendBreakpointListRequest(); + hostCooperate(); + }); +}; + +Debugger.prototype.disconnectDebugger = function () +{ + console.info1('Debugger.disconnectDebugger();'); + if (this.targetStream) + { + // We require a destroy() method from the actual target stream + this.targetStream.end(); + this.targetStream = null; + } + if (this.inputParser) { + this.inputParser.close(); + this.inputParser = null; + } + if (this.outputPassThroughStream) { + // There is no close() or destroy() for a passthrough stream, so just + // close the outputParser which will cancel timers etc. + } + if (this.outputParser) { + this.outputParser.close(); + this.outputParser = null; + } + console.info1('disconnecting'); + this.debugger_attached = false; + this.handshook = false; + this.reqQueue = null; + this.execStatus = { + attached: false, + state: 'detached', + fileName: '', + funcName: '', + line: 0, + pc: 0 + }; +}; + +Debugger.prototype.connectDebugger = function connectDebugger() +{ + var _this = this; + this.disconnectDebugger(); // close previous target connection + + // CUSTOMTRANSPORT: to use a custom transport, change this.targetStream to + // use your custom transport. + + console.info1('Connecting to ' + optTargetHost + ':' + optTargetPort + '...'); + this.targetStream = require('net').createConnection(optTargetPort, optTargetHost); + this.targetStream.Debugger = this; + + this.targetStream.on('connect', function OnDebugConnect() + { + console.info1('Debugger Transport Connection Attached...'); + hostCooperate(); + }); + //this.targetStream.on('data', function (msg) { console.log('recv');}); + + try + { + this.inputParser = new DebugProtocolParser( + this.targetStream, + null, + optDumpDebugRead, + optDumpDebugPretty, + optDumpDebugPretty ? 'Recv: ' : null, + null, + null // console logging is done at a higher level to match request/response + ); + } + catch(ee) + { + console.error(ee); + } + + //// Use a PassThrough stream to debug dump and get stats for output messages. + //// Simply write outgoing data to both the targetStream and this passthrough + //// separately. + //this.outputPassThroughStream = stream.PassThrough(); + //this.outputParser = new DebugProtocolParser( + // this.outputPassThroughStream, + // 1, + // optDumpDebugWrite, + // optDumpDebugPretty, + // optDumpDebugPretty ? 'Send: ' : null, + // null, + // null // console logging is done at a higher level to match request/response + //); + + this.inputParser.on('transport-close', function () + { + console.info1('TRANSPORT-CLOSE'); + _this.uiMessage('debugger-info', 'Debug transport closed'); + _this.disconnectDebugger(); + _this.emit('exec-status-update'); + _this.emit('detached'); + }); + + this.inputParser.on('transport-error', function (err) + { + console.info1('TRANSPORT-ERROR', err); + _this.uiMessage('debugger-info', 'Debug transport error: ' + err); + _this.disconnectDebugger(); + }); + + this.inputParser.on('protocol-version', function (msg) { + var ver = msg.protocolVersion; + console.info1('Debug version identification:', msg.versionIdentification); + _this.protocolVersion = ver; + _this.uiMessage('debugger-info', 'Debug version identification: ' + msg.versionIdentification); + if (ver !== SUPPORTED_DEBUG_PROTOCOL_VERSION) { + console.error('Unsupported debug protocol version (got ' + ver + ', support ' + SUPPORTED_DEBUG_PROTOCOL_VERSION + ')'); + _this.uiMessage('debugger-info', 'Protocol version ' + ver + ' unsupported, dropping connection'); + _this.targetStream.destroy(); + } else { + _this.uiMessage('debugger-info', 'Debug protocol version: ' + ver); + _this.handshook = true; + _this.execStatus = { + attached: true, + state: 'attached', + fileName: '', + funcName: '', + line: 0, + pc: 0 + }; + _this.debugger_attached = true; + _this.emit('exec-status-update'); + _this.emit('attached'); // inform web UI + + // Fetch basic info right away + _this.sendBasicInfoRequest(); + } + }); + + this.inputParser.on('debug-message', function (msg) + { + _this.processDebugMessage(msg); + }); + + this.inputParser.on('stats-update', function () { + _this.stats.rxBytes = this.bytes; + _this.stats.rxDvalues = this.dvalues; + _this.stats.rxMessages = this.messages; + _this.stats.rxBytesPerSec = this.bytesPerSec; + _this.emit('debug-stats-update'); + }); + + if (this.outputParser) + { + this.outputParser.on('stats-update', function () { + _this.stats.txBytes = this.bytes; + _this.stats.txDvalues = this.dvalues; + _this.stats.txMessages = this.messages; + _this.stats.txBytesPerSec = this.bytesPerSec; + _this.emit('debug-stats-update'); + }); + } +}; + +Debugger.prototype.processDebugMessage = function (msg) { + var req; + var prevState, newState; + var err; + + if (msg[0] === DVAL_REQ) { + // No actual requests sent by the target right now (just notifys). + console.error('Unsolicited reply message, dropping connection: ' + prettyDebugMessage(msg)); + } else if (msg[0] === DVAL_REP) { + if (this.reqQueue.length <= 0) { + console.error('Unsolicited reply message, dropping connection: [' + this.reqQueue.length + '] ' + prettyDebugMessage(msg)); + this.targetStream.destroy(); + } + req = this.reqQueue.shift(); + + if (optLogMessages) { + console.info1('Reply for ' + prettyDebugCommand(req.reqCmd) + ': ' + prettyDebugMessage(msg)); + } + + if (req.resolveCb) { + req.resolveCb(msg); + } else { + // nop: no callback + } + } else if (msg[0] === DVAL_ERR) { + if (this.reqQueue.length <= 0) { + console.error('Unsolicited error message, dropping connection: ' + prettyDebugMessage(msg)); + this.targetStream.destroy(); + } + err = new Error(String(msg[2]) + ' (code ' + String(msg[1]) + ')'); + err.errorCode = msg[1] || 0; + req = this.reqQueue.shift(); + + if (optLogMessages) { + console.info1('Error for ' + prettyDebugCommand(req.reqCmd) + ': ' + prettyDebugMessage(msg)); + } + + console.info1(req.rejectCb, err); + + if (req.rejectCb) + { + req.rejectCb(err); + } else + { + // nop: no callback + } + } else if (msg[0] === DVAL_NFY) { + if (optLogMessages) { + console.info1('Notify ' + prettyDebugCommand(msg[1]) + ': ' + prettyDebugMessage(msg)); + } + + if (msg[1] === CMD_STATUS) + { + prevState = this.execStatus.state; + newState = msg[2] === 0 ? 'running' : 'paused'; + this.execStatus = { + attached: true, + state: newState, + fileName: (typeof(msg[3])=='string'?msg[3]:''), + funcName: (typeof (msg[4]) == 'string' ? msg[4] : ''), + line: msg[5], + pc: msg[6] + }; + console.info1('MSG=>', msg); + for (var ii in msg) + { + console.info1('[' + ii + ']', msg[ii]); + } + if (prevState !== newState && newState === 'paused') { + // update run state now that we're paused + if(typeof(msg[3])=='string') + { + this.sendBreakpointListRequest(); + this.sendGetLocalsRequest(); + this.sendGetCallStackRequest(); + } + } + + this.emit('exec-status-update'); + } else if (msg[1] === CMD_THROW) + { + console.info1(msg); + this.emit('throw', { fatal: msg[2], message: (msg[2] ? 'UNCAUGHT: ' : 'THROW: ') + prettyUiStringUnquoted(msg[3], UI_MESSAGE_CLIPLEN), fileName: msg[4], lineNumber: msg[5] }); + this.uiMessage({ type: 'throw', fatal: msg[2], message: (msg[2] ? 'UNCAUGHT: ' : 'THROW: ') + prettyUiStringUnquoted(msg[3], UI_MESSAGE_CLIPLEN), fileName: msg[4], lineNumber: msg[5] }); + } else if (msg[1] === CMD_DETACHING) { + this.uiMessage({ type: 'detaching', reason: msg[2], message: 'DETACH: ' + (msg.length >= 5 ? prettyUiStringUnquoted(msg[3]) : 'detaching') }); + } else if(msg[1] === CMD_APPNOTIFY) + { + switch(msg[2]) + { + case 'MemoryAllocations': + console.info1('MemoryAllocation: ' + msg[3]); + this.emit('memory-allocation', { total: parseInt(msg[3]) }); + break; + default: + console.info1('Unknown AppNotify: ' + msg[2]); + break; + } + } + else + { + // Ignore unknown notify messages + console.info1('Unknown notify, ignoring: ' + prettyDebugMessage(msg)); + } + } else { + console.error('Invalid initial dvalue, dropping connection: ' + prettyDebugMessage(msg)); + this.targetStream.destroy(); + } +}; + +Debugger.prototype.run = function () +{ + var _this = this; + optTargetPort = transport; + + this.WebServer = require('http').createServer(); + this.WebServer.Debugger = this; + this.WebServer.listen({ port: webport }); + updateWebPort(this.WebServer.address().port); + this.WebServer.on('request', function OnDebugWebsiteRequest(imsg, rsp) + { + console.info1('Received inbound request: ' + imsg.method + ' ' + imsg.url); + if (imsg.url == '/') + { + rsp.statusCode = 200; + rsp.statusText = 'OK'; + rsp.setHeader('Content-Type', 'text/html'); + + if (require('fs').existsSync('debugger.html')) + { + rsp.fs = require('fs').createReadStream('debugger.html'); + rsp.fs.pipe(rsp); + } + else + { + rsp.end(Buffer.from(dbgHTML, 'base64')); + } + } + else + { + rsp.statusCode = 404; + rsp.end(); + } + }); + this.WebServer.on('upgrade', function OnDebugWebSocket(req, soc, head) + { + console.info1('WebSocket request on: ', req.url); + this.Debugger.websocket = soc.upgradeWebSocket(); + this.Debugger.websocket.Debugger = this.Debugger; + this.Debugger.websocket.on('end', function () + { + this.Debugger.disconnectDebugger(); + console.info1('web socket closed'); + }); + this.Debugger.websocket.on('data', function OnDebugCmd(buf) + { + var j = null; + + try + { + j = JSON.parse(buf); + } + catch(e) + { + console.error('Parse Error: ' + e); + return; + } + + switch(j.cmd) + { + case 'ATTACH': + console.info1('Attaching Debugger'); + this.Debugger.connectDebugger(); + break; + case 'GC': + _debugGC(); // Debugger Heap + hostGC(); // Host Heap + console.info1('Force GC'); + break; + case 'STEPOVER': + this.Debugger.sendStepOverRequest(); + break; + case 'STEPINTO': + this.Debugger.sendStepIntoRequest(); + break; + case 'STEPOUT': + this.Debugger.sendStepOutRequest(); + break; + case 'CALLSTACK': + this.Debugger.sendGetCallStackRequest(); + break; + case 'HEAP': + var p = this.Debugger.sendDumpHeapRequest(); + p.Debugger = this.Debugger; + p.then(function onSendDumpHeap(obj) + { + console.info1('\n\n\n\n'); + var heapObjects = obj.heapObjects; + for(var i in heapObjects) + { + console.info1(heapObjects[i].ptr, heapObjects[i].type); + } + //console.info1('this', this); + //process.stdout.write('THEN\n'); + //process.stdout.write(JSON.stringify(this) + '\n'); + //process.stdout.write(JSON.stringify(obj) + '\n'); + }, function OnError(err) { console.info1('ERROR=>', err); }); + break; + case 'PAUSE': + this.Debugger.sendPauseRequest(); + break; + case 'RESUME': + this.Debugger.sendResumeRequest(); + break; + case 'DETACH': + console.info1('Detach Debugger'); + this.Debugger.disconnectDebugger(); + break; + case 'BREAKPOINT': + console.info1(j); + if (j.file == '') + { + if ((j.file = this.Debugger.execStatus.fileName) == '') + { + j.file = _scriptPath; + } + console.info1('using filePath: ' + j.file); + } + + console.info1('ChangeBreakpoint => ' + j.file + ':' + j.line + ' (' + j.mode + ')'); + this.Debugger.changeBreakpoint(j.file, j.line, j.mode); + break; + case 'QUERY': + console.info1(j); + var p = this.Debugger.sendEvalRequest('JSON.stringify(' + j.var + ')', j.level); + p.var = j.var; + p.then(function (msg) + { + _this.websocket.write({ cmd: 'QUERY', var: this.promise.var, val: msg.value }); + }); + break; + case 'EVAL': + console.info1(j); + var p = this.Debugger.sendEvalRequest(j.eval, j.level); + p.eval = j.eval; + p.then(function (msg) + { + _this.websocket.write({ cmd: 'EVAL', eval: this.promise.eval, val: msg.value }); + }); + break; + break; + case 'LOCALS': + console.info1(j); + this.Debugger.sendGetLocalsRequest(j.level).then(function (locals) + { + var vals = []; + for (var v in locals) + { + console.info1('Pushing: [' + locals[v].key + '] = ' + locals[v].type); + vals.push({ key: locals[v].key, type: locals[v].type }); + } + _this.websocket.write({ cmd: 'LOCALS', val: vals }); + }); + + + break + case 'SOURCE': + console.info1('SOURCE', j); + if (j.name != '') + { + if (j.name.toLowerCase().endsWith('.js') || j.name == '<>') + { + console.info1(JSON.stringify({ cmd: 'SOURCE', name: j.name, source: _scriptTokens.join('\n') })); + _this.websocket.write({ cmd: 'SOURCE', name: j.name, source: _scriptTokens.join('\n') }); + } + else + { + var p = _this.sendEvalRequest("getJSModule('" + j.name + "');"); + p.websocket = _this.websocket; + p.name = j.name; + + p.then(function (result) { + this.promise.websocket.write({ cmd: 'SOURCE', name: this.promise.name, source: result.value }); + }); + } + } + break; + } + + hostCooperate(); + }); + + this.Debugger.on('memory-allocation', function (mem) + { + var cmd = { cmd: 'MEMORY', total: mem.total }; + this.websocket.write(cmd); + }); + this.Debugger.on('detached', function () + { + detachCleanup(); + }); + + this.Debugger.on('debug-stats-update', function () + { + // _this.debugStatsLimiter.trigger(); + }); + + this.Debugger.on('ui-message-update', function () + { + // Explicit rate limiter because this is a source of a lot of traffic. + //_this.uiMessageLimiter.trigger(); + }); + + this.Debugger.on('basic-info-update', function () + { + // _this.emitBasicInfo(); + }); + + this.Debugger.on('breakpoints-update', function (bpts) + { + console.info1('breakpoints updated'); + var cmd = { cmd: 'BREAKPOINT', list: bpts }; + this.websocket.write(cmd); + //_this.emitBreakpoints(); + }); + + this.Debugger.on('exec-status-update', function () + { + if (!this.previousState || this.previousState != this.execStatus.state) + { + this.previousState = this.execStatus.state; + console.info1('new state', this.execStatus.state); + if (this.execStatus.state == 'attached') { console.info1('forcing cooperate'); hostCooperate(); } + if (this.execStatus.attached) + { + this.websocket.pausedNative = false; + switch(this.execStatus.state) + { + case 'paused': + console.info1('PAUSED', this.execStatus); + var src = _scriptPath == this.execStatus.fileName ? _scriptTokens[this.execStatus.line - 1] : null; + if (src == null) + { + if (this.execStatus.fileName != '') + { + var p = this.sendEvalRequest("getJSModule('" + this.execStatus.fileName + "');"); + p.websocket = this.websocket; + p.execStatus = this.execStatus; + + p.then(function (result) { + var tk = result.value.split('\n'); + this.promise.websocket.write({ cmd: 'PAUSE', native: false, file: this.promise.execStatus.fileName, line: this.promise.execStatus.line, source: tk[this.promise.execStatus.line - 1] }); + }); + } + else + { + this.websocket.pausedNative = true; + this.websocket.write({ cmd: 'PAUSE', native: true, file: this.execStatus.fileName, line: this.execStatus.line, source: '(Native)' }); + } + } + else + { + this.websocket.write({ cmd: 'PAUSE', native: false, file: this.execStatus.fileName, line: this.execStatus.line, source: src }); + } + break; + case 'running': + this.websocket.write({ cmd: 'RUNNING', file: _scriptPath }); + break; + } + } + else if (this.execStatus.state == 'detached') + { + this.websocket.write({ cmd: 'DETACH'}); + } + } + // Explicit rate limiter because this is a source of a lot of traffic. + //_this.execStatusLimiter.trigger(); + }); + this.Debugger.on('throw', function OnThrown(err) + { + var source = err.fileName == _scriptPath ? (_scriptTokens[err.lineNumber - 1]) : null; + if (source == null) { + var p = this.sendEvalRequest("getJSModule('" + err.fileName + "');"); + p.ws = this.websocket; + p.err = err; + + p.then(function (result) + { + var tk = result.value.split('\n'); + this.promise.ws.write({ cmd: 'THROW', line: this.promise.err.lineNumber, msg: this.promise.err.message, file: this.promise.err.fileName, source: tk[this.promise.err.lineNumber - 1] }); + }); + } + else + { + this.websocket.write({ cmd: 'THROW', line: err.lineNumber, msg: err.message, file: err.fileName, source: source }); + } + }); + + this.Debugger.on('locals-update', function () + { + //_this.emitLocals(); + }); + + this.Debugger.on('callstack-update', function () + { + this.websocket.write({ cmd: 'CALLSTACK', callstack: this.callstack }); + }); + + + + }); + + + + + + + //// Initial debugger connection + + //this.connectDebugger(); + + //// Poll various state items when running + + //var sendRound = 0; + //var statusPending = false; + //var bplistPending = false; + //var localsPending = false; + //var callStackPending = false; + + //setInterval(function () { + // if (_this.execStatus.state !== 'running') { + // return; + // } + + // // Could also check for an empty request queue, but that's probably + // // too strict? + + // // Pending flags are used to avoid requesting the same thing twice + // // while a previous request is pending. The flag-based approach is + // // quite awkward. Rework to use promises. + + // switch (sendRound) { + // case 0: + // if (!statusPending) { + // statusPending = true; + // _this.sendStatusRequest().finally(function () { statusPending = false; }); + // } + // break; + // case 1: + // if (!bplistPending) { + // bplistPending = true; + // _this.sendBreakpointListRequest().finally(function () { bplistPending = false; }); + // } + // break; + // case 2: + // if (!localsPending) { + // localsPending = true; + // _this.sendGetLocalsRequest().finally(function () { localsPending = false; }); + // } + // break; + // case 3: + // if (!callStackPending) { + // callStackPending = true; + // _this.sendGetCallStackRequest().finally(function () { callStackPending = false; }); + // } + // break; + // } + // sendRound = (sendRound + 1) % 4; + //}, 500); +}; + + + + + +/* + * Miscellaneous helpers + */ + +var nybbles = '0123456789abcdef'; + +/* Convert a buffer into a string using Unicode codepoints U+0000...U+00FF. + * This is the NodeJS 'binary' encoding, but since it's being deprecated, + * reimplement it here. We need to avoid parsing strings as e.g. UTF-8: + * although Duktape strings are usually UTF-8/CESU-8 that's not always the + * case, e.g. for internal strings. Buffer values are also represented as + * strings in the debug protocol, so we must deal accurately with arbitrary + * byte arrays. + */ +function bufferToDebugString(buf) { + var cp = []; + var i, n; + + /* + // This fails with "RangeError: Maximum call stack size exceeded" for some + // reason, so use a much slower variant. + + for (i = 0, n = buf.length; i < n; i++) { + cp[i] = buf[i]; + } + + return String.fromCharCode.apply(String, cp); + */ + + for (i = 0, n = buf.length; i < n; i++) { + cp[i] = String.fromCharCode(buf[i]); + } + + return cp.join(''); +} + +/* Write a string into a buffer interpreting codepoints U+0000...U+00FF + * as bytes. Drop higher bits. + */ +function writeDebugStringToBuffer(str, buf, off) { + var i, n; + + for (i = 0, n = str.length; i < n; i++) { + buf[off + i] = str.charCodeAt(i) & 0xff; // truncate higher bits + } +} + +/* Encode an ordinary Unicode string into a dvalue compatible format, i.e. + * into a byte array represented as codepoints U+0000...U+00FF. Concretely, + * encode with UTF-8 and then represent the bytes with U+0000...U+00FF. + */ +function stringToDebugString(str) { + return utf8.encode(str); +} + +/* Pretty print a dvalue. Useful for dumping etc. */ +function prettyDebugValue(x) { + if (typeof x === 'object' && x !== null) { + if (x.type === 'eom') { + return 'EOM'; + } else if (x.type === 'req') { + return 'REQ'; + } else if (x.type === 'rep') { + return 'REP'; + } else if (x.type === 'err') { + return 'ERR'; + } else if (x.type === 'nfy') { + return 'NFY'; + } + } + return JSON.stringify(x); +} + +/* Pretty print a number for UI usage. Types and values should be easy to + * read and typing should be obvious. For numbers, support Infinity, NaN, + * and signed zeroes properly. + */ +function prettyUiNumber(x) { + if (x === 1 / 0) { return 'Infinity'; } + if (x === -1 / 0) { return '-Infinity'; } + if (isNaN(x)) { return 'NaN'; } + if (x === 0 && 1 / x > 0) { return '0'; } + if (x === 0 && 1 / x < 0) { return '-0'; } + return x.toString(); +} + +/* Pretty print a dvalue string (bytes represented as U+0000...U+00FF) + * for UI usage. Try UTF-8 decoding to get a nice Unicode string (JSON + * encoded) but if that fails, ensure that bytes are encoded transparently. + * The result is a quoted string with a special quote marker for a "raw" + * string when UTF-8 decoding fails. Very long strings are optionally + * clipped. + */ +function prettyUiString(x, cliplen) { + var ret; + + if (typeof x !== 'string') { + throw new Error('invalid input to prettyUiString: ' + typeof x); + } + try { + // Here utf8.decode() is better than decoding using NodeJS buffer + // operations because we want strict UTF-8 interpretation. + ret = JSON.stringify(utf8.decode(x)); + } catch (e) { + // When we fall back to representing bytes, indicate that the string + // is "raw" with a 'r"' prefix (a somewhat arbitrary convention). + // U+0022 = ", U+0027 = ' + ret = 'r"' + x.replace(/[\u0022\u0027\u0000-\u001f\u0080-\uffff]/g, function (match) { + var cp = match.charCodeAt(0); + return '\\x' + nybbles[(cp >> 4) & 0x0f] + nybbles[cp & 0x0f]; + }) + '"'; + } + + if (cliplen && ret.length > cliplen) { + ret = ret.substring(0, cliplen) + '...'; // trailing '"' intentionally missing + } + return ret; +} + +/* Pretty print a dvalue string (bytes represented as U+0000...U+00FF) + * for UI usage without quotes. + */ +function prettyUiStringUnquoted(x, cliplen) { + var ret; + + if (typeof x !== 'string') { + throw new Error('invalid input to prettyUiStringUnquoted: ' + typeof x); + } + + try { + // Here utf8.decode() is better than decoding using NodeJS buffer + // operations because we want strict UTF-8 interpretation. + + // XXX: unprintable characters etc? In some UI cases we'd want to + // e.g. escape newlines and in others not. + ret = utf8.decode(x); + } catch (e) { + // For the unquoted version we don't need to escape single or double + // quotes. + ret = x.replace(/[\u0000-\u001f\u0080-\uffff]/g, function (match) { + var cp = match.charCodeAt(0); + return '\\x' + nybbles[(cp >> 4) & 0x0f] + nybbles[cp & 0x0f]; + }); + } + + if (cliplen && ret.length > cliplen) { + ret = ret.substring(0, cliplen) + '...'; + } + return ret; +} + +/* Pretty print a dvalue for UI usage. Everything comes out as a ready-to-use + * string. + * + * XXX: Currently the debug client formats all values for UI use. A better + * solution would be to pass values in typed form and let the UI format them, + * so that styling etc. could take typing into account. + */ +function prettyUiDebugValue(x, cliplen) { + if (typeof x === 'object' && x !== null) { + // Note: typeof null === 'object', so null special case explicitly + if (x.type === 'eom') { + return 'EOM'; + } else if (x.type === 'req') { + return 'REQ'; + } else if (x.type === 'rep') { + return 'REP'; + } else if (x.type === 'err') { + return 'ERR'; + } else if (x.type === 'nfy') { + return 'NFY'; + } else if (x.type === 'unused') { + return 'unused'; + } else if (x.type === 'undefined') { + return 'undefined'; + } else if (x.type === 'buffer') { + return '|' + x.data + '|'; + } else if (x.type === 'object') { + console.info1('OBJECT => ', JSON.stringify(x)); + // return '[object ' + (dukClassNames[x.class] || ('class ' + x.class)) + ' ' + x.pointer + ']'; + return '[object ' + ('class ' + x.class) + ' ' + x.pointer + ']'; + } else if (x.type === 'pointer') { + return ''; + } else if (x.type === 'lightfunc') { + return ''; + } else if (x.type === 'number') { + // duk_tval number, any IEEE double + var tmp = new Buffer(x.data, 'hex'); // decode into hex + var val = tmp.readDoubleBE(0); // big endian ieee double + return prettyUiNumber(val); + } + } else if (x === null) { + return 'null'; + } else if (typeof x === 'boolean') { + return x ? 'true' : 'false'; + } else if (typeof x === 'string') { + return prettyUiString(x, cliplen); + } else if (typeof x === 'number') { + // Debug protocol integer + return prettyUiNumber(x); + } + + // We shouldn't come here, but if we do, JSON is a reasonable default. + return JSON.stringify(x); +} + +/* Pretty print a debugger message given as an array of parsed dvalues. + * Result should be a pure ASCII one-liner. + */ +function prettyDebugMessage(msg) { + return msg.map(prettyDebugValue).join(' '); +} + +/* Pretty print a debugger command. */ +function prettyDebugCommand(cmd) +{ + return debugCommandNames[cmd] || String(cmd); +} + +/* Decode and normalize source file contents: UTF-8, tabs to 8, + * CR LF to LF. + */ +function decodeAndNormalizeSource(data) { + var tmp; + var lines, line, repl; + var i, n; + var j, m; + + try { + tmp = data.toString('utf8'); + } catch (e) { + console.error('Failed to UTF-8 decode source file, ignoring: ' + e); + tmp = String(data); + } + + lines = tmp.split(/\r?\n/); + for (i = 0, n = lines.length; i < n; i++) { + line = lines[i]; + if (/\t/.test(line)) { + repl = ''; + for (j = 0, m = line.length; j < m; j++) { + if (line.charAt(j) === '\t') { + repl += ' '; + while ((repl.length % 8) != 0) { + repl += ' '; + } + } else { + repl += line.charAt(j); + } + } + lines[i] = repl; + } + } + + // XXX: normalize last newline (i.e. force a newline if contents don't + // end with a newline)? + + return lines.join('\n'); +} + + + +module.exports = Debugger; \ No newline at end of file diff --git a/agents/modules_meshcore/monitor-info.js b/agents/modules_meshcore/monitor-info.js index 34799496..bd8ed42f 100644 --- a/agents/modules_meshcore/monitor-info.js +++ b/agents/modules_meshcore/monitor-info.js @@ -55,56 +55,74 @@ function monitorinfo() else if(process.platform == 'linux') { // First thing we need to do, is determine where the X11 libraries are - var fs = require('fs'); - var files = fs.readdirSync('/usr/lib'); - var files2; - - /* - for (j in files) + var askOS = false; + try { - if (files[j].split('libX11.so.').length > 1 && files[j].split('.').length == 3) + if (require('user-sessions').isRoot()) { askOS = true; } + } + catch (e) + { } + + if (askOS) + { + // Sufficient access rights to use ldconfig + var p = require('child_process').execFile('/bin/sh', ['sh']); + p.stdout._lines = ''; + p.stdout.on('data', function (chunk) { this._lines += chunk.toString(); }); + p.stdin.write('ldconfig -v\nexit\n'); + p.waitExit(); + + var paths = p.stdout._lines.split('\n'); + var searchPath = ''; + for (var i in paths) { - Object.defineProperty(this, 'Location_X11LIB', { value: '/usr/lib/' + files[j] }); - } - if (files[j].split('libXtst.so.').length > 1 && files[j].split('.').length == 3) - { - Object.defineProperty(this, 'Location_X11TST', { value: '/usr/lib/' + files[j] }); - } - if (files[j].split('libXext.so.').length > 1 && files[j].split('.').length == 3) - { - Object.defineProperty(this, 'Location_X11EXT', { value: '/usr/lib/' + files[j] }); + if (paths[i].endsWith(':')) + { + searchPath = paths[i].substring(0, paths[i].length - 1); + } + else + { + if (paths[i].split('libX11.').length > 1) { Object.defineProperty(this, 'Location_X11LIB', { value: searchPath + '/' + paths[i].split('->')[1].trim() }); } + if (paths[i].split('libXtst.').length > 1) { Object.defineProperty(this, 'Location_X11TST', { value: searchPath + '/' + paths[i].split('->')[1].trim() }); } + if (paths[i].split('libXext.').length > 1) { Object.defineProperty(this, 'Location_X11EXT', { value: searchPath + '/' + paths[i].split('->')[1].trim() }); } + } } } - */ - - for (var i in files) + else { - try { - if (files[i].split('libX11.so.').length > 1 && files[i].split('.').length == 3) { - Object.defineProperty(this, 'Location_X11LIB', { value: '/usr/lib/' + files[i] }); - } - if (files[i].split('libXtst.so.').length > 1 && files[i].split('.').length == 3) { - Object.defineProperty(this, 'Location_X11TST', { value: '/usr/lib/' + files[i] }); - } - if (files[i].split('libXext.so.').length > 1 && files[i].split('.').length == 3) { - Object.defineProperty(this, 'Location_X11EXT', { value: '/usr/lib/' + files[i] }); - } + // Not enough access rights to use ldconfig, so manually search + var fs = require('fs'); + var files = fs.readdirSync('/usr/lib'); + var files2; - if (files[i].split('-linux-').length > 1) { - files2 = fs.readdirSync('/usr/lib/' + files[i]); - for (j in files2) { - if (files2[j].split('libX11.so.').length > 1 && files2[j].split('.').length == 3) { - Object.defineProperty(this, 'Location_X11LIB', { value: '/usr/lib/' + files[i] + '/' + files2[j] }); - } - if (files2[j].split('libXtst.so.').length > 1 && files2[j].split('.').length == 3) { - Object.defineProperty(this, 'Location_X11TST', { value: '/usr/lib/' + files[i] + '/' + files2[j] }); - } - if (files2[j].split('libXext.so.').length > 1 && files2[j].split('.').length == 3) { - Object.defineProperty(this, 'Location_X11EXT', { value: '/usr/lib/' + files[i] + '/' + files2[j] }); + for (var i in files) { + try { + if (files[i].split('libX11.so.').length > 1 && files[i].split('.').length == 3) { + Object.defineProperty(this, 'Location_X11LIB', { value: '/usr/lib/' + files[i] }); + } + if (files[i].split('libXtst.so.').length > 1 && files[i].split('.').length == 3) { + Object.defineProperty(this, 'Location_X11TST', { value: '/usr/lib/' + files[i] }); + } + if (files[i].split('libXext.so.').length > 1 && files[i].split('.').length == 3) { + Object.defineProperty(this, 'Location_X11EXT', { value: '/usr/lib/' + files[i] }); + } + + if (files[i].split('-linux-').length > 1) { + files2 = fs.readdirSync('/usr/lib/' + files[i]); + for (j in files2) { + if (files2[j].split('libX11.so.').length > 1 && files2[j].split('.').length == 3) { + Object.defineProperty(this, 'Location_X11LIB', { value: '/usr/lib/' + files[i] + '/' + files2[j] }); + } + if (files2[j].split('libXtst.so.').length > 1 && files2[j].split('.').length == 3) { + Object.defineProperty(this, 'Location_X11TST', { value: '/usr/lib/' + files[i] + '/' + files2[j] }); + } + if (files2[j].split('libXext.so.').length > 1 && files2[j].split('.').length == 3) { + Object.defineProperty(this, 'Location_X11EXT', { value: '/usr/lib/' + files[i] + '/' + files2[j] }); + } } } - } - } catch (ex) { } + } catch (ex) { } + } } Object.defineProperty(this, 'kvm_x11_support', { value: (this.Location_X11LIB && this.Location_X11TST && this.Location_X11EXT)?true:false }); diff --git a/agents/modules_meshcore/user-sessions.js b/agents/modules_meshcore/user-sessions.js index bbe1d613..f882fb7c 100644 --- a/agents/modules_meshcore/user-sessions.js +++ b/agents/modules_meshcore/user-sessions.js @@ -215,7 +215,7 @@ function UserSessions() self.parent._user32.ACDC_H = self.parent._user32.RegisterPowerSettingNotification(self.parent.hwnd, GUID_ACDC_POWER_SOURCE, 0); self.parent._user32.BATT_H = self.parent._user32.RegisterPowerSettingNotification(self.parent.hwnd, GUID_BATTERY_PERCENTAGE_REMAINING, 0); self.parent._user32.DISP_H = self.parent._user32.RegisterPowerSettingNotification(self.parent.hwnd, GUID_CONSOLE_DISPLAY_STATE, 0); - console.log(self.parent._user32.ACDC_H.Val, self.parent._user32.BATT_H.Val, self.parent._user32.DISP_H.Val); + //console.log(self.parent._user32.ACDC_H.Val, self.parent._user32.BATT_H.Val, self.parent._user32.DISP_H.Val); }, this); }); this._messagepump.on('message', function (msg) diff --git a/package.json b/package.json index a7c61e26..6d3e6b6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.3-f", + "version": "0.2.3-g", "keywords": [ "Remote Management", "Intel AMT",