Move timerID to pit entry; implement basic pipeline code
diff --git a/js/Closure.js b/js/Closure.js
index 2482e2b..7a1e9de 100644
--- a/js/Closure.js
+++ b/js/Closure.js
@@ -19,7 +19,6 @@
 	this.ndn_data = null;  // this holds the ndn_closure
     this.ndn_data_dirty = false;
     
-    this.timerID = -1;  // Store the interest timer; used to cancel the timer upon receiving interest
 };
 
 // Upcall result
diff --git a/js/NDN.js b/js/NDN.js
index 31e4cd0..0410425 100644
--- a/js/NDN.js
+++ b/js/NDN.js
@@ -81,6 +81,7 @@
 var PITEntry = function PITEntry(interest, closure) {
 	this.interest = interest;  // Interest
 	this.closure = closure;    // Closure
+	this.timerID = -1;  // Timer ID
 };
 
 // Return the longest entry from NDN.PITTable that matches name.
diff --git a/js/WebSocketTransport.js b/js/WebSocketTransport.js
index b40176e..594aff4 100644
--- a/js/WebSocketTransport.js
+++ b/js/WebSocketTransport.js
@@ -143,7 +143,7 @@
 						var currentClosure = pitEntry.closure;
 						
 						// Cancel interest timer
-						clearTimeout(currentClosure.timerID);
+						clearTimeout(pitEntry.timerID);
 						//console.log("Clear interest timer");
 						//console.log(currentClosure.timerID);
 						
@@ -316,6 +316,7 @@
 		if (closure != null) {
 			var pitEntry = new PITEntry(interest, closure);
 			NDN.PITTable.push(pitEntry);
+			closure.pitEntry = pitEntry;
 		}
 		
 		this.ws.send(bytearray.buffer);
