Recursive key fetching
diff --git a/js/WebSocketTransport.js b/js/WebSocketTransport.js
index 12d8fcf..f3ee4d9 100644
--- a/js/WebSocketTransport.js
+++ b/js/WebSocketTransport.js
@@ -103,7 +103,8 @@
 				}
 				
 			} else if (decoder.peekStartElement(CCNProtocolDTags.ContentObject)) {  // Content packet
-				if (LOG > 3) console.log('ContentObject packet received.');
+				//if (LOG > 3) 
+				console.log('ContentObject packet received.');
 				
 				var co = new ContentObject();
 				co.from_ccnb(decoder);
@@ -111,37 +112,6 @@
 				var nameStr = co.name.getName();
 				console.log(nameStr);
 				
-				// Key verification
-				if (co.signedInfo && co.signature) {
-					if (LOG > 3) console.log("Key verification...");
-					var signature = DataUtils.toHex(co.signature.signature).toLowerCase();
-					
-					var keylocator = co.signedInfo.locator;
-					if (keylocator.type == KeyLocatorType.KEYNAME) {
-						console.log("KeyLocator contains KEYNAME");
-						var keyname = keylocator.keyName.contentName.getName();
-						console.log(keyname);
-					} else if (keylocator.type == KeyLocatorType.KEY) {
-						console.log("Keylocator contains KEY");
-						var publickeyHex = DataUtils.toHex(co.signedInfo.locator.publicKey).toLowerCase();
-
-						var kp = publickeyHex.slice(56, 314);
-						var exp = publickeyHex.slice(318, 324);
-						
-						var rsakey = new RSAKey();
-						rsakey.setPublic(kp, exp);
-						var result = rsakey.verifyByteArray(co.rawSignatureData, signature);
-						if (result)
-							console.log('SIGNATURE VALID');
-						else
-							console.log('SIGNATURE INVALID');
-					} else {
-						var cert = keylocator.certificate;
-						console.log("KeyLocator contains CERT");
-						console.log(cert);
-					}
-				}
-				
 				if (self.ccndid == null && nameStr.match(NDN.ccndIdFetcher) != null) {
 					// We are in starting phase, record publisherPublicKeyDigest in self.ccndid
 					if(!co.signedInfo || !co.signedInfo.publisher 
@@ -166,19 +136,96 @@
 					var pitEntry = NDN.getEntryForExpressedInterest(co.name);
 					if (pitEntry != null) {
 						//console.log(pitEntry);
-						
-						// Cancel interest timer
-						clearTimeout(pitEntry.closure.timerID);
-						//console.log("Clear interest timer");
-						//console.log(pitEntry.closure.timerID);
-						
 						// Remove PIT entry from NDN.PITTable
 						var index = NDN.PITTable.indexOf(pitEntry);
 						if (index >= 0)
-                            NDN.PITTable.splice(index, 1);
+							NDN.PITTable.splice(index, 1);
 						
-						// Raise callback
-						pitEntry.closure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
+						var currentClosure = pitEntry.closure;
+						
+						// Cancel interest timer
+						clearTimeout(currentClosure.timerID);
+						//console.log("Clear interest timer");
+						//console.log(currentClosure.timerID);
+						
+						// Key verification
+						var verified = false;
+						
+						// Recursive key fetching closure
+						var KeyFetchClosure = function KeyFetchClosure(content, closure, key, signature) {
+							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.signature = signature;  // hex signature string to be verified
+							
+							Closure.call(this);
+						};
+						
+						KeyFetchClosure.prototype.upcall = function(kind, upcallInfo) {
+							if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+								console.log("In KeyFetchClosure.upcall: interest time out.");
+							} else if (kind == Closure.UPCALL_CONTENT) {
+								console.log("In KeyFetchClosure.upcall");
+								var keyHex = DataUtils.toHex(upcallInfo.contentObject.content).toLowerCase();
+								console.log("Key: " + keyHex);
+								
+								var kp = keyHex.slice(56, 314);
+								var exp = keyHex.slice(318, 324);
+								
+								var rsakey = new RSAKey();
+								rsakey.setPublic(kp, exp);
+								verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, sigHex);
+								var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+								
+								console.log("raise encapsulated closure");
+								this.closure.upcall(flag, new UpcallInfo(ndn, null, 0, this.contentObject));
+							}
+						};
+						
+						if (co.signedInfo && co.signedInfo.locator && co.signature) {
+							if (LOG > 3) console.log("Key verification...");
+							var sigHex = DataUtils.toHex(co.signature.signature).toLowerCase();
+							
+							var keylocator = co.signedInfo.locator;
+							if (keylocator.type == KeyLocatorType.KEYNAME) {
+								console.log("KeyLocator contains KEYNAME");
+								var keyname = keylocator.keyName.contentName.getName();
+								console.log(keyname);
+								
+								if (nameStr.match("/ccnx.org/Users/")) {
+									console.log("Key found");
+									currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
+								} else {
+									console.log("Fetch key according to keylocator");
+									var nextClosure = new KeyFetchClosure(co, currentClosure, keyname, sigHex);
+									var interest = new Interest(keylocator.keyName.contentName.getPrefix(4));
+									interest.interestLifetime = 4.0;
+									self.expressInterest(ndn, interest, nextClosure);
+								}
+							} else if (keylocator.type == KeyLocatorType.KEY) {
+								console.log("Keylocator contains KEY");
+								var publickeyHex = DataUtils.toHex(co.signedInfo.locator.publicKey).toLowerCase();
+								console.log(publickeyHex);
+		
+								var kp = publickeyHex.slice(56, 314);
+								var exp = publickeyHex.slice(318, 324);
+								
+								var rsakey = new RSAKey();
+								rsakey.setPublic(kp, exp);
+								verified = rsakey.verifyByteArray(co.rawSignatureData, sigHex);
+								
+								var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+								
+								// Raise callback
+								currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
+							} else {
+								var cert = keylocator.certificate;
+								console.log("KeyLocator contains CERT");
+								console.log(cert);
+								
+								// TODO: verify certificate
+							}
+						}
 					}
 				}
 			} else {
diff --git a/js/testing/test-get-async.html b/js/testing/test-get-async.html
index 015eeb1..a5ac9d8 100644
--- a/js/testing/test-get-async.html
+++ b/js/testing/test-get-async.html
@@ -26,6 +26,7 @@
 			if (kind == Closure.UPCALL_FINAL) {

 				// Do nothing.

 			} else if (kind == Closure.UPCALL_CONTENT) {

+				console.log("Closure.upcall: content signature verification pass.");

 				var content = upcallInfo.contentObject;

 				//console.log(content.name);

 				nameStr = escape(content.name.getName());

@@ -35,6 +36,8 @@
 				//console.log("In callback, content: ");

 				//console.log(content);

 				document.getElementById('content').innerHTML += contentObjectToHtml(content);

+			} else if (kind == Closure.UPCALL_CONTENT_BAD) {

+				console.log("Closure.upcall: content signature verification fail.");

 			} else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {

 				console.log("Closure.upcall called with interest time out.");

 			}

diff --git a/js/tools/build/ndn-js-uncomp.js b/js/tools/build/ndn-js-uncomp.js
index c287d2a..6f0cd3e 100644
--- a/js/tools/build/ndn-js-uncomp.js
+++ b/js/tools/build/ndn-js-uncomp.js
@@ -359,7 +359,8 @@
 				}
 				
 			} else if (decoder.peekStartElement(CCNProtocolDTags.ContentObject)) {  // Content packet
-				if (LOG > 3) console.log('ContentObject packet received.');
+				//if (LOG > 3) 
+				console.log('ContentObject packet received.');
 				
 				var co = new ContentObject();
 				co.from_ccnb(decoder);
@@ -367,37 +368,6 @@
 				var nameStr = co.name.getName();
 				console.log(nameStr);
 				
-				// Key verification
-				if (co.signedInfo && co.signature) {
-					if (LOG > 3) console.log("Key verification...");
-					var signature = DataUtils.toHex(co.signature.signature).toLowerCase();
-					
-					var keylocator = co.signedInfo.locator;
-					if (keylocator.type == KeyLocatorType.KEYNAME) {
-						console.log("KeyLocator contains KEYNAME");
-						var keyname = keylocator.keyName.contentName.getName();
-						console.log(keyname);
-					} else if (keylocator.type == KeyLocatorType.KEY) {
-						console.log("Keylocator contains KEY");
-						var publickeyHex = DataUtils.toHex(co.signedInfo.locator.publicKey).toLowerCase();
-
-						var kp = publickeyHex.slice(56, 314);
-						var exp = publickeyHex.slice(318, 324);
-						
-						var rsakey = new RSAKey();
-						rsakey.setPublic(kp, exp);
-						var result = rsakey.verifyByteArray(co.rawSignatureData, signature);
-						if (result)
-							console.log('SIGNATURE VALID');
-						else
-							console.log('SIGNATURE INVALID');
-					} else {
-						var cert = keylocator.certificate;
-						console.log("KeyLocator contains CERT");
-						console.log(cert);
-					}
-				}
-				
 				if (self.ccndid == null && nameStr.match(NDN.ccndIdFetcher) != null) {
 					// We are in starting phase, record publisherPublicKeyDigest in self.ccndid
 					if(!co.signedInfo || !co.signedInfo.publisher 
@@ -422,19 +392,96 @@
 					var pitEntry = NDN.getEntryForExpressedInterest(co.name);
 					if (pitEntry != null) {
 						//console.log(pitEntry);
-						
-						// Cancel interest timer
-						clearTimeout(pitEntry.closure.timerID);
-						//console.log("Clear interest timer");
-						//console.log(pitEntry.closure.timerID);
-						
 						// Remove PIT entry from NDN.PITTable
 						var index = NDN.PITTable.indexOf(pitEntry);
 						if (index >= 0)
-                            NDN.PITTable.splice(index, 1);
+							NDN.PITTable.splice(index, 1);
 						
-						// Raise callback
-						pitEntry.closure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
+						var currentClosure = pitEntry.closure;
+						
+						// Cancel interest timer
+						clearTimeout(currentClosure.timerID);
+						//console.log("Clear interest timer");
+						//console.log(currentClosure.timerID);
+						
+						// Key verification
+						var verified = false;
+						
+						// Recursive key fetching closure
+						var KeyFetchClosure = function KeyFetchClosure(content, closure, key, signature) {
+							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.signature = signature;  // hex signature string to be verified
+							
+							Closure.call(this);
+						};
+						
+						KeyFetchClosure.prototype.upcall = function(kind, upcallInfo) {
+							if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+								console.log("In KeyFetchClosure.upcall: interest time out.");
+							} else if (kind == Closure.UPCALL_CONTENT) {
+								console.log("In KeyFetchClosure.upcall");
+								var keyHex = DataUtils.toHex(upcallInfo.contentObject.content).toLowerCase();
+								console.log("Key: " + keyHex);
+								
+								var kp = keyHex.slice(56, 314);
+								var exp = keyHex.slice(318, 324);
+								
+								var rsakey = new RSAKey();
+								rsakey.setPublic(kp, exp);
+								verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, sigHex);
+								var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+								
+								console.log("raise encapsulated closure");
+								this.closure.upcall(flag, new UpcallInfo(ndn, null, 0, this.contentObject));
+							}
+						};
+						
+						if (co.signedInfo && co.signedInfo.locator && co.signature) {
+							if (LOG > 3) console.log("Key verification...");
+							var sigHex = DataUtils.toHex(co.signature.signature).toLowerCase();
+							
+							var keylocator = co.signedInfo.locator;
+							if (keylocator.type == KeyLocatorType.KEYNAME) {
+								console.log("KeyLocator contains KEYNAME");
+								var keyname = keylocator.keyName.contentName.getName();
+								console.log(keyname);
+								
+								if (nameStr.match("/ccnx.org/Users/")) {
+									console.log("Key found");
+									currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
+								} else {
+									console.log("Fetch key according to keylocator");
+									var nextClosure = new KeyFetchClosure(co, currentClosure, keyname, sigHex);
+									var interest = new Interest(keylocator.keyName.contentName.getPrefix(4));
+									interest.interestLifetime = 4.0;
+									self.expressInterest(ndn, interest, nextClosure);
+								}
+							} else if (keylocator.type == KeyLocatorType.KEY) {
+								console.log("Keylocator contains KEY");
+								var publickeyHex = DataUtils.toHex(co.signedInfo.locator.publicKey).toLowerCase();
+								console.log(publickeyHex);
+		
+								var kp = publickeyHex.slice(56, 314);
+								var exp = publickeyHex.slice(318, 324);
+								
+								var rsakey = new RSAKey();
+								rsakey.setPublic(kp, exp);
+								verified = rsakey.verifyByteArray(co.rawSignatureData, sigHex);
+								
+								var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+								
+								// Raise callback
+								currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
+							} else {
+								var cert = keylocator.certificate;
+								console.log("KeyLocator contains CERT");
+								console.log(cert);
+								
+								// TODO: verify certificate
+							}
+						}
 					}
 				}
 			} else {
diff --git a/js/tools/build/ndn-js.js b/js/tools/build/ndn-js.js
index 68efd9e..9dc4b33 100644
--- a/js/tools/build/ndn-js.js
+++ b/js/tools/build/ndn-js.js
@@ -9,14 +9,16 @@
 d.host);d.connectAndExpressInterest(a,b)},3E3);this.transport.expressInterest(this,c,new NDN.ConnectClosure(this,a,b,e))}};NDN.ConnectClosure=function(a,b,c,d){Closure.call(this);this.ndn=a;this.callerInterest=b;this.callerClosure=c;this.timerID=d};
 NDN.ConnectClosure.prototype.upcall=function(a){if(!(a==Closure.UPCALL_CONTENT||a==Closure.UPCALL_CONTENT_UNVERIFIED||a==Closure.UPCALL_INTEREST))return Closure.RESULT_ERR;clearTimeout(this.timerID);console.log(this.ndn.host+": Host is alive. Fetching callerInterest.");this.ndn.transport.expressInterest(this.ndn,this.callerInterest,this.callerClosure);return Closure.RESULT_OK};
 var WebSocketTransport=function(){this.ccndid=this.ws=null;this.maxBufferSize=1E4;this.buffer=new Uint8Array(this.maxBufferSize);this.bufferOffset=0;this.structureDecoder=new BinaryXMLStructureDecoder;this.defaultGetHostAndPort=NDN.makeShuffledGetHostAndPort(["A.ws.ndn.ucla.edu","B.ws.ndn.ucla.edu","C.ws.ndn.ucla.edu","D.ws.ndn.ucla.edu","E.ws.ndn.ucla.edu"],9696)};
