ndn-js-contrib
Version:
Reusable 'Classes' for Named Data Networking: NameTree, PIT, FIB, ContentStore, Interfaces, and Transports
490 lines (489 loc) • 17.2 kB
JavaScript
var Repository = require("./Repository.js"),
ContentStore = require("./ContentStore.js"),
PIT = require("./PIT.js"),
FIB = require("./FIB.js"),
Strategy = require("./Strategy.js"),
getFileChunks = require("./util/get-file-chunks.js"),
assembleFile = require("./util/assemble-file.js"),
Name = require("ndn-js/js/name.js").Name,
Data = require("ndn-js/js/data.js").Data,
Interest = require("ndn-js/js/interest").Interest,
Tlv = require('ndn-js/js/encoding/tlv/tlv.js').Tlv,
TlvDecoder = require('ndn-js/js/encoding/tlv/tlv-decoder.js').TlvDecoder,
listen = require("./util/server.js"),
connect = require("./util/connect.js"),
defaultKey_Cert = require("./util/default_key_cert.js"),
ControlParameters = require("ndn-js/js/control-parameters.js").ControlParameters,
Manager = require("./Manager");
function makeNonce() {
if (global.crypto && global.crypto.getRandomValues) {
var result = new Uint8Array(5);
crypto.getRandomValues(result);
return result;
} else {
var result = [],
i = 5;
while (i > 0) {
result.push(Math.floor(Math.random * 255));
--i;
}
return result;
}
}
function Node(params) {
this._contentStore = new ContentStore();
this._pit = new PIT();
this._fib = new FIB();
this._strategy = new Strategy();
this._manager = params.manager || new Manager();
this.listen = listen;
this.connect = connect;
this._servers = [];
this._faces = [];
this._routes = [];
this._interest_pool = [];
this._data_pool = [];
this._keyChain = params.keyChain || defaultKey_Cert.keyChain;
this._certificateName = params.certificateName || defaultKey_Cert.certificateName;
}
Node.create = function Node_create(params) {
params = params || {};
var node = new Node(params);
if (params.repo)
return Repository.Open(params.repo).then(function Node_create_Repository_Open(repo) {
node._repository = repo;
return node;
});
else
return Promise.resolve(new Node(params));
};
function chunkIterator(chunks) {
this.curr = 0;
this.size = chunks.length + 0;
this.chunks = chunks;
return this;
}
chunkIterator.prototype.next = function chunkIterator_next() {
var self = this;
var chunkNumber = this.curr;
var done = (self.size === chunkNumber);
this.curr++;
var next = (!done) ? new Promise(function(resolve, reject) {
resolve({
buffer: self.chunks.shift(),
chunkNumber: chunkNumber
});
}) : null;
return {
value: next,
done: done
};
};
Node.getStringChunks = function Node_getStringChunks(string) {
return new Promise(function getStringChunks_Promise(resolve, reject) {
var chunks = {};
chunks.array = [];
chunks.total = 0;
chunks[Symbol.iterator] = function() {
return new chunkIterator(chunks.array);
};
while (string.length > 0) {
chunks.array.push(string.substr(0, 8000));
string = string.substr(8000, string.length);
chunks.total++;
}
resolve(chunks);
});
};
Node.getBufferChunks = function getBufferChunks(buffer) {
return new Promise(function getBufferChunks_Promise(resolve, reject) {
var chunks = [];
var i = 0;
chunks.total = Math.ceil((buffer.length / 8000));
chunks[Symbol.iterator] = function() {
return new chunkIterator(chunks);
};
while (i * 8000 < buffer.length) {
chunks.push(buffer.slice(i * 8000, (i + 1) * 8000));
i++;
}
resolve(chunks);
});
};
Node.getFileChunks = getFileChunks;
Node.assemble = function Node_assemble(datas) {
var meta = JSON.parse(datas.shift().content.toString());
if (meta.type === "string" || meta.type === "json") {
var str = "";
while (datas.length)
str += datas.shift().content.toString();
if (meta.type === "json")
str = JSON.parse(str);
return str;
} else if (meta.type === "buffer") {
return Buffer.concat(datas.map(function(data) {
return data.content;
}));
} else if (meta.type === "file") {
return assembleFile(datas.map(function(data) {
return data.content;
}), meta);
}
};
Node.prototype.onData = function Node_onData(data, face) {
var self = this;
return self._pit.lookup(data, face).then(function Node_onData_pitResults(faceArray) {
for (var i in faceArray)
faceArray[i].putData(data);
return self._contentStore.insert(data);
}).then(function Node_onContentStoreDataEvicted(data) {
return data;
}).catch(function(err) {
return data;
});
};
Node.prototype.setCommandInfo = function(face) {
face.setCommandSigningInfo(this._keyChain, this._certificateName);
};
Node.prototype.registerPrefix = function(prefix, remoteFace) {
var self = this;
if (remoteFace.commandKeyChain == null)
return Promise.reject(new Error("registerPrefix: The command KeyChain has not been set. You must call setCommandSigningInfo."));
if (remoteFace.commandCertificateName.size() == 0)
return Promise.reject(new Error("registerPrefix: The command certificate name has not been set. You must call setCommandSigningInfo."));
var controlParameters = new ControlParameters();
controlParameters.setName(prefix);
var commandInterest = new Interest();
commandInterest.setName(new Name("/localhop/nfd/rib/register"));
commandInterest.setInterestLifetimeMilliseconds(4000.0);
commandInterest.getName().append(controlParameters.wireEncode());
remoteFace.nodeMakeCommandInterest(commandInterest, remoteFace.commandKeyChain, remoteFace.commandCertificateName);
var promise = this._pit.insert(commandInterest).then(function(res) {
self.recycleInterest(commandInterest);
return remoteFace;
}).catch(function(er) {
throw new Error("timeout in registration command Interest");
});
remoteFace.putData(commandInterest);
return promise;
};
Node.ERRORS = {
NO_ROUTES: 1,
NO_PIT: 2,
INTEREST_TIMEOUT: 3
};
Node.prototype.expressInterest = function Node_expressInterest(interest) {
var t = Date.now();
if (!interest.getNonceAsBuffer())
interest.setNonce(makeNonce());
return Node_fulfillInterest.call(this, interest, {putData: function(data) {
return {
data: data,
face: "local",
rtt: Date.now() - t
};
}}).catch(Node_forwardInterest);
};
function repository_lookup(interest) {
if (this._repository)
return this._repository.lookup(interest);
else
return Promise.reject(interest);
}
function Node_fulfillInterest(interest, face) {
var node = this;
return this._contentStore.lookup(interest).catch(repository_lookup.bind(this)).then(face.putData.bind(face)).catch(function(er) {
return Promise.reject({
interest: interest,
face: face,
node: node
});
});
}
function forwardInterest_with_strategy(results) {
var face = results.pop(),
node = results.pop(),
interest = results.pop(),
strategy = results.pop(),
nextHops = results.pop(),
choices = strategy.choose(nextHops);
for (var i in choices) {
choices[i].face.putData(interest);
}
return node._pit.insert(interest, face).then(function onData(res) {
res.face = face;
strategy.log(choices, res);
return res;
}).catch(function onTimeout(res) {
strategy.log(choices, null);
var er = new Error("Interest Timeout: " + interest.toUri());
er.code = Node.ERRORS.INTEREST_TIMEOUT;
return Promise.reject(er);
});
}
function Node_forwardInterest(request) {
var node = request.node,
interest = request.interest,
face = request.face;
return node._fib.lookup(interest).then(function(nexthops) {
return Promise.all([Promise.resolve(nexthops), node._strategy.lookup(interest), Promise.resolve(interest), Promise.resolve(node), Promise.resolve(face)]);
}).catch(function(er) {
node._pit.insert(interest, face).then(function() {}).catch(function() {});
var error = new Error("ForwardInterest, no FIB Entries: " + interest.toUri());
error.code = Node.ERRORS.NO_ROUTES;
return Promise.reject(er);
}).then(forwardInterest_with_strategy);
}
;
Node.prototype.add_nexthop = function Node_add_nexthop(interest, face) {
var commandComponent = interest.name.get(4);
var controlParameters = new ControlParameters();
controlParameters.wireDecode(commandComponent.getValueAsBuffer());
return this._fib.insert(controlParameters.name, face).then(function() {
face.registeredPrefixTable.push(controlParameters.name);
var ack = new Data(interest.name);
ack.setContent("SUCCESS");
face.putData(ack);
return interest;
});
};
Node.prototype.announce = function Node_announce(prefix) {
this._routes.push(prefix);
var faces = this._faces;
var proms = [];
for (var i in faces) {
proms.push(this.registerPrefix(prefix, faces[i]));
}
return Promise.all(proms);
};
Node.prototype.peer_aware = function Node_peer_aware(interest, face) {};
Node.prototype.onInterest = function Node_onInterest(interest, face) {
var self = this,
intercept;
if (intercept = this._manager.intercept(interest, face))
return this[intercept](interest, face);
else
return Node_fulfillInterest.call(this, interest, face).catch(Node_forwardInterest.bind(this)).then(function Node_onInterest_forward_Response(res) {
console.log("interest response");
return interest;
}).catch(function Node_onInterest_forward_Timeout(message) {
console.log("interest timeout");
return interest;
});
};
Node.prototype.putData = function Node_putData(data, store) {
var self = this;
store = store || this._contentStore;
return Promise.all([store.insert(data).then(function(wrap) {
return wrap[0];
}), self._pit.lookup(data).then(function Node_putPacket_PIT_Hit(faces) {
for (var i in faces)
faces[i].putData(data);
return true;
}).catch(function Node_putPacket_PIT_Miss(er) {
return false;
})]);
};
Node.prototype.put = function Node_put(param, store) {
var self = this,
store = store || this._contentStore,
type = param.type,
data = param.data,
versioned = param.versioned,
prefix = new Name(param.prefix),
freshnessPeriod = param.freshnessPeriod;
return new Promise(function Node_put_Promise(resolve, reject) {
if (!(type && data && param.prefix))
return reject(new Error("Node.put(param): param must include type, data, and prefix fields"));
if (versioned)
prefix.appendVersion(Date.now());
var chunkify;
if (type === "json") {
chunkify = Node.getStringChunks(JSON.stringify(data));
} else if (type === "string") {
chunkify = Node.getStringChunks(data);
} else if (type === "file") {
chunkify = Node.getFileChunks(data);
} else if (type === "buffer") {
chunkify = Node.getBufferChunks(data);
} else {
return reject(new Error("Node.put(param): param.type must be json, string, file, or buffer"));
}
chunkify.then(function Node_put_processChunks(chunks) {
var name = new Name(prefix);
name.appendSegment(0);
if (param.data.type) {
param.mime = param.data.type;
param.name = param.data.name;
param.lastModified = param.data.lastModified;
}
delete param.data;
var data0 = new Data(name, JSON.stringify(param));
data0.getMetaInfo().setFreshnessPeriod(freshnessPeriod);
data0.getMetaInfo().setFinalBlockID(Name.Component.fromNumberWithMarker(chunks.total, 0x00));
var proms = [self.putData(data0, store)];
var $__3 = true;
var $__4 = false;
var $__5 = undefined;
try {
for (var $__1 = void 0,
$__0 = (chunks)[$traceurRuntime.toProperty(Symbol.iterator)](); !($__3 = ($__1 = $__0.next()).done); $__3 = true) {
var chunk = $__1.value;
{
proms.push(chunk.then(function onChunk(chunk) {
var name = new Name(prefix);
name.appendSegment(chunk.chunkNumber + 1);
var data = new Data(name, chunk.buffer);
return self.putData(data, store);
}));
}
}
} catch ($__6) {
$__4 = true;
$__5 = $__6;
} finally {
try {
if (!$__3 && $__0.return != null) {
$__0.return();
}
} finally {
if ($__4) {
throw $__5;
}
}
}
Promise.all(proms).then(function Node_put_Promise_Resolve(puts) {
resolve(puts);
}).catch(function Node_put_Promise_Reject(err) {
reject(err);
});
});
});
};
Node.prototype.getMaximumPacketSendTime = function Node_getMaximumPacketSendTime() {
return undefined;
};
Node.prototype.pipelineFetch = function Node_pipelineFetch(params) {
var name = params.prefix.append("prototype");
var pipe = [];
var numberOfPackets = params.finalBlock + 1;
var millisecondsPerPacket = this.getMaximumPacketSendTime() || 200;
var timeToExpectedLastPacket = (millisecondsPerPacket * numberOfPackets) + params.rtt;
var proms = [];
for (var i = 0; i < numberOfPackets; i++) {
pipe[i] = new Interest(name.getPrefix(-1).appendSegment(i));
pipe[i].setInterestLifetimeMilliseconds(timeToExpectedLastPacket);
pipe[i].setMustBeFresh(params.mustBeFresh);
proms.push(this.expressInterest(pipe[i]).then(function(response) {
return response.data;
}).catch(function(er) {}));
}
return Promise.all(proms);
};
Node.prototype.fetch = function Node_fetch(params) {
var prefix = new Name(params.prefix),
versioned = params.versioned,
chained = params.chained,
onProgress = params.onProgress,
self = this,
mustBeFresh = params.mustBeFresh || true;
var firstInterest = new Interest(prefix);
firstInterest.setInterestLifetimeMilliseconds(4000);
var minSuffix = (versioned) ? 3 : 2;
var maxSuffix = minSuffix;
var childSelector = (versioned) ? 1 : 0;
firstInterest.setChildSelector(childSelector);
firstInterest.setMustBeFresh(mustBeFresh);
return self.expressInterest(firstInterest).then(function(response) {
return self.pipelineFetch({
prefix: response.data.name.getPrefix(-1),
rtt: response.rtt,
mustBeFresh: mustBeFresh,
finalBlock: response.data.getMetaInfo().getFinalBlockID().toNumberWithMarker(0x00)
});
});
};
Node.prototype.get = function Node_get(params) {
return this.fetch(params).then(function(datas) {
return Node.assemble(datas);
});
};
Node.prototype.store = function Node_store(params) {
return this.put(params, this._repository);
};
Node.prototype.steward = function Node_steward(params) {
var self = this;
return this.fetch(params).then(function(datas) {
var proms = [];
for (var i in datas)
proms.push(self._repository.insert(datas[i]));
return Promise.all(proms);
});
};
Node.prototype.getRemotes = function Node_getRemotes() {};
Node.prototype.getProfile = function Node_getProfile() {};
Node.prototype.setProfile = function Node_setProfile() {};
Node.prototype.advertiseRoute = function Node_advertiseRoute() {};
Node.prototype.createInterest = function Node_createInterest(element) {
var interest = this._interest_pool.pop() || new Interest();
interest.wireDecode(element);
return interest;
};
Node.prototype.recycleInterest = function Node_recycleInterest(interest) {
this._interest_pool.push(interest);
};
Node.prototype.createData = function Node_createData(element) {
var data = this._data_pool.pop() || new Data();
data.wireDecode(element);
return data;
};
Node.prototype.recycleData = function Node_recycleData(data) {
this._data_pool.push(data);
};
Node.prototype.onReceivedElement = function Node_onRecievedElement(element, face) {
var self = this;
if (element[0] == Tlv.Interest || element[0] == Tlv.Data) {
var decoder = new TlvDecoder(element);
if (decoder.peekType(Tlv.Interest, element.length)) {
var interest = this.createInterest(element);
this.onInterest(interest, face).then(function(ret) {
self.recycleInterest(ret);
});
} else if (decoder.peekType(Tlv.Data, element.length)) {
var data = self.createData(element);
this.onData(data, face).then(function() {
self.recycleData(data);
});
}
}
};
Node.prototype.drop = function drop() {};
Node.prototype.onPeerConnect = function Node_onPeerConnect(face, meta) {};
Node.prototype.onFace = function Node_onInboundFace(face) {
var self = this;
face.onclose = function() {
self.onFaceClose(face);
};
this.setCommandInfo(face);
face.set_onReceivedElement(this.onReceivedElement.bind(this));
var self = this;
this._fib.insert(new Name("localhop"), face);
this._faces.push(face);
var routes = this._routes;
var proms = [];
for (var i in routes)
proms.push(self.registerPrefix(routes[i], face));
return Promise.all(proms);
};
Node.prototype.onFaceClose = function Node_onFaceClose(face) {
this._fib.remove(face);
var faces = this._faces;
for (var i in faces) {
if (faces[i] === face)
return faces.splice(i, 1);
}
};
Node.prototype.onPeer = function Node_onPeer(face, peeringInfo) {
console.log("stuff", peeringInfo, face);
};
module.exports = Node;