@@ -323,7 +324,7 @@
 		
 		// Set interest timer
 		if (closure != null) {
-			closure.timerID = setTimeout(function() {
+			pitEntry.timerID = setTimeout(function() {
 				if (LOG > 3) console.log("Interest time out.");
 				
 				// Remove PIT entry from NDN.PITTable
@@ -332,6 +333,8 @@
 				if (index >= 0) 
 		            NDN.PITTable.splice(index, 1);
 				//console.log(NDN.PITTable);
+				//console.log(pitEntry.interest.name.getName());
+				
 				// Raise closure callback
 				closure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, new UpcallInfo(ndn, interest, 0, null));
 			}, interest.interestLifetime);  // interestLifetime is in milliseconds.
diff --git a/js/testing/test-throughput-ws-pipeline.html b/js/testing/test-throughput-ws-pipeline.html
new file mode 100644
index 0000000..f741fb0
--- /dev/null
+++ b/js/testing/test-throughput-ws-pipeline.html
@@ -0,0 +1,332 @@
+<?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;  // start time

+		    this.ndn = ndn;

+			this.totalBlocks = 0;  // total # of segments delivered to usr; 

+			                       // TODO: in this test script we simply discard the content after it is received

+			                       //  should consider some way to buffer the whole data in future refactor  --- SWT

+		    

+		    // We should always start with the first element so the first content object cannot be ooo data

+		    //this.firstReceivedSegmentNumber = null;

+		    //this.firstReceivedContentObject = null;

+		    

+		    // statistic counters

+		    this.ooo_count = 0;  // out-of-order content object counter; 

+		                         // when this counter reaches 3, apply fast retransmission alg.

+		    this.pkt_recved = 0;  // total # of content object received

+		    this.timed_out = 0;   // totle # of timed out interest

+		    this.interest_sent = 1;  // there is an initial interest before calling the closure upcall

+		    this.dups = 0;  // total # of dup content segments

+		    

+		    this.max_window = 32;  // max window size

+		    this.max_retrans = 5;  // max # of trial before give up; if we fail on one segment, the entire process is aborted

+		    

+		    this.snd_una = 0;  // pointer to unacked segments

+		    this.snd_wnd = 1;  // current window size

+		    this.snd_nxt = 1;  // pointer to next interest to be sent

+		    

+		    this.ooo_table_size = 128;

+		    this.ooo_table = [];  // hash table to mark ooo segments

+		    

+		    this.terminated = false;  // Set this flag after we receive all the segments; 

+		};

+		

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

+			this.pkt_recved++;

+			

+		    if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {

+		    	if (this.terminated == false) {

+			    	this.timed_out++;

+			    	

+			    	// Reduce window size to 1

+			    	this.snd_wnd = 1;

+			    	

+			    	// Retransmit interest for this segment

+			    	this.ndn.expressInterest(upcallInfo.interest.name, this);

+					//console.log("Resend interest sent for " + upcallInfo.interest.name.getName());

+					document.getElementById('content').innerHTML += "<p>Resend interest sent for " 

+					                         + upcallInfo.interest.name.getName() + "</p>";

+					this.interest_sent++;

+					

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

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

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

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

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

+					document.getElementById('content').innerHTML += "<p>Total number of time-out interest: " + this.timed_out + "</p>";

+					

+					document.getElementById('content').innerHTML += "<p>SND_UNA: " + this.snd_una + "</p>";

+					document.getElementById('content').innerHTML += "<p>SND_WND: " + this.snd_wnd + "</p>";

+					document.getElementById('content').innerHTML += "<p>SND_NXT: " + this.snd_nxt + "</p>";

+				}

+		        return Closure.RESULT_OK;

+		    }

+		    

+		    if (kind == Closure.UPCALL_CONTENT_BAD) {

+		    	console.log("NdnProtocol.ContentClosure: signature verification failed");

+		    	console.log(upcallInfo.contentObject.name.getName());

+		    	console.log(DataUtils.toHex(upcallInfo.contentObject.signature.Witness).toLowerCase());

+		    	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");

+		        return Closure.RESULT_ERR;

+		    }

+		

+		    // Use the segmentNumber to load multiple segments.

+		    var segmentNumber = DataUtils.bigEndianToUnsignedInt

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

+		    //console.log("Seg # " + segmentNumber + " received");

+		    //console.log("Seg name " + contentObject.name.getName());

+		    

+		    /*

+	        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.

+	                // SWT: this should not happen in normal case; 

+	                //      ccnd should always return the first segment if no seg# is specified

+	                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>";

+		    

+		    /*

+		    // 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++;

+		    }

+		    */

+			

+			// Record final seg# if present

+			var finalSegmentNumber = null;

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

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

+			

+			// Check for out-of-order segment

+			if (segmentNumber != this.snd_una) {

+				//console.log("Out-of-order segment #" + segmentNumber + " received.");

+				document.getElementById('content').innerHTML += "<p>Out-of-order segment #" + segmentNumber + " received.</p>";

+				this.ooo_count++;

+				

+				if (segmentNumber >= this.snd_nxt || segmentNumber < this.snd_una) {

+					// Discard segment that is out of window

+					//console.log("Out-of-window segment #" + segmentNumber);

+					document.getElementById('content').innerHTML += "<p>Out-of-window segment #" + segmentNumber + "</p>";

+					return Closure.RESULT_OK;

+				}

+				// Mark this segment in hash table

+				var slot = segmentNumber % this.ooo_table_size;

+				this.ooo_table[slot] = segmentNumber;

+				

+				if (this.ooo_count == 3) {

+					// Fast retransmit

+					// TODO: expressInterest for seg# = this.snd_una

+					//this.snd_wnd = Math.floor(this.snd_wnd / 2) + 3;

+				} else if (this.ooo_count > 3) {

+					//this.snd_wnd++;

+					// TODO: send a new interest if allowed by snd_wnd

+					// SWT: what if we never receive the first unacked segment???

+				}

+				

+				return Closure.RESULT_OK;

+			}

+			

+			// In-order segment; slide window forward

+			this.snd_una++

+			this.totalBlocks++;

+			var slot = this.snd_una % this.ooo_table_size;

+			while (this.ooo_table[slot] != undefined) {

+				// continue to move forward until we reach a hole in the seg# sequence

+				this.ooo_table[slot] = undefined;

+				this.snd_una++;

+				this.totalBlocks++;

+				slot = this.snd_una % this.ooo_table_size;

+			}

+			

+			if (finalSegmentNumber != null && this.snd_una == finalSegmentNumber + 1) {

+				// All blocks before final block, including final block, is received. Mission complete.

+				// Record stop time and print statistic result

+				this.terminated = true;

+				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>";

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

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

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

+				document.getElementById('content').innerHTML += "<p>Total number of time-out interest: " + this.timed_out + "</p>";

+				return Closure.RESULT_OK;

+			}

+			

+			// Adjust window size

+			if (this.snd_wnd < this.max_window) {

+				this.snd_wnd++;

+				//console.log("Window size after adjust: " + this.snd_wnd);

+			}

+			

+			// Send the next interest if allowed by snd_wnd

+			var nextNameComponents = contentObject.name.components.slice(0, contentObject.name.components.length - 1);

+			//console.log("SND_UNA: " + this.snd_una);

+			//console.log("SND_NXT: " + this.snd_nxt);

+			while (this.snd_nxt - this.snd_una < this.snd_wnd) {

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

+				var segmentNumberPlus1 = DataUtils.nonNegativeIntToBigEndian(this.snd_nxt);

+				// Put a 0 byte in front.

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

+				nextSegmentNumber.set(segmentNumberPlus1, 1);

+				

+				nextNameComponents.push(nextSegmentNumber);

+				

+				var nextName = new Name(nextNameComponents);

+				this.ndn.expressInterest(nextName, this);

+				//console.log("Interest sent for seg # " + this.snd_nxt + " name " + nextName.getName());

+				this.interest_sent++;

+				

+				this.snd_nxt++;

+				nextNameComponents.pop();  // Remove segment number from components

+			}

+			

+			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/mars/%00" /> 

+	</form>

+

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

+	

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

+

+</body>

+</html>

diff --git a/js/tools/build/ndn-js-uncomp.js b/js/tools/build/ndn-js-uncomp.js
index 8469498..6f1bc95 100644
--- a/js/tools/build/ndn-js-uncomp.js
+++ b/js/tools/build/ndn-js-uncomp.js
@@ -19,7 +19,7 @@
 	this.ndn_data = null;  // this holds the ndn_closure
     this.ndn_data_dirty = false;
     
-    this.timerID = -1;  // Store the interest timer; used to cancel the timer upon receiving interest
+    //this.timerID = -1;  // Store the interest timer; used to cancel the timer upon receiving interest
 };
 
 // Upcall result
