#!/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] [--