/*
* 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 w = 800;
var h = 800;
var fill = d3.scale.category10()
var DEBUGGING = false;
/** True if we don't want to map nodes with 0 reach because they might not actually be reachable. */
var EXCLUDE_ZERO_NODES = false;
var vis = d3.select("#viz").append("svg").attr("width", w).attr("height", h);
var uniqueNodes;
var pairs;
var thisNode;
var force;
var doRequest = function(query, callback) {
var url = '/api/?q=' + encodeURIComponent(bencode(query));
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", url, true);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState==4) {
callback(bdecode(xmlhttp.responseText));
}
};
xmlhttp.send(null);
};
var getData = function()
{
var log2 = function(number)
{
var out = 0;
while ((number >>= 1) > 0) {
out++;
}
return out;
};
// return true if a runs through b
var runsThrough = function(a, b)
{
var mask = log2(b);
return (a & mask) == (b & mask);
};
var buildBranch = function(parsedRoutes, indexOfTip)
{
var branch = [];
branch.push(parsedRoutes[indexOfTip]);
var path = parsedRoutes[indexOfTip].path;
for (var i = indexOfTip + 1; i < parsedRoutes.length; i++) {
if (runsThrough(path, parsedRoutes[i].path)) {
branch.push(parsedRoutes[i]);
path = parsedRoutes[i].path;
}
}
return branch;
}
var buildTree = function(parsedRoutes)
{
var tree = [];
for (var i = 0; i < parsedRoutes.length; i++) {
tree.push({
value: parsedRoutes[i].link,
branch: buildBranch(parsedRoutes, i)
});
}
return tree;
};
var parsePath = function(path) {
return new Number('0x' + path.replace(/\./g, ''));
};
var parseRoutes = function(list)
{
var out = [];
for (var i = 0; i < list.length; i++) {
if (EXCLUDE_ZERO_NODES && list[i].link == 0) {
continue;
}
out.push({
name: list[i].ip,
path: parsePath(list[i].path),
link: list[i].link
});
}
out.sort(function(a, b) { return b.path - a.path });
return out;
}
/**
* @param routes the full list of nodes.
* @param nodesByIp an empty hash which will be populated with nodes.
* @return a list of unique nodes.
*/
var getUniqueNodes = function(routes, nodesByIp)
{
var out = [];
for (var i = 0; i < routes.length; i++) {
if (!nodesByIp[routes[i].name]) {
out.push(nodesByIp[routes[i].name] = routes[i]);
}
}
return out;
};
var getPairs = function(tree, nodesByIp)
{
var out = [];
var pairs = {};
for (var i = 0; i < tree.length; i++) {
var branch = tree[i];
for (var j = 0; j < branch.branch.length - 1; j++) {
var nodes = [branch.branch[j], branch.branch[j + 1]];
if (nodes[0].name == nodes[1].name) {
continue;
}
nodes.sort(function(a, b) { return a.name.localeCompare(b.name); });
var p1 = pairs[nodes[0].name];
if (!p1) {
p1 = pairs[nodes[0].name] = {};
}
if (!p1[nodes[1].name]) {
var link = {
source: nodesByIp[nodes[0].name],
target: nodesByIp[nodes[1].name],
value: new Number(branch.value)
};
p1[nodes[1].name] = link;
out.push(link);
} else {
p1[nodes[1].name].value += new Number(branch.value);
}
}
}
return out;
};
var genNodeList = function(table) {
var out = '
';
out += ''
for (var i = 0; i < table.length; i++) {
if (i % 2 == 0)
out += '';
else
out += '
';
out += '' + table[i].ip + ' | ';
var p = table[i].path;
out += ''+p+' (ping) | ';
out += '' + table[i].link + ' ';
out += ' |
'
}
return out;
};
var getTable = function(firstRun, more, start) {
if (start && start > 20000) {
// sanity limit
return;
}
var q = {"q": "NodeStore_dumpTable"};
if (start) {
q.start = start;
}
doRequest(q, function(responseJSON) {
var table = responseJSON.routingTable;
if (more) {
for (var i = 0; i < more.routingTable.length; i++) {
table.push(more.routingTable[i]);
}
}
if (responseJSON.more == 1) {
getTable(firstRun, responseJSON, ((start) ? start : 0) + 500);
return;
}
table.sort(function(a, b) {
if (b.link == a.link) {
return parsePath(a.path) - parsePath(b.path);
}
return b.link - a.link;
});
document.getElementById('nodes-list').innerHTML = genNodeList(table);
var routes = parseRoutes(table);
var tree = buildTree(routes);
var nodeByIp = {};
uniqueNodes = getUniqueNodes(routes, nodeByIp);
thisNode = uniqueNodes[uniqueNodes.length - 1];
thisNode.group = 0;
pairs = getPairs(tree, nodeByIp);
if (firstRun) {
doLayout(1);
} else {
force.start();
}
});
//setTimeout(function() { getTable(false) }, 10000);
};
getTable(1);
var checkMemory = function() {
doRequest({"q":"memory"}, function(responseJSON) {
document.getElementById('memory-bytes').innerText = responseJSON.bytes;
});
setTimeout(checkMemory, 4000);
}
checkMemory();
};
var doAuthedRequest = function(request, password, callback)
{
doRequest({"q":"cookie"}, function(cookieJSON) {
var hashA = SHA256_hash('' + password + cookieJSON.cookie);
request.cookie = cookieJSON.cookie;
request.hash = hashA;
request.aq = request.q;
request.q = 'auth';
var reqStr = bencode(request);
var hashB = SHA256_hash(reqStr);
request.hash = hashB
doRequest(request, callback);
});
};
window.ping = function(node) {
var cmdInput = document.getElementById('runcmd-cmd');
cmdInput.value = '{"q":"RouterModule_pingNode", "args":{"path":"' + node + '"}}';
};
var setupSubmitEvent = function() {
document.removeEventListener('DOMContentLoaded', setupSubmitEvent, false);
document.getElementById('runcmd-submit').addEventListener('mousedown', function(ev) {
ev.stopPropagation();
var cmdInput = document.getElementById('runcmd-cmd').value;
var cmdPasswd = document.getElementById('runcmd-passwd').value;
var output = document.getElementById('runcmd-output');
output.innerText = '';
var query = null;
try {
query = JSON.parse(cmdInput);
} catch (e) {
output.innerText = "failed to parse input\n\n" + JSON.stringify(e);
return;
}
doAuthedRequest(query, cmdPasswd, function(response) {
output.innerText = JSON.stringify(response);
});
}, false);
};
document.addEventListener("DOMContentLoaded", setupSubmitEvent, false);
var doLayout = function(step)
{
if (!step) {
getData();
return;
}
force = d3.layout.force()
.charge(-400)
.linkDistance(100)//function(link){return 100 / (link.value + 1)})
.nodes(uniqueNodes)
.links(pairs)
.size([w, h])
.start();
var link = vis.selectAll("line.link")
.data(pairs)
.enter()
.append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value) / 300000; })
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
var node = vis.selectAll("circle.node")
.data(uniqueNodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return Math.max(3, Math.min(Math.sqrt(d.link) * 1/4000, 8)) })
.style("fill", function(d) { return fill(d.group); })
.call(force.drag);
node.append("title").text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
}