Merge branch 'master' into sigverify

Conflicts:
	js/tools/build/ndn-js.js
diff --git a/js/Key.js b/js/Key.js
index d60585e..ecce73d 100644
--- a/js/Key.js
+++ b/js/Key.js
@@ -20,23 +20,25 @@
  * KeyLocator
  */
 var KeyLocatorType = {
-	  NAME:1,
-	  KEY:2,
-	  CERTIFICATE:3
+	KEY:1,
+	CERTIFICATE:2,
+	KEYNAME:3
 };
 
 var KeyLocator = function KeyLocator(_input,_type){ 
 
-    this.type=_type;
+    this.type = _type;
     
-    if (_type==KeyLocatorType.NAME){
+    if (_type == KeyLocatorType.KEYNAME){
+    	if (LOG>3) console.log('KeyLocator: SET KEYNAME');
     	this.keyName = _input;
     }
-    else if(_type==KeyLocatorType.KEY){
-    	if(LOG>4)console.log('SET KEY');
+    else if (_type == KeyLocatorType.KEY){
+    	if (LOG>3) console.log('KeyLocator: SET KEY');
     	this.publicKey = _input;
     }
-    else if(_type==KeyLocatorType.CERTIFICATE){
+    else if (_type == KeyLocatorType.CERTIFICATE){
+    	if (LOG>3) console.log('KeyLocator: SET CERTIFICATE');
     	this.certificate = _input;
     }
 
@@ -44,95 +46,94 @@
 
 KeyLocator.prototype.from_ccnb = function(decoder) {
 
-		decoder.readStartElement(this.getElementLabel());
+	decoder.readStartElement(this.getElementLabel());
 
-		if (decoder.peekStartElement(CCNProtocolDTags.Key)) {
-			try {
-				encodedKey = decoder.readBinaryElement(CCNProtocolDTags.Key);
-				// This is a DER-encoded SubjectPublicKeyInfo.
-				
-				//TODO FIX THIS, This should create a Key Object instead of keeping bytes
+	if (decoder.peekStartElement(CCNProtocolDTags.Key)) {
+		try {
+			encodedKey = decoder.readBinaryElement(CCNProtocolDTags.Key);
+			// This is a DER-encoded SubjectPublicKeyInfo.
+			
+			//TODO FIX THIS, This should create a Key Object instead of keeping bytes
 
-				this.publicKey =   encodedKey;//CryptoUtil.getPublicKey(encodedKey);
-				this.type = 2;
-				
+			this.publicKey =   encodedKey;//CryptoUtil.getPublicKey(encodedKey);
+			this.type = KeyLocatorType.KEY;
+			
 
-				if(LOG>4) console.log('PUBLIC KEY FOUND: '+ this.publicKey);
-				//this.publicKey = encodedKey;
-				
-				
-			} catch (e) {
-				throw new Error("Cannot parse key: ", e);
-			} 
+			if(LOG>4) console.log('PUBLIC KEY FOUND: '+ this.publicKey);
+			//this.publicKey = encodedKey;
+			
+			
+		} catch (e) {
+			throw new Error("Cannot parse key: ", e);
+		} 
 
-			if (null == this.publicKey) {
-				throw new Error("Cannot parse key: ");
-			}
-
-		} else if ( decoder.peekStartElement(CCNProtocolDTags.Certificate)) {
-			try {
-				encodedCert = decoder.readBinaryElement(CCNProtocolDTags.Certificate);
-				
-				/*
-				 * Certificates not yet working
-				 */
-				
-				//CertificateFactory factory = CertificateFactory.getInstance("X.509");
-				//this.certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedCert));
-				
-
-				this.certificate = encodedCert;
-				this.type = 3;
-
-				if(LOG>4) console.log('CERTIFICATE FOUND: '+ this.certificate);
-				
-			} catch ( e) {
-				throw new Error("Cannot decode certificate: " +  e);
-			}
-			if (null == this.certificate) {
-				throw new Error("Cannot parse certificate! ");
-			}
-		} else  {
-			this.type = 1;
-
-
-			this.keyName = new KeyName();
-			this.keyName.from_ccnb(decoder);
+		if (null == this.publicKey) {
+			throw new Error("Cannot parse key: ");
 		}
-		decoder.readEndElement();
+
+	} else if ( decoder.peekStartElement(CCNProtocolDTags.Certificate)) {
+		try {
+			encodedCert = decoder.readBinaryElement(CCNProtocolDTags.Certificate);
+			
+			/*
+			 * Certificates not yet working
+			 */
+			
+			//CertificateFactory factory = CertificateFactory.getInstance("X.509");
+			//this.certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedCert));
+			
+
+			this.certificate = encodedCert;
+			this.type = KeyLocatorType.CERTIFICATE;
+
+			if(LOG>4) console.log('CERTIFICATE FOUND: '+ this.certificate);
+			
+		} catch ( e) {
+			throw new Error("Cannot decode certificate: " +  e);
+		}
+		if (null == this.certificate) {
+			throw new Error("Cannot parse certificate! ");
+		}
+	} else  {
+		this.type = KeyLocatorType.KEYNAME;
+		
+		this.keyName = new KeyName();
+		this.keyName.from_ccnb(decoder);
 	}
+	decoder.readEndElement();
+};
 	
 
-	KeyLocator.prototype.to_ccnb = function( encoder) {
-		
-		if(LOG>4) console.log('type is is ' + this.type);
-		//TODO Check if Name is missing
-		if (!this.validate()) {
-			throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing.");
-		}
+KeyLocator.prototype.to_ccnb = function( encoder) {
+	
+	if(LOG>4) console.log('type is is ' + this.type);
+	//TODO Check if Name is missing
+	if (!this.validate()) {
+		throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing.");
+	}
 
+	
+	//TODO FIX THIS TOO
+	encoder.writeStartElement(this.getElementLabel());
+	
+	if (this.type == KeyLocatorType.KEY) {
+		if(LOG>5)console.log('About to encode a public key' +this.publicKey);
+		encoder.writeElement(CCNProtocolDTags.Key, this.publicKey);
 		
-		//TODO FIX THIS TOO
-		encoder.writeStartElement(this.getElementLabel());
+	} else if (this.type == KeyLocatorType.CERTIFICATE) {
 		
-		if (this.type == KeyLocatorType.KEY) {
-			if(LOG>5)console.log('About to encode a public key' +this.publicKey);
-			encoder.writeElement(CCNProtocolDTags.Key, this.publicKey);
-			
-		} else if (this.type == KeyLocatorType.CERTIFICATE) {
-			
-			try {
-				encoder.writeElement(CCNProtocolDTags.Certificate, this.certificate);
-			} catch ( e) {
-				throw new Error("CertificateEncodingException attempting to write key locator: " + e);
-			}
-			
-		} else if (this.type == KeyLocatorType.NAME) {
-			
-			this.keyName.to_ccnb(encoder);
+		try {
+			encoder.writeElement(CCNProtocolDTags.Certificate, this.certificate);
+		} catch ( e) {
+			throw new Error("CertificateEncodingException attempting to write key locator: " + e);
 		}
-		encoder.writeEndElement();
 		
+	} else if (this.type == KeyLocatorType.KEYNAME) {
+		
+		this.keyName.to_ccnb(encoder);
+	}
+	encoder.writeEndElement();
+	
 };
 
 KeyLocator.prototype.getElementLabel = function() {
@@ -147,10 +148,8 @@
  * KeyName is only used by KeyLocator.
  */
 var KeyName = function KeyName() {
-	
-
-	this.contentName = this.contentName;//contentName
-	this.publisherID =this.publisherID;//publisherID
+	this.contentName = this.contentName;  //contentName
+	this.publisherID = this.publisherID;  //publisherID
 
 };
 
@@ -193,3 +192,21 @@
 		// null signedInfo ok
 		return (null != this.contentName);
 };
+
+KeyName.prototype.matches_name = function(/*Name*/ name) {
+	var i_name = this.contentName.components;
+	var o_name = name.components;
+
+	// The intrest name is longer than the name we are checking it against.
+	if (i_name.length > o_name.length)
+            return false;
+
+	// Check if at least one of given components doesn't match.
+        for (var i = 0; i < i_name.length; ++i) {
+            if (!DataUtils.arraysEqual(i_name[i], o_name[i]))
+                return false;
+        }
+
+	return true;
+}
+
diff --git a/js/NDN.js b/js/NDN.js
index 2b36d5b..1c35221 100644
--- a/js/NDN.js
+++ b/js/NDN.js
@@ -44,6 +44,29 @@
 	this.port=port;
 }
 
+
+NDN.KeyStore = new Array();
+
+var KeyStoreEntry = function KeyStoreEntry(name, key, rsa) {
+	this.keyName = name;  // KeyName
+	this.keyHex = key;    // Raw key hex string
+	this.rsaKey = rsa;    // RSA key
+};
+
+NDN.getKeyByName = function(/* KeyName */ name) {
+	var result = null;
+	
+	for (var i = 0; i < NDN.KeyStore.length; i++) {
+		if (NDN.KeyStore[i].keyName.matches_name(name.contentName)) {
+            if (result == null || 
+                NDN.KeyStore[i].keyName.contentName.components.length > result.keyName.contentName.components.length)
+                result = NDN.KeyStore[i];
+        }
+	}
+    
+	return result;
+};
+
 // For fetching data
 NDN.PITTable = new Array();
 
diff --git a/js/WebSocketTransport.js b/js/WebSocketTransport.js
index dcec482..b9900a8 100644
--- a/js/WebSocketTransport.js
+++ b/js/WebSocketTransport.js
@@ -108,8 +108,8 @@
 				var co = new ContentObject();
 				co.from_ccnb(decoder);
 				if (LOG > 3) console.log(co);
-				nameStr = co.name.getName();
-				if (LOG > 3) console.log(nameStr);
+				var nameStr = co.name.getName();
+				console.log(nameStr);
 				
 				if (self.ccndid == null && nameStr.match(NDN.ccndIdFetcher) != null) {
 					// We are in starting phase, record publisherPublicKeyDigest in self.ccndid
@@ -135,19 +135,132 @@
 					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 & verification 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: signature verification passed");
+								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);
+								var verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, this.signature);
+								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(keyname)) {
+									console.log("Content is key itself");
+									
+									var keyHex = DataUtils.toHex(co.content).toLowerCase();
+									console.log("Key content: " + keyHex);
+									
+									var kp = keyHex.slice(56, 314);
+									var exp = keyHex.slice(318, 324);
+									
+									var rsakey = new RSAKey();
+									rsakey.setPublic(kp, exp);
+									var verified = rsakey.verifyByteArray(co.rawSignatureData, sigHex);
+									var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+									
+									currentClosure.upcall(flag, new UpcallInfo(ndn, null, 0, co));
+									
+									// Store key in cache
+									var keyEntry = new KeyStoreEntry(keylocator.keyName, keyHex, rsakey);
+									NDN.KeyStore.push(keyEntry);
+								} else {
+									console.log("Fetch key according to keylocator");
+									
+									// Check local key store
+									var keyEntry = NDN.getKeyByName(keylocator.keyName);
+									if (keyEntry) {
+										// Key found, verify now
+										console.log("Local key cache hit");
+										var rsakey = keyEntry.rsaKey;
+										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 {
+										// Not found, fetch now
+										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(keylocator.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));
+								
+								// Store key in cache
+								var keyEntry = new KeyStoreEntry(keylocator.keyName, publickeyHex, rsakey);
+								NDN.KeyStore.push(keyEntry);
+							} else {
+								var cert = keylocator.certificate;
+								console.log("KeyLocator contains CERT");
+								console.log(cert);
+								
+								// TODO: verify certificate
+							}
+						}
 					}
 				}
 			} else {
diff --git a/js/testing/.DS_Store b/js/testing/.DS_Store
index e1477e1..a3d2768 100644
--- a/js/testing/.DS_Store
+++ b/js/testing/.DS_Store
Binary files differ
diff --git a/js/testing/test-get-async.html b/js/testing/test-get-async.html
index 015eeb1..e019537 100644
--- a/js/testing/test-get-async.html
+++ b/js/testing/test-get-async.html
@@ -16,6 +16,10 @@
 		var ndn = new NDN({port:9696});

         ndn.transport.connectWebSocket(ndn);

         

+        ndn.onopen = function() {

+        	document.getElementById("testBtn").disabled = false;

+        };

+        

         var AsyncGetClosure = function AsyncGetClosure() {

         	// Inherit from Closure.

 			Closure.call(this);

@@ -26,6 +30,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 +40,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.");

 			}

@@ -56,7 +63,7 @@
 		<input id="interest" type="text" name="INTEREST" size="50" value="/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY" /> 

 	</form>

 

-	<button onclick="run()">Fetch Content</button>

+	<button id="testBtn" onclick="run()" disabled="disabled">Fetch Content</button>

 	

 	<p id="content">Content: <br/></p>

 

diff --git a/js/testing/test-publish-async.html b/js/testing/test-publish-async.html
index cb8a7c1..f69756a 100644
--- a/js/testing/test-publish-async.html
+++ b/js/testing/test-publish-async.html
@@ -17,6 +17,10 @@
 		var ndn = new NDN();

         ndn.transport.connectWebSocket(ndn);

         

+        ndn.onopen = function() {

+        	document.getElementById("testBtn").disabled = false;

+        };

+        

         var AsyncPutClosure = function AsyncPutClosure() {

         	// Inherit from Closure.

 			Closure.call(this);

@@ -29,11 +33,11 @@
 				console.log('AsyncPutClosure.upcall() called.');

 				var content = document.getElementById('content').value;

 				var interest = upcallInfo.interest;

-				var nameStr = escape(interest.name.getName());

+				var nameStr = interest.name.getName();

 				

 				var si = new SignedInfo();

 				

-				var co = new ContentObject(new Name(nameStr), si, content, new Signature()); 

+				var co = new ContentObject(new Name(nameStr), si, content, new Signature());

 				co.sign();

 				

 				upcallInfo.contentObject = co;

@@ -70,7 +74,7 @@
 		</div>

 	</form>

 	<div>

-		<button onclick="run()">Publish Content</button>

+		<button id="testBtn" onclick="run()" disabled="disabled">Publish Content</button>

 	</div>

 	

 	<p id="result"></p>

diff --git a/js/testing/test-throughput-http.html b/js/testing/test-throughput-http.html
new file mode 100644
index 0000000..3720be7
--- /dev/null
+++ b/js/testing/test-throughput-http.html
@@ -0,0 +1,45 @@
+<?xml version = "1.0" encoding="utf-8" ?>

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

+"DTD/xhtml1-strict.dtd">

+<html xmlns = "http://www.w3.org/1999/xhtml">

+<meta charset="UTF-8">

+

+<head>

+	<title>HTTP Get via XHR</title>

+	

+	<script type="text/javascript" src="../tools/build/ndn-js.js"></script>

+

+	<script type="text/javascript">

+		function run() {

+			var xhr = new XMLHttpRequest();

+			var url = document.getElementById('interest').value;

+			xhr.open("GET", url, true);

+			//xhr.responseType = "arraybuffer";

+			var T0 = new Date();

+			xhr.onload = function(e) {

+				//var arraybuffer = xhr.response; // not responseText

+				/* ... */

+				//document.getElementById('content').innerHTML += xhr.responseText;

+				var T1 = new Date();

+				document.getElementById('content').innerHTML += "<p>Time elapsed: " + (T1 - T0) + " ms</p>";

+				document.getElementById('content').innerHTML += "<p>Buffer size: " + xhr.responseText.length + "</p>";

+			};

+			xhr.send();

+		}

+		

+	</script>

+	

+</head>

+<body >

+

+	<form>

+		Please Enter an Interest:<br />

+		<input id="interest" type="text" name="INTEREST" size="50" value="marsgale_curiosity_1452.jpg" /> 

+	</form>

+

+	<button onclick="run()">Fetch Content</button>

+	

+	<p id="content">Result: <br/></p>

+

+</body>

+</html>

diff --git a/js/testing/test-throughput-ws.html b/js/testing/test-throughput-ws.html
new file mode 100644
index 0000000..6544798
--- /dev/null
+++ b/js/testing/test-throughput-ws.html
@@ -0,0 +1,215 @@
+<?xml version = "1.0" encoding="utf-8" ?>

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

+"DTD/xhtml1-strict.dtd">

+<html xmlns = "http://www.w3.org/1999/xhtml">

+<meta charset="UTF-8">

+

+<head>

+	<title>NDN Get File via WebSocket</title>

+	

+	<script type="text/javascript" src="../tools/build/ndn-js.js"></script>

+

+	<script type="text/javascript">

+		hostip = "131.179.196.232";

+		//hostip = "localhost";

+		var ndncon = new NDN({port:9696,host:hostip});

+        ndncon.transport.connectWebSocket(ndncon);

+        

+///////////////////////////////////////////////////////////////////////////////////////////////////////////

+        /*

+		 * Closure for calling expressInterest to fetch big file.

+		 */                                                

+		var ContentClosure = function ContentClosure(ndn, T0) {

+		    // Inherit from Closure.

+		    Closure.call(this);

+		    

+		    this.T0 = T0;

+		    this.ndn = ndn;

+			this.totalBlocks = 0;

+		    

+		    this.firstReceivedSegmentNumber = null;

+		    this.firstReceivedContentObject = null;

+		}

+		

+		ContentClosure.prototype.upcall = function(kind, upcallInfo) {

+		    if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {

+		    	var T1 = new Date();

+		    	document.getElementById('content').innerHTML += "<p>Interest time out.</p>";

+		        document.getElementById('content').innerHTML += "<p>Time elapsed: " + (T1 - this.T0) + " ms including 4s timeout</p>";

+		        document.getElementById('content').innerHTML += "<p>Total number of blocks: " + this.totalBlocks + "</p>";

+		        return Closure.RESULT_OK;

+		    }

+		    

+		    if (!(kind == Closure.UPCALL_CONTENT ||

+		          kind == Closure.UPCALL_CONTENT_UNVERIFIED))

+		        // The upcall is not for us.

+		        return Closure.RESULT_ERR;

+		        

+		    var contentObject = upcallInfo.contentObject;

+		    if (contentObject.content == null) {

+		        console.log("NdnProtocol.ContentClosure: contentObject.content is null\n");

+		        return Closure.RESULT_ERR;

+		    }

+		

+		    // Use the segmentNumber to load multiple segments.

+		    //var segmentNumber = ArrayToNum(contentObject.name.components[contentObject.name.components.length - 1]);

+		    var segmentNumber = DataUtils.bigEndianToUnsignedInt

+		    						(contentObject.name.components[contentObject.name.components.length - 1]);

+		    //console.log(segmentNumber);

+		    

+	        if (this.firstReceivedSegmentNumber == null) {

+	            // This is the first call.

+	            this.firstReceivedSegmentNumber = segmentNumber;

+	            if (segmentNumber != 0) {

+	                // Special case: Save this content object for later and request segment zero.

+	                this.firstReceivedContentObject = contentObject;

+	                var componentsForZero = contentObject.name.components.slice

+	                    (0, contentObject.name.components.length - 1);

+	                componentsForZero.push([0]);

+	                this.ndn.expressInterest(new Name(componentsForZero), this);

+	                return Closure.RESULT_OK;

+	            }

+	        }

+		    

+		    // Process received data here...

+		    // Count content length

+		    //nameStr = escape(contentObject.name.getName());

+			//document.getElementById('content').innerHTML += "<p>Name string: " + nameStr + "</p>";

+			//document.getElementById('content').innerHTML += "<p>Content buffer length: " + contentObject.content.length + "</p>";

+			this.totalBlocks++;

+		    

+		    // Check for the special case if the saved content is for the next segment that we need.

+		    if (this.firstReceivedContentObject != null && 

+		        this.firstReceivedSegmentNumber == segmentNumber + 1) {

+		        // Substitute the saved contentObject send its content and keep going.

+		        contentObject = this.firstReceivedContentObject;

+		        segmentNumber = segmentNumber + 1;

+		        // Clear firstReceivedContentObject to save memory.

+		        this.firstReceivedContentObject = null;

+		        

+		        // Process received data here...

+		    	// Count content length

+		    	//nameStr = escape(contentObject.name.getName());

+				//document.getElementById('content').innerHTML += "<p>Name string: " + nameStr + "</p>";

+				//document.getElementById('content').innerHTML += "<p>Content buffer length: " + contentObject.content.length + "</p>";

+				this.totalBlocks++;

+		    }

+		

+		    var finalSegmentNumber = null;

+		    if (contentObject.signedInfo != null && contentObject.signedInfo.finalBlockID != null)

+		        //finalSegmentNumber = ArrayToNum(contentObject.signedInfo.finalBlockID);

+		        finalSegmentNumber = DataUtils.bigEndianToUnsignedInt(contentObject.signedInfo.finalBlockID);

+		    

+		    if (finalSegmentNumber == null || segmentNumber != finalSegmentNumber) {

+		        // Make a name for the next segment and get it.

+		        //var segmentNumberPlus1 = NumToArray(segmentNumber + 1);

+		        // Make a name for the next segment and get it.

+		        var segmentNumberPlus1 = DataUtils.nonNegativeIntToBigEndian(segmentNumber + 1);

+		        // Put a 0 byte in front.

+		        var nextSegmentNumber = new Uint8Array(segmentNumberPlus1.length + 1);

+		        nextSegmentNumber.set(segmentNumberPlus1, 1);

+        		

+		        var components = contentObject.name.components.slice

+		            (0, contentObject.name.components.length - 1);

+		        components.push(nextSegmentNumber);

+		        //components.push(segmentNumberPlus1);

+		        this.ndn.expressInterest(new Name(components), this);

+		    }

+		    else {

+		        // Final block received.

+		        // Record stop time

+		        var T1 = new Date();

+		        document.getElementById('content').innerHTML += "<p>Final block received.</p>";

+		        document.getElementById('content').innerHTML += "<p>Time elapsed: " + (T1 - this.T0) + " ms</p>";

+		        document.getElementById('content').innerHTML += "<p>Total number of blocks: " + this.totalBlocks + "</p>";

+		    }

+		        

+		    return Closure.RESULT_OK;

+		};

+		

+		/*

+		 * Convert the big endian Uint8Array to an unsigned int.

+		 * Don't check for overflow.

+		 */

+		function ArrayToNum(bytes) {

+		    var result = 0;

+		    for (var i = 0; i < bytes.length; ++i) {

+		        result = result * 10;

+		        result += (bytes[i] - 48);

+		    }

+		    return result;

+		};

+		

+		/*

+		 * Convert the int value to a new big endian Uint8Array and return.

+		 * If value is 0 or negative, return Uint8Array(0). 

+		 */

+		function NumToArray(value) {

+		    value = Math.round(value);

+		    if (value <= 0)

+		        return new Uint8Array(0);

+		    

+		    numString = value.toString();

+		    var size = numString.length;

+		    var result = new Uint8Array(size);

+		    for (i = 0; i < size; i++) {

+		    	result[i] = numString.charCodeAt(i);

+		    }

+		    return result;

+		};

+///////////////////////////////////////////////////////////////////////////////////////////////////////////

+        

+        /*

+        var AsyncGetClosure = function AsyncGetClosure(T0) {

+        	this.T0 = T0;  // Start time

+        	// Inherit from Closure.

+			Closure.call(this);

+		};

+		

+		AsyncGetClosure.prototype.upcall = function(kind, upcallInfo) {

+			//console.log("Closure.upcall() executed.");

+			if (kind == Closure.UPCALL_FINAL) {

+				// Do nothing.

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

+				var T1 = new Date();

+				

+				var content = upcallInfo.contentObject;

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

+				document.getElementById('content').innerHTML += "<p>Time elapsed: " + (T1 - this.T0) + " ms</p>"; 

+				document.getElementById('content').innerHTML += "<p>Name string: " + nameStr + "</p>";

+				document.getElementById('content').innerHTML += "<p>Content buffer length: " + content.content.length + "</p>";

+				//console.log("In callback, nameStr: " + nameStr);

+				//console.log("In callback, content: ");

+				//console.log(content.content.length);

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

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

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

+			}

+			return Closure.RESULT_OK;

+		};

+		*/

+		

+		function run() {

+			document.getElementById('content').innerHTML += "<p>Started...</p>";

+			//ndn.expressInterest(new Name(document.getElementById('interest').value), new AsyncGetClosure( new Date() ));

+			var name = new Name(document.getElementById('interest').value);

+            ndncon.expressInterest( name, 

+                    new ContentClosure( ndncon, new Date() ));

+		}

+		

+	</script>

+	

+</head>

+<body >

+

+	<form>

+		Please Enter an Interest:<br />

+		<input id="interest" type="text" name="INTEREST" size="50" value="/wentao.shang/choir_jail.png" /> 

+	</form>

+

+	<button onclick="run()">Fetch Content</button>

+	

+	<p id="content">Result: <br/></p>

+

+</body>

+</html>

diff --git a/js/testing/test-throughput-xpcom.html b/js/testing/test-throughput-xpcom.html
new file mode 100644
index 0000000..a8b3f46
--- /dev/null
+++ b/js/testing/test-throughput-xpcom.html
@@ -0,0 +1,27 @@
+<?xml version = "1.0" encoding="utf-8" ?>

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

+"DTD/xhtml1-strict.dtd">

+<html xmlns = "http://www.w3.org/1999/xhtml">

+<meta charset="UTF-8">

+

+<head>

+	<title>NDN Get via XPCOM</title>

+</head>

+<body >

+

+	<h1>NDN Protocol Examples</h1>

+	<h2>Segmented files</h2>

+	Here is a small text file:

+	<br/>

+	<a target="_blank" class="moz-txt-link-freetext"

+	 href="ndn:/wentao.shang/mars.jpg">ndn:/wentao.shang/mars.jpg</a>

+	<br/>

+	

+	Here is a large image file:

+	<br/>

+	<a target="_blank" class="moz-txt-link-freetext"

+	 href="ndn:/wentao.shang/choir_jail.png">ndn:/wentao.shang/choir_jail.png</a>

+	<br/>

+

+</body>

+</html>

diff --git a/js/tools/build/.gitignore b/js/tools/build/.gitignore
new file mode 100644
index 0000000..6faf4d3
--- /dev/null
+++ b/js/tools/build/.gitignore
@@ -0,0 +1,2 @@
+ndn-js-uncomp.js
+ndn-js.js
diff --git a/js/tools/build/ndn-js-uncomp.js b/js/tools/build/ndn-js-uncomp.js
index b5064ef..2f6f4f8 100644
--- a/js/tools/build/ndn-js-uncomp.js
+++ b/js/tools/build/ndn-js-uncomp.js
@@ -110,6 +110,29 @@
 	this.port=port;
 }
 
+
+NDN.KeyStore = new Array();
+
+var KeyStoreEntry = function KeyStoreEntry(name, key, rsa) {
+	this.keyName = name;  // KeyName
+	this.keyHex = key;    // Raw key hex string
+	this.rsaKey = rsa;    // RSA key
+};
+
+NDN.getKeyByName = function(/* KeyName */ name) {
+	var result = null;
+	
+	for (var i = 0; i < NDN.KeyStore.length; i++) {
+		if (NDN.KeyStore[i].keyName.matches_name(name.contentName)) {
+            if (result == null || 
+                NDN.KeyStore[i].keyName.contentName.components.length > result.keyName.contentName.components.length)
+                result = NDN.KeyStore[i];
+        }
+	}
+    
+	return result;
+};
+
 // For fetching data
 NDN.PITTable = new Array();
 
@@ -364,8 +387,8 @@
 				var co = new ContentObject();
 				co.from_ccnb(decoder);
 				if (LOG > 3) console.log(co);
-				nameStr = co.name.getName();
-				if (LOG > 3) console.log(nameStr);
+				var nameStr = co.name.getName();
+				console.log(nameStr);
 				
 				if (self.ccndid == null && nameStr.match(NDN.ccndIdFetcher) != null) {
 					// We are in starting phase, record publisherPublicKeyDigest in self.ccndid
@@ -391,19 +414,132 @@
 					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 & verification 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: signature verification passed");
+								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);
+								var verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, this.signature);
+								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(keyname)) {
+									console.log("Content is key itself");
+									
+									var keyHex = DataUtils.toHex(co.content).toLowerCase();
+									console.log("Key content: " + keyHex);
+									
+									var kp = keyHex.slice(56, 314);
+									var exp = keyHex.slice(318, 324);
+									
+									var rsakey = new RSAKey();
+									rsakey.setPublic(kp, exp);
+									var verified = rsakey.verifyByteArray(co.rawSignatureData, sigHex);
+									var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
+									
+									currentClosure.upcall(flag, new UpcallInfo(ndn, null, 0, co));
+									
+									// Store key in cache
+									var keyEntry = new KeyStoreEntry(keylocator.keyName, keyHex, rsakey);
+									NDN.KeyStore.push(keyEntry);
+								} else {
+									console.log("Fetch key according to keylocator");
+									
+									// Check local key store
+									var keyEntry = NDN.getKeyByName(keylocator.keyName);
+									if (keyEntry) {
+										// Key found, verify now
+										console.log("Local key cache hit");
+										var rsakey = keyEntry.rsaKey;
+										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 {
+										// Not found, fetch now
+										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(keylocator.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));
+								
+								// Store key in cache
+								var keyEntry = new KeyStoreEntry(keylocator.keyName, publickeyHex, rsakey);
+								NDN.KeyStore.push(keyEntry);
+							} else {
+								var cert = keylocator.certificate;
+								console.log("KeyLocator contains CERT");
+								console.log(cert);
+								
+								// TODO: verify certificate
+							}
+						}
 					}
 				}
 			} else {
@@ -1919,23 +2055,25 @@
  * KeyLocator
  */
 var KeyLocatorType = {
-	  NAME:1,
-	  KEY:2,
-	  CERTIFICATE:3
+	KEY:1,
+	CERTIFICATE:2,
+	KEYNAME:3
 };
 
 var KeyLocator = function KeyLocator(_input,_type){ 
 
-    this.type=_type;
+    this.type = _type;
     
-    if (_type==KeyLocatorType.NAME){
+    if (_type == KeyLocatorType.KEYNAME){
+    	if (LOG>3) console.log('KeyLocator: SET KEYNAME');
     	this.keyName = _input;
     }
-    else if(_type==KeyLocatorType.KEY){
-    	if(LOG>4)console.log('SET KEY');
+    else if (_type == KeyLocatorType.KEY){
+    	if (LOG>3) console.log('KeyLocator: SET KEY');
     	this.publicKey = _input;
     }
-    else if(_type==KeyLocatorType.CERTIFICATE){
+    else if (_type == KeyLocatorType.CERTIFICATE){
+    	if (LOG>3) console.log('KeyLocator: SET CERTIFICATE');
     	this.certificate = _input;
     }
 
@@ -1943,95 +2081,94 @@
 
 KeyLocator.prototype.from_ccnb = function(decoder) {
 
-		decoder.readStartElement(this.getElementLabel());
+	decoder.readStartElement(this.getElementLabel());
 
-		if (decoder.peekStartElement(CCNProtocolDTags.Key)) {
-			try {
-				encodedKey = decoder.readBinaryElement(CCNProtocolDTags.Key);
-				// This is a DER-encoded SubjectPublicKeyInfo.
-				
-				//TODO FIX THIS, This should create a Key Object instead of keeping bytes
+	if (decoder.peekStartElement(CCNProtocolDTags.Key)) {
+		try {
+			encodedKey = decoder.readBinaryElement(CCNProtocolDTags.Key);
+			// This is a DER-encoded SubjectPublicKeyInfo.
+			
+			//TODO FIX THIS, This should create a Key Object instead of keeping bytes
 
-				this.publicKey =   encodedKey;//CryptoUtil.getPublicKey(encodedKey);
-				this.type = 2;
-				
+			this.publicKey =   encodedKey;//CryptoUtil.getPublicKey(encodedKey);
+			this.type = KeyLocatorType.KEY;
+			
 
-				if(LOG>4) console.log('PUBLIC KEY FOUND: '+ this.publicKey);
-				//this.publicKey = encodedKey;
-				
-				
-			} catch (e) {
-				throw new Error("Cannot parse key: ", e);
-			} 
+			if(LOG>4) console.log('PUBLIC KEY FOUND: '+ this.publicKey);
+			//this.publicKey = encodedKey;
+			
+			
+		} catch (e) {
+			throw new Error("Cannot parse key: ", e);
+		} 
 
-			if (null == this.publicKey) {
-				throw new Error("Cannot parse key: ");
-			}
-
-		} else if ( decoder.peekStartElement(CCNProtocolDTags.Certificate)) {
-			try {
-				encodedCert = decoder.readBinaryElement(CCNProtocolDTags.Certificate);
-				
-				/*
-				 * Certificates not yet working
-				 */
-				
-				//CertificateFactory factory = CertificateFactory.getInstance("X.509");
-				//this.certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedCert));
-				
-
-				this.certificate = encodedCert;
-				this.type = 3;
-
-				if(LOG>4) console.log('CERTIFICATE FOUND: '+ this.certificate);
-				
-			} catch ( e) {
-				throw new Error("Cannot decode certificate: " +  e);
-			}
-			if (null == this.certificate) {
-				throw new Error("Cannot parse certificate! ");
-			}
-		} else  {
-			this.type = 1;
-
-
-			this.keyName = new KeyName();
-			this.keyName.from_ccnb(decoder);
+		if (null == this.publicKey) {
+			throw new Error("Cannot parse key: ");
 		}
-		decoder.readEndElement();
+
+	} else if ( decoder.peekStartElement(CCNProtocolDTags.Certificate)) {
+		try {
+			encodedCert = decoder.readBinaryElement(CCNProtocolDTags.Certificate);
+			
+			/*
+			 * Certificates not yet working
+			 */
+			
+			//CertificateFactory factory = CertificateFactory.getInstance("X.509");
+			//this.certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedCert));
+			
+
+			this.certificate = encodedCert;
+			this.type = KeyLocatorType.CERTIFICATE;
+
+			if(LOG>4) console.log('CERTIFICATE FOUND: '+ this.certificate);
+			
+		} catch ( e) {
+			throw new Error("Cannot decode certificate: " +  e);
+		}
+		if (null == this.certificate) {
+			throw new Error("Cannot parse certificate! ");
+		}
+	} else  {
+		this.type = KeyLocatorType.KEYNAME;
+		
+		this.keyName = new KeyName();
+		this.keyName.from_ccnb(decoder);
 	}
+	decoder.readEndElement();
+};
 	
 
-	KeyLocator.prototype.to_ccnb = function( encoder) {
-		
-		if(LOG>4) console.log('type is is ' + this.type);
-		//TODO Check if Name is missing
-		if (!this.validate()) {
-			throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing.");
-		}
+KeyLocator.prototype.to_ccnb = function( encoder) {
+	
+	if(LOG>4) console.log('type is is ' + this.type);
+	//TODO Check if Name is missing
+	if (!this.validate()) {
+		throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing.");
+	}
 
+	
+	//TODO FIX THIS TOO
+	encoder.writeStartElement(this.getElementLabel());
+	
+	if (this.type == KeyLocatorType.KEY) {
+		if(LOG>5)console.log('About to encode a public key' +this.publicKey);
+		encoder.writeElement(CCNProtocolDTags.Key, this.publicKey);
 		
-		//TODO FIX THIS TOO
-		encoder.writeStartElement(this.getElementLabel());
+	} else if (this.type == KeyLocatorType.CERTIFICATE) {
 		
-		if (this.type == KeyLocatorType.KEY) {
-			if(LOG>5)console.log('About to encode a public key' +this.publicKey);
-			encoder.writeElement(CCNProtocolDTags.Key, this.publicKey);
-			
-		} else if (this.type == KeyLocatorType.CERTIFICATE) {
-			
-			try {
-				encoder.writeElement(CCNProtocolDTags.Certificate, this.certificate);
-			} catch ( e) {
-				throw new Error("CertificateEncodingException attempting to write key locator: " + e);
-			}
-			
-		} else if (this.type == KeyLocatorType.NAME) {
-			
-			this.keyName.to_ccnb(encoder);
+		try {
+			encoder.writeElement(CCNProtocolDTags.Certificate, this.certificate);
+		} catch ( e) {
+			throw new Error("CertificateEncodingException attempting to write key locator: " + e);
 		}
-		encoder.writeEndElement();
 		
+	} else if (this.type == KeyLocatorType.KEYNAME) {
+		
+		this.keyName.to_ccnb(encoder);
+	}
+	encoder.writeEndElement();
+	
 };
 
 KeyLocator.prototype.getElementLabel = function() {
@@ -2046,10 +2183,8 @@
  * KeyName is only used by KeyLocator.
  */
 var KeyName = function KeyName() {
-	
-
-	this.contentName = this.contentName;//contentName
-	this.publisherID =this.publisherID;//publisherID
+	this.contentName = this.contentName;  //contentName
+	this.publisherID = this.publisherID;  //publisherID
 
 };
 
@@ -2092,6 +2227,24 @@
 		// null signedInfo ok
 		return (null != this.contentName);
 };
+
+KeyName.prototype.matches_name = function(/*Name*/ name) {
+	var i_name = this.contentName.components;
+	var o_name = name.components;
+
+	// The intrest name is longer than the name we are checking it against.
+	if (i_name.length > o_name.length)
+            return false;
+
+	// Check if at least one of given components doesn't match.
+        for (var i = 0; i < i_name.length; ++i) {
+            if (!DataUtils.arraysEqual(i_name[i], o_name[i]))
+                return false;
+        }
+
+	return true;
+}
+
 /**
  * @author: Meki Cheraoui
  * See COPYING for copyright and distribution information.
diff --git a/js/tools/build/ndn-js.js b/js/tools/build/ndn-js.js
index 578347e..0eb7b55 100644
--- a/js/tools/build/ndn-js.js
+++ b/js/tools/build/ndn-js.js
@@ -1,8 +1,8 @@
 var Closure=function(){this.ndn_data=null;this.ndn_data_dirty=!1;this.timerID=-1};Closure.RESULT_ERR=-1;Closure.RESULT_OK=0;Closure.RESULT_REEXPRESS=1;Closure.RESULT_INTEREST_CONSUMED=2;Closure.RESULT_VERIFY=3;Closure.RESULT_FETCHKEY=4;Closure.UPCALL_FINAL=0;Closure.UPCALL_INTEREST=1;Closure.UPCALL_CONSUMED_INTEREST=2;Closure.UPCALL_CONTENT=3;Closure.UPCALL_INTEREST_TIMED_OUT=4;Closure.UPCALL_CONTENT_UNVERIFIED=5;Closure.UPCALL_CONTENT_BAD=6;Closure.prototype.upcall=function(){return Closure.RESULT_OK};
 var UpcallInfo=function(a,b,c,d){this.ndn=a;this.interest=b;this.matchedComps=c;this.contentObject=d};UpcallInfo.prototype.toString=function(){var a="ndn = "+this.ndn,a=a+("\nInterest = "+this.interest),a=a+("\nmatchedComps = "+this.matchedComps);return a+="\nContentObject: "+this.contentObject};
 var LOG=0,NDN=function NDN(b){b=b||{};this.transport=(b.getTransport||function(){return new WebSocketTransport})();this.getHostAndPort=b.getHostAndPort||this.transport.defaultGetHostAndPort;this.host=void 0!==b.host?b.host:"localhost";this.port=b.port||9696;this.readyStatus=NDN.UNOPEN;this.onopen=b.onopen||function(){3<LOG&&console.log("NDN connection established.")};this.onclose=b.onclose||function(){3<LOG&&console.log("NDN connection closed.")}};NDN.UNOPEN=0;NDN.OPENED=1;NDN.CLOSED=2;
-NDN.ccndIdFetcher="/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY";NDN.prototype.createRoute=function(a,b){this.host=a;this.port=b};NDN.PITTable=[];var PITEntry=function(a,b){this.interest=a;this.closure=b};NDN.getEntryForExpressedInterest=function(a){for(var b=null,c=0;c<NDN.PITTable.length;c++)if(NDN.PITTable[c].interest.matches_name(a)&&(null==b||NDN.PITTable[c].interest.name.components.length>b.interest.name.components.length))b=NDN.PITTable[c];return b};
-NDN.makeShuffledGetHostAndPort=function(a,b){a=a.slice(0,a.length);DataUtils.shuffle(a);return function(){return 0==a.length?null:{host:a.splice(0,1)[0],port:b}}};
+NDN.ccndIdFetcher="/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY";NDN.prototype.createRoute=function(a,b){this.host=a;this.port=b};NDN.KeyStore=[];var KeyStoreEntry=function(a,b,c){this.keyName=a;this.keyHex=b;this.rsaKey=c};NDN.getKeyByName=function(a){for(var b=null,c=0;c<NDN.KeyStore.length;c++)if(NDN.KeyStore[c].keyName.matches_name(a.contentName)&&(null==b||NDN.KeyStore[c].keyName.contentName.components.length>b.keyName.contentName.components.length))b=NDN.KeyStore[c];return b};NDN.PITTable=[];
+var PITEntry=function(a,b){this.interest=a;this.closure=b};NDN.getEntryForExpressedInterest=function(a){for(var b=null,c=0;c<NDN.PITTable.length;c++)if(NDN.PITTable[c].interest.matches_name(a)&&(null==b||NDN.PITTable[c].interest.name.components.length>b.interest.name.components.length))b=NDN.PITTable[c];return b};NDN.makeShuffledGetHostAndPort=function(a,b){a=a.slice(0,a.length);DataUtils.shuffle(a);return function(){return 0==a.length?null:{host:a.splice(0,1)[0],port:b}}};
 NDN.prototype.expressInterest=function(a,b,c){a=new Interest(a);null!=c?(a.minSuffixComponents=c.minSuffixComponents,a.maxSuffixComponents=c.maxSuffixComponents,a.publisherPublicKeyDigest=c.publisherPublicKeyDigest,a.exclude=c.exclude,a.childSelector=c.childSelector,a.answerOriginKind=c.answerOriginKind,a.scope=c.scope,a.interestLifetime=c.interestLifetime):a.interestLifetime=4E3;null==this.host||null==this.port?null==this.getHostAndPort?console.log("ERROR: host OR port NOT SET"):this.connectAndExpressInterest(a,
 b):this.transport.expressInterest(this,a,b)};NDN.prototype.registerPrefix=function(a,b,c){return this.transport.registerPrefix(this,a,b,c)};
 NDN.prototype.connectAndExpressInterest=function(a,b){var c=this.getHostAndPort();if(null==c)console.log("ERROR: No more hosts from getHostAndPort"),this.host=null;else if(c.host==this.host&&c.port==this.port)console.log("ERROR: The host returned by getHostAndPort is not alive: "+this.host+":"+this.port);else{this.host=c.host;this.port=c.port;console.log("Trying host from getHostAndPort: "+this.host);c=new Interest(new Name(NDN.ccndIdFetcher));c.interestLifetime=4E3;var d=this,e=setTimeout(function(){console.log("Timeout waiting for host "+
@@ -11,11 +11,15 @@
 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){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);f=getEntryForRegisteredPrefix(f);null!=f&&(d=new UpcallInfo(a,d,0,null),f.closure.upcall(Closure.UPCALL_INTEREST,
-d)==Closure.RESULT_INTEREST_CONSUMED&&null!=d.contentObject&&(f=encodeToBinaryContentObject(d.contentObject),d=new Uint8Array(f.length),d.set(f),b.ws.send(d.buffer)))}else if(c.peekStartElement(CCNProtocolDTags.ContentObject))if(3<LOG&&console.log("ContentObject packet received."),d=new ContentObject,d.from_ccnb(c),3<LOG&&console.log(d),f=d.name.getName(),3<LOG&&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(f=NDN.getEntryForExpressedInterest(d.name),null!=f){clearTimeout(f.closure.timerID);var g=NDN.PITTable.indexOf(f);0<=g&&NDN.PITTable.splice(g,1);f.closure.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,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=4E3;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()}};
+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(3<LOG&&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);h=g.closure;clearTimeout(h.timerID);var i=!1,j=function(a,b,c,d){this.contentObject=a;this.closure=b;this.keyName=c;this.signature=d;Closure.call(this)};j.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: signature verification passed");var d=DataUtils.toHex(c.contentObject.content).toLowerCase(),e=d.slice(56,314),d=d.slice(318,324),f=new RSAKey;f.setPublic(e,d);e=!0==f.verifyByteArray(this.contentObject.rawSignatureData,this.signature)?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD;this.closure.upcall(e,new UpcallInfo(a,
+null,0,this.contentObject))}};if(d.signedInfo&&d.signedInfo.locator&&d.signature)if(3<LOG&&console.log("Key verification..."),i=DataUtils.toHex(d.signature.signature).toLowerCase(),g=d.signedInfo.locator,g.type==KeyLocatorType.KEYNAME){console.log("KeyLocator contains KEYNAME");var l=g.keyName.contentName.getName();console.log(l);if(f.match(l)){console.log("Content is key itself");j=DataUtils.toHex(d.content).toLowerCase();console.log("Key content: "+j);var l=j.slice(56,314),m=j.slice(318,324),f=
+new RSAKey;f.setPublic(l,m);i=f.verifyByteArray(d.rawSignatureData,i);i=!0==i?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD;h.upcall(i,new UpcallInfo(a,null,0,d));f=new KeyStoreEntry(g.keyName,j,f);NDN.KeyStore.push(f)}else console.log("Fetch key according to keylocator"),(f=NDN.getKeyByName(g.keyName))?(console.log("Local key cache hit"),f=f.rsaKey,i=f.verifyByteArray(d.rawSignatureData,i),i=!0==i?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,h.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,
+null,0,d))):(h=new j(d,h,l,i),d=new Interest(g.keyName.contentName.getPrefix(4)),d.interestLifetime=4,b.expressInterest(a,d,h))}else g.type==KeyLocatorType.KEY?(console.log("Keylocator contains KEY"),j=DataUtils.toHex(g.publicKey).toLowerCase(),console.log(j),l=j.slice(56,314),m=j.slice(318,324),f=new RSAKey,f.setPublic(l,m),i=f.verifyByteArray(d.rawSignatureData,i),i=!0==i?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,h.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,d)),f=new KeyStoreEntry(g.keyName,
+j,f),NDN.KeyStore.push(f)):(d=g.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=4E3;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))},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);
@@ -57,10 +61,10 @@
 SignedInfo.prototype.to_ccnb=function(a){if(!this.validate())throw Error("Cannot encode : field values missing.");a.writeStartElement(this.getElementLabel());null!=this.publisher&&(3<LOG&&console.log("ENCODING PUBLISHER KEY"+this.publisher.publisherPublicKeyDigest),this.publisher.to_ccnb(a));null!=this.timestamp&&a.writeDateTime(CCNProtocolDTags.Timestamp,this.timestamp);null!=this.type&&0!=this.type&&a.writeElement(CCNProtocolDTags.type,this.type);null!=this.freshnessSeconds&&a.writeElement(CCNProtocolDTags.FreshnessSeconds,
 this.freshnessSeconds);null!=this.finalBlockID&&a.writeElement(CCNProtocolDTags.FinalBlockID,this.finalBlockID);null!=this.locator&&this.locator.to_ccnb(a);a.writeEndElement()};SignedInfo.prototype.valueToType=function(){return null};SignedInfo.prototype.getElementLabel=function(){return CCNProtocolDTags.SignedInfo};SignedInfo.prototype.validate=function(){return null==this.publisher||null==this.timestamp||null==this.locator?!1:!0};
 var DateFormat=function(){var a=/d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,b=/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,c=/[^-+\dA-Z]/g,d=function(a,b){a=String(a);for(b=b||2;a.length<b;)a="0"+a;return a};return function(e,f,g){var h=dateFormat;1==arguments.length&&("[object String]"==Object.prototype.toString.call(e)&&!/\d/.test(e))&&(f=e,e=void 0);e=e?new Date(e):new Date;if(isNaN(e))throw SyntaxError("invalid date");
-f=String(h.masks[f]||f||h.masks["default"]);"UTC:"==f.slice(0,4)&&(f=f.slice(4),g=!0);var i=g?"getUTC":"get",k=e[i+"Date"](),m=e[i+"Day"](),l=e[i+"Month"](),n=e[i+"FullYear"](),j=e[i+"Hours"](),p=e[i+"Minutes"](),r=e[i+"Seconds"](),i=e[i+"Milliseconds"](),q=g?0:e.getTimezoneOffset(),s={d:k,dd:d(k),ddd:h.i18n.dayNames[m],dddd:h.i18n.dayNames[m+7],m:l+1,mm:d(l+1),mmm:h.i18n.monthNames[l],mmmm:h.i18n.monthNames[l+12],yy:String(n).slice(2),yyyy:n,h:j%12||12,hh:d(j%12||12),H:j,HH:d(j),M:p,MM:d(p),s:r,
-ss:d(r),l:d(i,3),L:d(99<i?Math.round(i/10):i),t:12>j?"a":"p",tt:12>j?"am":"pm",T:12>j?"A":"P",TT:12>j?"AM":"PM",Z:g?"UTC":(String(e).match(b)||[""]).pop().replace(c,""),o:(0<q?"-":"+")+d(100*Math.floor(Math.abs(q)/60)+Math.abs(q)%60,4),S:["th","st","nd","rd"][3<k%10?0:(10!=k%100-k%10)*k%10]};return f.replace(a,function(a){return a in s?s[a]:a.slice(1,a.length-1)})}}();
+f=String(h.masks[f]||f||h.masks["default"]);"UTC:"==f.slice(0,4)&&(f=f.slice(4),g=!0);var i=g?"getUTC":"get",j=e[i+"Date"](),l=e[i+"Day"](),m=e[i+"Month"](),n=e[i+"FullYear"](),k=e[i+"Hours"](),p=e[i+"Minutes"](),r=e[i+"Seconds"](),i=e[i+"Milliseconds"](),q=g?0:e.getTimezoneOffset(),s={d:j,dd:d(j),ddd:h.i18n.dayNames[l],dddd:h.i18n.dayNames[l+7],m:m+1,mm:d(m+1),mmm:h.i18n.monthNames[m],mmmm:h.i18n.monthNames[m+12],yy:String(n).slice(2),yyyy:n,h:k%12||12,hh:d(k%12||12),H:k,HH:d(k),M:p,MM:d(p),s:r,
+ss:d(r),l:d(i,3),L:d(99<i?Math.round(i/10):i),t:12>k?"a":"p",tt:12>k?"am":"pm",T:12>k?"A":"P",TT:12>k?"AM":"PM",Z:g?"UTC":(String(e).match(b)||[""]).pop().replace(c,""),o:(0<q?"-":"+")+d(100*Math.floor(Math.abs(q)/60)+Math.abs(q)%60,4),S:["th","st","nd","rd"][3<j%10?0:(10!=j%100-j%10)*j%10]};return f.replace(a,function(a){return a in s?s[a]:a.slice(1,a.length-1)})}}();
 DateFormat.masks={"default":"ddd mmm dd yyyy HH:MM:ss",shortDate:"m/d/yy",mediumDate:"mmm d, yyyy",longDate:"mmmm d, yyyy",fullDate:"dddd, mmmm d, yyyy",shortTime:"h:MM TT",mediumTime:"h:MM:ss TT",longTime:"h:MM:ss TT Z",isoDate:"yyyy-mm-dd",isoTime:"HH:MM:ss",isoDateTime:"yyyy-mm-dd'T'HH:MM:ss",isoUtcDateTime:"UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"};DateFormat.i18n={dayNames:"Sun Mon Tue Wed Thu Fri Sat Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),monthNames:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec January February March April May June July August September October November December".split(" ")};
-Date.prototype.format=function(a,b){return dateFormat(this,a,b)};var Interest=function(a,b,c,d,e,f,g,h,i,k,m){this.name=a;this.faceInstance=b;this.maxSuffixComponents=d;this.minSuffixComponents=c;this.publisherPublicKeyDigest=e;this.exclude=f;this.childSelector=g;this.answerOriginKind=h;this.scope=i;this.interestLifetime=k;this.nonce=m};Interest.RECURSIVE_POSTFIX="*";Interest.CHILD_SELECTOR_LEFT=0;Interest.CHILD_SELECTOR_RIGHT=1;Interest.ANSWER_CONTENT_STORE=1;Interest.ANSWER_GENERATED=2;
+Date.prototype.format=function(a,b){return dateFormat(this,a,b)};var Interest=function(a,b,c,d,e,f,g,h,i,j,l){this.name=a;this.faceInstance=b;this.maxSuffixComponents=d;this.minSuffixComponents=c;this.publisherPublicKeyDigest=e;this.exclude=f;this.childSelector=g;this.answerOriginKind=h;this.scope=i;this.interestLifetime=j;this.nonce=l};Interest.RECURSIVE_POSTFIX="*";Interest.CHILD_SELECTOR_LEFT=0;Interest.CHILD_SELECTOR_RIGHT=1;Interest.ANSWER_CONTENT_STORE=1;Interest.ANSWER_GENERATED=2;
 Interest.ANSWER_STALE=4;Interest.MARK_STALE=16;Interest.DEFAULT_ANSWER_ORIGIN_KIND=Interest.ANSWER_CONTENT_STORE|Interest.ANSWER_GENERATED;
 Interest.prototype.from_ccnb=function(a){a.readStartElement(CCNProtocolDTags.Interest);this.name=new Name;this.name.from_ccnb(a);a.peekStartElement(CCNProtocolDTags.MinSuffixComponents)&&(this.minSuffixComponents=a.readIntegerElement(CCNProtocolDTags.MinSuffixComponents));a.peekStartElement(CCNProtocolDTags.MaxSuffixComponents)&&(this.maxSuffixComponents=a.readIntegerElement(CCNProtocolDTags.MaxSuffixComponents));a.peekStartElement(CCNProtocolDTags.PublisherPublicKeyDigest)&&(this.publisherPublicKeyDigest=
 new PublisherPublicKeyDigest,this.publisherPublicKeyDigest.from_ccnb(a));a.peekStartElement(CCNProtocolDTags.Exclude)&&(this.exclude=new Exclude,this.exclude.from_ccnb(a));a.peekStartElement(CCNProtocolDTags.ChildSelector)&&(this.childSelector=a.readIntegerElement(CCNProtocolDTags.ChildSelector));a.peekStartElement(CCNProtocolDTags.AnswerOriginKind)&&(this.answerOriginKind=a.readIntegerElement(CCNProtocolDTags.AnswerOriginKind));a.peekStartElement(CCNProtocolDTags.Scope)&&(this.scope=a.readIntegerElement(CCNProtocolDTags.Scope));
@@ -70,13 +74,14 @@
 Interest.prototype.matches_name=function(a){var b=this.name.components,a=a.components;if(b.length>a.length)return!1;for(var c=0;c<b.length;++c)if(!DataUtils.arraysEqual(b[c],a[c]))return!1;return!0};var Exclude=function(a){this.OPTIMUM_FILTER_SIZE=100;this.values=a};Exclude.prototype.from_ccnb=function(a){a.readStartElement(this.getElementLabel());a.readEndElement()};
 Exclude.prototype.to_ccnb=function(a){if(!validate())throw new ContentEncodingException("Cannot encode "+this.getClass().getName()+": field values missing.");empty()||(a.writeStartElement(getElementLabel()),a.writeEndElement())};Exclude.prototype.getElementLabel=function(){return CCNProtocolDTags.Exclude};var ExcludeAny=function(){};ExcludeAny.prototype.from_ccnb=function(a){a.readStartElement(this.getElementLabel());a.readEndElement()};
 ExcludeAny.prototype.to_ccnb=function(a){a.writeStartElement(this.getElementLabel());a.writeEndElement()};ExcludeAny.prototype.getElementLabel=function(){return CCNProtocolDTags.Any};var ExcludeComponent=function(a){this.body=a};ExcludeComponent.prototype.from_ccnb=function(a){this.body=a.readBinaryElement(this.getElementLabel())};ExcludeComponent.prototype.to_ccnb=function(a){a.writeElement(this.getElementLabel(),this.body)};ExcludeComponent.prototype.getElementLabel=function(){return CCNProtocolDTags.Component};
-var Key=function(){},KeyLocatorType={NAME:1,KEY:2,CERTIFICATE:3},KeyLocator=function(a,b){this.type=b;b==KeyLocatorType.NAME?this.keyName=a:b==KeyLocatorType.KEY?(4<LOG&&console.log("SET KEY"),this.publicKey=a):b==KeyLocatorType.CERTIFICATE&&(this.certificate=a)};
-KeyLocator.prototype.from_ccnb=function(a){a.readStartElement(this.getElementLabel());if(a.peekStartElement(CCNProtocolDTags.Key)){try{this.publicKey=encodedKey=a.readBinaryElement(CCNProtocolDTags.Key),this.type=2,4<LOG&&console.log("PUBLIC KEY FOUND: "+this.publicKey)}catch(b){throw Error("Cannot parse key: ",b);}if(null==this.publicKey)throw Error("Cannot parse key: ");}else if(a.peekStartElement(CCNProtocolDTags.Certificate)){try{this.certificate=encodedCert=a.readBinaryElement(CCNProtocolDTags.Certificate),
-this.type=3,4<LOG&&console.log("CERTIFICATE FOUND: "+this.certificate)}catch(c){throw Error("Cannot decode certificate: "+c);}if(null==this.certificate)throw Error("Cannot parse certificate! ");}else this.type=1,this.keyName=new KeyName,this.keyName.from_ccnb(a);a.readEndElement()};
+var Key=function(){},KeyLocatorType={KEY:1,CERTIFICATE:2,KEYNAME:3},KeyLocator=function(a,b){this.type=b;b==KeyLocatorType.KEYNAME?(3<LOG&&console.log("KeyLocator: SET KEYNAME"),this.keyName=a):b==KeyLocatorType.KEY?(3<LOG&&console.log("KeyLocator: SET KEY"),this.publicKey=a):b==KeyLocatorType.CERTIFICATE&&(3<LOG&&console.log("KeyLocator: SET CERTIFICATE"),this.certificate=a)};
+KeyLocator.prototype.from_ccnb=function(a){a.readStartElement(this.getElementLabel());if(a.peekStartElement(CCNProtocolDTags.Key)){try{this.publicKey=encodedKey=a.readBinaryElement(CCNProtocolDTags.Key),this.type=KeyLocatorType.KEY,4<LOG&&console.log("PUBLIC KEY FOUND: "+this.publicKey)}catch(b){throw Error("Cannot parse key: ",b);}if(null==this.publicKey)throw Error("Cannot parse key: ");}else if(a.peekStartElement(CCNProtocolDTags.Certificate)){try{this.certificate=encodedCert=a.readBinaryElement(CCNProtocolDTags.Certificate),
+this.type=KeyLocatorType.CERTIFICATE,4<LOG&&console.log("CERTIFICATE FOUND: "+this.certificate)}catch(c){throw Error("Cannot decode certificate: "+c);}if(null==this.certificate)throw Error("Cannot parse certificate! ");}else this.type=KeyLocatorType.KEYNAME,this.keyName=new KeyName,this.keyName.from_ccnb(a);a.readEndElement()};
 KeyLocator.prototype.to_ccnb=function(a){4<LOG&&console.log("type is is "+this.type);if(!this.validate())throw new ContentEncodingException("Cannot encode "+this.getClass().getName()+": field values missing.");a.writeStartElement(this.getElementLabel());if(this.type==KeyLocatorType.KEY)5<LOG&&console.log("About to encode a public key"+this.publicKey),a.writeElement(CCNProtocolDTags.Key,this.publicKey);else if(this.type==KeyLocatorType.CERTIFICATE)try{a.writeElement(CCNProtocolDTags.Certificate,this.certificate)}catch(b){throw Error("CertificateEncodingException attempting to write key locator: "+
-b);}else this.type==KeyLocatorType.NAME&&this.keyName.to_ccnb(a);a.writeEndElement()};KeyLocator.prototype.getElementLabel=function(){return CCNProtocolDTags.KeyLocator};KeyLocator.prototype.validate=function(){return null!=this.keyName||null!=this.publicKey||null!=this.certificate};var KeyName=function(){this.contentName=this.contentName;this.publisherID=this.publisherID};
+b);}else this.type==KeyLocatorType.KEYNAME&&this.keyName.to_ccnb(a);a.writeEndElement()};KeyLocator.prototype.getElementLabel=function(){return CCNProtocolDTags.KeyLocator};KeyLocator.prototype.validate=function(){return null!=this.keyName||null!=this.publicKey||null!=this.certificate};var KeyName=function(){this.contentName=this.contentName;this.publisherID=this.publisherID};
 KeyName.prototype.from_ccnb=function(a){a.readStartElement(this.getElementLabel());this.contentName=new Name;this.contentName.from_ccnb(a);4<LOG&&console.log("KEY NAME FOUND: ");PublisherID.peek(a)&&(this.publisherID=new PublisherID,this.publisherID.from_ccnb(a));a.readEndElement()};
 KeyName.prototype.to_ccnb=function(a){if(!this.validate())throw Error("Cannot encode : field values missing.");a.writeStartElement(this.getElementLabel());this.contentName.to_ccnb(a);null!=this.publisherID&&this.publisherID.to_ccnb(a);a.writeEndElement()};KeyName.prototype.getElementLabel=function(){return CCNProtocolDTags.KeyName};KeyName.prototype.validate=function(){return null!=this.contentName};
+KeyName.prototype.matches_name=function(a){var b=this.contentName.components,a=a.components;if(b.length>a.length)return!1;for(var c=0;c<b.length;++c)if(!DataUtils.arraysEqual(b[c],a[c]))return!1;return!0};
 var PublisherType=function(a){this.KEY=CCNProtocolDTags.PublisherPublicKeyDigest;this.CERTIFICATE=CCNProtocolDTags.PublisherCertificateDigest;this.ISSUER_KEY=CCNProtocolDTags.PublisherIssuerKeyDigest;this.ISSUER_CERTIFICATE=CCNProtocolDTags.PublisherIssuerCertificateDigest;this.Tag=a},isTypeTagVal=function(a){return a==CCNProtocolDTags.PublisherPublicKeyDigest||a==CCNProtocolDTags.PublisherCertificateDigest||a==CCNProtocolDTags.PublisherIssuerKeyDigest||a==CCNProtocolDTags.PublisherIssuerCertificateDigest?
 !0:!1},PublisherID=function(){this.PUBLISHER_ID_DIGEST_ALGORITHM="SHA-256";this.PUBLISHER_ID_LEN=32;this.publisherType=this.publisherID=null};
 PublisherID.prototype.from_ccnb=function(a){var b=a.peekStartElementAsLong();if(null==b)throw Error("Cannot parse publisher ID.");this.publisherType=new PublisherType(b);if(!isTypeTagVal(b))throw Error("Invalid publisher ID, got unexpected type: "+b);this.publisherID=a.readBinaryElement(b);if(null==this.publisherID)throw new ContentDecodingException(Error("Cannot parse publisher ID of type : "+b+"."));};
@@ -164,8 +169,8 @@
 function sha256_Gamma1512(a){return sha256_S(a,19)^sha256_S(a,61)^sha256_R(a,6)}
 var sha256_K=[1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525,
 -778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998];function binb_sha256(a,b){var c=[1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225],d=Array(64);a[b>>5]|=128<<24-b%32;a[(b+64>>9<<4)+15]=b;for(var e=0;e<a.length;e+=16)processBlock_sha256(a,e,c,d);return c}
-function processBlock_sha256(a,b,c,d){var e,f,g,h,i,k,m,l,n,j,p;e=c[0];f=c[1];g=c[2];h=c[3];i=c[4];k=c[5];m=c[6];l=c[7];for(n=0;64>n;n++)d[n]=16>n?a[n+b]:safe_add(safe_add(safe_add(sha256_Gamma1256(d[n-2]),d[n-7]),sha256_Gamma0256(d[n-15])),d[n-16]),j=safe_add(safe_add(safe_add(safe_add(l,sha256_Sigma1256(i)),sha256_Ch(i,k,m)),sha256_K[n]),d[n]),p=safe_add(sha256_Sigma0256(e),sha256_Maj(e,f,g)),l=m,m=k,k=i,i=safe_add(h,j),h=g,g=f,f=e,e=safe_add(j,p);c[0]=safe_add(e,c[0]);c[1]=safe_add(f,c[1]);c[2]=
-safe_add(g,c[2]);c[3]=safe_add(h,c[3]);c[4]=safe_add(i,c[4]);c[5]=safe_add(k,c[5]);c[6]=safe_add(m,c[6]);c[7]=safe_add(l,c[7])}function safe_add(a,b){var c=(a&65535)+(b&65535);return(a>>16)+(b>>16)+(c>>16)<<16|c&65535}var Sha256=function(){this.W=Array(64);this.hash=[1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225];this.nTotalBytes=0;this.buffer=new Uint8Array(64);this.nBufferBytes=0};
+function processBlock_sha256(a,b,c,d){var e,f,g,h,i,j,l,m,n,k,p;e=c[0];f=c[1];g=c[2];h=c[3];i=c[4];j=c[5];l=c[6];m=c[7];for(n=0;64>n;n++)d[n]=16>n?a[n+b]:safe_add(safe_add(safe_add(sha256_Gamma1256(d[n-2]),d[n-7]),sha256_Gamma0256(d[n-15])),d[n-16]),k=safe_add(safe_add(safe_add(safe_add(m,sha256_Sigma1256(i)),sha256_Ch(i,j,l)),sha256_K[n]),d[n]),p=safe_add(sha256_Sigma0256(e),sha256_Maj(e,f,g)),m=l,l=j,j=i,i=safe_add(h,k),h=g,g=f,f=e,e=safe_add(k,p);c[0]=safe_add(e,c[0]);c[1]=safe_add(f,c[1]);c[2]=
+safe_add(g,c[2]);c[3]=safe_add(h,c[3]);c[4]=safe_add(i,c[4]);c[5]=safe_add(j,c[5]);c[6]=safe_add(l,c[6]);c[7]=safe_add(m,c[7])}function safe_add(a,b){var c=(a&65535)+(b&65535);return(a>>16)+(b>>16)+(c>>16)<<16|c&65535}var Sha256=function(){this.W=Array(64);this.hash=[1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225];this.nTotalBytes=0;this.buffer=new Uint8Array(64);this.nBufferBytes=0};
 Sha256.prototype.update=function(a){this.nTotalBytes+=a.length;if(0<this.nBufferBytes){var b=this.buffer.length-this.nBufferBytes;if(a.length<b){this.buffer.set(a,this.nBufferBytes);this.nBufferBytes+=a.length;return}this.buffer.set(a.subarray(0,b),this.nBufferBytes);processBlock_sha256(byteArray2binb(this.buffer),0,this.hash,this.W);this.nBufferBytes=0;a=a.subarray(b,a.length);if(0==a.length)return}b=a.length>>6;if(0<b){for(var b=64*b,c=byteArray2binb(a.subarray(0,b)),d=0;d<c.length;d+=16)processBlock_sha256(c,
 d,this.hash,this.W);a=a.subarray(b,a.length)}0<a.length&&(this.buffer.set(a),this.nBufferBytes=a.length)};Sha256.prototype.finalize=function(){var a=byteArray2binb(this.buffer.subarray(0,this.nBufferBytes)),b=8*this.nBufferBytes;a[b>>5]|=128<<24-b%32;a[(b+64>>9<<4)+15]=8*this.nTotalBytes;for(b=0;b<a.length;b+=16)processBlock_sha256(a,b,this.hash,this.W);return Sha256.binb2Uint8Array(this.hash)};
 Sha256.binb2Uint8Array=function(a){for(var b=new Uint8Array(4*a.length),c=0,d=0;d<32*a.length;d+=8)b[c++]=a[d>>5]>>>24-d%32&255;return b};var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b64pad="=";
@@ -180,8 +185,8 @@
 function RSAGenerate(a,b){var c=new SecureRandom,d=a>>1;this.e=parseInt(b,16);for(var e=new BigInteger(b,16);;){for(;!(this.p=new BigInteger(a-d,1,c),0==this.p.subtract(BigInteger.ONE).gcd(e).compareTo(BigInteger.ONE)&&this.p.isProbablePrime(10)););for(;!(this.q=new BigInteger(d,1,c),0==this.q.subtract(BigInteger.ONE).gcd(e).compareTo(BigInteger.ONE)&&this.q.isProbablePrime(10)););if(0>=this.p.compareTo(this.q)){var f=this.p;this.p=this.q;this.q=f}var f=this.p.subtract(BigInteger.ONE),g=this.q.subtract(BigInteger.ONE),
 h=f.multiply(g);if(0==h.gcd(e).compareTo(BigInteger.ONE)){this.n=this.p.multiply(this.q);this.d=e.modInverse(h);this.dmp1=this.d.mod(f);this.dmq1=this.d.mod(g);this.coeff=this.q.modInverse(this.p);break}}}function RSADoPrivate(a){if(null==this.p||null==this.q)return a.modPow(this.d,this.n);for(var b=a.mod(this.p).modPow(this.dmp1,this.p),a=a.mod(this.q).modPow(this.dmq1,this.q);0>b.compareTo(a);)b=b.add(this.p);return b.subtract(a).multiply(this.coeff).mod(this.p).multiply(this.q).add(a)}
 function RSADecrypt(a){a=parseBigInt(a,16);a=this.doPrivate(a);return null==a?null:pkcs1unpad2(a,this.n.bitLength()+7>>3)}RSAKey.prototype.doPrivate=RSADoPrivate;RSAKey.prototype.setPrivate=RSASetPrivate;RSAKey.prototype.setPrivateEx=RSASetPrivateEx;RSAKey.prototype.generate=RSAGenerate;RSAKey.prototype.decrypt=RSADecrypt;function _rsapem_pemToBase64(a){a=a.replace("-----BEGIN RSA PRIVATE KEY-----","");a=a.replace("-----END RSA PRIVATE KEY-----","");return a=a.replace(/[ \n]+/g,"")}
-function _rsapem_getPosArrayOfChildrenFromHex(a){var b=[],c=ASN1HEX.getStartPosOfV_AtObj(a,0),d=ASN1HEX.getPosOfNextSibling_AtObj(a,c),e=ASN1HEX.getPosOfNextSibling_AtObj(a,d),f=ASN1HEX.getPosOfNextSibling_AtObj(a,e),g=ASN1HEX.getPosOfNextSibling_AtObj(a,f),h=ASN1HEX.getPosOfNextSibling_AtObj(a,g),i=ASN1HEX.getPosOfNextSibling_AtObj(a,h),k=ASN1HEX.getPosOfNextSibling_AtObj(a,i),a=ASN1HEX.getPosOfNextSibling_AtObj(a,k);b.push(c,d,e,f,g,h,i,k,a);return b}
-function _rsapem_getHexValueArrayOfChildrenFromHex(a){var b=_rsapem_getPosArrayOfChildrenFromHex(a),c=ASN1HEX.getHexOfV_AtObj(a,b[0]),d=ASN1HEX.getHexOfV_AtObj(a,b[1]),e=ASN1HEX.getHexOfV_AtObj(a,b[2]),f=ASN1HEX.getHexOfV_AtObj(a,b[3]),g=ASN1HEX.getHexOfV_AtObj(a,b[4]),h=ASN1HEX.getHexOfV_AtObj(a,b[5]),i=ASN1HEX.getHexOfV_AtObj(a,b[6]),k=ASN1HEX.getHexOfV_AtObj(a,b[7]),a=ASN1HEX.getHexOfV_AtObj(a,b[8]),b=[];b.push(c,d,e,f,g,h,i,k,a);return b}
+function _rsapem_getPosArrayOfChildrenFromHex(a){var b=[],c=ASN1HEX.getStartPosOfV_AtObj(a,0),d=ASN1HEX.getPosOfNextSibling_AtObj(a,c),e=ASN1HEX.getPosOfNextSibling_AtObj(a,d),f=ASN1HEX.getPosOfNextSibling_AtObj(a,e),g=ASN1HEX.getPosOfNextSibling_AtObj(a,f),h=ASN1HEX.getPosOfNextSibling_AtObj(a,g),i=ASN1HEX.getPosOfNextSibling_AtObj(a,h),j=ASN1HEX.getPosOfNextSibling_AtObj(a,i),a=ASN1HEX.getPosOfNextSibling_AtObj(a,j);b.push(c,d,e,f,g,h,i,j,a);return b}
+function _rsapem_getHexValueArrayOfChildrenFromHex(a){var b=_rsapem_getPosArrayOfChildrenFromHex(a),c=ASN1HEX.getHexOfV_AtObj(a,b[0]),d=ASN1HEX.getHexOfV_AtObj(a,b[1]),e=ASN1HEX.getHexOfV_AtObj(a,b[2]),f=ASN1HEX.getHexOfV_AtObj(a,b[3]),g=ASN1HEX.getHexOfV_AtObj(a,b[4]),h=ASN1HEX.getHexOfV_AtObj(a,b[5]),i=ASN1HEX.getHexOfV_AtObj(a,b[6]),j=ASN1HEX.getHexOfV_AtObj(a,b[7]),a=ASN1HEX.getHexOfV_AtObj(a,b[8]),b=[];b.push(c,d,e,f,g,h,i,j,a);return b}
 function _rsapem_readPrivateKeyFromPEMString(a){a=_rsapem_pemToBase64(a);a=b64tohex(a);a=_rsapem_getHexValueArrayOfChildrenFromHex(a);this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8])}RSAKey.prototype.readPrivateKeyFromPEMString=_rsapem_readPrivateKeyFromPEMString;var _RSASIGN_DIHEAD=[];_RSASIGN_DIHEAD.sha1="3021300906052b0e03021a05000414";_RSASIGN_DIHEAD.sha256="3031300d060960864801650304020105000420";_RSASIGN_DIHEAD.sha384="3041300d060960864801650304020205000430";
 _RSASIGN_DIHEAD.sha512="3051300d060960864801650304020305000440";_RSASIGN_DIHEAD.md2="3020300c06082a864886f70d020205000410";_RSASIGN_DIHEAD.md5="3020300c06082a864886f70d020505000410";_RSASIGN_DIHEAD.ripemd160="3021300906052b2403020105000414";var _RSASIGN_HASHHEXFUNC=[];_RSASIGN_HASHHEXFUNC.sha1=function(a){return hex_sha1(a)};_RSASIGN_HASHHEXFUNC.sha256=function(a){return hex_sha256(a)};_RSASIGN_HASHHEXFUNC.sha512=function(a){return hex_sha512(a)};_RSASIGN_HASHHEXFUNC.md5=function(a){return hex_md5(a)};
 _RSASIGN_HASHHEXFUNC.ripemd160=function(a){return hex_rmd160(a)};var _RSASIGN_HASHBYTEFUNC=[];_RSASIGN_HASHBYTEFUNC.sha256=function(a){return hex_sha256_from_bytes(a)};var _RE_HEXDECONLY=RegExp("");_RE_HEXDECONLY.compile("[^0-9a-f]","gi");function _rsasign_getHexPaddedDigestInfoForString(a,b,c){for(var b=b/4,a=(0,_RSASIGN_HASHHEXFUNC[c])(a),c="00"+_RSASIGN_DIHEAD[c]+a,a="",b=b-4-c.length,d=0;d<b;d+=2)a+="ff";return sPaddedMessageHex="0001"+a+c}
@@ -211,8 +216,8 @@
 function _x509_readCertHex(a){var a=a.toLowerCase(),b=_x509_getPublicKeyHexArrayFromCertHex(a),c=new RSAKey;c.setPublic(b[0],b[1]);this.subjectPublicKeyRSA=c;this.subjectPublicKeyRSA_hN=b[0];this.subjectPublicKeyRSA_hE=b[1];this.hex=a}function _x509_readCertPEMWithoutRSAInit(a){var a=_x509_pemToHex(a),b=_x509_getPublicKeyHexArrayFromCertHex(a);this.subjectPublicKeyRSA.setPublic(b[0],b[1]);this.subjectPublicKeyRSA_hN=b[0];this.subjectPublicKeyRSA_hE=b[1];this.hex=a}
 function X509(){this.hex=this.subjectPublicKeyRSA_hE=this.subjectPublicKeyRSA_hN=this.subjectPublicKeyRSA=null}X509.prototype.readCertPEM=_x509_readCertPEM;X509.prototype.readCertHex=_x509_readCertHex;X509.prototype.readCertPEMWithoutRSAInit=_x509_readCertPEMWithoutRSAInit;X509.prototype.getSerialNumberHex=_x509_getSerialNumberHex;X509.prototype.getIssuerHex=_x509_getIssuerHex;X509.prototype.getSubjectHex=_x509_getSubjectHex;X509.prototype.getIssuerString=_x509_getIssuerString;
 X509.prototype.getSubjectString=_x509_getSubjectString;X509.prototype.getNotBefore=_x509_getNotBefore;X509.prototype.getNotAfter=_x509_getNotAfter;var dbits,canary=0xdeadbeefcafe,j_lm=15715070==(canary&16777215);function BigInteger(a,b,c){null!=a&&("number"==typeof a?this.fromNumber(a,b,c):null==b&&"string"!=typeof a?this.fromString(a,256):this.fromString(a,b))}function nbi(){return new BigInteger(null)}
-function am1(a,b,c,d,e,f){for(;0<=--f;){var g=b*this[a++]+c[d]+e,e=Math.floor(g/67108864);c[d++]=g&67108863}return e}function am2(a,b,c,d,e,f){for(var g=b&32767,b=b>>15;0<=--f;){var h=this[a]&32767,i=this[a++]>>15,k=b*h+i*g,h=g*h+((k&32767)<<15)+c[d]+(e&1073741823),e=(h>>>30)+(k>>>15)+b*i+(e>>>30);c[d++]=h&1073741823}return e}
-function am3(a,b,c,d,e,f){for(var g=b&16383,b=b>>14;0<=--f;){var h=this[a]&16383,i=this[a++]>>14,k=b*h+i*g,h=g*h+((k&16383)<<14)+c[d]+e,e=(h>>28)+(k>>14)+b*i;c[d++]=h&268435455}return e}j_lm&&"Microsoft Internet Explorer"==navigator.appName?(BigInteger.prototype.am=am2,dbits=30):j_lm&&"Netscape"!=navigator.appName?(BigInteger.prototype.am=am1,dbits=26):(BigInteger.prototype.am=am3,dbits=28);BigInteger.prototype.DB=dbits;BigInteger.prototype.DM=(1<<dbits)-1;BigInteger.prototype.DV=1<<dbits;
+function am1(a,b,c,d,e,f){for(;0<=--f;){var g=b*this[a++]+c[d]+e,e=Math.floor(g/67108864);c[d++]=g&67108863}return e}function am2(a,b,c,d,e,f){for(var g=b&32767,b=b>>15;0<=--f;){var h=this[a]&32767,i=this[a++]>>15,j=b*h+i*g,h=g*h+((j&32767)<<15)+c[d]+(e&1073741823),e=(h>>>30)+(j>>>15)+b*i+(e>>>30);c[d++]=h&1073741823}return e}
+function am3(a,b,c,d,e,f){for(var g=b&16383,b=b>>14;0<=--f;){var h=this[a]&16383,i=this[a++]>>14,j=b*h+i*g,h=g*h+((j&16383)<<14)+c[d]+e,e=(h>>28)+(j>>14)+b*i;c[d++]=h&268435455}return e}j_lm&&"Microsoft Internet Explorer"==navigator.appName?(BigInteger.prototype.am=am2,dbits=30):j_lm&&"Netscape"!=navigator.appName?(BigInteger.prototype.am=am1,dbits=26):(BigInteger.prototype.am=am3,dbits=28);BigInteger.prototype.DB=dbits;BigInteger.prototype.DM=(1<<dbits)-1;BigInteger.prototype.DV=1<<dbits;
 var BI_FP=52;BigInteger.prototype.FV=Math.pow(2,BI_FP);BigInteger.prototype.F1=BI_FP-dbits;BigInteger.prototype.F2=2*dbits-BI_FP;var BI_RM="0123456789abcdefghijklmnopqrstuvwxyz",BI_RC=[],rr,vv;rr=48;for(vv=0;9>=vv;++vv)BI_RC[rr++]=vv;rr=97;for(vv=10;36>vv;++vv)BI_RC[rr++]=vv;rr=65;for(vv=10;36>vv;++vv)BI_RC[rr++]=vv;function int2char(a){return BI_RM.charAt(a)}function intAt(a,b){var c=BI_RC[a.charCodeAt(b)];return null==c?-1:c}
 function bnpCopyTo(a){for(var b=this.t-1;0<=b;--b)a[b]=this[b];a.t=this.t;a.s=this.s}function bnpFromInt(a){this.t=1;this.s=0>a?-1:0;0<a?this[0]=a:-1>a?this[0]=a+DV:this.t=0}function nbv(a){var b=nbi();b.fromInt(a);return b}
 function bnpFromString(a,b){var c;if(16==b)c=4;else if(8==b)c=3;else if(256==b)c=8;else if(2==b)c=1;else if(32==b)c=5;else if(4==b)c=2;else{this.fromRadix(a,b);return}this.s=this.t=0;for(var d=a.length,e=!1,f=0;0<=--d;){var g=8==c?a[d]&255:intAt(a,d);0>g?"-"==a.charAt(d)&&(e=!0):(e=!1,0==f?this[this.t++]=g:f+c>this.DB?(this[this.t-1]|=(g&(1<<this.DB-f)-1)<<f,this[this.t++]=g>>this.DB-f):this[this.t-1]|=g<<f,f+=c,f>=this.DB&&(f-=this.DB))}8==c&&0!=(a[0]&128)&&(this.s=-1,0<f&&(this[this.t-1]|=(1<<this.DB-
@@ -224,8 +229,8 @@
 function bnpRShiftTo(a,b){b.s=this.s;var c=Math.floor(a/this.DB);if(c>=this.t)b.t=0;else{var d=a%this.DB,e=this.DB-d,f=(1<<d)-1;b[0]=this[c]>>d;for(var g=c+1;g<this.t;++g)b[g-c-1]|=(this[g]&f)<<e,b[g-c]=this[g]>>d;0<d&&(b[this.t-c-1]|=(this.s&f)<<e);b.t=this.t-c;b.clamp()}}
 function bnpSubTo(a,b){for(var c=0,d=0,e=Math.min(a.t,this.t);c<e;)d+=this[c]-a[c],b[c++]=d&this.DM,d>>=this.DB;if(a.t<this.t){for(d-=a.s;c<this.t;)d+=this[c],b[c++]=d&this.DM,d>>=this.DB;d+=this.s}else{for(d+=this.s;c<a.t;)d-=a[c],b[c++]=d&this.DM,d>>=this.DB;d-=a.s}b.s=0>d?-1:0;-1>d?b[c++]=this.DV+d:0<d&&(b[c++]=d);b.t=c;b.clamp()}
 function bnpMultiplyTo(a,b){var c=this.abs(),d=a.abs(),e=c.t;for(b.t=e+d.t;0<=--e;)b[e]=0;for(e=0;e<d.t;++e)b[e+c.t]=c.am(0,d[e],b,e,0,c.t);b.s=0;b.clamp();this.s!=a.s&&BigInteger.ZERO.subTo(b,b)}function bnpSquareTo(a){for(var b=this.abs(),c=a.t=2*b.t;0<=--c;)a[c]=0;for(c=0;c<b.t-1;++c){var d=b.am(c,b[c],a,2*c,0,1);if((a[c+b.t]+=b.am(c+1,2*b[c],a,2*c+1,d,b.t-c-1))>=b.DV)a[c+b.t]-=b.DV,a[c+b.t+1]=1}0<a.t&&(a[a.t-1]+=b.am(c,b[c],a,2*c,0,1));a.s=0;a.clamp()}
-function bnpDivRemTo(a,b,c){var d=a.abs();if(!(0>=d.t)){var e=this.abs();if(e.t<d.t)null!=b&&b.fromInt(0),null!=c&&this.copyTo(c);else{null==c&&(c=nbi());var f=nbi(),g=this.s,a=a.s,h=this.DB-nbits(d[d.t-1]);0<h?(d.lShiftTo(h,f),e.lShiftTo(h,c)):(d.copyTo(f),e.copyTo(c));d=f.t;e=f[d-1];if(0!=e){var i=e*(1<<this.F1)+(1<d?f[d-2]>>this.F2:0),k=this.FV/i,i=(1<<this.F1)/i,m=1<<this.F2,l=c.t,n=l-d,j=null==b?nbi():b;f.dlShiftTo(n,j);0<=c.compareTo(j)&&(c[c.t++]=1,c.subTo(j,c));BigInteger.ONE.dlShiftTo(d,
-j);for(j.subTo(f,f);f.t<d;)f[f.t++]=0;for(;0<=--n;){var p=c[--l]==e?this.DM:Math.floor(c[l]*k+(c[l-1]+m)*i);if((c[l]+=f.am(0,p,c,n,0,d))<p){f.dlShiftTo(n,j);for(c.subTo(j,c);c[l]<--p;)c.subTo(j,c)}}null!=b&&(c.drShiftTo(d,b),g!=a&&BigInteger.ZERO.subTo(b,b));c.t=d;c.clamp();0<h&&c.rShiftTo(h,c);0>g&&BigInteger.ZERO.subTo(c,c)}}}}function bnMod(a){var b=nbi();this.abs().divRemTo(a,null,b);0>this.s&&0<b.compareTo(BigInteger.ZERO)&&a.subTo(b,b);return b}function Classic(a){this.m=a}
+function bnpDivRemTo(a,b,c){var d=a.abs();if(!(0>=d.t)){var e=this.abs();if(e.t<d.t)null!=b&&b.fromInt(0),null!=c&&this.copyTo(c);else{null==c&&(c=nbi());var f=nbi(),g=this.s,a=a.s,h=this.DB-nbits(d[d.t-1]);0<h?(d.lShiftTo(h,f),e.lShiftTo(h,c)):(d.copyTo(f),e.copyTo(c));d=f.t;e=f[d-1];if(0!=e){var i=e*(1<<this.F1)+(1<d?f[d-2]>>this.F2:0),j=this.FV/i,i=(1<<this.F1)/i,l=1<<this.F2,m=c.t,n=m-d,k=null==b?nbi():b;f.dlShiftTo(n,k);0<=c.compareTo(k)&&(c[c.t++]=1,c.subTo(k,c));BigInteger.ONE.dlShiftTo(d,
+k);for(k.subTo(f,f);f.t<d;)f[f.t++]=0;for(;0<=--n;){var p=c[--m]==e?this.DM:Math.floor(c[m]*j+(c[m-1]+l)*i);if((c[m]+=f.am(0,p,c,n,0,d))<p){f.dlShiftTo(n,k);for(c.subTo(k,c);c[m]<--p;)c.subTo(k,c)}}null!=b&&(c.drShiftTo(d,b),g!=a&&BigInteger.ZERO.subTo(b,b));c.t=d;c.clamp();0<h&&c.rShiftTo(h,c);0>g&&BigInteger.ZERO.subTo(c,c)}}}}function bnMod(a){var b=nbi();this.abs().divRemTo(a,null,b);0>this.s&&0<b.compareTo(BigInteger.ZERO)&&a.subTo(b,b);return b}function Classic(a){this.m=a}
 function cConvert(a){return 0>a.s||0<=a.compareTo(this.m)?a.mod(this.m):a}function cRevert(a){return a}function cReduce(a){a.divRemTo(this.m,null,a)}function cMulTo(a,b,c){a.multiplyTo(b,c);this.reduce(c)}function cSqrTo(a,b){a.squareTo(b);this.reduce(b)}Classic.prototype.convert=cConvert;Classic.prototype.revert=cRevert;Classic.prototype.reduce=cReduce;Classic.prototype.mulTo=cMulTo;Classic.prototype.sqrTo=cSqrTo;
 function bnpInvDigit(){if(1>this.t)return 0;var a=this[0];if(0==(a&1))return 0;var b=a&3,b=b*(2-(a&15)*b)&15,b=b*(2-(a&255)*b)&255,b=b*(2-((a&65535)*b&65535))&65535,b=b*(2-a*b%this.DV)%this.DV;return 0<b?this.DV-b:-b}function Montgomery(a){this.m=a;this.mp=a.invDigit();this.mpl=this.mp&32767;this.mph=this.mp>>15;this.um=(1<<a.DB-15)-1;this.mt2=2*a.t}
 function montConvert(a){var b=nbi();a.abs().dlShiftTo(this.m.t,b);b.divRemTo(this.m,null,b);0>a.s&&0<b.compareTo(BigInteger.ZERO)&&this.m.subTo(b,b);return b}function montRevert(a){var b=nbi();a.copyTo(b);this.reduce(b);return b}
@@ -249,8 +254,8 @@
 function Barrett(a){this.r2=nbi();this.q3=nbi();BigInteger.ONE.dlShiftTo(2*a.t,this.r2);this.mu=this.r2.divide(a);this.m=a}function barrettConvert(a){if(0>a.s||a.t>2*this.m.t)return a.mod(this.m);if(0>a.compareTo(this.m))return a;var b=nbi();a.copyTo(b);this.reduce(b);return b}function barrettRevert(a){return a}
 function barrettReduce(a){a.drShiftTo(this.m.t-1,this.r2);a.t>this.m.t+1&&(a.t=this.m.t+1,a.clamp());this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);for(this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);0>a.compareTo(this.r2);)a.dAddOffset(1,this.m.t+1);for(a.subTo(this.r2,a);0<=a.compareTo(this.m);)a.subTo(this.m,a)}function barrettSqrTo(a,b){a.squareTo(b);this.reduce(b)}function barrettMulTo(a,b,c){a.multiplyTo(b,c);this.reduce(c)}Barrett.prototype.convert=barrettConvert;
 Barrett.prototype.revert=barrettRevert;Barrett.prototype.reduce=barrettReduce;Barrett.prototype.mulTo=barrettMulTo;Barrett.prototype.sqrTo=barrettSqrTo;
-function bnModPow(a,b){var c=a.bitLength(),d,e=nbv(1),f;if(0>=c)return e;d=18>c?1:48>c?3:144>c?4:768>c?5:6;f=8>c?new Classic(b):b.isEven()?new Barrett(b):new Montgomery(b);var g=[],h=3,i=d-1,k=(1<<d)-1;g[1]=f.convert(this);if(1<d){c=nbi();for(f.sqrTo(g[1],c);h<=k;)g[h]=nbi(),f.mulTo(c,g[h-2],g[h]),h+=2}for(var m=a.t-1,l,n=!0,j=nbi(),c=nbits(a[m])-1;0<=m;){c>=i?l=a[m]>>c-i&k:(l=(a[m]&(1<<c+1)-1)<<i-c,0<m&&(l|=a[m-1]>>this.DB+c-i));for(h=d;0==(l&1);)l>>=1,--h;if(0>(c-=h))c+=this.DB,--m;if(n)g[l].copyTo(e),
-n=!1;else{for(;1<h;)f.sqrTo(e,j),f.sqrTo(j,e),h-=2;0<h?f.sqrTo(e,j):(h=e,e=j,j=h);f.mulTo(j,g[l],e)}for(;0<=m&&0==(a[m]&1<<c);)f.sqrTo(e,j),h=e,e=j,j=h,0>--c&&(c=this.DB-1,--m)}return f.revert(e)}
+function bnModPow(a,b){var c=a.bitLength(),d,e=nbv(1),f;if(0>=c)return e;d=18>c?1:48>c?3:144>c?4:768>c?5:6;f=8>c?new Classic(b):b.isEven()?new Barrett(b):new Montgomery(b);var g=[],h=3,i=d-1,j=(1<<d)-1;g[1]=f.convert(this);if(1<d){c=nbi();for(f.sqrTo(g[1],c);h<=j;)g[h]=nbi(),f.mulTo(c,g[h-2],g[h]),h+=2}for(var l=a.t-1,m,n=!0,k=nbi(),c=nbits(a[l])-1;0<=l;){c>=i?m=a[l]>>c-i&j:(m=(a[l]&(1<<c+1)-1)<<i-c,0<l&&(m|=a[l-1]>>this.DB+c-i));for(h=d;0==(m&1);)m>>=1,--h;if(0>(c-=h))c+=this.DB,--l;if(n)g[m].copyTo(e),
+n=!1;else{for(;1<h;)f.sqrTo(e,k),f.sqrTo(k,e),h-=2;0<h?f.sqrTo(e,k):(h=e,e=k,k=h);f.mulTo(k,g[m],e)}for(;0<=l&&0==(a[l]&1<<c);)f.sqrTo(e,k),h=e,e=k,k=h,0>--c&&(c=this.DB-1,--l)}return f.revert(e)}
 function bnGCD(a){var b=0>this.s?this.negate():this.clone(),a=0>a.s?a.negate():a.clone();if(0>b.compareTo(a))var c=b,b=a,a=c;var c=b.getLowestSetBit(),d=a.getLowestSetBit();if(0>d)return b;c<d&&(d=c);0<d&&(b.rShiftTo(d,b),a.rShiftTo(d,a));for(;0<b.signum();)0<(c=b.getLowestSetBit())&&b.rShiftTo(c,b),0<(c=a.getLowestSetBit())&&a.rShiftTo(c,a),0<=b.compareTo(a)?(b.subTo(a,b),b.rShiftTo(1,b)):(a.subTo(b,a),a.rShiftTo(1,a));0<d&&a.lShiftTo(d,a);return a}
 function bnpModInt(a){if(0>=a)return 0;var b=this.DV%a,c=0>this.s?a-1:0;if(0<this.t)if(0==b)c=this[0]%a;else for(var d=this.t-1;0<=d;--d)c=(b*c+this[d])%a;return c}
 function bnModInverse(a){var b=a.isEven();if(this.isEven()&&b||0==a.signum())return BigInteger.ZERO;for(var c=a.clone(),d=this.clone(),e=nbv(1),f=nbv(0),g=nbv(0),h=nbv(1);0!=c.signum();){for(;c.isEven();){c.rShiftTo(1,c);if(b){if(!e.isEven()||!f.isEven())e.addTo(this,e),f.subTo(a,f);e.rShiftTo(1,e)}else f.isEven()||f.subTo(a,f);f.rShiftTo(1,f)}for(;d.isEven();){d.rShiftTo(1,d);if(b){if(!g.isEven()||!h.isEven())g.addTo(this,g),h.subTo(a,h);g.rShiftTo(1,g)}else h.isEven()||h.subTo(a,h);h.rShiftTo(1,