@@ -209,7 +209,7 @@
 						var currentClosure = pitEntry.closure;
 						
 						// Cancel interest timer
-						clearTimeout(currentClosure.timerID);
+						clearTimeout(pitEntry.timerID);
 						//console.log("Clear interest timer");
 						//console.log(currentClosure.timerID);
 						
@@ -382,6 +382,7 @@
 		if (closure != null) {
 			var pitEntry = new PITEntry(interest, closure);
 			NDN.PITTable.push(pitEntry);
+			closure.pitEntry = pitEntry;
 		}
 		
 		this.ws.send(bytearray.buffer);
@@ -389,7 +390,7 @@
 		
 		// Set interest timer
 		if (closure != null) {
-			closure.timerID = setTimeout(function() {
+			pitEntry.timerID = setTimeout(function() {
 				if (LOG > 3) console.log("Interest time out.");
 				
 				// Remove PIT entry from NDN.PITTable
@@ -398,6 +399,8 @@
 				if (index >= 0) 
 		            NDN.PITTable.splice(index, 1);
 				//console.log(NDN.PITTable);
+				//console.log(pitEntry.interest.name.getName());
+				
 				// Raise closure callback
 				closure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, new UpcallInfo(ndn, interest, 0, null));
 			}, interest.interestLifetime);  // interestLifetime is in milliseconds.
@@ -7856,6 +7859,7 @@
 var PITEntry = function PITEntry(interest, closure) {
 	this.interest = interest;  // Interest
 	this.closure = closure;    // Closure
+	this.timerID = -1;  // Timer ID
 };
 
 // Return the longest entry from NDN.PITTable that matches name.
diff --git a/js/tools/build/ndn-js.js b/js/tools/build/ndn-js.js
index 4c17018..43075ff 100644
--- a/js/tools/build/ndn-js.js
+++ b/js/tools/build/ndn-js.js
@@ -1,18 +1,18 @@
-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 Closure=function(){this.ndn_data=null;this.ndn_data_dirty=!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 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),null==b.ccndid&&NDN.ccndIdFetcher.match(d.name))!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){var g=NDN.PITTable.indexOf(f);0<=g&&NDN.PITTable.splice(g,1);f=f.closure;clearTimeout(f.timerID);var j=function(a,b,c,d,e){this.contentObject=a;this.closure=b;this.keyName=c;this.sigHex=d;this.witness=e;Closure.call(this)};j.prototype.upcall=function(b,c){if(b==Closure.UPCALL_INTEREST_TIMED_OUT)console.log("In KeyFetchClosure.upcall: interest time out."),
+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{var g=NDN.getEntryForExpressedInterest(d.name);if(null!=g){f=NDN.PITTable.indexOf(g);0<=f&&NDN.PITTable.splice(f,1);f=g.closure;clearTimeout(g.timerID);var j=function(a,b,c,d,e){this.contentObject=a;this.closure=b;this.keyName=c;this.sigHex=d;this.witness=e;Closure.call(this)};j.prototype.upcall=function(b,c){if(b==Closure.UPCALL_INTEREST_TIMED_OUT)console.log("In KeyFetchClosure.upcall: interest time out."),
 console.log(this.keyName.contentName.getName());else if(b==Closure.UPCALL_CONTENT){var d=decodeSubjectPublicKeyInfo(c.contentObject.content),e=!0==d.verifyByteArray(this.contentObject.rawSignatureData,this.witness,this.sigHex)?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD;this.closure.upcall(e,new UpcallInfo(a,null,0,this.contentObject));d=new KeyStoreEntry(l.keyName,d,(new Date).getTime());NDN.addKeyEntry(d)}else b==Closure.UPCALL_CONTENT_BAD&&console.log("In KeyFetchClosure.upcall: signature verification failed")};
 if(d.signedInfo&&d.signedInfo.locator&&d.signature){3<LOG&&console.log("Key verification...");var g=DataUtils.toHex(d.signature.signature).toLowerCase(),k=null;null!=d.signature.Witness&&(k=new Witness,k.decode(d.signature.Witness));var l=d.signedInfo.locator;if(l.type==KeyLocatorType.KEYNAME)if(3<LOG&&console.log("KeyLocator contains KEYNAME"),l.keyName.contentName.match(d.name))3<LOG&&console.log("Content is key itself"),j=decodeSubjectPublicKeyInfo(d.content),g=j.verifyByteArray(d.rawSignatureData,
 k,g),g=!0==g?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,f.upcall(g,new UpcallInfo(a,null,0,d));else{var p=NDN.getKeyByName(l.keyName);p?(3<LOG&&console.log("Local key cache hit"),j=p.rsaKey,g=j.verifyByteArray(d.rawSignatureData,k,g),g=!0==g?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,f.upcall(g,new UpcallInfo(a,null,0,d))):(3<LOG&&console.log("Fetch key according to keylocator"),f=new j(d,f,l.keyName,g,k),d=new Interest(l.keyName.contentName.getPrefix(4)),d.interestLifetime=4E3,b.expressInterest(a,
 d,f))}else l.type==KeyLocatorType.KEY?(3<LOG&&console.log("Keylocator contains KEY"),j=decodeSubjectPublicKeyInfo(d.signedInfo.locator.publicKey),g=j.verifyByteArray(d.rawSignatureData,k,g),g=!0==g?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,f.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,d))):(d=l.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(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);if(null!=c){var f=new PITEntry(b,c);NDN.PITTable.push(f)}this.ws.send(e.buffer);3<LOG&&console.log("ws.send() returned.");null!=c&&(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.")};
+WebSocketTransport.prototype.expressInterest=function(a,b,c){if(null!=this.ws){var d=encodeToBinaryInterest(b),e=new Uint8Array(d.length);e.set(d);if(null!=c){var f=new PITEntry(b,c);NDN.PITTable.push(f);c.pitEntry=f}this.ws.send(e.buffer);3<LOG&&console.log("ws.send() returned.");null!=c&&(f.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);
 a.set(d);3<LOG&&console.log("Send Interest registration packet.");b=new CSEntry(b.getName(),c);CSTable.push(b);this.ws.send(a.buffer);return 0}console.log("WebSocket connection is not established.");return-1};
@@ -263,7 +263,7 @@
 BigInteger.prototype.multiply=bnMultiply;BigInteger.prototype.divide=bnDivide;BigInteger.prototype.remainder=bnRemainder;BigInteger.prototype.divideAndRemainder=bnDivideAndRemainder;BigInteger.prototype.modPow=bnModPow;BigInteger.prototype.modInverse=bnModInverse;BigInteger.prototype.pow=bnPow;BigInteger.prototype.gcd=bnGCD;BigInteger.prototype.isProbablePrime=bnIsProbablePrime;
 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=new Name("/%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.rsaKey=b;this.timeStamp=c};NDN.addKeyEntry=function(a){null==NDN.getKeyByName(a.keyName)&&NDN.KeyStore.push(a)};
-NDN.getKeyByName=function(a){for(var b=null,c=0;c<NDN.KeyStore.length;c++)if(NDN.KeyStore[c].keyName.contentName.match(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.getKeyByName=function(a){for(var b=null,c=0;c<NDN.KeyStore.length;c++)if(NDN.KeyStore[c].keyName.contentName.match(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;this.timerID=-1};
 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)};