From 86565d39461f890b93b862a779d7fc2fb7f53b4c Mon Sep 17 00:00:00 2001 From: sbosse Date: Tue, 27 Aug 2024 00:12:44 +0200 Subject: [PATCH] Tue 27 Aug 00:12:21 CEST 2024 --- build/jamsh/jamsh.debug | 133782 +++++++++++++++++++++++++++++++++++++ 1 file changed, 133782 insertions(+) create mode 100644 build/jamsh/jamsh.debug diff --git a/build/jamsh/jamsh.debug b/build/jamsh/jamsh.debug new file mode 100644 index 0000000..1e34830 --- /dev/null +++ b/build/jamsh/jamsh.debug @@ -0,0 +1,133782 @@ +#!/usr/bin/env jx +var CoreModule = {}; +CoreModule['crypto']='crypto'; +CoreModule['util']='util'; +CoreModule['http']='http'; +CoreModule['fs']='fs'; +CoreModule['stream']='stream'; +CoreModule['url']='url'; +CoreModule['os']='os'; +CoreModule['net']='net'; +CoreModule['zlib']='zlib'; +CoreModule['path']='path'; +CoreModule['dgram']='dgram'; +CoreModule['child_process']='child_process'; +CoreModule['events']='events'; +CoreModule['string_decoder']='string_decoder'; +CoreModule['assert']='assert'; +CoreModule['buffer']='buffer'; + +var BundleModuleCode=[]; +var BundleObjectCode=[]; +var BundleModules = []; +function _isdir(path) { + var stats=Fs.statSync(path); + return stats && stats.isDirectory()}; +function _search(index,file) { + if (PATH.length==index) return file; + var path=PATH[index]; + if (Fs.existsSync(path+"/"+file+".js")) return path+"/"+file+".js"; + else if (Fs.existsSync(path+"/"+file) && !_isdir(path+"/"+fil)) return path+"/"+file; + else return _search(index+1,file); + } +var Fs = require("fs"); +if (typeof __dirname == 'undefined') __dirname = ''; +if (typeof __filename == 'undefined') __filename = '/home/sbosse/proj/jam/js/top/jamsh.js'; +if (typeof global == 'undefined') global={}; +PATH=[process.cwd(),".","/home/sbosse/proj/jam/js"]; +function search(index,file) { + if (file.indexOf("/")==-1) return file; + if (PATH.length==index) return file; + var path=PATH[index]; + if (Fs.existsSync(path+"/"+file+".js")) return path+"/"+file+".js"; + else if (Fs.existsSync(path+"/"+file)) return path+"/"+file; + else return search(index+1,file); + } +function Require(modupath) { + var file,filepath; + if (BundleModules[modupath]) return BundleModules[modupath]; + var exports={}; var module={exports:exports}; + if (CoreModule[modupath]!=undefined) modupath=CoreModule[modupath]; + if (modupath=='') return undefined; + if (BundleModuleCode[modupath]) BundleModuleCode[modupath](module,exports); + else if (BundleObjectCode[modupath]) BundleObjectCode[modupath](module,exports); + else { try { file=search(0,modupath); module = require(file)} + catch (e) { var more=""; + if ((e.name==="SyntaxError"||e.name==="TypeError") && file) { + var src=Fs.readFileSync(file,"utf8"); + var Esprima = Require("parser/esprima"); + try { + var ast = Esprima.parse(src, { tolerant: true, loc:true }); + if (ast.errors && ast.errors.length>0) more = ", "+ast.errors[0]; + } catch (e) { + if (e.lineNumber) more = ", in line "+e.lineNumber; + } + } + console.log("Require import of "+file+" failed: "+e+more); + // if (e.stack) console.log(e.stack); + throw e; // process.exit(-1); + }} + BundleModules[modupath]=module.exports||module; + return module.exports||module;}; +FilesEmbedded=global.FilesEmbedded = {}; +FileEmbedd=global.FileEmbedd = function (path,format) {}; +FileEmbedded=global.FileEmbedded = function (path,format) {return FilesEmbedded[path](format);}; +global.TARGET='node'; + +BundleModuleCode['com/io']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2020 bLAB + ** $CREATED: sbosse on 28-3-15. + ** $VERSION: 1.12.1 + ** + ** $INFO: + * + * This module encapsulates all IO operations (except networking) supporting + * node.js applications. + * + ** $ENDOFINFO + */ +/* + ************ + ** Node.js/jxcore + ************ + */ +var util = Require('util'); +var os = Require('os'); +var child = Require('child_process'); +var GetEnv = Require('os/getenv'); +var Base64 = Require('os/base64'); +var Fs = Require('fs'); + +Require('os/polyfill') + +var stderr_fun = function (str) { process.stderr.write(str); }; +var stdout_fun = function (str) { process.stdout.write(str); }; + +/* + ** node.js specific + */ + +var tracefile = undefined; +var tracing = false; +var timestamp = false; + +/** +* Open a module and append all exported properties to the context (e.g., global or this). +* (top-level scope) +*/ +global.open = function(name,context,as) { + var module = Require(name); + if (!context) context=global; + for (var p in module) { + context[p] = module[p]; + }; + if (as) context[as]=module; +} +global.print = console.log; +var NL = '\n'; + +global.checkOptions = function(options,defaultOptions) { + return Object.assign({}, defaultOptions||{}, options) }; +global.checkOption = function (option,defaultOption) { + return option==undefined? defaultOption:option }; + + +var io = { + options: { + columns: undefined, + rows: undefined, + log: console.log.bind(console), + err: console.err, + warn: console.warn, + }, + /** + * + * @param fd + */ + close: function (fd) { + Fs.closeSync(fd); + }, + /** + ** Return current date in year-month-day format + */ + Date: function () + { + var now = new Date(); + var year = "" + now.getFullYear(); + var month = "0" + (now.getMonth()+1); + month = month.substring(month.length-2); + var date = "0" + now.getDate(); + date = date.substring(date.length-2); + return year + "-" + month + "-" + date; + }, + /** + * + * @param msg + */ + debug: function (msg) { + io.options.err('Debug: ' + msg); + }, + /** + * + * @param path + */ + exists: function (path) { + return Fs.existsSync(path); + }, + error: undefined, + /** + * + * @param msg + */ + err: function (msg) { + io.options.err('Error: ' + msg); + throw Error(msg); + }, + exit: function (n) { + process.exit(n); + }, + /** + * + * @param msg + */ + fail: function (msg) { + io.options.err('Fatal Error: ' + msg); + process.exit(0); + }, + /** + * + * @param path + */ + file_exists: function (path) { + return Fs.existsSync(path); + }, + /** Search a file by iterating global PATH variable. + * + * @param name File name or partial (relative) path + */ + file_search: function (name) { + // Expecting global PATH variable !? + if (io.file_exists(name)) return name; + else if (typeof PATH !== 'undefined') { + for (var p in PATH) { + if (io.file_exists(PATH[p]+'/'+name)) return (PATH[p]+'/'+name); + } + return undefined; + } else return undefined; + }, + /** + * + * @param path + * @returns {number} + */ + file_size: function (path) { + var stat = Fs.statSync(path); + if (stat != undefined) + return stat.size; + else + return -1; + }, + /** + * + * @param path + * @param timekind a c m + * @returns {number} + */ + file_time: function (path,timekind) { + var stat = Fs.statSync(path); + if (stat != undefined) + switch (timekind) { + case 'a': return stat.atime.getTime()/1000; + case 'c': return stat.ctime.getTime()/1000; + case 'm': return stat.mtime.getTime()/1000; + default: return stat.mtime.getTime()/1000; + } + else + return -1; + }, + + /** + * @return {string []} + */ + getargs: function () { + return process.argv; + }, + getenv: function (name, def) { + return GetEnv(name, def); + }, + + + + /** + * + * @param obj + */ + inspect: util.inspect, + + /** + * + * @param {boolean|string} condmsg conditional message var log=X; log((log lt. N)||(msg)) + */ + log: function (condmsg) { + if (condmsg==true) return; + if (!timestamp) console.warn(condmsg); + else { + var date = new Date(); + var time = Math.floor(date.getTime()); + console.warn('['+process.pid+':'+time+']'+condmsg); + } + }, + /** + * + * @returns {*} RSS HEAP in kBytes {data,heap} + */ + mem: function () { + var mem = process.memoryUsage(); + return {data:(mem.rss/1024)|0,heap:(mem.heapUsed/1024)|0}; + }, + /** + * + * @param path + * @param mode + * @returns {*} + */ + open: function (path, mode) { + return Fs.openSync(path, mode); + }, + + /** + * + * @param msg + */ + out: function (msg) { + io.options.log(msg) + }, + /** + * + * @param e + * @param where + */ + printstack: function (e,where) { + if (!e.stack) e=new Error(e); + if (!e.stack) e.stack ='empty stack'; + var stack = e.stack //.replace(/^[^\(]+?[\n$]/gm, '') + .replace(/^\s+at\s+/gm, '') + .replace(/^Object.\s*\(/gm, '{anonymous}()@') + .split('\n'); + if (where==undefined) io.out(e); + else io.out(where+': '+e); + io.out('Stack Trace'); + io.out('--------------------------------'); + for(var i in stack) { + if (i>0) { + var line = stack[i]; + if(line.indexOf('Module.',0)>=0) break; + io.out(' at '+line); + } + } + io.out('--------------------------------'); + }, + /** + * + * @param fd + * @param len + * @param foff + */ + read: function (fd, len, foff) { + // TODO + }, + /** + * + * @param path + * @returns {string|undefined} + */ + read_file: function (path) { + try { + return Fs.readFileSync(path,'utf8'); + } catch (e) { + io.error=e; + return undefined; + } + }, + /** + * + * @param path + * @returns {*} + */ + read_file_bin: function (path) { + try { + return Fs.readFileSync(path); + } catch (e) { + io.error=e; + return undefined; + } + }, + /** + * + * @param fd + */ + read_line: function (fd) { + // TODO + }, + /** + * + * @param fd + * @param buf + * @param boff + * @param len + * @param [foff] + * @returns {number} + */ + read_buf: function (fd, buf, boff, len, foff) { + return Fs.readSync(fd, buf, boff, len, foff); + }, + /** + * + * @param e + * @param where + */ + sprintstack: function (e) { + var str=''; + if (e==_ || !e.stack) e=new Error(e); + if (!e.stack) e.stack ='empty stack'; + var stack = e.stack //.replace(/^[^\(]+?[\n$]/gm, '') + .replace(/^\s+at\s+/gm, '') + .replace(/^Object.\s*\(/gm, '{anonymous}()@') + .replace(/^Object.eval\s*\(/gm, '') + .split('\n'); + for(var i in stack) { + if (i>0) { + var line = stack[i]; + if(line.indexOf('Module.',0)>=0) break; + if (str!='') str += '\n'; + str += ' at '+line; + } + } + return str; + }, + /** + * + */ + stacktrace: function () { + var e = new Error('dummy'); + if (!e.stack) e.stack ='empty stack'; + var stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '') + .replace(/^\s+at\s+/gm, '') + .replace(/^Object.\s*\(/gm, '{anonymous}()@') + .split('\n'); + io.out('Stack Trace'); + io.out('--------------------------------'); + for(var i in stack) { + if (i>0) { + var line = stack[i]; + if(line.indexOf('Module.',0)>=0) break; + io.out(' at '+line); + } + } + io.out('--------------------------------'); + }, + /** + * + * @param fun + */ + set_stderr: function(fun) { + stderr_fun=fun; + }, + /** + * + * @param fun + */ + set_stdout: function(fun) { + stdout_fun=fun; + }, + /** + * + * @param msg + */ + stderr: function (msg) { + stderr_fun(msg); + }, + // sleep(ms) + sleep: function(delay) { + var start = new Date().getTime(); + while (new Date().getTime() < start + delay); + }, + + /** + * + * @param msg + */ + stdout: function (msg) { + stdout_fun(msg); + }, + /** + * + * @param fd + */ + sync: function (fd) { + Fs.fsyncSync(fd); + }, + date: function () { + var date = Date(); + return date.split(' ').slice(1,5).join(' '); + }, + /** Return system time in milliseconds + */ + time: function () { + var date = new Date(); + return Math.floor(date.getTime()); + }, + /** + ** Return current time in hour:minute:second:milli format + */ + Time: function () + { + var now = new Date(); + var hour = "0" + now.getHours(); + hour = hour.substring(hour.length-2); + var minute = "0" + now.getMinutes(); + minute = minute.substring(minute.length-2); + var second = "0" + now.getSeconds(); + second = second.substring(second.length-2); + var milli = "0" + Math.floor(now.getMilliseconds()/10); + milli = milli.substring(milli.length-2); + return hour + ":" + minute + ":" + second+':'+milli; + }, + /** Write a message with a time stamp written to the trace file. + * + * @param {boolean|string} condmsg conditional message var trace=Io.tracing; trace(trace||(msg)) + */ + trace: function (condmsg) { + if (condmsg != true && tracefile != undefined) { + var date = new Date(); + var time = Math.floor(date.getTime()); + Fs.writeSync(tracefile, '[' + time + '] ' + condmsg + '\n'); + } + }, + tracing: tracing, + /** + * + * @param {string} path + */ + trace_open: function (path) { + tracefile = Fs.openSync(path, 'w+'); + if (tracefile != undefined) io.tracing = false; + }, + /** + * + * @param msg + */ + warn: function (msg) { + if (!timestamp) io.options.warn('Warning: ' + msg); + else { + var date = new Date(); + var time = Math.floor(date.getTime()); + console.warn('['+process.pid+':'+time+'] Warning: '+msg); + } + }, + workdir: function () { + return io.getenv('PWD',io.getenv('CWD','')); + }, + /** + * + * @param fd + * @param data + * @param [foff] + * @returns {number} + */ + write: function (fd, data, foff) { + return Fs.writeSync(fd, data, foff); + }, + /** + * + * @param fd + * @param buf + * @param bpos + * @param blen + * @param [foff] + * @returns {number} + */ + write_buf: function (fd, buf, bpos, blen, foff) { + return Fs.writeSync(fd, buf, bpos, blen, foff); + }, + /** + * + * @param path + * @param {string} buf + */ + write_file: function (path,str) { + try { + Fs.writeFileSync(path, str, 'utf8'); + return str.length; + } catch (e) { + return -1; + } + }, + /** + * + * @param path + * @param buf + * @returns {*} + */ + write_file_bin: function (path,buf) { + try { + Fs.writeFileSync(path, buf, 'binary'); + return buf.length; + } catch (e) { + io.error=e; + return -1; + } + }, + /** + * + * @param fd + * @param {string} str + * @returns {number} + */ + write_line: function (fd, str) { + return Fs.writeSync(fd, str+NL); + }, + + + + + + /** + * Process management + */ + fork: child.fork, + exec: child.exec, + execSync: child.execSync, + spawn: child.spawn, + + /** + * OS + */ + hostname: os.hostname +}; + +module.exports = io; +}; +BundleModuleCode['os/getenv']=function (module,exports){ +var util = require("util"); +//var url = require("url"); + +var fallbacksDisabled = false; + +function _value(varName, fallback) { + var value = process.env[varName]; + if (value === undefined) { + if (fallback === undefined) { + throw new Error('GetEnv.Nonexistent: ' + varName + ' does not exist ' + + 'and no fallback value provided.'); + } + if (fallbacksDisabled) { + throw new Error('GetEnv.DisabledFallbacks: ' + varName + ' relying on fallback ' + + 'when fallbacks have been disabled'); + } + return '' + fallback; + } + return value; +} + +var convert = { + string: function(value) { + return '' + value; + }, + int: function(value) { + var isInt = value.match(/^-?\d+$/); + if (!isInt) { + throw new Error('GetEnv.NoInteger: ' + value + ' is not an integer.'); + } + + return +value; + }, + float: function(value) { + var isInfinity = (+value === Infinity || +value === -Infinity); + if (isInfinity) { + throw new Error('GetEnv.Infinity: ' + value + ' is set to +/-Infinity.'); + } + + var isFloat = !(isNaN(value) || value === ''); + if (!isFloat) { + throw new Error('GetEnv.NoFloat: ' + value + ' is not a number.'); + } + + return +value; + }, + bool: function(value) { + var isBool = (value === 'true' || value === 'false'); + if (!isBool) { + throw new Error('GetEnv.NoBoolean: ' + value + ' is not a boolean.'); + } + + return (value === 'true'); + } + // , url: url.parse +}; + +function converter(type) { + return function(varName, fallback) { + if(typeof varName == 'string') { // default + var value = _value(varName, fallback); + return convert[type](value); + } else { // multibert! + return getenv.multi(varName); + } + }; +}; + +var getenv = converter('string'); + +Object.keys(convert).forEach(function(type) { + getenv[type] = converter(type); +}); + +getenv.array = function array(varName, type, fallback) { + type = type || 'string'; + if (Object.keys(convert).indexOf(type) === -1) { + throw new Error('GetEnv.ArrayUndefinedType: Unknown array type ' + type); + } + var value = _value(varName, fallback); + return value.split(/\s*,\s*/).map(convert[type]); +}; + +getenv.multi = function multi(spec) { + var key, value; + var result = {}; + for(var key in spec) { + var value = spec[key]; + if(util.isArray(value)) { // default value & typecast + switch(value.length) { + case 1: // no default value + case 2: // no type casting + result[key] = getenv(value[0], value[1]); // dirty, when case 1: value[1] is undefined + break; + case 3: // with typecast + result[key] = getenv[value[2]](value[0], value[1]); + break; + default: // wtf? + throw('getenv.multi(): invalid spec'); + break; + } + } else { // value or throw + result[key] = getenv(value); + } + } + return result; +}; + +getenv.disableFallbacks = function() { + fallbacksDisabled = true; +}; + +getenv.enableFallbacks = function() { + fallbacksDisabled = false; +}; + +module.exports = getenv; +}; +BundleModuleCode['os/base64']=function (module,exports){ +var keyStr = "ABCDEFGHIJKLMNOP" + + "QRSTUVWXYZabcdef" + + "ghijklmnopqrstuv" + + "wxyz0123456789+/" + + "="; + +var Base64 = { + encode: function (input) { + input = escape(input); + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + + keyStr.charAt(enc4); + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + } while (i < input.length); + + return output; + }, + + encodeBuf: function (input) { + var output = ""; + var NaN = output.charCodeAt(2); + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + var len = input.length; + do { + chr1 = input.readUInt8(i++); + chr2 = (i> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + + keyStr.charAt(enc4); + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + } while (i < len); + + return output; + }, + + decode: function (input) { + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + + } while (i < input.length); + + return unescape(output); + }, + decodeBuf: function (input) { + var len = input.length; + var buf = new Buffer(len); + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + var buflen = 0; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + buf.fill(0); + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + buf.writeUInt8(chr1,buflen); + buflen++; + if (enc3 != 64) { + buf.writeUInt8(chr2,buflen); + buflen++; + } + if (enc4 != 64) { + buf.writeUInt8(chr3,buflen); + buflen++; + } + + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + + } while (i < input.length); + + return buf.slice(0,buflen); + } + +}; + + +module.exports = Base64; +}; +BundleModuleCode['os/polyfill']=function (module,exports){ + +/********** OBJECT **************/ + +Object.addProperty = function (obj,name,fun) { + if (obj.prototype[name]) return; + obj.prototype[name]=fun; + Object.defineProperty(obj.prototype, name, {enumerable: false}); +}; + +Object.updateProperty = function (obj,name,fun) { + obj.prototype[name]=fun; + Object.defineProperty(obj.prototype, name, {enumerable: false}); +}; + +if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, "assign", { + value: function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + writable: true, + configurable: true + }); +} + +/************** ARRAY ********************/ + +if (!Array.prototype.find) { + Object.addProperty(Array, 'find', function(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, ( kValue, k, O ))). + // d. If testResult is true, return kValue. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + // e. Increase k by 1. + k++; + } + + // 7. Return undefined. + return undefined; + }); +} + +// String prototype extensions +if (!String.prototype.contains){ + Object.addProperty(String,'contains', function (el) { + return this.indexOf(el)!=-1 + }) +} + +// Check Options Extension +checkOptions = function(options,defaultOptions) { + return Object.assign({}, defaultOptions||{}, options) }; +checkOption = function (option,defaultOption) { + return option==undefined? defaultOption:option }; +}; +BundleModuleCode['com/path']=function (module,exports){ +var Fs = Require('fs'); + +var _process = process || {}; +(function () { + "use strict"; + +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + + +var isWindows = _process.platform === 'win32'; +var util = Require('util'); +if (!util.deprecate) util.deprecate=function(f,w) {return f;}; + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + + +if (isWindows) { + // Regex to split a windows path into three parts: [*, device, slash, + // tail] windows-only + var splitDeviceRe = + /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + + // Regex to split the tail part of the above into [*, dir, basename, ext] + var splitTailRe = + /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; + + // Function to split a filename into [root, dir, basename, ext] + // windows version + var splitPath = function(filename) { + // Separate device+slash from tail + var result = splitDeviceRe.exec(filename), + device = (result[1] || '') + (result[2] || ''), + tail = result[3] || ''; + // Split the tail into dir, basename and extension + var result2 = splitTailRe.exec(tail), + dir = result2[1], + basename = result2[2], + ext = result2[3]; + return [device, dir, basename, ext]; + }; + + var normalizeUNCRoot = function(device) { + return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); + }; + + // path.resolve([from ...], to) + // windows version + exports.resolve = function() { + var resolvedDevice = '', + resolvedTail = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1; i--) { + var path; + if (i >= 0) { + path = arguments[i]; + } else if (!resolvedDevice) { + path = _process.cwd(); + } else { + // Windows has the concept of drive-specific current working + // directories. If we've resolved a drive letter but not yet an + // absolute path, get cwd for that drive. We're sure the device is not + // an unc path at this points, because unc paths are always absolute. + path = _process.env['=' + resolvedDevice]; + // Verify that a drive-local cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if (!path || path.substr(0, 3).toLowerCase() !== + resolvedDevice.toLowerCase() + '\\') { + path = resolvedDevice + '\\'; + } + } + + // Skip empty and invalid entries + if (!util.isString(path)) { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + var result = splitDeviceRe.exec(path), + device = result[1] || '', + isUnc = device && device.charAt(1) !== ':', + isAbsolute = exports.isAbsolute(path), + tail = result[3]; + + if (device && + resolvedDevice && + device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + + if (!resolvedDevice) { + resolvedDevice = device; + } + if (!resolvedAbsolute) { + resolvedTail = tail + '\\' + resolvedTail; + resolvedAbsolute = isAbsolute; + } + + if (resolvedDevice && resolvedAbsolute) { + break; + } + } + + // Convert slashes to backslashes when `resolvedDevice` points to an UNC + // root. Also squash multiple slashes into a single one where appropriate. + if (isUnc) { + resolvedDevice = normalizeUNCRoot(resolvedDevice); + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + + function f(p) { + return !!p; + } + + resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f), + !resolvedAbsolute).join('\\'); + + return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || + '.'; + }; + + // windows version + exports.normalize = function(path) { + var result = splitDeviceRe.exec(path), + device = result[1] || '', + isUnc = device && device.charAt(1) !== ':', + isAbsolute = exports.isAbsolute(path), + tail = result[3], + trailingSlash = /[\\\/]$/.test(tail); + + // If device is a drive letter, we'll normalize to lower case. + if (device && device.charAt(1) === ':') { + device = device[0].toLowerCase() + device.substr(1); + } + + // Normalize the tail path + tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) { + return !!p; + }), !isAbsolute).join('\\'); + + if (!tail && !isAbsolute) { + tail = '.'; + } + if (tail && trailingSlash) { + tail += '\\'; + } + + // Convert slashes to backslashes when `device` points to an UNC root. + // Also squash multiple slashes into a single one where appropriate. + if (isUnc) { + device = normalizeUNCRoot(device); + } + + return device + (isAbsolute ? '\\' : '') + tail; + }; + + // windows version + exports.isAbsolute = function(path) { + var result = splitDeviceRe.exec(path), + device = result[1] || '', + isUnc = !!device && device.charAt(1) !== ':'; + // UNC paths are always absolute + return !!result[2] || isUnc; + }; + + // windows version + exports.join = function() { + function f(p) { + if (!util.isString(p)) { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + } + + var paths = Array.prototype.filter.call(arguments, f); + var joined = paths.join('\\'); + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\') + if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) { + joined = joined.replace(/^[\\\/]{2,}/, '\\'); + } + + return exports.normalize(joined); + }; + + // path.relative(from, to) + // it will solve the relative path from 'from' to 'to', for instance: + // from = 'C:\\orandea\\test\\aaa' + // to = 'C:\\orandea\\impl\\bbb' + // The output of the function should be: '..\\..\\impl\\bbb' + // windows version + exports.relative = function(from, to) { + from = exports.resolve(from); + to = exports.resolve(to); + + // windows is not case sensitive + var lowerFrom = from.toLowerCase(); + var lowerTo = to.toLowerCase(); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end + 1); + } + + var toParts = trim(to.split('\\')); + + var lowerFromParts = trim(lowerFrom.split('\\')); + var lowerToParts = trim(lowerTo.split('\\')); + + var length = Math.min(lowerFromParts.length, lowerToParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (lowerFromParts[i] !== lowerToParts[i]) { + samePartsLength = i; + break; + } + } + + if (samePartsLength == 0) { + return to; + } + + var outputParts = []; + for (var i = samePartsLength; i < lowerFromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('\\'); + }; + + exports.sep = '\\'; + exports.delimiter = ';'; + +} else /* posix */ { + + // Split a filename into [root, dir, basename, ext], unix version + // 'root' is just a slash, or nothing. + var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); + }; + + // path.resolve([from ...], to) + // posix version + exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : _process.cwd(); + + // Skip empty and invalid entries + if (!util.isString(path)) { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; + }; + + // path.normalize(path) + // posix version + exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = path[path.length - 1] === '/', + segments = path.split('/'), + nonEmptySegments = []; + + // Normalize the path + for (var i = 0; i < segments.length; i++) { + if (segments[i]) { + nonEmptySegments.push(segments[i]); + } + } + path = normalizeArray(nonEmptySegments, !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; + }; + + // posix version + exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; + }; + + // posix version + exports.join = function() { + var path = ''; + for (var i = 0; i < arguments.length; i++) { + var segment = arguments[i]; + if (!util.isString(segment)) { + throw new TypeError('Arguments to path.join must be strings'); + } + if (segment) { + if (!path) { + path += segment; + } else { + path += '/' + segment; + } + } + } + return exports.normalize(path); + }; + + + // path.relative(from, to) + // posix version + exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); + }; + + exports.sep = '/'; + exports.delimiter = ':'; +} + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + + +exports.exists = util.deprecate(function(path, callback) { + if (Fs) Fs.exists(path, callback); + else callback(false); +}, 'path.exists is now called `fs.exists`.'); + + +exports.existsSync = util.deprecate(function(path) { + if (Fs) return Fs.existsSync(path); + else return false; +}, 'path.existsSync is now called `fs.existsSync`.'); + + +if (isWindows) { + exports._makeLong = function(path) { + // Note: this will *probably* throw somewhere. + if (!util.isString(path)) + return path; + + if (!path) { + return ''; + } + + var resolvedPath = exports.resolve(path); + + if (/^[a-zA-Z]\:\\/.test(resolvedPath)) { + // path is local filesystem path, which needs to be converted + // to long UNC path. + return '\\\\?\\' + resolvedPath; + } else if (/^\\\\[^?.]/.test(resolvedPath)) { + // path is network UNC path, which needs to be converted + // to long UNC path. + return '\\\\?\\UNC\\' + resolvedPath.substring(2); + } + + return path; + }; +} else { + exports._makeLong = function(path) { + return path; + }; +} +}()); +}; +BundleModuleCode['com/sprintf']=function (module,exports){ +(function(window) { + var re = { + not_string: /[^s]/, + number: /[diefg]/, + json: /[j]/, + not_json: /[^j]/, + text: /^[^\x25]+/, + modulo: /^\x25{2}/, + placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/, + key: /^([a-z_][a-z_\d]*)/i, + key_access: /^\.([a-z_][a-z_\d]*)/i, + index_access: /^\[(\d+)\]/, + sign: /^[\+\-]/ + } + + function sprintf() { + var key = arguments[0], cache = sprintf.cache + if (!(cache[key] && cache.hasOwnProperty(key))) { + cache[key] = sprintf.parse(key) + } + return sprintf.format.call(null, cache[key], arguments) + } + + sprintf.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = "" + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]) + if (node_type === "string") { + output[output.length] = parse_tree[i] + } + else if (node_type === "array") { + match = parse_tree[i] // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor] + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k])) + } + arg = arg[match[2][k]] + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]] + } + else { // positional argument (implicit) + arg = argv[cursor++] + } + + if (get_type(arg) == "function") { + arg = arg() + } + + if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) { + throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg))) + } + + if (re.number.test(match[8])) { + is_positive = arg >= 0 + } + + switch (match[8]) { + case "b": + arg = arg.toString(2) + break + case "c": + arg = String.fromCharCode(arg) + break + case "d": + case "i": + arg = parseInt(arg, 10) + break + case "j": + arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0) + break + case "e": + arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential() + break + case "f": + arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg) + break + case "g": + arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg) + break + case "o": + arg = arg.toString(8) + break + case "s": + arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg) + break + case "u": + arg = arg >>> 0 + break + case "x": + arg = arg.toString(16) + break + case "X": + arg = arg.toString(16).toUpperCase() + break + } + if (re.json.test(match[8])) { + output[output.length] = arg + } + else { + if (re.number.test(match[8]) && (!is_positive || match[3])) { + sign = is_positive ? "+" : "-" + arg = arg.toString().replace(re.sign, "") + } + else { + sign = "" + } + pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " " + pad_length = match[6] - (sign + arg).length + pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : "" + output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg) + } + } + } + return output.join("") + } + + sprintf.cache = {} + + sprintf.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0 + while (_fmt) { + if ((match = re.text.exec(_fmt)) !== null) { + parse_tree[parse_tree.length] = match[0] + } + else if ((match = re.modulo.exec(_fmt)) !== null) { + parse_tree[parse_tree.length] = "%" + } + else if ((match = re.placeholder.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1 + var field_list = [], replacement_field = match[2], field_match = [] + if ((field_match = re.key.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") { + if ((field_match = re.key_access.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + } + else if ((field_match = re.index_access.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + } + else { + throw new SyntaxError("[sprintf] failed to parse named argument key") + } + } + } + else { + throw new SyntaxError("[sprintf] failed to parse named argument key") + } + match[2] = field_list + } + else { + arg_names |= 2 + } + if (arg_names === 3) { + throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported") + } + parse_tree[parse_tree.length] = match + } + else { + throw new SyntaxError("[sprintf] unexpected placeholder") + } + try {_fmt = _fmt.substring(match[0].length)} catch (e) {throw new SyntaxError("[sprintf] unexpected fromat")} + } + return parse_tree + } + + var vsprintf = function(fmt, argv, _argv) { + _argv = (argv || []).slice(0) + _argv.splice(0, 0, fmt) + return sprintf.apply(null, _argv) + } + + /** + * helpers + */ + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase() + } + + function str_repeat(input, multiplier) { + return Array(multiplier + 1).join(input) + } + + /** + * export to either browser or node.js + */ + if (typeof exports !== "undefined") { + exports.sprintf = sprintf + exports.vsprintf = vsprintf + } + else { + window.sprintf = sprintf + window.vsprintf = vsprintf + + if (typeof define === "function" && define.amd) { + define(function() { + return { + sprintf: sprintf, + vsprintf: vsprintf + } + }) + } + } +})(typeof window === "undefined" ? this : window); +}; +BundleModuleCode['/home/sbosse/proj/jam/js/top/jamsh.js']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 1-3-18 by sbosse. + ** $VERSION: 1.2.1 + ** + ** $INFO: + ** + ** JAM Shell Interpreter (Front end) + ** + ** $ENDOFINFO + */ +/* Soem hacks */ +process.noDeprecation = true + +var Comp = Require('com/compat'); +var Io = Require('com/io'); +var doc = Require('doc/doc'); +var table = Require('doc/table'); +var readline = Require('com/readline'); +var readlineSync = Require('term/readlineSync'); +var renderer = doc.Renderer({lazy:true}); +var http = Require('http'); +var httpserv = Require('http/https'); // a file server! +try { var https = require('https'); } catch (e) {}; if (!https.request) https=undefined; +var geoip = Require('geoip/geoip'); // a geo location database server +var util = Require('util') +var sip = Require('top/rendezvous'); +var db = Require('db/db'); +// JAM Shell Interpreter (Back end) +var JamShell = Require('shell/shell'); + +var nlp = Require('nlp/nlp'); + +var ml = Require('ml/ml') +var nn = Require('nn/nn') +var csp = Require('csp/csp') +var sat = Require('logic/sat') +var logic = Require('logic/prolog') +var csv = Require('parser/papaparse'); +var ampCOM = Require('jam/ampCOM'); +var numerics = Require('numerics/numerics') +var osutils = Require('os/osutils'); +var UI = Require('ui/app/app'); +var Des48 = Require('dos/des48'); + +var p; + +var options= { + args:[], + echo: true, + modules : { + csp : csp, + csv : csv, + db : db, + des48 : Des48, + doc : doc, + geoip : geoip, + http : http, + httpserv : httpserv, + https : https, + logic:logic, + ml:ml, + nlp:nlp, + nn:nn, + numerics:numerics, + os:osutils, + readline : readline, + readlineSync : readlineSync, + sat : sat, + sip : sip, + table : table, + UI: UI, + }, + extensions : { + url2addr: ampCOM.url2addr, + sleep: process.watchdog&&process.watchdog.sleep? + function (milli) { + process.watchdog.sleep(milli) + }:undefined + }, + nameopts : {length:8, memorable:true, lowercase:true}, + Nameopts : {length:8, memorable:true, uppercase:true}, + output : null, + renderer : renderer, + server : false, + verbose : 0, +} + +process.on('uncaughtException', function (err) { + console.error(err.stack); + console.log("jamsh not exiting..."); +}); + +if ((p=process.argv.indexOf('--'))>0) { + options.args=process.argv.slice(p+1,process.argv.length); + process.argv=process.argv.slice(0,p); +} + +if (process.argv[1].match(/jamsh$/)||process.argv[1].match(/jamsh\.debug$/)) { + var ind; + if (process.argv.indexOf('-h')>0) return print('usage: jamsh [-v -s] [script] [-e "shell commands"]'); + if ((ind=process.argv.indexOf('-e'))>0) { + options.server=true; + options.output=console.log; + options.exec=process.argv[ind+1]; + } else if (process.argv.length>2) { + var script = process.argv.filter(function (arg,ind) { + return ind>1 && arg.indexOf(':') == -1 && arg.indexOf('-') != 0; + }); + if (script.length == 1) { + options.script=script[0]; + options.output=console.log; + options.server=true; + } + } + process.argv.forEach(function (arg) { + switch (arg) { + case '-v': options.verbose++; break; + case '-s': options.server=false; break; + } + }) + if (!options.server && options.verbose==0) options.verbose=1; + JamShell(options).init(); +} +}; +BundleModuleCode['com/compat']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2021 bLAB + ** $CREATED: 30-3-15 by sbosse. + ** $VERSION: 1.24.1 + ** + ** $INFO: + ** + ** JavaScript-OCaML Compatibility Module + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Path = Require('com/path'); +var Sprintf = Require('com/sprintf'); + +/******************************* +** Some global special "values" +********************************/ + +/** A matching template pattern matching any value + * + * @type {undefined} + */ +global.any = undefined; +/** A matching template pattern matching any value + * + * @type {undefined} + */ +global._ = undefined; + +/** + * + * @type {null} + */ +global.none = null; +/** + * + * @type {null} + */ +global.empty = null; + +global.NL = '\n'; + +global.int = function (v) {return v|0}; +global.div = function (a,b) {return a/b|0}; + +if (!Object.prototype.forEach) { + Object.defineProperties(Object.prototype, { + 'forEach': { + value: function (callback) { + if (this == null) { + throw new TypeError('Not an object'); + } + var obj = this; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + callback.call(obj, obj[key], key, obj); + } + } + }, + writable: true + } + }); +} +/** Just transfer parent prototypes to child + * + */ +function inherit(child,parent) { + for(var p in parent.prototype) { + if (p == '__proto__') continue; + child.prototype[p]=parent.prototype[p]; + } +} + +/** Portable class inheritance and instanceOf polyfill + * + */ +// SomeObject.prototype.__proto__=SomeObject2.prototype; +// Child class inherits prototype from parent using __proto__ +function inheritPrototype(child,parent) { + var __proto__=child.__proto__; + child.prototype.__proto__=parent.prototype; + if (!__proto__) for(var p in parent.prototype) { + if (p == '__proto__') continue; + child.prototype[p]=parent.prototype[p]; + } +} +// Polyfill fir o instanceof c with inheritance check (checking __proto__) +function instanceOf(obj,cla) { + var p=obj.__proto__; + if (obj instanceof cla) return true; + while (p) { + if (p === cla.prototype) return true; + p=p.__proto__ + } + return false; +} +// Polyfill for __defineGetter__ / __defineSetter__ +function defineGetter(cla,prop,fun) { + Object.defineProperty(cla.prototype,prop,{ + configurable:true, + get:fun + }); +} +function defineSetter(cla,prop,fun) { + Object.defineProperty(cla.prototype,prop,{ + configurable:true, + set:fun + }); + +} + +Object.addProperty = function (obj,name,fun) { + if (obj.prototype[name]) return; + obj.prototype[name]=fun; + Object.defineProperty(obj.prototype, name, {enumerable: false}); +}; + +Object.updateProperty = function (obj,name,fun) { + obj.prototype[name]=fun; + Object.defineProperty(obj.prototype, name, {enumerable: false}); +}; + +Object.addProperty(Array,'contains',function (el) { return this.indexOf(el)!=-1 }); +Object.addProperty(Array,'last',function () { return this[this.length-1] }); + +global.inherit = inherit; +global.inheritPrototype = inheritPrototype; +global.instanceOf = instanceOf; +global.defineGetter = defineGetter; +global.defineSetter = defineSetter; + +/** + * + */ +var assert = function(condmsg) { + if (condmsg != true) { + Io.out('** Assertion failed: '+condmsg+' **'); + Io.stacktrace(); + throw Error(condmsg); + } +}; +global.assert=assert; + +function forof(obj,f) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = obj[Symbol.iterator](), _step; + !(_iteratorNormalCompletion = (_step = _iterator.next()).done); + _iteratorNormalCompletion = true) { + element = _step.value; + + f(element); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } +} + + +global.forof=forof; + +/** OBJ + * + */ +var obj = { + /** Compact an object: + * [{a:b},[c:d},..] -> {a:b,c:d,..} + * {a:[b]} -> {a:b} + * + */ + compact: function (o) { + var a; + if (obj.isArray(o)) { + if (o.length==1 && obj.isObject(o[0])) return obj.compact(o[0]); + else return o; + } else if (obj.isObject(o)) for (a in o) { + var elem=o[a]; + o[a]=obj.compact(elem); + } + return o; + }, + copy: function (o) { + if (o === null || typeof o !== 'object') { + return o; + } + + var temp = (o instanceof Array) ? [] : {}; + for (var key in o) { + temp[key] = obj.copy(o[key]); + } + + return temp; + }, + equal: function (o1,o2) { + if (!o1 || !o2) return false; + for(var i in o1) if (o1[i]!=o2[i]) return false; + for(var i in o2) if (o1[i]!=o2[i]) return false; + return true; + }, + extend: function (o1,o2) { + for(var i in o2) o1[i]=o2[i]; + return o1; + }, + find: function(obj,fun) { + var p; + for(p in obj) { + if (fun(obj[p],p)) return obj[p]; + } + }, + + hasProperty: function (o,p) { + return o[p]!=undefined || (p in o); + }, + head:function(o) { + for (var p in o) return p; + return undefined; + }, + // transfer src attributes to dst recusively (no object overwrite) + inherit: function (dst,src) { + for(var i in src) { + if (typeof dst[i] == 'object' && typeof src[i] == 'object') + inherit(dst[i],src[i]); + else if (typeof dst[i] == 'undefined') + dst[i]=src[i]; + } + return dst; + }, + isArray:function (o) { + if (o==_ || o ==null) return false; + else return typeof o == "array" || (typeof o == "object" && o.constructor === Array); + }, + isMatrix:function (o) { + if (o==_ || o ==null) return false; + else return obj.isArray(o) && + obj.isArray(o[0]); + }, + isEmpty: function (o) { + for(var prop in o) { + if (o[prop]!=undefined) return false; + } + return true; + }, + isError: function (o) { + return o instanceof Error + }, + isFunction: function (o) { + return typeof o == "function"; + }, + isObj:function (o) { + return typeof o == "object"; + }, + isObject:function (o) { + return typeof o == "object"; + }, + isRegex: function (o) { + return o instanceof RegExp; + }, + isString: function (o) { + return typeof o == "string" || (typeof o == "object" && o.constructor === String); + }, + isNumber: function (o) { + return typeof o == "number" || (typeof o == "object" && o.constructor === Number); + }, + + + iter: function(obj,fun) { + var p; + for(p in obj) { + fun(obj[p],p) + } + } +}; + +/** ARRAY + * + */ +var array = { + /** Evaluate a function returning a boolean value for each member of the array and + * compute the boolean conjunction. + * + * @param {* []} array + * @param {function(*,number)} fun + */ + and: function(array,fun) { + var res=true; + var i=0; + var len=array.length; + for(i=0;i|string|Blob|ArrayBuffer} + */ + copy: function(src,dst) { + var i; + if (dst) { + for(i in src) dst[i]=src[i]; + } else return src.slice(); + }, + /** Create a new array with initial element values. + * + * @param length + * @param init + * @returns {Array} + */ + create : function(length,init) { + var arr = [], i = length; + while (i--) { + arr[i] = init; + } + return arr; + }, + /** Create a matrix (array of array) with initial element values. + * + */ + create_matrix : function(rows,cols,init) { + var m = []; + var r = []; + var i,j; + for (i = 0; i < rows; i++) { + r=[]; + for(j=0;j=0;i--) { + fun(array[i],i) + } + }, + /** Return last element of array. + * + */ + last : function(array) { + var len=array.length; + if (len==0) return none; + else return array[len-1]; + }, + + length : function(array) { + return array.length; + }, + /** + * + * @param {* []} array1 + * @param {* []} array2 + * @param {function(*,*,number)} fun + * @returns {* []} + */ + map2: function(array1,array2,fun) { + var i=0; + assert((array1.length == array2.length)||('Array.map2: arrays of different lengths')); + var len=array1.length; + var res=[]; + for(i=0;i1) { + var hd = this.head(array); + var tl = this.tail(array); + fun_hdtl(hd,tl); + } else fun_hdtl(this.head(array),[]); + }, + /** + * + * @param {* []} array + * @param {Function} fun_hd1hd2 - function(hd1,hd2) + * @param {Function} [fun_hdtl] - function(hd,tl) + * @param {Function} [fun_empty] - function() + */ + match2: function(array,fun_hd1hd2,fun_hdtl,fun_empty) { + if (array.length == 0 && fun_empty) + fun_empty(); + else if (array.length == 2) { + var hd1 = this.head(array); + var hd2 = this.second(array); + fun_hd1hd2(hd1,hd2); + } + else if (array.length>1 && fun_hdtl) { + var hd = this.head(array); + var tl = this.tail(array); + fun_hdtl(hd,tl); + } else if (fun_hdtl) fun_hdtl(this.head(array),[]); + }, + /** Return the maximum element of an array applying + * an optional mapping function. + * + * @param {* []} array + * @param [fun] + * @returns {number|undefined} + */ + max : function (array,fun) { + var res,max,num; + for(var i in array) { + if (fun) num=fun(array[i]); else num=array[i]; + if (max==undefined) { max=num; res=array[i] } + else if (num > max) { max=num; res=array[i] } + } + return res; + }, + /** Return the minimum element of an array applying + * an optional mapping function. + * + * @param {* []} array + * @param [fun] + * @returns {number|undefined} + */ + min : function (array,fun) { + var res,min,num; + for(var i in array) { + if (fun) num=fun(array[i]); else num=array[i]; + if (min==undefined) { min=num; res=array[i] } + else if (num < min) { min=num; res=array[i] } + } + return res; + }, + /** Check for an element in the array. + * + * @param {(number|string|boolean) []} array + * @param {number|string|boolean} element + * @returns {boolean} + */ + member: function(array,element) { + var i,exist; + var len=array.length; + exist=false; + loop: for(i=0;i ['barney', 'fred'] + */ + pluck: function(collection, key) { + return collection.map(function(object) { + return object == null ? undefined : object[key]; + }); + }, + /* + ** Push/pop head elements (Stack behaviour) + */ + /** Remove and return top element of array. + * + * @param array + * @returns {*} + */ + pop : function(array) { + var element=array[0]; + array.shift(); + return element; + }, + print: function(array) { + var i; + var len=array.length; + var str='['; + for(i=0;i [1,2,6] + * @param {* []} array + * @returns {* []} + */ + remove: function(array,begin,end) { + var i,a; + if (end==undefined) end=begin+1; + if (begin<0 || end >= array.length) return []; + a=array.slice(0,begin); + for(i=end;i use remove!!! split should return two arrays!! + * + * @param array + * @param pos + * @param [len] + * @param element + */ + split: function(array,pos,len) { + if (pos==0) return array.slice((len||1)); + else { + var a1=array.slice(0,pos); + var a2=array.slice(pos+(len||1)); + return a1.concat(a2); + } + }, + /** Return the sum number of an array applying + * an optional mapping function. + * + * @param {* []} array + * @param [fun] + * @returns {number|undefined} + */ + sum : function (array,fun) { + var res=0; + for(var i in array) { + var num=0; + if (fun) num=fun(array[i]); else num=array[i]; + if (!obj.isNumber(num)) return undefined; + res += num; + } + return res; + }, + /** Return a new array w/o the head element (or optional + * w/o the first top elements). + * + */ + tail : function(array,top) { + var array2=array.slice(); + array2.shift(); + if (top) for(;top>1;top--) array2.shift(); + return array2; + }, + /** Return union of two sets (== conjunction set) + * + * @param {* []} set1 + * @param {* []} set2 + * @param {function} [fun] Equality test + * @returns {* []} + */ + union : function(set1,set2,fun) { + var i,j,res = []; + for (i in set1) { + for (j in set2) { + if (fun != undefined && fun(set1[i],set2[j])) res.push(set1[i]); + else if (fun == undefined && set1[i]==set2[j]) res.push(set1[i]); + } + } + return res; + }, + + /** + * Creates a duplicate-free version of an array + */ + unique: function(array) { + var length = array ? array.length : 0; + function baseUniq(array) { + var index = -1, + length = array.length, + seen, + result = []; + + seen = result; + outer: + while (++index < length) { + var value = array[index]; + var seenIndex = seen.length; + while (seenIndex--) { + if (seen[seenIndex] === value) { + continue outer; + } + } + result.push(value); + } + return result; + } + if (!length) { + return []; + } + return baseUniq(array); + }, + + /** + * Creates an array excluding all provided values + * without([1, 2, 1, 3], 1, 2); + * // => [3] + */ + without: function () { + var array, + values=[]; + for(var i in arguments) { + if (i==0) array=arguments[0]; + else values.push(arguments[i]); + } + return array.filter(function (e) { + return values.indexOf(e) == -1; + }); + }, + /** Test for zero elements {0, '', false, undefined, ..} + */ + zero: function (array) { + for(var i in array) if (!!array[i]) return false; + return true; + }, +}; + +/** STRING + * + */ +var string = { + /** Is pattern conatined in template? + * + */ + contains: function (template,pattern) { + return template.indexOf(pattern)>-1; + }, + copy: function(src) { + var i; + var dst=''; + for(i=0;i>4) & 0xf).toString(16))+ + ((n&0xf).toString(16)); + case 4: return (((n>>12) & 0xf).toString(16)+ + ((n>>8) & 0xf).toString(16)+ + ((n>>4) & 0xf).toString(16)+ + (n&0xf).toString(16)); + case 6: return (((n>>20) & 0xf).toString(16)+ + ((n>>16) & 0xf).toString(16)+ + ((n>>12) & 0xf).toString(16)+ + ((n>>8) & 0xf).toString(16)+ + ((n>>4) & 0xf).toString(16)+ + (n&0xf).toString(16)); + case 8: return (((n>>28) & 0xf).toString(16)+ + ((n>>24) & 0xf).toString(16)+ + ((n>>20) & 0xf).toString(16)+ + ((n>>16) & 0xf).toString(16)+ + ((n>>12) & 0xf).toString(16)+ + ((n>>8) & 0xf).toString(16)+ + ((n>>4) & 0xf).toString(16)+ + (n&0xf).toString(16)); + default: return 'format_hex??'; + } + }, + /** + * + * @param {string} str + * @param {number} index + * @returns {string} + */ + get: function (str,index) { + assert((str != undefined && index < str.length && index >= 0)||('string.get ('+str.length+')')); + return str.charAt(index); + }, + isBoolean: function (str) { + return (str=='true' || str=='false') + }, + isNumeric: function (str) { + return !isNaN(parseFloat(str)) && isFinite(str); + }, + isText: function (s) { + var is_text=true; + string.iter(s,function (ch,i) { + string.match(ch,[ + ['a','z',function () {}], + ['A','Z',function () {}], + ['0','9',function () {if (i==0) is_text=false;}], + function () {is_text=false;} + ]); + }); + return is_text; + }, + /** + * + * @param {string} str + * @param {function(string,number)} fun + */ + iter: function(str,fun) { + var i; + var len=str.length; + for (i = 0; i < len; i++) { + var c = str.charAt(i); + fun(c,i); + } + }, + /** + * + * @param str + * @returns {*} + */ + length: function(str) { + if (str!=undefined) return str.length; + else return 0; + }, + /** + * + * @param str + * @returns {string} + */ + lowercase : function (str) { + return str.toLowerCase(); + }, + /** + * + * @param {number} size + * @param {string} init + * @returns {string} + */ + make: function(size,init) + { + var i; + var s=''; + for(i=0;i,,..],fun] | [:string,:string,fun] | fun) [] + */ + match: function(str,cases) { + var i,j; + var cas,cex,cv; + for(i in cases) { + cas=cases[i]; + if (obj.isArray(cas)) { + switch (cas.length) { + case 2: + // Multi-value-case + cex=cas[0]; + if (!obj.isArray(cex)) { + if (this.equal(str,cex)) { + cas[1](); + return; + } + } else { + for(j in cex) { + cv=cex[j]; + if (this.equal(str,cv)) { + cas[1](); + return; + } + } + } + break; + case 3: + // Character range check + try { + j=pervasives.int_of_char(str); + if (j>= pervasives.int_of_char(cas[0]) && j<=pervasives.int_of_char(cas[1])) { + cas[2](str); + return; + } + } catch(e) { + return + }; + break; + case 1: + cas[0](str); // Default case - obsolete + return; + default: + throw 'String.match #args'; + } + } else if (obj.isFunction(cas)) { + // Default case + cas(str); + return; + } + } + }, + /** Pad a string on the left (pre-str.length) if pre>0, + * right (post-str.length) if post>0, or centered (pre>0&post>0). + * + */ + + pad: function (str,pre,post,char) { + var len = str.length; + if (pre>0 && post==0) return string.make(len-pre,char||' ')+str; + else if (post>0 && pre==0) return str+string.make(post-len,char||' '); + else return string.make(len-pre/2,char||' ')+str+string.make(len-post/2,char||' '); + }, + /** + * + * @param str + * @param pos + * @param len + * @returns {Number} + */ + parse_hex: function (str,pos,len) { + // parse a hexadecimal number in string 'str' starting at position 'pos' with 'len' figures. + return parseInt(this.sub(str,pos,len),16); + }, + /** Return the sub-string after a point in the source string ('.' or optional point string). + * If there is no splitting point, the original string is returned. + * + * @param str + * @param [point] + * @returns {string} + */ + postfix: function (str,point) { + var n = str.indexOf(point||'.'); + if (n <= 0) return str; + else return str.substr(n+1); + }, + /** Return the sub-string before a point in the source string ('.' or optional point string) + * If there is no splitting point, the original string is returned. + * + * @param str + * @param [point] + * @returns {string} + */ + prefix: function (str,point) { + var n = str.indexOf(point||'.'); + if (n <= 0) return str; + else return str.substr(0,n); + }, + replace_first: function (pat,repl,str) { + return str.replace(pat,repl); + }, + replace_all: function (pat,repl,str) { + return str.replace('/'+pat+'/g',repl); + }, + /** + * + * @param str + * @param index + * @param char + * @returns {string} + */ + set: function (str,index,char) { + assert((str != undefined && index < str.length && index >= 0)||'string.get'); + return str.substr(0, index) + char + str.substr(index+1) + }, + /** + * + * @param delim + * @param str + * @returns {*|Array} + */ + split: function (delim,str) { + return str.split(delim); + }, + startsWith : function (str,head) { + return !str.indexOf(head); + }, + /** Return a sub-string. + * + * @param str + * @param off + * @param [len] If not give, return a sub-string from off to end + * @returns {string} + */ + sub: function (str,off,len) { + if (len) + return str.substr(off,len); + else + return str.substr(off); + }, + /** Remove leading and trailing characters from string + * + * @param str + * @param {number} pref number of head characters to remove + * @param {number} post number of tail characters to remove + * @returns {*} + */ + trim: function (str,pref,post) { + if (str.length==0 || + pref>str.length || + post>str.length || + pref < 0 || post < 0 || + (pref==0 && post==0) + ) return str; + return str.substr(pref,str.length-pref-post); + }, + /** Return a string with all characters converted to uppercase letters. + * + * @param str + * @returns {string} + */ + uppercase : function (str) { + return str.toUpperCase(); + }, + /** Return a string with first character converted to uppercase letter. + * + * @param str + * @returns {string} + */ + Uppercase : function (str) { + var len = str.length; + if (len > 1) { + var head = str.substr(0,1); + var tail = str.substr(1,len-1); + return head.toUpperCase()+tail.toLowerCase() + } if (len==1) return str.toUpperCase(); + else return ''; + } +}; + +/** RANDOM + * + */ +var rnd = Math.random; +/* Antti Syk\E4ri's algorithm adapted from Wikipedia MWC +** Returns a random generator function [0.0,1.0| with seed initialization +*/ +var seeder = function(s) { + var m_w = s; + var m_z = 987654321; + var mask = 0xffffffff; + + return function() { + m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask; + m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask; + + var result = ((m_z << 16) + m_w) & mask; + result /= 4294967296; + + return result + 0.5; + } +} + +var random = { + float: function(max) { + return rnd()*max + }, + int: function(max) { + return Math.floor(rnd()*max+0) + }, + // integer + interval: function(min,max) { + return Math.round(min+rnd()*(max-min)) + }, + // float + range: function(min,max) { + return min+rnd()*(max-min) + }, + seed: function (s) { + // Create a new initialized random generator + rnd=seeder(s); + } +}; + +/** PRINTF + * + */ +var printf = { + /** Trim string(s). + * + * @param str + * @param indent + * @param [width] + * @param {string} [tab] + * @returns {string} + */ + align: function (str,indent,width,tab) { + var lines = string.split('\n',str); + var form = ''; + var sp = printf.spaces(indent); + var spbreak = sp; + + array.iter(lines,function(line){ + var rest; + function breakit(spbreak,str) { + if (width < (str.length + spbreak.length)) { + return spbreak+string.sub(str,0,width-spbreak.length)+'\n'+ + breakit(spbreak,string.sub(str,width-spbreak.length,str.length-width+spbreak.length)); + } else return spbreak+str+'\n'; + } + if (width && width < (line.length + indent)) { + if (tab) { + var pos = string.find(tab,line); + if (pos > 0 && pos < width) spbreak=printf.spaces(pos+indent+1); + else spbreak=sp; + } + form=form+sp+string.sub(line,0,width-indent)+'\n'; + rest=string.sub(line,width-indent,line.length-width+indent); + form=form+breakit(spbreak,rest); + } + else + form=form+sp+line+'\n'; + }); + return form; + }, + /** Format a list of array elements using the (optional) mapping + * function and the separator (optional, too, default is ','). + * + */ + list: function (array,fun,sep) { + var i, str=''; + if (sep==undefined) sep=','; + if (fun==undefined) fun=function (s) {return s;}; + if (!obj.isArray(array)) array=[array]; + for (i in array) { + if (str==='') str=fun(array[i]); + else str=str+sep+fun(array[i]); + } + return str; + }, + /** + * + * @param n + * @returns {string} + */ + spaces: function (n){ + return string.make(n,' '); + }, + /** Formatted printer (simplified) + * + * @param {* []} args (['%format',arg]|string) [] format=%s,%d,%f,%c,%x,%#d,%#s,.. + * @returns {string} + */ + sprintf2: function(args) { + var str=''; + array.iter(args,function(fmtarg) { + var len, n,fs; + if (obj.isArray(fmtarg)) { + if (fmtarg.length==2) { + var fmt=fmtarg[0]; + var arg=fmtarg[1]; + var fc=''; + var fn=0; + string.iter(fmt,function(c) { + if (c=='s' || c=='d' || c=='f' || c=='x') { + fc=c; + } else if (c!='%') { + fn=fn*10; + n=parseInt(c); + if (!isNaN(n)) fn=fn+n; + } + }); + if (fc=='s' && obj.isString(arg)) { + str=str+arg; + if (fn!=0) { + len=arg.length; + if (len 0 && path[0] == '/'); + }, + /** + * + * @param pathl + * @param absolute + * @returns {string} + */ + join: function (pathl,absolute) { + var path=(absolute?'/':''); + array.iter(pathl,function (name,index) { + if (index>0) { + path=path+'/'+name; + } + else { + path=path+name; + } + }); + return path; + }, + /** + * + * @param path + * @returns {string} + */ + normalize : function (path) { + return Path.normalize(path) + }, + /** + * + * @param path + * @returns {*} + */ + path_absolute: function (path) { + if (this.is_relative(path)) { + var workdir = Io.workdir(); + return this.path_normalize(workdir + '/' + path); + } else return this.path_normalize(path); + }, + /** Duplicate of Path.normalize!? + * + * @param path + * @returns {string} + */ + path_normalize: function (path) { + var i; + if (string.equal(path, '')) path = '/'; + var relpath = !(string.get(path, 0) == '/'); + var pathlist = path.split('/'); + var pathlist2 = pathlist.filter(function (s) { + return (!string.equal(s, '') && !string.equal(s, '.')) + }); + var pathlist3 = []; + array.iter(pathlist2, function (pe) { + if (!string.equal(pe, '..')) { + array.push(pathlist3, pe) + } else { + if (pathlist3.length == 0) return ''; + else + pathlist3 = array.tail(pathlist3); + } + }); + var path2 = ''; + i = 0; + array.iter(pathlist3, function (pe) { + var sep; + if (i == 0) sep = ''; else sep = '/'; + path2 = pe + sep + path2; + i++; + }); + if (relpath) return path2; else return '/' + path2; + }, + removeext: function (path) { + return path.substr(0, path.lastIndexOf('.')); + } +}; + +/** PERVASIVES + * + * + */ +var pervasives = { + assert:assert, + char_of_int: function (i) {return String.fromCharCode(i)}, + div: function(a,b) {return a/b|0;}, + failwith: function(msg) {Io.err(msg);}, + float_of_string: function(s) {var num=parseFloat(s); if (isNaN(num)) throw 'NaN'; else return num;}, + int_of_char: function(c) {return c.charCodeAt()}, + int_of_float: function(f) {return f|0;}, + int_of_string: function(s) { + var num=parseInt(s); if (isNaN(num)) throw 'NaN'; else return num; + }, + + /** Try to find a value in a search list and return a mapping value. + * + * @param {*} value + * @param {* []} mapping [testval,mapval] [] + * @returns {*} + */ + map: function(value,mapping) { + function eq(v1,v2) { + if (v1==v2) return true; + if (obj.isString(v1) && obj.isString(v2)) return string.equal(v1,v2); + return false; + } + if (!array.empty(mapping)) { + var hd=array.head(mapping); + var tl=array.tail(mapping); + if (eq(hd[0],value)) return hd[1]; + else return pervasives.map(value,tl); + } else return undefined; + }, + /** Apply a matcher function to a list of cases with case handler functions. + * A case is matched if the matcher function returns a value/object. + * + * The result of the matcher function is passed as an argument ot the case handler function. + * The return value of the case handler fucntion is finally returned by this match function + * or undefined if there was no matching case. + * + * @param {function(*,*):*} matcher function(expr,pat) + * @param {*} expr + * @param {*[]} cases (pattern,handler function | handler function) [] + * @returns {*|undefined} + */ + match: function (matcher,expr,cases) { + var ret = undefined; + array.iter_break(cases, function (match) { + var quit, succ, pat, fun; + + if (match.length == 2) { + /* + ** Pattern, Function + */ + pat = match[0]; + fun = match[1]; + succ = matcher(expr, pat); + if (succ) ret = fun(succ); + quit = succ!=undefined; + } else if (match.length == 1) { + /* + ** Default case, Function + */ + fun = match[0]; + ret = fun(); + quit= true; + } + return quit; + }); + return ret; + }, + mtime: function () {var time = new Date(); return time.getTime();}, + min: function(a,b) { return (ab)?a:b}, + string_of_float: function(f) {return f.toString()}, + string_of_int: function(i) {return i.toString()}, + string_of_int64: function(i) {return i.toString()}, + time: function () {var time = new Date(); return (time.getTime()/1000)|0;} +}; + +/** BIT + * + */ +var bit = { + get: function (v,b) {return (v >> b) && 1;}, + isSet: function (v,b) {return ((v >> b) && 1)==1;}, + set: function (v,b) {return v & (1 << b);} +}; + +/** ARGS + * + */ +var args = { + /** Parse process or command line arguments (array argv). The first offset [1] arguments are + ** ignored. The numarg pattern '*' consumes all remaining arguments. + * + * @param {string []} argv + * @param {*[]} map [,,]|[] [] + * @param {number} [offset] + */ + parse: function(argv,map,offset) { + var shift=undefined, + in_shift=0, + shift_args=[], + names, + mapfun, + numarg, + len=argv.length; + + if (offset==undefined) offset=1; + + argv.forEach(function (val, index) { + var last=index==(len-1); + if(index>=offset) { + if (in_shift==0) { + array.check(map,function (onemap) { + assert(onemap!=undefined||'map'); + if (onemap.length==3) { + names = onemap[0]; + numarg = onemap[1]; + mapfun = onemap[2]; + if (!obj.isArray(names)) names=[names]; + var found = array.find(names,function (name) { + if (string.equal(val, name)) return name; else _; + }); + if (found) { + if (numarg==0) mapfun(found); + else { + in_shift=numarg; + shift_args=[]; + shift=mapfun; + } + return true; + } + } else if (obj.isFunction(onemap)) { + onemap(val); + return true; + } else if (onemap.length==1) { + mapfun = onemap[0]; + mapfun(val); + return true; + } + return false; + }); + } else { + shift_args.push(val); + if (in_shift!='*') in_shift--; + if (in_shift==0 && shift!=undefined) { + numarg=shift_args.length; + switch (numarg) { + case 0: shift(val);break; + case 1: shift(shift_args[0],val); break; + case 2: shift(shift_args[0],shift_args[1],val); break; + case 3: shift(shift_args[0],shift_args[1],shift_args[2],val); break; + default: break; + } + shift=undefined; + } else if (in_shift=='*' && last) shift(shift_args); + } + } + }); + } + +}; + +/** HASHTBL + * + */ +var hashtbl = { + add: function(hash,key,data) { + hash[key]=data; + }, + create: function(initial) { + return []; + }, + empty: function(hash) { + for (var key in hash) return false; + return true; + }, + find: function(hash,key) { + return hash[key]; + }, + invalidate: function(hash,key) { + hash[key]=undefined; + }, + iter: function(hash,fun) { + for (var key in hash) { + if (hash[key]!=undefined) fun(key,hash[key]); + } + }, + mem: function(hash,key) { + return hash[key] != undefined; + }, + remove: function(hash,key) { + // TODO: check, its wrong! + if (!hash.hasOwnProperty(key)) + return; + if (isNaN(parseInt(key)) || !(hash instanceof Array)) + delete hash[key]; + else + hash.splice(key, 1) + } +}; + +var types = []; +/** + * + * @param name + * @returns {number} + */ +function register_type(name) { + var typoff = 1000+types.length*1000; + if (array.member(types,name)) throw('[COMP] register_type: type '+name+' exists already.'); + types.push(name); + return typoff; +} + +/** + * + * @typedef {{v1:*, v2:*, v3:*, v4:*, v5:*, v6:*, v7:*, v8:*, v9:* }} tuple + */ +/** + * + * @typedef {{t:number, v1:*, v2:*, v3:*, v4:*, v5:*, v6:*, v7:*, v8:*, v9:* }} tagged_tuple + */ + +module.exports = { + args:args, + assert: assert, + array:array, + bit:bit, + copy:obj.copy, + div:pervasives.div, + filename:filename, + hashtbl:hashtbl, + isNodeJS: function () { + return (typeof global !== "undefined" && + {}.toString.call(global) == '[object global]'); + }, + obj:obj, + pervasives:pervasives, + printf:printf, + random:random, + string:string, + isArray: obj.isArray, + isString: obj.isString, + isNumber: obj.isNumber, + + register_type:register_type, + /** + * + * @param tag + * @param [val1] + * @param [val2] + * @param [val3] + * @returns {(tagged_tuple)} + */ + Tuple: function (tag,val1,val2,val3) { + if(val3) return {t:tag,v1:val1,v2:val2,v3:val3}; + else if (val2) return {t:tag,v1:val1,v2:val2}; + else if (val1) return {t:tag,v1:val1}; + else return {t:tag}; + } +}; +}; +BundleModuleCode['doc/doc']=function (module,exports){ +/** Markdown renderer for highlighted and formatted terminal output + * by embedding (escaped) terminal control sequences + * + */ + +var Marked = Require('doc/marked'); +var Colors = Require('doc/colors'); +var List = Require('doc/list'); +var Table = Require('doc/cli-table'); +var NL = '\n'; + +function id (x) {return x} + +// default css +var css = { + bold:Colors.black.bold, + italic:Colors.underline, + h1:Colors.bold, + h2:Colors.blue.bold, + h3:Colors.red.bold, + + ol:{ + label:['1','a','i'], + }, + ul:{ + label:['*','-','+'], + }, + +} + +// Increment numeric/alphanumeric list label +function incr(label,start) { + switch (label) { + case '1': return start.toString(); + } + return label; +} + +function B (text) { return css.bold(text) } +function I (text) { return css.italic(text) } +function P (text) { return text+'\n' } + +function H (text, level) { + var color, + escapedText = text.toLowerCase().replace(/[^\w]+/g, '-'); + + switch (level) { + case 1: color=css.h1; break; + case 2: color=css.h2; break; + case 3: color=css.h3; break; + default: color=id; + } + + return color(text+'\n'); +}; + +function CD(text) { + return text+'\n'; +} + + +function DL(body) { + var item; + list=new List({type:'dl',tab:2}); + while (this._data.stack.length && this._data.stack[0].dt != undefined) { + item=this._data.stack.shift(); + //print(item) + list.unshift({dt:css.bold(item.dt),dd:item.dd}); + } + return list.toString()+NL; + +} +function DT(body) { + this._data.stack.unshift({dt:body}); +} +function DD(body) { + if (this._data.stack.length && this._data.stack[0].dt!=undefined) + this._data.stack[0].dd=body; +} + +function L(body, ordered, start) { + var list,label; + if (ordered) label=incr(css.ol.label[this._data.olist],start); + else label=css.ul.label[this._data.ulist]; + list=new List({type:label}); + + if (ordered) this._data.olist++; else this._data.ulist++; + + while (this._data.stack.length && this._data.stack[0].item != undefined) { + list.unshift(this._data.stack.shift().item); + } + + if (ordered) this._data.olist--; else this._data.ulist--; + return list.toString()+NL; +} + +function LI(text) { + this._data.stack.unshift({item:text}); +} + +function text(text) { + return text.replace(/"/g,'"'). + replace(/>/g,'>'). + replace(/</g,'<'); +} + +// Terminal MarkDown Renderer +function Renderer (options) { + var marked = Marked(), + renderer = new marked.Renderer(); + + renderer.heading = H.bind(renderer); + renderer.list = L.bind(renderer); + renderer.listitem = LI.bind(renderer); + renderer.paragraph = P.bind(renderer); + renderer.strong = B.bind(renderer); + renderer.em = I.bind(renderer); + renderer._data={stack:[],ulist:0,olist:0}; + renderer.dt = DT.bind(renderer); + renderer.dd = DD.bind(renderer); + renderer.dl = DL.bind(renderer); + renderer.code = CD.bind(renderer); + renderer.text = text; + + marked.setOptions({ + renderer: renderer, + highlight: function(code) { + return require('highlight.js').highlightAuto(code).value; + }, + pedantic: false, + gfm: true, + tables: true, + breaks: false, + sanitize: false, + smartLists: true, + smartypants: false, + xhtml: false + }); + if (options.lazy) return function (text) { try { return marked(text) } catch (e) { return text }}; + else return marked; +} + +module.exports = { + Colors:Colors, + List:List, + Marked:Marked, + Renderer:Renderer, + Table:Table +} +}; +BundleModuleCode['doc/marked']=function (module,exports){ +/** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * 2016-2019 (c) Dr. Stefan Bosse + * https://github.com/markedjs/marked + * + * Version 1.2.2 + */ + +module.exports = function() { +'use strict'; + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^( {4}[^\n]+\n*)+/, + fences: noop, + hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, + nptable: noop, + blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, + list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + html: '^ {0,3}(?:' // optional indentation + + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) + + '|comment[^\\n]*(\\n+|$)' // (2) + + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) + + '|\\n*' // (4) + + '|\\n*' // (5) + + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) + + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag + + '|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag + + ')', + def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, + dl: /^ *(dt)\n: *(dd)/, + table: noop, + lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, + paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)+)/, + text: /^[^\n]+/ +}; + +block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; +block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; +block.def = edit(block.def) + .replace('label', block._label) + .replace('title', block._title) + .getRegex(); + + +block.bullet = /(?:[*+-]|\d+\.)/; +block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.item = edit(block.item, 'gm') + .replace(/bull/g, block.bullet) + .getRegex(); + +block.list = edit(block.list) + .replace(/bull/g, block.bullet) + .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') + .replace('def', '\\n+(?=' + block.def.source + ')') + .getRegex(); + +block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + + '|track|ul'; +block._comment = //; +block.html = edit(block.html, 'i') + .replace('comment', block._comment) + .replace('tag', block._tag) + .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) + .getRegex(); + +block.paragraph = edit(block.paragraph) + .replace('hr', block.hr) + .replace('heading', block.heading) + .replace('lheading', block.lheading) + .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks + .getRegex(); + +block.blockquote = edit(block.blockquote) + .replace('paragraph', block.paragraph) + .getRegex(); + +block.dl = edit(block.dl) + .replace('dt', block.text) + .replace('dd', block.text) + .getRegex(); + +/** + * Normal Block Grammar + */ + +block.normal = merge({}, block); + +/** + * GFM Block Grammar + */ + +block.gfm = merge({}, block.normal, { + fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, + paragraph: /^/, + heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ +}); + +block.gfm.paragraph = edit(block.paragraph) + .replace('(?!', '(?!' + + block.gfm.fences.source.replace('\\1', '\\2') + '|' + + block.list.source.replace('\\1', '\\3') + '|') + .getRegex(); + +/** + * GFM + Tables Block Grammar + */ + +block.tables = merge({}, block.gfm, { + nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, + table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ +}); + +/** + * Pedantic grammar + */ + +block.pedantic = merge({}, block.normal, { + html: edit( + '^ *(?:comment *(?:\\n|\\s*$)' + + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag + + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') + .replace('comment', block._comment) + .replace(/tag/g, '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') + .getRegex(), + def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ +}); + +/** + * Block Lexer + */ + +function Lexer(options) { + this.tokens = []; + this.tokens.links = {}; + this.options = options || marked.defaults; + this.rules = block.normal; + + if (this.options.pedantic) { + this.rules = block.pedantic; + } else if (this.options.gfm) { + if (this.options.tables) { + this.rules = block.tables; + } else { + this.rules = block.gfm; + } + } +} + +/** + * Expose Block Rules + */ + +Lexer.rules = block; + +/** + * Static Lex Method + */ + +Lexer.lex = function(src, options) { + var lexer = new Lexer(options); + return lexer.lex(src); +}; + +/** + * Preprocessing + */ + +Lexer.prototype.lex = function(src) { + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' ') + .replace(/\u00a0/g, ' ') + .replace(/\u2424/g, '\n'); + + return this.token(src, true); +}; + +/** + * Lexing + */ + +Lexer.prototype.token = function(src, top) { + src = src.replace(/^ +$/gm, ''); + var next, + loose, + cap, + bull, + b, + item, + space, + i, + tag, + l, + isordered; + + while (src) { + // newline + if (cap = this.rules.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + this.tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + this.tokens.push({ + type: 'code', + text: !this.options.pedantic + ? cap.replace(/\n+$/, '') + : cap + }); + continue; + } + + // fences (gfm) + if (cap = this.rules.fences.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'code', + lang: cap[2], + text: cap[3] || '' + }); + continue; + } + + // heading + if (cap = this.rules.heading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // table no leading pipe (gfm) + if (top && (cap = this.rules.nptable.exec(src))) { + src = src.substring(cap[0].length); + + item = { + type: 'table', + header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3].replace(/\n$/, '').split('\n') + }; + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = splitCells(item.cells[i]); + } + + this.tokens.push(item); + + continue; + } + + // hr + if (cap = this.rules.hr.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = this.rules.blockquote.exec(src)) { + src = src.substring(cap[0].length); + + this.tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + this.token(cap, top); + + this.tokens.push({ + type: 'blockquote_end' + }); + + continue; + } + + // list + if (cap = this.rules.list.exec(src)) { + src = src.substring(cap[0].length); + bull = cap[2]; + isordered = bull.length > 1; + + this.tokens.push({ + type: 'list_start', + ordered: isordered, + start: isordered ? +bull : '' + }); + + // Get each top-level item. + cap = cap[0].match(this.rules.item); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = !this.options.pedantic + ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') + : item.replace(/^ {1,4}/gm, ''); + } + + // Determine whether the next list item belongs here. + // Backpedal if it does not belong in this list. + if (this.options.smartLists && i !== l - 1) { + b = block.bullet.exec(cap[i + 1])[0]; + if (bull !== b && !(bull.length > 1 && b.length > 1)) { + src = cap.slice(i + 1).join('\n') + src; + i = l - 1; + } + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item.charAt(item.length - 1) === '\n'; + if (!loose) loose = next; + } + + this.tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start' + }); + + // Recurse. + this.token(item, false); + + this.tokens.push({ + type: 'list_item_end' + }); + } + + this.tokens.push({ + type: 'list_end' + }); + + continue; + } + + + // dl + if (cap = this.rules.dl.exec(src)) { + // TODO + this.tokens.push({ + type: 'dl_start', + }); + this.tokens.push({ + type: 'dt_start', + }); + this.tokens.push({ + type: 'text', + text: cap[1] + }); + this.tokens.push({ + type: 'dt_end', + }); + + this.tokens.push({ + type: 'dd_start', + }); + this.tokens.push({ + type: 'text', + text: cap[2] + }); + this.tokens.push({ + type: 'dd_end', + }); + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'dl_end' + }); + continue; + } + + // html + if (cap = this.rules.html.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: this.options.sanitize + ? 'paragraph' + : 'html', + pre: !this.options.sanitizer + && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), + text: cap[0] + }); + continue; + } + + // def + if (top && (cap = this.rules.def.exec(src))) { + src = src.substring(cap[0].length); + if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); + tag = cap[1].toLowerCase().replace(/\s+/g, ' '); + if (!this.tokens.links[tag]) { + this.tokens.links[tag] = { + href: cap[2], + title: cap[3] + }; + } + continue; + } + + // table (gfm) + if (top && (cap = this.rules.table.exec(src))) { + src = src.substring(cap[0].length); + + item = { + type: 'table', + header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') + }; + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = splitCells( + item.cells[i].replace(/^ *\| *| *\| *$/g, '')); + } + + this.tokens.push(item); + + continue; + } + + // lheading + if (cap = this.rules.lheading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // top-level paragraph + if (top && (cap = this.rules.paragraph.exec(src))) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'paragraph', + text: cap[1].charAt(cap[1].length - 1) === '\n' + ? cap[1].slice(0, -1) + : cap[1] + }); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + + if (src) { + throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return this.tokens; +}; + +/** + * Inline-Level Grammar + */ + +var inline = { + escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, + autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, + url: noop, + tag: '^comment' + + '|^' // self-closing tag + + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag + + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. + + '|^' // declaration, e.g. + + '|^', // CDATA section + link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, + reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, + nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, + // fix *x* **x** one character em/strong formatters + // strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/, + // em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/, + strong: /^__[^\s_\*]__|^\*\*[^\s]\*\*|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/, + em: /^_[^\s_\*]_|^\*[^\s_\*]\*|^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/, + code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + del: noop, + text: /^[\s\S]+?(?=[\\?@\[\]\\^_`{|}~])/g; + +inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; +inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; +inline.autolink = edit(inline.autolink) + .replace('scheme', inline._scheme) + .replace('email', inline._email) + .getRegex(); + +inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; + +inline.tag = edit(inline.tag) + .replace('comment', block._comment) + .replace('attribute', inline._attribute) + .getRegex(); + +inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; +inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/; +inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; + +inline.link = edit(inline.link) + .replace('label', inline._label) + .replace('href', inline._href) + .replace('title', inline._title) + .getRegex(); + +inline.reflink = edit(inline.reflink) + .replace('label', inline._label) + .getRegex(); + +/** + * Normal Inline Grammar + */ + +inline.normal = merge({}, inline); + +/** + * Pedantic Inline Grammar + */ + +inline.pedantic = merge({}, inline.normal, { + strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, + em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, + link: edit(/^!?\[(label)\]\((.*?)\)/) + .replace('label', inline._label) + .getRegex(), + reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) + .replace('label', inline._label) + .getRegex() +}); + +/** + * GFM Inline Grammar + */ + +inline.gfm = merge({}, inline.normal, { + escape: edit(inline.escape).replace('])', '~|])').getRegex(), + url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) + .replace('email', inline._email) + .getRegex(), + _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, + del: /^~~(?=\S)([\s\S]*?\S)~~/, + text: edit(inline.text) + .replace(']|', '~]|') + .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') + .getRegex() +}); + +/** + * GFM + Line Breaks Inline Grammar + */ + +inline.breaks = merge({}, inline.gfm, { + br: edit(inline.br).replace('{2,}', '*').getRegex(), + text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() +}); + +/** + * Inline Lexer & Compiler + */ + +function InlineLexer(links, options) { + this.options = options || marked.defaults; + this.links = links; + this.rules = inline.normal; + this.renderer = this.options.renderer || new Renderer(); + this.renderer.options = this.options; + + if (!this.links) { + throw new Error('Tokens array requires a `links` property.'); + } + + if (this.options.pedantic) { + this.rules = inline.pedantic; + } else if (this.options.gfm) { + if (this.options.breaks) { + this.rules = inline.breaks; + } else { + this.rules = inline.gfm; + } + } +} + +/** + * Expose Inline Rules + */ + +InlineLexer.rules = inline; + +/** + * Static Lexing/Compiling Method + */ + +InlineLexer.output = function(src, links, options) { + var inline = new InlineLexer(links, options); + return inline.output(src); +}; + +/** + * Lexing/Compiling + */ + +InlineLexer.prototype.output = function(src) { + var out = '', + link, + text, + href, + title, + cap; + + while (src) { + // escape + if (cap = this.rules.escape.exec(src)) { + src = src.substring(cap[0].length); + out += cap[1]; + continue; + } + + // autolink + if (cap = this.rules.autolink.exec(src)) { + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = escape(this.mangle(cap[1])); + href = 'mailto:' + text; + } else { + text = escape(cap[1]); + href = text; + } + out += this.renderer.link(href, null, text); + continue; + } + + // url (gfm) + if (!this.inLink && (cap = this.rules.url.exec(src))) { + cap[0] = this.rules._backpedal.exec(cap[0])[0]; + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = escape(cap[0]); + href = 'mailto:' + text; + } else { + text = escape(cap[0]); + if (cap[1] === 'www.') { + href = 'http://' + text; + } else { + href = text; + } + } + out += this.renderer.link(href, null, text); + continue; + } + + // tag + if (cap = this.rules.tag.exec(src)) { + if (!this.inLink && /^/i.test(cap[0])) { + this.inLink = false; + } + src = src.substring(cap[0].length); + out += this.options.sanitize + ? this.options.sanitizer + ? this.options.sanitizer(cap[0]) + : escape(cap[0]) + : cap[0] + continue; + } + + // link + if (cap = this.rules.link.exec(src)) { + src = src.substring(cap[0].length); + this.inLink = true; + href = cap[2]; + if (this.options.pedantic) { + link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); + + if (link) { + href = link[1]; + title = link[3]; + } else { + title = ''; + } + } else { + title = cap[3] ? cap[3].slice(1, -1) : ''; + } + href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); + out += this.outputLink(cap, { + href: InlineLexer.escapes(href), + title: InlineLexer.escapes(title) + }); + this.inLink = false; + continue; + } + + // reflink, nolink + if ((cap = this.rules.reflink.exec(src)) + || (cap = this.rules.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = this.links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0].charAt(0); + src = cap[0].substring(1) + src; + continue; + } + this.inLink = true; + out += this.outputLink(cap, link); + this.inLink = false; + continue; + } + + // strong + if (cap = this.rules.strong.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); + continue; + } + + // em + if (cap = this.rules.em.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); + continue; + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.codespan(escape(cap[2].trim(), true)); + continue; + } + + // br + if (cap = this.rules.br.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.br(); + continue; + } + + // del (gfm) + if (cap = this.rules.del.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.del(this.output(cap[1])); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.text(escape(this.smartypants(cap[0]))); + continue; + } + + if (src) { + throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return out; +}; + +InlineLexer.escapes = function(text) { + return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; +} + +/** + * Compile Link + */ + +InlineLexer.prototype.outputLink = function(cap, link) { + var href = link.href, + title = link.title ? escape(link.title) : null; + + return cap[0].charAt(0) !== '!' + ? this.renderer.link(href, title, this.output(cap[1])) + : this.renderer.image(href, title, escape(cap[1])); +}; + +/** + * Smartypants Transformations + */ + +InlineLexer.prototype.smartypants = function(text) { + if (!this.options.smartypants) return text; + return text + // em-dashes + .replace(/---/g, '\u2014') + // en-dashes + .replace(/--/g, '\u2013') + // opening singles + .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') + // closing singles & apostrophes + .replace(/'/g, '\u2019') + // opening doubles + .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') + // closing doubles + .replace(/"/g, '\u201d') + // ellipses + .replace(/\.{3}/g, '\u2026'); +}; + +/** + * Mangle Links + */ + +InlineLexer.prototype.mangle = function(text) { + if (!this.options.mangle) return text; + var out = '', + l = text.length, + i = 0, + ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +}; + +/** + * Renderer + */ + +function Renderer(options) { + this.options = options || marked.defaults; +} + +Renderer.prototype.code = function(code, lang, escaped) { + if (this.options.highlight) { + var out = this.options.highlight(code, lang); + if (out != null && out !== code) { + escaped = true; + code = out; + } + } + + if (!lang) { + return '
'
+      + (escaped ? code : escape(code, true))
+      + '\n
'; + } + + return '
'
+    + (escaped ? code : escape(code, true))
+    + '\n
\n'; +}; + +Renderer.prototype.blockquote = function(quote) { + return '
\n' + quote + '
\n'; +}; + +Renderer.prototype.html = function(html) { + return html; +}; + +Renderer.prototype.heading = function(text, level, raw) { + if (this.options.headerIds) { + return '' + + text + + '\n'; + } + // ignore IDs + return '' + text + '\n'; +}; + +Renderer.prototype.hr = function() { + return this.options.xhtml ? '
\n' : '
\n'; +}; + +Renderer.prototype.list = function(body, ordered, start) { + var type = ordered ? 'ol' : 'ul', + startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; + return '<' + type + startatt + '>\n' + body + '\n'; +}; + +Renderer.prototype.listitem = function(text) { + return '
  • ' + text + '
  • \n'; +}; + +Renderer.prototype.dl = function(body) { + return '
    \n' + body + '
    \n'; +}; +Renderer.prototype.dt = function(body) { + return '
    ' + body + '
    \n'; +}; +Renderer.prototype.dd = function(body) { + return '
    ' + body + '
    \n'; +}; + +Renderer.prototype.paragraph = function(text) { + return '

    ' + text + '

    \n'; +}; + +Renderer.prototype.table = function(header, body) { + return '\n' + + '\n' + + header + + '\n' + + '\n' + + body + + '\n' + + '
    \n'; +}; + +Renderer.prototype.tablerow = function(content) { + return '\n' + content + '\n'; +}; + +Renderer.prototype.tablecell = function(content, flags) { + var type = flags.header ? 'th' : 'td'; + var tag = flags.align + ? '<' + type + ' style="text-align:' + flags.align + '">' + : '<' + type + '>'; + return tag + content + '\n'; +}; + +// span level renderer +Renderer.prototype.strong = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.em = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.codespan = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.br = function() { + return this.options.xhtml ? '
    ' : '
    '; +}; + +Renderer.prototype.del = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.link = function(href, title, text) { + if (this.options.sanitize) { + try { + var prot = decodeURIComponent(unescape(href)) + .replace(/[^\w:]/g, '') + .toLowerCase(); + } catch (e) { + return text; + } + if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { + return text; + } + } + if (this.options.baseUrl && !originIndependentUrl.test(href)) { + href = resolveUrl(this.options.baseUrl, href); + } + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch (e) { + return text; + } + var out = '
    '; + return out; +}; + +Renderer.prototype.image = function(href, title, text) { + if (this.options.baseUrl && !originIndependentUrl.test(href)) { + href = resolveUrl(this.options.baseUrl, href); + } + var out = '' + text + '' : '>'; + return out; +}; + +Renderer.prototype.text = function(text) { + return text; +}; + +/** + * TextRenderer + * returns only the textual part of the token + */ + +function TextRenderer() {} + +// no need for block level renderers + +TextRenderer.prototype.strong = +TextRenderer.prototype.em = +TextRenderer.prototype.codespan = +TextRenderer.prototype.del = +TextRenderer.prototype.text = function (text) { + return text; +} + +TextRenderer.prototype.link = +TextRenderer.prototype.image = function(href, title, text) { + return '' + text; +} + +TextRenderer.prototype.br = function() { + return ''; +} + +/** + * Parsing & Compiling + */ + +function Parser(options) { + this.tokens = []; + this.token = null; + this.options = options || marked.defaults; + this.options.renderer = this.options.renderer || new Renderer(); + this.renderer = this.options.renderer; + this.renderer.options = this.options; +} + +/** + * Static Parse Method + */ + +Parser.parse = function(src, options) { + var parser = new Parser(options); + return parser.parse(src); +}; + +/** + * Parse Loop + */ + +Parser.prototype.parse = function(src) { + this.inline = new InlineLexer(src.links, this.options); + // use an InlineLexer with a TextRenderer to extract pure text + this.inlineText = new InlineLexer( + src.links, + merge({}, this.options, {renderer: new TextRenderer()}) + ); + this.tokens = src.reverse(); + + var out = ''; + while (this.next()) { + out += this.tok(); + } + + return out; +}; + +/** + * Next Token + */ + +Parser.prototype.next = function() { + return this.token = this.tokens.pop(); +}; + +/** + * Preview Next Token + */ + +Parser.prototype.peek = function() { + return this.tokens[this.tokens.length - 1] || 0; +}; + +/** + * Parse Text Tokens + */ + +Parser.prototype.parseText = function() { + var body = this.token.text; + + while (this.peek().type === 'text') { + body += '\n' + this.next().text; + } + + return this.inline.output(body); +}; + +/** + * Parse Current Token + */ + +Parser.prototype.tok = function() { + switch (this.token.type) { + case 'space': { + return ''; + } + case 'hr': { + return this.renderer.hr(); + } + case 'heading': { + return this.renderer.heading( + this.inline.output(this.token.text), + this.token.depth, + unescape(this.inlineText.output(this.token.text))); + } + case 'code': { + return this.renderer.code(this.token.text, + this.token.lang, + this.token.escaped); + } + case 'table': { + var header = '', + body = '', + i, + row, + cell, + j; + + // header + cell = ''; + for (i = 0; i < this.token.header.length; i++) { + cell += this.renderer.tablecell( + this.inline.output(this.token.header[i]), + { header: true, align: this.token.align[i] } + ); + } + header += this.renderer.tablerow(cell); + + for (i = 0; i < this.token.cells.length; i++) { + row = this.token.cells[i]; + + cell = ''; + for (j = 0; j < row.length; j++) { + cell += this.renderer.tablecell( + this.inline.output(row[j]), + { header: false, align: this.token.align[j] } + ); + } + + body += this.renderer.tablerow(cell); + } + return this.renderer.table(header, body); + } + case 'blockquote_start': { + body = ''; + + while (this.next().type !== 'blockquote_end') { + body += this.tok(); + } + + return this.renderer.blockquote(body); + } + case 'list_start': { + body = ''; + var ordered = this.token.ordered, + start = this.token.start; + + while (this.next().type !== 'list_end') { + body += this.tok(); + } + + return this.renderer.list(body, ordered, start); + } + case 'list_item_start': { + body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.token.type === 'text' + ? this.parseText() + : this.tok(); + } + + return this.renderer.listitem(body); + } + case 'loose_item_start': { + body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.tok(); + } + + return this.renderer.listitem(body); + } + case 'dl_start': { + body = ''; + while (this.next().type !== 'dl_end') { + body += this.tok(); + } + return this.renderer.dl(body); + } + case 'dt_start': { + body = ''; + while (this.next().type !== 'dt_end') { + body += this.parseText(); + } + return this.renderer.dt(body); + } + case 'dd_start': { + body = ''; + while (this.next().type !== 'dd_end') { + body += this.parseText(); + } + return this.renderer.dd(body); + } + case 'html': { + // TODO parse inline content if parameter markdown=1 + return this.renderer.html(this.token.text); + } + case 'paragraph': { + return this.renderer.paragraph(this.inline.output(this.token.text)); + } + case 'text': { + return this.renderer.paragraph(this.parseText()); + } + } +}; + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function unescape(html) { + // explicitly match decimal, hex, and named HTML entities + return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { + n = n.toLowerCase(); + if (n === 'colon') return ':'; + if (n.charAt(0) === '#') { + return n.charAt(1) === 'x' + ? String.fromCharCode(parseInt(n.substring(2), 16)) + : String.fromCharCode(+n.substring(1)); + } + return ''; + }); +} + +function edit(regex, opt) { + regex = regex.source || regex; + opt = opt || ''; + return { + replace: function(name, val) { + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return this; + }, + getRegex: function() { + return new RegExp(regex, opt); + } + }; +} + +function resolveUrl(base, href) { + if (!baseUrls[' ' + base]) { + // we can ignore everything in base after the last slash of its path component, + // but we might need to add _that_ + // https://tools.ietf.org/html/rfc3986#section-3 + if (/^[^:]+:\/*[^/]*$/.test(base)) { + baseUrls[' ' + base] = base + '/'; + } else { + baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); + } + } + base = baseUrls[' ' + base]; + + if (href.slice(0, 2) === '//') { + return base.replace(/:[\s\S]*/, ':') + href; + } else if (href.charAt(0) === '/') { + return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; + } else { + return base + href; + } +} +var baseUrls = {}; +var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; + +function noop() {} +noop.exec = noop; + +function merge(obj) { + var i = 1, + target, + key; + + for (; i < arguments.length; i++) { + target = arguments[i]; + for (key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + obj[key] = target[key]; + } + } + } + + return obj; +} + +function splitCells(tableRow) { + var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */), + i = 0; + + for (; i < cells.length; i++) { + cells[i] = cells[i].replace(/\\\|/g, '|'); + } + return cells; +} + +/** + * Marked + */ + +function marked(src, opt, callback) { + // throw error in case of non string input + if (typeof src === 'undefined' || src === null) { + throw new Error('marked(): input parameter is undefined or null'); + } + if (typeof src !== 'string') { + throw new Error('marked(): input parameter is of type ' + + Object.prototype.toString.call(src) + ', string expected'); + } + + if (callback || typeof opt === 'function') { + if (!callback) { + callback = opt; + opt = null; + } + + opt = merge({}, marked.defaults, opt || {}); + + var highlight = opt.highlight, + tokens, + pending, + i = 0; + + try { + tokens = Lexer.lex(src, opt) + } catch (e) { + return callback(e); + } + + pending = tokens.length; + + var done = function(err) { + if (err) { + opt.highlight = highlight; + return callback(err); + } + + var out; + + try { + out = Parser.parse(tokens, opt); + } catch (e) { + err = e; + } + + opt.highlight = highlight; + + return err + ? callback(err) + : callback(null, out); + }; + + if (!highlight || highlight.length < 3) { + return done(); + } + + delete opt.highlight; + + if (!pending) return done(); + + for (; i < tokens.length; i++) { + (function(token) { + if (token.type !== 'code') { + return --pending || done(); + } + return highlight(token.text, token.lang, function(err, code) { + if (err) return done(err); + if (code == null || code === token.text) { + return --pending || done(); + } + token.text = code; + token.escaped = true; + --pending || done(); + }); + })(tokens[i]); + } + + return; + } + try { + if (opt) opt = merge({}, marked.defaults, opt); + return Parser.parse(Lexer.lex(src, opt), opt); + } catch (e) { + e.message += '\nPlease report this to https://github.com/markedjs/marked.'; + if ((opt || marked.defaults).silent) { + return '

    An error occurred:

    '
    +        + escape(e.message + '', true)
    +        + '
    '; + } + throw e; + } +} + +/** + * Options + */ + +marked.options = +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; + +marked.getDefaults = function () { + return { + baseUrl: null, + breaks: false, + gfm: true, + headerIds: true, + headerPrefix: '', + highlight: null, + langPrefix: 'lang-', + mangle: true, + pedantic: false, + renderer: new Renderer(), + sanitize: false, + sanitizer: null, + silent: false, + smartLists: false, + smartypants: false, + tables: true, + xhtml: false + }; +} + +marked.defaults = marked.getDefaults(); + +/** + * Expose + */ + +marked.Parser = Parser; +marked.parser = Parser.parse; + +marked.Renderer = Renderer; +marked.TextRenderer = TextRenderer; + +marked.Lexer = Lexer; +marked.lexer = Lexer.lex; + +marked.InlineLexer = InlineLexer; +marked.inlineLexer = InlineLexer.output; + +marked.parse = marked; +return marked; + +} +}; +BundleModuleCode['doc/colors']=function (module,exports){ +/* + +The MIT License (MIT) + +Original Library + - Copyright (c) Marak Squires + +Additional functionality + - Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. + +*/ + +var colors = {}; +module['exports'] = colors; + +colors.themes = {}; + +var ansiStyles = colors.styles = Require('doc/styles'); +var defineProps = Object.defineProperties; + +colors.supportsColor = Require('doc/system/supports-colors').supportsColor; + +if (typeof colors.enabled === "undefined") { + colors.enabled = colors.supportsColor() !== false; +} + +colors.stripColors = colors.strip = function(str){ + return ("" + str).replace(/\x1B\[\d+m/g, ''); +}; + + +var stylize = colors.stylize = function stylize (str, style) { + if (!colors.enabled) { + return str+''; + } + + return ansiStyles[style].open + str + ansiStyles[style].close; +} + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; +var escapeStringRegexp = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + return str.replace(matchOperatorsRe, '\\$&'); +} + +function build(_styles) { + var builder = function builder() { + return applyStyle.apply(builder, arguments); + }; + builder._styles = _styles; + // __proto__ is used because we must return a function, but there is + // no way to create a function with a different prototype. + builder.__proto__ = proto; + return builder; +} + +var styles = (function () { + var ret = {}; + ansiStyles.grey = ansiStyles.gray; + Object.keys(ansiStyles).forEach(function (key) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + ret[key] = { + get: function () { + return build(this._styles.concat(key)); + } + }; + }); + return ret; +})(); + +var proto = defineProps(function colors() {}, styles); + +function applyStyle() { + var args = arguments; + var argsLen = args.length; + var str = argsLen !== 0 && String(arguments[0]); + if (argsLen > 1) { + for (var a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } + + if (!colors.enabled || !str) { + return str; + } + + var nestedStyles = this._styles; + + var i = nestedStyles.length; + while (i--) { + var code = ansiStyles[nestedStyles[i]]; + str = code.open + str.replace(code.closeRe, code.open) + code.close; + } + + return str; +} + +colors.setTheme = function (theme) { + if (typeof theme === 'string') { + console.log('colors.setTheme now only accepts an object, not a string. ' + + 'If you are trying to set a theme from a file, it is now your (the caller\'s) responsibility to require the file. ' + + 'The old syntax looked like colors.setTheme(__dirname + \'/../themes/generic-logging.js\'); ' + + 'The new syntax looks like colors.setTheme(require(__dirname + \'/../themes/generic-logging.js\'));'); + return; + } + for (var style in theme) { + (function(style){ + colors[style] = function(str){ + if (typeof theme[style] === 'object'){ + var out = str; + for (var i in theme[style]){ + out = colors[theme[style][i]](out); + } + return out; + } + return colors[theme[style]](str); + }; + })(style) + } +} + +function init() { + var ret = {}; + Object.keys(styles).forEach(function (name) { + ret[name] = { + get: function () { + return build([name]); + } + }; + }); + return ret; +} + +var sequencer = function sequencer (map, str) { + var exploded = str.split(""), i = 0; + exploded = exploded.map(map); + return exploded.join(""); +}; + +// custom formatter methods +colors.trap = Require('doc/custom/trap'); +colors.zalgo = Require('doc/custom/zalgo'); + +// maps +colors.maps = {}; +colors.maps.america = (function() { + return function (letter, i, exploded) { + if(letter === " ") return letter; + switch(i%3) { + case 0: return colors.red(letter); + case 1: return colors.white(letter) + case 2: return colors.blue(letter) + } + } +})(); + +colors.maps.zebra = function (letter, i, exploded) { + return i % 2 === 0 ? letter : colors.inverse(letter); +} +colors.maps.rainbow = (function () { + var rainbowColors = ['red', 'yellow', 'green', 'blue', 'magenta']; //RoY G BiV + return function (letter, i, exploded) { + if (letter === " ") { + return letter; + } else { + return colors[rainbowColors[i++ % rainbowColors.length]](letter); + } + }; +})(); +colors.maps.random = (function () { + var available = ['underline', 'inverse', 'grey', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta']; + return function(letter, i, exploded) { + return letter === " " ? letter : colors[available[Math.round(Math.random() * (available.length - 1))]](letter); + }; +})(); + +for (var map in colors.maps) { + (function(map){ + colors[map] = function (str) { + return sequencer(colors.maps[map], str); + } + })(map) +} + +defineProps(colors, init()); +}; +BundleModuleCode['doc/styles']=function (module,exports){ +/* +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. + +*/ + +var styles = {}; +module['exports'] = styles; + +var codes = { + reset: [0, 0], + + bold: [1, 22], + dim: [2, 22], + italic: [3, 23], + underline: [4, 24], + inverse: [7, 27], + hidden: [8, 28], + strikethrough: [9, 29], + + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + blue: [34, 39], + magenta: [35, 39], + cyan: [36, 39], + white: [37, 39], + gray: [90, 39], + grey: [90, 39], + + bgBlack: [40, 49], + bgRed: [41, 49], + bgGreen: [42, 49], + bgYellow: [43, 49], + bgBlue: [44, 49], + bgMagenta: [45, 49], + bgCyan: [46, 49], + bgWhite: [47, 49], + + // legacy styles for colors pre v1.0.0 + blackBG: [40, 49], + redBG: [41, 49], + greenBG: [42, 49], + yellowBG: [43, 49], + blueBG: [44, 49], + magentaBG: [45, 49], + cyanBG: [46, 49], + whiteBG: [47, 49] + +}; + +Object.keys(codes).forEach(function (key) { + var val = codes[key]; + var style = styles[key] = []; + style.open = '\u001b[' + val[0] + 'm'; + style.close = '\u001b[' + val[1] + 'm'; +});}; +BundleModuleCode['doc/system/supports-colors']=function (module,exports){ +/* +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. + +*/ + +'use strict'; + +var os = Require('os'); +var hasFlag = Require('doc/system/has-flag.js'); + +var env = process.env; + +var forceColor = void 0; +if (hasFlag('no-color') || hasFlag('no-colors') || hasFlag('color=false')) { + forceColor = false; +} else if (hasFlag('color') || hasFlag('colors') || hasFlag('color=true') || hasFlag('color=always')) { + forceColor = true; +} +if ('FORCE_COLOR' in env) { + forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; +} + +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level: level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} + +function supportsColor(stream) { + if (forceColor === false) { + return 0; + } + + if (hasFlag('color=16m') || hasFlag('color=full') || hasFlag('color=truecolor')) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + + if (stream && !stream.isTTY && forceColor !== true) { + return 0; + } + + var min = forceColor ? 1 : 0; + + if (process.platform === 'win32') { + // Node.js 7.5.0 is the first version of Node.js to include a patch to + // libuv that enables 256 color output on Windows. Anything earlier and it + // won't work. However, here we target Node.js 8 at minimum as it is an LTS + // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows + // release that supports 256 colors. Windows 10 build 14931 is the first release + // that supports 16m/TrueColor. + var osRelease = os.release().split('.'); + if (Number(process.versions.node.split('.')[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(function (sign) { + return sign in env; + }) || env.CI_NAME === 'codeship') { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return (/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0 + ); + } + + if ('TERM_PROGRAM' in env) { + var version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Hyper': + return 3; + case 'Apple_Terminal': + return 2; + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + if (env.TERM === 'dumb') { + return min; + } + + return min; +} + +function getSupportLevel(stream) { + var level = supportsColor(stream); + return translateLevel(level); +} + +module.exports = { + supportsColor: getSupportLevel, + stdout: getSupportLevel(process.stdout), + stderr: getSupportLevel(process.stderr) +}; +}; +BundleModuleCode['doc/system/has-flag.js']=function (module,exports){ +/* +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. +*/ + +'use strict'; + +module.exports = function (flag, argv) { + argv = argv || process.argv; + + var terminatorPos = argv.indexOf('--'); + var prefix = /^-{1,2}/.test(flag) ? '' : '--'; + var pos = argv.indexOf(prefix + flag); + + return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos); +}; +}; +BundleModuleCode['doc/custom/trap']=function (module,exports){ +module['exports'] = function runTheTrap (text, options) { + var result = ""; + text = text || "Run the trap, drop the bass"; + text = text.split(''); + var trap = { + a: ["\u0040", "\u0104", "\u023a", "\u0245", "\u0394", "\u039b", "\u0414"], + b: ["\u00df", "\u0181", "\u0243", "\u026e", "\u03b2", "\u0e3f"], + c: ["\u00a9", "\u023b", "\u03fe"], + d: ["\u00d0", "\u018a", "\u0500" , "\u0501" ,"\u0502", "\u0503"], + e: ["\u00cb", "\u0115", "\u018e", "\u0258", "\u03a3", "\u03be", "\u04bc", "\u0a6c"], + f: ["\u04fa"], + g: ["\u0262"], + h: ["\u0126", "\u0195", "\u04a2", "\u04ba", "\u04c7", "\u050a"], + i: ["\u0f0f"], + j: ["\u0134"], + k: ["\u0138", "\u04a0", "\u04c3", "\u051e"], + l: ["\u0139"], + m: ["\u028d", "\u04cd", "\u04ce", "\u0520", "\u0521", "\u0d69"], + n: ["\u00d1", "\u014b", "\u019d", "\u0376", "\u03a0", "\u048a"], + o: ["\u00d8", "\u00f5", "\u00f8", "\u01fe", "\u0298", "\u047a", "\u05dd", "\u06dd", "\u0e4f"], + p: ["\u01f7", "\u048e"], + q: ["\u09cd"], + r: ["\u00ae", "\u01a6", "\u0210", "\u024c", "\u0280", "\u042f"], + s: ["\u00a7", "\u03de", "\u03df", "\u03e8"], + t: ["\u0141", "\u0166", "\u0373"], + u: ["\u01b1", "\u054d"], + v: ["\u05d8"], + w: ["\u0428", "\u0460", "\u047c", "\u0d70"], + x: ["\u04b2", "\u04fe", "\u04fc", "\u04fd"], + y: ["\u00a5", "\u04b0", "\u04cb"], + z: ["\u01b5", "\u0240"] + } + text.forEach(function(c){ + c = c.toLowerCase(); + var chars = trap[c] || [" "]; + var rand = Math.floor(Math.random() * chars.length); + if (typeof trap[c] !== "undefined") { + result += trap[c][rand]; + } else { + result += c; + } + }); + return result; + +} +}; +BundleModuleCode['doc/custom/zalgo']=function (module,exports){ +// please no +module['exports'] = function zalgo(text, options) { + text = text || " he is here "; + var soul = { + "up" : [ + '̍', '̎', '̄', '̅', + '̿', '̑', '̆', '̐', + '͒', '͗', '͑', '̇', + '̈', '̊', '͂', '̓', + '̈', '͊', '͋', '͌', + '̃', '̂', '̌', '͐', + '̀', '́', '̋', '̏', + '̒', '̓', '̔', '̽', + '̉', 'ͣ', 'ͤ', 'ͥ', + 'ͦ', 'ͧ', 'ͨ', 'ͩ', + 'ͪ', 'ͫ', 'ͬ', 'ͭ', + 'ͮ', 'ͯ', '̾', '͛', + '͆', '̚' + ], + "down" : [ + '̖', '̗', '̘', '̙', + '̜', '̝', '̞', '̟', + '̠', '̤', '̥', '̦', + '̩', '̪', '̫', '̬', + '̭', '̮', '̯', '̰', + '̱', '̲', '̳', '̹', + '̺', '̻', '̼', 'ͅ', + '͇', '͈', '͉', '͍', + '͎', '͓', '͔', '͕', + '͖', '͙', '͚', '̣' + ], + "mid" : [ + '̕', '̛', '̀', '́', + '͘', '̡', '̢', '̧', + '̨', '̴', '̵', '̶', + '͜', '͝', '͞', + '͟', '͠', '͢', '̸', + '̷', '͡', ' ҉' + ] + }, + all = [].concat(soul.up, soul.down, soul.mid), + zalgo = {}; + + function randomNumber(range) { + var r = Math.floor(Math.random() * range); + return r; + } + + function is_char(character) { + var bool = false; + all.filter(function (i) { + bool = (i === character); + }); + return bool; + } + + + function heComes(text, options) { + var result = '', counts, l; + options = options || {}; + options["up"] = typeof options["up"] !== 'undefined' ? options["up"] : true; + options["mid"] = typeof options["mid"] !== 'undefined' ? options["mid"] : true; + options["down"] = typeof options["down"] !== 'undefined' ? options["down"] : true; + options["size"] = typeof options["size"] !== 'undefined' ? options["size"] : "maxi"; + text = text.split(''); + for (l in text) { + if (is_char(l)) { + continue; + } + result = result + text[l]; + counts = {"up" : 0, "down" : 0, "mid" : 0}; + switch (options.size) { + case 'mini': + counts.up = randomNumber(8); + counts.mid = randomNumber(2); + counts.down = randomNumber(8); + break; + case 'maxi': + counts.up = randomNumber(16) + 3; + counts.mid = randomNumber(4) + 1; + counts.down = randomNumber(64) + 3; + break; + default: + counts.up = randomNumber(8) + 1; + counts.mid = randomNumber(6) / 2; + counts.down = randomNumber(8) + 1; + break; + } + + var arr = ["up", "mid", "down"]; + for (var d in arr) { + var index = arr[d]; + for (var i = 0 ; i <= counts[index]; i++) { + if (options[index]) { + result = result + soul[index][randomNumber(soul[index].length)]; + } + } + } + } + return result; + } + // don't summon him + return heComes(text, options); +} +}; +BundleModuleCode['doc/list']=function (module,exports){ +var NL='\n',SP=' '; +function spaces(n) {var s=''; while(n) s+=SP,n--; return s;} + +/** List Constructor + * typeof @options = { + * type:string=' '|'*'|'-'|'+'|'1'|'2'|..|'a'|'b'|..|'dl', + * } + */ +function List (options) { + this.type = options.type|| '*'; + this.margin = options.margin || {left:0,right:0,top:0,bottom:0}; + this.width = options.width || 80; + this.tab = options.tab || 2; + this.tab2 = options.tab || 4; +} +/** + * Inherit from Array. Each item of the list is one array element. + */ + +List.prototype.__proto__ = Array.prototype; + +/** List formatter + * + */ +List.prototype.render +List.prototype.toString = function (){ + var i,self=this,ret='',line='',lines=[],label, + tokens, + textwidth=this.width-this.margin.left-this.margin.right-this.tab; + for(i=0;i max_height) { max_height = height }; + + cells.push({ contents: contents , height: height }); + }); + + // transform vertical cells into horizontal lines + var lines = new Array(max_height); + cells.forEach(function (cell, i) { + cell.contents.forEach(function (line, j) { + if (!lines[j]) { lines[j] = [] }; + if (style || (first_cell_head && i === 0 && options.style.head)) { + line = applyStyles(options.style.head, line) + } + + lines[j].push(line); + }); + + // populate empty lines in cell + for (var j = cell.height, l = max_height; j < l; j++) { + if (!lines[j]) { lines[j] = [] }; + lines[j].push(string('', i)); + } + }); + var ret = ""; + lines.forEach(function (line, index) { + if (ret.length > 0) { + ret += "\n" + applyStyles(options.style.border, chars.left); + } + + ret += line.join(applyStyles(options.style.border, chars.middle)) + applyStyles(options.style.border, chars.right); + }); + + return applyStyles(options.style.border, chars.left) + ret; + }; + + function applyStyles(styles, subject) { + if (!subject) + return ''; + styles.forEach(function(style) { + subject = colors[style](subject); + }); + return subject; + }; + + // renders a string, by padding it or truncating it + function string (str, index){ + var str = String(typeof str == 'object' && str.text ? str.text : str) + , length = utils.strlen(str) + , width = colWidths[index] + - (style['padding-left'] || 0) + - (style['padding-right'] || 0) + , align = options.colAligns[index] || 'left'; + + return repeat(' ', style['padding-left'] || 0) + + (length == width ? str : + (length < width + ? pad(str, ( width + (str.length - length) ), ' ', align == 'left' ? 'right' : + (align == 'middle' ? 'both' : 'left')) + : (truncater ? truncate(str, width, truncater) : str)) + ) + + repeat(' ', style['padding-right'] || 0); + }; + + if (head.length){ + lineTop(); + + ret += generateRow(head, style.head) + "\n" + } + + if (this.length) + this.forEach(function (cells, i){ + if (!head.length && i == 0) + lineTop(); + else { + if (!style.compact || i<(!!head.length) ?1:0 || cells.length == 0){ + var l = line(chars.mid + , chars['left-mid'] + , chars['right-mid'] + , chars['mid-mid']); + if (l) + ret += l + "\n" + } + } + + if (cells.hasOwnProperty("length") && !cells.length) { + return + } else { + ret += generateRow(cells) + "\n"; + }; + }); + + var l = line(chars.bottom + , chars['bottom-left'] || chars.bottom + , chars['bottom-right'] || chars.bottom + , chars['bottom-mid']); + if (l) + ret += l; + else + // trim the last '\n' if we didn't add the bottom decoration + ret = ret.slice(0, -1); + + return ret; +}; + +/** + * Module exports. + */ + +module.exports = Table; + +module.exports.version = '0.0.1'; +}; +BundleModuleCode['doc/cli-utils']=function (module,exports){ + +/** + * Repeats a string. + * + * @param {String} char(s) + * @param {Number} number of times + * @return {String} repeated string + */ + +exports.repeat = function (str, times){ + return Array(times + 1).join(str); +}; + +/** + * Pads a string + * + * @api public + */ + +exports.pad = function (str, len, pad, dir) { + if (len + 1 >= str.length) + switch (dir){ + case 'left': + str = Array(len + 1 - str.length).join(pad) + str; + break; + + case 'both': + var right = Math.ceil((padlen = len - str.length) / 2); + var left = padlen - right; + str = Array(left + 1).join(pad) + str + Array(right + 1).join(pad); + break; + + default: + str = str + Array(len + 1 - str.length).join(pad); + }; + + return str; +}; + +/** + * Truncates a string + * + * @api public + */ + +exports.truncate = function (str, length, chr){ + chr = chr || '…'; + return str.length >= length ? str.substr(0, length - chr.length) + chr : str; +}; + +/** + * Copies and merges options with defaults. + * + * @param {Object} defaults + * @param {Object} supplied options + * @return {Object} new (merged) object + */ + +function options(defaults, opts) { + for (var p in opts) { + if (opts[p] && opts[p].constructor && opts[p].constructor === Object) { + defaults[p] = defaults[p] || {}; + options(defaults[p], opts[p]); + } else { + defaults[p] = opts[p]; + } + } + return defaults; +}; +exports.options = options; + +// +// For consideration of terminal "color" programs like colors.js, +// which can add ANSI escape color codes to strings, +// we destyle the ANSI color escape codes for padding calculations. +// +// see: http://en.wikipedia.org/wiki/ANSI_escape_code +// +exports.strlen = function(str){ + var code = /\u001b\[(?:\d*;){0,5}\d*m/g; + var stripped = ("" + (str != null ? str : '')).replace(code,''); + var split = stripped.split("\n"); + return split.reduce(function (memo, s) { return (s.length > memo) ? s.length : memo }, 0); +} +}; +BundleModuleCode['doc/table']=function (module,exports){ +var doc = Require('doc/doc'); +var com = Require('com/compat'); + +function Table (data,options) { + var totalWidth=(process.stdout.columns)-2; + if (com.obj.isArray(options)) options={head:options}; + options=options||{}; + var head=options.head,table; + if (com.obj.isMatrix(data)) { + } else if (com.obj.isArray(data) && com.obj.isObject(data[0])) { + options.head=true; + head=Object.keys(data[0]); + data=data.map(function (row) { + return head.map(function (key) { return row[key] }) + }); + } else return new Error('Table: Inavlid data'); + if (!options.colWidths) { + totalWidth-= ((head||data[0]).length-1); + options.colWidths=(head||data[0]).map(function (x,i) { + return Math.max(4,Math.floor(totalWidth/(head||data[0]).length)); + }); + } + if (head) + table = new doc.Table({ + head : head, + colWidths :options.colWidths, + }); + else + table = new doc.Table({ + colWidths : options.colWidths, + }); + data.forEach(function (row,rowi) { + table.push(row); + }); + print(table.toString()); +} + +module.exports = Table; +}; +BundleModuleCode['com/readline']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Joyent, Inc. and other Node contributors, Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $VERSION: 1.2.5 + ** + ** $INFO: + ** + +// Inspiration for this code comes from Salvatore Sanfilippo's linenoise. +// https://github.com/antirez/linenoise +// Reference: +// * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html +// * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + +*/ + +var kHistorySize = 30; + +var util = Require('util'); +var Buffer = Require('buffer').Buffer; +var inherits = Require('util').inherits; +var EventEmitter = Require('events').EventEmitter; +var StringDecoder = Require('string_decoder').StringDecoder; + +// listenerCount isn't in node 0.10, so here's a basic polyfill +EventEmitter._listenerCount = EventEmitter._listenerCount || function (ee, event) { + var listeners = ee && ee._events && ee._events[event] + if (Array.isArray(listeners)) { + return listeners.length + } else if (typeof listeners === 'function') { + return 1 + } else { + return 0 + } +} + +exports.createInterface = function(input, output, completer, terminal) { + var rl; + if (arguments.length === 1) { + rl = new Interface(input); + } else { + rl = new Interface(input, output, completer, terminal); + } + return rl; +}; + + +function Interface(input, output, completer, terminal) { + if (!(this instanceof Interface)) { + return new Interface(input, output, completer, terminal); + } + + this._sawReturn = false; + + EventEmitter.call(this); + + if (arguments.length === 1) { + // an options object was given + output = input.output; + completer = input.completer; + terminal = input.terminal; + input = input.input; + } + + completer = completer || function() { return []; }; + + if (!util.isFunction(completer)) { + throw new TypeError('Argument \'completer\' must be a function'); + } + + // backwards compat; check the isTTY prop of the output stream + // when `terminal` was not specified + if (util.isUndefined(terminal) && !util.isNullOrUndefined(output)) { + terminal = !!output.isTTY; + } + + var self = this; + + this.output = output; + this.input = input; + + // Check arity, 2 - for async, 1 for sync + this.completer = completer.length === 2 ? completer : function(v, callback) { + callback(null, completer(v)); + }; + + this.setPrompt('> '); + + this.terminal = !!terminal; + + function ondata(data) { + self._normalWrite(data); + } + + function onend() { + if (util.isString(self._line_buffer) && self._line_buffer.length > 0) { + self.emit('line', self._line_buffer); + } + self.close(); + } + + function ontermend() { + if (util.isString(self.line) && self.line.length > 0) { + self.emit('line', self.line); + } + self.close(); + } + + function onkeypress(s, key) { + self._ttyWrite(s, key); + } + + function onresize() { + self._refreshLine(); + } + + if (!this.terminal) { + input.on('data', ondata); + input.on('end', onend); + self.once('close', function() { + input.removeListener('data', ondata); + input.removeListener('end', onend); + }); + this._decoder = new StringDecoder('utf8'); + + } else { + + exports.emitKeypressEvents(input); + + // input usually refers to stdin + input.on('keypress', onkeypress); + input.on('end', ontermend); + + // Current line + this.line = ''; + + this._setRawMode(true); + this.terminal = true; + + // Cursor position on the line. + this.cursor = 0; + + this.history = []; + this.historyIndex = -1; + + if (!util.isNullOrUndefined(output)) + output.on('resize', onresize); + + self.once('close', function() { + input.removeListener('keypress', onkeypress); + input.removeListener('end', ontermend); + if (!util.isNullOrUndefined(output)) { + output.removeListener('resize', onresize); + } + }); + } + + input.resume(); +} + +inherits(Interface, EventEmitter); + +Object.defineProperty(Interface.prototype,'columns',{ + get: function() { + var columns = Infinity; + if (this.output && this.output.columns) + columns = this.output.columns; + return columns; + } +}); +/* Depricated +Interface.prototype.__defineGetter__('columns', function() { + var columns = Infinity; + if (this.output && this.output.columns) + columns = this.output.columns; + return columns; +}); +*/ + +Interface.prototype.setPrompt = function(prompt) { + this._prompt = prompt; +}; + + +Interface.prototype._setRawMode = function(mode) { + if (util.isFunction(this.input.setRawMode)) { + return this.input.setRawMode(mode); + } +}; + + +Interface.prototype.prompt = function(preserveCursor) { + if (this.paused) this.resume(); + if (this.terminal) { + if (!preserveCursor) this.cursor = 0; + this._refreshLine(); + } else { + this._writeToOutput(this._prompt); + } +}; + + +Interface.prototype.question = function(query, cb) { + if (util.isFunction(cb)) { + if (this._questionCallback) { + this.prompt(); + } else { + this._oldPrompt = this._prompt; + this.setPrompt(query); + this._questionCallback = cb; + this.prompt(); + } + } +}; + + +Interface.prototype._onLine = function(line) { + if (this._questionCallback) { + var cb = this._questionCallback; + this._questionCallback = null; + this.setPrompt(this._oldPrompt); + cb(line); + } else { + this.emit('line', line); + } +}; + +Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) { + if (!util.isString(stringToWrite)) + throw new TypeError('stringToWrite must be a string'); + + if (!util.isNullOrUndefined(this.output)) + this.output.write(stringToWrite); +}; + +Interface.prototype._addHistory = function() { + if (this.line.length === 0) return ''; + + if (this.history.length === 0 || this.history[0] !== this.line) { + this.history.unshift(this.line); + + // Only store so many + if (this.history.length > kHistorySize) this.history.pop(); + } + + this.historyIndex = -1; + return this.history[0]; +}; + + +Interface.prototype._refreshLine = function() { + // line length + var line = this._prompt + this.line; + var dispPos = this._getDisplayPos(line); + var lineCols = dispPos.cols; + var lineRows = dispPos.rows; + + // cursor position + var cursorPos = this._getCursorPos(); + + // first move to the bottom of the current line, based on cursor pos + var prevRows = this.prevRows || 0; + if (prevRows > 0) { + exports.moveCursor(this.output, 0, -prevRows); + } + + // Cursor to left edge. + exports.cursorTo(this.output, 0); + // erase data + exports.clearScreenDown(this.output); + + // Write the prompt and the current buffer content. + this._writeToOutput(line); + + // Force terminal to allocate a new line + if (lineCols === 0) { + this._writeToOutput(' '); + } + + // Move cursor to original position. + exports.cursorTo(this.output, cursorPos.cols); + + var diff = lineRows - cursorPos.rows; + if (diff > 0) { + exports.moveCursor(this.output, 0, -diff); + } + + this.prevRows = cursorPos.rows; +}; + + +Interface.prototype.close = function() { + if (this.closed) return; + this.pause(); + if (this.terminal) { + this._setRawMode(false); + } + this.closed = true; + this.emit('close'); +}; + + +Interface.prototype.pause = function() { + if (this.paused) return; + this.input.pause(); + this.paused = true; + this.emit('pause'); + return this; +}; + + +Interface.prototype.resume = function() { + if (!this.paused) return; + this.input.resume(); + this.paused = false; + this.emit('resume'); + return this; +}; + + +Interface.prototype.write = function(d, key) { + if (this.paused) this.resume(); + this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d); +}; + +// \r\n, \n, or \r followed by something other than \n +var lineEnding = /\r?\n|\r(?!\n)/; +Interface.prototype._normalWrite = function(b) { + if (util.isUndefined(b)) { + return; + } + var string = this._decoder.write(b); + if (this._sawReturn) { + string = string.replace(/^\n/, ''); + this._sawReturn = false; + } + + // Run test() on the new string chunk, not on the entire line buffer. + var newPartContainsEnding = lineEnding.test(string); + + if (this._line_buffer) { + string = this._line_buffer + string; + this._line_buffer = null; + } + if (newPartContainsEnding) { + this._sawReturn = /\r$/.test(string); + + // got one or more newlines; process into "line" events + var lines = string.split(lineEnding); + // either '' or (concievably) the unfinished portion of the next line + string = lines.pop(); + this._line_buffer = string; + lines.forEach(function(line) { + this._onLine(line); + }, this); + } else if (string) { + // no newlines this time, save what we have for next time + this._line_buffer = string; + } +}; + +Interface.prototype._insertString = function(c) { + //BUG: Problem when adding tabs with following content. + // Perhaps the bug is in _refreshLine(). Not sure. + // A hack would be to insert spaces instead of literal '\t'. + if (this.cursor < this.line.length) { + var beg = this.line.slice(0, this.cursor); + var end = this.line.slice(this.cursor, this.line.length); + this.line = beg + c + end; + this.cursor += c.length; + this._refreshLine(); + } else { + this.line += c; + this.cursor += c.length; + + if (this._getCursorPos().cols === 0) { + this._refreshLine(); + } else { + this._writeToOutput(c); + } + + // a hack to get the line refreshed if it's needed + this._moveCursor(0); + } +}; + +Interface.prototype._tabComplete = function() { + var self = this; + + self.pause(); + self.completer(self.line.slice(0, self.cursor), function(err, rv) { + self.resume(); + + if (err) { + // XXX Log it somewhere? + return; + } + + var completions = rv[0], + completeOn = rv[1]; // the text that was completed + if (completions && completions.length) { + // Apply/show completions. + if (completions.length === 1) { + self._insertString(completions[0].slice(completeOn.length)); + } else { + self._writeToOutput('\r\n'); + var width = completions.reduce(function(a, b) { + return a.length > b.length ? a : b; + }).length + 2; // 2 space padding + var maxColumns = Math.floor(self.columns / width) || 1; + var group = [], c; + for (var i = 0, compLen = completions.length; i < compLen; i++) { + c = completions[i]; + if (c === '') { + handleGroup(self, group, width, maxColumns); + group = []; + } else { + group.push(c); + } + } + handleGroup(self, group, width, maxColumns); + + // If there is a common prefix to all matches, then apply that + // portion. + var f = completions.filter(function(e) { if (e) return e; }); + var prefix = commonPrefix(f); + if (prefix.length > completeOn.length) { + self._insertString(prefix.slice(completeOn.length)); + } + + } + self._refreshLine(); + } + }); +}; + +// this = Interface instance +function handleGroup(self, group, width, maxColumns) { + if (group.length == 0) { + return; + } + var minRows = Math.ceil(group.length / maxColumns); + for (var row = 0; row < minRows; row++) { + for (var col = 0; col < maxColumns; col++) { + var idx = row * maxColumns + col; + if (idx >= group.length) { + break; + } + var item = group[idx]; + self._writeToOutput(item); + if (col < maxColumns - 1) { + for (var s = 0, itemLen = item.length; s < width - itemLen; + s++) { + self._writeToOutput(' '); + } + } + } + self._writeToOutput('\r\n'); + } + self._writeToOutput('\r\n'); +} + +function commonPrefix(strings) { + if (!strings || strings.length == 0) { + return ''; + } + var sorted = strings.slice().sort(); + var min = sorted[0]; + var max = sorted[sorted.length - 1]; + for (var i = 0, len = min.length; i < len; i++) { + if (min[i] != max[i]) { + return min.slice(0, i); + } + } + return min; +} + + +Interface.prototype._wordLeft = function() { + if (this.cursor > 0) { + var leading = this.line.slice(0, this.cursor); + var match = leading.match(/([^\w\s]+|\w+|)\s*$/); + this._moveCursor(-match[0].length); + } +}; + + +Interface.prototype._wordRight = function() { + if (this.cursor < this.line.length) { + var trailing = this.line.slice(this.cursor); + var match = trailing.match(/^(\s+|\W+|\w+)\s*/); + this._moveCursor(match[0].length); + } +}; + + +Interface.prototype._deleteLeft = function() { + if (this.cursor > 0 && this.line.length > 0) { + this.line = this.line.slice(0, this.cursor - 1) + + this.line.slice(this.cursor, this.line.length); + + this.cursor--; + this._refreshLine(); + } +}; + + +Interface.prototype._deleteRight = function() { + this.line = this.line.slice(0, this.cursor) + + this.line.slice(this.cursor + 1, this.line.length); + this._refreshLine(); +}; + + +Interface.prototype._deleteWordLeft = function() { + if (this.cursor > 0) { + var leading = this.line.slice(0, this.cursor); + var match = leading.match(/([^\w\s]+|\w+|)\s*$/); + leading = leading.slice(0, leading.length - match[0].length); + this.line = leading + this.line.slice(this.cursor, this.line.length); + this.cursor = leading.length; + this._refreshLine(); + } +}; + + +Interface.prototype._deleteWordRight = function() { + if (this.cursor < this.line.length) { + var trailing = this.line.slice(this.cursor); + var match = trailing.match(/^(\s+|\W+|\w+)\s*/); + this.line = this.line.slice(0, this.cursor) + + trailing.slice(match[0].length); + this._refreshLine(); + } +}; + + +Interface.prototype._deleteLineLeft = function() { + this.line = this.line.slice(this.cursor); + this.cursor = 0; + this._refreshLine(); +}; + + +Interface.prototype._deleteLineRight = function() { + this.line = this.line.slice(0, this.cursor); + this._refreshLine(); +}; + + +Interface.prototype.clearLine = function() { + this._moveCursor(+Infinity); + this._writeToOutput('\r\n'); + this.line = ''; + this.cursor = 0; + this.prevRows = 0; +}; + +// Get current input line content +Interface.prototype.getLine = function() { + return this.line; +}; + + +// Insert a message before actual prompt input line +Interface.prototype.insertOutput = function(msg) { + this._moveCursor(+Infinity); + this._writeToOutput('\r'); + this._writeToOutput(msg+'\n'); + this._writeToOutput(this._prompt+this.line); + +}; + +Interface.prototype._line = function() { + var line = this._addHistory(); + this.clearLine(); + this._onLine(line); +}; + + +Interface.prototype._historyNext = function() { + if (this.historyIndex > 0) { + this.historyIndex--; + this.line = this.history[this.historyIndex]; + this.cursor = this.line.length; // set cursor to end of line. + this._refreshLine(); + + } else if (this.historyIndex === 0) { + this.historyIndex = -1; + this.cursor = 0; + this.line = ''; + this._refreshLine(); + } +}; + + +Interface.prototype._historyPrev = function() { + if (this.historyIndex + 1 < this.history.length) { + this.historyIndex++; + this.line = this.history[this.historyIndex]; + this.cursor = this.line.length; // set cursor to end of line. + + this._refreshLine(); + } +}; + + +// Returns the last character's display position of the given string +Interface.prototype._getDisplayPos = function(str) { + var offset = 0; + var col = this.columns; + var row = 0; + var code; + str = stripVTControlCharacters(str); + for (var i = 0, len = str.length; i < len; i++) { + code = codePointAt(str, i); + if (code >= 0x10000) { // surrogates + i++; + } + if (code === 0x0a) { // new line \n + offset = 0; + row += 1; + continue; + } + if (isFullWidthCodePoint(code)) { + if ((offset + 1) % col === 0) { + offset++; + } + offset += 2; + } else { + offset++; + } + } + var cols = offset % col; + var rows = row + (offset - cols) / col; + return {cols: cols, rows: rows}; +}; + + +// Returns current cursor's position and line +Interface.prototype._getCursorPos = function() { + var columns = this.columns; + var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor); + var dispPos = this._getDisplayPos(stripVTControlCharacters(strBeforeCursor)); + var cols = dispPos.cols; + var rows = dispPos.rows; + // If the cursor is on a full-width character which steps over the line, + // move the cursor to the beginning of the next line. + if (cols + 1 === columns && + this.cursor < this.line.length && + isFullWidthCodePoint(codePointAt(this.line, this.cursor))) { + rows++; + cols = 0; + } + return {cols: cols, rows: rows}; +}; + + +// This function moves cursor dx places to the right +// (-dx for left) and refreshes the line if it is needed +Interface.prototype._moveCursor = function(dx) { + var oldcursor = this.cursor; + var oldPos = this._getCursorPos(); + this.cursor += dx; + + // bounds check + if (this.cursor < 0) this.cursor = 0; + else if (this.cursor > this.line.length) this.cursor = this.line.length; + + var newPos = this._getCursorPos(); + + // check if cursors are in the same line + if (oldPos.rows === newPos.rows) { + var diffCursor = this.cursor - oldcursor; + var diffWidth; + if (diffCursor < 0) { + diffWidth = -getStringWidth( + this.line.substring(this.cursor, oldcursor) + ); + } else if (diffCursor > 0) { + diffWidth = getStringWidth( + this.line.substring(this.cursor, oldcursor) + ); + } + exports.moveCursor(this.output, diffWidth, 0); + this.prevRows = newPos.rows; + } else { + this._refreshLine(); + } +}; + + +// handle a write from the tty +Interface.prototype._ttyWrite = function(s, key) { + key = key || {}; + + // Ignore escape key - Fixes #2876 + if (key.name == 'escape') return; + + if (key.ctrl && key.shift) { + /* Control and shift pressed */ + switch (key.name) { + case 'backspace': + this._deleteLineLeft(); + break; + + case 'delete': + this._deleteLineRight(); + break; + } + + } else if (key.ctrl) { + /* Control key pressed */ + + switch (key.name) { + case 'c': + if (EventEmitter.listenerCount(this, 'SIGINT') > 0) { + this.emit('SIGINT'); + } else { + // This readline instance is finished + this.close(); + } + break; + + case 'h': // delete left + this._deleteLeft(); + break; + + case 'd': // delete right or EOF + if (this.cursor === 0 && this.line.length === 0) { + // This readline instance is finished + this.close(); + } else if (this.cursor < this.line.length) { + this._deleteRight(); + } + break; + + case 'u': // delete the whole line + this.cursor = 0; + this.line = ''; + this._refreshLine(); + break; + + case 'k': // delete from current to end of line + this._deleteLineRight(); + break; + + case 'a': // go to the start of the line + this._moveCursor(-Infinity); + break; + + case 'e': // go to the end of the line + this._moveCursor(+Infinity); + break; + + case 'b': // back one character + this._moveCursor(-1); + break; + + case 'f': // forward one character + this._moveCursor(+1); + break; + + case 'l': // clear the whole screen + exports.cursorTo(this.output, 0, 0); + exports.clearScreenDown(this.output); + this._refreshLine(); + break; + + case 'n': // next history item + this._historyNext(); + break; + + case 'p': // previous history item + this._historyPrev(); + break; + + case 'z': + if (process.platform == 'win32') break; + if (EventEmitter.listenerCount(this, 'SIGTSTP') > 0) { + this.emit('SIGTSTP'); + } else { + process.once('SIGCONT', (function(self) { + return function() { + // Don't raise events if stream has already been abandoned. + if (!self.paused) { + // Stream must be paused and resumed after SIGCONT to catch + // SIGINT, SIGTSTP, and EOF. + self.pause(); + self.emit('SIGCONT'); + } + // explicitly re-enable "raw mode" and move the cursor to + // the correct position. + // See https://github.com/joyent/node/issues/3295. + self._setRawMode(true); + self._refreshLine(); + }; + })(this)); + this._setRawMode(false); + process.kill(process.pid, 'SIGTSTP'); + } + break; + + case 'w': // delete backwards to a word boundary + case 'backspace': + this._deleteWordLeft(); + break; + + case 'delete': // delete forward to a word boundary + this._deleteWordRight(); + break; + + case 'left': + this._wordLeft(); + break; + + case 'right': + this._wordRight(); + break; + } + + } else if (key.meta) { + /* Meta key pressed */ + + switch (key.name) { + case 'b': // backward word + this._wordLeft(); + break; + + case 'f': // forward word + this._wordRight(); + break; + + case 'd': // delete forward word + case 'delete': + this._deleteWordRight(); + break; + + case 'backspace': // delete backwards to a word boundary + this._deleteWordLeft(); + break; + } + + } else { + /* No modifier keys used */ + + // \r bookkeeping is only relevant if a \n comes right after. + if (this._sawReturn && key.name !== 'enter') + this._sawReturn = false; + + switch (key.name) { + case 'return': // carriage return, i.e. \r + this._sawReturn = true; + this._line(); + break; + + case 'enter': + if (this._sawReturn) + this._sawReturn = false; + else + this._line(); + break; + + case 'backspace': + this._deleteLeft(); + break; + + case 'delete': + this._deleteRight(); + break; + + case 'tab': // tab completion + this._tabComplete(); + break; + + case 'left': + this._moveCursor(-1); + break; + + case 'right': + this._moveCursor(+1); + break; + + case 'home': + this._moveCursor(-Infinity); + break; + + case 'end': + this._moveCursor(+Infinity); + break; + + case 'up': + this._historyPrev(); + break; + + case 'down': + this._historyNext(); + break; + + default: + if (util.isBuffer(s)) + s = s.toString('utf-8'); + + if (s) { + var lines = s.split(/\r\n|\n|\r/); + for (var i = 0, len = lines.length; i < len; i++) { + if (i > 0) { + this._line(); + } + this._insertString(lines[i]); + } + } + } + } +}; + + +exports.Interface = Interface; + + + +/** + * accepts a readable Stream instance and makes it emit "keypress" events + */ + +function emitKeypressEvents(stream) { + if (stream._keypressDecoder) return; + var StringDecoder = Require('string_decoder').StringDecoder; // lazy load + stream._keypressDecoder = new StringDecoder('utf8'); + + function onData(b) { + if (EventEmitter.listenerCount(stream, 'keypress') > 0) { + var r = stream._keypressDecoder.write(b); + if (r) emitKeys(stream, r); + } else { + // Nobody's watching anyway + stream.removeListener('data', onData); + stream.on('newListener', onNewListener); + } + } + + function onNewListener(event) { + if (event == 'keypress') { + stream.on('data', onData); + stream.removeListener('newListener', onNewListener); + } + } + + if (EventEmitter.listenerCount(stream, 'keypress') > 0) { + stream.on('data', onData); + } else { + stream.on('newListener', onNewListener); + } +} +exports.emitKeypressEvents = emitKeypressEvents; + +/* + Some patterns seen in terminal key escape codes, derived from combos seen + at http://www.midnight-commander.org/browser/lib/tty/key.c + + ESC letter + ESC [ letter + ESC [ modifier letter + ESC [ 1 ; modifier letter + ESC [ num char + ESC [ num ; modifier char + ESC O letter + ESC O modifier letter + ESC O 1 ; modifier letter + ESC N letter + ESC [ [ num ; modifier char + ESC [ [ 1 ; modifier letter + ESC ESC [ num char + ESC ESC O letter + + - char is usually ~ but $ and ^ also happen with rxvt + - modifier is 1 + + (shift * 1) + + (left_alt * 2) + + (ctrl * 4) + + (right_alt * 8) + - two leading ESCs apparently mean the same as one leading ESC +*/ + +// Regexes used for ansi escape code splitting +var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/; +var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$'); +var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [ + '(\\d+)(?:;(\\d+))?([~^$])', + '(?:M([@ #!a`])(.)(.))', // mouse + '(?:1;)?(\\d+)?([a-zA-Z])' +].join('|') + ')'); +var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source); +var escapeCodeReAnywhere = new RegExp([ + functionKeyCodeReAnywhere.source, metaKeyCodeReAnywhere.source, /\x1b./.source +].join('|')); + +function emitKeys(stream, s) { + if (util.isBuffer(s)) { + if (s[0] > 127 && util.isUndefined(s[1])) { + s[0] -= 128; + s = '\x1b' + s.toString(stream.encoding || 'utf-8'); + } else { + s = s.toString(stream.encoding || 'utf-8'); + } + } + + var buffer = []; + var match; + while (match = escapeCodeReAnywhere.exec(s)) { + buffer = buffer.concat(s.slice(0, match.index).split('')); + buffer.push(match[0]); + s = s.slice(match.index + match[0].length); + } + buffer = buffer.concat(s.split('')); + + buffer.forEach(function(s) { + var ch, + key = { + sequence: s, + name: undefined, + ctrl: false, + meta: false, + shift: false + }, + parts; + + if (s === '\r') { + // carriage return + key.name = 'return'; + + } else if (s === '\n') { + // enter, should have been called linefeed + key.name = 'enter'; + + } else if (s === '\t') { + // tab + key.name = 'tab'; + + } else if (s === '\b' || s === '\x7f' || + s === '\x1b\x7f' || s === '\x1b\b') { + // backspace or ctrl+h + key.name = 'backspace'; + key.meta = (s.charAt(0) === '\x1b'); + + } else if (s === '\x1b' || s === '\x1b\x1b') { + // escape key + key.name = 'escape'; + key.meta = (s.length === 2); + + } else if (s === ' ' || s === '\x1b ') { + key.name = 'space'; + key.meta = (s.length === 2); + + } else if (s.length === 1 && s <= '\x1a') { + // ctrl+letter + key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1); + key.ctrl = true; + + } else if (s.length === 1 && s >= 'a' && s <= 'z') { + // lowercase letter + key.name = s; + + } else if (s.length === 1 && s >= 'A' && s <= 'Z') { + // shift+letter + key.name = s.toLowerCase(); + key.shift = true; + + } else if (parts = metaKeyCodeRe.exec(s)) { + // meta+character key + key.name = parts[1].toLowerCase(); + key.meta = true; + key.shift = /^[A-Z]$/.test(parts[1]); + + } else if (parts = functionKeyCodeRe.exec(s)) { + // ansi escape sequence + + // reassemble the key code leaving out leading \x1b's, + // the modifier key bitflag and any meaningless "1;" sequence + var code = (parts[1] || '') + (parts[2] || '') + + (parts[4] || '') + (parts[9] || ''), + modifier = (parts[3] || parts[8] || 1) - 1; + + // Parse the key modifier + key.ctrl = !!(modifier & 4); + key.meta = !!(modifier & 10); + key.shift = !!(modifier & 1); + key.code = code; + + // Parse the key itself + switch (code) { + /* xterm/gnome ESC O letter */ + case 'OP': key.name = 'f1'; break; + case 'OQ': key.name = 'f2'; break; + case 'OR': key.name = 'f3'; break; + case 'OS': key.name = 'f4'; break; + + /* xterm/rxvt ESC [ number ~ */ + case '[11~': key.name = 'f1'; break; + case '[12~': key.name = 'f2'; break; + case '[13~': key.name = 'f3'; break; + case '[14~': key.name = 'f4'; break; + + /* from Cygwin and used in libuv */ + case '[[A': key.name = 'f1'; break; + case '[[B': key.name = 'f2'; break; + case '[[C': key.name = 'f3'; break; + case '[[D': key.name = 'f4'; break; + case '[[E': key.name = 'f5'; break; + + /* common */ + case '[15~': key.name = 'f5'; break; + case '[17~': key.name = 'f6'; break; + case '[18~': key.name = 'f7'; break; + case '[19~': key.name = 'f8'; break; + case '[20~': key.name = 'f9'; break; + case '[21~': key.name = 'f10'; break; + case '[23~': key.name = 'f11'; break; + case '[24~': key.name = 'f12'; break; + + /* xterm ESC [ letter */ + case '[A': key.name = 'up'; break; + case '[B': key.name = 'down'; break; + case '[C': key.name = 'right'; break; + case '[D': key.name = 'left'; break; + case '[E': key.name = 'clear'; break; + case '[F': key.name = 'end'; break; + case '[H': key.name = 'home'; break; + + /* xterm/gnome ESC O letter */ + case 'OA': key.name = 'up'; break; + case 'OB': key.name = 'down'; break; + case 'OC': key.name = 'right'; break; + case 'OD': key.name = 'left'; break; + case 'OE': key.name = 'clear'; break; + case 'OF': key.name = 'end'; break; + case 'OH': key.name = 'home'; break; + + /* xterm/rxvt ESC [ number ~ */ + case '[1~': key.name = 'home'; break; + case '[2~': key.name = 'insert'; break; + case '[3~': key.name = 'delete'; break; + case '[4~': key.name = 'end'; break; + case '[5~': key.name = 'pageup'; break; + case '[6~': key.name = 'pagedown'; break; + + /* putty */ + case '[[5~': key.name = 'pageup'; break; + case '[[6~': key.name = 'pagedown'; break; + + /* rxvt */ + case '[7~': key.name = 'home'; break; + case '[8~': key.name = 'end'; break; + + /* rxvt keys with modifiers */ + case '[a': key.name = 'up'; key.shift = true; break; + case '[b': key.name = 'down'; key.shift = true; break; + case '[c': key.name = 'right'; key.shift = true; break; + case '[d': key.name = 'left'; key.shift = true; break; + case '[e': key.name = 'clear'; key.shift = true; break; + + case '[2$': key.name = 'insert'; key.shift = true; break; + case '[3$': key.name = 'delete'; key.shift = true; break; + case '[5$': key.name = 'pageup'; key.shift = true; break; + case '[6$': key.name = 'pagedown'; key.shift = true; break; + case '[7$': key.name = 'home'; key.shift = true; break; + case '[8$': key.name = 'end'; key.shift = true; break; + + case 'Oa': key.name = 'up'; key.ctrl = true; break; + case 'Ob': key.name = 'down'; key.ctrl = true; break; + case 'Oc': key.name = 'right'; key.ctrl = true; break; + case 'Od': key.name = 'left'; key.ctrl = true; break; + case 'Oe': key.name = 'clear'; key.ctrl = true; break; + + case '[2^': key.name = 'insert'; key.ctrl = true; break; + case '[3^': key.name = 'delete'; key.ctrl = true; break; + case '[5^': key.name = 'pageup'; key.ctrl = true; break; + case '[6^': key.name = 'pagedown'; key.ctrl = true; break; + case '[7^': key.name = 'home'; key.ctrl = true; break; + case '[8^': key.name = 'end'; key.ctrl = true; break; + + /* misc. */ + case '[Z': key.name = 'tab'; key.shift = true; break; + default: key.name = 'undefined'; break; + + } + } + + // Don't emit a key if no name was found + if (util.isUndefined(key.name)) { + key = undefined; + } + + if (s.length === 1) { + ch = s; + } + + if (key || ch) { + stream.emit('keypress', ch, key); + } + }); +} + + +/** + * moves the cursor to the x and y coordinate on the given stream + */ + +function cursorTo(stream, x, y) { + if (util.isNullOrUndefined(stream)) + return; + + if (!util.isNumber(x) && !util.isNumber(y)) + return; + + if (!util.isNumber(x)) + throw new Error("Can't set cursor row without also setting it's column"); + + if (!util.isNumber(y)) { + stream.write('\x1b[' + (x + 1) + 'G'); + } else { + stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H'); + } +} +exports.cursorTo = cursorTo; + + +/** + * moves the cursor relative to its current location + */ + +function moveCursor(stream, dx, dy) { + if (util.isNullOrUndefined(stream)) + return; + + if (dx < 0) { + stream.write('\x1b[' + (-dx) + 'D'); + } else if (dx > 0) { + stream.write('\x1b[' + dx + 'C'); + } + + if (dy < 0) { + stream.write('\x1b[' + (-dy) + 'A'); + } else if (dy > 0) { + stream.write('\x1b[' + dy + 'B'); + } +} +exports.moveCursor = moveCursor; + + +/** + * clears the current line the cursor is on: + * -1 for left of the cursor + * +1 for right of the cursor + * 0 for the entire line + */ + +function clearLine(stream, dir) { + if (util.isNullOrUndefined(stream)) + return; + + if (dir < 0) { + // to the beginning + stream.write('\x1b[1K'); + } else if (dir > 0) { + // to the end + stream.write('\x1b[0K'); + } else { + // entire line + stream.write('\x1b[2K'); + } +} +exports.clearLine = clearLine; + + +/** + * clears the screen from the current position of the cursor down + */ + +function clearScreenDown(stream) { + if (util.isNullOrUndefined(stream)) + return; + + stream.write('\x1b[0J'); +} +exports.clearScreenDown = clearScreenDown; + + +/** + * Returns the number of columns required to display the given string. + */ + +function getStringWidth(str) { + var width = 0; + str = stripVTControlCharacters(str); + for (var i = 0, len = str.length; i < len; i++) { + var code = codePointAt(str, i); + if (code >= 0x10000) { // surrogates + i++; + } + if (isFullWidthCodePoint(code)) { + width += 2; + } else { + width++; + } + } + return width; +} +exports.getStringWidth = getStringWidth; + + +/** + * Returns true if the character represented by a given + * Unicode code point is full-width. Otherwise returns false. + */ + +function isFullWidthCodePoint(code) { + if (isNaN(code)) { + return false; + } + + // Code points are derived from: + // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + if (code >= 0x1100 && ( + code <= 0x115f || // Hangul Jamo + 0x2329 === code || // LEFT-POINTING ANGLE BRACKET + 0x232a === code || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (0x2e80 <= code && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + 0x3250 <= code && code <= 0x4dbf || + // CJK Unified Ideographs .. Yi Radicals + 0x4e00 <= code && code <= 0xa4c6 || + // Hangul Jamo Extended-A + 0xa960 <= code && code <= 0xa97c || + // Hangul Syllables + 0xac00 <= code && code <= 0xd7a3 || + // CJK Compatibility Ideographs + 0xf900 <= code && code <= 0xfaff || + // Vertical Forms + 0xfe10 <= code && code <= 0xfe19 || + // CJK Compatibility Forms .. Small Form Variants + 0xfe30 <= code && code <= 0xfe6b || + // Halfwidth and Fullwidth Forms + 0xff01 <= code && code <= 0xff60 || + 0xffe0 <= code && code <= 0xffe6 || + // Kana Supplement + 0x1b000 <= code && code <= 0x1b001 || + // Enclosed Ideographic Supplement + 0x1f200 <= code && code <= 0x1f251 || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + 0x20000 <= code && code <= 0x3fffd)) { + return true; + } + return false; +} +exports.isFullWidthCodePoint = isFullWidthCodePoint; + + +/** + * Returns the Unicode code point for the character at the + * given index in the given string. Similar to String.charCodeAt(), + * but this function handles surrogates (code point >= 0x10000). + */ + +function codePointAt(str, index) { + var code = str.charCodeAt(index); + var low; + if (0xd800 <= code && code <= 0xdbff) { // High surrogate + low = str.charCodeAt(index + 1); + if (!isNaN(low)) { + code = 0x10000 + (code - 0xd800) * 0x400 + (low - 0xdc00); + } + } + return code; +} +exports.codePointAt = codePointAt; + + +/** + * Tries to remove all VT control characters. Use to estimate displayed + * string width. May be buggy due to not running a real state machine + */ +function stripVTControlCharacters(str) { + str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), ''); + return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), ''); +} +exports.stripVTControlCharacters = stripVTControlCharacters; + +}; +BundleModuleCode['term/readlineSync']=function (module,exports){ +/* + * readlineSync + * https://github.com/anseki/readline-sync + * + * Copyright (c) 2018 anseki + * Licensed under the MIT license. + */ + +'use strict'; + +var + IS_WIN = process.platform === 'win32', + + ALGORITHM_CIPHER = 'aes-256-cbc', + ALGORITHM_HASH = 'sha256', + DEFAULT_ERR_MSG = 'The current environment doesn\'t support interactive reading from TTY.', + + fs = require('fs'), + TTY = process.binding('tty_wrap').TTY, + childProc = require('child_process'), + pathUtil = require('path'), + + defaultOptions = { + /* eslint-disable key-spacing */ + prompt: '> ', + hideEchoBack: false, + mask: '*', + limit: [], + limitMessage: 'Input another, please.$<( [)limit(])>', + defaultInput: '', + trueValue: [], + falseValue: [], + caseSensitive: false, + keepWhitespace: false, + encoding: 'utf8', + bufferSize: 1024, + print: void 0, + history: true, + cd: false, + phContent: void 0, + preCheck: void 0 + /* eslint-enable key-spacing */ + }, + + fdR = 'none', fdW, ttyR, isRawMode = false, + extHostPath, extHostArgs, tempdir, salt = 0, + lastInput = '', inputHistory = [], rawInput, + _DBG_useExt = false, _DBG_checkOptions = false, _DBG_checkMethod = false; + +function getHostArgs(options) { + // Send any text to crazy Windows shell safely. + function encodeArg(arg) { + return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) { + return '#' + chr.charCodeAt(0) + ';'; + }); + } + + return extHostArgs.concat((function(conf) { + var args = []; + Object.keys(conf).forEach(function(optionName) { + if (conf[optionName] === 'boolean') { + if (options[optionName]) { args.push('--' + optionName); } + } else if (conf[optionName] === 'string') { + if (options[optionName]) { + args.push('--' + optionName, encodeArg(options[optionName])); + } + } + }); + return args; + })({ + /* eslint-disable key-spacing */ + display: 'string', + displayOnly: 'boolean', + keyIn: 'boolean', + hideEchoBack: 'boolean', + mask: 'string', + limit: 'string', + caseSensitive: 'boolean' + /* eslint-enable key-spacing */ + })); +} + +// piping via files (for Node.js v0.10-) +function _execFileSync(options, execOptions) { + + function getTempfile(name) { + var filepath, suffix = '', fd; + tempdir = tempdir || require('os').tmpdir(); + + while (true) { + filepath = pathUtil.join(tempdir, name + suffix); + try { + fd = fs.openSync(filepath, 'wx'); + } catch (e) { + if (e.code === 'EEXIST') { + suffix++; + continue; + } else { + throw e; + } + } + fs.closeSync(fd); + break; + } + return filepath; + } + + var hostArgs, shellPath, shellArgs, res = {}, exitCode, extMessage, + pathStdout = getTempfile('readline-sync.stdout'), + pathStderr = getTempfile('readline-sync.stderr'), + pathExit = getTempfile('readline-sync.exit'), + pathDone = getTempfile('readline-sync.done'), + crypto = require('crypto'), shasum, decipher, password; + + shasum = crypto.createHash(ALGORITHM_HASH); + shasum.update('' + process.pid + (salt++) + Math.random()); + password = shasum.digest('hex'); + decipher = crypto.createDecipher(ALGORITHM_CIPHER, password); + + hostArgs = getHostArgs(options); + if (IS_WIN) { + shellPath = process.env.ComSpec || 'cmd.exe'; + process.env.Q = '"'; // The quote (") that isn't escaped. + // `()` for ignore space by echo + shellArgs = ['/V:ON', '/S', '/C', + '(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + /* ESLint bug? */ // eslint-disable-line no-path-concat + '%Q%' + extHostPath + '%Q%' + + hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') + + ' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' + + ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' + + ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' + + ' >%Q%' + pathStdout + '%Q%' + + ' & (echo 1)>%Q%' + pathDone + '%Q%']; + } else { + shellPath = '/bin/sh'; + shellArgs = ['-c', + // Use `()`, not `{}` for `-c` (text param) + '("' + extHostPath + '"' + /* ESLint bug? */ // eslint-disable-line no-path-concat + hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') + + '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' + + ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + + ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + + ' >"' + pathStdout + '"' + + '; echo 1 >"' + pathDone + '"']; + } + if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); } + try { + childProc.spawn(shellPath, shellArgs, execOptions); + } catch (e) { + res.error = new Error(e.message); + res.error.method = '_execFileSync - spawn'; + res.error.program = shellPath; + res.error.args = shellArgs; + } + + while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} // eslint-disable-line no-empty + if ((exitCode = + fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') { + res.input = + decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), + 'hex', options.encoding) + + decipher.final(options.encoding); + } else { + extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim(); + res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); + res.error.method = '_execFileSync'; + res.error.program = shellPath; + res.error.args = shellArgs; + res.error.extMessage = extMessage; + res.error.exitCode = +exitCode; + } + + fs.unlinkSync(pathStdout); + fs.unlinkSync(pathStderr); + fs.unlinkSync(pathExit); + fs.unlinkSync(pathDone); + + return res; +} + +function readlineExt(options) { + var hostArgs, res = {}, extMessage, + execOptions = {env: process.env, encoding: options.encoding}; + + if (!extHostPath) { + if (IS_WIN) { + if (process.env.PSModulePath) { // Windows PowerShell + extHostPath = 'powershell.exe'; + extHostArgs = ['-ExecutionPolicy', 'Bypass', '-File', __dirname + '\\read.ps1']; // eslint-disable-line no-path-concat + } else { // Windows Script Host + extHostPath = 'cscript.exe'; + extHostArgs = ['//nologo', __dirname + '\\read.cs.js']; // eslint-disable-line no-path-concat + } + } else { + extHostPath = '/bin/sh'; + extHostArgs = [__dirname + '/read.sh']; // eslint-disable-line no-path-concat + } + } + if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host + // ScriptPW (Win XP and Server2003) needs TTY stream as STDIN. + // In this case, If STDIN isn't TTY, an error is thrown. + execOptions.stdio = [process.stdin]; + } + + if (childProc.execFileSync) { + hostArgs = getHostArgs(options); + if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); } + try { + res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); + } catch (e) { // non-zero exit code + extMessage = e.stderr ? (e.stderr + '').trim() : ''; + res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); + res.error.method = 'execFileSync'; + res.error.program = extHostPath; + res.error.args = hostArgs; + res.error.extMessage = extMessage; + res.error.exitCode = e.status; + res.error.code = e.code; + res.error.signal = e.signal; + } + } else { + res = _execFileSync(options, execOptions); + } + if (!res.error) { + res.input = res.input.replace(/^\s*'|'\s*$/g, ''); + options.display = ''; + } + + return res; +} + +/* + display: string + displayOnly: boolean + keyIn: boolean + hideEchoBack: boolean + mask: string + limit: string (pattern) + caseSensitive: boolean + keepWhitespace: boolean + encoding, bufferSize, print +*/ +function _readlineSync(options) { + var input = '', displaySave = options.display, + silent = !options.display && + options.keyIn && options.hideEchoBack && !options.mask; + + function tryExt() { + var res = readlineExt(options); + if (res.error) { throw res.error; } + return res.input; + } + + if (_DBG_checkOptions) { _DBG_checkOptions(options); } + + (function() { // open TTY + var fsB, constants, verNum; + + function getFsB() { + if (!fsB) { + fsB = process.binding('fs'); // For raw device path + constants = process.binding('constants'); + } + return fsB; + } + + if (typeof fdR !== 'string') { return; } + fdR = null; + + if (IS_WIN) { + // iojs-v2.3.2+ input stream can't read first line. (#18) + // ** Don't get process.stdin before check! ** + // Fixed v5.1.0 + // Fixed v4.2.4 + // It regressed again in v5.6.0, it is fixed in v6.2.0. + verNum = (function(ver) { // getVerNum + var nums = ver.replace(/^\D+/, '').split('.'); + var verNum = 0; + if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; } + if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; } + if ((nums[2] = +nums[2])) { verNum += nums[2]; } + return verNum; + })(process.version); + if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100 || verNum >= 50600 && verNum < 60200) && + process.stdin.isTTY) { + process.stdin.pause(); + fdR = process.stdin.fd; + ttyR = process.stdin._handle; + } else { + try { + // The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode. + // 'CONIN$' might fail on XP, 2000, 7 (x86). + fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8)); + ttyR = new TTY(fdR, true); + } catch (e) { /* ignore */ } + } + + if (process.stdout.isTTY) { + fdW = process.stdout.fd; + } else { + try { + fdW = fs.openSync('\\\\.\\CON', 'w'); + } catch (e) { /* ignore */ } + if (typeof fdW !== 'number') { // Retry + try { + fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8)); + } catch (e) { /* ignore */ } + } + } + + } else { + if (process.stdin.isTTY) { + process.stdin.pause(); + try { + fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin + ttyR = process.stdin._handle; + } catch (e) { /* ignore */ } + } else { + // Node.js v0.12 read() fails. + try { + fdR = fs.openSync('/dev/tty', 'r'); + ttyR = new TTY(fdR, false); + } catch (e) { /* ignore */ } + } + + if (process.stdout.isTTY) { + fdW = process.stdout.fd; + } else { + try { + fdW = fs.openSync('/dev/tty', 'w'); + } catch (e) { /* ignore */ } + } + } + })(); + + (function() { // try read + var atEol, limit, + isCooked = !options.hideEchoBack && !options.keyIn, + buffer, reqSize, readSize, chunk, line; + rawInput = ''; + + // Node.js v0.10- returns an error if same mode is set. + function setRawMode(mode) { + if (mode === isRawMode) { return true; } + if (ttyR.setRawMode(mode) !== 0) { return false; } + isRawMode = mode; + return true; + } + + if (_DBG_useExt || !ttyR || + typeof fdW !== 'number' && (options.display || !isCooked)) { + input = tryExt(); + return; + } + + if (options.display) { + fs.writeSync(fdW, options.display); + options.display = ''; + } + if (options.displayOnly) { return; } + + if (!setRawMode(!isCooked)) { + input = tryExt(); + return; + } + + reqSize = options.keyIn ? 1 : options.bufferSize; + // Check `allocUnsafe` to make sure of the new API. + buffer = Buffer.allocUnsafe && Buffer.alloc ? Buffer.alloc(reqSize) : new Buffer(reqSize); + + if (options.keyIn && options.limit) { + limit = new RegExp('[^' + options.limit + ']', + 'g' + (options.caseSensitive ? '' : 'i')); + } + + while (true) { + readSize = 0; + try { + readSize = fs.readSync(fdR, buffer, 0, reqSize); + } catch (e) { + if (e.code !== 'EOF') { + setRawMode(false); + input += tryExt(); + return; + } + } + if (readSize > 0) { + chunk = buffer.toString(options.encoding, 0, readSize); + rawInput += chunk; + } else { + chunk = '\n'; + rawInput += String.fromCharCode(0); + } + + if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') { + chunk = line; + atEol = true; + } + + // other ctrl-chars + // eslint-disable-next-line no-control-regex + if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); } + if (chunk && limit) { chunk = chunk.replace(limit, ''); } + + if (chunk) { + if (!isCooked) { + if (!options.hideEchoBack) { + fs.writeSync(fdW, chunk); + } else if (options.mask) { + fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask)); + } + } + input += chunk; + } + + if (!options.keyIn && atEol || + options.keyIn && input.length >= reqSize) { break; } + } + + if (!isCooked && !silent) { fs.writeSync(fdW, '\n'); } + setRawMode(false); + })(); + + if (options.print && !silent) { + options.print(displaySave + (options.displayOnly ? '' : + (options.hideEchoBack ? (new Array(input.length + 1)).join(options.mask) + : input) + '\n'), // must at least write '\n' + options.encoding); + } + + return options.displayOnly ? '' : + (lastInput = options.keepWhitespace || options.keyIn ? input : input.trim()); +} + +function flattenArray(array, validator) { + var flatArray = []; + function _flattenArray(array) { + if (array == null) { + return; + } else if (Array.isArray(array)) { + array.forEach(_flattenArray); + } else if (!validator || validator(array)) { + flatArray.push(array); + } + } + _flattenArray(array); + return flatArray; +} + +function escapePattern(pattern) { + return pattern.replace(/[\x00-\x7f]/g, // eslint-disable-line no-control-regex + function(s) { return '\\x' + ('00' + s.charCodeAt().toString(16)).substr(-2); }); +} + +// margeOptions(options1, options2 ... ) +// margeOptions(true, options1, options2 ... ) +// arg1=true : Start from defaultOptions and pick elements of that. +function margeOptions() { + var optionsList = Array.prototype.slice.call(arguments), + optionNames, fromDefault; + + if (optionsList.length && typeof optionsList[0] === 'boolean') { + fromDefault = optionsList.shift(); + if (fromDefault) { + optionNames = Object.keys(defaultOptions); + optionsList.unshift(defaultOptions); + } + } + + return optionsList.reduce(function(options, optionsPart) { + if (optionsPart == null) { return options; } + + // ======== DEPRECATED ======== + if (optionsPart.hasOwnProperty('noEchoBack') && + !optionsPart.hasOwnProperty('hideEchoBack')) { + optionsPart.hideEchoBack = optionsPart.noEchoBack; + delete optionsPart.noEchoBack; + } + if (optionsPart.hasOwnProperty('noTrim') && + !optionsPart.hasOwnProperty('keepWhitespace')) { + optionsPart.keepWhitespace = optionsPart.noTrim; + delete optionsPart.noTrim; + } + // ======== /DEPRECATED ======== + + if (!fromDefault) { optionNames = Object.keys(optionsPart); } + optionNames.forEach(function(optionName) { + var value; + if (!optionsPart.hasOwnProperty(optionName)) { return; } + value = optionsPart[optionName]; + switch (optionName) { + // _readlineSync <- * * -> defaultOptions + // ================ string + case 'mask': // * * + case 'limitMessage': // * + case 'defaultInput': // * + case 'encoding': // * * + value = value != null ? value + '' : ''; + if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); } + options[optionName] = value; + break; + // ================ number(int) + case 'bufferSize': // * * + if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') { + options[optionName] = value; // limited updating (number is needed) + } + break; + // ================ boolean + case 'displayOnly': // * + case 'keyIn': // * + case 'hideEchoBack': // * * + case 'caseSensitive': // * * + case 'keepWhitespace': // * * + case 'history': // * + case 'cd': // * + options[optionName] = !!value; + break; + // ================ array + case 'limit': // * * to string for readlineExt + case 'trueValue': // * + case 'falseValue': // * + options[optionName] = flattenArray(value, function(value) { + var type = typeof value; + return type === 'string' || type === 'number' || + type === 'function' || value instanceof RegExp; + }).map(function(value) { + return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value; + }); + break; + // ================ function + case 'print': // * * + case 'phContent': // * + case 'preCheck': // * + options[optionName] = typeof value === 'function' ? value : void 0; + break; + // ================ other + case 'prompt': // * + case 'display': // * + options[optionName] = value != null ? value : ''; + break; + // no default + } + }); + return options; + }, {}); +} + +function isMatched(res, comps, caseSensitive) { + return comps.some(function(comp) { + var type = typeof comp; + return type === 'string' ? + (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) : + type === 'number' ? parseFloat(res) === comp : + type === 'function' ? comp(res) : + comp instanceof RegExp ? comp.test(res) : false; + }); +} + +function replaceHomePath(path, expand) { + var homePath = pathUtil.normalize( + IS_WIN ? (process.env.HOMEDRIVE || '') + (process.env.HOMEPATH || '') : + process.env.HOME || '').replace(/[\/\\]+$/, ''); + path = pathUtil.normalize(path); + return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) : + path.replace(new RegExp('^' + escapePattern(homePath) + + '(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~'); +} + +function replacePlaceholder(text, generator) { + var PTN_INNER = '(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?', + rePlaceholder = new RegExp('(\\$)?(\\$<' + PTN_INNER + '>)', 'g'), + rePlaceholderCompat = new RegExp('(\\$)?(\\$\\{' + PTN_INNER + '\\})', 'g'); + + function getPlaceholderText(s, escape, placeholder, pre, param, post) { + var text; + return escape || typeof (text = generator(param)) !== 'string' ? placeholder : + text ? (pre || '') + text + (post || '') : ''; + } + + return text.replace(rePlaceholder, getPlaceholderText) + .replace(rePlaceholderCompat, getPlaceholderText); +} + +function array2charlist(array, caseSensitive, collectSymbols) { + var values, group = [], groupClass = -1, charCode = 0, symbols = '', suppressed; + function addGroup(groups, group) { + if (group.length > 3) { // ellipsis + groups.push(group[0] + '...' + group[group.length - 1]); + suppressed = true; + } else if (group.length) { + groups = groups.concat(group); + } + return groups; + } + + values = array.reduce( + function(chars, value) { return chars.concat((value + '').split('')); }, []) + .reduce(function(groups, curChar) { + var curGroupClass, curCharCode; + if (!caseSensitive) { curChar = curChar.toLowerCase(); } + curGroupClass = /^\d$/.test(curChar) ? 1 : + /^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0; + if (collectSymbols && curGroupClass === 0) { + symbols += curChar; + } else { + curCharCode = curChar.charCodeAt(0); + if (curGroupClass && curGroupClass === groupClass && + curCharCode === charCode + 1) { + group.push(curChar); + } else { + groups = addGroup(groups, group); + group = [curChar]; + groupClass = curGroupClass; + } + charCode = curCharCode; + } + return groups; + }, []); + values = addGroup(values, group); // last group + if (symbols) { values.push(symbols); suppressed = true; } + return {values: values, suppressed: suppressed}; +} + +function joinChunks(chunks, suppressed) { + return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/'); +} + +function getPhContent(param, options) { + var text, values, resCharlist = {}, arg; + if (options.phContent) { + text = options.phContent(param, options); + } + if (typeof text !== 'string') { + switch (param) { + case 'hideEchoBack': + case 'mask': + case 'defaultInput': + case 'caseSensitive': + case 'keepWhitespace': + case 'encoding': + case 'bufferSize': + case 'history': + case 'cd': + text = !options.hasOwnProperty(param) ? '' : + typeof options[param] === 'boolean' ? (options[param] ? 'on' : 'off') : + options[param] + ''; + break; + // case 'prompt': + // case 'query': + // case 'display': + // text = options.hasOwnProperty('displaySrc') ? options.displaySrc + '' : ''; + // break; + case 'limit': + case 'trueValue': + case 'falseValue': + values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param]; + if (options.keyIn) { // suppress + resCharlist = array2charlist(values, options.caseSensitive); + values = resCharlist.values; + } else { + values = values.filter(function(value) { + var type = typeof value; + return type === 'string' || type === 'number'; + }); + } + text = joinChunks(values, resCharlist.suppressed); + break; + case 'limitCount': + case 'limitCountNotZero': + text = options[options.hasOwnProperty('limitSrc') ? + 'limitSrc' : 'limit'].length; + text = text || param !== 'limitCountNotZero' ? text + '' : ''; + break; + case 'lastInput': + text = lastInput; + break; + case 'cwd': + case 'CWD': + case 'cwdHome': + text = process.cwd(); + if (param === 'CWD') { + text = pathUtil.basename(text); + } else if (param === 'cwdHome') { + text = replaceHomePath(text); + } + break; + case 'date': + case 'time': + case 'localeDate': + case 'localeTime': + text = (new Date())['to' + + param.replace(/^./, function(str) { return str.toUpperCase(); }) + + 'String'](); + break; + default: // with arg + if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') { + text = inputHistory[inputHistory.length - arg] || ''; + } + } + } + return text; +} + +function getPhCharlist(param) { + var matches = /^(.)-(.)$/.exec(param), text = '', from, to, code, step; + if (!matches) { return null; } + from = matches[1].charCodeAt(0); + to = matches[2].charCodeAt(0); + step = from < to ? 1 : -1; + for (code = from; code !== to + step; code += step) { text += String.fromCharCode(code); } + return text; +} + +// cmd "arg" " a r g " "" 'a"r"g' "a""rg" "arg +function parseCl(cl) { + var reToken = new RegExp(/(\s*)(?:("|')(.*?)(?:\2|$)|(\S+))/g), matches, + taken = '', args = [], part; + cl = cl.trim(); + while ((matches = reToken.exec(cl))) { + part = matches[3] || matches[4] || ''; + if (matches[1]) { + args.push(taken); + taken = ''; + } + taken += part; + } + if (taken) { args.push(taken); } + return args; +} + +function toBool(res, options) { + return ( + (options.trueValue.length && + isMatched(res, options.trueValue, options.caseSensitive)) ? true : + (options.falseValue.length && + isMatched(res, options.falseValue, options.caseSensitive)) ? false : res); +} + +function getValidLine(options) { + var res, forceNext, limitMessage, + matches, histInput, args, resCheck; + + function _getPhContent(param) { return getPhContent(param, options); } + function addDisplay(text) { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; } + + options.limitSrc = options.limit; + options.displaySrc = options.display; + options.limit = ''; // for readlineExt + options.display = replacePlaceholder(options.display + '', _getPhContent); + + while (true) { + res = _readlineSync(options); + forceNext = false; + limitMessage = ''; + + if (options.defaultInput && !res) { res = options.defaultInput; } + + if (options.history) { + if ((matches = /^\s*\!(?:\!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p` + histInput = inputHistory[0] || ''; + if (matches[1]) { // only display + forceNext = true; + } else { // replace input + res = histInput; + } + // Show it even if it is empty (NL only). + addDisplay(histInput + '\n'); + if (!forceNext) { // Loop may break + options.displayOnly = true; + _readlineSync(options); + options.displayOnly = false; + } + } else if (res && res !== inputHistory[inputHistory.length - 1]) { + inputHistory = [res]; + } + } + + if (!forceNext && options.cd && res) { + args = parseCl(res); + switch (args[0].toLowerCase()) { + case 'cd': + if (args[1]) { + try { + process.chdir(replaceHomePath(args[1], true)); + } catch (e) { + addDisplay(e + ''); + } + } + forceNext = true; + break; + case 'pwd': + addDisplay(process.cwd()); + forceNext = true; + break; + // no default + } + } + + if (!forceNext && options.preCheck) { + resCheck = options.preCheck(res, options); + res = resCheck.res; + if (resCheck.forceNext) { forceNext = true; } // Don't switch to false. + } + + if (!forceNext) { + if (!options.limitSrc.length || + isMatched(res, options.limitSrc, options.caseSensitive)) { break; } + if (options.limitMessage) { + limitMessage = replacePlaceholder(options.limitMessage, _getPhContent); + } + } + + addDisplay((limitMessage ? limitMessage + '\n' : '') + + replacePlaceholder(options.displaySrc + '', _getPhContent)); + } + return toBool(res, options); +} + +// for dev +exports._DBG_set_useExt = function(val) { _DBG_useExt = val; }; +exports._DBG_set_checkOptions = function(val) { _DBG_checkOptions = val; }; +exports._DBG_set_checkMethod = function(val) { _DBG_checkMethod = val; }; +exports._DBG_clearHistory = function() { lastInput = ''; inputHistory = []; }; + +// ------------------------------------ + +exports.setDefaultOptions = function(options) { + defaultOptions = margeOptions(true, options); + return margeOptions(true); // copy +}; + +exports.question = function(query, options) { + /* eslint-disable key-spacing */ + return getValidLine(margeOptions(margeOptions(true, options), { + display: query + })); + /* eslint-enable key-spacing */ +}; + +exports.prompt = function(options) { + var readOptions = margeOptions(true, options); + readOptions.display = readOptions.prompt; + return getValidLine(readOptions); +}; + +exports.keyIn = function(query, options) { + /* eslint-disable key-spacing */ + var readOptions = margeOptions(margeOptions(true, options), { + display: query, + keyIn: true, + keepWhitespace: true + }); + /* eslint-enable key-spacing */ + + // char list + readOptions.limitSrc = readOptions.limit.filter(function(value) { + var type = typeof value; + return type === 'string' || type === 'number'; + }) + .map(function(text) { return replacePlaceholder(text + '', getPhCharlist); }); + // pattern + readOptions.limit = escapePattern(readOptions.limitSrc.join('')); + + ['trueValue', 'falseValue'].forEach(function(optionName) { + readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) { + var type = typeof comp; + if (type === 'string' || type === 'number') { + comps = comps.concat((comp + '').split('')); + } else { comps.push(comp); } + return comps; + }, []); + }); + + readOptions.display = replacePlaceholder(readOptions.display + '', + function(param) { return getPhContent(param, readOptions); }); + + return toBool(_readlineSync(readOptions), readOptions); +}; + +// ------------------------------------ + +exports.questionEMail = function(query, options) { + if (query == null) { query = 'Input e-mail address: '; } + /* eslint-disable key-spacing */ + return exports.question(query, margeOptions({ + // -------- default + hideEchoBack: false, + // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address + limit: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, + limitMessage: 'Input valid e-mail address, please.', + trueValue: null, + falseValue: null + }, options, { + // -------- forced + keepWhitespace: false, + cd: false + })); + /* eslint-enable key-spacing */ +}; + +exports.questionNewPassword = function(query, options) { + /* eslint-disable key-spacing */ + var resCharlist, min, max, + readOptions = margeOptions({ + // -------- default + hideEchoBack: true, + mask: '*', + limitMessage: 'It can include: $\n' + + 'And the length must be: $', + trueValue: null, + falseValue: null, + caseSensitive: true + }, options, { + // -------- forced + history: false, + cd: false, + // limit (by charlist etc.), + phContent: function(param) { + return param === 'charlist' ? resCharlist.text : + param === 'length' ? min + '...' + max : null; + } + }), + // added: charlist, min, max, confirmMessage, unmatchMessage + charlist, confirmMessage, unmatchMessage, + limit, limitMessage, res1, res2; + /* eslint-enable key-spacing */ + options = options || {}; + + charlist = replacePlaceholder( + options.charlist ? options.charlist + '' : '$', getPhCharlist); + if (isNaN(min = parseInt(options.min, 10)) || typeof min !== 'number') { min = 12; } + if (isNaN(max = parseInt(options.max, 10)) || typeof max !== 'number') { max = 24; } + limit = new RegExp('^[' + escapePattern(charlist) + + ']{' + min + ',' + max + '}$'); + resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); + resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); + + confirmMessage = options.confirmMessage != null ? options.confirmMessage : + 'Reinput a same one to confirm it: '; + unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage : + 'It differs from first one.' + + ' Hit only the Enter key if you want to retry from first one.'; + + if (query == null) { query = 'Input new password: '; } + + limitMessage = readOptions.limitMessage; + while (!res2) { + readOptions.limit = limit; + readOptions.limitMessage = limitMessage; + res1 = exports.question(query, readOptions); + + readOptions.limit = [res1, '']; + readOptions.limitMessage = unmatchMessage; + res2 = exports.question(confirmMessage, readOptions); + } + + return res1; +}; + +function _questionNum(query, options, parser) { + var validValue; + function getValidValue(value) { + validValue = parser(value); + return !isNaN(validValue) && typeof validValue === 'number'; + } + /* eslint-disable key-spacing */ + exports.question(query, margeOptions({ + // -------- default + limitMessage: 'Input valid number, please.' + }, options, { + // -------- forced + limit: getValidValue, + cd: false + // trueValue, falseValue, caseSensitive, keepWhitespace don't work. + })); + /* eslint-enable key-spacing */ + return validValue; +} +exports.questionInt = function(query, options) { + return _questionNum(query, options, function(value) { return parseInt(value, 10); }); +}; +exports.questionFloat = function(query, options) { + return _questionNum(query, options, parseFloat); +}; + +exports.questionPath = function(query, options) { + /* eslint-disable key-spacing */ + var validPath, error = '', + readOptions = margeOptions({ + // -------- default + hideEchoBack: false, + limitMessage: '$Input valid path, please.' + + '$<( Min:)min>$<( Max:)max>', + history: true, + cd: true + }, options, { + // -------- forced + keepWhitespace: false, + limit: function(value) { + var exists, stat, res; + value = replaceHomePath(value, true); + error = ''; // for validate + // mkdir -p + function mkdirParents(dirPath) { + dirPath.split(/\/|\\/).reduce(function(parents, dir) { + var path = pathUtil.resolve((parents += dir + pathUtil.sep)); + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } else if (!fs.statSync(path).isDirectory()) { + throw new Error('Non directory already exists: ' + path); + } + return parents; + }, ''); + } + + try { + exists = fs.existsSync(value); + validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value); + // options.exists default: true, not-bool: no-check + if (!options.hasOwnProperty('exists') && !exists || + typeof options.exists === 'boolean' && options.exists !== exists) { + error = (exists ? 'Already exists' : 'No such file or directory') + + ': ' + validPath; + return false; + } + if (!exists && options.create) { + if (options.isDirectory) { + mkdirParents(validPath); + } else { + mkdirParents(pathUtil.dirname(validPath)); + fs.closeSync(fs.openSync(validPath, 'w')); // touch + } + validPath = fs.realpathSync(validPath); + } + if (exists && (options.min || options.max || + options.isFile || options.isDirectory)) { + stat = fs.statSync(validPath); + // type check first (directory has zero size) + if (options.isFile && !stat.isFile()) { + error = 'Not file: ' + validPath; + return false; + } else if (options.isDirectory && !stat.isDirectory()) { + error = 'Not directory: ' + validPath; + return false; + } else if (options.min && stat.size < +options.min || + options.max && stat.size > +options.max) { + error = 'Size ' + stat.size + ' is out of range: ' + validPath; + return false; + } + } + if (typeof options.validate === 'function' && + (res = options.validate(validPath)) !== true) { + if (typeof res === 'string') { error = res; } + return false; + } + } catch (e) { + error = e + ''; + return false; + } + return true; + }, + // trueValue, falseValue, caseSensitive don't work. + phContent: function(param) { + return param === 'error' ? error : + param !== 'min' && param !== 'max' ? null : + options.hasOwnProperty(param) ? options[param] + '' : ''; + } + }); + // added: exists, create, min, max, isFile, isDirectory, validate + /* eslint-enable key-spacing */ + options = options || {}; + + if (query == null) { query = 'Input path (you can "cd" and "pwd"): '; } + + exports.question(query, readOptions); + return validPath; +}; + +// props: preCheck, args, hRes, limit +function getClHandler(commandHandler, options) { + var clHandler = {}, hIndex = {}; + if (typeof commandHandler === 'object') { + Object.keys(commandHandler).forEach(function(cmd) { + if (typeof commandHandler[cmd] === 'function') { + hIndex[options.caseSensitive ? cmd : cmd.toLowerCase()] = commandHandler[cmd]; + } + }); + clHandler.preCheck = function(res) { + var cmdKey; + clHandler.args = parseCl(res); + cmdKey = clHandler.args[0] || ''; + if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); } + clHandler.hRes = + cmdKey !== '_' && hIndex.hasOwnProperty(cmdKey) ? + hIndex[cmdKey].apply(res, clHandler.args.slice(1)) : + hIndex.hasOwnProperty('_') ? hIndex._.apply(res, clHandler.args) : null; + return {res: res, forceNext: false}; + }; + if (!hIndex.hasOwnProperty('_')) { + clHandler.limit = function() { // It's called after preCheck. + var cmdKey = clHandler.args[0] || ''; + if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); } + return hIndex.hasOwnProperty(cmdKey); + }; + } + } else { + clHandler.preCheck = function(res) { + clHandler.args = parseCl(res); + clHandler.hRes = typeof commandHandler === 'function' ? + commandHandler.apply(res, clHandler.args) : true; // true for break loop + return {res: res, forceNext: false}; + }; + } + return clHandler; +} + +exports.promptCL = function(commandHandler, options) { + /* eslint-disable key-spacing */ + var readOptions = margeOptions({ + // -------- default + hideEchoBack: false, + limitMessage: 'Requested command is not available.', + caseSensitive: false, + history: true + }, options), + // -------- forced + // trueValue, falseValue, keepWhitespace don't work. + // preCheck, limit (by clHandler) + clHandler = getClHandler(commandHandler, readOptions); + /* eslint-enable key-spacing */ + readOptions.limit = clHandler.limit; + readOptions.preCheck = clHandler.preCheck; + exports.prompt(readOptions); + return clHandler.args; +}; + +exports.promptLoop = function(inputHandler, options) { + /* eslint-disable key-spacing */ + var readOptions = margeOptions({ + // -------- default + hideEchoBack: false, + trueValue: null, + falseValue: null, + caseSensitive: false, + history: true + }, options); + /* eslint-enable key-spacing */ + while (true) { if (inputHandler(exports.prompt(readOptions))) { break; } } + return; +}; + +exports.promptCLLoop = function(commandHandler, options) { + /* eslint-disable key-spacing */ + var readOptions = margeOptions({ + // -------- default + hideEchoBack: false, + limitMessage: 'Requested command is not available.', + caseSensitive: false, + history: true + }, options), + // -------- forced + // trueValue, falseValue, keepWhitespace don't work. + // preCheck, limit (by clHandler) + clHandler = getClHandler(commandHandler, readOptions); + /* eslint-enable key-spacing */ + readOptions.limit = clHandler.limit; + readOptions.preCheck = clHandler.preCheck; + while (true) { + exports.prompt(readOptions); + if (clHandler.hRes) { break; } + } + return; +}; + +exports.promptSimShell = function(options) { + /* eslint-disable key-spacing */ + return exports.prompt(margeOptions({ + // -------- default + hideEchoBack: false, + history: true + }, options, { + // -------- forced + prompt: (function() { + return IS_WIN ? + '$>' : + // 'user@host:cwd$ ' + (process.env.USER || '') + + (process.env.HOSTNAME ? + '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + + ':$$ '; + })() + })); + /* eslint-enable key-spacing */ +}; + +function _keyInYN(query, options, limit) { + var res; + if (query == null) { query = 'Are you sure? '; } + if ((!options || options.guide !== false) && (query += '')) { + query = query.replace(/\s*:?\s*$/, '') + ' [y/n]: '; + } + /* eslint-disable key-spacing */ + res = exports.keyIn(query, margeOptions(options, { + // -------- forced + hideEchoBack: false, + limit: limit, + trueValue: 'y', + falseValue: 'n', + caseSensitive: false + // mask doesn't work. + })); + // added: guide + /* eslint-enable key-spacing */ + return typeof res === 'boolean' ? res : ''; +} +exports.keyInYN = function(query, options) { return _keyInYN(query, options); }; +exports.keyInYNStrict = function(query, options) { return _keyInYN(query, options, 'yn'); }; + +exports.keyInPause = function(query, options) { + if (query == null) { query = 'Continue...'; } + if ((!options || options.guide !== false) && (query += '')) { + query = query.replace(/\s+$/, '') + ' (Hit any key)'; + } + /* eslint-disable key-spacing */ + exports.keyIn(query, margeOptions({ + // -------- default + limit: null + }, options, { + // -------- forced + hideEchoBack: true, + mask: '' + })); + // added: guide + /* eslint-enable key-spacing */ + return; +}; + +exports.keyInSelect = function(items, query, options) { + /* eslint-disable key-spacing */ + var readOptions = margeOptions({ + // -------- default + hideEchoBack: false + }, options, { + // -------- forced + trueValue: null, + falseValue: null, + caseSensitive: false, + // limit (by items), + phContent: function(param) { + return param === 'itemsCount' ? items.length + '' : + param === 'firstItem' ? (items[0] + '').trim() : + param === 'lastItem' ? (items[items.length - 1] + '').trim() : null; + } + }), + // added: guide, cancel + keylist = '', key2i = {}, charCode = 49 /* '1' */, display = '\n'; + /* eslint-enable key-spacing */ + if (!Array.isArray(items) || !items.length || items.length > 35) { + throw '`items` must be Array (max length: 35).'; + } + + items.forEach(function(item, i) { + var key = String.fromCharCode(charCode); + keylist += key; + key2i[key] = i; + display += '[' + key + '] ' + (item + '').trim() + '\n'; + charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1; + }); + if (!options || options.cancel !== false) { + keylist += '0'; + key2i['0'] = -1; + display += '[0] ' + + (options && options.cancel != null && typeof options.cancel !== 'boolean' ? + (options.cancel + '').trim() : 'CANCEL') + '\n'; + } + readOptions.limit = keylist; + display += '\n'; + + if (query == null) { query = 'Choose one from list: '; } + if ((query += '')) { + if (!options || options.guide !== false) { + query = query.replace(/\s*:?\s*$/, '') + ' [$]: '; + } + display += query; + } + + return key2i[exports.keyIn(display, readOptions).toLowerCase()]; +}; + +exports.getRawInput = function() { return rawInput; }; + +// ======== DEPRECATED ======== +function _setOption(optionName, args) { + var options; + if (args.length) { options = {}; options[optionName] = args[0]; } + return exports.setDefaultOptions(options)[optionName]; +} +exports.setPrint = function() { return _setOption('print', arguments); }; +exports.setPrompt = function() { return _setOption('prompt', arguments); }; +exports.setEncoding = function() { return _setOption('encoding', arguments); }; +exports.setMask = function() { return _setOption('mask', arguments); }; +exports.setBufferSize = function() { return _setOption('bufferSize', arguments); }; +}; +BundleModuleCode['http/https']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** BSSLAB, Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 18-05-15 by sbosse. + ** $RCS: $Id:$ + ** $VERSION: 1.3.1 + ** + ** $INFO: + ** + ** HTTP(S) File Server Module. + ** + * + ** $ENDOFINFO + */ + + +"use strict"; +var log = 0; + +var Io = Require('com/io'); +var util = Require('util'); +var http = Require('http'); +var https; try { https = require('https'); } catch (e) {} +var fs = Require('fs'); +var Comp = Require('com/compat'); +var Perv = Comp.pervasives; +var String = Comp.string; +var Array = Comp.array; +var Filename = Comp.filename; +var trace = Io.tracing; +var div = Perv.div; + +var isNode = Comp.isNodeJS(); + +/********************************************* +** HTTP File SERVER + *********************************************/ +/** Auxiliary File Server + * + * typeof @options = { sip,ipport,dir,verbose?,index?,log? } + * typeof File = constructor + */ +var HTTPSrv = function(options) { + if (!(this instanceof HTTPSrv)) return new HTTPSrv(options); + this.srv_ip = options.ip; // URL + this.srv_ipport = options.ipport; // URL:port + this.dir = options.dir; // Local file directory to be served + this.proto = options.proto||'http'; + this.https=undefined; + this.verbose=options.verbose||0; + this.index=options.index||'index.html'; + this.log=options.log||Io.log; + this.options=options; +}; + +HTTPSrv.prototype.init=function () { + var self=this, + stat=''; + this.dir=Filename.path_absolute(this.dir); + + function handler(request, response) { + //Io.inspect(request); + response.origin=request.headers.origin; + var path=String.prefix(request.url,'?'); + String.match(request.method,[ + ['GET',function() { + // TODO + if (self.verbose>2) self.log('[HTTP] Get: '+path); + var data=''; + try { + path=self.dir+'/'+Filename.path_normalize(path=='/'?self.index:path); + data=Io.read_file_bin(path); + stat='OK'; + } catch (e) { + data='File server: failed to read file '+path+' , '+util.inspect(e); + stat=data; + } + if (data == undefined) { + stat='Failed: no data read.'; + if (self.verbose>1) self.log('[HTTP] : Failed to get data for file '+path); + data='Error: Not found: '+path; + } + if (self.verbose>2) self.log('[HTTP] Get: '+request.url+' -> '+stat+' ['+(data?data.length:0)+']'); + + if (data!=undefined) { + //response.writeHead(200); + if (response.origin!=undefined) + response.writeHead(200,{'Access-Control-Allow-Origin': response.origin, + 'Access-Control-Allow-Credentials': 'true', + }); + else + // response.writeHead(200,{'Content-Type': 'text/html'}); + response.writeHead(200,{'Access-Control-Allow-Origin': '*'}); + response.write(data); + response.end(); + } + }] + ]) + }; + if (this.proto=='http') this.https = http.createServer(handler); + else if (this.proto=='https' && https) { + // Dummy certs, can be overriden by options + var _options={ + key:"-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCj1c0IRFOg2FZt\ncDtVgdQetk0RtOmU5ukMs09xw+irPHZeHmtu0gWy11yCHfqHwaqsrYdmnC1EAJsr\nlyBgdoiOn2MJNxW52/x7/I1ZVUke6p4OPyhNGaQHcCPmp/dBzMH9yY6K/HHPDqR/\ncDR1ait3ttpsvMxFT0baHZsxm/bajKUETSkGOW5gugq32egyjAKfHYSbbSY2zm8R\n3g1kluYKGvhjt/SvTPjcGYiTMwkyKBTuvpfZqxRArkaMlQdKKKBT+X3cY47ZD4I3\n0Hy8kirTeLvPf91THeI8pcTVU8a6qPryttOB9cRruzYJF4Z0sdnAzTPmVugPjRqn\n6BFPb0v1AgMBAAECggEASTMAHV5xwt6FlvXa/LQ58tLekjezWRzmKQ+AQkMWlFM6\nS4jp1SSu+R2xrkz4n2kO+YG6ikTjEIv4yDwIcjDjiF18ISTkZxr7ruXCvZQWTGLk\n5VagifoXyF75G1gWZ+a1Ec/ZCQ4LR0iyhGG8fm1GKIGhC4468giejltF+J9HZpNT\nJTcOZ/d5+WtwFa67o1vEqp8tIZ6bA6as9Jp4brmWifXSNZpGh3oIa6eQcVAl9b32\nxnh9F1oBwAz5D5TbHZ7RfiRsoUKeEprsJ8XEfVwO5R8xd7IMc5eXqDcZIZHJEWeV\nRqY0GOGRCdBWZydrHnyIpkCcJ9TytN4nx3OD0BsCYQKBgQDRrXDM88lVWW3htatu\nZiEZQIVkJ3Lj/S9/YByeU22UBUr7UZfWAQWEF7nhDnoa3NeQULMekgeH8O4Yd7Qd\nsGHm9DwiqPiyw2MRUU2eM074GiDpgy1K+oP669YHSMe+Vq5TnW1deNDuPYm4R85V\nGqG0rpG5yN6FojMmQsn+0qTxDQKBgQDIB7E8AMLFV7g1e8mor4uMa68GyScl1bFK\ngQ3Yoq+yLUV0zziFIcR9IwGxopC81QN4qXynb1JnlQyTASEPxJT558wFIUqRwnND\nxbwfwcNL5KVN7F1yTn55mmKHuxYGURs3Au8ErwQ+cdDu3bFsQxk8eBEob3OEzAd1\nxEW1yAh8iQKBgGaU4y3yS1rtULvvhHqTjrfrABe60RPHp7g6jmXLTT3wxPllttIl\nV8yDSxZXXdfMmc3qHWfka7jPX70quz0XMR6r+MvAPURAITS0wTOXyJfLOLTlz3/y\nRiW5wdF4gviVMd6Ik5v6YsVb6Af3YXPzfo+GJJdvNabNbxbV8DsyVS31AoGAGaTy\n0fB/B/HRCfpOxjOLPntnuwT64dzdl+GntshUohEvwGP4qQjFOg3M38sppyvgAA4q\njwS0mdb//7C7XlwjhU50V4wHFVzKjjvBfIjI0ugDUVQmPstVZ52lWCViE3k+dfUI\nU59keeT5lkYRwwFvMNNrz7VKKBJIOo7pKP72J5ECgYAygg6zNNUzrcUuuZHSpyEM\nx5uy5nmoD81RwlGS0N8m5SwN8jW+c7C7ROUdGakXV69zubMXzAsz4xMJTTSQBep2\nsNTNjlV71UikZhhx/spLZqaLb0ZIgCxj4dfNZS3XRh7Wi1bYuf2III+SUf4zitG0\nuGKHIqJgcSumSzjYGiMSAA==\n-----END PRIVATE KEY-----\n", + cert:"-----BEGIN CERTIFICATE-----\nMIIDITCCAgmgAwIBAgIJAKMxU7sE4FnyMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV\nBAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwHhcNMjIwNjA1MTEzMDMy\nWhcNMjUwMzI1MTEzMDMyWjAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPRXhhbXBs\nZS1Sb290LUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9XNCERT\noNhWbXA7VYHUHrZNEbTplObpDLNPccPoqzx2Xh5rbtIFstdcgh36h8GqrK2HZpwt\nRACbK5cgYHaIjp9jCTcVudv8e/yNWVVJHuqeDj8oTRmkB3Aj5qf3QczB/cmOivxx\nzw6kf3A0dWord7babLzMRU9G2h2bMZv22oylBE0pBjluYLoKt9noMowCnx2Em20m\nNs5vEd4NZJbmChr4Y7f0r0z43BmIkzMJMigU7r6X2asUQK5GjJUHSiigU/l93GOO\n2Q+CN9B8vJIq03i7z3/dUx3iPKXE1VPGuqj68rbTgfXEa7s2CReGdLHZwM0z5lbo\nD40ap+gRT29L9QIDAQABo1AwTjAdBgNVHQ4EFgQU763HyX73limLTXAwJ4SwpVGv\nD/AwHwYDVR0jBBgwFoAU763HyX73limLTXAwJ4SwpVGvD/AwDAYDVR0TBAUwAwEB\n/zANBgkqhkiG9w0BAQsFAAOCAQEAaO662eNFN2wWtMsUrITX8pwUAJRKxkFvxEQI\nt0HgtfxxvZeTgYjLeTv5U0Jmv8K+6QnNnFIfoc9CD0fFaETw9Z6a+mzliMnHwzZ2\ndI+3eahIcRZ86VuvwisJsDzpW1931Jz+/arIEZprTTSfCPkJs9U790W4wfA6/7Cc\nyZ57EWiug8sP/0NcgofKNNCiixlnlNhXJIOh7/7gXw+zJVdyoKUHMJMoii1UElzN\nVTm6YKSTiuOc+rOIbC4Aw5gQqRDtUqbf/Vcr2IEdOqlL7r4vW9urH+/p3sLVF20C\n8ssjea8dmHcrb5Omu0tUMbhzMM1/eHZS3iwcauu2VWzBDOOjeQ==\n-----END CERTIFICATE-----\n" + } + if (fs.existsSync(this.options.key)) { console.log('Loading '+_this.options.key); _options.key=fs.readFileSync(this.options.key,'utf8') }; + if (fs.existsSync(this.options.cert)) { console.log('Loading '+this.options.cert); _options.cert=fs.readFileSync(this.options,'utf8') }; + this.https = https.createServer(_options,handler); + } else throw "ENOTSUPPORTED"; + this.https.on("connection", function (socket) { + socket.setNoDelay(true); + }); + this.log('[HTTP] servicing directory: ' + this.dir); + +}; + +HTTPSrv.prototype.start=function () { + var self=this; + if (self.verbose) Io.out('[HTTP] Starting ..'); + this.https.listen(this.srv_ipport, function () { + self.log('[HTTP] listen: listening on *:' + self.srv_ipport); + }); +}; + +HTTPSrv.prototype.stop=function () { + if (this.https) this.https.close(); +} + +module.exports = { + /** Auxiliary File/HTML Server + * + */ + // typeof @options = {ip,ipport,dir,verbose?,index?} + HTTPSrv: HTTPSrv +} +}; +BundleModuleCode['geoip/geoip']=function (module,exports){ +/** + ** ============================== + ** OOOO O O OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2020 bLAB + ** $CREATED: 20-10-16 by sbosse. + ** $VERSION: 1.2.1 + ** + ** $INFO: + ** + ** GEO IP Database Services and http/https Server + ** Database source: GeoLiteCit + ** Compatibility: ip-api.com/json + ** Can be used as a proxy to ip-api.com to overcome ad-blockers! + ** + ** Look-up returns: + ** { + ** country: string, // country + ** countryCode: string, // countryCode + ** region: string, // region + ** city: string, // city + ** zip: string, // postal code + ** lat: string, // latitude + ** lon: string // longitude + ** }; + ** + ** $ENDOFINFO + */ +var fs = require("fs"); +var sat = Require('dos/ext/satelize'); + +var out = function (msg) { console.log('[GEOIP] '+msg) }; + +var geoip = module.exports = { + dir : process.cwd()||__dirname, + ipblocks : [], + locations : [], + midpoints : [], + numblocks : 0, + ready : 0, + verbose : 0, + config : function (a,v) { geoip[a]=v }, + + // Cold start, load and compile CSV DB + init : function (cb) { + out ('Loading '+ geoip.dir + "/GeoLiteCity-Blocks.csv .."); + var block = fs.createReadStream(geoip.dir + "/GeoLiteCity-Blocks.csv"); + out ('Loading '+ geoip.dir + "/GeoLiteCity-Location.csv .."); + var location = fs.createReadStream(geoip.dir + "/GeoLiteCity-Location.csv"); + var buffer1 = "",buffer2 = ""; + + block.addListener("data", function(data) { + buffer1 += data.toString().replace(/"/g, ""); + }); + + block.addListener("end", function() { + var entries = buffer1.split("\n"); + out ('Compiling GeoLiteCity-Blocks ..'); + for(var i=0; i= 1) { + n = Math.floor(n / 1.5); + geoip.midpoints.push(n); + } + + geoip.ready++; + if (cb && geoip.ready==2) cb(); + }); + + location.addListener("data", function(data) { + buffer2 += data.toString().replace(/"/g, ""); + }); + + location.addListener("end", function() { + + var entries = buffer2.split("\n"); + var locid=0; + out ('Compiling GeoLiteCity-Location ..'); + + for(var i=0; i= 1) { + n = Math.floor(n / 1.5); + geoip.midpoints.push(n); + } + geoip.ready++; + if (cb && geoip.ready==2) cb(); + }); + + location.addListener("data", function(data) { + buffer2 += data.toString(); + }); + + location.addListener("end", function() { + out ('Parsing GeoLiteCity-Location ..'); + geoip.locations = JSON.parse(buffer2); + out ('Parsing GeoLiteCity-Location done.'); + + geoip.ready++; + if (cb && geoip.ready==2) cb(); + }); + + }, + + // Search a matching GEO entry + lookup: function(ip) { + + if(geoip.ready<2) { + return { error: "GeoIP not ready" }; + } + + var ipl = iplong(ip); + + if(ipl == 0) { + return { error: "Invalid ip address " + ip + " -> " + ipl + " as integer" }; + } + + var found = find(ipl); + if (found) { + var loc = geoip.locations[found.i]; + return { + status:"success", + country: getCountryName(loc.cn), + countryCode:loc.cn, + city:loc.ci, + region:loc.re, + zip:loc.pc, + lon:loc.lo, + lat:loc.la, + } + } else return none; + }, + + // ip-api.com relay using satelize module! + proxy : function (options) { + options=options||{http:9999}; + var http = require('http'); + var https; + try { https = require('https') } catch (e) { } + if (options.http) { + var httpSrv = http.createServer(function (request,response) { + var url=request.url,body,header,sep,query,now, + remote=request.connection.remoteAddress; + if (request.url.length) + query=parseQueryString(request.remote||request.url); + else + query={} + if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1]; + if (geoip.verbose>0) print(url,query,remote); + switch (request.method) { + case 'GET': + sat.satelize({ip:query.ip||remote},function (err,info) { + if (err) { + return reply(response,JSON.stringify({error:err})) + } else { + if (request.headers && request.headers.host) info.proxy=request.headers.host; + return reply(response,JSON.stringify(info)) + } + }) + + break; + } + }); + httpSrv.on("connection", function (socket) { + // socket.setNoDelay(true); + }); + + httpSrv.on("error", function (err) { + out(err) + }); + + httpSrv.listen(options.http,function (err) { + out('HTTP server started on port '+options.http); + }); + }; + if (options.https && https && options.pem) { + // requires options.pem={key,cert} + var httpsSrv = https.createServer(options.pem,function (request,response) { + var url=request.url,body,header,sep,query,now, + remote=request.connection.remoteAddress; + if (request.url.length) + query=parseQueryString(request.remote||request.url); + else + query={} + if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1]; + if (geoip.verbose>0) print(url,query,remote); + switch (request.method) { + case 'GET': + sat.satelize({ip:query.ip||remote},function (err,info) { + if (err) { + return reply(response,JSON.stringify({error:err})) + } else { + if (request.headers && request.headers.host) info.proxy=request.headers.host; + return reply(response,JSON.stringify(info)) + } + }) + + break; + } + }); + httpsSrv.on("connection", function (socket) { + // socket.setNoDelay(true); + }); + + httpsSrv.on("error", function (err) { + out(err) + }); + + httpsSrv.listen(options.https,function (err) { + out('HTTPS server started on port '+options.https); + }); + }; + }, + + // Start an ip-api.com compatible web server API + server : function (options) { + options=options||{http:{address:'localhost',port:9999}}; + var http = require('http'); + var https; + try { https = require('https') } catch (e) { } + geoip.load(function (err) { + if (err) return; + if (options.http) { + var httpSrv = http.createServer(function (request,response) { + var url=request.url,body,header,sep,query,now, + remote=request.connection.remoteAddress; + if (request.url.length) + query=parseQueryString(request.remote||request.url); + else + query={} + if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1]; + if (geoip.verbose>0) print(url,query,remote); + switch (request.method) { + case 'GET': + reply(response,JSON.stringify(geoip.lookup(query.ip||remote))) + break; + } + }) + + httpSrv.on("connection", function (socket) { + // socket.setNoDelay(true); + }); + + httpSrv.on("error", function (err) { + out(err) + }); + + httpSrv.listen(options.http.port,function (err) { + out('HTTP server started on port '+options.http.port); + }); + } + if (options.https && https && options.pem) { + // requires options.pem={key,cert} + var httpsSrv = https.createServer(options.pem,function (request,response) { + var url=request.url,body,header,sep,query,now, + remote=request.connection.remoteAddress; + if (request.url.length) + query=parseQueryString(request.remote||request.url); + else + query={} + if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1]; + if (geoip.verbose>0) print(url,query,remote); + switch (request.method) { + case 'GET': + reply(response,JSON.stringify(geoip.lookup(query.ip||remote))) + break; + } + }) + + httpsSrv.on("connection", function (socket) { + // socket.setNoDelay(true); + }); + + httpsSrv.on("error", function (err) { + out(err) + }); + + httpsSrv.listen(options.http.port,function (err) { + out('HTTPS server started on port '+options.http.port); + }); + } + }); + }, + + // Save the DB in JSON format + save : function () { + out ('Saving '+geoip.dir + "/GeoLiteCity-Blocks.json .."); + var jsblocks = JSON.stringify(geoip.ipblocks); + fs.writeFileSync(geoip.dir + "/GeoLiteCity-Blocks.json", jsblocks, 'utf8'); + out ('Saving '+geoip.dir + "/GeoLiteCity-Location.json .."); + var jslocations = JSON.stringify(geoip.locations); + fs.writeFileSync(geoip.dir + "/GeoLiteCity-Location.json", jslocations, 'utf8'); + }, + +}; + +function iplong(ip) { + + if(!ip) { + return 0; + } + + ip = ip.toString(); + + if(isNaN(ip) && ip.indexOf(".") == -1) { + return 0; + } + + if(ip.indexOf(".") == -1) { + + try { + ip = parseFloat(ip); + return ip < 0 || ip > 4294967296 ? 0 : ip; + } + catch(s) { + } + } + + var parts = ip.split("."); + + if(parts.length != 4) { + return 0; + } + + var ipl = 0; + + for(var i=0; i<4; i++) { + parts[i] = parseInt(parts[i], 10); + + if(parts[i] < 0 || parts[i] > 255) { + return 0; + } + + ipl += parts[3-i] * (Math.pow(256, i)); + } + + return ipl > 4294967296 ? 0 : ipl; +} + +/** + * A qcuick little binary search + * @param ip the ip we're looking for + * @return {*} + */ +function find(ipl) { + + var mpi = 0; + var n = geoip.midpoints[0]; + var step; + var current; + var next; + var prev; + var nn; + var pn; + while(true) { + + step = geoip.midpoints[mpi]; + mpi++; + current = geoip.ipblocks[n]; + nn = n + 1; + pn = n - 1; + + next = nn < geoip.numblocks ? geoip.ipblocks[nn] : null; + prev = pn > -1 ? geoip.ipblocks[pn] : null; + + // take another step? + if(step > 0) { + if(!next || next.a < ipl) { + n += step; + } else { + n -= step; + } + + continue; + } + + // we're either current, next or previous depending on which is closest to ipl + var cd = Math.abs(ipl - current.a); + var nd = next && next.a < ipl ? ipl - next.a : 1000000000; + var pd = prev && prev.a < ipl ? ipl - prev.a : 1000000000; + + + // current wins + if(cd < nd && cd < pd) { + return current; + } + + // next wins + if(nd < cd && nd < pd) { + return next; + + } + + // prev wins + return prev; + } + return none; +} + +// https://gist.github.com/maephisto + +var isoCountries = { + 'AF' : 'Afghanistan', + 'AX' : 'Aland Islands', + 'AL' : 'Albania', + 'DZ' : 'Algeria', + 'AS' : 'American Samoa', + 'AD' : 'Andorra', + 'AO' : 'Angola', + 'AI' : 'Anguilla', + 'AQ' : 'Antarctica', + 'AG' : 'Antigua And Barbuda', + 'AR' : 'Argentina', + 'AM' : 'Armenia', + 'AW' : 'Aruba', + 'AU' : 'Australia', + 'AT' : 'Austria', + 'AZ' : 'Azerbaijan', + 'BS' : 'Bahamas', + 'BH' : 'Bahrain', + 'BD' : 'Bangladesh', + 'BB' : 'Barbados', + 'BY' : 'Belarus', + 'BE' : 'Belgium', + 'BZ' : 'Belize', + 'BJ' : 'Benin', + 'BM' : 'Bermuda', + 'BT' : 'Bhutan', + 'BO' : 'Bolivia', + 'BA' : 'Bosnia And Herzegovina', + 'BW' : 'Botswana', + 'BV' : 'Bouvet Island', + 'BR' : 'Brazil', + 'IO' : 'British Indian Ocean Territory', + 'BN' : 'Brunei Darussalam', + 'BG' : 'Bulgaria', + 'BF' : 'Burkina Faso', + 'BI' : 'Burundi', + 'KH' : 'Cambodia', + 'CM' : 'Cameroon', + 'CA' : 'Canada', + 'CV' : 'Cape Verde', + 'KY' : 'Cayman Islands', + 'CF' : 'Central African Republic', + 'TD' : 'Chad', + 'CL' : 'Chile', + 'CN' : 'China', + 'CX' : 'Christmas Island', + 'CC' : 'Cocos (Keeling) Islands', + 'CO' : 'Colombia', + 'KM' : 'Comoros', + 'CG' : 'Congo', + 'CD' : 'Congo, Democratic Republic', + 'CK' : 'Cook Islands', + 'CR' : 'Costa Rica', + 'CI' : 'Cote D\'Ivoire', + 'HR' : 'Croatia', + 'CU' : 'Cuba', + 'CY' : 'Cyprus', + 'CZ' : 'Czech Republic', + 'DK' : 'Denmark', + 'DJ' : 'Djibouti', + 'DM' : 'Dominica', + 'DO' : 'Dominican Republic', + 'EC' : 'Ecuador', + 'EG' : 'Egypt', + 'SV' : 'El Salvador', + 'GQ' : 'Equatorial Guinea', + 'ER' : 'Eritrea', + 'EE' : 'Estonia', + 'ET' : 'Ethiopia', + 'FK' : 'Falkland Islands (Malvinas)', + 'FO' : 'Faroe Islands', + 'FJ' : 'Fiji', + 'FI' : 'Finland', + 'FR' : 'France', + 'GF' : 'French Guiana', + 'PF' : 'French Polynesia', + 'TF' : 'French Southern Territories', + 'GA' : 'Gabon', + 'GM' : 'Gambia', + 'GE' : 'Georgia', + 'DE' : 'Germany', + 'GH' : 'Ghana', + 'GI' : 'Gibraltar', + 'GR' : 'Greece', + 'GL' : 'Greenland', + 'GD' : 'Grenada', + 'GP' : 'Guadeloupe', + 'GU' : 'Guam', + 'GT' : 'Guatemala', + 'GG' : 'Guernsey', + 'GN' : 'Guinea', + 'GW' : 'Guinea-Bissau', + 'GY' : 'Guyana', + 'HT' : 'Haiti', + 'HM' : 'Heard Island & Mcdonald Islands', + 'VA' : 'Holy See (Vatican City State)', + 'HN' : 'Honduras', + 'HK' : 'Hong Kong', + 'HU' : 'Hungary', + 'IS' : 'Iceland', + 'IN' : 'India', + 'ID' : 'Indonesia', + 'IR' : 'Iran, Islamic Republic Of', + 'IQ' : 'Iraq', + 'IE' : 'Ireland', + 'IM' : 'Isle Of Man', + 'IL' : 'Israel', + 'IT' : 'Italy', + 'JM' : 'Jamaica', + 'JP' : 'Japan', + 'JE' : 'Jersey', + 'JO' : 'Jordan', + 'KZ' : 'Kazakhstan', + 'KE' : 'Kenya', + 'KI' : 'Kiribati', + 'KR' : 'Korea', + 'KW' : 'Kuwait', + 'KG' : 'Kyrgyzstan', + 'LA' : 'Lao People\'s Democratic Republic', + 'LV' : 'Latvia', + 'LB' : 'Lebanon', + 'LS' : 'Lesotho', + 'LR' : 'Liberia', + 'LY' : 'Libyan Arab Jamahiriya', + 'LI' : 'Liechtenstein', + 'LT' : 'Lithuania', + 'LU' : 'Luxembourg', + 'MO' : 'Macao', + 'MK' : 'Macedonia', + 'MG' : 'Madagascar', + 'MW' : 'Malawi', + 'MY' : 'Malaysia', + 'MV' : 'Maldives', + 'ML' : 'Mali', + 'MT' : 'Malta', + 'MH' : 'Marshall Islands', + 'MQ' : 'Martinique', + 'MR' : 'Mauritania', + 'MU' : 'Mauritius', + 'YT' : 'Mayotte', + 'MX' : 'Mexico', + 'FM' : 'Micronesia, Federated States Of', + 'MD' : 'Moldova', + 'MC' : 'Monaco', + 'MN' : 'Mongolia', + 'ME' : 'Montenegro', + 'MS' : 'Montserrat', + 'MA' : 'Morocco', + 'MZ' : 'Mozambique', + 'MM' : 'Myanmar', + 'NA' : 'Namibia', + 'NR' : 'Nauru', + 'NP' : 'Nepal', + 'NL' : 'Netherlands', + 'AN' : 'Netherlands Antilles', + 'NC' : 'New Caledonia', + 'NZ' : 'New Zealand', + 'NI' : 'Nicaragua', + 'NE' : 'Niger', + 'NG' : 'Nigeria', + 'NU' : 'Niue', + 'NF' : 'Norfolk Island', + 'MP' : 'Northern Mariana Islands', + 'NO' : 'Norway', + 'OM' : 'Oman', + 'PK' : 'Pakistan', + 'PW' : 'Palau', + 'PS' : 'Palestinian Territory, Occupied', + 'PA' : 'Panama', + 'PG' : 'Papua New Guinea', + 'PY' : 'Paraguay', + 'PE' : 'Peru', + 'PH' : 'Philippines', + 'PN' : 'Pitcairn', + 'PL' : 'Poland', + 'PT' : 'Portugal', + 'PR' : 'Puerto Rico', + 'QA' : 'Qatar', + 'RE' : 'Reunion', + 'RO' : 'Romania', + 'RU' : 'Russian Federation', + 'RW' : 'Rwanda', + 'BL' : 'Saint Barthelemy', + 'SH' : 'Saint Helena', + 'KN' : 'Saint Kitts And Nevis', + 'LC' : 'Saint Lucia', + 'MF' : 'Saint Martin', + 'PM' : 'Saint Pierre And Miquelon', + 'VC' : 'Saint Vincent And Grenadines', + 'WS' : 'Samoa', + 'SM' : 'San Marino', + 'ST' : 'Sao Tome And Principe', + 'SA' : 'Saudi Arabia', + 'SN' : 'Senegal', + 'RS' : 'Serbia', + 'SC' : 'Seychelles', + 'SL' : 'Sierra Leone', + 'SG' : 'Singapore', + 'SK' : 'Slovakia', + 'SI' : 'Slovenia', + 'SB' : 'Solomon Islands', + 'SO' : 'Somalia', + 'ZA' : 'South Africa', + 'GS' : 'South Georgia And Sandwich Isl.', + 'ES' : 'Spain', + 'LK' : 'Sri Lanka', + 'SD' : 'Sudan', + 'SR' : 'Suriname', + 'SJ' : 'Svalbard And Jan Mayen', + 'SZ' : 'Swaziland', + 'SE' : 'Sweden', + 'CH' : 'Switzerland', + 'SY' : 'Syrian Arab Republic', + 'TW' : 'Taiwan', + 'TJ' : 'Tajikistan', + 'TZ' : 'Tanzania', + 'TH' : 'Thailand', + 'TL' : 'Timor-Leste', + 'TG' : 'Togo', + 'TK' : 'Tokelau', + 'TO' : 'Tonga', + 'TT' : 'Trinidad And Tobago', + 'TN' : 'Tunisia', + 'TR' : 'Turkey', + 'TM' : 'Turkmenistan', + 'TC' : 'Turks And Caicos Islands', + 'TV' : 'Tuvalu', + 'UG' : 'Uganda', + 'UA' : 'Ukraine', + 'AE' : 'United Arab Emirates', + 'GB' : 'United Kingdom', + 'US' : 'United States', + 'UM' : 'United States Outlying Islands', + 'UY' : 'Uruguay', + 'UZ' : 'Uzbekistan', + 'VU' : 'Vanuatu', + 'VE' : 'Venezuela', + 'VN' : 'Viet Nam', + 'VG' : 'Virgin Islands, British', + 'VI' : 'Virgin Islands, U.S.', + 'WF' : 'Wallis And Futuna', + 'EH' : 'Western Sahara', + 'YE' : 'Yemen', + 'ZM' : 'Zambia', + 'ZW' : 'Zimbabwe' +}; + +function getCountryName (countryCode) { + if (isoCountries.hasOwnProperty(countryCode)) { + return isoCountries[countryCode]; + } else { + return countryCode; + } +} +/* +** Parse query string '?attr=val&attr=val... and return parameter record +*/ +function parseQueryString( url ) { + var queryString = url.substring( url.indexOf('?') + 1 ); + if (queryString == url) return []; + var params = {}, queries, temp, i, l; + // Split into key/value pairs + queries = queryString.split("&"); + // Convert the array of strings into an object + for ( i = 0, l = queries.length; i < l; i++ ) { + temp = queries[i].split('='); + if (temp[1]==undefined) temp[1]='true'; + params[temp[0]] = temp[1].replace('%20',' '); + } + return params; +} + +function reply(response,body,mimetype) { + header={'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Credentials': 'true', + 'Content-Type': mimetype||'text/plain'}; + + response.writeHead(200,header); + response.write(body); + response.end(); +} +}; +BundleModuleCode['dos/ext/satelize']=function (module,exports){ +/* +* satelize - v0.1.3 +* +* (c) 2013 Julien VALERY https://github.com/darul75/satelize, 2018-2020 modfied by bLAB Dr. Stefan Bosse +* +* Usage: satelize(ip:string|undefined,function (err,info)) +* +* License: MIT +*/ + + +var http=Require("http"), + serviceHost="ip-api.com", + servicePort=80, + servicePath="/json", + serviceJSONP=""; + +function Satelize(options){ + this.init() +} + +Satelize.prototype.init=function(options){ + return this +} + +Satelize.prototype.satelize=function(a,b){ + var c=(a.ip?"/"+a.ip:"")+(a.JSONP?serviceJSONP:""), + d=a.timeout||1e3, + h=a.url||a.host||serviceHost, + p=a.port||servicePort, + e, + f; + if (!http) return b('ENOTSUPPORTED',null); + if (!http.xhr && http.request) { + // server + e={hostname:h,path:servicePath+c,method:"GET",port:p}; + f=http.request(e,function(a){ + a.setEncoding("utf8"); + var c=""; + a.on("data",function(a){c+=a}), + a.on("end",function(){ + try { + return b(null,JSON.parse(c)); + } catch (err) { + b(err.toString()+', '+e.hostname+':'+e.port); + } + }) + }); + return f.on("error",function(a){return b(a)}), + f.setTimeout(d,function(){return b(new Error("timeout"))}), + f.end(),this; + } else { + // Browser + e={uri:a.url?a.url:(a.proto?a.proto:'http')+'://'+h+':'+p+servicePath+c, + method:"GET", + headers:{}}; + console.log(e); + http.request(e,function(err,xhr,body){ + if (err) return b(err); + else try { b(null,JSON.parse(body)); } catch (err) { b(err.toString()+', '+e.uri) } + }) + return this; + } +} + +var sat = new Satelize +module.exports=sat; +}; +BundleModuleCode['top/rendezvous']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM.io + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $CREATED: 30-11-17 by sbosse. + ** $RCS: $Id: rendezvous.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.2.2 + ** + ** $INFO: + ** + ** Simple public P2P rendezvous (pairing) server with associative naming service. + ** Primary use: Enabling JAM2JAM connections with JAMs behind NATs + ** (hosts in different private networks). + ** Uses hole-punching technique to overcome router limitations occuring with NAT traversal of + ** UDP streams. + ** + ** + ** A host stores tokens in a cache, One token is removed on each pairing request or if the lifetime + ** of the token has expired. There is an upper limit of tokens that are cached. + ** + ** $ENDOFINFO + */ +global.config={simulation:false,nonetwork:false}; +var Comp = Require('com/compat'); +var Io = Require('com/io'); +var Chan = Require('jam/chan'); +var Amp = Require('jam/amp'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var sprintf = Comp.printf.sprintf; +var ipnet = Require('net'); +var dgram = Require('dgram'); +var sprintf = Comp.printf.sprintf; + +var onexit=false; +var start=false; + +var options = { + connport:Net.uniqport(), + http: {address:'134.102.50.219',port:80}, + ip : {address:'0.0.0.0',port:10001}, + verbose:1, + CACHETMO:60000, + MAXTOKENS: 4, // maximal cached register tokens from each host + TIMER:200, + TRIES:3, + version:'1.2.2' +} + + + +var usage = function (exit) { + out('Usage: rendezvous [-h] [verbose:#] [port:#]'); + if (exit) onexit=true,start=false; +} + +if (process.argv[1].indexOf('ampbroker')!=-1 || process.argv[1].indexOf('rendezvous')!=-1) + start=true,process.argv.forEach(function (arg) { + var tokens=arg.split(':'); + if (arg=='-h' || arg=='-help') usage(true); + if (tokens.length!=2) return; + switch (tokens[0]) { + case 'verbose': options.verbose=Number(tokens[1]); break; + case 'port': options.ip.port=Number(tokens[1]); break; + } +}); + +// Use remote TCP connection to get this host IP (private address if behind NAT) +function getNetworkIP(callback) { + var socket = ipnet.createConnection(options.http.port, options.http.address); + socket.on('connect', function() { + callback(undefined, socket.address().address); + socket.end(); + }); + socket.on('error', function(e) { + callback(e, 'error'); + }); +} + +function timestamp() { + return Date.now(); +} + +// typeof @ip = { address:string, port:number } +function Broker (_options) { + var self=this; + if (!(this instanceof Broker)) return new Broker(_options); + + this.options=options; + for (var p in _options) if (_options[p]!=undefined) options[p]=_options[p]; + + this.out = function (msg) {console.log('[RED '+Chan.addr2url(options.ip)+' '+Io.Time()+'] '+msg)}; + + this.udp = dgram.createSocket('udp4'); + + // The rendezvous cache (register tokens) + this.clients = {}; + + function doUntil(interval, fn, cond, arg) { + if (cond()) return; + fn(arg); + return setTimeout(function() { + doUntil(interval, fn, cond, arg); + }, interval); + } + + // Compare two client db entries + function eq(client1,client2) { + var p; + if (!client1 || !client2 || + client1.name != client2.name) return false; + for(p in client1.connections) { + if (client1.connections[p].address != client2.connections[p].address || + client1.connections[p].port != client2.connections[p].port) return false; + } + return true; + } + + // Store and lookup + function store(name,client) { + client.time=timestamp() + // Note: Old obsolete tokens of a client (changed IP/PORT) must be flushed! + if (!self.clients[name] || !eq(client,self.clients[name][0])) self.clients[name]=[client]; + else if (self.clients[name].length1) self.out('# sent '+msg.type+' to '+host+':'+port); + if (cb) cb(); + } + }); + } + + this.udp.on('listening', function() { + var address = self.udp.address(); + if (options.verbose) self.out(sprintf ('# listening [%s:%s]', address.address, address.port)); + }); + + this.udp.on('message', function(message, rinfo) { + var buf = Buf.Buffer(),reply, + port,data,msg,obj,i,j,newreg=false; + + Buf.buf_init(buf); + Buf.buf_of_str(buf,message); + msgtyp=Buf.buf_get_int16(buf); + + if (msgtyp != Amp.AMMessageType.AMMCONTROL) { + if (options.verbose) + self.out(sprintf('# Invalid message from %s:%s', + rinfo.address, rinfo.port)); + return; + } + port = Buf.buf_get_port(buf); + data = Buf.buf_get_string(buf); + + try { + msg = JSON.parse(data); + } catch (e) { + self.out(sprintf('! Couldn\'t parse data (%s):\n%s', e, data)); + return; + } + + switch (msg.type) { + case 'lookup': + reply=search(msg.data); + console.log(msg.data,reply) + send(rinfo.address,rinfo.port,{type:'lookup',from:'BROKER', data:reply, path:msg.data}); + break; + + case 'register': + obj={ + name: msg.name, + connections: { + local: msg.linfo, + public: rinfo + }, + }; + // copy optional attributes + for(p in msg) { + switch (p) { + case 'name': + case 'linfo': + case 'type': + continue; + default: + obj[p]=msg[p]; + } + } + store(msg.name,obj); + if (self.clients[msg.name].length==1) newreg=1; + if (options.verbose && newreg) + self.out(sprintf('# Client registered: P %s@[%s:%s | L %s:%s]', msg.name, + rinfo.address, rinfo.port, msg.linfo.address, msg.linfo.port)); + send(rinfo.address,rinfo.port,{type:'registered',from:'BROKER'}); + break; + + case 'pair': + // Pair request from one client + var couple = [lookup(msg.from,rinfo), lookup(msg.to,rinfo) ], counter=options.TRIES; + if (options.verbose>1) + self.out(sprintf('# Pair request: %s@[%s:%s] to %s [%b,%b]', msg.from, + rinfo.address, rinfo.port, msg.to, couple[0]!=undefined,couple[1]!=undefined)); + else if (options.verbose && couple[0]!=undefined && couple[1]!=undefined) + self.out(sprintf('# Pairing %s@[%s:%d] and %s@[%s:%d]', + msg.from,couple[0].connections.public.address, couple[0].connections.public.port, + msg.to,couple[1].connections.public.address, couple[1].connections.public.port)); + + for (i=0; iconn.time+self.options.CACHETMO; + }); + } + },this.options.CACHETMO); +} + +Broker.prototype.start = function () { + var self=this; + getNetworkIP(function (err,addr) { + if (!err) { + self.options.ip.address=addr; + self.out('# got IP '+addr); + } + }); + this.udp.bind(options.ip.port,options.ip.address); +} + +Broker.prototype.stop = function () { + if (this.gc) clearInterval(this.gc),this.gc=undefined; +} + +if (start) { + var bs = new Broker(options); + bs.start() +} + +module.exports = { Broker:Broker }; +}; +BundleModuleCode['jam/chan']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 09-02-16 by sbosse. + ** $RCS: $Id: chan.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.16.1 + ** + ** $INFO: + ** + ** JavaScript AIOS Agent Node Communication Module offering P2P communication with another nodes + ** + ** 1. Virtual Link: Connecting virtual (logical) nodes using buffers + ** + ** 2. Physical Link: Connecting physical nodes (on the same physical host or remote hosts) + ** using AMP protocol and IP communication (including endpoint pairing across NAT routers + ** using a rendezvous broker service) + ** + ** 3. Physical Link: Connecting node processes (in a cluster on the same physical host) using process streams + ** + ** For IP-based communication ports an internal IP router is provided offering operation + ** of multiple ports and connections. + ** + ** Communciation link object provided by 1.-3.: + ** + ** type link = { + ** on: method (@event,@handler) with @event={'agent'|'signal'|'class'}, + ** send: method (@msg) with @msg:{agent:string|object,to:dir}|{signal:string|object},to:dir}, + ** status: method (dir) -> boolean, + ** count: method () -> number is returning number of received (phy only) and sent bytes, + ** connect?:method (@to), + ** disconnect?:method (@to), + ** start?:method, + ** stop?:method + ** } + ** + ** + ** Events, emitter: link+ link- error(err="link"|string,arg?) + ** + ** + ** TODO: + ** - Phy capability protected communication and operations + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); +var Amp = Require('jam/amp'); +var Sec = Require('jam/security'); + +var options = { + debug:{}, + verbose:1, + version:'1.15.6' +} +module.exports.options=options; + +var SLINK = { + INIT:'INIT', + INITED:'INITED', + RUNNING:'RUNNING' +} + +/******************** + * Virtual Circuit + ******************** + */ + +var virtual= function (node1,node2,dir,options) { + var self=this; + this.node1=node1; + this.node2=node2; + this.dir=dir; // node1 -> node2 + this.buffer1=[]; + this.buffer2=[]; + this.count1={rcv:0,snd:0}; + this.count2={rcv:0,snd:0}; + this.compress=options.compress; + + /* NEWCOMM */ + this.handler1=[]; + this.handler2=[]; + + // External API + this.link1 = { + control : function (msg,to,callback) { + // TODO + }, + on: function (event,callback) { + var data; + self.handler1[event]=callback; + if (event=='agent' && self.buffer2.length>0) { + // Agent receiver + data=Comp.array.pop(self.buffer2); + if (self.compress) data=Lz.decompress(data); + callback(data); + } + }, + + send: function (msg) { + var data; + if (msg.agent) { + // Agent migration + data=msg.agent; + if (self.compress) data=Lz.compress(data); + if (self.handler2.agent) self.handler2.agent(self.compress?Lz.decompress(data):data); + else self.buffer1.push(data); + if (data.length) self.count1.snd += data.length; else self.count1.snd++; + } else if (msg.signal) { + // Signal propagation - signals are not queued + data=msg.signal; + if (data.length) self.count1.snd += data.length; else self.count1.snd++; + if (self.handler2.signal) self.handler2.signal(data); + } + }, + count: function () {return self.count1.snd}, + status: function () {return true}, // Linked? + virtual:true + + } + + this.link2 = { + control : function (msg,to,callback) { + // TODO + }, + on: function (event,callback) { + var data; + self.handler2[event]=callback; + if (event=='agent' && self.buffer1.length>0) { + // Agent receiver + data=Comp.array.pop(self.buffer1); + if (self.compress) data=Lz.decompress(data); + callback(data); + } + }, + + send: function (msg) { + var data; + if (msg.agent) { + // Agent migration + data=msg.agent; + if (self.compress) data=Lz.compress(data); + if (self.handler1.agent) self.handler1.agent(self.compress?Lz.decompress(data):data); + else self.buffer2.push(data); + if (data.length) self.count2.snd += data.length; else self.count2.snd++; + } else if (msg.signal) { + // Signal propagation - signals are not queued + data=msg.signal; + if (data.length) self.count2.snd += data.length; else self.count2.snd++; + if (self.handler1.signal) self.handler1.signal(data); + } + }, + count: function () {return self.count2.snd}, + status: function () {return true}, // Linked? + virtual:true + + } +}; + +virtual.prototype.init = function () {}; +virtual.prototype.start = function () {}; +virtual.prototype.stop = function () {}; + +var Virtual = function (node1,node2,dir,options) { + var obj=new virtual(node1,node2,dir,options); + return obj; +} + +module.exports.Virtual=Virtual; +module.exports.current=function (module) { current=module.current; Aios=module; Amp.current(module); }; + + + + +if (global.config.nonetwork) return; +/******************************* PHYSICAL *************************************/ + + +/********************* + ** Physical Circuit + ********************* + * + * Using AMP or process stream connections (TODO) + * typeof options={ + * broker?:url is UDP hole punching rendezvous broker + * compress?:boolean, + * device?:string, + * name?:string is optional name of the comm. port e.g. the JAM node name, + * on?: { } is event handler object, + * oneway?:boolean, + * out?:function, + * proto?:'udp'|'tcp'|'http'|'hardware', + * rcv:url is this endpoint address, + * secure?:port string, + * snd?:url is remote endpoint address, + * stream?:boolean, + * verbose? + * .. + * } + * with type url = ":" | ":" | "" + * and type ipport = (1-65535) | "*" + */ +var physical= function (node,dir,options) { + var self=this; + options=checkOptions(options,{}); + this.options=options; + + this.ip=none; + if (options.rcv) this.ip=url2addr(options.rcv); + else this.ip={address:Amp.options.localhost,port:undefined}; + if (options.proto && this.ip) this.ip.proto=options.proto; + + this.node=node; + this.dir=dir; // outgoing port (node -> dst), e.g., IP + this.count=0; + this.broker=options.broker; + + this.mode=this.options.compress?Amp.AMMode.AMO_COMPRESS:0; + + this.state = SLINK.INIT; + this.linked = 0; + + this.events = []; + this.callbacks = []; + + this.out=function (msg,async) { async?Aios.logAsync(msg):Aios.log(msg) }; + + if (this.ip.parameter && this.ip.parameter.secure) { + this.options.secure=Sec.Port.ofString(this.ip.parameter.secure); + delete this.ip.parameter; + this.dir.ip=addr2url(this.ip); + } + + this.amp= Amp.Amp({ + broker:options.broker?url2addr(options.broker,this.ip.address):undefined, + dir:this.dir, + keepAlive : options.keepAlive, + mode:this.options.mode, + multicast:this.options.multicast, + name:this.options.name, + node:node, + nodeid:this.options.nodeid, + oneway:this.options.oneway, + proto:this.options.proto, + pem:this.options.pem, + rcv:this.ip, + secure:this.options.secure, + snd:options.snd?url2addr(options.snd):undefined, + sharedSocket:options.sharedSocket, + sock:options.sock, + verbose:options.verbose, + }); + + // External API + this.link = { + // Control RPC + // STD_STATUS/PS_STUN/... + control : function (msg,to,callback) { + var buf,data,addr=to?url2addr(to):{}; + buf=Buf.Buffer(); + msg.tid=Comp.random.int(65536/2); + self.callbacks[msg.tid]=callback; + Buf.buf_put_int16(buf,msg.tid); + Buf.buf_put_string(buf,JSON.stringify(msg.args||{})); + self.amp.request(msg.cmd, + buf, + self.amp.mode & Amp.AMMode.AMO_MULTICAST? addr:undefined); + + }, + on: function (event,callback) { + self.events[event]=callback; + }, + send: function (msg,to) { + var buf,data,addr=to?url2addr(to):{}; + if (msg.agent) { + data=msg.agent; // string of JSON+ + buf=Buf.Buffer(); + if (self.mode & Amp.AMMode.AMO_COMPRESS) data=Lz.compress(data); + Buf.buf_put_string(buf,data); + // function request(cmd:integer,msg:Buffer,snd?:address) + self.amp.request(Command.PS_MIGRATE, + buf, + self.amp.mode & Amp.AMMode.AMO_MULTICAST? addr:undefined); + } else if (msg.signal) { + data=msg.signal; // string of JSON + // Signal propagation + buf=Buf.Buffer(); + if (self.mode & Amp.AMMode.AMO_COMPRESS) data=Lz.compress(data); + Buf.buf_put_string(buf,data); + // function request(cmd:integer,msg:Buffer,snd?:address) + self.amp.request(Command.PS_SIGNAL, + buf, + self.amp.mode & Amp.AMMode.AMO_MULTICAST? addr:undefined); + } + }, + count: function () {return self.amp.count.rcv+self.amp.count.snd}, + status : function (to) { + if (self.amp) { + switch (to) { + case '%': + // P2P link?; return remote node/ip/link id + return self.amp.status(to); + break; + default: + if (to) to=url2addr(to); + return to?self.amp.status(to.address,to.port):self.amp.status(); + } + } + }, // Linked? + stats : function () { + return { + transferred:(self.amp.count.rcv+self.amp.count.snd), + linked:self.linked + } + }, + ip:this.ip, + mode:this.amp.mode + } + + /** Connect to remote endpoint with optional capability key protection + * typeof @to = "" | "" | ":" | "" + * typeof @key = string "[]()[]" + */ + this.link.connect=function (to,key) { + // allow url2addr DNS lookup + url2addr(to,self.ip.address,function (addr) { + self.amp.link(addr,true,key); + }) + }; + + // Disconnect remote endpoint + this.link.disconnect=function (to) { + var tokens; + if (!to){ + if (self.amp.snd && self.amp.snd.address && self.amp.snd.port) + self.amp.unlink(self.amp.snd); + } else { + var addr=url2addr(to,self.ip.address); + self.amp.unlink(addr); + } + }; + this.link.init=function (cb) { + if (self.state!=SLINK.INIT) return cb?cb():null; + self.state=SLINK.INITED; + return self.amp.init(cb); + } + this.link.start=function (cb) { + if (self.state!=SLINK.INITED) return cb?cb():null; + self.state=SLINK.RUNNING; + return self.amp.start(cb); + } + this.link.stop=function (cb) { + if (self.state!=SLINK.RUNNING) return cb?cb():null; + self.state=SLINK.INITED; + return self.amp.stop(cb); + } + + if (this.broker) this.link.lookup = function (path,callback) { + if (self.amp.lookup) self.amp.lookup(path,callback); + else if (callback) callback([]); + } + // Install route notification propagation to router (if installed) + this.amp.on('route+',function (url,node,remote) { + if (remote) self.ip.public=remote; + if (self.router) self.router.add(url,self.link,node); + self.emit('link+',url,node); + Aios.emit('link+',url,node); + self.linked++; + }); + this.amp.on('route-',function (url) { + if (self.router) self.router.delete(url,self.link); + self.emit('link-',url); + Aios.emit('link-',url); + self.linked--; + }); + this.amp.on('error',function (err,arg) { + self.emit('error',err,arg); + }); + if (options.on) { + for(var p in options.on) this.on(p,options.on[p]); + } + // Register message receiver handler with STD/PS RPC + this.amp.receiver(function (handler) { + var code,name,env,agentid,stat,obj,buf,status,tid; + if (!handler) return; + if (self.options.verbose>2) { + self.out('AMP: got request: '+ Io.inspect(handler),true); + }; + switch (handler.cmd) { + case Command.PS_MIGRATE: + code = Buf.buf_get_string(handler.buf); + // console.log(code); + // console.log(myJam.amp.url(handler.remote)) + if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code); + if (self.events.agent) self.events.agent(code,false,handler.remote); + break; + case Command.PS_CREATE: + code = Buf.buf_get_string(handler.buf); + // console.log(code); + // console.log(myJam.amp.url(handler.remote)) + if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code); + if (self.events.agent) self.events.agent(code,true); + break; + case Command.PS_WRITE: + name = Buf.buf_get_string(handler.buf); + code = Buf.buf_get_string(handler.buf); + env = Buf.buf_get_string(handler.buf); + // console.log(code); + // console.log(myJam.amp.url(handler.remote)) + if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code); + obj={}; + try {eval("env = "+env)} catch (e) {}; + obj[name]={ + fun:code, + env:env + } + if (self.events['class']) self.events['class'](obj); + break; + case Command.PS_SIGNAL: + // TODO + code = Buf.buf_get_string(handler.buf); + // console.log(code); + if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code); + if (self.events.signal) self.events.signal(code,handler.remote); + break; + case Command.PS_STUN: + // Kill an agent (or all) + code = Buf.buf_get_string(handler.buf); + break; + + // Control Mesages + case Command.STD_STATUS: + // Send status of requested object (node: process table..) + tid = Buf.buf_get_int16(handler.buf); + code = Buf.buf_get_string(handler.buf); + code = JSON.parse(code); + status = {}; + if (typeof code == 'string') { + switch (code) { + case 'node': + case 'links': + case 'ports': + case 'agents': + status=self.node.std_status(code); break; + } + } + buf=Buf.Buffer(); + Buf.buf_put_int16(buf,tid); + Buf.buf_put_string(buf,JSON.stringify(status)); + self.amp.reply(Command.STD_MONITOR, // Hack: Reply to STD_STATUS request + buf, + self.amp.mode & Amp.AMMode.AMO_MULTICAST? handler.remote:undefined); + break; + case Command.STD_INFO: + // Send info about .. (agent ..) + tid = Buf.buf_get_int16(handler.buf); + code = JSON.parse(Buf.buf_get_string(handler.buf)); + status = {}; + if (typeof code == 'string') { + switch (code) { + case 'node' : status=self.node.std_info(); break; + } + } else if (typeof code == 'object') { + if (code.node) { + // get info of a specific node; probably not this but maybe attached? + if (code.node==self.node.id) status=self.node.std_info(); + else { + // one hop is possible if destination node is connected to this node, too + var to = COM.lookupNode(self.node,code.node); + console.log(to); + // to.link.control({ + // cmd:COM.Command.STD_INFO, + // args:code, + // },to.url, function (reply) { + // self.amp.reply(Command.STD_MONITOR()... + // }) + } + } + if (code.agent) { + status=self.node.std_info(code); + } + } + buf=Buf.Buffer(); + Buf.buf_put_int16(buf,tid); + Buf.buf_put_string(buf,JSON.stringify(status)); + + self.amp.reply(Command.STD_MONITOR, // Hack: Reply to STD_INFO request + buf, + self.amp.mode & Amp.AMMode.AMO_MULTICAST? handler.remote:undefined); + break; + case Command.STD_MONITOR: + // Hack: Reply to STD_STATUS/INFO request + tid = Buf.buf_get_int16(handler.buf); + code = Buf.buf_get_string(handler.buf); + code = JSON.parse(code); + if (self.callbacks[tid]) { + self.callbacks[tid](code); + delete self.callbacks[tid]; + } + break; + } + + }); +}; + +physical.prototype.emit = function (event,arg,aux1,aux2) { if (this.events[event]) this.events[event](arg,aux1,aux2)}; +physical.prototype.on = function (event,handler) {this.events[event]=handler}; +physical.prototype.init = function (callback) { return this.link.init(callback)}; +physical.prototype.start = function (callback) {return this.link.start(callback)}; +physical.prototype.stop = function () {return this.link.stop()}; + +var Physical = function (node,dir,options) { + var obj=new physical(node,dir,options); + return obj; +} + +module.exports.Physical=Physical; + +/************************* +** IP UTILS +*************************/ +var url2addr=Amp.url2addr; +var addr2url=Amp.addr2url; +var resolve=Amp.resolve; + +/* url = ":" | ":" | "" + * and ipport = (1-65535) | "*" +function url2addr(url,defaultIP) { + var addr={address:defaultIP||'localhost',proto:'IP',port:undefined}, + parts = url.toString().split(':'); + if (parts.length==1) { + if (Comp.string.isNumeric(parts[0])) addr.port=Number(parts[0]); // port number + else if (parts[0].indexOf('-') != -1) addr.port=parts[0]; // port range p0-p1 + else if (parts[0]=='*') addr.port=undefined; // any port + else addr.address=parts[0]; // ip/url + } else return {address:parts[0],port:parts[1]=='*'?undefined:Number(parts[1])||parts[1]}; + return addr; +}; + +function addr2url(addr) { + return (addr.proto?(addr.proto+'://'):'')+addr.address+':'+(addr.port?addr.port:'*') +}; +function resolve (url,defaultIP) { + var addr=url2addr(url,defaultIP); + return addr2url(addr) +} + */ + +function addrequal(addr1,addr2) { + return ipequal(addr1.address,addr2.address) && addr1.port==addr2.port; +} + + +function ipequal(ip1,ip2) { + if (ip1==undefined || ip2==undefined) return false; + else if ((Comp.string.equal(ip1,'localhost') || Comp.string.equal(ip1,'127.0.0.1')) && + (Comp.string.equal(ip2,'localhost') || Comp.string.equal(ip2,'127.0.0.1'))) return true; + else return ip1==ip2; +} + + +/*********************************************** + * IP Router using AMP/UDP/TCP/HTTP links + * Entry point for move and send operations DIR.IP + *********************************************** + */ + +function iprouter() { + this.routingTable={}; + this.nodeTable={}; + this.links=[]; +} +// Add route and link to be used for the route (and optional remote node id) +iprouter.prototype.add = function (to,link,node) { + to=resolve(to); + if (options.verbose) Aios.logAsync('[IP] iprouter: add route '+addr2url(link.ip)+' -> '+to+(node?'#'+node:'')); + this.routingTable[to]=link; + this.nodeTable[to]=node; +} + +// Add link device +iprouter.prototype.addLink = function (link) { + if (!link.ip) link.ip='*'; + if (options.verbose) Aios.logAsync('[IP] iprouter: add link '+addr2url(link.ip)); + this.links.push(link); +} + +// Connect to a remote endpoint +iprouter.prototype.connect = function (to,key) { + var link,p,addr; + to=resolve(to); + addr=url2addr(to); + // Search for an unconnected port!? + for(p in this.links) { + if (this.links[p].status(to)) return; + if (!(this.links[p].mode&Amp.AMMode.AMO_MULTICAST) && this.links[p].status()) continue; + if (addr.proto && this.links[p].ip && this.links[p].ip.proto != addr.proto) continue; + link=this.links[p]; + break; + } + if (link && link.connect) { + link.connect(to,key); + } +} + +// +iprouter.prototype.count = function (dest) { + var res=0; + for(var i in this.links) { + res += this.links[i].count(); + } + return res; +} + +// Remove route +iprouter.prototype.delete = function (to) { + to=resolve(to); + if (this.routingTable[to]) { + if (options.verbose) Aios.logAsync('[IP] iprouter: remove route '+addr2url(this.routingTable[to].ip)+ ' -> ' + to); + delete this.routingTable[to]; + delete this.nodeTable[to]; + } +} + +// Disconnect a remote endpoint +iprouter.prototype.disconnect = function (to) { + // Search for a connected port! + to=resolve(to); + if (this.routingTable[to] && this.routingTable[to].status(to)) { + this.routingTable[to].disconnect(to); + } +} + +/** Lookup a IP:PORT address pair of a nodeid OR contact a broker to get reachable + * nodeid-IP address pairs + * + */ +iprouter.prototype.lookup = function (nodeid,callback) { + var p,result=[],n=0; + // Broker lookup with a pattern like /domain/* (DIR.PATH) + if (nodeid.indexOf('*')!=-1) { + // TODO + for (p in this.links) { + if (this.links[p].lookup) { + n++; + this.links[p].lookup(nodeid,function (_result) { + if (_result && _result.length) result=result.concat(_result); + n--; + if (n==0) callback(result); + }); + } + } + } else for(p in this.nodeTable) { + if (this.nodeTable[p] == nodeid && this.routingTable[p]) return p; + } +} + + +/** Try to find our local IP address. + * + */ +iprouter.prototype.ip = function () { + for(var i in this.links) { + if (this.links[i].ip) return this.links[i].ip; + } +} + +/** Reverse lookup: Get the nodeid from an IP:PORT address +* typeof @ip = string +*/ +iprouter.prototype.reverse = function (ip) { + return this.nodeTable[ip]; +} + + + +/** Send a message +* +*/ + +iprouter.prototype.send = function (msg) { + msg.to=resolve(msg.to); + if (this.routingTable[msg.to]) { + this.routingTable[msg.to].send(msg,msg.to); + } else { + + } +} + +/** Start all attached devices +* +*/ +iprouter.prototype.start = function (callback) { + var cbl=CBL(callback||function(){}); + this.links.forEach(function (link) { + cbl.push(function (next) {link.start(next)}); + }); + cbl.start(); +} + +iprouter.prototype.stats = function () { + return { + links:Object.keys(this.routingTable).length + } +} + +// Check status of link in given direction (or any direction dest==undefined) +// OR return all current registered routes string [] (dest=='*')! +// OR return all current connected nodes string [] (dest=='%')! +// OR return all current registered links (ip) string [] (dest=='$')! +iprouter.prototype.status = function (dest) { + var res,p; + if (dest==undefined) { + // Any registered routes? + for(p in this.routingTable) { if (this.routingTable[p]) return true } + } else if (dest=='*') { + res=[]; + for(p in this.routingTable) { if (this.routingTable[p]) res.push(p) } + return res; + } else if (dest=='%') { + res=[]; + for(p in this.nodeTable) { + if (this.nodeTable[p] && this.routingTable[p]) res.push(this.nodeTable[p]); + } + return res; + } else { + dest=resolve(dest); + if (this.routingTable[dest]) + return this.routingTable[dest].status(dest); + else + return false; + } + return false; +} + +// Stop all attached devices +iprouter.prototype.stop = function (callback) { + var cbl=CBL(callback||function(){}); + this.links.forEach(function (link) { + cbl.push(function (next) {link.stop(next)}); + }); + cbl.start(); +} + + +module.exports.iprouter=iprouter; + +module.exports.Command=Command +module.exports.Status=Status + +module.exports.url2addr=url2addr; +module.exports.addr2url=addr2url; +}; +BundleModuleCode['os/lz-string']=function (module,exports){ +// Copyright (c) 2013 Pieroxy +// This work is free. You can redistribute it and/or modify it +// under the terms of the WTFPL, Version 2 +// For more information see LICENSE.txt or http://www.wtfpl.net/ +// +// For more information, the home page: +// http://pieroxy.net/blog/pages/lz-string/testing.html +// +// LZ-based compression algorithm, version 1.4.4 +var LZString = (function() { + +// private property +var f = String.fromCharCode; +var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; +var baseReverseDic = {}; + +function getBaseValue(alphabet, character) { + if (!baseReverseDic[alphabet]) { + baseReverseDic[alphabet] = {}; + for (var i=0 ; i>> 8; + buf[i*2+1] = current_value % 256; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array:function (compressed) { + if (compressed===null || compressed===undefined){ + return LZString.decompress(compressed); + } else { + var buf=new Array(compressed.length/2); // 2 bytes per character + for (var i=0, TotalLen=buf.length; i> 1; + } + } else { + value = 1; + for (i=0 ; i> 1; + } + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = Math.pow(2, context_numBits); + context_numBits++; + } + delete context_dictionaryToCreate[context_w]; + } else { + value = context_dictionary[context_w]; + for (i=0 ; i> 1; + } + + + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = Math.pow(2, context_numBits); + context_numBits++; + } + // Add wc to the dictionary. + context_dictionary[context_wc] = context_dictSize++; + context_w = String(context_c); + } + } + + // Output the code for w. + if (context_w !== "") { + if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { + if (context_w.charCodeAt(0)<256) { + for (i=0 ; i> 1; + } + } else { + value = 1; + for (i=0 ; i> 1; + } + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = Math.pow(2, context_numBits); + context_numBits++; + } + delete context_dictionaryToCreate[context_w]; + } else { + value = context_dictionary[context_w]; + for (i=0 ; i> 1; + } + + + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = Math.pow(2, context_numBits); + context_numBits++; + } + } + + // Mark the end of the stream + value = 2; + for (i=0 ; i> 1; + } + + // Flush the last char + while (true) { + context_data_val = (context_data_val << 1); + if (context_data_position == bitsPerChar-1) { + context_data.push(getCharFromInt(context_data_val)); + break; + } + else context_data_position++; + } + return context_data.join(''); + }, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); + }, + + _decompress: function (length, resetValue, getNextValue) { + var dictionary = [], + next, + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + i, + w, + bits, resb, maxpower, power, + c, + data = {val:getNextValue(0), position:resetValue, index:1}; + + for (i = 0; i < 3; i += 1) { + dictionary[i] = i; + } + + bits = 0; + maxpower = Math.pow(2,2); + power=1; + while (power!=maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb>0 ? 1 : 0) * power; + power <<= 1; + } + + switch (next = bits) { + case 0: + bits = 0; + maxpower = Math.pow(2,8); + power=1; + while (power!=maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb>0 ? 1 : 0) * power; + power <<= 1; + } + c = f(bits); + break; + case 1: + bits = 0; + maxpower = Math.pow(2,16); + power=1; + while (power!=maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb>0 ? 1 : 0) * power; + power <<= 1; + } + c = f(bits); + break; + case 2: + return ""; + } + dictionary[3] = c; + w = c; + result.push(c); + while (true) { + if (data.index > length) { + return ""; + } + + bits = 0; + maxpower = Math.pow(2,numBits); + power=1; + while (power!=maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb>0 ? 1 : 0) * power; + power <<= 1; + } + + switch (c = bits) { + case 0: + bits = 0; + maxpower = Math.pow(2,8); + power=1; + while (power!=maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb>0 ? 1 : 0) * power; + power <<= 1; + } + + dictionary[dictSize++] = f(bits); + c = dictSize-1; + enlargeIn--; + break; + case 1: + bits = 0; + maxpower = Math.pow(2,16); + power=1; + while (power!=maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb>0 ? 1 : 0) * power; + power <<= 1; + } + dictionary[dictSize++] = f(bits); + c = dictSize-1; + enlargeIn--; + break; + case 2: + return result.join(''); + } + + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; + } + + if (dictionary[c]) { + entry = dictionary[c]; + } else { + if (c === dictSize) { + entry = w + w.charAt(0); + } else { + return null; + } + } + result.push(entry); + + // Add w+entry[0] to the dictionary. + dictionary[dictSize++] = w + entry.charAt(0); + enlargeIn--; + + w = entry; + + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; + } + + } + } +}; + return LZString; +})(); + +if (typeof define === 'function' && define.amd) { + define(function () { return LZString; }); +} else if( typeof module !== 'undefined' && module != null ) { + module.exports = LZString +} +}; +BundleModuleCode['dos/buf']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $CREATED: 29-4-15 by sbosse. + ** $RCS: $Id$ + ** $VERSION: 1.1.5 + ** + ** $INFO: + ** + ** DOS: Buffer Management + ** + ** $ENDOFINFO + */ +"use strict"; +var log = 0; + +var util = Require('util'); +var Io = Require('com/io'); +var Comp = Require('com/compat'); +var String = Comp.string; +var Array = Comp.array; +var Perv = Comp.pervasives; +var des48 = Require('dos/des48'); +var Rand = Comp.random; +var Net = Require('dos/network'); +var Status = Net.Status; +var Fs = Require('fs'); + + +var SIZEOF_INT16 = 2; +var SIZEOF_INT32 = 4; +var PORT_SIZE = 6; +var PRIV_SIZE = PORT_SIZE+SIZEOF_INT32; +var CAP_SIZE = PORT_SIZE+PRIV_SIZE; + +/** Generic buffer, union with rpcio object + ** Argument: optional, hex-ascci string or number (size), passed to Buffer instantiation + * + * + * @param {number|string|Buffer} [data] + * @constructor + */ +var buffer = function (data) { + var size; + this.pos=0; + if (Comp.isNumber(data)) { + this.data=new Buffer(data); + } else if (Comp.isString(data)) { + this.data=new Buffer(''); + buf_of_hex(this,data) + } else if (Comp.isArray(data)) { + this.data=new Buffer(data); + } else if (typeof data == "object" && data.constructor === Buffer) { + this.data=data; + } else this.data=new Buffer(''); +}; + +/** Extend a buffer to new size (buf.pos+off). + * + * @param buf + * @param off + */ +function buf_extend(buf,off) { + if (buf.data.length<(buf.pos+off)) { + buf.data=Buffer.concat([buf.data,new Buffer(off-(buf.data.length-buf.pos))]); + } +} + +/** Expand buffer to new size. + * + * @param buf + * @param size + */ +function buf_expand(buf,size) { + if (buf.data.lengthsize) { + buf.data=Buffer.slice(buf.data,size); + } +} + +/* + ** BUFFER encoding and decoding of native data types + ** Supported objects: rpcio, buffer. + ** Supported native data types: int16, int32, string, float, port, private, capability, ... + ** ALL buffer data is stored in byte buffers that extends automatically (buf_put_XX). + */ +function buf_put_string (buf,str) { + buf_extend(buf,str.length+1); + for(var i=0;i> 8) & 0xff; + buf.pos=buf.pos+2; +} + +function buf_get_int16 (buf) { + var n=0; + var end=buf.data.length; + if (buf.pos+2 <= end) { + n = buf.data[buf.pos]; + n = n | (buf.data[buf.pos+1] << 8); + buf.pos = buf.pos + 2; + if (n&0x8000) return (n-0x10000); else return (n); + } else throw Status.BUF_OVERFLOW; +} + +function buf_put_int32 (buf,n) { + buf_extend(buf,4); + buf.data[buf.pos]=n & 0xff; + buf.data[buf.pos+1]=(n >> 8) & 0xff; + buf.data[buf.pos+2]=(n >> 16) & 0xff; + buf.data[buf.pos+3]=(n >> 24) & 0xff; + buf.pos=buf.pos+4; +} + +function buf_get_int32 (buf) { + var n=0; + var end=buf.data.length; + if (buf.pos+4 <= end) { + n = buf.data[buf.pos]; + n = n | (buf.data[buf.pos+1] << 8); + n = n | (buf.data[buf.pos+2] << 16); + n = n | (buf.data[buf.pos+3] << 24); + buf.pos = buf.pos + 4; + // TBD: Sign check??? + return (n); + } else throw Status.BUF_OVERFLOW; +} + +function buf_put_port (buf,port) { + buf_extend(buf,Net.PORT_SIZE); + for(var i=0;i> 8) & 0xff; + buf.data[buf.pos+2]=(priv.prv_obj >> 16) & 0xff; + buf.data[buf.pos+3]=priv.prv_rights & 0xff; + buf.pos=buf.pos+4; + buf_put_port(buf,priv.prv_rand); +} + +function buf_get_priv (buf,priv) { + var n; + var end=buf.data.length; + if (buf.pos+(Net.PRIV_SIZE) <= end) { + if (priv == undefined) priv = Net.Private(); + n = buf.data[buf.pos]; + n = n | (buf.data[buf.pos+1] << 8); + n = n | (buf.data[buf.pos+2] << 16); + priv.prv_obj=n; + priv.prv_rights=buf.data[buf.pos+3]; + buf.pos=buf.pos+4; + priv.prv_rand=buf_get_port(buf); + return priv; + } else throw Status.BUF_OVERFLOW; +} + +function buf_put_cap (buf,cap) { + buf_put_port(buf,cap.cap_port); + buf_put_priv(buf,cap.cap_priv); +} + +function buf_get_cap (buf,cap) { + var end=buf.data.length; + if (buf.pos+(Net.CAP_SIZE) <= end) { + if (cap == undefined) cap = Net.Capability(); + cap.cap_port=buf_get_port(buf); + buf_get_priv(buf,cap.cap_priv); + return cap; + } else throw Status.BUF_OVERFLOW; +} + +function buf_put_hdr (buf,hdr) { + buf_put_port(buf,hdr.h_port); + buf_put_priv(buf,hdr.h_priv); + buf_put_int32(buf,hdr.h_command); + buf_put_int32(buf,hdr.h_status); +} + +function buf_get_hdr (buf,hdr) { + if (hdr==undefined) hdr=Net.Header(); + hdr.h_port=buf_get_port(buf); + buf_get_priv(buf,hdr.h_priv); + hdr.h_command=buf_get_int32(buf); + hdr.h_status=buf_get_int32(buf); + return hdr; +} + +/** TODO: buf blit + * + * @param buf + * @param bufsrc + * @param [srcoff] + * @param [len] + */ +function buf_put_buf (buf,bufsrc,srcoff,len) { + if (srcoff==undefined) srcoff=0; + if (len==undefined) len=bufsrc.data.length; + buf_extend(buf,len); + for(var i=0;i= buf.data.length) buf_expand(buf,off+1); + buf.pos=off; +} +/** + * @param {file} fd + * @param {buffer} buf + * @param {number} [off] file offset + * @param {number} [len] + * @returns {number} n + */ +function buf_write (fd,buf,off,len) { + var n; + if (off==undefined) n=Io.write_buf(fd,buf.data,0,buf.data.length); + else { + if (len==undefined) len=buf.data.length; + n=Io.write_buf(fd,buf.data,0,len,off); + } + return n; +} +/** + * @param {file} fd + * @param {buffer} buf + * @param {number} off file offset + * @param {number} len + * @returns {number} n + */ +function buf_read (fd,buf,off,len) { + var n; + buf_expand(buf,len); + n=Io.read_buf(fd,buf.data,0,len,off); + buf.pos=0; + return n; +} + +function buf_print(buf) { + var str='['; + for(var i=0;i0) str=str+','+buf.data[i]; + else str=str+buf.data[i]; + } + return str+']'+buf.pos+':'+buf.data.length; +} + +function buf_set (buf,off,byte) { + if (off >= buf.data.length) buf_expand(buf,off+1); + buf.data[off]=byte; +} + +function buf_get (buf,off) { + return buf.data[off]; +} + +/** Reset buffer + * + * @param buf + */ +function buf_init (buf) { + buf.data=new Buffer(''); + buf.pos=0; +} + +function buf_copy (dst,src) { + dst.data=new Buffer(src.data); + dst.pos=0; +} + +function buf_blit (dst,dstoff,src,srcoff,len) { + buf_expand(dst,dstoff+len); + src.data.copy(dst.data,dstoff,srcoff,srcoff+len); + dst.pos=0; +} + + +/** + * + * @type {{SIZEOF_INT16: number, SIZEOF_INT32: number, PORT_SIZE: number, PRIV_SIZE: number, CAP_SIZE: number, Buffer: Function, buf_put_string: buf_put_string, buf_put_int16: buf_put_int16, buf_put_int32: buf_put_int32, buf_put_port: buf_put_port, buf_put_priv: buf_put_priv, buf_put_cap: buf_put_cap, buf_put_hdr: buf_put_hdr, buf_put_buf: buf_put_buf, buf_put_bytes: buf_put_bytes, buf_get_string: buf_get_string, buf_get_int16: buf_get_int16, buf_get_int32: buf_get_int32, buf_get_port: buf_get_port, buf_get_priv: buf_get_priv, buf_get_cap: buf_get_cap, buf_get_hdr: buf_get_hdr, buf_get_buf: buf_get_buf, buf_get_bytes: buf_get_bytes, buf_pad: buf_pad, buf_set: buf_set, buf_get: buf_get, buf_set_pos: buf_set_pos, buf_init: buf_init, buf_blit: buf_blit, buf_copy: buf_copy, buf_extend: buf_extend, buf_expand: buf_expand, buf_shrink: buf_shrink, buf_read: buf_read, buf_write: buf_write, buf_print: buf_print, buf_to_hex: buf_to_hex, buf_of_hex: buf_of_hex, buf_to_str: buf_to_str, buf_of_str: buf_of_str}} + */ +module.exports = { + SIZEOF_INT16: SIZEOF_INT16, + SIZEOF_INT32: SIZEOF_INT32, + PORT_SIZE: PORT_SIZE, + PRIV_SIZE: PRIV_SIZE, + CAP_SIZE: CAP_SIZE, + /** + * + * @param {number|string|Buffer} [data] + * @returns {buffer} + */ + Buffer: function Buffer(data) { + var obj = new buffer(data); + Object.preventExtensions(obj); + return obj; + }, + // Buffer data operations + buf_put_string:buf_put_string, + buf_put_int16:buf_put_int16, + buf_put_int32:buf_put_int32, + buf_put_port:buf_put_port, + buf_put_priv:buf_put_priv, + buf_put_cap:buf_put_cap, + buf_put_hdr:buf_put_hdr, + buf_put_buf:buf_put_buf, + buf_put_bytes:buf_put_bytes, + buf_get_string:buf_get_string, + buf_get_int16:buf_get_int16, + buf_get_int32:buf_get_int32, + buf_get_port:buf_get_port, + buf_get_priv:buf_get_priv, + buf_get_cap:buf_get_cap, + buf_get_hdr:buf_get_hdr, + buf_get_buf:buf_get_buf, + buf_get_bytes:buf_get_bytes, + buf_pad:buf_pad, + buf_set:buf_set, + buf_get:buf_get, + buf_set_pos:buf_set_pos, + buf_init:buf_init, + buf_blit:buf_blit, + buf_copy:buf_copy, + buf_extend:buf_extend, + buf_expand:buf_expand, + buf_shrink:buf_shrink, + // Buffer IO + buf_read:buf_read, + buf_write:buf_write, + buf_print:buf_print, + // Conversion + buf_to_hex:buf_to_hex, + buf_of_hex:buf_of_hex, + buf_to_str:buf_to_str, + buf_of_str:buf_of_str, + + length: function(buf) { + if (buf.data==undefined) return 0; + else return buf.data.length; + } +}; +}; +BundleModuleCode['dos/des48']=function (module,exports){ +/** + ** ================================== + ** OOOO OOOO OOOO O O OOOO + ** O O O O O O O O O + ** O O O O O O O O O + ** OOOO OOOO OOOO O OOO OOOO + ** O O O O O O O O O + ** O O O O O O O O O + ** OOOO OOOO OOOO OOOO O O OOOO + ** ================================== + ** BSSLAB, Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR. + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 BSSLAB + ** $CREATED: 3/30/15 by sbosse. + ** $VERSION: 1.1.5 + ** + ** $INFO: + ** + ** DOS: Encryption 48bit + ** + ** $ENDOFINFO + */ +var util = Require('util'); +var Io = Require('com/io'); +var Comp = Require('com/compat'); +var Array = Comp.array; +var assert = Comp.assert; + + +const des_HBS = 24; +const des_BS = des_HBS * 2; + + +/* +** Initial permutation, +*/ + +var des_IP = [ + 23, 27, 34, 44, 37, 17, 12, 42, + 3, 32, 41, 29, 20, 2, 1, 10, + 0, 28, 40, 6, 7, 11, 16, 8, + 25, 30, 14, 26, 47, 38, 19, 43, + 18, 5, 35, 39, 36, 21, 4, 45, + 24, 22, 13, 33, 31, 9, 15, 46 ]; + +/* +** Final permutation, FP = IP^(-1) +*/ + +var des_FP = [ + 16, 14, 13, 8, 38, 33, 19, 20, + 23, 45, 15, 21, 6, 42, 26, 46, + 22, 5, 32, 30, 12, 37, 41, 0, + 40, 24, 27, 1, 17, 11, 25, 44, + 9, 43, 2, 34, 36, 4, 29, 35, + 18, 10, 7, 31, 3, 39, 47, 28 ]; + +/* +** Permuted-choice 1 from the key bits +** to yield C and D. +** Note that bits 8,16... are left out: + ** They are intended for a parity check. +*/ + +var des_PC1_C = [ + 57,49,41,33,25,17, 9, + 1,58,50,42,34,26,18, + 10, 2,59,51,43,35,27, + 19,11, 3,60,52,44,36 ]; + +var des_PC1_D = [ + 63,55,47,39,31,23,15, + 7,62,54,46,38,30,22, + 14, 6,61,53,45,37,29, + 21,13, 5,28,20,12, 4 ]; + + +/* +** Sequence of shifts used for the key schedule. +*/ + +var des_shifts = [ + 1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1 ]; + + + +/* +** Permuted-choice 2, to pick out the bits from +** the CD array that generate the key schedule. +*/ + +var des_PC2_C = [ + 14,17,11,24, 1, 5, + 3,28,15, 6,21,10, + 23,19,12, 4,26, 8, + 16, 7,27,20,13, 2 ]; + +var des_PC2_D = [ + 41,52,31,37,47,55, + 30,40,51,45,33,48, + 44,49,39,56,34,53, + 46,42,50,36,29,32 ]; + +/* +** The C and D arrays used to calculate the key schedule. +*/ + + +var des_C = Array.create(56,0); +// des_D = des_C[28] +var des_D_get = function (i) {return des_C[i+28]}; +var des_D_set = function (i,sval) { des_C[i+28] = sval }; + +/* +** The key schedule. +** Generated from the key. +*/ + +var des_KS= Array.create_matrix(16,48,0); + +var des_OWsetkey = function(key) { + var ks = []; + var t = 0; + var i,j,k; + /* + ** First, generate C and D by permuting + ** the key. The low order bit of each + ** 8-bit char is not used, so C and D are only 28 + ** bits apiece. + */ + + for(i = 0;i < 28;i++) { + + var index1 = des_PC1_C[i] - 1; + var index2 = des_PC1_D[i] - 1; + + des_C[i] = key[index1]; + des_D_set(i,key[index2]); + } + + /* + ** To generate Ki, rotate C and D according + ** to schedule and pick up a permutation + ** using PC2. + */ + + + for (i = 0 ;i< 16;i++) { + + ks = des_KS[i]; + + // rotate + for (k = 0; k < des_shifts[i]; k++) { + t = des_C[0]; + + for (j = 0; j < 27; j++) { + des_C[j] = des_C[j + 1]; + } + + des_C[27] = t; + t = des_D_get(0); + + for (j = 0; j < 27; j++) { + des_D_set(j, des_D_get(j + 1)); + } + des_D_set(27, t); + } + + /* + ** get Ki. Note C and D are concatenated. + */ + + for (j = 0; j < 24; j++) { + ks[j] = des_C[des_PC2_C[j] - 1]; + ks[j + 24] = des_D_get(des_PC2_D[j] - 28 - 1); + } + + } + return { C:des_C, KS:des_KS } +}; + + +/* +** The E bit-selection table. +*/ + +var des_E = [ + 22, 15, 12, 3, 8, 2, 23, 16, + 14, 13, 9, 10, 0, 1, 21, 19, + 18, 6, 11, 7, 17, 4, 20, 5, + 5, 17, 11, 13, 12, 14, 8, 7, + 19, 22, 18, 9, 3, 4, 1, 6, + 16, 2, 20, 15, 10, 23, 0, 21 ]; + + +/* +** The 8 selection functions. +** For some reason, they give a 0-origin +** index, unlike everything else. +*/ + +var des_S = [ + [ 14, 4,13, 1, 2,15,11, 8, 3,10, 6,12, 5, 9, 0, 7, + 0,15, 7, 4,14, 2,13, 1,10, 6,12,11, 9, 5, 3, 8, + 4, 1,14, 8,13, 6, 2,11,15,12, 9, 7, 3,10, 5, 0, + 15,12, 8, 2, 4, 9, 1, 7, 5,11, 3,14,10, 0, 6,13 ], + + [ 15, 1, 8,14, 6,11, 3, 4, 9, 7, 2,13,12, 0, 5,10, + 3,13, 4, 7,15, 2, 8,14,12, 0, 1,10, 6, 9,11, 5, + 0,14, 7,11,10, 4,13, 1, 5, 8,12, 6, 9, 3, 2,15, + 13, 8,10, 1, 3,15, 4, 2,11, 6, 7,12, 0, 5,14, 9 ], + + [ 10, 0, 9,14, 6, 3,15, 5, 1,13,12, 7,11, 4, 2, 8, + 13, 7, 0, 9, 3, 4, 6,10, 2, 8, 5,14,12,11,15, 1, + 13, 6, 4, 9, 8,15, 3, 0,11, 1, 2,12, 5,10,14, 7, + 1,10,13, 0, 6, 9, 8, 7, 4,15,14, 3,11, 5, 2,12 ], + + [ 7,13,14, 3, 0, 6, 9,10, 1, 2, 8, 5,11,12, 4,15, + 13, 8,11, 5, 6,15, 0, 3, 4, 7, 2,12, 1,10,14, 9, + 10, 6, 9, 0,12,11, 7,13,15, 1, 3,14, 5, 2, 8, 4, + 3,15, 0, 6,10, 1,13, 8, 9, 4, 5,11,12, 7, 2,14 ], + + [ 2,12, 4, 1, 7,10,11, 6, 8, 5, 3,15,13, 0,14, 9, + 14,11, 2,12, 4, 7,13, 1, 5, 0,15,10, 3, 9, 8, 6, + 4, 2, 1,11,10,13, 7, 8,15, 9,12, 5, 6, 3, 0,14, + 11, 8,12, 7, 1,14, 2,13, 6,15, 0, 9,10, 4, 5, 3 ], + + [ 12, 1,10,15, 9, 2, 6, 8, 0,13, 3, 4,14, 7, 5,11, + 10,15, 4, 2, 7,12, 9, 5, 6, 1,13,14, 0,11, 3, 8, + 9,14,15, 5, 2, 8,12, 3, 7, 0, 4,10, 1,13,11, 6, + 4, 3, 2,12, 9, 5,15,10,11,14, 1, 7, 6, 0, 8,13 ], + + [ 4,11, 2,14,15, 0, 8,13, 3,12, 9, 7, 5,10, 6, 1, + 13, 0,11, 7, 4, 9, 1,10,14, 3, 5,12, 2,15, 8, 6, + 1, 4,11,13,12, 3, 7,14,10,15, 6, 8, 0, 5, 9, 2, + 6,11,13, 8, 1, 4,10, 7, 9, 5, 0,15,14, 2, 3,12 ], + + [ 13, 2, 8, 4, 6,15,11, 1,10, 9, 3,14, 5, 0,12, 7, + 1,15,13, 8,10, 3, 7, 4,12, 5, 6,11, 0,14, 9, 2, + 7,11, 4, 1, 9,12,14, 2, 0, 6,10,13,15, 3, 5, 8, + 2, 1,14, 7, 4,10, 8,13,15,12, 9, 0, 3, 5, 6,11 ] + ]; + + +/* +** P is a permutation on the selected combination +** of the current L and key. +*/ + +var des_P = [ + 3, 13, 9, 12, 8, 20, 21, 7, + 5, 23, 16, 1, 14, 18, 4, 15, + 22, 10, 2, 0, 11, 19, 17, 6 ]; + +var des_L = Array.create(des_BS,0); +var des_R_get = function (i) { return des_L[(i+des_HBS)]}; +var des_R_set = function (i,sval) { des_L[i+des_HBS]= sval}; +var des_tempL = Array.create(des_HBS,0); +var des_f = Array.create (32,0); + +/* +** Warning!! +** +** f[] used to be HBS for some years. +** 21/6/1990 cbo and sater discovered that inside the loop where f is computed +** indices are used from 0 to 31. These overlapped the preS array which is +** declared hereafter on all compilers upto that point, but only those +** values that were not used anymore. But the values of f are only used +** upto HBS. Makes you wonder about the one-way property. +** Then came ACK, and reversed the order of the arrays in the image. +** +** As a short term solution f[] was increased to 32, but in the long run +** someone should have a good look at our "oneway" function +*/ + +/* +** The combination of the key and the input, before selection. +*/ +var des_preS = Array.create (48,0); + +/* +** The payoff: encrypt a block. (Now 48 bytes, 1 bit/byte) +*/ + +var des_OWcrypt48 = function(block) { + var ks = []; + var t1 = 0; + var t2 = 0; + var i, j, k; + /* + ** First, permute the bits in the input + */ + + for (j = 0; j <= (des_BS - 1); j++) { + des_L[j] = block[des_IP[j]]; + } + /* + ** Perform an encryption operation 16 times. + */ + + for (i = 0; i <= 15; i++) { + ks = des_KS[i]; + + /* + ** Save the R array, + ** which will be the new L. + */ + + for (j = 0; j < (des_HBS - 1); j++) { + des_tempL[j] = des_R_get(j); + } + /* + ** Expand R to 48 bits using the E selector; + ** exclusive-or with the current key bits. + */ + + for (j = 0; j <= 47; j++) { + des_preS[j] = (des_R_get(des_E[j])) ^ ks[j]; + } + + /* + ** The pre-select bits are now considered + ** in 8 groups of 6 bits each. + ** The 8 selection functions map these + ** 6-bit quantities into 4-bit quantities + ** and the results permuted + ** to make an f(R, K). + ** The indexing into the selection functions + ** is peculiar; it could be simplified by + ** rewriting the tables. + */ + + t1 = 0; + t2 = 0; + + for (j = 0; j <= 7; j++) { + /* + C: for (j=0,t1=0,t2=0; j<8; j++,t1+=6,t2+=4) { + k = S[j][(preS[t1+0]<<5)+ + (preS[t1+1]<<3)+ + (preS[t1+2]<<2)+ + (preS[t1+3]<<1)+ + (preS[t1+4]<<0)+ + (preS[t1+5]<<4)]; + f[t2+0] = (k>>3)&01; + f[t2+1] = (k>>2)&01; + f[t2+2] = (k>>1)&01; + f[t2+3] = (k>>0)&01; + } + */ + var sind2 = + ((des_preS[t1 + 0] << 5) & 0xff) + + ((des_preS[t1 + 1] << 3) & 0xff) + + ((des_preS[t1 + 2] << 2) & 0xff) + + ((des_preS[t1 + 3] << 1) & 0xff) + + ((des_preS[t1 + 4] << 0) & 0xff) + + ((des_preS[t1 + 5] << 4) & 0xff); + + k = des_S[j][sind2]; + + des_f[t2 + 0] = (k >> 3) & 0x1; + des_f[t2 + 1] = (k >> 2) & 0x1; + des_f[t2 + 2] = (k >> 1) & 0x1; + des_f[t2 + 3] = (k >> 0) & 0x1; // 3 .. 31 !!! + + t1 = t1 + 6; + t2 = t2 + 4; + } + + /* + ** The new R is L ^ f(R, K). + ** The f here has to be permuted first, though. + */ + + for (j = 0; j < des_HBS; j++) { + des_R_set(j, (des_L[j] ^ des_f[des_P[j]])); + } + + /* + ** Finally, the new L (the original R) + ** is copied back. + */ + + for (j = 0; j < des_HBS; j++) { + des_L[j] = des_tempL[j]; + } + + } + + + /* + ** The output L and R are reversed. + */ + + for (j = 0; j < des_HBS; j++) { + t1 = des_L[j]; + des_L[j] = des_R_get(j); + des_R_set(j, t1); + } + + /* + ** The final output + ** gets the inverse permutation of the very original. + */ + + for (j = 0; j < des_BS; j++) { + block[j] = des_L[des_FP[j]]; + } + return block; +}; + +module.exports = { + des_OWsetkey:des_OWsetkey, + des_OWcrypt48:des_OWcrypt48 +}; +}; +BundleModuleCode['dos/network']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR. + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $CREATED: 3-5-15 by sbosse. + ** $VERSION: 1.2.6 + ** + ** $INFO: + ** + ** DOS: Networking, Commands, Status/Error codes, .. + ** + ** $ENDOFINFO + */ +"use strict"; +var log = 0; + +var util = Require('util'); + +var Io = Require('com/io'); +var Comp = Require('com/compat'); +var String = Comp.string; +var Array = Comp.array; +var Perv =Comp.pervasives; +var Des48 = Require('dos/des48'); +var Base64 = Require('os/base64'); +var Rand = Comp.random; +var Fs = Require('fs'); + +//var xmldoc = Require('xmldoc'); + + +function pad(str,size) { + while (str.length < (size || 2)) {str = "0" + str;} + return str; +} + + +/** Direction + * +var Direction = { + NORTH:1, + WEST:2, + EAST:3, + SOUTH:4, + ORIGIN:5, + tostring:function (i) { + switch (i) { + case 1: return 'NORTH'; + case 2: return 'WEST'; + case 3: return 'EAST'; + case 4: return 'SOUTH'; + case 5: return 'ORIGIN'; + default: return 'Direction?'; + } + } +}; +*/ + + +// Standard Object Service +var STD_FIRST_COM = 1000; +var STD_LAST_COM = 1999; +var STD_FIRST_ERR = (-STD_FIRST_COM); +var STD_LAST_ERR = (-STD_LAST_COM); + +// File Server +var AFS_FIRST_COM = 2000; +var AFS_LAST_COM = 2099; +var AFS_FIRST_ERR = (-AFS_FIRST_COM); +var AFS_LAST_ERR = (-AFS_LAST_COM); +var AFS_REQBUFSZ = 1024*32; + + +// Directory and Name Server +var DNS_FIRST_COM = 2100; +var DNS_LAST_COM = 2199; +var DNS_FIRST_ERR = (-DNS_FIRST_COM); +var DNS_LAST_ERR = (-DNS_LAST_COM); +var DNS_MAXCOLUMNS = 4; + +// System Process Server (incl. Agent Platform Manager) +var PS_FIRST_COM = 2200; +var PS_LAST_COM = 2299; +var PS_FIRST_ERR = (-PS_FIRST_COM); +var PS_LAST_ERR = (-PS_LAST_COM); + +// Broker Server +var BR_FIRST_COM = 2300; +var BR_LAST_COM = 2399; +var BR_FIRST_ERR = (-BR_FIRST_COM); +var BR_LAST_ERR = (-BR_LAST_COM); +/** RPC Status + * + * @enum {number} + */ +var Status = { + STD_OK:0, + STD_CAPBAD : STD_FIRST_ERR, + STD_COMBAD : (STD_FIRST_ERR-1), + STD_ARGBAD : (STD_FIRST_ERR-2), + STD_NOTNOW : (STD_FIRST_ERR-3), + STD_NOSPACE : (STD_FIRST_ERR-4), + STD_DENIED : (STD_FIRST_ERR-5), + STD_NOMEM : (STD_FIRST_ERR-6), + STD_EXISTS : (STD_FIRST_ERR-7), + STD_NOTFOUND : (STD_FIRST_ERR-8), + STD_SYSERR : (STD_FIRST_ERR-9), + STD_INTR : (STD_FIRST_ERR-10), + STD_OVERFLOW : (STD_FIRST_ERR-11), + STD_WRITEPROT : (STD_FIRST_ERR-12), + STD_NOMEDIUM : (STD_FIRST_ERR-13), + STD_IOERR : (STD_FIRST_ERR-14), + STD_WRONGSRV : (STD_FIRST_ERR-15), + STD_OBJBAD : (STD_FIRST_ERR-16), + STD_UNKNOWN : (STD_FIRST_ERR-17), + DNS_UNAVAIL : (DNS_FIRST_ERR -1), + DNS_NOTEMPTY : (DNS_FIRST_ERR -2), + DNS_UNREACH : (DNS_FIRST_ERR -3), + DNS_CLASH : (DNS_FIRST_ERR -4), + RPC_FAILURE : -1, + BUF_OVERFLOW : -2, + print: function(stat) { + switch(stat) { + case Status.STD_OK : return 'STD_OK'; + case Status.STD_CAPBAD : return 'STD_CAPBAD'; + case Status.STD_COMBAD : return 'STD_COMBAD'; + case Status.STD_ARGBAD : return 'STD_ARGBAD'; + case Status.STD_NOTNOW : return 'STD_NOTNOW'; + case Status.STD_NOSPACE : return 'STD_NOSPACE'; + case Status.STD_DENIED : return 'STD_DENIED'; + case Status.STD_NOMEM : return 'STD_NOMEM'; + case Status.STD_EXISTS : return 'STD_EXISTS'; + case Status.STD_NOTFOUND : return 'STD_NOTFOUND'; + case Status.STD_SYSERR : return 'STD_SYSERR'; + case Status.STD_INTR : return 'STD_INTR'; + case Status.STD_OVERFLOW : return 'STD_OVERFLOW'; + case Status.STD_WRITEPROT : return 'STD_WRITEPROT'; + case Status.STD_NOMEDIUM : return 'STD_NOMEDIUM'; + case Status.STD_IOERR : return 'STD_IOERR'; + case Status.STD_WRONGSRV : return 'STD_WRONGSRV'; + case Status.STD_OBJBAD : return 'STD_OBJBAD'; + case Status.STD_UNKNOWN : return 'STD_UNKNOWN'; + case Status.DNS_UNAVAIL : return 'DNS_UNAVAIL'; + case Status.DNS_NOTEMPTY : return 'DNS_NOTEMPTY'; + case Status.DNS_UNREACH : return 'DNS_UNREACH'; + case Status.DNS_CLASH : return 'DNS_CLASH'; + case Status.RPC_FAILURE : return 'RPC_FAILURE'; + case Status.BUF_OVERFLOW : return 'BUF_OVERFLOW'; + default : return '"'+stat+'"'; + } + } +}; + +/** RPC Command + * + * @enum {number} + */ + +var Command = { + /* + ** Standard Commands + */ + STD_MONITOR : STD_FIRST_COM, + STD_AGE : (STD_FIRST_COM+1), + STD_COPY : (STD_FIRST_COM + 2), + STD_DESTROY : (STD_FIRST_COM + 3), + STD_INFO : (STD_FIRST_COM + 4), + STD_RESTRICT : (STD_FIRST_COM + 5), + STD_STATUS : (STD_FIRST_COM + 6), + STD_TOUCH : (STD_FIRST_COM + 7), + STD_GETPARAMS : (STD_FIRST_COM + 8), + STD_SETPARAMS : (STD_FIRST_COM + 9), + STD_NTOUCH : (STD_FIRST_COM + 10), + STD_EXIT : (STD_FIRST_COM + 11), + STD_RIGHTS : (STD_FIRST_COM + 12), + STD_EXEC : (STD_FIRST_COM + 13), + STD_LOCATION : (STD_FIRST_COM + 20), + STD_LABEL : (STD_FIRST_COM + 21), + + /* + ** AFC Commands + */ + AFS_CREATE : (AFS_FIRST_COM + 1), + AFS_DELETE : (AFS_FIRST_COM + 2), + AFS_FSCK : (AFS_FIRST_COM + 3), + AFS_INSERT : (AFS_FIRST_COM + 4), + AFS_MODIFY : (AFS_FIRST_COM + 5), + AFS_READ : (AFS_FIRST_COM + 6), + AFS_SIZE : (AFS_FIRST_COM + 7), + AFS_DISK_COMPACT : (AFS_FIRST_COM + 8), + AFS_SYNC : (AFS_FIRST_COM + 9), + AFS_DESTROY : (AFS_FIRST_COM + 10), + + /* + ** DNS Commands + */ + + DNS_CREATE : (DNS_FIRST_COM), + DNS_DISCARD : (DNS_FIRST_COM + 1), + DNS_LIST : (DNS_FIRST_COM + 2), + DNS_APPEND : (DNS_FIRST_COM + 3), + DNS_CHMOD : (DNS_FIRST_COM + 4), + DNS_DELETE : (DNS_FIRST_COM + 5), + DNS_LOOKUP : (DNS_FIRST_COM + 6), + DNS_SETLOOKUP : (DNS_FIRST_COM + 7), + DNS_INSTALL : (DNS_FIRST_COM + 8), + DNS_REPLACE : (DNS_FIRST_COM + 10), + DNS_GETMASKS : (DNS_FIRST_COM + 11), + DNS_GETSEQNR : (DNS_FIRST_COM + 12), + DNS_RENAME : (DNS_FIRST_COM + 13), + DNS_GETROOT : (DNS_FIRST_COM + 14), + DNS_GETDEFAFS : (DNS_FIRST_COM + 15), + + PS_STUN : (PS_FIRST_COM), // Kill a process/ create a snapshot + PS_MIGRATE : (PS_FIRST_COM+1), // Execute a process from a snapshot after migration (->next+) + PS_EXEC : (PS_FIRST_COM+2), // Execute a process from a snapshot (->next) + PS_WRITE : (PS_FIRST_COM+4), // Store a process class template + PS_READ : (PS_FIRST_COM+5), // Get a process class template + PS_CREATE : (PS_FIRST_COM+6), // Create a process from a template and execute + PS_FORK : (PS_FIRST_COM+7), // Fork a process from a running process + PS_SIGNAL : (PS_FIRST_COM+8), // Send a signal to a process + + BR_CONNECT : (BR_FIRST_COM), + BR_DISCONN : (BR_FIRST_COM+1), + + print: function(cmd) { + switch(cmd) { + case Command.STD_MONITOR : return 'STD_MONITOR'; + case Command.STD_AGE : return 'STD_AGE'; + case Command.STD_COPY : return 'STD_COPY'; + case Command.STD_DESTROY : return 'STD_DESTROY'; + case Command.STD_INFO : return 'STD_INFO'; + case Command.STD_RESTRICT : return 'STD_RESTRICT'; + case Command.STD_STATUS : return 'STD_STATUS'; + case Command.STD_TOUCH : return 'STD_TOUCH'; + case Command.STD_GETPARAMS : return 'STD_GETPARAMS'; + case Command.STD_SETPARAMS : return 'STD_SETPARAMS'; + case Command.STD_NTOUCH : return 'STD_NTOUCH'; + case Command.STD_EXIT : return 'STD_EXIT'; + case Command.STD_RIGHTS : return 'STD_RIGHTS'; + case Command.STD_EXEC : return 'STD_EXEC'; + case Command.STD_LOCATION : return 'STD_LOCATION'; + case Command.STD_LABEL : return 'STD_LABEL'; + case Command.AFS_CREATE : return 'AFS_CREATE'; + case Command.AFS_DELETE : return 'AFS_DELETE'; + case Command.AFS_FSCK : return 'AFS_FSCK'; + case Command.AFS_INSERT : return 'AFS_INSERT'; + case Command.AFS_MODIFY : return 'AFS_MODIFY'; + case Command.AFS_READ : return 'AFS_READ'; + case Command.AFS_SIZE : return 'AFS_SIZE'; + case Command.AFS_DISK_COMPACT : return 'AFS_DISK_COMPACT'; + case Command.AFS_SYNC : return 'AFS_SYNC'; + case Command.AFS_DESTROY : return 'AFS_DESTROY'; + case Command.DNS_CREATE : return 'DNS_CREATE'; + case Command.DNS_DISCARD : return 'DNS_DISCARD'; + case Command.DNS_LIST : return 'DNS_LIST'; + case Command.DNS_APPEND : return 'DNS_APPEND'; + case Command.DNS_CHMOD : return 'DNS_CHMOD'; + case Command.DNS_DELETE : return 'DNS_DELETE'; + case Command.DNS_LOOKUP : return 'DNS_LOOKUP'; + case Command.DNS_SETLOOKUP : return 'DNS_SETLOOKUP'; + case Command.DNS_INSTALL : return 'DNS_INSTALL'; + case Command.DNS_REPLACE : return 'DNS_REPLACE'; + case Command.DNS_GETMASKS : return 'DNS_GETMASKS'; + case Command.DNS_GETSEQNR : return 'DNS_GETSEQNR'; + case Command.DNS_RENAME : return 'DNS_RENAME'; + case Command.DNS_GETROOT : return 'DNS_GETRROT'; + case Command.DNS_GETDEFAFS : return 'DNS_GETDEFAFS'; + case Command.PS_STUN : return 'PS_STUN'; + case Command.PS_EXEC : return 'PS_EXEC'; + case Command.PS_MIGRATE : return 'PS_MIGRATE'; + case Command.PS_READ : return 'PS_READ'; + case Command.PS_WRITE : return 'PS_WRITE'; + case Command.PS_CREATE : return 'PS_CREATE'; + case Command.PS_FORK : return 'PS_FORK'; + case Command.PS_SIGNAL : return 'PS_SIGNAL'; + case Command.BR_CONNECT : return 'BR_CONNECT'; + case Command.BR_DISCONN : return 'BR_DISCONN'; + default: return '"'+cmd+'"'; + } + + + } +}; + +var PORT_SIZE = 6; +var PRIV_SIZE = 4+PORT_SIZE; +var CAP_SIZE = 16; + +/** Object Rights + * + * @enum {number} + */ +var Rights = { + AFS_RGT_READ : 0x1, + AFS_RGT_CREATE : 0x2, + AFS_RGT_MODIFY : 0x4, + AFS_RGT_DESTROY : 0x8, + AFS_RGT_ADMIN : 0x80, + + DNS_COLMASK : ((1 << DNS_MAXCOLUMNS) - 1), // Rights to access specific columns of a directory row, one bit, one column. + DNS_RGT_COLALL : ((1 << DNS_MAXCOLUMNS) - 1), + DNS_RGT_COL1 : 0x01, + DNS_RGT_OWNER : 0x01, + DNS_RGT_COL2 : 0x02, + DNS_RGT_GROUP : 0x02, + DNS_RGT_COL3 : 0x04, + DNS_RGT_OTHERS : 0x04, + DNS_RGT_COL4 : 0x08, + DNS_RGT_READ : 0x10, + DNS_RGT_CREATE : 0x20, + DNS_RGT_MODIFY : 0x40, + DNS_RGT_DELETE : 0x80, + + HOST_INFO : 0x01, + HOST_READ : 0x02, + HOST_WRITE : 0x04, + HOST_EXEC : 0x08, + + PSR_READ : 0x01, + PSR_WRITE : 0x02, + PSR_CREATE : 0x04, + PSR_DELETE : 0x08, + PSR_EXEC : 0x10, + PSR_KILL : 0x20, + PSR_ALL : 0xff, + + NEG_SCHED : 0x08, + NEG_CPU : 0x10, + NEG_RES : 0x20, + NEG_LEVEL : 0x40, + + PRV_ALL_RIGHTS : 0xff + +}; + + + +var DEF_RPC_MAX_HOP = 4; + +var priv2pub_cache = []; + +/** + * + * @param {number []} [port_vals] + * @returns {string} + */ +var port = function (port_vals) { + if (port_vals==undefined) port_vals=[0,0,0,0,0,0]; + var port=''; + for(var i = 0; i< PORT_SIZE;i++) { + port=port+Perv.char_of_int(port_vals[i]); + } + return port; + +}; +/** + * + * @param {number} [obj] + * @param {number} [rights] + * @param {port} [rand] + * @constructor + */ +var privat = function (obj,rights,rand) { + if (obj==undefined) { + // Create empty private field + this.prv_obj=0; + this.prv_rights=0; + this.prv_rand=port(); + } else { + this.prv_obj = obj; // Integer + this.prv_rights = rights; // Integer + this.prv_rand = rand; // Port=string + } +}; + +/** + * + * @param {port} [cap_port] + * @param {privat} [cap_priv] + * @constructor + */ +var capability = function(cap_port, cap_priv) { + if (cap_port==undefined) { + // Create empty capability + this.cap_port = port(); + this.cap_priv = new privat(); + } else { + this.cap_port = cap_port; // Port=string + if (cap_priv==undefined) + this.cap_priv = new privat(); + else + this.cap_priv = cap_priv; // Private + } +}; + +/* + ** RPC communication is XML based using the HTTP interface. + ** RPC communication is synchronous, hence a callback + ** function is used to handle the reply (acknowledge). + */ +/** + * + * @param {port} [h_port] + * @param {privat} [h_priv] + * @param {Command} [h_command] + * @param {(Status.STD_OK|*)} [h_status] + * @constructor + */ +var header = function(h_port,h_priv,h_command,h_status) { + if (h_port==undefined) { + // Create empty header + this.h_port = port(); + this.h_priv = new privat(); + this.h_command = undefined; + this.h_status = undefined; + } else { + this.h_port = h_port; + this.h_priv = h_priv; + this.h_command = h_command; + this.h_status = h_status; + } +}; + +/** + * + * @param {number} [obj] + * @param {number} [rights] + * @param {port} [rand] + * @returns {privat} + */ +function Private(obj,rights,rand) { + var _obj = new privat(obj,rights,rand); + Object.preventExtensions(_obj); + return _obj; +} +/** + * + * @param {port} [cap_port] + * @param {privat} [cap_priv] + * @returns {capability} + */ +function Capability (cap_port, cap_priv) { + var obj = new capability(cap_port, cap_priv); + Object.preventExtensions(obj); + return obj; +} +/** + * + * @param {port} [h_port] + * @param {privat} [h_priv] + * @param {Command} [h_command] + * @param {(Status.STD_OK|*)} [h_status] + * @returns {header} + */ + +function Header(h_port,h_priv,h_command,h_status) { + var obj = new header(h_port,h_priv,h_command,h_status); + Object.preventExtensions(obj); + return obj; +} + +/* +** Hash table of all locally created unique ports. + */ +var uniqports=[]; + + +/** + * + */ +var Net = { + // Direction:Direction, + PORT_SIZE:PORT_SIZE, + PRIV_SIZE:PRIV_SIZE, + + AFS_REQBUFSZ:AFS_REQBUFSZ, + CAP_SIZE:CAP_SIZE, + DNS_MAXCOLUMNS:DNS_MAXCOLUMNS, + TIMEOUT:5000, + DEF_RPC_MAX_HOP:DEF_RPC_MAX_HOP, + + Status:Status, + Command:Command, + Rights:Rights, + + Private:Private, + Capability: Capability, + Header: Header, + Port: port, + + /** + * @type {port} + */ + nilport: port(), + nilpriv: Private(0,0,this.nilport), + nilcap: Capability(this.nilport,this.nilpriv), + + /* + ** Utils to get and set single bytes of a port + */ + get_portbyte: function(port,i) { + return Perv.int_of_char(String.get(port,i)) + }, + set_portbyte: function(port,i,byte) { + return String.set(port, i, (Perv.char_of_int(byte))); + }, + /* + * Return a unique key of a capability that can be used for hash tables + */ + key: function (cap) { + return cap.cap_port+ + cap.cap_priv.prv_obj+ + cap.cap_priv.prv_rights+ + cap.cap_priv.prv_rand; + }, + + /* + ** Encryption function + */ + one_way: function (port) { + var key = Array.create(64,0); + var block = Array.create(48,0); + var pubport = String.make (PORT_SIZE,'\0'); + var i, j, k; + + /* + ** We actually need 64 bit key. + ** Throw some zeroes in at bits 6 and 7 mod 8 + ** The bits at 7 mod 8 etc are not used by the algorithm + */ + j=0; + for (i = 0; i< 64; i++) { + if ((i & 7) > 5) + key[i] = 0; + else { + if ((this.get_portbyte(port, (j >> 3)) & (1 << (j & 7))) != 0) + key[i] = 1; + else + key[i] = 0; + j++; + } + } + + Des48.des_OWsetkey(key); + /* + ** Now go encrypt constant 0 + */ + block=Des48.des_OWcrypt48(block); + + + /* + ** and put the bits in the destination port + */ + var pb = 0; + + for (i = 0; i < PORT_SIZE;i++) { + var pbyte = 0; + for (j = 0; j < 8; j++) { + pbyte = pbyte | (block[pb] << j); + pb++; + } + pubport=this.set_portbyte(pubport, i, pbyte); + } + + return pubport; + }, + /* + ** Check whether the required rights [R1;R2;..] are + ** present in the rights field rg. Return a boolean value. + */ + rights_req : function(rights,required) { + var all=true; + Array.iter(required,function(rq) { + if (rq & rights == 0) all = false; + }); + return all; + }, + port_cmp: function(port1,port2) { + var i; + var eq=true; + for(i=0;i 0) str = str + ':'; + str = str + pad(num.toString(16).toUpperCase(), 2); + } + } else str='undefined'; + return str; + }, + port_of_str: function (str,compact) { + var tokens=str.split(':'),i,port=''; + for (i=0;i 0) str = str + ':'; + str = str + pad(num.toString(16).toUpperCase(), 2); + } + } else str='undefined'; + return str; + }, + /** + * + * @param priv + * @returns {string} + */ + private: function(priv) { + var str=''; + if (priv==undefined) return 'undefined'; + str=priv.prv_obj; + str=str+'('+String.format_hex(priv.prv_rights,2).toUpperCase()+')['; + str=str+this.port(priv.prv_rand)+']'; + return str; + }, + status: Status.print + } +}; + +module.exports = Net; +}; +BundleModuleCode['com/cbl']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2017 bLAB + ** $CREATED: 27-11-17 by sbosse. + ** $VERSION: 1.1.1 + ** + ** $INFO: + ** + ** JavaScript Callback List + ** + ** Assume there is a set of non-blocking IO operations with callbacks io1,io2,.., and there is a final + ** callback function that should be called after all io operations have finished. + ** + ** $ENDOFINFO + */ + +function CBL(callback) { + if (!(this instanceof CBL)) return new CBL(callback); + this.schedules=[]; + this.callback=callback; +} + +// Next schedule +CBL.prototype.next = function (status) { + var f=this.schedules.shift(); + // if (f) console.log('next '+f.toString()) + if (f) { + f(this.next.bind(this),status); + } else if (this.callback) this.callback(status); +} + +// Add next IO callback at the end of the list +CBL.prototype.push = function (f) { + this.schedules.push(f); +} + +// Execute CBL +CBL.prototype.start = function () { + this.next(); +} + +// Add next IO callback at the top of the list +CBL.prototype.top = function (f) { + this.schedules.unshift(f); +} + +module.exports=CBL; +}; +BundleModuleCode['jam/amp']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2020 bLAB + ** $CREATED: 09-02-16 by sbosse. + ** $RCS: $Id: amp.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.11.1 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) over UDP/HTTP/devices/streams + ** + ** + ** New: Fully negotiated IP Multicast Ports (P2N) + ** + ** $ENDOFINFO + */ + +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + getNetworkIP=COM.getNetworkIP, + doUntilAck=COM.doUntilAck; + +options.localhost='localhost'; +options.version='1.11.1', +options.AMC_MAXLIVE=5, +options.TIMER=500, +options.TRIES=10; +options.REGTMO=1000; +options.pem={}; + +/*********************** +** AMP +************************/ + +var ampMAN = Require('jam/ampMAN'); +var ampRPC = Require('jam/ampRPC'); + +if (global.TARGET!= 'browser') { + /******************************* AMP *************************************/ + + var ampUDP = Require('jam/ampUDP'); + var ampTCP = Require('jam/ampTCP'); + var ampStream = Require('jam/ampStream'); +} + +var ampHTTP = Require('jam/ampHTTP'); +var ampHTTPS = Require('jam/ampHTTPS'); + + +/** Main AMP constructor + * ==================== + * + */ +var Amp = function (options) { + var obj; + + switch (options.proto) { + case 'stream': + obj=new amp.stream(options); + break; + case 'http': + obj=new amp.http(options); + break; + case 'https': + obj=new amp.https(options); + break; + case 'tcp': + obj=new amp.tcp(options); + break; + case 'udp': + default: + obj=new amp.udp(options); + } + return obj; +} + +module.exports.current=function (module) { + current=module.current; Aios=module; + if (ampMAN) ampMAN.current(module); + if (ampUDP) ampUDP.current(module); + if (ampHTTP) ampHTTP.current(module); + if (ampHTTPS) ampHTTPS.current(module); + if (ampTCP) ampTCP.current(module); + if (ampStream) ampStream.current(module); + if (ampRPC) ampRPC.current(module); +}; + +module.exports.Amp=Amp; +module.exports.AMMessageType=AMMessageType; +module.exports.AMState=AMState; +module.exports.AMMode=AMMode; +module.exports.Com=COM; +module.exports.Rpc=ampRPC; + +module.exports.url2addr=url2addr; +module.exports.addr2url=addr2url; +module.exports.resolve=resolve; + +module.exports.Command=Command +module.exports.Status=Status + +module.exports.options=options; +}; +BundleModuleCode['jam/ampCOM']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 09-02-16 by sbosse. + ** $RCS: $Id: ampCOM.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.16.1 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) - Common Types and Utils + ** + ** + ** + ** $ENDOFINFO + */ + +var options = { + debug:{}, + magic: 0x6dab, // start of an AMP message (16bit) + peekIP: '134.102.22.124', // used by getnetworkip, must be an HTTP server + localhost : 'localhost', // default name for localhost +} + +var Comp = Require('com/compat'); +var Dns = Require('dns'); + +// Channel mode flags +var AMMode = { + AMO_UNICAST: 1, // P2P + AMO_MULTICAST: 2, // P2N + AMO_STATIC: 4, // + AMO_BUFFER: 8, // Transfer buffer data + AMO_OBJECT: 16, // Transfer objects instead of buffer data + AMO_COMPRESS: 32, // Compress data + AMO_SERVER: 64, // This is HTTP server mode + AMO_CLIENT: 128, // This is HTTP client mode + AMO_ONEWAY:256, // Other side can be reache dw/o link negotiation + print: function (m) { + var s='',sep=''; + if (m & AMMode.AMO_UNICAST) s += (sep+'UNI'),sep='|'; + if (m & AMMode.AMO_MULTICAST) s += (sep+'MUL'),sep='|'; + if (m & AMMode.AMO_STATIC) s += (sep+'STA'),sep='|'; + if (m & AMMode.AMO_BUFFER) s += (sep+'BUF'),sep='|'; + if (m & AMMode.AMO_OBJECT) s += (sep+'OBJ'),sep='|'; + if (m & AMMode.AMO_COMPRESS) s += (sep+'ZIP'),sep='|'; + if (m & AMMode.AMO_CLIENT) s += (sep+'CLI'),sep='|'; + if (m & AMMode.AMO_SERVER) s += (sep+'SRV'),sep='|'; + if (m & AMMode.AMO_ONEWAY) s += (sep+'ONE'),sep='|'; + return s; + } +} + +// Message type +var AMMessageType = { + AMMACK:0, + AMMPING:1, + AMMPONG:2, + AMMLINK:3, + AMMUNLINK:4, + AMMRPCHEAD:6, // Header followed by multiple data requests + AMMRPCDATA:7, + // Broker Rendezvous support + AMMCONTROL:8, + AMMRPC:9, // Header + data in one message + AMMCOLLECT:10, // Collect messages + AMMRPCHEADDATA:11, // Header with embedded data + + // Port Scan for external Run Server - returns AMP info + AMMSCAN : 12, + AMMINFO : 13, + + print:function(op) { + switch (op) { + case AMMessageType.AMMACK: return "AMMACK"; + case AMMessageType.AMMPING: return "AMMPING"; + case AMMessageType.AMMPONG: return "AMMPONG"; + case AMMessageType.AMMLINK: return "AMMLINK"; + case AMMessageType.AMMUNLINK: return "AMMUNLINK"; + case AMMessageType.AMMRPCHEAD: return "AMMRPCHEAD"; + case AMMessageType.AMMRPCHEADDATA: return "AMMRPCHEADDATA"; + case AMMessageType.AMMRPCDATA: return "AMMRPCDATA"; + case AMMessageType.AMMRPC: return "AMMRPC"; + case AMMessageType.AMMCOLLECT: return "AMMCOLLECT"; + // Rendezvous Broker Message + case AMMessageType.AMMCONTROL: return "AMMCONTROL"; + case AMMessageType.AMMSCAN: return "AMMSCAN"; + case AMMessageType.AMMINFO: return "AMMINFO"; + default: return "Chan.AMMessageType?"; + } + } + +}; + +// Channel state +var AMState = { + AMS_NOTINIT:1, // AMP Not initialized conenction + AMS_INIT:2, // AMP Server started, but not confirmed + AMS_READY:3, // AMP Server initialized and confirmed (other end point not connected) + AMS_NEGOTIATE:4, // AMP Server intiialized, in negotiation state (other end point not connected) + AMS_CONNECTED:5, // AMP Other side connected + AMS_AWAIT:6, // AMP waits for event (pairing) + AMS_NOTCONNECTED:10, // AMP Other side not connected + // Optional IP broker service + AMS_RENDEZVOUS:7, // Broker IP P2P rendezvous; starting + AMS_REGISTERED:8, // Broker IP P2P rendezvous; registered; expecting pairing + AMS_PAIRING:9, // Broker IP P2P rendezvous; now pairing; send punches until paired + AMS_PAIRED:10, // Broker IP P2P rendezvous; acknowldeged and paired -> NOTCONNECTED + print:function(op) { + switch (op) { + case AMState.AMS_NOTINIT: return "AMS_NOTINIT"; + case AMState.AMS_INIT: return "AMS_INIT"; + case AMState.AMS_READY: return "AMS_READY"; + case AMState.AMS_NEGOTIATE: return "AMS_NEGOTIATE"; + case AMState.AMS_CONNECTED: return "AMS_CONNECTED"; + case AMState.AMS_AWAIT: return "AMS_AWAIT"; + case AMState.AMS_NOTCONNECTED: return "AMS_NOTCONNECTED"; + case AMState.AMS_RENDEZVOUS: return "AMS_RENDEZVOUS"; + case AMState.AMS_REGISTERED: return "AMS_REGISTERED"; + case AMState.AMS_PAIRING: return "AMS_PAIRING"; + case AMState.AMS_PAIRED: return "AMS_PAIRED"; + default: return "Chan.AMState?"; + } + } + }; + +/** Used by AMP messages (msg.cmd,msg.status) + * + */ + +// Standard Object Service +var STD_FIRST_COM = 1000; +var STD_LAST_COM = 1999; +var STD_FIRST_ERR = (-STD_FIRST_COM); +var STD_LAST_ERR = (-STD_LAST_COM); + +// System Process Server (incl. Agent Platform Manager) +var PS_FIRST_COM = 2200; +var PS_LAST_COM = 2299; +var PS_FIRST_ERR = (-PS_FIRST_COM); +var PS_LAST_ERR = (-PS_LAST_COM); + +var Command = { + /* + ** Standard Commands + */ + STD_MONITOR : STD_FIRST_COM, + STD_AGE : (STD_FIRST_COM+1), + STD_COPY : (STD_FIRST_COM + 2), + STD_DESTROY : (STD_FIRST_COM + 3), + // Get agent or node info + STD_INFO : (STD_FIRST_COM + 4), + STD_RESTRICT : (STD_FIRST_COM + 5), + // Get agent or node status + STD_STATUS : (STD_FIRST_COM + 6), + STD_TOUCH : (STD_FIRST_COM + 7), + STD_GETPARAMS : (STD_FIRST_COM + 8), + STD_SETPARAMS : (STD_FIRST_COM + 9), + STD_NTOUCH : (STD_FIRST_COM + 10), + STD_EXIT : (STD_FIRST_COM + 11), + STD_RIGHTS : (STD_FIRST_COM + 12), + STD_EXEC : (STD_FIRST_COM + 13), + STD_LOCATION : (STD_FIRST_COM + 20), + STD_LABEL : (STD_FIRST_COM + 21), + + /* + ** Agent Process Control + */ + PS_STUN : (PS_FIRST_COM), // Kill a process/ create a snapshot + PS_MIGRATE : (PS_FIRST_COM+1), // Execute a process from a snapshot after migration (->next+) + PS_EXEC : (PS_FIRST_COM+2), // Execute a process from a snapshot (->next) + PS_WRITE : (PS_FIRST_COM+4), // Store a process class template + PS_READ : (PS_FIRST_COM+5), // Get a process class template + PS_CREATE : (PS_FIRST_COM+6), // Create a process from a template and execute + PS_FORK : (PS_FIRST_COM+7), // Fork a process from a running process + PS_SIGNAL : (PS_FIRST_COM+8), // Send a signal to a process + +}; + +var Status = { + STD_OK:0, + STD_CAPBAD : STD_FIRST_ERR, + STD_COMBAD : (STD_FIRST_ERR-1), + STD_ARGBAD : (STD_FIRST_ERR-2), + STD_NOTNOW : (STD_FIRST_ERR-3), + STD_NOSPACE : (STD_FIRST_ERR-4), + STD_DENIED : (STD_FIRST_ERR-5), + STD_NOMEM : (STD_FIRST_ERR-6), + STD_EXISTS : (STD_FIRST_ERR-7), + STD_NOTFOUND : (STD_FIRST_ERR-8), + STD_SYSERR : (STD_FIRST_ERR-9), + STD_INTR : (STD_FIRST_ERR-10), + STD_OVERFLOW : (STD_FIRST_ERR-11), + STD_WRITEPROT : (STD_FIRST_ERR-12), + STD_NOMEDIUM : (STD_FIRST_ERR-13), + STD_IOERR : (STD_FIRST_ERR-14), + STD_WRONGSRV : (STD_FIRST_ERR-15), + STD_OBJBAD : (STD_FIRST_ERR-16), + STD_UNKNOWN : (STD_FIRST_ERR-17), + RPC_FAILURE : -1, + BUF_OVERFLOW : -2, +} + +var amp={ + AMMessageType:AMMessageType, + AMState:AMState +}; + +/** Search a channel that is connected to node 'destnode' + * + */ +function lookupNode(node,destnode) { + var chan,url; + if (node.connections.ip && node.connections.ip.lookup) { + url=node.connections.ip.lookup(destnode); + if (url) return { + chan:node.connections.ip, + url:url, + link:node.connections.ip.routingTable[url] + }; + } +} + + +/************************* +** IP UTILS +*************************/ +function isLocal(addr) { + return addr=='localhost'|| + addr=='127.0.0.1' +} +function isIpAddr(addr) { + return (/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/.test(addr)) +} +/* typeof @url = "://:" | ":" | +* ":" | ":" | ":" | "" + * and @ipport = (1-65535) | "*" + * and @port = string + */ +function parseUrl(url) { + if (!isNaN(Number(url)) || url=='*') return { + proto: undefined, + address: undefined, + port: url, + param: undefined, + value: undefined + } + var tokens = url.match(/((http|https|udp|tcp):\/\/)?([a-zA-Z0-9_\.\-]+):(\[?[a-zA-Z0-9]+\]?|\*)(\?([a-zA-z0-9]+)=([a-zA-Z0-9:]+))?/) + if (!tokens) + tokens = url.match(/((http|https|udp|tcp):\/\/)?([a-zA-Z0-9_\.\-]+)/); + return { + proto: tokens[2], + address: tokens[3], + port: tokens[4], + param: tokens[6], + value: tokens[7] + } +} + +function url2addr(url,defaultIP,callback) { + var addr={address:defaultIP||options.localhost,port:undefined}, + parts = parseUrl(url); + if (parts.proto) addr.proto=parts.proto; + if (parts.address) addr.address=parts.address; + if (parts.port && parts.port!='*') + addr.port=!isNaN(Number(parts.port))?Number(parts.port):parts.port; + if (parts.param) { + addr.parameter={}; + addr.parameter[parts.param]=parts.value; + } + + if (!isLocal(parts.address) && !isIpAddr(parts.address)) { + // : + // needs dns lookup with callback (async) + if (Dns) + Dns.lookup(parts.address, function (err,_addr) { + if (!err) addr.address=_addr; + if (callback) callback(addr); + }); + else if (callback) callback(addr); + return addr; + } + if (callback) callback(addr); + else return addr; +}; + +function params(po) { + var s='?',sep=''; + for(var p in po) { + s += (sep+p+'='+po[p]); + sep='&'; + } + return s; +} +function addr2url(addr,noproto) { + return (!noproto && addr.proto?(addr.proto+'://'):'')+ + (isLocal(addr.address)?options.localhost:addr.address)+':'+ + (addr.port?addr.port:'*')+ + (!noproto && addr.parameter?params(addr.parameter):'') +}; + +function obj2url(obj) { + if (!obj) return '*'; + if (obj.name && !obj.address) return obj.name+':*'; + if (!obj.address) return '*'; + return (isLocal(obj.address)?options.localhost:obj.address)+':'+(obj.port?obj.port:'*') +}; + +function addrequal(addr1,addr2) { + return ipequal(addr1.address,addr2.address) && addr1.port==addr2.port; +} + +function addrempty(addr) { + return !(addr && addr.address && addr.port); +} + +function resolve (url) {return addr2url(url2addr(url)) } + +function ipequal(ip1,ip2) { + if (ip1==undefined || ip2==undefined) return false; + else if ((Comp.string.equal(ip1,'localhost') || Comp.string.equal(ip1,'127.0.0.1')) && + (Comp.string.equal(ip2,'localhost') || Comp.string.equal(ip2,'127.0.0.1'))) return true; + else return ip1==ip2; +} + +// Use remote TCP connection to get this host IP (private address if behind NAT) +var ipnet = Require('net'); +var myip; +function getNetworkInterfaces() { + var results = null; + try { + var networkInterfaces = require('os').networkInterfaces; + var nets = networkInterfaces(); + for (var name of Object.keys(nets)) { + for (var net of nets[name]) { + // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses + if (net.family === 'IPv4' && !net.internal) { + results=results||{}; + if (!results[name]) { + results[name] = []; + } + results[name].push(net.address); + } + } + } + } catch (e) {}; + return results +} +function getNetworkIP(server,callback) { + var socket; + // 0. Use user defined environment variable + try { + if (typeof process != 'undefined' && + process.env && + process.env['HOSTIP']) + return callback(undefined,process.env['HOSTIP']); + } catch (e) { + + } + // 1. Try to connect external HTTP server to get our public IP + if (!ipnet) return callback('Not supported','error'); + if (myip) return callback(undefined,myip); + if (!server) server={address:options.peekIP,port:80}; + socket = ipnet.createConnection(server.port, server.address); + socket.on('connect', function() { + myip=socket.address().address; + callback(undefined, socket.address().address); + socket.end(); + }); + socket.on('error', function(e) { + // Try to get our (local) IP from network interface information + var results = getNetworkInterfaces(); + if (!results) + return callback(e, 'error'); + else { + for(var i in results) return callback(undefined,results[i]); + } + }); +} + + +function doUntilAck(interval, fn, ack, arg) { + if (ack()) return; + fn(arg); + return setTimeout(function() { + doUntilAck(interval, fn, ack, arg); + }, interval); +} + + +module.exports = { + AMMode:AMMode, + AMMessageType:AMMessageType, + AMState:AMState, + doUntilAck:doUntilAck, + getNetworkIP:getNetworkIP, + amp:amp, + options:options, + addrempty:addrempty, + addrequal:addrequal, + addr2url:addr2url, + ipequal:ipequal, + isLocal:isLocal, + lookupNode:lookupNode, + obj2url:obj2url, + resolve:resolve, + url2addr:url2addr, + Command:Command, + Status:Status, +} +}; +BundleModuleCode['jam/ampMAN']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2020 bLAB + ** $CREATED: 30-01-18 by sbosse. + ** $RCS: $Id: ampMAN.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.14.9 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) - General Management Operations + ** + ** + ** New: + ** - Single message transfers (HEADER+DATA) + ** + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Sec = Require('jam/security'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + addrempty=COM.addrempty, + getNetworkIP=COM.getNetworkIP; + +// Get data from message +function msgData(msg) { + // typeof msg.data = Array | Buffer | { type: 'Buffer', data: Array } + return msg.data && msg.data.data?msg.data.data:msg.data; +} + +module.exports.current=function (module) { current=module.current; Aios=module; }; + + +amp.man = function (options) { + +} + +// Message logger +amp.man.prototype.LOG = function (op,msg) { + if (!this.logging) return; + switch (op) { + case 'print': + for(var i in this.logs) { + Aios.log(this.logs[i].op,this.logs[i].time,this.logs[i].msg,AMState.print(this.logs[i].state)); + } + this.logs=[]; + break; + case 'enable': + this.logging=true; + break; + case 'disable': + this.logging=false; + break; + default: + var date = new Date(); + var time = Math.floor(date.getTime()); + this.logs.push({op:op,time:time,msg:msg,state:(this.url && this.links[this.url].state)}); + } +} + + + +/** Transation cache for receiving data fragments that can be out of order. + * typeof @data = [handler:{tid,remote,cmd,size,frags,buf},data:[],timeout:number] + * + */ +amp.man.prototype.addTransaction = function (remote,tid,data) { + if (this.mode & AMMode.AMO_MULTICAST) + this.transactions[remote.address+remote.port+tid]=data; + else + this.transactions[tid]=data; +} +amp.man.prototype.deleteTransaction = function (remote,tid) { + if (this.mode & AMMode.AMO_MULTICAST) + delete this.transactions[remote.address+remote.port+tid]; + else + delete this.transactions[tid]; +} +amp.man.prototype.findTransaction = function (remote,tid) { + if (this.mode & AMMode.AMO_MULTICAST) + return this.transactions[remote.address+remote.port+tid]; + else + return this.transactions[tid]; +} + +/** Check the state of a link + * + */ +amp.man.prototype.checkState = function (state,addr) { + switch (state) { + case AMState.AMS_CONNECTED: + if (this.mode & AMMode.AMO_ONEWAY) return true; + if (this.mode & AMMode.AMO_MULTICAST) return this.links[addr2url(addr,true)]; + if (this.url && this.links[this.url].state == AMState.AMS_CONNECTED) return true; + break; + } + return false; +} + +/** Update AMP object configuration + * + */ +amp.man.prototype.config = function(options) { + for(var p in options) this[p]=options[p]; +} +/** Handle events + * + */ +amp.man.prototype.emit = function(event,arg,aux,aux2) { + if (this.events[event]) this.events[event](arg,aux,aux2); +} + +/** Handler for incoming messages (proecssed by receiver) + * + */ +amp.man.prototype.handle = function (msg,remote,response) { + var handler,thisnum,ipport,cmsg,url,ack,info; + if (this.verbose > 1) this.out('handle '+AMMessageType.print(msg.type)+' from '+addr2url(remote),true); + switch (msg.type) { + case AMMessageType.AMMRPCHEAD: + case AMMessageType.AMMRPCHEADDATA: + if (!this.checkState(AMState.AMS_CONNECTED,remote)) return; + + handler={}; + handler.tid=msg.tid; + // handler.remote=remote.address+':'+Buf.buf_get_int16(buf); + handler.remote=remote; + handler.cmd=msg.cmd; + handler.size=msg.size; + handler.frags=msg.frags; + if (msg.size<0) this.err('Got invalid message (size<0) from '+addr2url(remote)); // in16 limit + // console.log(handler) + if (handler.size>0 && handler.frags>0) { + // AMMRPCDATA messages are following (used by UDP) + handler.buf=Buf.Buffer(); + dlist = Comp.array.range(0, handler.frags - 1); + // Add transaction to cache for pending data + this.addTransaction(remote, handler.tid, [handler,dlist,1000]); + } else if (handler.size>0) { + // Single message transfer; message contains all data (msg.data: Buf.buffer!, used by TCP) + handler.buf=msg.data; + this.callback(handler); + } else { + // No data; control message + handler.buf=Buf.Buffer(); + this.callback(handler); + } + break; + + case AMMessageType.AMMRPCDATA: + if (!this.checkState(AMState.AMS_CONNECTED,remote)) return; + thisnum = msg.off/this.dlimit; + transaction = this.findTransaction(remote,msg.tid); + if (transaction!=undefined) { + handler=transaction[0]; + if (this.verbose>1) + this.out('receiver: adding data num='+ + thisnum+' off='+msg.off+' size='+msg.size+' dlist='+transaction[1],true); + + Buf.buf_get_buf(msg.data,handler.buf,msg.off,msg.size); + transaction[1]=Comp.array.filter(transaction[1],function(num) {return (num!=thisnum)}); + if (Comp.array.empty(transaction[1])) { + if (this.verbose>2) this.out('[AMP] receiver: finalize '+addr2url(remote),true); + // Io.out(handler.data.toString()); + // Deliver + this.callback(handler); + this.deleteTransaction(remote,msg.tid); + } + } + break; + + case AMMessageType.AMMRPC: + // Single data transfer - used by HTTP/Browser + if (!this.checkState(AMState.AMS_CONNECTED,remote)) return; + // Complete RPC message + handler={}; + handler.tid=msg.tid; + // handler.remote=remote.address+':'+Buf.buf_get_int16(buf); + handler.remote=remote; + handler.cmd=msg.cmd; + handler.size=msg.size; + handler.frags=msg.frags; + handler.buf=Buf.Buffer(msgData(msg)); + this.callback(handler); + if (this.ack && response) this.ack(response); + break; + + case AMMessageType.AMMPING: + url=addr2url(remote,true); + ipport=remote.port; + if (this.mode&AMMode.AMO_MULTICAST) { + if (!this.links[url] || this.links[url].state!=AMState.AMS_CONNECTED) return; + } else if (this.url) { + if (this.links[this.url].state!=AMState.AMS_CONNECTED) return; + } + // Send back a PONG message only if we're connected + this.pong({address:remote.address,port:ipport},response); + break; + + case AMMessageType.AMMPONG: + ipport=remote.port; + if (this.mode&AMMode.AMO_MULTICAST) { + url=addr2url(remote,true); + if (this.links[url] && this.links[url].state==AMState.AMS_CONNECTED) { + this.links[url].live = options.AMC_MAXLIVE; + } + } else if (this.url && this.links[this.url].state==AMState.AMS_CONNECTED) { + this.links[this.url].live = options.AMC_MAXLIVE; + } + if (this.ack && response) this.ack(response); + break; + + case AMMessageType.AMMACK: + // TODO: check pending waiters (scan mode) + if (msg.status=="ELINKED") { + if (this.mode&AMMode.AMO_MULTICAST) { + // Multicast mode + url=addr2url(remote,true); + if (!this.links[url] || this.links[url].state==AMState.AMS_NOTCONNECTED) { + // Ad-hoc remote connect + if (!this.links[url]) this.links[url]={}; + this.links[url].snd=remote; + this.links[url].live=options.AMC_MAXLIVE; + this.links[url].port=msg.port; + this.links[url].ipport=remote.port; + this.links[url].state=AMState.AMS_CONNECTED; + this.links[url].node=msg.node; + this.emit('route+',url,msg.node); + this.watchdog(true); + if (this.verbose) + this.out('Linked with ad-hoc '+this.proto+' '+url+', AMP '+ + Net.Print.port(msg.port)+', Node '+msg.node,true); + } + } + } + break; + + case AMMessageType.AMMLINK: + ipport=remote.port; + url=addr2url(remote,true); + if (this.secure && (!msg.secure || !Sec.Port.equal(this.secure,msg.secure))) return; + if (this.mode&AMMode.AMO_MULTICAST) { + // Multicast mode + if (!this.links[url] || this.links[url].state==AMState.AMS_NOTCONNECTED) { + // Ad-hoc remote connect + if (!this.links[url]) this.links[url]={}; + this.links[url].snd=remote; + this.links[url].live=options.AMC_MAXLIVE; + this.links[url].port=msg.port; + this.links[url].ipport=remote.port; + // back link acknowledge + this.link(this.links[url].snd,false,none,response); + // no ack="EOK" -- ack send by link response!; + this.links[url].state=AMState.AMS_CONNECTED; + this.links[url].node=msg.node; + // if (this.mode&AMMode.AMO_UNICAST) this.snd=remote,this.url=url; + this.emit('route+',url,msg.node,msg.remote); + this.watchdog(true); + if (this.verbose) + this.out('Linked with ad-hoc '+this.proto+' '+url+', AMP '+ + Net.Print.port(msg.port)+', Node '+msg.node,true); + } else if (this.links[url].state==AMState.AMS_CONNECTED) { + // Already linked! Just acknowledge + ack="ELINKED"; + } + } else { + + // Unicast mode; only one connection + if (this.links[url] && !addrempty(this.links[url].snd) && + this.links[url].state==AMState.AMS_NOTCONNECTED && + ipequal(this.links[url].snd.address,remote.address) && + this.links[url].snd.port==ipport) // ipport or remote.port?? + { + // Preferred / expected remote connect + this.links[url].snd=remote; + this.links[url].port=msg.port; + this.links[url].ipport=remote.port; + this.links[url].node=msg.node; + this.links[url].live=options.AMC_MAXLIVE; + + // back link acknowledge + this.link(this.links[url].snd); + + this.links[url].state=AMState.AMS_CONNECTED; + // Inform router + this.emit('route+',url,msg.node,msg.remote); + this.watchdog(true); + if (this.verbose) + this.out('Linked with preferred '+this.proto+' '+ url +', '+ + Net.Print.port(msg.port),true); + } else if ((!this.links[url] && !this.url) || + (this.links[url] && this.links[url].state==AMState.AMS_NOTCONNECTED) || + (this.broker && this.url && this.links[this.url].state==AMState.AMS_NOTCONNECTED)) { + if (!this.links[url]) this.links[url]={}; + this.links[url].snd=remote; + this.links[url].live=options.AMC_MAXLIVE; + this.links[url].port=msg.port; + this.links[url].ipport=remote.port; + this.links[url].node=msg.node; + + // back link acknowledge + this.link(this.links[url].snd,false,none,response); + // no ack="EOK"; - ack was send with link message! + + this.links[url].state=AMState.AMS_CONNECTED; + this.url=url; // remember this link + + // Inform router + this.emit('route+',url,msg.node); + this.watchdog(true); + + if (this.verbose) + this.out('Linked with ad-hoc ' + this.proto +' '+ url +', '+ + Net.Print.port(msg.port),true); + } + } + if (ack && this.ack && response) this.ack(response,ack); + break; + + case AMMessageType.AMMUNLINK: + ipport=remote.port; + if (this.mode&AMMode.AMO_MULTICAST) { + // Multicast mode + url=addr2url(remote,true); // ipport or remote.port?? + if (this.links[url] && !addrempty(this.links[url].snd) && ipequal(this.links[url].snd.address,remote.address) && + this.links[url].snd.port==ipport && this.links[url].state==AMState.AMS_CONNECTED) { + this.links[url].state=AMState.AMS_NOTCONNECTED; + // Not negotiated. Just close the link! + if (this.verbose) + this.out('Unlinked ' +url+', '+ + Net.Print.port(msg.port),true); + // Inform router + this.emit('route-',url); + if (!this.links[url].snd.connect) this.links[url].snd={}; + if (this.cleanup) this.cleanup(url); + } + } else { + // Unicast mode + if (this.url && !addrempty(this.links[this.url].snd) && + ipequal(this.links[this.url].snd.address,remote.address) && + this.links[this.url].snd.port==ipport && + this.links[this.url].state==AMState.AMS_CONNECTED) + { + this.links[this.url].state=AMState.AMS_NOTCONNECTED; + addr=this.links[this.url].snd; + // Not negotiated. Just close the link! + if (this.verbose) + this.out('Unlinked ' + this.url +', '+ + Net.Print.port(msg.port),true); + + // Inform router + this.emit('route-',addr2url(addr)); + if (!this.links[this.url].snd.connect) this.links[this.url].snd=null; + if (this.cleanup) this.cleanup(url); + } + } + if (this.ack && response) this.ack(response); + break; + + // optional rendezvous brokerage ; remote is broker IP!!! + case AMMessageType.AMMCONTROL: + cmsg = JSON.parse(msgData(msg)); + if (this.verbose>1) this.out('# got message '+msgData(msg),true); + this.LOG('rcv',cmsg); + // All brokerage and pairing is handled by the root path '*'! + if (this.control && this.links['*']) + this.control(this.links['*'],cmsg,remote); + break; + + case AMMessageType.AMMSCAN: + url=addr2url(remote,true); + ipport=remote.port; + info={ + world:(current.world&¤t.world.id), + stats:(current.world&¤t.world.info()), + }; + this.scan({address:remote.address,port:ipport,info:info},response); + break; + + default: + this.out('handle: Unknown message type '+msg.type,true); + } +} + +/** Install event handler + * + */ +amp.man.prototype.on = function(event,handler) { + this.events[event]=handler; +} + +// Status of link, optionally checking destination +amp.man.prototype.status = function (ip,ipport) { + var p,url,sl=[]; + if (ip=='%') { + // return all connected nodes + for(p in this.links) if (this.links[p] && this.links[p].state==AMState.AMS_CONNECTED) + sl.push(this.links[p].node); + return sl; + } + if (this.mode&AMMode.AMO_MULTICAST) { + if (!ip) { + for(p in this.links) if (this.links[p] && this.links[p].state==AMState.AMS_CONNECTED) return true; + return false; + } else { + url=addr2url({address:ip,port:ipport}); + if (!this.links[url]) return false; + return this.links[url].state==AMState.AMS_CONNECTED; + } + } + if (!ip && this.url) return this.links[this.url].state==AMState.AMS_CONNECTED || + (this.mode&AMMode.AMO_ONEWAY)==AMMode.AMO_ONEWAY; + + return (this.url && ipequal(this.links[this.url].snd.address,ip) && this.links[this.url].snd.port==ipport); +} +}; +BundleModuleCode['jam/security']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2019 bLAB + ** $CREATED: 04-02-19 by sbosse. + ** $RCS: $Id: security.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.1.3 + ** + ** $INFO: + ** + ** JAM Capability and Security Management. Derived from dos/net module. + ** + ** + ** + ** $ENDOFINFO + */ + +var Io = Require('com/io'); +var Des48 = Require('dos/des48'); +var Base64 = Require('os/base64'); +var Comp = Require('com/compat'); +var String = Comp.string; +var Array = Comp.array; +var Perv = Comp.pervasives; +var current = none; +var Aios = none; +var Rnd = Require('com/pwgen'); + + +var PORT_SIZE = 6; +var PRIV_SIZE = 4+PORT_SIZE; +var CAP_SIZE = 16; +var PRV_ALL_RIGHTS = 0xff; + + +var priv2pub_cache = []; +var uniquePorts = {}; + +var Rights = { + HOST_INFO : 0x01, + HOST_READ : 0x02, + HOST_WRITE : 0x04, + HOST_EXEC : 0x08, + + PSR_READ : 0x01, + PSR_WRITE : 0x02, + PSR_CREATE : 0x04, + PSR_DELETE : 0x08, + PSR_EXEC : 0x10, + PSR_KILL : 0x20, + PSR_ALL : 0xff, + + NEG_SCHED : 0x08, + NEG_CPU : 0x10, + NEG_RES : 0x20, + NEG_LIFE : 0x40, + NEG_LEVEL : 0x80, + + PRV_ALL_RIGHTS : 0xff +}; + +/** + * + * typeof @port_valse = number [] + * typeof return = string + */ +var Port = function (port_vals) { + if (port_vals==undefined) port_vals=[0,0,0,0,0,0]; + var port=''; + for(var i = 0; i< PORT_SIZE;i++) { + port=port+Perv.char_of_int(port_vals[i]); + } + return port; + +}; +/** + * + * typeof @obj = number | undefined + * typeof @rights = number | undefined + * typeof @rand = port | undefined + * typeof function = constructor + */ +var Private = function (obj,rights,rand) { + if (obj==undefined) { + // Create empty private field + return { + prv_obj : 0, + prv_rights : 0, + prv_rand : Port() + } + } else { + return { + prv_obj : obj, // Integer + prv_rights : rights, // Integer + prv_rand : rand // Port=string + } + } +} + +/** + * + * typeof @cap_port = port + * typeof @cap_priv = privat + * typeof function = @constructor + */ +var Capability = function(cap_port, cap_priv) { + if (cap_port==undefined) { + // Create empty capability + return { + cap_port : Port(), + cap_priv : Private() + } + } else { + return { + cap_port : cap_port, // Port=string + cap_priv : cap_priv?cap_priv:Private() + } + } +} +function cap_parse(str,offset) { + var cap=Capability(), + pos=0; + if (offset!=undefined) pos=offset; + var pp=port_parse(str,pos); + if (pp==undefined) return undefined; + cap.cap_port=pp.port; + pos=pp.pos; + pp=prv_parse(str,pos); + if (pp==undefined) return undefined; + cap.cap_priv=pp.priv; + pos=pp.pos; + return {cap:cap,pos:pos}; +} + +function cap_of_string(str) { var pp = cap_parse(str,0); return pp?pp.cap:undefined } + +function cap_to_string(cap) { + var str=''; + if (cap==undefined) return 'undefined'; + if (cap.cap_port!=undefined) str='['+port_to_string(cap.cap_port)+']'; else str = '[]'; + if (cap.cap_priv!=undefined) str=str+'('+prv_to_string(cap.cap_priv)+')'; else str=str+'()'; + return str; +} + +/* + ** Utils to get and set single bytes of a port + */ +function get_portbyte(port,i) { + return Perv.int_of_char(String.get(port,i)) +} +function set_portbyte(port,i,byte) { + return String.set(port, i, (Perv.char_of_int(byte))); +} + +/* + ** Encryption function + */ +function one_way(port) { + var key = Array.create(64,0); + var block = Array.create(48,0); + var pubport = String.make (PORT_SIZE,'\0'); + var i, j, k; + + /* + ** We actually need 64 bit key. + ** Throw some zeroes in at bits 6 and 7 mod 8 + ** The bits at 7 mod 8 etc are not used by the algorithm + */ + j=0; + for (i = 0; i< 64; i++) { + if ((i & 7) > 5) + key[i] = 0; + else { + if ((get_portbyte(port, (j >> 3)) & (1 << (j & 7))) != 0) + key[i] = 1; + else + key[i] = 0; + j++; + } + } + + Des48.des_OWsetkey(key); + /* + ** Now go encrypt constant 0 + */ + block=Des48.des_OWcrypt48(block); + + + /* + ** and put the bits in the destination port + */ + var pb = 0; + + for (i = 0; i < PORT_SIZE;i++) { + var pbyte = 0; + for (j = 0; j < 8; j++) { + pbyte = pbyte | (block[pb] << j); + pb++; + } + pubport=set_portbyte(pubport, i, pbyte); + } + return pubport; +} + +function pad(str,size) { + while (str.length < (size || 2)) {str = "0" + str;} + return str; +} + +function port_cmp(port1,port2) { + if (port1==undefined || port2==undefined) return (port1==port2); + else return String.equal(port1,port2); +} + +function port_copy(port) { + return String.copy(port); +} + +// Expected format: XX:XX:XX:XX:XX +function port_of_string(str,compact) { + var tokens=str.split(':'),i,port=''; + for (i=0;i 0) str = str + ':'; + str = str + pad(num.toString(16).toUpperCase(), 2); + } + } else str='undefined'; + return str; +} + + +function prv2pub (port) { + var putport; + if (priv2pub_cache[port] == undefined) { + putport=one_way(port); + priv2pub_cache[port] = putport; + } else putport = priv2pub_cache[port]; + return putport; +} + +function prv_cmp(prv1,prv2) { + return (prv1==undefined&&prv2==undefined) || + (prv1.prv_obj==prv2.prv_obj && + prv1.prv_rights==prv2.prv_rights && + port_cmp(prv1.prv_rand,prv2.prv_rand)) +} + +/** + ** Decode a private structure (check for a valid private field) + * + * typeof @prv = privat + * typeof @rand = port + * returns boolean + */ +function prv_decode (prv,rand) { + if (prv.prv_rights == PRV_ALL_RIGHTS) + return port_cmp(prv.prv_rand,rand); + else { + var tmp_port = port_copy(rand), + pt0 = get_portbyte(tmp_port, 0), + pr0 = prv.prv_rights; + tmp_port = set_portbyte(tmp_port, 0, (pt0 ^ pr0)); + tmp_port = one_way(tmp_port); + return port_cmp(prv.prv_rand, tmp_port) + } +} + +/* + ** Encode a private part from the object number, the rights field + ** and the random port. + ** Returns the created private structure. + */ +function prv_encode(obj,rights,rand) { + var tmp_port = port_copy(rand), + r1 = rights, + rmask = PRV_ALL_RIGHTS; + + if (rights == PRV_ALL_RIGHTS) + return Private(obj,r1 & rmask,tmp_port); + else { + var pt0 = get_portbyte(tmp_port,0); + tmp_port = set_portbyte(tmp_port,0,pt0 ^ r1); + tmp_port = one_way(tmp_port); + return Private(obj,r1 & rmask,tmp_port) + } +} + +function prv_of_string(str) { var pp=prv_parse(str,0); return pp?pp.priv:undefined } + +/* + ** Return the private object number form a private structure + */ +function prv_number(prv) { + return prv.prv_obj; +} + +// Expected format: obj(right)[port] +function prv_parse(str,offset) { + var priv=Private(); + var sv; + var len=str.length,pos=offset; + if (str[pos]=='(') pos++; + sv=''; + while(str[pos]!='(') { + sv=sv+str[pos]; + pos++; + } + priv.prv_obj=Perv.int_of_string(sv); + sv=''; + if (str[pos]=='(') pos++; + while(str[pos]!=')') { + sv=sv+str[pos]; + pos++; + } + priv.prv_rights=Perv.int_of_string('0x'+sv); + if (str[pos]==')') pos++; + var pp=port_parse(str,pos); + if (pp==undefined) return undefined; + priv.prv_rand=pp.port; + pos=pp.pos; + return {priv:priv,pos:pos}; +} + + +function prv_to_string(priv) { + var str=''; + if (priv==undefined) return 'undefined'; + str=priv.prv_obj; + str=str+'('+String.format_hex(priv.prv_rights,2).toUpperCase()+')['; + str=str+port_to_string(priv.prv_rand)+']'; + return str; +} + +/** Restrict a private field (rights&mask) of a capability. + * + * @param {privat} priv + * @param {number} mask rights restriction mask + * @param {port} random secret server random port + */ +function prv_restrict(priv,mask,random) { + var pr = prv_encode(priv.prv_obj, + priv.prv_rights & mask, + random); + return pr; +} +/* + ** Return the private rights field. + */ +function prv_rights(prv) { + return prv.prv_rights & Rights.PRV_ALL_RIGHTS; +} +/* + ** Check the private rights field: 1. Validation, 2: Required rights. + */ +function prv_rights_check(prv,rand,required) { + if (!prv_decode(prv,rand)) return false; + return (prv.prv_rights & required)==required; +} + +/* + * Return a new random unique port. + * + * Warning: the quality of the random ports are strongly + * related to JSVMs underlying random generator. + * + * typeof return = port + */ +function uniqport() { + var port = String.create (PORT_SIZE); + var i,values; + + do { + values = Rnd.generate({number:true,length:PORT_SIZE}); + for (i = 0; i <= (PORT_SIZE - 1); i++) + port = String.set(port, i, (Perv.char_of_int(values[i]))); + if (uniquePorts[port]) uniquePorts[port]++; + else uniquePorts[port]=1; + } while (uniquePorts[port]>1); + return port; +} + +Port.equal = port_cmp +Port.toString = port_to_string +Port.ofString = port_of_string +Port.prv2pub = prv2pub +Port.random = uniqport +Port.unique = uniqport +Private.decode = prv_decode +Private.encode = prv_encode +Private.equal = prv_cmp +Private.number = prv_number +Private.ofString = prv_of_string +Private.restrict = prv_restrict +Private.rights = prv_rights +Private.rights_check = prv_rights_check +Private.toString = prv_to_string +Capability.toString = cap_to_string +Capability.ofString = cap_of_string + + +var Security = { + current:function (module) { current=module.current; Aios=module; }, + + PORT_SIZE:PORT_SIZE, + PRIV_SIZE:PRIV_SIZE, + Rights:Rights, + Private:Private, + Capability: Capability, + Port: Port, + nilport: Port(), + nilpriv: Private(0,0,Port()), + nilcap: Capability(Port(),Private(0,0,Port())), + one_way : one_way, + prv2pub : prv2pub, +} + +module.exports = Security; + +}; +BundleModuleCode['com/pwgen']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Bermi Ferrer, Stefan Bosse + ** $INITIAL: (C) 2011-2015 Bermi Ferrer , 2017-2018 Stefan Bosse + ** $REVESIO: 1.3.1 + ** + ** $INFO: + * + * password-generator using crypto random number generation (slow,HQ) + * !using built-in crypto random generators using either native crypto module or polyfill! + * + * options = {length,memorable,lowercase,uppercase,pattern,number?:boolean,range?:[]} + * + * Using always twister random byte generator (not random byte array) + * + * $ENDINFO + */ + +var Crypto = Require('os/crypto.rand'); // Require('crypto'); + +module.exports.generate = function (options) { + + function numgen (options) { + // assuming byte number range 0-255 + var arr = new Uint8Array(options.length||8); + getRandomValues(arr); + return arr; + } + + function pwgen (options) { + var localName, consonant, letter, vowel, pattern = options.pattern, + char = "", n, i, validChars = [], prefix=options.prefix; + letter = /[a-zA-Z]$/; + vowel = /[aeiouAEIOU]$/; + consonant = /[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]$/; + if (options.length == null) { + options.length = 10; + } + if (pattern == null) { + pattern = /\w/; + } + if (prefix == null) { + prefix = ''; + } + + // Non memorable passwords will pick characters from a pre-generated + // list of characters + if (!options.memorable) { + for (i = 33; 126 > i; i += 1) { + char = String.fromCharCode(i); + if (char.match(pattern)) { + validChars.push(char); + } + } + + if (!validChars.length) { + throw new Error("Could not find characters that match the " + + "password pattern " + pattern + ". Patterns must match individual " + + "characters, not the password as a whole."); + } + } + + + while (prefix.length < options.length) { + if (options.memorable) { + if (prefix.match(consonant)) { + pattern = vowel; + } else { + pattern = consonant; + } + n = Crypto.randomByte(33,126); // rand(33, 126); + char = String.fromCharCode(n); + } else { + char = validChars[rand(0, validChars.length)]; + } + + if (options.lowercase) char = char.toLowerCase(); + else if (options.uppercase) char = char.toUpperCase(); + + if (char.match(pattern)) { + prefix = "" + prefix + char; + } + } + return prefix; + }; + + + function rand(min, max) { + var key, value, arr = new Uint8Array(max); + getRandomValues(arr); + for (key in arr) { + if (arr.hasOwnProperty(key)) { + value = arr[key]; + if (value > min && value < max) { + return value; + } + } + } + return rand(min, max); + } + + + function getRandomValues(buf) { + var bytes = Crypto.randomBytes(buf.length); + buf.set(bytes); + } + if (options.number) + return numgen(options) + else + return pwgen(options); +}; +}; +BundleModuleCode['os/crypto.rand']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $CREATED: 15-1-16 by sbosse. + ** $VERSION: 1.2.4 + ** + ** $INFO: + ** + ** Crypto module with HQ random number generators (replacing not available crypto.getRandomValues + ** if there is no global crypto module). + ** + ** $ENDOFINFO + */ +var crypto = global.crypto || global.msCrypto; + +if (!crypto && typeof require != 'undefined') try { crypto=global.crypto=require('require') } catch (e) {}; + +var twister; + +var MersenneTwister = function(seed) { + if (seed == undefined) { + /** + ** It is not sure that Math.random is seeded randomly + ** Thus, a combination of current system time and Math.random + ** is used to seed and initialize this random generator + */ + seed = new Date().getTime(); + seed *= Math.random()*91713; + seed |= 0; + } + + /* Period parameters */ + this.N = 624; + this.M = 397; + this.MATRIX_A = 0x9908b0df; /* constant vector a */ + this.UPPER_MASK = 0x80000000; /* most significant w-r bits */ + this.LOWER_MASK = 0x7fffffff; /* least significant r bits */ + + this.mt = new Array(this.N); /* the array for the state vector */ + this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */ + + if (seed.constructor == Array) { + this.init_by_array(seed, seed.length); + } + else { + this.init_seed(seed); + } +} + +/* initializes mt[N] with a seed */ +/* origin name init_genrand */ +MersenneTwister.prototype.init_seed = function(s) { + this.mt[0] = s >>> 0; + for (this.mti=1; this.mti>> 30); + this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253) + + this.mti; + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + this.mt[this.mti] >>>= 0; + /* for >32 bit machines */ + } +} + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +/* slight change for C++, 2004/2/26 */ +MersenneTwister.prototype.init_by_array = function(init_key, key_length) { + var i, j, k; + this.init_seed(19650218); + i=1; j=0; + k = (this.N>key_length ? this.N : key_length); + for (; k; k--) { + var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30) + this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525))) + + init_key[j] + j; /* non linear */ + this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ + i++; j++; + if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; } + if (j>=key_length) j=0; + } + for (k=this.N-1; k; k--) { + var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30); + this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941)) + - i; /* non linear */ + this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ + i++; + if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; } + } + + this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ +} + +/* generates a random number on [0,0xffffffff]-interval */ +/* origin name genrand_int32 */ +MersenneTwister.prototype.random_int = function() { + var y; + var mag01 = new Array(0x0, this.MATRIX_A); + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (this.mti >= this.N) { /* generate N words at one time */ + var kk; + + if (this.mti == this.N+1) /* if init_seed() has not been called, */ + this.init_seed(5489); /* a default initial seed is used */ + + for (kk=0;kk>> 1) ^ mag01[y & 0x1]; + } + for (;kk>> 1) ^ mag01[y & 0x1]; + } + y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK); + this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1]; + + this.mti = 0; + } + + y = this.mt[this.mti++]; + + /* Tempering */ + y ^= (y >>> 11); + y ^= (y << 7) & 0x9d2c5680; + y ^= (y << 15) & 0xefc60000; + y ^= (y >>> 18); + + return y >>> 0; +} + +/* generates a random number on [0,0x7fffffff]-interval */ +/* origin name genrand_int31 */ +MersenneTwister.prototype.random_int31 = function() { + return (this.random_int()>>>1); +} + +/* generates a random number on [0,1]-real-interval */ +/* origin name genrand_real1 */ +MersenneTwister.prototype.random_incl = function() { + return this.random_int()*(1.0/4294967295.0); + /* divided by 2^32-1 */ +} + +/* generates a random number on [0,1)-real-interval */ +MersenneTwister.prototype.random = function() { + return this.random_int()*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on (0,1)-real-interval */ +/* origin name genrand_real3 */ +MersenneTwister.prototype.random_excl = function() { + return (this.random_int() + 0.5)*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on [0,1) with 53-bit resolution*/ +/* origin name genrand_res53 */ +MersenneTwister.prototype.random_long = function() { + var a=this.random_int()>>>5, b=this.random_int()>>>6; + return(a*67108864.0+b)*(1.0/9007199254740992.0); +} + +function polyfill () { + twister = new MersenneTwister(); // (Math.random()*Number.MAX_SAFE_INTEGER)|0) + if (!crypto) crypto=global.crypto={}; + crypto.getRandomValues = function getRandomValues (abv) { + var l = abv.length + while (l--) { + abv[l] = Math.floor(twister.random() * 256) + } + return abv + } + if (!global.Uint8Array && !Uint8Array) throw new Error('crypto.rand: No Uint8Array found!'); + if (!global.Uint8Array) global.Uint8Array=Uint8Array; +} + + +function randomByte (min,max) { + if (!twister) twister = new MersenneTwister(); + return Math.floor(twister.random() * (max-min))+min; +} + +function randomBytes (size, cb) { + // phantomjs needs to throw + if (size > 65536) throw new Error('requested too many random bytes') + if (!crypto || !crypto.getRandomValues) polyfill(); + + // in case browserify isn't using the Uint8Array version + var rawBytes = new global.Uint8Array(size); + // This will not work in older browsers. + // See https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues + if (size > 0) { // getRandomValues fails on IE if size == 0 + crypto.getRandomValues(rawBytes); + } + // phantomjs doesn't like a buffer being passed here + var bytes = new Buffer(rawBytes); + if (typeof cb === 'function') { + cb(null, bytes) + } + + return bytes +} + +module.exports = { + randomByte:randomByte, + randomBytes:randomBytes +} +}; +BundleModuleCode['jam/ampRPC']=function (module,exports){ +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Command = Net.Command; +var Status = Net.Status; +var COM = Require('jam/ampCOM'); + +var current=none; +var Aios=none; + +// Current node must be set! +// E.g., by using jamlib.setCurrentNode(0) + +// typeof @obj = +// { agent : string } | +// { node : string } | +// 'processes' | +// 'agents' | +// 'links' | +// 'ports' | +// 'node' | +// 'ts' | +// { prv: .., .. } +// + +// Server side implementation is in chan.js + +var Std = { + + info : function (node,obj,callback) { + var node0=current.node; + var to=COM.lookupNode(node0,node); + if (to) { + to.link.control({ + cmd:COM.Command.STD_INFO, + args:obj, + },to.url, function (reply) { + callback(reply) + }) + } + }, + + status : function (node,obj,callback) { + var node0=current.node; + var to=COM.lookupNode(node0,node); + if (to) { + to.link.control({ + cmd:COM.Command.STD_STATUS, + args:obj, + },to.url, function (reply) { + callback(reply) + }) + } + + }, +} + +var Run = { + stun : function (node,agent) { + + }, +} + +module.exports = { + current : function (module) { current=module.current; Aios=module; }, + Run : Run, + Std : Std +}; + +}; +BundleModuleCode['jam/ampUDP']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2020 bLAB + ** $CREATED: 09-02-16 by sbosse. + ** $RCS: $Id: ampUDP.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.17.6 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) over UDP + ** + ** Supports: + ** + ** - Unicast link + ** - Multicast link + ** - Oneway link + ** - Broker service with UDP punch holing and pairing, lookup of registered nodes + ** + ** + ** Events out: 'error','route+','route-' + ** + ** TODO: Garbage collection + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Sec = Require('jam/security'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + obj2url=COM.obj2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + doUntilAck=COM.doUntilAck, + getNetworkIP=COM.getNetworkIP, + magic=COM.options.magic; + +module.exports.current=function (module) { current=module.current; Aios=module; }; + +var dgram = Require('dgram'); + + + +/** AMP port using UDP + * ================== + * + * type url = string + * type amp.udp = function (options:{rcv:address,snd?:address,verbose?,logging?,out?:function,log?:number,broker?:address}) + * type address = {address:string,port:number} + */ +amp.udp = function (options) { + var self=this; + options=checkOptions(options,{}); + this.proto = 'udp'; + // Some sanity checks + this.options=options; + if (options.oneway && options.multicast) this.err('Invalid: Both ONEWAY and MULTICAST modes enabled!'); + this.verbose=checkOption(options.verbose,0); + if (!options.rcv) options.rcv=url2addr('localhost:*'); + + this.dir = options.dir; // attached to JAM port + this.rcv = options.rcv; // IP (this side) address + this.broker = options.broker; // IP (rendezvous broker) address + this.node = options.node; // Attached to this node + + this.port = options.port||Net.uniqport(); // This AMP Port + this.secure = this.options.secure; + + if (this.broker && !this.broker.port) + Io.err('['+Io.Time()+' AMP] No broker port specified!'); + + this.out = function (msg) { + Aios.print('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.err = function (msg) { + Aios.print('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg); + throw 'AMP'; + } + + + // Virtual link table + // MULTICAST (P2N) mode: link cache [{addr,live,state}] of all connected links + // UNICAST (P2P): One link only, remebered by this.url! + this.links={}; + + if (options.snd) { + url=addr2url(options.snd,true); + this.links[url]={ + snd:options.snd, + tries:0, + state:this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED, + live:COM.AMC_MAXLIVE + }; + if (this.verbose>0) this.out('Added destiantion route '+url+', '+Io.inspect(this.links[url])); + if (!options.multicast) this.url=url; // Remember this link + } + if (this.broker) { + // Create a root path that handles all brokerag and pairing + url='*'; + this.links[url]={ + tries:0, + state:AMState.AMS_RENDEZVOUS, + live:COM.options.AMC_MAXLIVE, + queue:{pairing:[],lookup:[]} + }; + if (this.verbose>0) this.out('Added default registration route '+url); + } + this.rcv.name=options.name; // Optional name of this port + + + + this.mode = options.multicast?AMMode.AMO_MULTICAST:AMMode.AMO_UNICAST; + if (options.oneway) this.mode |= AMMode.AMO_ONEWAY; // Oneway: No link negotation; no ack. + + this.sock = dgram.createSocket("udp4"); // Receiver and sender socket + + this.dlimit = options.dlimit||512; + + this.count={rcv:0,snd:0,lnk:0,png:0}; + + this.timer=undefined; + this.inwatchdog=false; + + this.events = []; + this.transactions = Comp.hashtbl.create(); + + this.logs=[]; + this.logging=options.logging||false; + if (this.logging) { + setInterval(function () { self.LOG('print') },5000); + } +}; + +amp.udp.prototype.LOG = amp.man.prototype.LOG; +amp.udp.prototype.addTransaction = amp.man.prototype.addTransaction; +amp.udp.prototype.checkState = amp.man.prototype.checkState; +amp.udp.prototype.deleteTransaction = amp.man.prototype.deleteTransaction; +amp.udp.prototype.emit = amp.man.prototype.emit; +amp.udp.prototype.findTransaction = amp.man.prototype.findTransaction; +amp.udp.prototype.handle = amp.man.prototype.handle; +amp.udp.prototype.on = amp.man.prototype.on; +amp.udp.prototype.status = amp.man.prototype.status; + + +/** Handle AMP control messages (used by ampbroker and UDP hole punching sub-system) + * for P2P network pairing before linking is performed (NAT traversal)! + * + */ +amp.udp.prototype.control = function(link, msg, remote) { + var self=this; + + switch (msg.type) { + case 'lookup': + // Lookup reply + if (link.queue.lookup[msg.path]) { + link.queue.lookup[msg.path](msg.data); + delete link.queue.lookup[msg.path]; + } + break; + + case 'registered': + if (link.state==AMState.AMS_RENDEZVOUS) { + link.state=AMState.AMS_REGISTERED; + if (this.verbose) self.out('Registered on broker with name '+this.rcv.name); + } + break; + + case 'pairing': + if (link.state == AMState.AMS_REGISTERED) { + // pairing of remote endpoint sent by broker; punch now + var punch = { type: 'punch', from: this.rcv.name, to: msg.client.name}, + counter = options.TRIES; + link.state=AMState.AMS_PAIRING; // stops sending pair requests + for (var con in msg.client.connections) { + doUntilAck(options.TIMER, + // DO + function(i) { + counter--; + self.send(punch, msg.client.connections[i]); + }, + // UNTIL ACK = true + function () { + return counter==0 || link.state!=AMState.AMS_PAIRING; + }, + con); + } + } + break; + + case 'ack': + if (link.state == AMState.AMS_PAIRING) { + if (this.verbose) self.out('Paired with '+msg.from+'('+addr2url(remote)+')'); + // Find waiting virtual link ... + self.links.forEach(function (link2,url) { + if (url=='*') return; + if (link2 && link2.state==AMState.AMS_AWAIT && link2.snd.name==link.snd.name) { + var newurl=addr2url(remote,true); + // Move link to new url + self.links[url]=undefined; + self.links[newurl]=link2; + link2.state=AMState.AMS_NOTCONNECTED; + link2.snd.address=remote.address; + link2.snd.port=remote.port; // Correct address/port??? + if (self.mode&AMMode.AMO_UNICAST) self.url=newurl,self; + // console.log('wakeup ',link); + } + }); + + // this.watchdog(true,true); + // This is the root link '*' performed the pairing on this destination side. + // Wait for next pairing... + link.state=AMState.AMS_RENDEZVOUS; + if (link.queue.pairing.length) + link.snd = {name:link.queue.pairing.shift().snd.name}; + else + link.snd = undefined; + self.watchdog(true); + } + break; + + case 'punch': + if (msg.to == this.rcv.name) { + // send ACK + this.send({type:'ack',from:this.rcv.name},remote); + } + break; + } + +} + +/** Initialize AMP + * + */ +amp.udp.prototype.init = function(callback) { + if (callback) callback(); +}; + +/** Negotiate (create) a virtual communication link (peer-to-peer). + * + * The link operation can be called externally establishing a connection or internally. + * + * In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori! + * + * typeof @snd = address + * typeof @callback = function + * typeof @connect = boolean is indicating an initial connect request and not an acknowledge + * typeof @key = string + * +------------+ + * VCMessageType (int16) + * Connection Port (port) + * Node ID (string) + * //Receiver IP Port (int32) + * +------------+ + * + */ + +amp.udp.prototype.link=function(snd,connect,key) { + var self = this, + url, + buf = Buf.Buffer(), + sock = this.sock; // snd_sock; + + if (this.verbose>1) this.out('amp.link: to '+(snd && snd.address) + ':' + ((snd && snd.port) || '*')); + snd=this.updateLinkTable(snd||(this.url && this.links[this.url].snd) ,connect,key); + + if (snd && snd.parameter && snd.parameter.secure) key=snd.parameter.secure; + + if (!snd) return this.watchdog(true); + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMLINK); + Buf.buf_put_port(buf,this.port); // This AMP id + Buf.buf_put_string(buf,this.node?this.node.id:'*'); + Buf.buf_put_string(buf,key?key:''); + + // Buf.buf_put_int32(buf, this.rcv.port); + this.count.snd += Buf.length(buf); + this.count.lnk++; + + sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + sock.close(); + self.emit('error',err); + } + }); +}; + +// Ask broker for registered hosts/nodes +amp.udp.prototype.lookup = function(path,callback) { + var link=this.links['*']; + if (!link && callback) return callback([]); + if (callback) link.queue.lookup[path]=callback; + this.send( + {type:'lookup',name: this.rcv.name, linfo: this.rcv, data:path }, + this.broker, + function () {} + ); + +} + +// Return link for destination +amp.udp.prototype.lookupLinkTable=function(snd) { + if (this.url) return this.links[this.url]; // Unicast mode + if (!snd) return; + var url = obj2url(snd); + return this.links[url]; +} + + +// Initiate a pairing of two nodes via broker handled by the '*' root link +amp.udp.prototype.pairing = function(link) { + if (!this.links['*'].snd) { + // Root link will perform pairing ... + this.links['*'].snd={name:link.snd.name}; + this.watchdog(true); + } else { + this.links['*'].queue.pairing.push(link); + } +} + +/** + * + * typeof @snd = address + * typeof @callback = function + * + * +------------+ + * AMMessageType (int16) + * Connection Port (port) + * Receiver IP Port (int32) + * +------------+ + */ +amp.udp.prototype.ping=function(snd) { + var self = this, + buf = Buf.Buffer(), + sock = this.sock; // snd_sock; + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMPING); + Buf.buf_put_port(buf,this.port); + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('amp.udp.ping: no destinataion set (snd==null)'); + + // Buf.buf_put_int32(buf, self.rcv.port); + + if (this.verbose>1) this.out('amp.ping: to '+addr2url(snd)); + this.count.snd += Buf.length(buf); + this.count.png++; + + sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + sock.close(); + self.emit('error',err); + } + }); +}; + +/** + * + * typeof @snd = address + * typeof @callback = function + * +------------+ + * AMMessageType (int16) + * Connection Port (port) + * Receiver IP Port (int32) + * +------------+ + */ +amp.udp.prototype.pong=function(snd) { + var self = this, + buf = Buf.Buffer(), + sock = this.sock; // snd_sock; + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMPONG); + Buf.buf_put_port(buf,this.port); + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('amp.udp.pong: no destinataion set (snd==null)'); + + // Buf.buf_put_int32(buf, this.rcv.port); + + if (this.verbose>1) this.out('amp.pong: to '+addr2url(snd)); + this.count.snd += Buf.length(buf); + this.count.png++; + + sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + sock.close(); + self.emit('error',err); + } + }); +}; + + +/* +** Set-up a message receiver. +** +** Message structure +** +** msgtyp msgtyp 2 +** =AMMRPCHEAD =AMMRPCDATA +** tid tid 2 +** remoteport 2 +** cmd 2 +** size 2 +** frags 2 +** off 4 +** size 2 +** more 2 +** buf * +** +** +*/ +/** Message receiver and handler. + * typeof @rcv=address|undefined + */ +amp.udp.prototype.receiver = function (callback,rcv) { + var self = this; + + if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address; + if (rcv.port==undefined) rcv.port=this.rcv.port; + if (callback) this.callback=callback; + + var buf = Buf.Buffer(); + var sock = this.sock; // rcv_sock; + + sock.on('listening', function () { + var address = sock.address(); + if (!rcv.port) self.rcv.port=rcv.port=address.port; + if (self.verbose>1) self.out('UDP receiver listening on ' + addr2url(rcv)); + if (self.dir.ip=='*') self.dir=Aios.DIR.IP(self.rcv.port); + // Try to get network IP address of this host + getNetworkIP(undefined,function (err,ip) { + if (!err) self.rcv.address=ip; + if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+')'); + if (err) return self.out("! Unable to obtain network connection information: "+err); + }); + }); + sock.on('error', function (err) { + Io.out('[AMP] UDP error: '+err); + self.sock.close(); + }); + sock.on('message', function (message, remote) { + var handler,dfrags,dlist,msgtyp,tid,ipport,discard,off,size,thisnum,transaction,more,port,addr,url,data,msg; + remote.address=remote.address.replace(/^::ffff:/,'') + Buf.buf_init(buf); + Buf.buf_of_str(buf,message); + self.count.rcv += message.length; + msg={}; + + if (message.length >= 10) { + msg.magic=Buf.buf_get_int16(buf); + // consistency check + if (msg.magic!=magic) return; + + msg.type=Buf.buf_get_int16(buf); + + + discard=false; + if (self.verbose>1) { + url=addr2url(remote,true); + self.out('receiver: Receiving Message from '+ url + ' [' + message.length+'] '+ + AMMessageType.print(msg.type)+' in state '+ + (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)): + (self.links[self.url] && AMState.print(self.links[self.url].state)))); + } + switch (msg.type) { + + case AMMessageType.AMMRPCHEAD: + if (!self.checkState(AMState.AMS_CONNECTED,remote)) return; + msg.tid = Buf.buf_get_int16(buf); + msg.port = Buf.buf_get_port(buf); + msg.cmd=Buf.buf_get_int16(buf); + msg.size=Buf.buf_get_int32(buf); // total data size + msg.frags=Buf.buf_get_int16(buf); + msg.data=Buf.Buffer(); + self.handle(msg,remote); + break; + + case AMMessageType.AMMRPCDATA: + if (!self.checkState(AMState.AMS_CONNECTED,remote)) return; + msg.tid = Buf.buf_get_int16(buf); + msg.port = Buf.buf_get_port(buf); + msg.off = Buf.buf_get_int32(buf); + msg.size = Buf.buf_get_int16(buf); // fragment size + msg.more = Buf.buf_get_int16(buf); + msg.data = buf; + self.handle(msg,remote); + break; + + case AMMessageType.AMMPING: + msg.port = Buf.buf_get_port(buf); + self.handle(msg,remote); + break; + + case AMMessageType.AMMPONG: + msg.port = Buf.buf_get_port(buf); + self.handle(msg,remote); + break; + + case AMMessageType.AMMLINK: + msg.port = Buf.buf_get_port(buf); + msg.node = Buf.buf_get_string(buf); + msg.secure = Buf.buf_get_string(buf); + if (msg.secure!='') msg.secure=Sec.Port.ofString(msg.secure); + self.handle(msg,remote); + break; + + case AMMessageType.AMMUNLINK: + msg.port = Buf.buf_get_port(buf); + self.handle(msg,remote); + break; + + // optional rendezvous brokerage + case AMMessageType.AMMCONTROL: + // Control message; + msg.port = Buf.buf_get_port(buf); + msg.data = Buf.buf_get_string(buf); + self.handle(msg,remote); + break; + } + } + }); +}; + + + +/** Send a request message to a remote node endpoint + * + * function (cmd:integer,msg:Buffer,snd?:address) + */ + +amp.udp.prototype.request = function (cmd,msg,snd) { + var self=this, + buf = Buf.Buffer(), + sock = this.sock, // snd_sock; + size = msg.data.length, + frags = div((size+self.dlimit-1),self.dlimit), + tid = msg.tid||Comp.random.int(65536/2); + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('amp.udp.request: no destinataion set(snd==null)'); + + Buf.buf_put_int16(buf,magic); + Buf.buf_put_int16(buf,AMMessageType.AMMRPCHEAD); + Buf.buf_put_int16(buf,tid); // Transaction Message ID + Buf.buf_put_port(buf,this.port); + // Buf.buf_put_int16(buf,self.rcv.port); // For reply + Buf.buf_put_int16(buf,cmd); + Buf.buf_put_int32(buf,size); + Buf.buf_put_int16(buf,frags); + + if (self.verbose>1) self.out('Send AMMRPCHEAD tid='+tid+' @'+Comp.pervasives.mtime()); + this.count.snd += Buf.length(buf); + + sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (self.verbose>1) self.out('Send AMMRPCHEAD tid='+tid+'. Done @'+Comp.pervasives.mtime()); + if (err) { + if (self.verbose>1) self.out('AMMRPCHEAD Error: '+err); + sock.close(); + if (callback) callback(Status.STD_IOERR,err); + } else { + if (size >0) { + var dsend = function (n, off) { + var fsize,more; + if (frags == 1) fsize = size; + else if (n < frags) fsize = self.dlimit; + else fsize = size - off; + if (n==frags) more=0; else more=1; + Buf.buf_init(buf); + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMRPCDATA); + Buf.buf_put_int16(buf, tid); // Transaction Message number + Buf.buf_put_port(buf,self.port); + Buf.buf_put_int32(buf, off); // Data fragment offset + Buf.buf_put_int16(buf, fsize); // Data fragment size + Buf.buf_put_int16(buf, more); // More data? + Buf.buf_put_buf(buf, msg, off, fsize); + if (self.verbose>1) self.out('Send AMMRPCDATA tid='+tid+'. Start #'+n+'/'+frags+' @'+Comp.pervasives.mtime()); + self.count.snd += Buf.length(buf); + + sock.send(buf.data, 0, Buf.length(buf), snd.port, snd.address, function (err) { + if (self.verbose>1) self.out('Send AMMRPCDATA tid='+tid+'. Done #'+n+'/'+frags+' @'+Comp.pervasives.mtime()); + if (err) { + if (self.verbose>1) self.out('AMMRPCDATA Error: '+err); + sock.close(); + self.emit('error',err); + } + else if (n < frags) dsend(n + 1, off + fsize); + }); + }; + dsend(1,0); + } + } + }); +}; + +/** Reply to a request (msg.tid contains request tid) + */ +amp.udp.prototype.reply = function (cmd,msg,snd) { + this.request(cmd,msg,snd); +} + +// typeof @snd : {address,port} +amp.udp.prototype.scan=function(snd,response,callback) { + var self = this,msg={magic:magic}; + + msg.type=response?AMMessageType.AMMACK:AMMessageType.AMMSCAN; + + if (this.verbose>1) this.out('amp.scan: to '+addr2url(snd)); + // TODO: callback!? Must be handled by the receiver/pkt. manager + + // TODO ?? + // this.send(msg,snd); +} + +// Send a short control message +// typeof @msg : {type:string,..} +// typeof @snd : {address,port} +amp.udp.prototype.send = function (msg, snd) { + var buf = Buf.Buffer(), + sock = this.sock, // snd_sock; + data = JSON.stringify(msg); + this.LOG('snd',msg); + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('amp.udp.send: no destinataion set (snd==null)'); + + if (this.verbose>1) this.out('amp.send: to '+addr2url(snd)+': '+data); + Buf.buf_put_int16(buf,magic); + Buf.buf_put_int16(buf,AMMessageType.AMMCONTROL); + Buf.buf_put_port(buf,this.port); + Buf.buf_put_string(buf,data); + this.count.snd += Buf.length(buf); + + sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) self.emit('error',err); + }); +}; + + + +// Start AMP watchdog and receiver +amp.udp.prototype.start = function(callback) { + var self=this,link,startwatch=false + s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':''; + + if (this.verbose>0) this.out('Starting ' + (this.rcv.name?(this.rcv.name+' '):'')+ addr2url(this.rcv)+ + (this.mode&AMMode.AMO_UNICAST && this.url?(' -> '+this.url):'')+ + ' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s); + if (this.mode&AMMode.AMO_UNICAST) { + if (this.url) { + link=this.links[this.url]; + link.state = this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED; + if (link.snd && !(this.mode&AMMode.AMO_ONEWAY)) + startwatch=true; + if (link.snd && (this.mode&AMMode.AMO_ONEWAY)) + this.emit('route+',addr2url(link.snd,true)); + if (this.broker) this.pairing(link); + } + } + + if (this.broker) { + startwatch=true; + // TODO init link['*'].snd / state .. + } + if (startwatch) this.watchdog(true); + + if (!this.sock) { + // after stop? + this.sock=dgram.createSocket("udp4"); + // restart listener + this.receiver(); + } + this.sock/*rcv_sock*/.bind(this.rcv.port, undefined /*this.rcv.address*/, function (arg) { + if (callback) callback(); + }); +} + +// Stop AMP +amp.udp.prototype.stop = function(callback) { + if (this.mode&AMMode.AMO_MULTICAST) + for(var p in this.links) { + if (this.links[p]) { + // Try to unlink remote endpoint + this.unlink(this.links[p].snd); + this.links[p].state=AMState.AMS_NOTCONNECTED; + } + } + else + this.links.state = AMState.AMS_NOTCONNECTED; + if (this.timer) clearTimeout(this.timer),this.timer=undefined; + if (this.sock) this.sock.close(),this.sock=undefined; + if (callback) callback(); +} + + + +// Unlink remote endpoint +amp.udp.prototype.unlink=function(snd) { + var self = this, + buf = Buf.Buffer(), + url = snd?addr2url(snd,true):null, + sock = this.sock; //snd_sock; + + if (!this.links[url||this.url] || this.links[url||this.url].state!=AMState.AMS_CONNECTED) return; + this.emit('route-',addr2url(snd,true)); + if (this.mode&AMMode.AMO_ONEWAY) return; + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMUNLINK); + Buf.buf_put_port(buf,this.port); + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + url=this.url; + } + if (snd == undefined) this.err('amp.udp.unlink: no destination (snd==null)'); + + // Buf.buf_put_int32(buf, this.rcv.port); + + if (this.verbose>1) this.out('amp.unlink: to '+addr2url(snd)); + this.count.snd += Buf.length(buf); + + sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + sock.close(); + self.emit('error',err) + } + }); + this.links[url].state=AMState.AMS_NOTCONNECTED; + if (!this.links[url].snd.connect) this.links[url].snd={}; // Invalidate link - or remove it from table? + if (this.broker) { + // Special case: brokerage! Remove link entry entirely!? + this.links[url]=undefined; + if (this.url) this.url=undefined; + } + if (this.verbose) this.out('Unlinked ' + addr2url(snd)); +}; + + +// Update link table, add new entry, and return snd address (or none if the watchdog should handle the messaging) +amp.udp.prototype.updateLinkTable=function(snd,connect) { + var link; + if (!snd) this.err('amp.udp.link: no destinataion set (snd==null)'); + url=addr2url(snd,true); + + // Add new link to link table if not already existing + if (this.broker && !snd.port && !this.links[url]) { + // Initial broker rendezvous delivering endpoint ip address and port + link=this.links[url]={ + state:AMState.AMS_AWAIT, + tries:0, + connect:connect, + live:options.AMC_MAXLIVE, + snd:{name:snd.address} // Watchdog will send link messages initially to broker if address is resolved + }; + if (connect) link.snd.connect=true; + if (this.mode&AMMode.AMO_UNICAST) this.url=url; // Remember this link + + this.pairing(link); + + // Let watchdog handle rendezvous and connect request messages + return; + } else if (this.mode&AMMode.AMO_UNICAST) { + // UNICAST mode + if (!this.links[url]) link=this.links[url]={state:AMState.AMS_NOTCONNECTED}; + else link=this.links[url]; + + if (snd != undefined && snd.address!=undefined && snd.port!=undefined && !link.snd) + link.snd=snd; + + if (snd != undefined && snd.address!=undefined && snd.port!=undefined && snd.port!='*' && link.snd.address==undefined) + link.snd.address=snd.address; + + if (snd != undefined && snd.port!=undefined && link.snd.port==undefined) + link.snd.port=snd.port; + + if (connect) link.snd.connect=true; + + // Nothing to do or let watchdog handle link messages? + if ((link.state && link.state!=AMState.AMS_NOTCONNECTED && link.state!=AMState.AMS_PAIRED) || + this.mode&AMMode.AMO_ONEWAY) return; + + // Random port range p0-p1? Let watchdog do the work + if (typeof link.snd.port == 'string') return; + + // Send link message + if (snd==undefined || snd.address==undefined) snd={},snd.address=link.snd.address; + if (snd.port==undefined) snd.port=link.snd.port; + + this.url=url; // Remember this link + } else { + // MULTICAST mode + if (!this.links[url] || !this.links[url].snd.address) + link=this.links[url]={ + snd:snd, + state:AMState.AMS_NOTCONNECTED, + tries:0, + connect:connect, + live:options.AMC_MAXLIVE + }; + // Let watchdog handle connect request link messages + if (!this.inwatchdog && connect) { + this.watchdog(true); + return; + } + // if (this.verbose>1) this.out('send link '+Io.inspect(snd)); + } + return snd; +} + + + +/** Install a watchdog timer. + * + * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set. + * 2. If link state is AMS_CONNECTED, check link end point. + * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker + * + * @param run + */ +amp.udp.prototype.watchdog = function(run,immed) { + var self=this; + if (this.timer) clearTimeout(self.timer),this.timer=undefined; + if (run) this.timer=setTimeout(function () { + var con,to,tokens; + if (!self.timer || !self.sock || self.inwatchdog) return; // stopped or busy? + self.timer = undefined; + self.inwatchdog=true; + + function handle(obj,url) { + if (self.verbose>1) self.out('Watchdog: handle link '+ + url+(obj.snd?('('+obj2url(obj.snd)+')'):'')+' in state '+AMState.print(obj.state)+ + '['+obj.live+'] '+ + (obj.tries!=undefined?('[#'+obj.tries+']'):'')); + switch (obj.state) { + + case AMState.AMS_CONNECTED: + if (obj.live == 0) { + // No PING received, disconnect... + if (self.verbose>0) + self.out('Endpoint ' + addr2url(obj.snd) + + ' not responding, propably dead. Unlinking...'); + // self.emit('route-',addr2url(obj.snd)) .. done in unlink + if (self.mode&AMMode.AMO_MULTICAST) self.unlink(obj.snd); + else self.unlink(); + obj.state = AMState.AMS_NOTCONNECTED; + if (!obj.snd.connect) obj.snd={}; + if (self.broker) { + // Re-register on broker for rendezvous ... + self.watchdog(true); + if (self.links['*']) { + self.links['*'].state=AMState.AMS_RENDEZVOUS; + } + } + } else { + obj.tries=0; + obj.live--; + self.watchdog(true); + if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd); + else self.ping(); + } + break; + + case AMState.AMS_NOTCONNECTED: + if (!obj.snd) return; + if (obj.snd.port && typeof obj.snd.port == 'string') { + // Random port connection from a port range p0-p1; save it and start with first + // random selection + tokens=obj.snd.port.split('-'); + if (tokens.length==2) obj.range=[Number(tokens[0]),Number(tokens[1])]; + } + if (obj.range) { + // Get a random port from range + obj.snd.port=Comp.random.interval(obj.range[0],obj.range[1]); + if (self.verbose>0) + self.out('Trying link to ' + addr2url(obj.snd)); + if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); + else self.link(); + obj.tries++; + if (obj.tries < options.TRIES) self.watchdog(true); + else { + obj.snd={},obj.tries=0,obj.range=undefined; + } + } else if (obj.snd.port && typeof obj.snd.port == 'number') { + // Try link to specified remote endpoint obj.snd + if (self.verbose>0 && obj.tries==0) + self.out('Trying link to ' + addr2url(obj.snd)); + if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); + else self.link(); + obj.tries++; + if (obj.tries < options.TRIES) self.watchdog(true); + else { + self.out('Giving up to link '+addr2url(obj.snd)); + self.emit('error','link',addr2url(obj.snd,true)); + obj.snd={},obj.tries=0; + } + } + break; + + // AMP Broker P2P Control and Management + case AMState.AMS_RENDEZVOUS: + obj.next=Aios.time()+options.REGTMO; + obj.interval=options.REGTMO; + self.send( + {type:'register',name: self.rcv.name, linfo: self.rcv}, + self.broker, + function () {} + ); + self.watchdog(true); + break; + + case AMState.AMS_REGISTERED: + if (obj.snd && obj.snd.name && obj.tries < options.TRIES) { + obj.tries++; + self.send( + {type:'pair', from:self.rcv.name, to: obj.snd.name}, + self.broker, + function () {} + ); + // self.watchdog(true); + } else if (options.REGTMO && Aios.time() > obj.next) { + // Update registration periodically; messages can be lost + obj.interval *= 2; + obj.interval = Math.min(obj.interval,options.REGTMO*8); + obj.next=Aios.time()+obj.interval; + self.send( + {type:'register',name: self.rcv.name, linfo: self.rcv}, + self.broker, + function () {} + ); + } + self.watchdog(true); + break; + } + } + for(var p in self.links) if (self.links[p]) handle(self.links[p],p); + self.inwatchdog=false; + },immed?0:options.TIMER); +}; +}; +BundleModuleCode['jam/ampTCP']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 02-11-18 by sbosse. + ** $RCS: $Id: ampTCP.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.19.1 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) over TCP + ** Three modes: + ** (A) Pair of ad-hoc connections for each message + ** (B) Pair of permanent connections with message stream; keepAlive:true + ** (C) Single permanent connection from "client" to "server", i.e., an AMP handler for each socket, sharedSocket:true + ** => for client behind NATs? + ** + ** Supports: + ** + ** - Unicast link + ** - Multicast link + ** + ** Default mode A: A TCP connection is opened each time a message has to be sent and closed after transfer is finished. + ** (Workaround for wine+wineserver - WIN32 node.js / nw.js bug, probably a libuv wine bug) + ** + ** Simplified data transfer: No requirement for data fragmentation! + ** + ** Events out: 'error','route+','route-' + ** + ** TODO: + ** - Garbage collection + ** + ** New: + ** - No size limit of data transfers + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Sec = Require('jam/security'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + obj2url=COM.obj2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + doUntilAck=COM.doUntilAck, + getNetworkIP=COM.getNetworkIP, + magic=COM.options.magic; + +module.exports.current=function (module) { current=module.current; Aios=module; }; + +var net = require('net'); + +var dgram = Require('dgram'); + + + +/** AMP port using TCP + * ================== + * + * type url = string + * type amp.tcp = function (options:{rcv:address,snd?:address,verbose?,logging?,out?:function,log?:number,broker?:address}) + * type address = {address:string,port:number} + */ +amp.tcp = function (options) { + var self=this; + options=checkOptions(options,{}); + this.options=options; + this.proto = 'tcp'; + // Some sanity checks + if (options.oneway && options.multicast) this.err('Invalid: Both ONEWAY and MULTICAST modes enabled!'); + + this.verbose=checkOption(options.verbose,0); + if (!options.rcv) options.rcv=url2addr('localhost:*'); + + if (this.verbose>2) console.log(options); + + this.dir = options.dir; // attached to JAM port + this.rcv = options.rcv; // IP (this side) address + this.broker = options.broker; // IP (rendezvous broker) address + this.node = options.node; // Attached to this node + + this.port = options.port||Net.uniqport(); // This AMP Port + this.secure = this.options.secure; + this.client = {}; + + this.keepAlive = checkOption(options.keepAlive,false); + this.sharedSocket = checkOption(options.sharedSocket,false); + + if (this.broker && !this.broker.port) + Io.err('[AMP] No broker port specified!'); + + this.out = function (msg) { + Aios.print('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.err = function (msg) { + Aios.print('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg); + throw 'AMP'; + } + + + // Virtual link table + // MULTICAST (P2N) mode: link cache [{addr,live,state}] of all connected links + // UNICAST (P2P): One link only, remebered by this.url! + this.links={}; + + if (options.snd) { + url=addr2url(options.snd,true); + this.links[url]={ + snd:options.snd, + tries:0, + state:this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED, + live:COM.AMC_MAXLIVE + }; + if (this.verbose>0) this.out('Added destiantion route '+url+', '+Io.inspect(this.links[url])); + if (!options.multicast) this.url=url; // Remember this link + } + if (this.broker) { + // Create a root path that handles all brokerag and pairing + url='*'; + this.links[url]={ + tries:0, + state:AMState.AMS_RENDEZVOUS, + live:COM.options.AMC_MAXLIVE, + queue:{pairing:[],lookup:[]} + }; + if (this.verbose>0) this.out('Added default registration route '+url); + } + this.rcv.name=options.name; // Optional name of this port + + this.mode = options.multicast?AMMode.AMO_MULTICAST:AMMode.AMO_UNICAST; + if (options.oneway) this.mode |= AMMode.AMO_ONEWAY; // Oneway: No link negotation; no ack. + + this.sock = undefined; // Receiver socket + + this.count={rcv:0,snd:0,lnk:0,png:0}; + + this.timer=undefined; + this.inwatchdog=false; + + this.events = []; + this.transactions = Comp.hashtbl.create(); + + this.logs=[]; + this.logging=options.logging||false; + if (this.logging) { + setInterval(function () { self.LOG('print') },5000); + } +}; + +amp.tcp.prototype.LOG = amp.man.prototype.LOG; +amp.tcp.prototype.addTransaction = amp.man.prototype.addTransaction; +amp.tcp.prototype.checkState = amp.man.prototype.checkState; +amp.tcp.prototype.deleteTransaction = amp.man.prototype.deleteTransaction; +amp.tcp.prototype.emit = amp.man.prototype.emit; +amp.tcp.prototype.findTransaction = amp.man.prototype.findTransaction; +amp.tcp.prototype.handle = amp.man.prototype.handle; +amp.tcp.prototype.on = amp.man.prototype.on; +amp.tcp.prototype.status = amp.man.prototype.status; + + + +/** Initialize AMP + * + */ +amp.tcp.prototype.init = function(callback) { + if (callback) callback(); +}; + +/** Negotiate (create) a virtual communication link (peer-to-peer). + * + * The link operation can be called externally establishing a connection or internally. + * + * In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori! + * + * typeof @snd = address + * typeof @callback = function + * typeof @connect = boolean is indicating an initial connect request and not an acknowledge + * typeof @key = string + * +------------+ + * VCMessageType (int16) + * Connection Port (port) + * Node ID (string) + * Receiver IP Port (int32) + * +------------+ + * + */ + +amp.tcp.prototype.link=function(snd,connect,key) { + var self = this, + url = addr2url(snd,true), + buf = Buf.Buffer(); + + if (this.verbose>1) this.out('amp.link: to '+(snd && snd.address) + ':' + ((snd && snd.port) || '*')); + snd=this.updateLinkTable(snd||(this.url && this.links[this.url].snd) ,connect,key); + + if (snd && snd.parameter && snd.parameter.secure) key=snd.parameter.secure; + + if (!snd) return this.watchdog(true); + + // Create sockets here for permanent TCP sessions + if ((this.keepAlive || this.sharedSocket) && + (!this.client[url] || !this.client[url].socket)) { + if (!this.client[url]) this.client[url]={busy:false,id:Math.random(),connected:false,queue:[]} + if (!this.client[url].socket) { + if (self.verbose>1) console.log('amp.link Creating keepAlive socket for client '+url); + this.client[url].socket=new net.Socket(); + this.client[url].connected=false; + this.client[url].socket.on('close', function () { + console.log('close',url); + delete self.client[url]; + }); + this.client[url].socket.on('error', function (err) { + if (self.verbose>1) console.log('error',url,err) + delete self.client[url]; + }); + } + if (this.sharedSocket && !this.rcv.port) { + // TODO bind receiver + if (self.verbose>1) console.log('amp.link Creating sharedSocket receiver for client '+url); + this.receiverSocket(this.client[url].socket); + if (!this.inwatchdog) { + this.watchdog(true); + } + } + this.client[url].busy=true; + console.log('Connecting to client '+url); + this.client[url].socket.connect(snd.port,snd.address, function () { + var rcv=self.sharedSocket?self.client[url].socket.address():self.rcv; + if (self.verbose>1) console.log('amp.link.connect:',url,rcv) + self.client[url].rcv=rcv; + self.client[url].busy=false; + send(self.client[url].rcv.port); + }); + this.client[url].connected=true; + return; + } + function send(rcvport) { + if (self.verbose>1) self.out('amp.link.send: '+url+' rcvport='+rcvport); + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMLINK); + Buf.buf_put_port(buf,self.port); // This AMP id + Buf.buf_put_string(buf,self.node?self.node.id:'*'); + Buf.buf_put_string(buf,key?key:''); + Buf.buf_put_int32(buf, rcvport); + // Buf.buf_put_int32(buf, this.rcv.port); + self.count.snd += Buf.length(buf); + self.count.lnk++; + + self.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + // self.close(); + self.emit('error',err); + } + }); + } + send(this.sharedSocket && this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port); +}; + +// Ask broker for registered hosts/nodes +amp.tcp.prototype.lookup = function(path,callback) { + var link=this.links['*']; + if (!link && callback) return callback([]); + if (callback) link.queue.lookup[path]=callback; + this.send( + {type:'lookup',name: this.rcv.name, linfo: this.rcv, data:path }, + this.broker, + function () {} + ); + +} + +// Return link for destination +amp.tcp.prototype.lookupLinkTable=function(snd) { + if (this.url) return this.links[this.url]; // Unicast mode + if (!snd) return; + var url = obj2url(snd); + return this.links[url]; +} + + + +/** + * + * typeof @snd = address + * typeof @callback = function + * + * +------------+ + * AMMessageType (int16) + * Connection Port (port) + * Receiver IP Port (int32) + * +------------+ + */ +amp.tcp.prototype.ping=function(snd) { + var self = this, + buf = Buf.Buffer(); + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMPING); + Buf.buf_put_port(buf,this.port); + if (!this.sharedSocket) + Buf.buf_put_int32(buf,this.rcv.port); // For reply + else // from this.client[url] + Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port); // For reply + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('ping: snd=null'); + + // Buf.buf_put_int32(buf, self.rcv.port); + + if (this.verbose>1) this.out('amp.ping: to '+addr2url(snd)); + this.count.snd += Buf.length(buf); + this.count.png++; + + this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + // self.close(); + self.emit('error',err); + } + }); +}; + +/** + * + * typeof @snd = address + * typeof @callback = function + * +------------+ + * AMMessageType (int16) + * Connection Port (port) + * Receiver IP Port (int32) + * +------------+ + */ +amp.tcp.prototype.pong=function(snd) { + var self = this, + buf = Buf.Buffer(); + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMPONG); + Buf.buf_put_port(buf,this.port); + if (!this.sharedSocket) + Buf.buf_put_int32(buf,this.rcv.port); // For reply + else // from this.client[url] + Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port); // For reply + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('pong: snd=null'); + + // Buf.buf_put_int32(buf, this.rcv.port); + + if (this.verbose>1) this.out('amp.pong: to '+addr2url(snd)); + this.count.snd += Buf.length(buf); + this.count.png++; + + this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + // self.close(); + self.emit('error',err); + } + }); +}; + + +/* +** Set-up a message receiver. +** +** Message structure +** +** msgtyp msgtyp 2 +** =AMMRPCHEAD =AMMRPCDATA +** tid tid 2 +** remoteport 2 +** cmd 2 +** size 2 +** frags 2 +** off 4 +** size 2 +** more 2 +** buf * +** +** +*/ +/** Message receiver and handler. + * typeof @rcv=address|undefined + */ +amp.tcp.prototype.receiverHandler = function handler (message,remote) { + var self=this,handler,dfrags,dlist,msgtyp,tid,ipport,discard,off,size, + thisnum,transaction,more,port,addr,url,data,msg, + url0=addr2url(remote,true); + // console.log('handler',message.length); + var buf = Buf.Buffer(); + Buf.buf_init(buf); + Buf.buf_of_str(buf,message); + self.count.rcv += message.length; + msg={}; + + if (message.length >= 10) { + msg.magic=Buf.buf_get_int16(buf); + // consistency check + if (msg.magic!=magic) return; + + msg.type=Buf.buf_get_int16(buf); + discard=false; + if (self.verbose>1) { + url=addr2url(remote,true); + self.out('receiver: Receiving Message from '+ url + ' [' + message.length+'] '+ + AMMessageType.print(msg.type)); + } + switch (msg.type) { + + case AMMessageType.AMMRPCHEADDATA: // single message transfers only + msg.tid = Buf.buf_get_int16(buf); + msg.port = Buf.buf_get_port(buf); + msg.sndport = Buf.buf_get_int32(buf); + remote.port=msg.sndport; // reply to this port! + url=addr2url(remote,true); + if (self.verbose>1)self.out('receiver: Receiving Message in state '+ + (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)): + (self.links[self.url] && AMState.print(self.links[self.url].state)))); + if (!self.checkState(AMState.AMS_CONNECTED,remote)) return; + msg.cmd=Buf.buf_get_int16(buf); + msg.size=Buf.buf_get_int32(buf); + msg.data = buf; + msg.frags=0; + msg.more=false; + self.handle(msg,remote); + break; + + case AMMessageType.AMMPING: + msg.port = Buf.buf_get_port(buf); + msg.sndport = Buf.buf_get_int32(buf); + remote.port=msg.sndport; // reply to this port! + url=addr2url(remote,true); + if (self.verbose>1)self.out('receiver: Receiving Message in state '+ + (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)): + (self.links[self.url] && AMState.print(self.links[self.url].state)))); + self.handle(msg,remote); + break; + + case AMMessageType.AMMPONG: + msg.port = Buf.buf_get_port(buf); + msg.sndport = Buf.buf_get_int32(buf); + remote.port=msg.sndport; // reply to this port! + url=addr2url(remote,true); + if (self.verbose>1)self.out('receiver: Receiving Message in state '+ + (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)): + (self.links[self.url] && AMState.print(self.links[self.url].state)))); + self.handle(msg,remote); + break; + + case AMMessageType.AMMLINK: + msg.port = Buf.buf_get_port(buf); + msg.node = Buf.buf_get_string(buf); + msg.secure = Buf.buf_get_string(buf); + if (msg.secure!='') msg.secure=Sec.Port.ofString(msg.secure); + msg.sndport = Buf.buf_get_int32(buf); + + var oldport = remote.port; + remote.port=msg.sndport; // reply to this port! + url=addr2url(remote,true); + + if (remote.port!=oldport) { + // client behind NAT? IP port changed + if (self.verbose>1) console.log('LINK NAT fix',remote.port,oldport,url,url0,typeof self.client[url0]) + if (self.client[url0]) { + // migrate client entry to new url + self.client[url]={address:remote.address,port:remote.port, + busy:self.client[url0].busy, + connected:self.client[url0].connected, + queue:self.client[url0].queue, + socket:self.client[url0].socket}; + } + } + + if (self.verbose>1)self.out('receiver: Receiving Message in state '+ + (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)): + (self.links[self.url] && AMState.print(self.links[self.url].state)))); + self.handle(msg,remote); + break; + + case AMMessageType.AMMUNLINK: + msg.port = Buf.buf_get_port(buf); + msg.sndport = Buf.buf_get_int32(buf); + remote.port=msg.sndport; // reply to this port! + url=addr2url(remote,true); + self.handle(msg,remote); + break; + + // optional rendezvous brokerage + case AMMessageType.AMMCONTROL: + // Control message; + msg.port = Buf.buf_get_port(buf); + msg.data = Buf.buf_get_string(buf); + self.handle(msg,remote); + break; + } + } +}; + +amp.tcp.prototype.receiverSocket = function (sock) { + var self=this,chunks,remote,expect=0,url; + // Remote connect and request + sock.on('data', function (data) { + var pending; + // console.log(data); + if (!remote) { + remote = {address:sock.remoteAddress.replace(/^::ffff:/,''),port:sock.remotePort}; + url = addr2url(remote); + // console.log(remote,url) + if (self.sharedSocket && !self.client[url]) { + self.client[url]={busy:false,connected:true,socket:sock,queue:[]}; + } + } + if (self.keepAlive || self.sharedSocket) { + // still broken + do { + if (pending) { data=pending; pending=null }; + if (data.length==0) process.exit(); + // message stream connectioN; first 4 bytes: length of message + if (data.length==4) { expect=data.readInt32LE(0); return; } + else if (expect==0) { + if (data.length<4) return console.log('uff',data.length); + var dataLength = data.slice(0,4); + data=data.slice(4); + expect=dataLength.readInt32LE(0); + } + if (expect) { + if (chunks && (data.length+chunks.length) > expect || + data.length > expect) { + var diff = (data.length+(chunks?chunks.length:0)) - expect, + need = expect-(chunks?chunks.length:0); + // console.log('mess',expect,diff,need,chunks && chunks.length,data.length) + pending=data.slice(need); + data=data.slice(0,need); + // console.log(data.length,pending.length); + } + } + + if (!chunks) chunks=data; + else chunks=Buffer.concat([chunks,data]); + if (expect && chunks.length == expect) { + // console.log(chunks) + self.receiverHandler(Buffer(chunks),remote); + expect=0; + chunks=null; + } + } while (pending && pending.length) + } else { + if (!chunks) chunks=data; + else chunks=Buffer.concat([chunks,data]); + } + }); + sock.on('end', function () { + if (chunks) self.receiverHandler(Buffer(chunks),remote); + }); + if (!this.sharedSocket) { + // Add a 'close' event handler to this instance of socket + sock.on('close', function(data) { + // console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort); + if (self.sharedSocket && self.client[url]) delete self.client[url]; + }); + sock.on('error', function(data) { + // console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort); + if (self.sharedSocket && self.client[url]) delete self.client[url]; + }); + } +} +amp.tcp.prototype.receiver = function (callback,rcv) { + var self = this; + + if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address; + if (rcv.port==undefined) rcv.port=this.rcv.port; + if (callback) this.callback=callback; + + if (this.sharedSocket && rcv.port==undefined) { + if (this.verbose) this.out('IP port * (proto '+this.options.proto+') SS'); + return; // client side socket receiver, installed later on each link request + } + this.sock=net.createServer({keepAlive:this.keepAlive,noDelay:true},this.receiverSocket.bind(this)); + + this.sock.on('listening', function () { + var address = self.sock.address(); + if (!rcv.port) self.rcv.port=rcv.port=address.port; + if (self.verbose>1) self.out('TCP receiver listening on ' + addr2url(rcv)); + if (self.dir.ip=='*') self.dir=Aios.DIR.IP(self.rcv.port); + // Try to get network IP address of this host + getNetworkIP(undefined,function (err,ip) { + if (!err) self.rcv.address=ip; + if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+') '+ + (self.keepAlive?'KA ':'')+ + (self.sharedSocket?'SS':'')); + if (err) return self.out("! Unable to obtain network connection information: "+err); + }); + }); + this.sock.on('error', function (err) { + Io.out('[AMP] TCP error: '+err); + self.sock.close(); + }); +}; + + + +/** Send a request message to a remote node endpoint + * + * function (cmd:integer,msg:Buffer,snd?:address) + */ + +amp.tcp.prototype.request = function (cmd,msg,snd) { + var self=this, + buf = Buf.Buffer(), + size = msg.data.length, + url = snd?addr2url(snd,true):'', + tid = msg.tid||Comp.random.int(65536/2); + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('request: request=null'); + + Buf.buf_put_int16(buf,magic); + Buf.buf_put_int16(buf,AMMessageType.AMMRPCHEADDATA); + Buf.buf_put_int16(buf,tid); // Transaction Message ID + Buf.buf_put_port(buf,this.port); + if (!this.sharedSocket) + Buf.buf_put_int32(buf,this.rcv.port); // For reply + else // from this.client[url] + Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port); // For reply + Buf.buf_put_int16(buf,cmd); + Buf.buf_put_int32(buf,size); + Buf.buf_put_buf(buf, msg, 0, size); + + if (self.verbose>1) self.out('Send AMMRPCHEAD tid='+tid+' @'+Comp.pervasives.mtime()); + this.count.snd += Buf.length(buf); + + this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (self.verbose>1) self.out('Send AMMRPCHEADDATA tid='+tid+'. Done @'+Comp.pervasives.mtime()); + if (err) { + if (self.verbose>1) self.out('AMMRPCHEADDATA Error: '+err); + if (callback) callback(Status.STD_IOERR,err); + } + }); +}; +/** Reply to a request (msg.tid contains request tid) + */ +amp.tcp.prototype.reply = function (cmd,msg,snd) { + this.request(cmd,msg,snd); +} + +// Send a short control message +// typeof @msg : {type:string,..} +// typeof @snd : address +amp.tcp.prototype.send = function (msg, snd) { + var buf = Buf.Buffer(), + data = JSON.stringify(msg); + this.LOG('snd',msg); + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + } + if (snd == undefined) this.err('send: snd=null'); + + if (this.verbose>1) this.out('amp.send: to '+addr2url(snd)+': '+data); + Buf.buf_put_int16(buf,magic); + Buf.buf_put_int16(buf,AMMessageType.AMMCONTROL); + Buf.buf_put_port(buf,this.port); + Buf.buf_put_string(buf,data); + this.count.snd += Buf.length(buf); + + this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) self.emit('error',err); + }); +}; + + + +// Start AMP watchdog and receiver +amp.tcp.prototype.start = function(callback) { + var self=this,link,startwatch=false, + s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':''; + + if (this.verbose>0) this.out('Starting ' + (this.rcv.name?(this.rcv.name+' '):'')+ addr2url(this.rcv)+ + (this.mode&AMMode.AMO_UNICAST && this.url?(' -> '+this.url):'')+ + ' ['+AMMode.print(this.mode)+'] (proto '+this.proto+') '+s); + if (this.mode&AMMode.AMO_UNICAST) { + if (this.url) { + link=this.links[this.url]; + link.state = this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED; + if (link.snd && !(this.mode&AMMode.AMO_ONEWAY)) + startwatch=true; + if (link.snd && (this.mode&AMMode.AMO_ONEWAY)) + this.emit('route+',addr2url(link.snd,true)); + if (this.broker) this.pairing(link); + } + } + + if (startwatch) this.watchdog(true); + + if (!this.sock && !this.sharedSocket && this.rcv.port) { + // restart listener + this.receiver(); + } + if (this.sock) { + this.sock.listen(this.rcv.port, undefined /*this.rcv.address*/); + } + if (callback) callback(); +} + +// Stop AMP +amp.tcp.prototype.stop = function(callback) { + if (this.mode&AMMode.AMO_MULTICAST) + for(var p in this.links) { + if (this.links[p]) { + // Try to unlink remote endpoint + this.unlink(this.links[p].snd); + this.links[p].state=AMState.AMS_NOTCONNECTED; + } + } + else + this.links.state = AMState.AMS_NOTCONNECTED; + if (this.timer) clearTimeout(this.timer),this.timer=undefined; + // TODO + if (this.sock) this.sock.close(),this.sock=undefined; + for (var p in this.client) { + if (this.client[p] && this.client[p].socket) { + this.client[p].socket.destroy(); + delete this.client[p]; + } + } + + if (callback) callback(); +} + + + +// Unlink remote endpoint +amp.tcp.prototype.unlink=function(snd) { + var self = this, + buf = Buf.Buffer(), + url = snd?addr2url(snd,true):null; + + if (!this.links[url||this.url] || this.links[url||this.url].state!=AMState.AMS_CONNECTED) return; + this.emit('route-',addr2url(snd,true)); + if (this.mode&AMMode.AMO_ONEWAY) return; + + Buf.buf_put_int16(buf, magic); + Buf.buf_put_int16(buf, AMMessageType.AMMUNLINK); + Buf.buf_put_port(buf,this.port); + if (!this.sharedSocket) + Buf.buf_put_int32(buf,this.rcv.port); // For reply + else // from this.client[url] + Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port); // For reply + + if (this.mode&AMMode.AMO_UNICAST) { + if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address; + if (snd.port==undefined) snd.port=this.links[this.url].snd.port; + url=this.url; + } + if (snd == undefined) this.err('unlink: no destination'); + + // Buf.buf_put_int32(buf, this.rcv.port); + + if (this.verbose>1) this.out('amp.unlink: to '+addr2url(snd)); + this.count.snd += Buf.length(buf); + + this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) { + if (err) { + self.emit('error',err) + } + }); + this.links[url].state=AMState.AMS_NOTCONNECTED; + + if (!this.links[url].snd.connect) this.links[url].snd={}; // Invalidate link - or remove it from table? + if (this.broker) { + // Special case: brokerage! Remove link entry entirely!? + this.links[url]=undefined; + if (this.url) this.url=undefined; + } + if (this.client[url]) { this.client[url].socket.destroy(); delete this.client[url] }; + if (this.verbose) this.out('Unlinked ' + url); +}; + + +// Update link table, add new entry, and return snd address (or none if the watchdog should handle the messaging) +amp.tcp.prototype.updateLinkTable=function(snd,connect) { + var link; + if (!snd) this.err('link: no destinataion set'); + url=addr2url(snd,true); + + // Add new link to link table if not already existing + if (this.broker && !snd.port && !this.links[url]) { + // Initial broker rendezvous delivering endpoint ip address and port + link=this.links[url]={ + state:AMState.AMS_AWAIT, + tries:0, + connect:connect, + live:options.AMC_MAXLIVE, + snd:{name:snd.address} // Watchdog will send link messages initially to broker if address is resolved + }; + if (connect) link.snd.connect=true; + if (this.mode&AMMode.AMO_UNICAST) this.url=url; // Remember this link + + this.pairing(link); + + // Let watchdog handle rendezvous and connect request messages + return; + } else if (this.mode&AMMode.AMO_UNICAST) { + // UNICAST mode + if (!this.links[url]) link=this.links[url]={state:AMState.AMS_NOTCONNECTED}; + else link=this.links[url]; + + if (snd != undefined && snd.address!=undefined && snd.port!=undefined && !link.snd) + link.snd=snd; + + if (snd != undefined && snd.address!=undefined && snd.port!=undefined && snd.port!='*' && link.snd.address==undefined) + link.snd.address=snd.address; + + if (snd != undefined && snd.port!=undefined && link.snd.port==undefined) + link.snd.port=snd.port; + + if (connect) link.snd.connect=true; + + // Nothing to do or let watchdog handle link messages? + if ((link.state && link.state!=AMState.AMS_NOTCONNECTED && link.state!=AMState.AMS_PAIRED) || + this.mode&AMMode.AMO_ONEWAY) return; + + // Random port range p0-p1? Let watchdog do the work + if (typeof link.snd.port == 'string') return; + + // Send link message + if (snd==undefined || snd.address==undefined) snd={},snd.address=link.snd.address; + if (snd.port==undefined) snd.port=link.snd.port; + + this.url=url; // Remember this link + } else { + // MULTICAST mode + url=addr2url(snd,true); + if (!this.links[url] || !this.links[url].snd.address) + this.links[url]={ + snd:snd, + state:AMState.AMS_NOTCONNECTED, + tries:0, + connect:connect, + live:options.AMC_MAXLIVE + }; + // Let watchdog handle connect request link messages + if (!this.inwatchdog && connect && !this.sharedSocket) { + this.watchdog(true); + return; + } + // if (this.verbose>1) this.out('send link '+Io.inspect(snd)); + } + return snd; +} + + + +/** Install a watchdog timer. + * + * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set. + * 2. If link state is AMS_CONNECTED, check link end point. + * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker + * + * @param run + */ +amp.tcp.prototype.watchdog = function(run,immed) { + var self=this; + if (this.timer) clearTimeout(self.timer),this.timer=undefined; + if (this.verbose>1) this.out('Starting watchdog run='+run+' immed='+immed); + if (run) this.timer=setTimeout(function () { + var con,to,tokens; + if (!self.timer || (!self.sock && !self.sharedSocket) || self.inwatchdog) return; // stopped or busy? + self.timer = undefined; + self.inwatchdog=true; + + function handle(obj,url) { + if (self.verbose>1) self.out('Watchdog: handle link '+ + url+(obj.snd?('('+obj2url(obj.snd)+')'):'')+' in state '+AMState.print(obj.state)+ + (obj.tries!=undefined?('[#'+obj.tries+']'):'')); + switch (obj.state) { + + case AMState.AMS_CONNECTED: + if (obj.live == 0) { + // No PING received, disconnect... + if (self.verbose>0) + self.out('(TCP) Endpoint ' + addr2url(obj.snd) + + ' not responding, propably dead. Unlinking...'); + // self.emit('route-',addr2url(obj.snd)) .. done in unlink + if (self.mode&AMMode.AMO_MULTICAST) self.unlink(obj.snd); + else self.unlink(); + obj.state = AMState.AMS_NOTCONNECTED; + if (!obj.snd.connect) obj.snd={}; + if (self.broker) { + // Re-register on broker for rendezvous ... + self.watchdog(true); + if (self.links['*']) { + self.links['*'].state=AMState.AMS_RENDEZVOUS; + } + } + } else { + obj.tries=0; + obj.live--; + self.watchdog(true); + if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd); + else self.ping(); + } + break; + + case AMState.AMS_NOTCONNECTED: + if (!obj.snd) return; + if (obj.snd.port && typeof obj.snd.port == 'string') { + // Random port connection from a port range p0-p1; save it and start with first + // random selection + tokens=obj.snd.port.split('-'); + if (tokens.length==2) obj.range=[Number(tokens[0]),Number(tokens[1])]; + } + if (obj.range) { + // Get a random port from range + obj.snd.port=Comp.random.interval(obj.range[0],obj.range[1]); + if (self.verbose>0) + self.out('Trying link to ' + addr2url(obj.snd)); + if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); + else self.link(); + obj.tries++; + if (obj.tries < options.TRIES) self.watchdog(true); + else { + obj.snd={},obj.tries=0,obj.range=undefined; + } + } else if (obj.snd.port && typeof obj.snd.port == 'number') { + // Try link to specified remote endpoint obj.snd + if (self.verbose>0 && obj.tries==0) + self.out('(TCP) Trying link to ' + addr2url(obj.snd)); + if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); + else self.link(); + obj.tries++; + if (obj.tries < options.TRIES) self.watchdog(true); + else { + self.out('(TCP) Giving up to link '+addr2url(obj.snd)); + self.emit('error','link',addr2url(obj.snd,true)); + obj.snd={},obj.tries=0; + } + } + break; + + // AMP Broker P2P Control and Management + case AMState.AMS_RENDEZVOUS: + obj.next=Aios.time()+options.REGTMO; + obj.interval=options.REGTMO; + self.send( + {type:'register',name: self.rcv.name, linfo: self.rcv }, + self.broker, + function () {} + ); + self.watchdog(true); + break; + + case AMState.AMS_REGISTERED: + if (obj.snd && obj.snd.name && obj.tries < options.TRIES) { + obj.tries++; + self.send( + {type:'pair', from:self.rcv.name, to: obj.snd.name }, + self.broker, + function () {} + ); + // self.watchdog(true); + } else if (options.REGTMO && Aios.time() > obj.next) { + // Update registration periodically; messages can be lost + obj.interval *= 2; + obj.interval = Math.min(obj.interval,options.REGTMO*8); + obj.next=Aios.time()+obj.interval; + self.send( + {type:'register',name: self.rcv.name, linfo: self.rcv }, + self.broker, + function () {} + ); + } + self.watchdog(true); + break; + } + } + for(var p in self.links) if (self.links[p]) handle(self.links[p],p); + self.inwatchdog=false; + },immed?0:options.TIMER); +}; + +/** Write message data to remote endpoint address:port over a temporary TCP connection + * + */ +amp.tcp.prototype.write = function(data,off,len,port,address,cb) { + var self=this, + url=addr2url({address:address,port:port}); + if (off!=0) this.err('tpc.socket.write: buffer offset <> 0'); + if (this.keepAlive || this.sharedSocket) { + // reuse TCP session and connection + if (!this.client[url]) return; // closed? + // keep tcp connection alive and send messages as a stream + // if (!this.client[url]) this.client[url]={busy:false,connected:false,queue:[]} + if (this.client[url].busy) { + // console.log('enqueue'); + this.client[address+':'+port].queue.push([data,off,len,port,address,cb]); + return; + } + this.client[url].busy=true; + + function send() { + if (!self.client[url]) return; // closed? + // console.log('send',data.length); + // 1. send data length + var dataLength = Buffer(4); + dataLength.writeInt32LE(data.length,0); + try { + self.client[url].socket.write(dataLength, function () { + if (!self.client[url]) return; // closed? + // 2. send data payload + self.client[url].socket.write(data, function () { + if (!self.client[url]) return; + // console.log('write done',self.client[address+':'+port].queue.length) + if (cb) cb(); + self.client[url].busy=false; + if (self.client[url].queue.length) { + // console.log('dequeue'); + self.client[url].socket.write.apply(self,self.client[url].shift()); + } + }) + }); + } catch (e) { if (self.verbose) self.out(url+': '+e); delete self.client[url]; } + } + /* + if (!this.client[url].socket) { + this.client[url].socket=new net.Socket(); + console.log('write.connect',url); + this.client[url].socket.connect(port,address, send); + this.client[url].connected=true; + this.client[url].socket.on('close', function () { + console.log('close',url); + delete self.client[url]; + }); + this.client[url].socket.on('error', function (err) { + console.log('error',url,err) + delete self.client[url]; + }); + } else*/ + if (!this.client[url].connected) { + console.log('write.connect',url); + this.client[url].busy=true; + this.client[url].socket.connect(port,address, function () { + self.client[url].busy=false; + send(); + }); + this.client[url].connected=true; + } else { + send(); + } + } else { + // for each message a new ad-hoc connection + var client = new net.Socket(); + client.on('error',function (e) { if (cb) cb(e.toString()) }) + client.connect(port,address, function () { + client.write(data,function () { + client.destroy(); + // console.log(len) + if (cb) cb(); + }); + }); + } +} +}; +BundleModuleCode['jam/ampStream']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $CREATED: 09-02-16 by sbosse. + ** $RCS: $Id: ampStream.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.11.2 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) over streams + ** + ** + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + getNetworkIP=COM.getNetworkIP; + +module.exports.current=function (module) { current=module.current; Aios=module; }; + +/** AMP port using streams + * ====================== + * + * Note: Process streams transfer objects not data! + * No negotiation is performed. Data transfer can be fragmented. + * + * type amp.stream = function (options:{sock:stream,verbose?,logging?,out?:function,log?,mode?:'buffer'|'object'}) + */ +amp.stream = function (options) { + var self=this; + options=checkOptions(options,{}); + this.options=options; + this.verbose=checkOption(options.verbose,0); + + this.dir=options.dir; // Logical direction + + this.mode = AMMode.AMO_UNICAST | + AMMode.AMO_STATIC | // No link change + (options.mode=='object'? AMMode.AMO_OBJECT:AMMode.AMO_BUFFER); // Transfer data buffers or objects? + + this.port = options.port||Net.uniqport(); // Connection Link Port (this side) + this.id = Net.Print.port(this.port); + + this.links={state:AMState.AMS_NOTCONNECTED}; + + // Stream socket; can be a process object! + this.sock = options.sock; + + this.dlimit = options.dlimit||512; + + this.out = function (msg) { + Aios.print('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.state = AMState.AMS_INIT; + + this.events = []; + + this.logs=[]; + this.logging=options.logging||false; + if (this.logging) { + setInterval(function () { self.LOG('print') },5000); + } + + if (this.mode & AMMode.AMO_OBJECT) + this.receiver=this.receiverObj, + this.request=this.requestObj; +}; + +amp.stream.prototype.LOG = amp.udp.prototype.LOG; +amp.stream.prototype.emit = amp.udp.prototype.emit; +amp.stream.prototype.init = amp.udp.prototype.init; +amp.stream.prototype.on = amp.udp.prototype.on; + +amp.stream.prototype.receiver=function (callback,rcv) { + var self = this; + + if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address; + if (rcv.port==undefined) rcv,port=this.rcv.port; + + var cache = Comp.hashtbl.create(); + var buf = Buf.Buffer(); + var sock = this.sock; // rcv_sock; + + sock.on('message', function (message, remote) { + var handler,dfrags,dlist,msgtyp,tid,ipport,discard,off,size,thisnum,transaction,more,port,ip,data,msg; + handler={}; + + Buf.buf_init(buf); + Buf.buf_of_str(buf,message); + self.count.rcv += message.length; + + if (message.length >= 12) { + msgtyp=Buf.buf_get_int16(buf); + discard=false; + if (self.verbose>1) + self.out('receiver: Receiving Message [' + message.length+'] '+AMMessageType.print(msgtyp)); + + switch (msgtyp) { + + case AMMessageType.AMMRPCHEAD: + tid = Buf.buf_get_int16(buf); + port = Buf.buf_get_port(buf); + handler.tid=tid; + handler.remote=remote.address+':'+Buf.buf_get_int16(buf); + handler.cmd=Buf.buf_get_int16(buf); + handler.size=Buf.buf_get_int16(buf); + handler.frags=Buf.buf_get_int16(buf); + handler.buf=Buf.Buffer(); + // console.log(handler) + if (handler.size>0) { + dlist = Comp.array.range(0, handler.frags - 1); + // Add transaction to cache for pending data + Comp.hashtbl.add(cache, handler.tid, [handler,dlist,1000]); + } else { + callback(handler); + } + break; + + case AMMessageType.AMMRPCDATA: + tid = Buf.buf_get_int16(buf); + port = Buf.buf_get_port(buf); + off = Buf.buf_get_int32(buf); + size = Buf.buf_get_int16(buf); + more = Buf.buf_get_int16(buf); + thisnum = off/self.dlimit; + transaction = Comp.hashtbl.find(cache,tid); + if (transaction!=undefined) { + handler=transaction[0]; + if (self.verbose>1) + self.out('receiver: adding data num='+ + thisnum+' off='+off+' size='+size+' dlist='+transaction[1]); + + Buf.buf_get_buf(buf,handler.buf,off,size); + transaction[1]=Comp.array.filter(transaction[1],function(num) {return (num!=thisnum)}); + if (Comp.array.empty(transaction[1])) { + if (self.verbose>1) self.out('[AMP] receiver: finalize '+addr2url(remote)); + // Io.out(handler.data.toString()); + // Deliver + callback(handler); + Comp.hashtbl.remove(cache,tid); + } + handler=undefined; + } + break; + } + } + }); +}; + +// Object transfer mode (process streams) +// Message format: {cmd:*,msg:buffer} +amp.stream.prototype.receiverObj=function (callback,rcv) { + this.sock.on('message', function (obj) { + var handler={cmd:obj.cmd,buf:Buf.Buffer(obj.msg)}; + self.count.rcv += obj.msg.length; + if (callback) callback(handler); + }); +} + +amp.stream.prototype.request=amp.udp.prototype.request; + +// Object transfer mode (process streams) +// function (cmd:integer,msg:{pos:number,data:buffer},callback:function) +amp.stream.prototype.requestObj=function (cmd,msg,callback) { + // Sync. operation + this.count.snd += msg.data.length; + this.sock.send({cmd:cmd,msg:msg.data}); + if (callback) callback() +} +amp.stream.prototype.start = function(callback) { + if (this.verbose) this.out('Stream link '+Aios.DIR.print(this.dir)+' started.'); + this.links.state=AMState.AMS_CONNECTED; + if (callback) callback() +}; +amp.stream.prototype.stop = function(callback) { if (callback) callback()}; +amp.stream.prototype.status=amp.udp.prototype.status; + +}; +BundleModuleCode['jam/ampHTTP']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2020 bLAB + ** $CREATED: 09-02-16 by sbosse. + ** $RCS: $Id: ampHTTP.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ + ** $VERSION: 1.14.7 + ** + ** $INFO: + ** + ** JAM Agent Management Port (AMP) over HTTP + ** Only Mulitcast IP(*) mode is supported! + ** + ** Events out: 'error','route-' + ** + ** TODO: Garbage collection + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); +var Bas64 = Require('os/base64'); +var Sec = Require('jam/security') +var JSONfn = Require('jam/jsonfn') + +var options = { + version:"1.14.7" +} + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + isLocal=COM.isLocal, + getNetworkIP=COM.getNetworkIP + magic=COM.options.magic; + +var debug = false; + +module.exports.current=function (module) { current=module.current; Aios=module; }; + +/* +** Parse query string '?attr=val&attr=val... and return parameter record +*/ +function parseQueryString( url ) { + var queryString = url.substring( url.indexOf('?') + 1 ); + if (queryString == url) return []; + var params = {}, queries, temp, i, l; + + // Split into key/value pairs + queries = queryString.split("&"); + + // Convert the array of strings into an object + for ( i = 0, l = queries.length; i < l; i++ ) { + temp = queries[i].split('='); + if (temp[1]==undefined) temp[1]='true'; + params[temp[0]] = temp[1].replace('%20',' '); + } + + return params; +} +/* +** Format a query string from a parameter record +*/ +function formatQueryString (msg) { + var path= '/?'; + path += "magic="+msg.magic; + path += "&type="+AMMessageType.print(msg.type); + if (msg.cmd) path += '&cmd='+msg.cmd; + if (msg.tid) path += '&tid='+msg.tid; + if (msg.port) path += '&port='+Net.port_to_str(msg.port); + if (msg.timeout) path += '&timeout='+msg.timeout; + if (msg.node) path += '&node='+msg.node.replace(' ','%20'); + if (msg.index) path += '&index='+msg.index; + if (msg.secure) path += '&secure='+(msg.secure.length==8?Net.port_to_str(msg.secure):msg.secure); + return path; +} + +function msg2JSON(msg) { + if (msg.port) msg.port=Net.port_to_str(msg.port); + if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) { + if (msg.port) msg.port=Net.port_to_str(msg.port); + }); + return JSONfn.stringify(msg); +} +function JSON2msg(data) { + var msg=JSONfn.parse(data); + if (msg.port) msg.port=Net.port_of_str(msg.port); + if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) { + if (msg.port) msg.port=Net.port_of_str(msg.port); + }); + return msg; +} + +/** Get XML data + * + */ +function getData(data) { + if (data==undefined) return undefined; + else if (data.val!='') return data.val; + else return data.children.toString(); +} + +function is_error(data,err) { + if (data==undefined) return true; + if (err==undefined) + return (data.length > 0 && Comp.string.get(data,0)=='E'); + else + return (Comp.string.equal(data,err)); +}; + +/** AMP port using HTTP + * =================== + * + * No negotiation is performed. Data transfer can be fragmented. + * Each time a remote endpoint sends a GET/PUT request, we stall the request until + * a timeout occurs or we have to send data to the remote endpoint. A link is established. + * The routing table is refreshed each time the same client send a + * GET/PUT request again. If the client do not send requests anymore after a timeout, it is considered to be + * unlinked and the route is removed. + * + * type amp.http = function (options:{rcv:address,snd?:address,verbose?,logging?,out?:function,log?}) + */ +var http = Require('http'); + +amp.http = function (options) { + var self=this; + this.proto = 'http'; + this.options=checkOptions(options,{}); + this.verbose=checkOption(this.options.verbose,0); + + this.dir = options.dir; // attached to JAM port + this.rcv = options.rcv; // Local HTTP Server Port; Server Mode + this.mode = AMMode.AMO_MULTICAST; // We can handle multiple links at once + this.node = options.node; // Attached to this node + if (options.nodeid) this.node={id:options.nodeid}; // Different public node id + + if (options.rcv && options.rcv.address!='*' && options.rcv.port) this.mode |= AMMode.AMO_SERVER; + else this.mode |= AMMode.AMO_CLIENT; + + this.options.keepalive=checkOption(options.keepAlive,true); + this.secure = this.options.secure; + + this.port = options.port||Net.uniqport(); // Connection Link Port (this side) + this.id = Net.Print.port(this.port); + // Stream socket; can be a process object! + this.out = function (msg,async) { + (async?Aios.logAsync:Aios.log) + ('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.debug = function (msg) { + Aios.logAsync + ('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.err = function (msg,async) { + (async?Aios.logAsync:Aios.log) + ('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg); + throw 'AMP'; + } + + this.events = []; + // typeof linkentry = {snd:address,tries:number,state:amstate,collect?,collecting?,msgqueue?:{} []} + this.links = {}; + this.count={rcv:0,snd:0,lnk:0,png:0}; + if (options.snd) { + url=addr2url(options.snd,true); + this.links[url]={snd:options.snd,tries:0,state:AMState.AMS_NOTCONNECTED,live:options.AMC_MAXLIVE}; + //this.out(url) + } + // Collector thread collecting messages from server (AMO_CLIENT mode) + this.collector=undefined; + + this.logs=[]; + this.logging=options.logging||false; + if (this.logging) { + setInterval(function () { self.LOG('print') },5000); + } + this.index=0; +}; + +amp.http.prototype.LOG = amp.man.prototype.LOG; +amp.http.prototype.checkState = amp.man.prototype.checkState; +amp.http.prototype.config = amp.man.prototype.config; +amp.http.prototype.emit = amp.man.prototype.emit; +amp.http.prototype.on = amp.man.prototype.on; +amp.http.prototype.handle = amp.man.prototype.handle; +amp.http.prototype.status = amp.man.prototype.status; + +/** Acknowledge reply + * + */ +amp.http.prototype.ack=function(snd,status) { + this.response(snd,{type:AMMessageType.AMMACK,status:status||"EOK", + port:this.port,node:this.node?this.node.id:'*'}); +} + +/** Callback from ampMAN handler to inform about remote unlink event + * + */ +amp.http.prototype.cleanup=function(url,keep) { + // Cleanup link + var obj=this.links[url]; + if (!obj) return; + obj.state=AMState.AMS_NOTCONNECTED + if (obj.collect) clearTimeout(obj.collect), obj.collect=undefined; + if (obj.collecting) this.response(obj.collecting,{status:'ENOENTRY'}),obj.collecting=undefined; + // Link was initiated on remote side + // Remove link! + if (!keep) { + obj.snd={}; + this.links[url]=undefined; + } +} + +/** Collect request + * + */ +amp.http.prototype.collect=function(snd) { + var self=this, + url=addr2url(snd,true), + msg={type:AMMessageType.AMMCOLLECT,port:this.port,index:this.index++,magic:magic}; + if (this.links[url] && this.links[url].state==AMState.AMS_CONNECTED) + this.send(snd,msg,function (reply) { + var err=is_error(reply); + if (err) return; // self.cleanup(url,true); + if (reply.msg) Comp.array.iter(reply.msg,function (msg) { + self.handle(msg,snd); + }); + if (!self.links[url]) return; // unlinked? + self.links[url].collect=setTimeout(function () { + self.collect(snd); + },0); + }); +} +/** Service collect request + * + */ +amp.http.prototype.collecting=function(msg,remote,response) { + var url; + if (this.verbose>2) this.debug('handle AMMCOLLECT from '+addr2url(remote)); + url=addr2url(remote,true); // ipport or remote.port?? + if (this.links[url] && this.links[url].msgqueue && this.links[url].msgqueue.length) { + this.response(response,{msg:this.links[url].msgqueue}); + this.links[url].msgqueue=[]; + } + else if (this.links[url]) this.links[url].collecting=response; + else this.response(response,{status:'ENOENTRY'}); +} + +/** HTTP GET request to send a messageto the server broker returning data on reply. + * + * @param path + * @param callback + */ + +amp.http.prototype.get = function (snd,path,callback) { + var body,req, + self=this; + + if (this.verbose>2) this.debug('get '+addr2url(snd)+ path); + this.count.snd = this.count.snd + path.length; + if (!http.xhr) { + req = http.request({ + host: snd.address, + port: snd.port, + path: path, + method: 'GET', + keepAlive: this.options.keepalive, + headers: { + } + } , function(res) { + if (self.verbose>2) self.debug('got '+addr2url(snd)+ path); + if (res.setEncoding != null) res.setEncoding('utf8'); + body = ''; + res.on('data', function (chunk) { + body = body + chunk; + }); + res.once('end', function () { + self.count.rcv += body.length; + if (callback) callback(body); + }); + }); + req.once('error', function(err) { + if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true); + self.emit('error',err); + if (callback) callback(); + }); + req.end(); + } else { + // XHR Browser + http.request({ + host: snd.address, + port: snd.port, + path:path, + proto:'http', + keepAlive: this.options.keepalive, + method: 'GET', + headers: { + } + } , function(err,xhr,body) { + if (err) { + if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true); + self.emit('error',err); + if (callback) callback(); + } else { + self.count.rcv += body.length; + if (callback) callback(body); + } + }); + } +}; + +/** Initialize AMP + * + */ +amp.http.prototype.init = function(callback) { + if (callback) callback(); +}; + +/** Negotiate a virtual communication link (peer-to-peer). + * In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori! + * + * typeof @snd = address + * typeof @callback = function + * typeof @connect = boolean is indicating an initial connect request and not an acknowledge + * typeof @key = private + * typeof @response = object + * + * +------------+ + * VCMessageType (int16) + * Connection Port (port) + * Node ID (string) + * // Receiver IP Port (int32) + * +------------+ + * + */ +amp.http.prototype.link=function(snd,connect,key,response) { + var self = this, + msg, + url; + if (this.verbose>1) this.out('amp.link: to '+addr2url(snd),true); + + // MULTICAST mode + // Add new link to cache of links + if (!snd) this.err(true,'link: no destinataion set in MULTICAST mode'); + if (snd.parameter && snd.parameter.secure) key=snd.parameter.secure; + url=addr2url(snd,true); + if (!this.links[url] || !this.links[url].snd.address) { + if (connect) snd.connect=true; + this.links[url]={ + snd:snd, + state:AMState.AMS_NOTCONNECTED, + tries:0, + connect:connect, + live:options.AMC_MAXLIVE}; + } + // Let watchdog handle connect request link messages + if (!this.inwatchdog && connect) + return this.watchdog(true); + // if (this.verbose>1) this.debug('send link '+Io.inspect(snd)); + msg={ + type:AMMessageType.AMMLINK, + port:this.port, + node:this.node?this.node.id:'*', + index:this.index++, + magic:magic, + remote:snd.address, + }; + if (key) msg.secure=key; + + this.count.lnk++; + + if (response) + this.response(response,msg); + else this.send(snd,msg,function (reply) { + if (is_error(reply)) return; // error + // start message collector thread after first link reply! + if ((self.mode & AMMode.AMO_CLIENT) && !self.links[url].collect) { + self.links[url].collect=setTimeout(function () { + self.collect(snd); + },0); + } + // handle reply + self.handle(reply,snd); + }); +}; + +amp.http.prototype.ping=function(snd,response) { + var self = this,msg={}; + + + msg.type=AMMessageType.AMMPING; + msg.port=this.port; + msg.index=this.index++; + msg.magic=magic; + + if (this.verbose>1) this.debug('amp.ping'+(response?'in response':'')+': to '+addr2url(snd)); + + this.count.png++; + + if (response) + this.response(response,msg); + else this.send(snd,msg,function (reply) { + if (is_error(reply)) return; // error + // handle reply + self.handle(reply,snd); + }); +} + +amp.http.prototype.pong=function(snd,response) { + var self = this,msg={}; + + msg.type=AMMessageType.AMMPONG; + msg.port=this.port; + msg.index=this.index++; + msg.magic=magic; + + if (this.verbose>1) this.debug('amp.pong '+(response?'in response':'')+': to '+addr2url(snd)); + + this.count.png++; + + if (response) + this.response(response,msg); + else this.send(snd,msg,function (reply) { + if (is_error(reply)) { + self.emit('error',reply); + } + }); +} + +/** HTTP PUT request to send a message and data to the AMP HTTP server. + * + * @param path + * @param data + */ +amp.http.prototype.put = function (snd,path,data) { + var self=this, + req,body; + this.count.snd = this.count.snd + path.length + data.length; + if (!http.xhr) { + req = http.request({ + host: snd.address, + port: snd.port, + path: path, + method: 'POST', + keepAlive: this.options.keepalive, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': data.length + } + } , function(res) { + if (res.setEncoding != null) res.setEncoding('utf8'); + // TODO body=+chunk, res.on('end') ..?? + res.once('data', function (chunk) { + // TODO + }); + }); + req.once('error', function(err) { + self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true); + self.emit('error',err); + }); + + // write data to request body + req.write(data); + req.end(); + } else { + // XHR Browser + http.request({ + host: snd.address, + port: snd.port, + path: path, + method: 'POST', + body:data, + keepAlive: this.options.keepalive, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': data.length + } + } , function(err,xhr,body) { + if (err) { + if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true); + self.emit('error',err); + } + // TODO + }) + } +}; + +amp.http.prototype.receiver = function (callback,rcv) { + var self = this; + + if (callback) this.callback=callback; + + if (this.mode & AMMode.AMO_SERVER) { + // Only if this is a public or locally visible network node this node + // should provide a server port! + if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address; + if (rcv.port==undefined) rcv.port=this.rcv.port; + + this.server=http.createServer(function (request,response) { + if(parseQueryString(request.url).length==0) return response.end('EINVALID'); // accidental access by WEB browser + // console.log(request.connection.remoteAddress); + var i,body, + msg = parseQueryString(request.url), + remote = {address:request.connection.remoteAddress.replace(/^::ffff:/,'').replace(/^::1/,'localhost'), + port:'['+msg.port.replace(/:/g,'')+']' /* unique remote identifier */}; + + if (self.verbose>2) + console.log(request.method,request.url,msg,addr2url(remote),url2addr(addr2url(remote))); + + // consistency check + if (msg.magic!=magic) return; + + self.count.rcv += 1; + msg.type=AMMessageType[msg.type]; + + if (msg.secure) msg.secure=Net.port_of_str(msg.secure); + + if (debug) console.log(Io.Time(),msg) + + response.origin=request.headers.origin||request.headers.Origin; + Comp.string.match(request.method,[ + ['GET',function() { + if (msg.type==AMMessageType.AMMCOLLECT) + self.collecting(msg,remote,response); + else + self.handle(msg,remote,response); + }], + ['POST',function() { + body = ''; + request.on('data', function (chunk) { + body = body + chunk; + }); + request.on('end', function () { + msg.data=Buffer(body,'hex'); + self.count.rcv += msg.data.length; + if (msg.cmd) msg.cmd=Number(msg.cmd); + self.handle(msg,remote,response); + }); + }] + ]) + }); + + this.server.on("connection", function (socket) { + socket.setNoDelay(true); + }); + + this.server.on("error", function (err) { + self.out('Warning: receiver failed: '+err,true); + if (err) self.err(true,err); + }); + + this.server.listen(rcv.port,function (err) { + // Try to get network IP address of this host + if (!err) getNetworkIP(undefined,function (err,ip) { + if (!err) self.rcv.address=isLocal(ip)?options.localhost:ip; + if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+')',true); + if (err) return self.out("! Unable to obtain network connection information: "+err,true); + }); + if (callback) callback(err); + }); + } + if (this.mode & AMMode.AMO_CLIENT) { + + // If this is a hidden node (e.g., inside a WEB browser), we have to connect to a remote public server + // by using stalled GET requests. + if (callback) this.callback=callback; + } +} + +/** Reply to a request (msg.tid contains request tid) + */ +amp.http.prototype.reply = function (cmd,msg,snd) { + this.request(cmd,msg,snd); +} + +/** Send a response reply for a pending HTTP GET/PUT request (AMO_SERVER) + * + */ +amp.http.prototype.response = function (response,msg) { + var data=msg2JSON(msg), header; + + if (response.origin!=undefined) + header={'Access-Control-Allow-Origin': response.origin, + 'Access-Control-Allow-Credentials': 'true', + 'Content-Type': 'text/plain'}; + else + header={'Content-Type': 'text/plain'}; + if (this.options.keepalive) header["Connection"]="keep-alive"; + + response.writeHead(200,header); + response.write(data); + if (debug) console.log(Io.Time(),msg) + response.end(); +} + +/** Send a request message to a remote node endpoint + * + * function (cmd:integer,msg:Buffer,snd:address) + */ + +amp.http.prototype.request = function (cmd,msg,snd) { + var self=this,req={}, + size = msg.data.length, + tid = msg.tid||Comp.random.int(65536/2); + + if (snd==undefined) this.err(true,'request: snd=null'); + + req.type=AMMessageType.AMMRPC; + req.tid=tid; // Transaction Message ID + req.port=this.port; // This AMP id + req.cmd=cmd; + req.size=size; + req.magic=magic; + req.data=msg.data; + this.send(snd,req); + +} + + +amp.http.prototype.scan=function(snd,response,callback) { + var self = this,msg={}; + + msg.type=response?AMMessageType.AMMACK:AMMessageType.AMMSCAN; + msg.port=this.port; + msg.magic=magic; + + if (response) msg.info=snd.info; + + if (this.verbose>1 && snd) this.debug('amp.scan: to '+addr2url(snd)); + + if (response) + this.response(response,msg); + else + this.send(snd,msg,function (reply) { + callback(reply) + }); +} + +/** Main entry for requests with JSON interface. Multiplexer for HTTP GET/PUT. + * + * msg: JSON + * callback : function (reply:object) + */ +amp.http.prototype.send = function (snd,msg,callback) { + var path, + url, + body, + self=this; + // Create query selector + path = formatQueryString(msg); + + if (typeof snd.port == 'string') { + url=addr2url(snd,true); + // If Pending get from client + + // Else queue message, client will collect them later (or never) + if (this.links[url]) { + if (!this.links[url].msgqueue) this.links[url].msgqueue=[]; + if (this.links[url].collecting) {// pending AMMCOLLECT request + if (this.verbose>1) this.debug('REPLY msg '+AMMessageType.print(msg.type)+' to '+url); + this.response(this.links[url].collecting,{msg:[msg]}); + this.links[url].collecting=undefined; + } else { + if (this.verbose>1) this.debug('QUEUE msg '+AMMessageType.print(msg.type)+' for '+url); + this.links[url].msgqueue.push(msg); + } + } + } else if (msg.data!=undefined) { + // Convert buffer data to hex formatted string + body=msg.data.toString('hex'); + + this.put(snd,path,body,function (body) { + if (is_error(body)) self.emit('error',body); + else if (!is_status(body)) self.emit('error','EINVALID'); + // No reply expected! + }); + } else { + this.get(snd,path,function (body) { + var xml,i, + reply; + if (!body || is_error(body)) { + self.emit('error','EINVALID'); + } else { + reply=JSON2msg(body); + // { status:string,reply:*,msg?:{}[],..} + } + if (callback) callback(reply); + }); + } +} + + +// Start AMP watchdog and receiver +amp.http.prototype.start = function(callback) { + var self=this, + s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':''; + if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) + this.out('Starting ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s); + if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) + this.out('Starting ['+AMMode.print(this.mode)+'] (proto http)'); + this.watchdog(true); + if (!this.server && this.mode & AMMode.AMO_SERVER) { + // After stop? Restart receiver. + this.receiver(); + } + if (callback) callback(); +} + +// Stop AMP +amp.http.prototype.stop = function(callback) { + if (this.links) for(var p in this.links) { + if (this.links[p]) { + // Try to unlink remote endpoint + if (this.links[p].collect) clearTimeout(this.links[p].collect),this.links[p].collect=undefined; + this.unlink(this.links[p].snd); + if (this.links[p]) this.links[p].state=AMState.AMS_NOTCONNECTED; + } + } + if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) + this.out('Stopping ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s); + if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) + this.out('Stopping ['+AMMode.print(this.mode)+'] (proto http)'); + if (this.timer) clearTimeout(this.timer),this.timer=undefined; + if (this.server) this.server.close(),this.server=undefined; + + if (callback) callback(); +} + +// Unlink remote endpoint +amp.http.prototype.unlink=function(snd) { + var self = this,msg, + url = snd?addr2url(snd,true):null; + if (this.mode&AMMode.AMO_MULTICAST) { + if (!this.links[url] || this.links[url].state!=AMState.AMS_CONNECTED) return; + } else { + if (this.links.state!=AMState.AMS_CONNECTED) return; + } + msg={type:AMMessageType.AMMUNLINK,port:this.port,node:this.node?this.node.id:'*',index:this.index++,magic:magic}; + + this.send(snd,msg,function (reply) { + // handle reply + if (reply) {} + }); + this.emit('route-',addr2url(snd,true)); + if (this.mode&AMMode.AMO_MULTICAST) { + this.links[url].state=AMState.AMS_NOTCONNECTED; + if (!this.links[url].snd.connect) this.links[url].snd={}; + } else { + this.links.state=AMState.AMS_NOTCONNECTED; + if (!this.links.snd.connect) this.links.snd={}; + } + this.cleanup(url); +} + + +/** Install a watchdog timer. + * + * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set. + * 2. If link state is AMS_CONNECTED, check link end point. + * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker + * + * @param run + */ +amp.http.prototype.watchdog = function(run,immedOrDelay) { + var self=this; + if (this.timer) clearTimeout(self.timer),this.timer=undefined; + if (run) self.timer=setTimeout(function () { + if (!self.timer || self.inwatchdog) return; // stopped or busy? + self.timer = undefined; + self.inwatchdog=true; + + function handle(obj,url) { + if (self.verbose>1) self.debug('Watchdog: handle link ('+url+') '+ + (obj.snd?addr2url(obj.snd):'')+' in state '+ + AMState.print(obj.state)+' live '+obj.live,true); + switch (obj.state) { + case AMState.AMS_CONNECTED: + if (obj.live == 0) { + // No PING received, disconnect... + if (self.verbose>0) + self.out('Endpoint ' + addr2url(obj.snd) + + ' not responding, propably dead. Unlinking...',true); + obj.state = AMState.AMS_NOTCONNECTED; + self.emit('route-',addr2url(obj.snd,true)); + self.cleanup(url,obj.snd.connect); + if (obj.snd.connect) self.watchdog(true,2000); + } else { + obj.tries=0; + obj.live--; + self.watchdog(true); + if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd); + else self.ping(); + } + break; + case AMState.AMS_NOTCONNECTED: + case AMState.AMS_PAIRED: + if (obj.snd.port && typeof obj.snd.port == 'number') { + // Try link to specified remote endpoint obj.snd + if (self.verbose>0 && obj.tries==0) + self.out('Trying link to ' + addr2url(obj.snd),true); + self.link(obj.snd); + obj.tries++; + if (obj.tries < options.TRIES) self.watchdog(true); + else { + self.out('Giving up to link '+addr2url(obj.snd),true); + self.emit('error','link',addr2url(obj.snd)); + obj.snd={},obj.tries=0; + } + } + break; + // AMP P2P Control + case AMState.AMS_RENDEZVOUS: + obj.send( + {type:'register',name: self.rcv.name, linfo: self.rcv}, + self.broker, + function () {} + ); + self.watchdog(true); + break; + case AMState.AMS_REGISTERED: + if (obj.tries < options.TRIES && obj.snd.name) { + obj.tries++; + self.send( + {type:'pair', from:self.rcv.name, to: obj.snd.name}, + self.broker, + function () { + } + ); + } + if (obj.tries < options.TRIES) self.watchdog(true); + break; + } + } + for(var p in self.links) if (self.links[p]) handle(self.links[p],p); + self.inwatchdog=false; + },immedOrDelay==true?0:immedOrDelay||options.TIMER); +}; + +}; +BundleModuleCode['jam/jsonfn']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Vadim Kiryukhin, Stefan Bosse + ** $INITIAL: (C) 2006-2017 Vadim Kiryukhin + ** $MODIFIED: by sbosse. + ** $RCS: $Id: jsonfn.js,v 1.1 2017/05/20 15:56:53 sbosse Exp $ + ** $VERSION: 1.3.3X + ** + ** $INFO: + ** + ** JSONfn - javascript (both node.js and browser) plugin to stringify, + ** parse and clone objects with embedded functions in an optional masked context (mask). + ** - supported data types: number, boolean, string, array, buffer, typedarray, function, regex + ** + ** browser: + ** JSONfn.stringify(obj); + ** JSONfn.parse(str[, date2obj]); + ** JSONfn.clone(obj[, date2obj]); + ** + ** nodejs: + ** var JSONfn = require('path/to/json-fn'); + ** JSONfn.stringify(obj); + ** JSONfn.parse(str[, date2obj]); + ** JSONfn.clone(obj[, date2obj]); + ** + ** + ** @obj - Object; + ** @str - String, which is returned by JSONfn.stringify() function; + ** @mask - Environment Mask (optional) + ** + ** $ENDOFINFO + */ + +var current=null; + + +function typedarrayTObase64(ta,ftyp) { + var b,i; + if (ta.buffer instanceof ArrayBuffer) { + b=Buffer(ta.buffer); + if (b.length>0) return b.toString('base64'); + } + // Fall-back conversion + switch (ftyp) { + case Float32Array: + b = Buffer(ta.length*4); + for(i=0;i/fullchain.pem + ** SSLCertificateKeyFile /etc/letsencrypt/live//privkey.pem + ** + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Lz = Require('os/lz-string'); +var Comp = Require('com/compat'); +var Buf = Require('dos/buf'); +var Net = Require('dos/network'); +var Command = Net.Command; +var Status = Net.Status; +var current=none; +var Aios=none; +var CBL = Require('com/cbl'); +var Bas64 = Require('os/base64'); +var Sec = Require('jam/security') +var JSONfn = Require('jam/jsonfn') + +var options = { + version:"1.14.7", +} + +var COM = Require('jam/ampCOM'), + AMMode=COM.AMMode, + AMMessageType=COM.AMMessageType, + AMState=COM.AMState, + amp=COM.amp, + options=COM.options, + url2addr=COM.url2addr, + addr2url=COM.addr2url, + addrequal=COM.addrequal, + resolve=COM.resolve, + ipequal=COM.ipequal, + isLocal=COM.isLocal, + getNetworkIP=COM.getNetworkIP, + pem=COM.options.pem, + magic=COM.options.magic; + +var debug = false; + +module.exports.current=function (module) { current=module.current; Aios=module; }; + +/* +** Parse query string '?attr=val&attr=val... and return parameter record +*/ +function parseQueryString( url ) { + var queryString = url.substring( url.indexOf('?') + 1 ); + if (queryString == url) return []; + var params = {}, queries, temp, i, l; + + // Split into key/value pairs + queries = queryString.split("&"); + + // Convert the array of strings into an object + for ( i = 0, l = queries.length; i < l; i++ ) { + temp = queries[i].split('='); + if (temp[1]==undefined) temp[1]='true'; + params[temp[0]] = temp[1].replace('%20',' '); + } + + return params; +} +/* +** Format a query string from a parameter record +*/ +function formatQueryString (msg) { + var path= '/?'; + path += "magic="+msg.magic; + path += "&type="+AMMessageType.print(msg.type); + if (msg.cmd) path += '&cmd='+msg.cmd; + if (msg.tid) path += '&tid='+msg.tid; + if (msg.port) path += '&port='+Net.port_to_str(msg.port); + if (msg.timeout) path += '&timeout='+msg.timeout; + if (msg.node) path += '&node='+msg.node.replace(' ','%20'); + if (msg.index) path += '&index='+msg.index; + if (msg.secure) path += '&secure='+(msg.secure.length==8?Net.port_to_str(msg.secure):msg.secure); + return path; +} + +function msg2JSON(msg) { + if (msg.port) msg.port=Net.port_to_str(msg.port); + if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) { + if (msg.port) msg.port=Net.port_to_str(msg.port); + }); + return JSONfn.stringify(msg); +} +function JSON2msg(data) { + var msg=JSONfn.parse(data); + if (msg.port) msg.port=Net.port_of_str(msg.port); + if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) { + if (msg.port) msg.port=Net.port_of_str(msg.port); + }); + return msg; +} + +/** Get XML data + * + */ +function getData(data) { + if (data==undefined) return undefined; + else if (data.val!='') return data.val; + else return data.children.toString(); +} + +function is_error(data,err) { + if (data==undefined) return true; + if (err==undefined) + return (data.length > 0 && Comp.string.get(data,0)=='E'); + else + return (Comp.string.equal(data,err)); +}; + +/** AMP port using HTTP + * =================== + * + * No negotiation is performed. Data transfer can be fragmented. + * Each time a remote endpoint sends a GET/PUT request, we stall the request until + * a timeout occurs or we have to send data to the remote endpoint. A link is established. + * The routing table is refreshed each time the same client send a + * GET/PUT request again. If the client do not send requests anymore after a timeout, it is considered to be + * unlinked and the route is removed. + * + * type amp.https = function (options:{pem?:{key,cert}, rcv:address,snd?:address,verbose?,logging?,out?:function,log?}) + */ +var https; +var http = Require('http'); + +amp.https = function (options) { + var self=this; + this.proto = 'http'; + this.options = checkOptions(options,{}); + this.verbose = checkOption(this.options.verbose,0); + + + if (global.TARGET!= 'browser' && !https) try { + https=require('https'); + } catch (e) { + throw 'amp.https: no https/crypto support ('+e+')'; + } + + this.dir = options.dir; // attached to JAM port + this.rcv = options.rcv; // Local HTTP Server Port; Server Mode + this.mode = AMMode.AMO_MULTICAST; // We can handle multiple links at once + this.node = options.node; // Attached to this node + + if (options.rcv && options.rcv.address!='*' && options.rcv.port) this.mode |= AMMode.AMO_SERVER; + else this.mode |= AMMode.AMO_CLIENT; + + if (!options.pem) this.options.pem=pem; + + if ((this.mode & AMMode.AMO_CLIENT)==0 && + (!this.options.pem || !this.options.pem.key || !this.options.pem.cert)) + throw "amp.https: no pem certificate and key provided like pem:{key,cert}"; + + + this.options.keepalive=checkOption(options.keepAlive,true); + this.secure = this.options.secure; + + this.port = options.port||Net.uniqport(); // Connection Link Port (this side) + this.id = Net.Print.port(this.port); + // Stream socket; can be a process object! + this.out = function (msg,async) { + (async?Aios.logAsync:Aios.log) + ('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.debug = function (msg) { + Aios.logAsync + ('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg); + } + this.err = function (msg,async) { + (async?Aios.logAsync:Aios.log) + ('[AMP '+Net.Print.port(self.port)+ + (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg); + throw 'AMP'; + } + + this.events = []; + // typeof linkentry = {snd:address,tries:number,state:amstate,collect?,collecting?,msgqueue?:{} []} + this.links = {}; + this.count={rcv:0,snd:0,lnk:0,png:0}; + if (options.snd) { + url=addr2url(options.snd,true); + this.links[url]={snd:options.snd,tries:0,state:AMState.AMS_NOTCONNECTED,live:options.AMC_MAXLIVE}; + //this.out(url) + } + // Collector thread collecting messages from server (AMO_CLIENT mode) + this.collector=undefined; + + this.logs=[]; + this.logging=options.logging||false; + if (this.logging) { + setInterval(function () { self.LOG('print') },5000); + } + this.index=0; +}; + +amp.https.prototype.LOG = amp.man.prototype.LOG; +amp.https.prototype.checkState = amp.man.prototype.checkState; +amp.https.prototype.config = amp.man.prototype.config; +amp.https.prototype.emit = amp.man.prototype.emit; +amp.https.prototype.on = amp.man.prototype.on; +amp.https.prototype.handle = amp.man.prototype.handle; +amp.https.prototype.status = amp.man.prototype.status; + +/** Acknowledge reply + * + */ +amp.https.prototype.ack=function(snd,status) { + this.response(snd,{type:AMMessageType.AMMACK,status:status||"EOK", + port:this.port,node:this.node?this.node.id:'*'}); +} + +/** Callback from ampMAN handler to inform about remote unlink event + * + */ +amp.https.prototype.cleanup=function(url,keep) { + // Cleanup link + var obj=this.links[url]; + if (!obj) return; + obj.state=AMState.AMS_NOTCONNECTED + if (obj.collect) clearTimeout(obj.collect), obj.collect=undefined; + if (obj.collecting) this.response(obj.collecting,{status:'ENOENTRY'}),obj.collecting=undefined; + // Link was initiated on remote side + // Remove link! + if (!keep) { + obj.snd={}; + this.links[url]=undefined; + } +} + +/** Collect request + * + */ +amp.https.prototype.collect=function(snd) { + var self=this, + url=addr2url(snd,true), + msg={type:AMMessageType.AMMCOLLECT,port:this.port,index:this.index++,magic:magic}; + if (this.links[url] && this.links[url].state==AMState.AMS_CONNECTED) + this.send(snd,msg,function (reply) { + var err=is_error(reply); + if (err) return; // self.cleanup(url,true); + if (reply.msg) Comp.array.iter(reply.msg,function (msg) { + self.handle(msg,snd); + }); + if (!self.links[url]) return; // unlinked? + self.links[url].collect=setTimeout(function () { + self.collect(snd); + },0); + }); +} +/** Service collect request + * + */ +amp.https.prototype.collecting=function(msg,remote,response) { + var url; + if (this.verbose>2) this.debug('handle AMMCOLLECT from '+addr2url(remote)); + url=addr2url(remote,true); // ipport or remote.port?? + if (this.links[url] && this.links[url].msgqueue && this.links[url].msgqueue.length) { + this.response(response,{msg:this.links[url].msgqueue}); + this.links[url].msgqueue=[]; + } + else if (this.links[url]) this.links[url].collecting=response; + else this.response(response,{status:'ENOENTRY'}); +} + +/** HTTP GET request to send a messageto the server broker returning data on reply. + * + * @param path + * @param callback + */ + +amp.https.prototype.get = function (snd,path,callback) { + var body,req, + self=this; + + if (this.verbose>2) this.debug('get '+addr2url(snd)+ path); + this.count.snd = this.count.snd + path.length; + if (https) { + req = https.request({ + host: snd.address, + port: snd.port, + path: path, + method: 'GET', + keepAlive: this.options.keepalive, + headers: { + } + } , function(res) { + if (self.verbose>2) self.debug('got '+addr2url(snd)+ path); + if (res.setEncoding != null) res.setEncoding('utf8'); + body = ''; + res.on('data', function (chunk) { + body = body + chunk; + }); + res.once('end', function () { + self.count.rcv += body.length; + if (callback) callback(body); + }); + }); + req.once('error', function(err) { + if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true); + self.emit('error',err); + if (callback) callback(); + }); + req.end(); + } else { + // XHR Browser + http.request({ + host: snd.address, + port: snd.port, + path: path, + proto:'https', + method: 'GET', + headers: { + } + } , function(err,xhr,body) { + if (err) { + if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true); + self.emit('error',err); + if (callback) callback(); + } else { + self.count.rcv += body.length; + if (callback) callback(body); + } + }); + } +}; + +/** Initialize AMP + * + */ +amp.https.prototype.init = function(callback) { + if (callback) callback(); +}; + +/** Negotiate a virtual communication link (peer-to-peer). + * In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori! + * + * typeof @snd = address + * typeof @callback = function + * typeof @connect = boolean is indicating an initial connect request and not an acknowledge + * typeof @key = private + * typeof @response = object + * + * +------------+ + * VCMessageType (int16) + * Connection Port (port) + * Node ID (string) + * // Receiver IP Port (int32) + * +------------+ + * + */ +amp.https.prototype.link=function(snd,connect,key,response) { + var self = this, + msg, + url; + if (this.verbose>1) this.debug('amp.link: to '+addr2url(snd)); + + // MULTICAST mode + // Add new link to cache of links + if (!snd) this.err('link: no destinataion set in MULTICAST mode'); + if (snd.parameter && snd.parameter.secure) key=snd.parameter.secure; + url=addr2url(snd,true); + if (!this.links[url] || !this.links[url].snd.address) { + if (connect) snd.connect=true; + this.links[url]={ + snd:snd, + state:AMState.AMS_NOTCONNECTED, + tries:0, + connect:connect, + live:options.AMC_MAXLIVE}; + } + // Let watchdog handle connect request link messages + if (!this.inwatchdog && connect) + return this.watchdog(true); + // if (this.verbose>1) this.out('send link '+Io.inspect(snd)); + msg={ + type:AMMessageType.AMMLINK, + port:this.port, + node:this.node?this.node.id:'*', + index:this.index++, + magic:magic, + remote:snd.address, + }; + if (key) msg.secure=key; + + this.count.lnk++; + + if (response) + this.response(response,msg); + else this.send(snd,msg,function (reply) { + if (is_error(reply)) return; // error + // start message collector thread after first link reply! + if ((self.mode & AMMode.AMO_CLIENT) && !self.links[url].collect) { + self.links[url].collect=setTimeout(function () { + self.collect(snd); + },0); + } + // handle reply + self.handle(reply,snd); + }); +}; + +amp.https.prototype.ping=function(snd,response) { + var self = this,msg={}; + + + msg.type=AMMessageType.AMMPING; + msg.port=this.port; + msg.index=this.index++; + msg.magic=magic; + + if (this.verbose>1) this.debug('amp.ping'+(response?'in response':'')+': to '+addr2url(snd)); + + this.count.png++; + + if (response) + this.response(response,msg); + else this.send(snd,msg,function (reply) { + if (is_error(reply)) return; // error + // handle reply + self.handle(reply,snd); + }); +} + +amp.https.prototype.pong=function(snd,response) { + var self = this,msg={}; + + msg.type=AMMessageType.AMMPONG; + msg.port=this.port; + msg.index=this.index++; + msg.magic=magic; + + if (this.verbose>1) this.debug('amp.pong '+(response?'in response':'')+': to '+addr2url(snd)); + + this.count.png++; + + if (response) + this.response(response,msg); + else this.send(snd,msg,function (reply) { + if (is_error(reply)) { + self.emit('error',reply); + } + }); +} + +/** HTTP PUT request to send a message and data to the AMP HTTP server. + * + * @param path + * @param data + */ +amp.https.prototype.put = function (snd,path,data) { + var self=this, + req,body; + this.count.snd = this.count.snd + path.length + data.length; + if (https) { + req = https.request({ + host: snd.address, + port: snd.port, + path: path, + method: 'POST', + keepAlive: this.options.keepalive, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': data.length + } + } , function(res) { + if (res.setEncoding != null) res.setEncoding('utf8'); + // TODO body=+chunk, res.on('end') ..?? + res.once('data', function (chunk) { + // TODO + }); + }); + req.once('error', function(err) { + self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true); + self.emit('error',err); + }); + + // write data to request body + req.write(data); + req.end(); + } else { + // XHR Browser + http.request({ + host: snd.address, + port: snd.port, + path: path, + proto: 'https', + method: 'POST', + body:data, + keepAlive: this.options.keepalive, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': data.length + } + } , function(err,xhr,body) { + if (err) { + if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true); + self.emit('error',err); + } + // TODO + }) + } +}; + +amp.https.prototype.receiver = function (callback,rcv) { + var self = this; + + if (callback) this.callback=callback; + + if (this.mode & AMMode.AMO_SERVER) { + // Only if this is a public or locally visible network node this node + // should provide a server port! + if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address; + if (rcv.port==undefined) rcv.port=this.rcv.port; + + var _options = { + key : this.options.pem.key, + cert: this.options.pem.cert, + }; + this.server=https.createServer(_options, function (request,response) { + if(parseQueryString(request.url).length==0) return response.end('EINVALID'); // accidental access by WEB browser + // console.log(request.connection.remoteAddress); + var i,body, + msg = parseQueryString(request.url), + remote = {address:request.connection.remoteAddress.replace(/^::ffff:/,'').replace(/^::1/,'localhost'), + port:'['+msg.port.replace(/:/g,'')+']' /* unique remote identifier */}; + + if (self.verbose>2) + console.log(request.method,request.url,msg,addr2url(remote),url2addr(addr2url(remote))); + + // consistency check + if (msg.magic!=magic) return; + + self.count.rcv += msg.length; + msg.type=AMMessageType[msg.type]; + + if (msg.secure) msg.secure=Net.port_of_str(msg.secure); + + if (debug) console.log(Io.Time(),msg) + + response.origin=request.headers.origin||request.headers.Origin; + Comp.string.match(request.method,[ + ['GET',function() { + if (msg.type==AMMessageType.AMMCOLLECT) + self.collecting(msg,remote,response); + else + self.handle(msg,remote,response); + }], + ['POST',function() { + body = ''; + request.on('data', function (chunk) { + body = body + chunk; + }); + request.on('end', function () { + msg.data=Buffer(body,'hex'); + self.count.rcv += msg.data.length; + if (msg.cmd) msg.cmd=Number(msg.cmd); + self.handle(msg,remote,response); + }); + }] + ]) + }); + + this.server.on("connection", function (socket) { + socket.setNoDelay(true); + }); + + this.server.on("error", function (err) { + self.out('Warning: receiver failed: '+err,true); + if (err) self.err(err); + }); + + this.server.listen(rcv.port,function (err) { + // Try to get network IP address of this host + if (!err) getNetworkIP(undefined,function (err,ip) { + if (!err) self.rcv.address=isLocal(ip)?options.localhost:ip; + if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+')',true); + if (err) return self.out("! Unable to obtain network connection information: "+err,true); + }); + if (callback) callback(err); + }); + } + if (this.mode & AMMode.AMO_CLIENT) { + + // If this is a hidden node (e.g., inside a WEB browser), we have to connect to a remote public server + // by using stalled GET requests. + if (callback) this.callback=callback; + } +} + +/** Reply to a request (msg.tid contains request tid) + */ +amp.http.prototype.reply = function (cmd,msg,snd) { + this.request(cmd,msg,snd); +} + +/** Send a response reply for a pending HTTP GET/PUT request (AMO_SERVER) + * + */ +amp.https.prototype.response = function (response,msg) { + var data=msg2JSON(msg), header; + + if (response.origin!=undefined) + header={'Access-Control-Allow-Origin': response.origin, + 'Access-Control-Allow-Credentials': 'true', + 'Content-Type': 'text/plain'}; + else + header={'Content-Type': 'text/plain'}; + if (this.options.keepalive) header["Connection"]="keep-alive"; + + response.writeHead(200,header); + response.write(data); + if (debug) console.log(Io.Time(),msg) + response.end(); +} + +/** Send a request message to a remote node endpoint + * + * function (cmd:integer,msg:Buffer,snd:address) + */ + +amp.https.prototype.request = function (cmd,msg,snd) { + var self=this,req={}, + size = msg.data.length, + tid = msg.tid||Comp.random.int(65536/2); + + if (snd==undefined) this.err('request: snd=null'); + + req.type=AMMessageType.AMMRPC; + req.tid=tid; // Transaction Message ID + req.port=this.port; // This AMP id + req.cmd=cmd; + req.size=size; + req.magic=magic; + req.data=msg.data; + this.send(snd,req); + +} + + +amp.https.prototype.scan=function(snd,response,callback) { + var self = this,msg={}; + + msg.type=response?AMMessageType.AMMACK:AMMessageType.AMMSCAN; + msg.port=this.port; + msg.magic=magic; + + if (response) msg.info=snd.info; + + if (this.verbose>1 && snd) this.debug('amp.scan: to '+addr2url(snd)+' '+(response?'R':'')); + + if (response) + this.response(response,msg); + else + this.send(snd,msg,function (reply) { + callback(reply) + }); +} + +/** Main entry for requests with JSON interface. Multiplexer for HTTP GET/PUT. + * + * msg: JSON + * callback : function (reply:object) + */ +amp.https.prototype.send = function (snd,msg,callback) { + var path, + url, + body, + self=this; + // Create query selector + path = formatQueryString(msg); + + if (typeof snd.port == 'string') { + url=addr2url(snd,true); + // If Pending get from client + + // Else queue message, client will collect them later (or never) + if (this.links[url]) { + if (!this.links[url].msgqueue) this.links[url].msgqueue=[]; + if (this.links[url].collecting) {// pending AMMCOLLECT request + if (this.verbose>1) this.debug('REPLY msg '+AMMessageType.print(msg.type)+' to '+url); + this.response(this.links[url].collecting,{msg:[msg]}); + this.links[url].collecting=undefined; + } else { + if (this.verbose>1) this.debug('QUEUE msg '+AMMessageType.print(msg.type)+' for '+url); + this.links[url].msgqueue.push(msg); + } + } + } else if (msg.data!=undefined) { + // Convert buffer data to hex formatted string + body=msg.data.toString('hex'); + + this.put(snd,path,body,function (body) { + if (is_error(body)) self.emit('error',body); + else if (!is_status(body)) self.emit('error','EINVALID'); + // No reply expected! + }); + } else { + this.get(snd,path,function (body) { + var xml,i, + reply; + if (!body || is_error(body)) { + self.emit('error','EINVALID'); + } else { + reply=JSON2msg(body); + // { status:string,reply:*,msg?:{}[],..} + } + if (callback) callback(reply); + }); + } +} + + +// Start AMP watchdog and receiver +amp.https.prototype.start = function(callback) { + var self=this, + s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':''; + if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) + this.out('Starting ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s); + if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) + this.out('Starting ['+AMMode.print(this.mode)+'] (proto http)'); + this.watchdog(true); + if (!this.server && this.mode & AMMode.AMO_SERVER) { + // After stop? Restart receiver. + this.receiver(); + } + if (callback) callback(); +} + +// Stop AMP +amp.https.prototype.stop = function(callback) { + if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) + this.out('Stopping ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s); + if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) + this.out('Stopping ['+AMMode.print(this.mode)+'] (proto http)'); + if (this.links) for(var p in this.links) { + if (this.links[p]) { + // Try to unlink remote endpoint + this.unlink(this.links[p].snd); + this.links[p].state=AMState.AMS_NOTCONNECTED; + if (this.links[p].collect) clearTimeout(this.links[p].collect),this.links[p].collect=undefined; + } + } + if (this.timer) clearTimeout(this.timer),this.timer=undefined; + if (this.server) this.server.close(),this.server=undefined; + + if (callback) callback(); +} + +// Unlink remote endpoint +amp.https.prototype.unlink=function(snd) { + var self = this,msg, + url = snd?addr2url(snd,true):null; + if (this.mode&AMMode.AMO_MULTICAST) { + if (!this.links[url] || this.links[url].state!=AMState.AMS_CONNECTED) return; + } else { + if (this.links.state!=AMState.AMS_CONNECTED) return; + } + msg={type:AMMessageType.AMMUNLINK,port:this.port,node:this.node?this.node.id:'*',index:this.index++,magic:magic}; + + this.send(snd,msg,function (reply) { + // handle reply + if (reply) {} + }); + this.emit('route-',addr2url(snd,true)); + if (this.mode&AMMode.AMO_MULTICAST) { + this.links[url].state=AMState.AMS_NOTCONNECTED; + if (!this.links[url].snd.connect) this.links[url].snd={}; + } else { + this.links.state=AMState.AMS_NOTCONNECTED; + if (!this.links.snd.connect) this.links.snd={}; + } + this.cleanup(url); +} + + +/** Install a watchdog timer. + * + * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set. + * 2. If link state is AMS_CONNECTED, check link end point. + * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker + * + * @param run + */ +amp.https.prototype.watchdog = function(run,immedOrDelay) { + var self=this; + if (this.timer) clearTimeout(self.timer),this.timer=undefined; + if (run) self.timer=setTimeout(function () { + if (!self.timer || self.inwatchdog) return; // stopped or busy? + self.timer = undefined; + self.inwatchdog=true; + + function handle(obj,url) { + if (self.verbose>1) self.debug('Watchdog: handle link ('+url+') '+ + (obj.snd?addr2url(obj.snd):'')+' in state '+ + AMState.print(obj.state)+' live '+obj.live); + switch (obj.state) { + case AMState.AMS_CONNECTED: + if (obj.live == 0) { + // No PING received, disconnect... + if (self.verbose>0) + self.out('Endpoint ' + addr2url(obj.snd) + + ' not responding, propably dead. Unlinking...',true); + obj.state = AMState.AMS_NOTCONNECTED; + self.emit('route-',addr2url(obj.snd,true)); + self.cleanup(url,obj.snd.connect); + if (obj.snd.connect) self.watchdog(true,2000); + } else { + obj.tries=0; + obj.live--; + self.watchdog(true); + if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd); + else self.ping(); + } + break; + case AMState.AMS_NOTCONNECTED: + case AMState.AMS_PAIRED: + if (obj.snd.port && typeof obj.snd.port == 'number') { + // Try link to specified remote endpoint obj.snd + if (self.verbose>0 && obj.tries==0) + self.out('Trying link to ' + addr2url(obj.snd),true); + self.link(obj.snd); + obj.tries++; + if (obj.tries < options.TRIES) self.watchdog(true); + else { + self.out('Giving up to link '+addr2url(obj.snd),true); + self.emit('error','link',addr2url(obj.snd)); + obj.snd={},obj.tries=0; + } + } + break; + // AMP P2P Control + case AMState.AMS_RENDEZVOUS: + obj.send( + {type:'register',name: self.rcv.name, linfo: self.rcv}, + self.broker, + function () {} + ); + self.watchdog(true); + break; + case AMState.AMS_REGISTERED: + if (obj.tries < options.TRIES && obj.snd.name) { + obj.tries++; + self.send( + {type:'pair', from:self.rcv.name, to: obj.snd.name}, + self.broker, + function () { + } + ); + } + if (obj.tries < options.TRIES) self.watchdog(true); + break; + } + } + for(var p in self.links) if (self.links[p]) handle(self.links[p],p); + self.inwatchdog=false; + },immedOrDelay==true?0:immedOrDelay||options.TIMER); +}; + +}; +BundleModuleCode['db/db']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2018 bLAB + ** $CREATED: 09/02/16 by sbosse. + ** $VERSION: 1.6.3 + ** + ** $INFO: + ** + ** JavaScript AIOS: SQL Service API + ** + ** Sqlc: SQLD Database Client Module using named FIFO pipes OR built-in SQL server (see below) + ** + ** path_in: the write to SQLD channel (SQLD input) + ** path_out: the read from SQLD channel (SQLD output) + ** + ** + ** Sqld: SQL Database Server Module (and client interface) + ** ------------------------------------------------------- + ** + ** Using built-in sqlite3 module accessing a database file (jx: embedded, node.js: native module) + ** + ** Example: + ** + ** db = sql('/var/db/sensors.sql',{mode:'r+'}); // or in memory + ** db = sql(':memory:',{mode:'r+'}) + ** + ** db.init(); + ** db.createTable('sensors',{name:'',value:0, unit:''}); + ** db.insertTable('sensors',{name:'current',value:1.0, unit:'A'}); + ** db.insertTable('sensors',{name:'voltage',value:12.1, unit:'V'}); + ** db.readTable('sensors',function (res) { + ** print('callback',res); + ** }); + ** print(db.readTable('sensors')); + ** print(db.select('sensors','*')); + ** print('finished') + ** + ** Synchronous Version (using blocking IO, readFileSync) + ** + ** TODO: Merge with dbS!!!!! + ** Options: Sync/Async/Server ... + ** + ** $ENDOFINFO + */ +var Io = Require('com/io'); +var Comp = Require('com/compat'); +var Fs = Require('fs'); +var current={}; +var Aios=none; +var NL='\n'; + +var options = { + version:'1.6.3' +} + +function await() { + if (jxcore) jxcore.utils.jump(); +} + +function wakeup() { + if (jxcore) jxcore.utils.continue(); +} + +function exists(path) { + try { + var fd=Fs.openSync(path,'r'); + Fs.close(fd); + return true; + } catch (e) { return false } +} + +/* Some hacks for wine/win32 node.js/nw.js */ +var Buffer = require('buffer').Buffer; + +function tryReadSync(fd, buffer, pos, len) { + var threw = true; + var bytesRead; + try { + bytesRead = Fs.readSync(fd, buffer, pos, len); + threw = false; + } finally { + if (threw) fs.closeSync(fd); + } + return bytesRead; +} + +function readFileSync(path,encoding) { + var fd = Fs.openSync(path, 'r', 666), + bytesRead,buffer,pos=0, + buffers=[]; + + do { + buffer = Buffer(8192); + bytesRead = tryReadSync(fd, buffer, 0, 8192); + if (bytesRead !== 0) { + buffers.push(buffer.slice(0, bytesRead)); + } + pos += bytesRead; + } while (bytesRead !== 0); + + Fs.closeSync(fd); + buffer = Buffer.concat(buffers, pos); + + if (encoding) buffer = buffer.toString(encoding); + return buffer; +} + +function sleep(time) { + var stop = new Date().getTime(); + while(new Date().getTime() < stop + time) { + ; + } +} + +function writeSync(path,str) { + var n=0; + var fd = Fs.openSync(path,'r+'); + n=Fs.writeSync(fd, str); + Fs.closeSync(fd); + return n; +} + +/******************* SQLC ************************/ + +var sqlc = function (path,chan) { + this.path=path; + // Client -> Server + this.input=none; + // Server -> Client + this.output=none; + this.chan=chan; + this.id='SQLC'; + this.log=function (msg) { + ((Aios && Aios.print)||Io.log)('[SQLC] '+msg); + } + this.todo=[]; +} + +/** Return string list of comma seperated value list + * + Strip string delimeters '' + * + */ +sqlc.prototype.args = function (msg) { + var args=Comp.string.split(',',msg); + return Comp.array.filtermap(args, function (arg) { + if (arg=='') return none; + else if (Comp.string.get(arg,0)=="'") return Comp.string.trim(arg,1,1); + else return arg; + }); +}; + +/** Create a numeric matrix + * + * typeof @header = * [] is type interface provider for all rows; + * + */ + +sqlc.prototype.createMatrix = function (matname,header,callback) { + var repl, + intf='', line='', sep='', self=this; + if (!this.connected) return callback?callback(false):false; + if (header.length==0) return false; + Comp.array.iter(header,function (col,i) { + intf += (sep+'c'+(i+1)+(Comp.obj.isNumber(col)?' integer':' varchar(32)')); sep=','; + }); + sep=''; + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl;return 0;} + else return 1; + } + return this.seq([ + ['exec','drop table if exists '+matname, done], + ['exec','create table '+matname+' ('+intf+')', done] + ],callback); + +} + +/** Create a generic table + * + * typeof @header = {} is type and name interface provider for all rows; + * + */ +sqlc.prototype.createTable = function (tabname,header,callback) { + var repl, + intf='', sep='', self=this; + if (!this.connected) return callback?callback(false):false; + // if (header.length==0) return false; + Comp.obj.iter(header,function (attr,key) { + intf += (sep+key+(Comp.obj.isNumber(attr)?' integer':' varchar(32)')); sep=','; + }); + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl;return 0;} + else return 1; + } + return this.seq([ + ['exec','drop table if exists '+tabname, done], + ['exec','create table '+tabname+' ('+intf+')', done], + ],callback); +} + + +/** Check end message + * + */ +sqlc.prototype.end = function (msg) { return msg.indexOf('END') == 0 } +/** Check error message + * + */ +sqlc.prototype.err = function (msg) { + if (!msg) return false; + if (Comp.obj.isObject(msg)) return msg.errno != undefined; + if (Comp.obj.isString(msg)) return msg.indexOf('ERR') == 0 || msg.indexOf('Error') == 0; + return false; +} + + +/** Execute: send a request and wait for reply. + */ +sqlc.prototype.exec = function (cmd,callback) { + var n,str='',fd; + if (!this.connected) return callback?callback(none):none; + n = Fs.writeSync(this.input,cmd+NL); + if (n<=0) { + if (callback) { callback(none); return;} + else return none; + } + str = readFileSync(this.output,'utf8'); + if (callback) callback(str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none); + else return str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none; +}; + +/** GET ROW operation + * @fmt: {}|none + * @callback: function () -> string [] | * [] | none + * + */ +sqlc.prototype.get = function (fmt,callback) { + var row,self=this,repl; + if (!this.connected) return callback?callback(none):none; + function done(_repl) { + repl=_repl?_repl[0]:none; + if (!repl || self.err(repl)) {current.error=repl; row=none; return 0;} + else return 1; + } + this.seq([ + ['exec','get row',function (_repl) { + var cols,i,p; + if (!row) row=[]; + if (done(_repl) && !self.end(repl)) { + repl=Comp.string.replace_all('\n','',repl); + cols=Comp.string.split(',',repl); + if (fmt) { + i=0;row=[]; + for(p in fmt) { + switch (fmt[p]) { + case 'string': + row.push(cols[i]); + break; + case 'number': + row.push(Number(cols[i])); + break; + default: + row.push(cols[i]); + } + i++; + } + } else row=cols; + } + return 1; + }] + ], callback?function (res) { if (res) callback(row); else callback(none);}:wakeup); + if (callback) return; else if (row) return row; else { await(); return row } + +} + +sqlc.prototype.getError = function () { var err=current.error; current.error=undefined; return current.error || 'OK'} + + +/** Setup client-server connection. + * Only the input stream is opened (used for sending data to the SQLD server). + * + */ +sqlc.prototype.init = function (callback) { + var path_in = Comp.printf.sprintf("%sI%s%d",this.path,this.chan<10?'0':'',this.chan), + path_out = Comp.printf.sprintf("%sO%s%d",this.path,this.chan<10?'0':'',this.chan), + stat,repl,self=this; + this.id = Comp.printf.sprintf("[SQLC%s%d]",this.chan<10?'0':'',this.chan); + this.log (Comp.printf.sprintf("%s Connecting to server channel %s...", + this.id,path_in)); + if (!exists(path_in) || !exists(path_out)) { + this.log (Comp.printf.sprintf("%s Connecting to server channel %s failed: %s", + this.id,path_in,'No such file(s)')); + if (callback) {callback(false); return;} + else return false; + } + + try { + this.input = Fs.openSync(path_in,'r+'); + } catch (e) { + this.log (Comp.printf.sprintf("%s Connecting to server channel %s failed: %s", + this.id,path_in,e)); + if (callback) {callback(false); return;} else return false; + } + + // this.input = path_in; + this.output = path_out; + this.connected = true; + return this.seq([ + ['set','nl',function (repl) {return (repl && !self.err(repl[0]))}], + ['set','csv',function (repl) {return (repl && !self.err(repl[0]))}] + ], callback); +} + +/** Insert operation + * + */ +sqlc.prototype.insert = function (tbl,row,callback) { + var repl, + line='', + sep='', + self=this; + if (!this.connected) return callback?callback(false):false; + if (Comp.obj.isArray(row)) + Comp.array.iter(row,function (col,i) {line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';}); + else if (Comp.obj.isObject(row)) + Comp.obj.iter(row,function (attr,key) {line += sep+(Comp.obj.isNumber(attr)?int(attr):"'"+attr+"'"); sep=',';}); + else + throw 'sql.insert: row neither array nor object!'; + + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl;return 0;} + else return 1; + } + return this.seq([ + ['exec','insert into '+tbl+' values ('+line+')', done] + ], callback); +} + +/** Insert a matrix row + * + */ +sqlc.prototype.insertMatrix = function (matname,row,callback) { + var repl, + line='', + sep='', + self=this; + if (this.connected) return callback?callback(false):false; + Comp.array.iter(row,function (col,i) {line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';}); + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl;return 0;} + else return 1; + } + return this.seq([ + ['exec','insert into '+matname+' values ('+line+')', done] + ], callback); +} + +sqlc.prototype.insertTable = sqlc.prototype.insert; + +/** Read a matrix; return [][] + * + */ +sqlc.prototype.readMatrix = function (matname,callback) { + var mat,repl,cols, self=this; + if (!this.connected) return callback?callback(none):none; + + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl; mat=none; return 0;} + else return 1; + } + function doneSelect(_repl,_rows) { + repl=_repl[0]; + if (_rows) { mat=_rows; return 2 }; // Maybe we got already all rows? + if (!repl || self.err(repl)) {current.error=repl; tbl=none; return 0;} + else return 1; + } + + this.seq([ + ['exec','select * from '+matname,doneSelect], + ['exec','get row',function (_repl) { + if (done(_repl)) { + if (!mat) mat=[]; + if (rows) { print('done'); mat=rows; return true /* all done */ }; + if (!self.end(repl)) { + cols=_; + if (typeof repl == 'string') { + repl=Comp.string.replace_all('\n','',repl); + var cols=Comp.array.map(Comp.string.split(',',repl),function (col) { + return Number(col); + }); + } else if (Comp.obj.isArray(repl)) { + // Array + cols=repl; // !!! + } + mat.push(cols); + return -1; + } else return 1; + } else return 0; + }] + ], callback?function (res) { if (res) callback(mat); else callback(none);}:wakeup); + if (callback) return; else if (mat) return mat; else { await(); return mat }; +} + + +/** Read a generic table; return {}[] + * + */ +sqlc.prototype.readTable = function (tblname,callback) { + var tbl,intf=[],repl,cols, self=this; + if (!this.connected) return callback?callback(none):none; + + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl; tbl=none; return false;} + else return true; + } + function doneSelect(_repl,_rows) { + repl=_repl[0]; + // Maybe we got already all rows? + if (_rows) { tbl=_rows; return 2}; + if (!repl || self.err(repl)) {current.error=repl; tbl=none; return 0;} + else return 1; + } + + this.seq([ + // TODO: Check SQLC API; this is only valid for SQLD! + ['get',"select * from sqlite_master where type='table' and name='"+tblname+"'",function (_repl) { + var tokens; + if (done(_repl)) { + if (repl.sql) { + tokens=repl.sql.match(/\((.+)\)/)[1].split(','); + tokens.forEach(function (token) { + var cols=token.split(' '); + if (cols.length==2) { + intf.push({tag:cols[0],type:self.sqlType2native(cols[1])}); + } else intf.push(null); + }); + return !Comp.obj.isEmpty(intf)?1:0; + } + } else return 0; + }], + ['exec','select * from '+tblname,doneSelect], + ['exec','get row',function (_repl) { + var o={}; + if (!tbl) tbl=[]; + if (done(_repl)) { + if (!self.end(repl)) { + cols=_; + if (typeof repl == 'string') { + repl=Comp.string.replace_all('\n','',repl); + repl.split(',').forEach(function (e,i) { + var io=intf[i]; + if (io) o[io.tag]=io.type=='number'?Number(e):e; + }); + } else if (Comp.obj.isArray(repl)) { + // Array + repl.forEach(function (e,i) { + var io=intf[i]; + if (io) o(io.tag)=io.type=='number'?Number(e):e; + }); + } + + tbl.push(o); + return -1; + } else return 1; + } else return 0; + }] + ], callback?function (res) { if (res) callback(tbl); else callback(none);}:wakeup); + if (callback) return; else if (tbl) return tbl; else { await(); return tbl } +} + + +/** SELECT operation + * tbl: string + * vars?: string | string [] + * cond: string + * callback: function () -> status string + * + */ +sqlc.prototype.select = function (tbl,vars,cond,callback) { + var self=this,repl,stat; + function done(_repl) { + repl=_repl?_repl[0]:none; + if (!repl || self.err(repl)) {current.error=repl; return 0;} + else return 1; + } + if (vars==undefined) vars='*'; + stat = this.seq([ + ['exec',Comp.printf.sprintf('select %s from %s%s', + Comp.obj.isArray(vars)?Comp.printf.list(vars):vars, + tbl, + cond?(' '+cond):''),done] + ],callback?callback:wakeup); + if (!callback) await(); + return stat; +} + +/** Execute a SQL operation sequence + ** todo format: [op: string, + ** args?: string, + ** result: function returning boolean (false: break (error), true: next, _:loop)] + ** callback: function () -> status + */ +sqlc.prototype.seq = function (todo,callback) { + var l=todo,self=this,status,res, + next; + if (callback) { // Async non.block. + function Todo() { + if (self.todo.length>0) { + var f = Comp.array.head(self.todo); + self.error=undefined; + f(_,function () { + self.todo = Comp.array.tail(self.todo); + Todo(); + }); + } + } + next=function (loop,finalize) { + if (l.length==0 && !loop) { callback(status); if (finalize) finalize() } + else { + var hd; + if (!loop) { + hd= Comp.array.head(l); + l = Comp.array.tail(l); + } else hd=loop; + switch (hd[0]) { + case 'set': + self.set(hd[1],function (repl) { + status=hd[2](repl); + if (status==1) next(_,finalize); else callback(status); + }); + break; + case 'exec': + self.exec(hd[1],function (repl) { + status=hd[2](repl); + if (status==1) next(_,finalize); + else if (status==-1) next(hd,finalize); + else callback(status); + }); + break; + } + } + } + self.todo.push(next); + if (self.todo.length==1) Todo(); + return; + } else { // Sync block. + next=function (loop) { + if (l.length==0 && !loop) return status; + else { + if (!loop) { + hd= Comp.array.head(l); + l = Comp.array.tail(l); + } else hd=loop; + switch (hd[0]) { + case 'set': + status=self.set(hd[1]); + if (status==1) next(); else return status; + break; + case 'exec': + res=self.exec(hd[1]); + status=hd[2](res); + if (status==1) next(); else if (status==-1) next(hd); else return status; + break; + } + } + } + return next(); + } +}; + +/** Set a SQLD flag (nl,csv,..). + * + */ +sqlc.prototype.set = function (flag,callback) { + var n,fd,str=''; + if (!this.connected) return callback?callback(false):false; + n=Fs.writeSync(this.input, 'set '+flag); + if (n<=0) { + if (callback) { callback(none); return;} + else return none; + } + str = readFileSync(this.output,'utf8'); + if (callback) callback(str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none); + else return str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none; +}; + +sqlc.prototype.sqlType2native = function (str) { + if (str=='integer') return 'number'; + if (str.indexOf('varchar')==0) return 'string'; +} + +/** Write a matrix [][] (create + insert values) + * + */ +sqlc.prototype.writeMatrix = function (matname,matrix,callback) { + var repl, line='', self=this, + intf='', sep='', i=0, row; + if (!this.connected) return callback?callback(false):false; + if (matrix.length==0) return false; + Comp.array.iter(matrix[0],function (col,i) { + intf += sep+'c'+(i+1)+(Comp.obj.isNumber(col)?' integer':' varchar(32)'); sep=','; + }); + row=matrix[0]; + Comp.array.iter(row,function (col,i) { + line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=','; + }); + function done(_repl) { + repl=_repl[0]; + if (!repl || self.err(repl)) {current.error=repl;return 0;} + else return 1; + } + seq=[ + ['exec','drop table if exists '+matname,done], + ['exec','create table '+matname+' ('+intf+')',done] + ]; + for(i=0;i status + */ +sqld.prototype.seq = function (todo,callback) { + var l=todo,self=this,status,res,row,rows,cols, + next; + if (callback) { // Async non.block. + function Todo() { + if (self.todo.length>0) { + var f = Comp.array.head(self.todo); + f(_,function () { + self.todo = Comp.array.tail(self.todo); + Todo(); + }); + } + } + next=function (loop,finalize) { + var p; + if (l.length==0 && !loop) { callback(status); if (finalize) finalize()} + else { + var hd; + if (!loop) { + hd= l.shift(); + } else hd=loop; + switch (hd[0]) { + case 'exec': + if (hd[1]=='get row') { + if (rows && rows.length) { + row=rows.shift(); + cols=[]; + for(p in row) cols.push(row[p]); + status=hd[2]([cols.join(',')]); + } else status=hd[2](['END']); + if (status==1) next(_,finalize); + else if (status==-1) next(hd,finalize); + else callback(status); + } else if (hd[1].indexOf('select') == 0) { + self.db.all(hd[1],[],function (repl,_rows) { + rows=_rows; + if (repl!=null && !Comp.obj.isArray(repl)) repl=[repl]; + if (repl==null) repl=['OK']; + status=hd[2](repl,_rows); + if (status==1) next(_,finalize); + else if (status==-1) next(hd,finalize); + else if (status==2) { l=[]; next(_,finalize) } + else callback(status); + }); + + } else { + self.db.run(hd[1],[],function (repl) { + rows=_rows; + if (repl!=null && !Comp.obj.isArray(repl)) repl=[repl]; + if (repl==null) repl=['OK']; + status=hd[2](repl); + if (status==1) next(_,finalize); + else if (status==-1) next(hd,finalize); + else if (status==2) { l=[]; next(_,finalize) } + else callback(status); + }); + } + break; + case 'get': + self.db.get(hd[1], function (err, table) { + var repl; + if (err) repl=[err]; + else repl=[table]; + status=hd[2](repl); + if (status==1) next(_,finalize); + else if (status==-1) next(hd,finalize); + else callback(status); + + }); + break; + } + } + } + self.todo.push(next); + if (self.todo.length==1) Todo(); + return; + } else { // Sync block., limited usability + next=function (loop) { + if (l.length==0 && !loop) return status; + else { + if (!loop) { + hd=l.shift(); + } else hd=loop; + switch (hd[0]) { + case 'exec': + // w/o callback we get no status immediately! + res='OK'; + self.db.run(hd[1], function (err) { + if (err) self.log(err) + }); + status=hd[2](res); + if (status==1) return next(); + else if (status==-1) return next(hd); + else if (status==2) { l=[]; next() } + else return status; + break; + } + } + } + return next(); + } +}; + +/** SELECT operation returning rows (implicit GET ROW)!!!! + * tbl: string + * vars?: string | string [] + * cond: string + * callback: function () -> status string + * + */ +sqld.prototype.select = function (tbl,vars,cond,callback) { + var self=this,repl,stat,rows; + function done(_repl,_rows) { + repl=_repl?_repl[0]:none; + rows=_rows; + if (!repl || self.err(repl)) {current.error=repl; return 0;} + else return 1; + } + if (vars==undefined) vars='*'; + stat = this.seq([ + ['exec',Comp.printf.sprintf('select %s from %s%s', + Comp.obj.isArray(vars)?Comp.printf.list(vars):vars, + tbl, + cond?(' '+cond):''),done] + ],callback?callback:wakeup); + if (!callback) await(); + return rows||stat; +} + + +sqld.prototype.sqlType2native = sqlc.prototype.sqlType2native; + +/** Write a matrix [][] (create + insert values) + * + */ +sqld.prototype.writeMatrix = sqlc.prototype.writeMatrix; + +var Sqld = function (file,options) { + var obj = new sqld(file,options); + return obj; +}; + + +module.exports = { + current:function (module) { current=module.current; Aios=module; }, + Sqlc:Sqlc, + Sqld:Sqld +}; +}; +BundleModuleCode['shell/shell']=function (module,exports){ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 1-3-18 by sbosse. + ** $VERSION: 1.36.1 + ** + ** $INFO: + ** + ** JAM Shell Interpreter (Back end) + ** + ** Highly customizable command shell interpreter for JAMLIB + ** + ** + ** typeof @options= { + ** echo?: boolean, + ** modules? : { + ** csp? : Require('csp/csp'), + ** csv? : Require('parser/papaparse') + ** db? : + ** des48? : + ** doc? : Require('doc/doc'), + ** http? : Require('http'), + ** https? : Require('https'), + ** httpserv? : Require('http/https'), + ** logic?: Require('logic/prolog'), + ** ml?: Require('ml/ml'), + ** nn?: Require('nn/nn'), + ** numerics?: Require('numerics/numerics'), + ** sat?: Require('sat/sat'), + ** readline? : Require('com/readline'), + ** readlineSync? : Require('term/readlineSync'), + ** sip? : Require('top/rendezvous'), + ** sql? : Require('db/db'), + ** }, + ** nameopts? : {length:8, memorable:true, lowercase:true}, + ** Nameopts? : {length:8, memorable:true, uppercase:true}, + ** output? : function, // AIOS output (for all, except if defined ..) + ** outputAgent? : function, // Agent output + ** outputAsync? : function, // AIOS/generic async output + ** outputPrint? : function, // jamsh print output + ** renderer? : renderer, + ** server? : boolean, + ** } + ** + ** $ENDOFINFO + */ + +Require('os/polyfill'); + +var JamLib = Require('top/jamlib'); +var util = Require('util'); +var Comp = Require('com/compat'); +var Io = Require('com/io'); +var Sat = Require('dos/ext/satelize'); +var Cluster = Require('shell/cluster'); +var Sec = Require('jam/security'); +var Rpc = Require('rpc/rpc'); +var Esprima = Require("parser/esprima"); +var Json = Require('jam/jsonfn'); + +var options = { + verbose : JamLib.environment.verbose||1, + version : '1.36.1', +} + +Cluster.current(JamLib.Aios); + +// Utils +if (typeof print == 'undefined') print=console.log; +DIR = JamLib.Aios.DIR; + +var NET = Require('jam/ampCOM'), + url2addr=NET.url2addr, + addr2url=NET.addr2url; + +function format(line) { + var msg; + switch (typeof line) { + case 'boolean': msg=line.toString(); break; + case 'string': msg=line; break; + case 'number': msg=line.toString(); break; + case 'function': msg=line.toString(); break; + case 'object': msg=Io.inspect(line); break; + default: msg=''; + } + return msg; +} + + + +/** Shell Interpreter Object +* +*/ +function Shell (_options) { + if (!(this instanceof Shell)) return new Shell(_options); + this.options=Comp.obj.extend(options,_options); + this.modules=options.modules||{}; + this.events = {}; + this.env = {}; + this.modules.forEach(function (mod,name) { + switch (name) { + case 'des48': + JamLib.Aios[name]=mod; + break; + case 'ml': + case 'nn': + case 'csp': + case 'sat': + case 'numerics': + mod.current(JamLib.Aios); + JamLib.Aios[name]=mod.agent; + JamLib.Aios.aios1[name]=mod.agent; + JamLib.Aios.aios2[name]=mod.agent; + JamLib.Aios.aios3[name]=mod.agent; + break; + case 'nlp': + case 'logic': + JamLib.Aios[name]=mod; + JamLib.Aios.aios1[name]=mod; + JamLib.Aios.aios2[name]=mod; + JamLib.Aios.aios3[name]=mod; + break; + } + }) + if (!this.options.renderer) { + if (this.modules.doc) this.options.renderer=this.modules.doc.Renderer({lazy:true}); + else + this.options.renderer = function (text) { + return text.replace(/\n:/g,'\n '); + } + } +} + +Shell.prototype.cmd = function () { return this.env } + + +Shell.prototype.emit = function (ev,arg1,arg2,arg3,arg4) { + if (this.events[ev]) this.events[ev](arg1,arg2,arg3,arg4); +} + + +Shell.prototype.help = function() { +return this.options.renderer([ +'# Usage', +' jamsh [-v] [script.js] [--