-WebSocketTransport.prototype.connectWebSocket=function(a){null!=this.ws&&delete this.ws;this.ws=new WebSocket("ws://"+a.host+":"+a.port);0<LOG&&console.log("ws connection created.");this.ws.binaryType="arraybuffer";var b=this;this.ws.onmessage=function(c){var d=c.data;if(null==d||void 0==d||""==d)console.log("INVALID ANSWER");else if(d instanceof ArrayBuffer){var e=new Uint8Array(d);3<LOG&&console.log("BINARY RESPONSE IS "+DataUtils.toHex(e));try{if(e.length+b.bufferOffset>=b.buffer.byteLength){3<
-LOG&&console.log("NDN.ws.onmessage: buffer overflow. Accumulate received length: "+b.bufferOffset+". Current packet length: "+e.length+".");delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0;return}b.buffer.set(e,b.bufferOffset);b.bufferOffset+=e.length;if(!b.structureDecoder.findElementEnd(b.buffer.subarray(0,b.bufferOffset))){3<LOG&&console.log("Incomplete packet received. Length "+e.length+". Wait for more input.");
-return}3<LOG&&console.log("Complete packet received. Length "+e.length+". Start decoding.")}catch(f){console.log("NDN.ws.onmessage exception: "+f);return}c=new BinaryXMLDecoder(b.buffer);if(c.peekStartElement(CCNProtocolDTags.Interest)){3<LOG&&console.log("Interest packet received.");e=new Interest;e.from_ccnb(c);3<LOG&&console.log(e);var g=escape(e.name.getName());3<LOG&&console.log(g);g=getEntryForRegisteredPrefix(g);null!=g&&(e=new UpcallInfo(a,e,0,null),g.closure.upcall(Closure.UPCALL_INTEREST,
-e)==Closure.RESULT_INTEREST_CONSUMED&&null!=e.contentObject&&(g=encodeToBinaryContentObject(e.contentObject),e=new Uint8Array(g.length),e.set(g),b.ws.send(e.buffer)))}else if(c.peekStartElement(CCNProtocolDTags.ContentObject)){3<LOG&&console.log("ContentObject packet received.");e=new ContentObject;e.from_ccnb(c);3<LOG&&console.log(e);g=e.name.getName();console.log(g);if(e.signedInfo&&e.signature){3<LOG&&console.log("Key verification...");var d=DataUtils.toHex(e.signature.signature).toLowerCase(),
-h=e.signedInfo.locator;if(h.type==KeyLocatorType.KEYNAME)console.log("KeyLocator contains KEYNAME"),d=h.keyName.contentName.getName(),console.log(d);else if(h.type==KeyLocatorType.KEY){console.log("Keylocator contains KEY");var j=DataUtils.toHex(e.signedInfo.locator.publicKey).toLowerCase(),h=j.slice(56,314),j=j.slice(318,324),k=new RSAKey;k.setPublic(h,j);(d=k.verifyByteArray(e.rawSignatureData,d))?console.log("SIGNATURE VALID"):console.log("SIGNATURE INVALID")}else d=h.certificate,console.log("KeyLocator contains CERT"),
-console.log(d)}null==b.ccndid&&null!=g.match(NDN.ccndIdFetcher)?!e.signedInfo||!e.signedInfo.publisher||!e.signedInfo.publisher.publisherPublicKeyDigest?(console.log("Cannot contact router, close NDN now."),a.readyStatus=NDN.CLOSED,a.onclose()):(b.ccndid=e.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(b.ccndid),a.readyStatus=NDN.OPENED,a.onopen()):(g=NDN.getEntryForExpressedInterest(e.name),null!=g&&(clearTimeout(g.closure.timerID),d=NDN.PITTable.indexOf(g),0<=d&&NDN.PITTable.splice(d,
-1),g.closure.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,e))))}else console.log("Incoming packet is not Interest or ContentObject. Discard now.");delete c;delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0}};this.ws.onopen=function(a){3<LOG&&console.log(a);3<LOG&&console.log("ws.onopen: WebSocket connection opened.");3<LOG&&console.log("ws.onopen: ReadyState: "+this.readyState);a=new Interest(new Name(NDN.ccndIdFetcher));
-a.interestLifetime=4;var a=encodeToBinaryInterest(a),d=new Uint8Array(a.length);d.set(a);b.ws.send(d.buffer)};this.ws.onerror=function(a){console.log("ws.onerror: ReadyState: "+this.readyState);console.log(a);console.log("ws.onerror: WebSocket error: "+a.data)};this.ws.onclose=function(){console.log("ws.onclose: WebSocket connection closed.");b.ws=null;a.readyStatus=NDN.CLOSED;a.onclose()}};
+WebSocketTransport.prototype.connectWebSocket=function(a){null!=this.ws&&delete this.ws;this.ws=new WebSocket("ws://"+a.host+":"+a.port);0<LOG&&console.log("ws connection created.");this.ws.binaryType="arraybuffer";var b=this;this.ws.onmessage=function(c){c=c.data;if(null==c||void 0==c||""==c)console.log("INVALID ANSWER");else if(c instanceof ArrayBuffer){var d=new Uint8Array(c);3<LOG&&console.log("BINARY RESPONSE IS "+DataUtils.toHex(d));try{if(d.length+b.bufferOffset>=b.buffer.byteLength){3<LOG&&
+console.log("NDN.ws.onmessage: buffer overflow. Accumulate received length: "+b.bufferOffset+". Current packet length: "+d.length+".");delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0;return}b.buffer.set(d,b.bufferOffset);b.bufferOffset+=d.length;if(!b.structureDecoder.findElementEnd(b.buffer.subarray(0,b.bufferOffset))){3<LOG&&console.log("Incomplete packet received. Length "+d.length+". Wait for more input.");
+return}3<LOG&&console.log("Complete packet received. Length "+d.length+". Start decoding.")}catch(e){console.log("NDN.ws.onmessage exception: "+e);return}c=new BinaryXMLDecoder(b.buffer);if(c.peekStartElement(CCNProtocolDTags.Interest)){3<LOG&&console.log("Interest packet received.");d=new Interest;d.from_ccnb(c);3<LOG&&console.log(d);var f=escape(d.name.getName());3<LOG&&console.log(f);var g=getEntryForRegisteredPrefix(f);null!=g&&(d=new UpcallInfo(a,d,0,null),g.closure.upcall(Closure.UPCALL_INTEREST,
+d)==Closure.RESULT_INTEREST_CONSUMED&&null!=d.contentObject&&(g=encodeToBinaryContentObject(d.contentObject),d=new Uint8Array(g.length),d.set(g),b.ws.send(d.buffer)))}else if(c.peekStartElement(CCNProtocolDTags.ContentObject))if(console.log("ContentObject packet received."),d=new ContentObject,d.from_ccnb(c),3<LOG&&console.log(d),f=d.name.getName(),console.log(f),null==b.ccndid&&null!=f.match(NDN.ccndIdFetcher))!d.signedInfo||!d.signedInfo.publisher||!d.signedInfo.publisher.publisherPublicKeyDigest?
+(console.log("Cannot contact router, close NDN now."),a.readyStatus=NDN.CLOSED,a.onclose()):(b.ccndid=d.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(b.ccndid),a.readyStatus=NDN.OPENED,a.onopen());else{if(g=NDN.getEntryForExpressedInterest(d.name),null!=g){var h=NDN.PITTable.indexOf(g);0<=h&&NDN.PITTable.splice(h,1);g=g.closure;clearTimeout(g.timerID);var j=!1,k=function(a,b,c,d){this.contentObject=a;this.closure=b;this.keyName=c;this.signature=d;Closure.call(this)};k.prototype.upcall=
+function(b,c){if(b==Closure.UPCALL_INTEREST_TIMED_OUT)console.log("In KeyFetchClosure.upcall: interest time out.");else if(b==Closure.UPCALL_CONTENT){console.log("In KeyFetchClosure.upcall");var d=DataUtils.toHex(c.contentObject.content).toLowerCase();console.log("Key: "+d);var e=d.slice(56,314),d=d.slice(318,324),f=new RSAKey;f.setPublic(e,d);j=f.verifyByteArray(this.contentObject.rawSignatureData,n);e=!0==j?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD;console.log("raise encapsulated closure");
+this.closure.upcall(e,new UpcallInfo(a,null,0,this.contentObject))}};if(d.signedInfo&&d.signedInfo.locator&&d.signature){3<LOG&&console.log("Key verification...");var n=DataUtils.toHex(d.signature.signature).toLowerCase(),h=d.signedInfo.locator;if(h.type==KeyLocatorType.KEYNAME){console.log("KeyLocator contains KEYNAME");var m=h.keyName.contentName.getName();console.log(m);f.match("/ccnx.org/Users/")?(console.log("Key found"),g.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,d))):(console.log("Fetch key according to keylocator"),
+g=new k(d,g,m,n),d=new Interest(h.keyName.contentName.getPrefix(4)),d.interestLifetime=4,b.expressInterest(a,d,g))}else h.type==KeyLocatorType.KEY?(console.log("Keylocator contains KEY"),h=DataUtils.toHex(d.signedInfo.locator.publicKey).toLowerCase(),console.log(h),f=h.slice(56,314),h=h.slice(318,324),k=new RSAKey,k.setPublic(f,h),j=k.verifyByteArray(d.rawSignatureData,n),g.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,d))):(d=h.certificate,console.log("KeyLocator contains CERT"),console.log(d))}}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.");
+delete c;delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0}};this.ws.onopen=function(a){3<LOG&&console.log(a);3<LOG&&console.log("ws.onopen: WebSocket connection opened.");3<LOG&&console.log("ws.onopen: ReadyState: "+this.readyState);a=new Interest(new Name(NDN.ccndIdFetcher));a.interestLifetime=4;var a=encodeToBinaryInterest(a),d=new Uint8Array(a.length);d.set(a);b.ws.send(d.buffer)};this.ws.onerror=
+function(a){console.log("ws.onerror: ReadyState: "+this.readyState);console.log(a);console.log("ws.onerror: WebSocket error: "+a.data)};this.ws.onclose=function(){console.log("ws.onclose: WebSocket connection closed.");b.ws=null;a.readyStatus=NDN.CLOSED;a.onclose()}};
 WebSocketTransport.prototype.expressInterest=function(a,b,c){if(null!=this.ws){var d=encodeToBinaryInterest(b),e=new Uint8Array(d.length);e.set(d);var f=new PITEntry(b,c);NDN.PITTable.push(f);this.ws.send(e.buffer);3<LOG&&console.log("ws.send() returned.");c.timerID=setTimeout(function(){3<LOG&&console.log("Interest time out.");var d=NDN.PITTable.indexOf(f);0<=d&&NDN.PITTable.splice(d,1);c.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,new UpcallInfo(a,b,0,null))},1E3*b.interestLifetime)}else console.log("WebSocket connection is not established.")};
 var CSTable=[],CSEntry=function(a,b){this.name=a;this.closure=b};function getEntryForRegisteredPrefix(a){for(var b=0;b<CSTable.length;b++)if(null!=CSTable[b].name.match(a))return CSTable[b];return null}
 WebSocketTransport.prototype.registerPrefix=function(a,b,c){if(null!=this.ws){if(null==this.ccndid)return console.log("ccnd node ID unkonwn. Cannot register prefix."),-1;var a=new ForwardingEntry("selfreg",b,null,null,3,2147483647),a=encodeForwardingEntry(a),d=new SignedInfo;d.setFields();a=new ContentObject(new Name,d,a,new Signature);a.sign();a=encodeToBinaryContentObject(a);a=new Name(["ccnx",this.ccndid,"selfreg",a]);a=new Interest(a);a.scope=1;d=encodeToBinaryInterest(a);a=new Uint8Array(d.length);