Steps for merging WebSocket and Xpcom code: Moved the processing of Interest and ContentObject from WebSocketTransport onmessages to NDN.onReceivedData.
Added method WebSocketTransport.send and use in expressInterest, etc.
Moved CSEntry definition and CSTable to NDN.js.
Moved registerPrefix from WebSocketTransport to NDN.
diff --git a/js/NDN.js b/js/NDN.js
index 0410425..41de1d7 100644
--- a/js/NDN.js
+++ b/js/NDN.js
@@ -31,6 +31,7 @@
// Event handler
this.onopen = (settings.onopen || function() { if (LOG > 3) console.log("NDN connection established."); });
this.onclose = (settings.onclose || function() { if (LOG > 3) console.log("NDN connection closed."); });
+ this.ccndid = null;
};
NDN.UNOPEN = 0; // created but not opened yet
@@ -101,6 +102,22 @@
return result;
};
+// For publishing data
+NDN.CSTable = new Array();
+
+var CSEntry = function CSEntry(name, closure) {
+ this.name = name; // String
+ this.closure = closure; // Closure
+};
+
+function getEntryForRegisteredPrefix(name) {
+ for (var i = 0; i < NDN.CSTable.length; i++) {
+ if (NDN.CSTable[i].name.match(name) != null)
+ return NDN.CSTable[i];
+ }
+ return null;
+}
+
/*
* Return a function that selects a host at random from hostList and returns { host: host, port: port }.
* If no more hosts remain, return null.
@@ -155,7 +172,219 @@
};
NDN.prototype.registerPrefix = function(name, closure, flag) {
- return this.transport.registerPrefix(this, name, closure, flag);
+ if (this.readyStatus != NDN.OPENED) {
+ console.log('Connection is not established.');
+ return -1;
+ }
+
+ if (this.ccndid == null) {
+ console.log('ccnd node ID unkonwn. Cannot register prefix.');
+ return -1;
+ }
+
+ var fe = new ForwardingEntry('selfreg', name, null, null, 3, 2147483647);
+ var bytes = encodeForwardingEntry(fe);
+
+ var si = new SignedInfo();
+ si.setFields();
+
+ var co = new ContentObject(new Name(), si, bytes, new Signature());
+ co.sign();
+ var coBinary = encodeToBinaryContentObject(co);
+
+ //var nodename = unescape('%00%88%E2%F4%9C%91%16%16%D6%21%8E%A0c%95%A5%A6r%11%E0%A0%82%89%A6%A9%85%AB%D6%E2%065%DB%AF');
+ var nodename = this.ccndid;
+ var interestName = new Name(['ccnx', nodename, 'selfreg', coBinary]);
+
+ var interest = new Interest(interestName);
+ interest.scope = 1;
+ if (LOG > 3) console.log('Send Interest registration packet.');
+
+ var csEntry = new CSEntry(name.getName(), closure);
+ NDN.CSTable.push(csEntry);
+
+ this.transport.send(encodeToBinaryInterest(interest));
+
+ return 0;
+};
+
+/*
+ * This is called when an entire binary XML element is received, such as a ContentObject or Interest.
+ * Look up in the PITTable and call the closure callback.
+ */
+NDN.prototype.onReceivedElement = function(element) {
+ var decoder = new BinaryXMLDecoder(element);
+ // Dispatch according to packet type
+ if (decoder.peekStartElement(CCNProtocolDTags.Interest)) { // Interest packet
+ if (LOG > 3) console.log('Interest packet received.');
+
+ var interest = new Interest();
+ interest.from_ccnb(decoder);
+ if (LOG > 3) console.log(interest);
+ var nameStr = escape(interest.name.getName());
+ if (LOG > 3) console.log(nameStr);
+
+ var entry = getEntryForRegisteredPrefix(nameStr);
+ if (entry != null) {
+ //console.log(entry);
+ var info = new UpcallInfo(this, interest, 0, null);
+ var ret = entry.closure.upcall(Closure.UPCALL_INTEREST, info);
+ if (ret == Closure.RESULT_INTEREST_CONSUMED && info.contentObject != null)
+ this.transport.send(encodeToBinaryContentObject(info.contentObject));
+ }
+ } else if (decoder.peekStartElement(CCNProtocolDTags.ContentObject)) { // Content packet
+ if (LOG > 3) console.log('ContentObject packet received.');
+
+ var co = new ContentObject();
+ co.from_ccnb(decoder);
+
+ if (this.ccndid == null && NDN.ccndIdFetcher.match(co.name)) {
+ // We are in starting phase, record publisherPublicKeyDigest in ccndid
+ if(!co.signedInfo || !co.signedInfo.publisher
+ || !co.signedInfo.publisher.publisherPublicKeyDigest) {
+ console.log("Cannot contact router, close NDN now.");
+
+ // Close NDN if we fail to connect to a ccn router
+ this.readyStatus = NDN.CLOSED;
+ this.onclose();
+ //console.log("NDN.onclose event fired.");
+ } else {
+ //console.log('Connected to ccnd.');
+ this.ccndid = co.signedInfo.publisher.publisherPublicKeyDigest;
+ if (LOG>3) console.log(ndn.ccndid);
+
+ // Call NDN.onopen after success
+ this.readyStatus = NDN.OPENED;
+ this.onopen();
+ //console.log("NDN.onopen event fired.");
+ }
+ } else {
+ var pitEntry = NDN.getEntryForExpressedInterest(co.name);
+ if (pitEntry != null) {
+ //console.log(pitEntry);
+ // Remove PIT entry from NDN.PITTable
+ var index = NDN.PITTable.indexOf(pitEntry);
+ if (index >= 0)
+ NDN.PITTable.splice(index, 1);
+
+ var currentClosure = pitEntry.closure;
+
+ // Cancel interest timer
+ clearTimeout(pitEntry.timerID);
+ //console.log("Clear interest timer");
+ //console.log(currentClosure.timerID);
+
+ // Key verification
+
+ // Recursive key fetching & verification closure
+ var KeyFetchClosure = function KeyFetchClosure(content, closure, key, sig, wit) {
+ this.contentObject = content; // unverified content object
+ this.closure = closure; // closure corresponding to the contentObject
+ this.keyName = key; // name of current key to be fetched
+ this.sigHex = sig; // hex signature string to be verified
+ this.witness = wit;
+
+ Closure.call(this);
+ };
+
+ var thisNdn = this;
+ KeyFetchClosure.prototype.upcall = function(kind, upcallInfo) {
+ if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+ console.log("In KeyFetchClosure.upcall: interest time out.");
+ console.log(this.keyName.contentName.getName());
+ } else if (kind == Closure.UPCALL_CONTENT) {
+ //console.log("In KeyFetchClosure.upcall: signature verification passed");
+
+ var rsakey = decodeSubjectPublicKeyInfo(upcallInfo.contentObject.content);
+ var verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, this.witness, this.sigHex);
+
+ var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+ //console.log("raise encapsulated closure");
+ this.closure.upcall(flag, new UpcallInfo(thisNdn, null, 0, this.contentObject));
+
+ // Store key in cache
+ var keyEntry = new KeyStoreEntry(keylocator.keyName, rsakey, new Date().getTime());
+ NDN.addKeyEntry(keyEntry);
+ //console.log(NDN.KeyStore);
+ } else if (kind == Closure.UPCALL_CONTENT_BAD) {
+ console.log("In KeyFetchClosure.upcall: signature verification failed");
+ }
+ };
+
+ if (co.signedInfo && co.signedInfo.locator && co.signature) {
+ if (LOG > 3) console.log("Key verification...");
+ var sigHex = DataUtils.toHex(co.signature.signature).toLowerCase();
+
+ var wit = null;
+ if (co.signature.Witness != null) {
+ wit = new Witness();
+ wit.decode(co.signature.Witness);
+ }
+
+ var keylocator = co.signedInfo.locator;
+ if (keylocator.type == KeyLocatorType.KEYNAME) {
+ if (LOG > 3) console.log("KeyLocator contains KEYNAME");
+ //var keyname = keylocator.keyName.contentName.getName();
+ //console.log(nameStr);
+ //console.log(keyname);
+
+ if (keylocator.keyName.contentName.match(co.name)) {
+ if (LOG > 3) console.log("Content is key itself");
+
+ var rsakey = decodeSubjectPublicKeyInfo(co.content);
+ var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
+ var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+
+ currentClosure.upcall(flag, new UpcallInfo(this, null, 0, co));
+
+ // SWT: We don't need to store key here since the same key will be
+ // stored again in the closure.
+ //var keyEntry = new KeyStoreEntry(keylocator.keyName, rsakey, new Date().getTime());
+ //NDN.addKeyEntry(keyEntry);
+ //console.log(NDN.KeyStore);
+ } else {
+ // Check local key store
+ var keyEntry = NDN.getKeyByName(keylocator.keyName);
+ if (keyEntry) {
+ // Key found, verify now
+ if (LOG > 3) console.log("Local key cache hit");
+ var rsakey = keyEntry.rsaKey;
+ var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
+ var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+
+ // Raise callback
+ currentClosure.upcall(flag, new UpcallInfo(this, null, 0, co));
+ } else {
+ // Not found, fetch now
+ if (LOG > 3) console.log("Fetch key according to keylocator");
+ var nextClosure = new KeyFetchClosure(co, currentClosure, keylocator.keyName, sigHex, wit);
+ this.expressInterest(keylocator.keyName.contentName.getPrefix(4), nextClosure);
+ }
+ }
+ } else if (keylocator.type == KeyLocatorType.KEY) {
+ if (LOG > 3) console.log("Keylocator contains KEY");
+
+ var rsakey = decodeSubjectPublicKeyInfo(co.signedInfo.locator.publicKey);
+ var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
+
+ var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+ // Raise callback
+ currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(this, null, 0, co));
+
+ // Since KeyLocator does not contain key name for this key,
+ // we have no way to store it as a key entry in KeyStore.
+ } else {
+ var cert = keylocator.certificate;
+ console.log("KeyLocator contains CERT");
+ console.log(cert);
+
+ // TODO: verify certificate
+ }
+ }
+ }
+ }
+ } else
+ console.log('Incoming packet is not Interest or ContentObject. Discard now.');
};
/*
@@ -180,8 +409,8 @@
this.port = hostAndPort.port;
console.log("Trying host from getHostAndPort: " + this.host);
- // Fetch the ccndId.
- var interest = new Interest(NDN.ccndIdFetcher);
+ // Fetch any content.
+ var interest = new Interest(new Name("/"));
interest.interestLifetime = 4000; // milliseconds
var thisNDN = this;
@@ -207,8 +436,7 @@
NDN.ConnectClosure.prototype.upcall = function(kind, upcallInfo) {
if (!(kind == Closure.UPCALL_CONTENT ||
- kind == Closure.UPCALL_CONTENT_UNVERIFIED ||
- kind == Closure.UPCALL_INTEREST))
+ kind == Closure.UPCALL_CONTENT_UNVERIFIED))
// The upcall is not for us.
return Closure.RESULT_ERR;
@@ -220,3 +448,44 @@
return Closure.RESULT_OK;
};
+/*
+ * A BinaryXmlElementReader lets you call onReceivedData multiple times which uses a
+ * BinaryXMLStructureDecoder to detect the end of a binary XML element and calls
+ * elementListener.onReceivedElement(element) with the element.
+ * This handles the case where a single call to onReceivedData may contain multiple elements.
+ */
+var BinaryXmlElementReader = function BinaryXmlElementReader(elementListener) {
+ this.elementListener = elementListener;
+ this.dataParts = [];
+ this.structureDecoder = new BinaryXMLStructureDecoder();
+};
+
+BinaryXmlElementReader.prototype.onReceivedData = function(/* Uint8Array */ rawData) {
+ dump("got " + rawData.length + " bytes\n");
+ // Process multiple objects in the data.
+ while(true) {
+ // Scan the input to check if a whole ccnb object has been read.
+ this.structureDecoder.seek(0);
+ if (this.structureDecoder.findElementEnd(rawData)) {
+ // Got the remainder of an object. Report to the caller.
+ this.dataParts.push(rawData.subarray(0, this.structureDecoder.offset));
+ dump("calling onReceivedElement\n");
+ this.elementListener.onReceivedElement(DataUtils.concatArrays(this.dataParts));
+
+ // Need to read a new object.
+ rawData = rawData.subarray(this.structureDecoder.offset, rawData.length);
+ this.dataParts = [];
+ this.structureDecoder = new BinaryXMLStructureDecoder();
+ if (rawData.length == 0)
+ // No more data in the packet.
+ return;
+
+ // else loop back to decode.
+ }
+ else {
+ // Save for a later call to concatArrays so that we only copy data once.
+ this.dataParts.push(rawData);
+ return;
+ }
+ }
+}
\ No newline at end of file