UNPKG

ndn-js-contrib

Version:

Reusable 'Classes' for Named Data Networking: NameTree, PIT, FIB, ContentStore, Interfaces, and Transports

819 lines (674 loc) 23.8 kB
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;