diff --git a/contrib/nodejs/tools/cexec.js b/contrib/nodejs/tools/cexec.js
new file mode 100755
index 000000000..ff7a1140c
--- /dev/null
+++ b/contrib/nodejs/tools/cexec.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+var Cjdns = require('../cjdnsadmin/cjdnsadmin');
+
+Cjdns.connectWithAdminInfo(function (cjdns) {
+ var code = process.argv[process.argv.length-1].replace(/\).*$/, ',cb);');
+ code = code.replace('(,cb);', '(cb);');
+ var f = new Function('x', 'cb', 'x.' + code);
+ f(cjdns, function (err, ret) {
+ if (err) { throw err; }
+ console.log(JSON.stringify(ret, null, ' '));
+ cjdns.disconnect();
+ });
+});
diff --git a/contrib/nodejs/tools/dumptable.js b/contrib/nodejs/tools/dumptable.js
new file mode 100755
index 000000000..3a64a6fc9
--- /dev/null
+++ b/contrib/nodejs/tools/dumptable.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+var Cjdns = require('../cjdnsadmin/cjdnsadmin');
+
+Cjdns.connectWithAdminInfo(function (cjdns) {
+
+ var again = function (i) {
+ cjdns.NodeStore_dumpTable(i, function (err, table) {
+ if (err) { throw err; }
+ var j;
+ for (j = 0; j < table.routingTable.length; j++) {
+ var r = table.routingTable[j];
+ console.log(r['ip'] + ' ' + r['path'] + ' ' + r['link'] + ' ' + r['version']);
+ }
+ if (j) {
+ again(i+1);
+ } else {
+ console.log(i + ' nodes');
+ cjdns.disconnect();
+ }
+ });
+ };
+ again(0);
+
+});
diff --git a/contrib/nodejs/tools/getLinks.js b/contrib/nodejs/tools/getLinks.js
new file mode 100755
index 000000000..2e0e6bd71
--- /dev/null
+++ b/contrib/nodejs/tools/getLinks.js
@@ -0,0 +1,241 @@
+#!/usr/bin/env node
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+var Cjdns = require('../cjdnsadmin/cjdnsadmin');
+var nThen = require('../cjdnsadmin/nthen');
+var PublicToIp6 = require('./lib/publicToIp6');
+
+var NodeStore_getNode = function(cjdns, state, addr, callback) {
+ if (typeof(state['NodeStore_getNode' + addr]) !== 'undefined') {
+ callback(state['NodeStore_getNode' + addr]);
+ } else {
+ //console.log('NodeStore_getNode(' + addr + ');');
+ cjdns.NodeStore_getNode(addr, function (err, ret) {
+ if (err) { throw err; }
+ state['NodeStore_getNode' + addr] = ret;
+ callback(ret);
+ });
+ }
+};
+
+var NodeStore_getRouteLabel = function(cjdns, state, parentPath, childAddr, callback) {
+ var key = 'NodeStore_getRouteLabel' + parentPath + ', ' + childAddr;
+ if (typeof(state[key]) !== 'undefined') {
+ callback(state[key]);
+ } else {
+ //console.log('NodeStore_getRouteLabel(' + parentPath + ', ' + childAddr + ');');
+ cjdns.NodeStore_getRouteLabel(parentPath, childAddr, function (err, ret) {
+ if (err) { throw err; }
+ state[key] = ret;
+ callback(ret);
+ });
+ }
+};
+
+var NodeStore_getLink = function (cjdns, state, addr, num, callback) {
+ var key = 'NodeStore_getLink' + addr + ', ' + num;
+ if (typeof(state[key]) !== 'undefined') {
+ callback(state[key]);
+ } else {
+ //console.log('NodeStore_getLink(' + addr + ', ' + num + ');');
+ cjdns.NodeStore_getLink(addr, num, function (err, ret) {
+ if (err) { throw err; }
+ state[key] = ret;
+ callback(ret);
+ });
+ }
+};
+
+var getNode = function (cjdns, next, output, state, parentPath, ipsByReach, nodes, callback) {
+
+ if (next.parent === next.child || nodes.indexOf(next.child) > -1) { process.nextTick(callback); return; }
+ nodes.push(next.child);
+
+ var getNodeRet;
+ var path = undefined;
+ nThen(function (waitFor) {
+
+ NodeStore_getNode(cjdns, state, next.child, waitFor(function (ret) {
+ //console.log('cjdns.NodeStore_getNode(' + next.child + '); --> ' + JSON.stringify(ret, null, ' '));
+ getNodeRet = ret;
+ }));
+
+ }).nThen(function (waitFor) {
+
+ if (!parentPath) {
+ return;
+ }
+
+ NodeStore_getRouteLabel(cjdns, state, parentPath, next.child, waitFor(function (ret) {
+ if (ret.error !== 'none') {
+ throw new Error('cjdns.NodeStore_getRouteLabel(' + parentPath + ', ' + next.child +
+ '); --> ' + JSON.stringify([ret, parents], null, ' '));
+ }
+ if (ret.result !== 'ffff.ffff.ffff.ffff') {
+ path = ret.result;
+ }
+ }));
+
+ }).nThen(function (waitFor) {
+
+ //console.log(spaces + next.child + ' ' + next.cannonicalLabel + " -> " + path);
+ // if next.parent skips the bootstrap route
+ if (next.parent) {
+ var out = {};
+ output.push(out);
+ output = out;
+ }
+ if (output.peers) { /* sanity check */ throw new Error(); }
+ output.addr = next.child;
+ output.cannonicalLabel = next.cannonicalLabel;
+ output.fullPath = path;
+ output.peers = [];
+
+ if (!path) { return; }
+
+ var links = [];
+ nThen(function (waitFor) {
+
+ for (var i = 0; i < getNodeRet.result.linkCount; i++) {
+ NodeStore_getLink(cjdns, state, next.child, i, waitFor(function (ret) {
+ links.push(ret);
+ }));
+ }
+
+ }).nThen(function (waitFor) {
+
+ //console.log(JSON.stringify(links, null, ' '));
+ links.sort(function (a,b) {
+ return (ipsByReach.indexOf(a.result.child) < ipsByReach.indexOf(b.result.child)) ?
+ -1 : 1;
+ });
+ //console.log(JSON.stringify(links, null, ' '));
+ //console.log(JSON.stringify(ipsByReach, null, ' '));
+ for (var i = 0; i < links.length; i++) {
+ getNode(cjdns, links[i].result, output.peers, state, path, ipsByReach, nodes, waitFor());
+ }
+
+ }).nThen(waitFor());
+
+ }).nThen(function (waitFor) {
+
+ callback();
+
+ });
+};
+
+var dumpOldTable = function (cjdns, callback) {
+ var output = [];
+ var again = function (i) {
+ cjdns.NodeStore_dumpTable(i, function (err, table) {
+ if (err) { throw err; }
+ var j;
+ for (j = 0; j < table.routingTable.length; j++) {
+ var r = table.routingTable[j];
+ output.push(r);
+ }
+ if (j) {
+ again(i+1);
+ } else {
+ callback(output);
+ }
+ });
+ };
+ again(0);
+};
+
+var ipsByReachDesc = function (cjdns, callback) {
+ dumpOldTable(cjdns, function (oldTable) {
+
+ oldTable.sort(function (a, b) {
+ if (a.ip !== b.ip) { return (a.ip > b.ip) ? 1 : -1; }
+ if (a.link !== b.link) { return (Number(a.link) < Number(b.link)) ? 1 : -1; }
+ if (a.path !== b.path) { return (a.path > b.path) ? 1 : -1; }
+ throw new Error("dupe entry");
+ });
+ var bestReaches = [];
+ var last;
+ for (var i = 0; i < oldTable.length; i++) {
+ var r = oldTable[i];
+ if (last !== r.ip) {
+ bestReaches.push({ip:r.ip, link:r.link});
+ last = r.ip;
+ }
+ }
+ bestReaches.sort(function (a, b) { return (a.link > b.link) ? 1 : -1; });
+ var out = [];
+ for (var i = 0; i < bestReaches.length; i++) {
+ out.push(bestReaches[i].ip);
+ }
+ callback(out);
+ //bestReaches.forEach(function (node) { console.log(node.ip + ' ' + node.link); });
+ });
+};
+
+var getTree = function (cjdns, callback) {
+ ipsByReachDesc(cjdns, function (ipsByReach) {
+
+ cjdns.NodeStore_getNode(undefined, function (err, ret) {
+ if (err) { throw err; }
+ var myIp6 = PublicToIp6.convert(ret['result']['key']);
+ var output = {};
+ var selfRoute = '0000.0000.0000.0001';
+ var initialNode = { child: myIp6, cannonicalLabel: selfRoute };
+ getNode(cjdns, initialNode, output, {}, selfRoute, ipsByReach, [], function () {
+ callback(output);
+ });
+ });
+ });
+};
+
+var printTree = function (cjdns, tree, callback) {
+ var pt = function (tree, spaces, callback) {
+ var nt = nThen(function (waitFor) {
+ process.stdout.write(spaces + tree.addr + ' ' + tree.cannonicalLabel + ' --> ' + tree.fullPath);
+ if (tree.fullPath === '0000.0000.0000.0001') { return; }
+ cjdns.RouterModule_pingNode(tree.fullPath, waitFor(function (err, ret) {
+ if (err) { throw err; }
+ var resp = (ret.result !== 'pong') ? "[" + ret.error + "]" : (ret.ms + 'ms.');
+ process.stdout.write(' rp:' + resp);
+ }));
+ }).nThen(function (waitFor) {
+ cjdns.SwitchPinger_ping(tree.fullPath, waitFor(function (err, ret) {
+ if (err) { throw err; }
+ var resp = (ret.result !== 'pong') ? ret.error : (ret.ms + 'ms.');
+ process.stdout.write(' sp:' + resp + '\n');
+ }));
+ }).nThen;
+
+ tree.peers.forEach(function (peer) {
+ nt = nt(function (waitFor) {
+ pt(peer, ' ' + spaces, waitFor());
+ }).nThen;
+ });
+
+ nt(callback);
+ };
+ pt(tree, '', callback);
+};
+
+Cjdns.connectWithAdminInfo(function (cjdns) {
+
+ getTree(cjdns, function (output) {
+ //console.log(JSON.stringify(output, null, ' '));
+ printTree(cjdns, output, function() {
+ cjdns.disconnect();
+ });
+ });
+
+});
diff --git a/contrib/nodejs/tools/lib/publicToIp6.js b/contrib/nodejs/tools/lib/publicToIp6.js
new file mode 100644
index 000000000..5cdaac9f3
--- /dev/null
+++ b/contrib/nodejs/tools/lib/publicToIp6.js
@@ -0,0 +1,75 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+var Crypto = require('crypto');
+
+var numForAscii = [
+ 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,
+ 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,
+ 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,99,99,99,99,99,99,
+ 99,99,10,11,12,99,13,14,15,99,16,17,18,19,20,99,
+ 21,22,23,24,25,26,27,28,29,30,31,99,99,99,99,99,
+ 99,99,10,11,12,99,13,14,15,99,16,17,18,19,20,99,
+ 21,22,23,24,25,26,27,28,29,30,31,99,99,99,99,99,
+];
+
+// see util/Base32.h
+var Base32_decode = function (input) {
+ var output = [];
+ var outputIndex = 0;
+ var inputIndex = 0;
+ var nextByte = 0;
+ var bits = 0;
+
+ while (inputIndex < input.length) {
+ var o = input.charCodeAt(inputIndex);
+ if (o & 0x80) { throw new Error(); }
+ var b = numForAscii[o];
+ inputIndex++;
+ if (b > 31) { throw new Error("bad character " + input[inputIndex] + " in " + input); }
+
+ nextByte |= (b << bits);
+ bits += 5;
+
+ if (bits >= 8) {
+ output[outputIndex] = nextByte & 0xff;
+ outputIndex++;
+ bits -= 8;
+ nextByte >>= 8;
+ }
+ }
+
+ if (bits >= 5 || nextByte) {
+ throw new Error("bits is " + bits + " and nextByte is " + nextByte);
+ }
+
+ return new Buffer(output);
+};
+
+var convert = module.exports.convert = function (pubKey) {
+ if (pubKey.substring(pubKey.length-2) !== ".k") { throw new Error("key does not end with .k"); }
+ keyBytes = Base32_decode(pubKey.substring(0, pubKey.length-2));
+ var hashOneBuff = new Buffer(Crypto.createHash('sha512').update(keyBytes).digest('hex'), 'hex');
+ var hashTwo = Crypto.createHash('sha512').update(hashOneBuff).digest('hex');
+ var first16 = hashTwo.substring(0,32);
+ var out = [];
+ for (var i = 0; i < 8; i++) {
+ out.push(first16.substring(i*4, i*4+4));
+ }
+ return out.join(':');
+};
+
+//console.log(convert('rjndc8rvg194ddf2j5v679cfjcpmsmhv8p022q3lvpym21cqwyh0.k'));
diff --git a/contrib/nodejs/tools/peerStats.js b/contrib/nodejs/tools/peerStats.js
new file mode 100755
index 000000000..1b412ad91
--- /dev/null
+++ b/contrib/nodejs/tools/peerStats.js
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+var Cjdns = require('../cjdnsadmin/cjdnsadmin');
+var PublicToIp6 = require('./lib/publicToIp6');
+
+Cjdns.connectWithAdminInfo(function (cjdns) {
+
+ var again = function (i) {
+ cjdns.InterfaceController_peerStats(i, function (err, ret) {
+ if (err) { throw err; }
+ ret.peers.forEach(function (peer) {
+ p = (PublicToIp6.convert(peer['publicKey']) + '\t' + peer['switchLabel'] +
+ '\tin ' + peer['bytesIn'] + '\tout ' + peer['bytesOut'] + '\t' +
+ peer['state'] +
+ '\tdup ' + peer['duplicates'] +
+ ' los ' + peer['lostPackets'] +
+ ' oor ' + peer['receivedOutOfRange']);
+ if (typeof(peer.user) === 'string') {
+ p += ' "' + peer['user'] + '"';
+ }
+ console.log(p);
+ });
+ if (typeof(ret.more) !== 'undefined') {
+ again(i+1);
+ } else {
+ cjdns.disconnect();
+ }
+ });
+ };
+ again(0);
+
+});