ndn-js-contrib
Version:
Reusable 'Classes' for Named Data Networking: NameTree, PIT, FIB, ContentStore, Interfaces, and Transports
819 lines (674 loc) • 23.8 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
}
}
//http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
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._id = guid();
this.listen = listen;
this.connect = connect;
this._servers = [];
this._faces = [];
this._routes = [];
this._handlers = {};
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){
//console.log("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){
console.log("putting data found via pitmatch")
for (var i in faceArray)
faceArray[i].putData(data);
return self._contentStore.insert(data);
})
.then(function Node_onContentStoreDataEvicted(data){
return data;
})
.catch(function(err){
//console.log("no pit records for that data")
return data;
})
}
Node.prototype.setCommandInfo = function (face){
face.setCommandSigningInfo(this._keyChain, this._certificateName);
}
Node.prototype.registerPrefix = function (prefix, remoteFace){
var self = this;
//console.log("really begin", remoteFace.commandKeyChain, remoteFace.commandCertificateName.size())
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."));
//console.log("begin")
var controlParameters = new ControlParameters();
controlParameters.setName(prefix);
var commandInterest = new Interest();
commandInterest.setName(new Name("/localhop/nfd/rib/register"));
// The host is remote, so set a longer timeout.
commandInterest.setInterestLifetimeMilliseconds(4000.0);
// NFD only accepts TlvWireFormat packets.
commandInterest.getName().append
(controlParameters.wireEncode());
return new Promise(function(resolve, reject){
remoteFace.commandInterestGenerator.generate
(commandInterest, remoteFace.commandKeyChain, remoteFace.commandCertificateName, null, function(interest){
self._pit
.insert(interest)
.then(function(res){
//console.log("got response to commandInterest", res)
self.recycleInterest(interest)
resolve(remoteFace);
})
.catch(function(er){
//console.log("er", er, remoteFace, prefix.toUri())
reject(new Error("timeout in registration command Interest" + prefix.toUri()));
})
remoteFace.putData(interest);
});
})
}
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, ignore,face){
return {
data: data
, face: face
, 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){
//console.log(er, interest)
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, interest);
for (var i in choices){
choices[i].face.putData(interest);
}
return node._pit
.insert(interest, face)
.then(function onData(res){
//console.log("got data",interest, 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;
er.interest = interest;
//console.log("TIMEOUT", er)
return Promise.reject(er);
});
}
function Node_forwardInterest(request){
var node = request.node
, interest = request.interest
, face = request.face;
//console.log("forwardInterest", request)
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){
//console.log("forwardInterestWithStrategyError", er)
node._pit.insert(interest, face)
.then(function(){
//console.log("we published the data ourselve")
})
.catch(function(){
//console.log("it timed out anyway")
})
var error = new Error("ForwardInterest, no FIB Entries: " + interest.toUri())
error.code = Node.ERRORS.NO_ROUTES;
return Promise.reject(error);
})
.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());
//console.log("add_nexthop", controlParameters.name.toUri())
return this._fib.insert(controlParameters.name, face)
.then(function(){
face.registeredPrefixTable.push(controlParameters.name);
var ack = new Data(interest.name)
ack.setContent("SUCCESS")
//console.log("sending ack")
face.putData(ack);
return interest;
});
}
Node.prototype.get_nexthops = function Node_get_nexthops(interest, face){
var name = interest.name.getSubName(4)
this._fib._nameTree.left(name);
var routes = [];
for (var node of this._fib._nameTree){
if (node.item)
routes.push(node.getItem()._prefix.toUri());
}
var response = new Data(interest.name, JSON.stringify(routes))
face.putData(response);
return Promise.resolve(interest);
}
Node.prototype.register_id = function Node_register_id(interest, face){
var _id = interest.name.get(4).getValueAsBuffer().toString();
face._id = _id;
if (this._handlers["face-open"])
this._handlers["face-open"](face._id)
return Promise.resolve(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;
//console.log("oninterest", interest.toUri())
if (intercept = this._manager.intercept(interest, face)){
this[intercept](interest, face);
return Promise.resolve(interest);
}
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;
//self._keyChain.sign(data, self._certificateName)
//console.log("signed Data", data)
return Promise.all([
store.insert(data)
.then(function(wrap){
return wrap[0];
})
, self._pit
.lookup(data)
.then(function Node_putPacket_PIT_Hit(faces){
//console.log("putPackit PIT hit?", faces)
for (var i in faces)
faces[i].putData(data, null, face);
return true;
})
.catch(function Node_putPacket_PIT_Miss(er){
//console.log("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;
//console.log(Name.Component.fromNumberWithMarker(chunks.total, 0x00))
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)
];
for (var chunk of chunks){
proms.push(chunk.then(function onChunk(chunk){
var name = new Name(prefix);
name.appendSegment(chunk.chunkNumber+1);
var data = new Data(name, chunk.buffer)
data.getMetaInfo().setFreshnessPeriod(freshnessPeriod);
return self.putData(data, store);
}));
}
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 self = this;
var pipe = [];
var numberOfPackets = params.finalBlock + 1;
var millisecondsPerPacket = 4000;
var timeToExpectedLastPacket = (millisecondsPerPacket * numberOfPackets) + params.rtt;
var timeouts = 0
var proms = []
var interests = []
for (var i = 0; i < numberOfPackets; i++ ){
interests.push(new Interest(name.getPrefix(-1).appendSegment(i)));
interests[i].setInterestLifetimeMilliseconds(millisecondsPerPacket);
interests[i].setMustBeFresh(params.mustBeFresh)
}
var pipeSize = (numberOfPackets < 10) ? numberOfPackets : 10;
var expressedInterests = 0;
while (++expressedInterests < pipeSize){
proms.push(this.expressInterest(interests.shift())
.then().catch(function(er){
console.log("pipe timeout", er.interest, er)
var interest = er.interest
if ((er.code == Node.ERRORS.INTEREST_TIMEOUT) && interest && (++timeouts < 10))
return self.expressInterest(interest)
else
return er;
}));
}
function pipeline_onPacket(response){
//console.log("pipe data", response.data.name.get(-1).toEscapedString())
if (params.onProgress)
params.onProgress(response.data.name.get(-1).toNumberWithMarker(0x00) / (numberOfPackets-1), (response.face) ? response.face._id : self._id)
//console.log("onPacket",response, proms.length, numberOfPackets)
proms.push(self.expressInterest(interests.shift())
.catch(function(er){
console.log("pipe timeout", er.interest, er)
if ((er.code == Node.ERRORS.INTEREST_TIMEOUT) && er.interest && (++timeouts < 10))
return self.expressInterest(er.interest)
else
return er;
}))
if (interests.length > 0)
return Promise.race(proms)
.then(pipeline_onPacket)
else
return Promise.all(proms)
.then(function(res){
var datas = res.map(function(response){
//console.log("resp", response.data)
return response.data;
})
return datas;
})
//},0)
}
return Promise.race(proms)
.then(pipeline_onPacket)
.then(function(datas){
console.log("GOT DATAS")
return datas
})
.catch(function(er){
console.log("ERRRRRRRRRRRRRRRR", er.stack)
});
};
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.setMinSuffixComponents(minSuffix);
//firstInterest.setMaxSuffixComponents(maxSuffix);
firstInterest.setChildSelector(childSelector);
firstInterest.setMustBeFresh(mustBeFresh);
return self.expressInterest(firstInterest )
.then(function(response){
console.log("first data", response.data.getMetaInfo().getFinalBlockID())
return self.pipelineFetch({
prefix : response.data.name.getPrefix(-1)
, rtt : response.rtt
, mustBeFresh : mustBeFresh
, finalBlock : response.data.getMetaInfo().getFinalBlockID().toNumberWithMarker(0x00)
, onProgress : params.onProgress
});
});
};
Node.prototype.get = function Node_get(params){
return this.fetch(params)
.then(function (datas){
console.log("GOT")
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){
//console.log("called");
var data = this._data_pool.pop() || new Data();
//console.log("dafd",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;
//console.log("got element", this, face)
if (element[0] == Tlv.Interest || element[0] == Tlv.Data) {
//console.log("is Interest or 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)) {
//console.log("is Data",this, this.createData)
var data = self.createData(element);
console.log("create", data.name.toUri())
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));
//console.log("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));
var registerID = new Interest(new Name("localhop/nfd/id/register/" + self._id));
face.putData(registerID);
return Promise.all(proms);
};
Node.prototype.onFaceClose = function Node_onFaceClose (face){
console.log("onFaceClose")
this._fib.remove(face);
var faces = this._faces;
var toRemove;
for (var i in faces){
if (faces[i] === face){
toRemove = faces.splice(i, 1);
break;
}
}
if (this._handlers["face-closed"]){
this._handlers["face-closed"](face._id)
}
return toRemove;
}
Node.prototype.on = function Node_on(eventname, callback){
this._handlers[eventname] = callback;
}
Node.prototype.onPeer = function Node_onPeer(face, peeringInfo){
console.log("stuff", peeringInfo, face)
}
Node.prototype.closeFace = function Node_closeFace(faceID){
var toClose;
for (var index in this._faces){
if (this._faces[index]._id === faceID){
toClose = this._faces[index];
break;
}
}
if (toClose){
console.log("closing face")
toClose.close()
}
}
module.exports = Node;