ndn-io
Version:
NDN I/O module for node.js and the browser
252 lines (208 loc) • 9.03 kB
JavaScript
var Publisher = require("./Publisher.js"),
Fetcher = require("./Fetcher.js"),
contrib = require("ndn-contrib"),
ndn = contrib.ndn
, debug = require("debug")("IO");
Publisher.installContrib(contrib);
Fetcher.installContrib(contrib);
/**
*@constructor
*@param {Transport} transportClass a transport class
*@param {Object} connectionParameters the necessary connection info for the given class
*@return {io}
*/
function IO (transportClass, connectionParameters, contentStore){
debug("constructor called");
debug("transportClass: %s", transportClass);
debug("connectionParameters: %s", connectionParameters);
this.interfaces = new contrib.Interfaces(this);
var transports = Object.keys(contrib.Transports);
for (var i = 0; i < transports.length; i++){
debug("installing tranport %s from ndn-js-contrib", transports[i]);
this.interfaces.installTransport(contrib.Transports[transports[i]]);
}
this.interfaces.newFace(transportClass, connectionParameters);
this.nameTree = (contentStore) ? contentStore.nameTree : new contrib.NameTree();
this.PIT = new contrib.PIT(this.nameTree);
this.FIB = new contrib.FIB(this.nameTree);
this.contentStore = contentStore || new contrib.ContentStore(this.nameTree);
this.ndn = ndn;
this.publisher = new Publisher(this);
this.fetcher = new Fetcher(this);
return this;
}
IO.ndn = ndn;
/** import ndn-contrib into Class scope
*@static
*@param {Object} NDN the ndn-contrib object
*/
IO.installContrib = function(contrib){
Publisher.installContrib(contrib);
Fetcher.installContrib(contrib);
ndn = contrib.ndn;
this.ndn = contrib.ndn;
console.log("contrib installed");
};
IO.localTransport = require("ndn-lib/js/transport/unix-transport.js");
/** Publish a file, json object or string
*@param {Buffer|Blob|File|FilePath|JSON|String} toPublish the thing you want to publish
*@param {String|ndn.Name} name the name to publish the data under (excluding segment)
*/
IO.prototype.publish = function(name, toPublish, announcer ){
debug("called publish convenience method... aren't you daring!");
this.publisher = this.publisher || new Publisher(this);
return this.publisher.setToPublish(toPublish)
.setName(name)
.setFreshnessPeriod( 60 * 60 * 1000)
.publish(announcer);
};
/** Fetch a Blob/Buffer, JSON object, or String
*@param {String} type a MIME string (eg 'application/javascript') or 'json', 'object', 'string'
*@param {String} uri the uri of the thing to fetch
*@param {Function} callback success callback
*@param {Function} timeout timeout callback
*@returns {this} for chaining
*/
IO.prototype.fetch = function(uriString, callback){
debug("called fetch convenience method... aren't you daring!");
this.fetcher = this.fetcher || new Fetcher(this);
var parts = uriString.split("://");
//console.log("parts", parts)
this.fetcher.setName(parts[1])
.setInterestLifetimeMilliseconds(400)
.setType(parts[0])
.get(callback);
return this;
};
/** settable announce function. Rather than enforce a handshake naming convention/protocol
* it is up to application developer convention to negotiate storage request handshakes.
* This function is called within {IO.publish} after the data is in the contentStore
*@param {Object} firstData the ndn.Data object of the first segment data packet
*/
IO.prototype.announcer = function(firstData){};
/** set the announcer function
*@param {function} announcer
*@returns {this} this for chaining
*/
IO.prototype.setAnnouncer = function(announcer){
this.announcer = announcer;
return this;
};
/** create an IPC face and a forwarding entry to send interest packets to a listener in the main thread
*@param {String} prefix the uri of the prefix to listen on
*@param {Class} connectionParameters to use with IO.localTransport (unix in Node, MessageChannel in browser)
*@returns {this} this for chaining
*/
IO.prototype.addListener = function(prefix, connectionParameters){
this.FIB.addEntry(prefix, [{
faceID: this.Interfaces.newFace(IO.localTransport, connectionParameters)
}]);
};
/** handler for incoming interests
*@param {Buffer} element the raw interest packet
*@param {number} faceID the integer faceID of the receiving face
*/
IO.prototype.handleInterest = function(element, faceID){
var interest = new ndn.Interest();
interest.wireDecode(element);
debug("got interest %s", interest.toUri());
var Self = this;
this.contentStore.check(interest, function(result){
debug("contentStore found match for $%s", interest.name.toUri());
if (result){
Self.interfaces.dispatch(result, 0 | (1 << faceID));
} /*else {
var dispatchFlag = this.FIB.findAllNextHops(interest.name.toUri());
if (dispatchFlag !== 0){
Self.interfaces.dispatch(element, dispatchFlag);
}
} */
});
};
/**handler for incoming data
*@param {Buffer} element the raw data packet
*@param {number} faceID the integer faceID of the receiving face
*/
IO.prototype.handleData = function(element, faceID){
var data = new ndn.Data();
data.wireDecode(element);
debug("got data %s", data.name.toUri());
var results = this.PIT.lookup(data);
for (var i = 0; i < results.pitEntries.length; i++){
debug("data matches pitEntry %s", results.pitEntries[i].uri);
results.pitEntries[i].callback(element, data, data.signedInfo.finalBlockID);
}
};
/** fetch all segments of any data, excecuting the callback with each packet
*@param {Interest} firstSegmentInterest the interest for the first segment of a data item
*@param {function} onEachData function to call with each incoming data packet, recieves the raw packet, the ndn.Data object, and the finalBlockID of the item
*@param {function} onTimeout function to call if the entire object can't be retrieved, passed the firstSegmentInterest as the only argument
*/
IO.prototype.fetchAllSegments = function(firstSegmentInterest, onEachData, onTimeout){
var interestsInFlight = 0
, windowSize = 50
, masterInterest = new ndn.Interest(firstSegmentInterest)
, finalSegmentNumber
, interest = new ndn.Interest(masterInterest)
, callbackTriggered = false
, segmentRequested = []
, segmentGot = []
, Self = this;
masterInterest.name = firstSegmentInterest.name.getPrefix(-1);
debug("fetching all segments for data named %s", masterInterest.name.toUri());
var callback = function(element, data, finalBlockID) {
if (!element){
var interest = data;
var seg = ndn.DataUtils.bigEndianToUnsignedInt(interest.name.get(-1).getValue().buf());
if (!segmentGot[seg]){
debug("insterest timeout for segment %s of %s", seg, masterInterest.name.toUri());
if (segmentRequested[seg] < 6) {
debug("re-expressing");
segmentRequested[seg]++;
var packet = interest.wireEncode().buffer;
Self.PIT.insertPitEntry(packet, interest, callback);
Self.interfaces.dispatch(packet, 1);
} else if ((callbackTriggered === false)) {
callbackTriggered = true;
debug("triggering onTimeout and aborting");
onTimeout(new Error("fetching data failed due to timeout: ", firstSegmentInterest.toUri()), null);
}
}
} else {
finalBlockID = finalBlockID || firstSegmentInterest.name.get(-1);
onEachData(element, data, finalBlockID);
interestsInFlight--;
var segmentNumber = ndn.DataUtils.bigEndianToUnsignedInt(data.name.get(-1).getValueAsBuffer());
segmentGot[segmentNumber] = true;
finalSegmentNumber = 1 + ndn.DataUtils.bigEndianToUnsignedInt(finalBlockID);
debug("got segment %s of %s for %s", segmentNumber+1, finalSegmentNumber, masterInterest.name.toUri());
if (interestsInFlight < windowSize) {
var p;
for (var i = 0; i < finalSegmentNumber; i++) {
if (segmentRequested[i] === undefined) {
var newInterest = new ndn.Interest(masterInterest);
newInterest.name.appendSegment(i);
newInterest.setInterestLifetimeMilliseconds(masterInterest.getInterestLifetimeMilliseconds());
p = newInterest.wireEncode();
segmentRequested[i] = 0;
Self.PIT.insertPitEntry(p, newInterest, callback);
debug("expressing interest for segment %s of %s for %s", i+1, finalSegmentNumber, masterInterest.name.toUri());
Self.interfaces.dispatch(p, 1);
interestsInFlight++;
if (interestsInFlight === windowSize) {
i = finalSegmentNumber;
}
}
}
}
}
};
segmentRequested[0] = 0;
var packet = firstSegmentInterest.wireEncode().buffer;
firstSegmentInterest = new ndn.Interest();
firstSegmentInterest.wireDecode(packet);
this.PIT.insertPitEntry(packet, firstSegmentInterest, callback);
this.interfaces.dispatch(packet, 1);
//console.log("dispatched");
};
module.exports = IO;