Added util.ExponentialReExpressClosure.js.
diff --git a/js/Helper.js b/js/Helper.js
index 02e33fc..91bb6cb 100644
--- a/js/Helper.js
+++ b/js/Helper.js
@@ -15,59 +15,37 @@
 console.log(path);
 
 document.write('<script type="text/javascript" src="' + path + 'WebSocketTransport.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'util/CCNProtocolDTags.js"></script>');
-	
 document.write('<script type="text/javascript" src="' + path + 'util/CCNTime.js"></script>');
-
+document.write('<script type="text/javascript" src="' + path + 'util/ExponentialReExpressClosure.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'Name.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'ContentObject.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/DateFormat.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'Interest.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'Key.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'PublisherID.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'PublisherPublicKeyDigest.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'FaceInstance.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'ForwardingEntry.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'Closure.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/DynamicUint8Array.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/BinaryXMLEncoder.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/BinaryXMLDecoder.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/BinaryXMLStructureDecoder.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/DataUtils.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'encoding/EncodingUtils.js"></script>');
-                
 document.write('<script type="text/javascript" src="' + path + 'security/KeyManager.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'NDN.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'securityLib/jsbn.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/jsbn2.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/rsa.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/rsa2.js"></script>');
-
 //document.write('<script type="text/javascript" src="' + path + 'securityLib/sha1.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/sha256.js"></script>');
 //document.write('<script type="text/javascript" src="' + path + 'securityLib/sha512.js"></script>');
 //document.write('<script type="text/javascript" src="' + path + 'securityLib/md5.js"></script>');
 //document.write('<script type="text/javascript" src="' + path + 'securityLib/ripemd160.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/base64.js"></script>');
-
 document.write('<script type="text/javascript" src="' + path + 'securityLib/rsapem-1.1.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/rsasign-1.2.js"></script>');
 document.write('<script type="text/javascript" src="' + path + 'securityLib/asn1hex-1.1.js"></script>');
diff --git a/js/tools/build/make-js.sh b/js/tools/build/make-js.sh
index 49dda47..a592700 100755
--- a/js/tools/build/make-js.sh
+++ b/js/tools/build/make-js.sh
@@ -6,6 +6,7 @@
   ../../WebSocketTransport.js \
   ../../util/CCNProtocolDTags.js \
   ../../util/CCNTime.js \
+  ../../util/ExponentialReExpressClosure.js \
   ../../Name.js \
   ../../ContentObject.js \
   ../../encoding/DateFormat.js \
diff --git a/js/tools/build/ndn-js-uncomp.js b/js/tools/build/ndn-js-uncomp.js
index 9b1be2a..30d137d 100644
--- a/js/tools/build/ndn-js-uncomp.js
+++ b/js/tools/build/ndn-js-uncomp.js
@@ -425,6 +425,61 @@
 }*/
 	
 /**
+ * @author: Jeff Thompson
+ * See COPYING for copyright and distribution information.
+ * This is the closure class for use in expressInterest to re express with exponential falloff.
+ */
+
+/*
+ * Create a new ExponentialReExpressClosure where upcall responds to UPCALL_INTEREST_TIMED_OUT
+ *   by expressing the interest again with double the interestLifetime. If the interesLifetime goes
+ *   over maxInterestLifetime, then call callerClosure.upcall with UPCALL_INTEREST_TIMED_OUT.
+ * When upcall is not UPCALL_INTEREST_TIMED_OUT, just call callerClosure.upcall.
+ * 
+ * settings is an associative array with the following defaults:
+ * {
+ *   maxInterestLifetime: 16000 // milliseconds
+ * }
+ */
+var ExponentialReExpressClosure = function ExponentialReExpressClosure
+        (callerClosure, settings) {
+    // Inherit from Closure.
+    Closure.call(this);
+    
+    this.callerClosure = callerClosure;
+    settings = (settings || {});
+	this.maxInterestLifetime = (settings.maxInterestLifetime || 16000);
+};
+
+ExponentialReExpressClosure.prototype.upcall = function(kind, upcallInfo) {
+    try {
+        if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+            var interestLifetime = upcallInfo.interest.interestLifetime;
+            if (interestLifetime == null)
+                return this.callerClosure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, upcallInfo);
+            
+            var nextInterestLifetime = interestLifetime * 2;
+            if (nextInterestLifetime > this.maxInterestLifetime) 
+                console.log("nextInterestLifetime " + nextInterestLifetime + " > max " + 
+                this.maxInterestLifetime + ". Timing out for " + upcallInfo.interest.name.to_uri()); // DEBUG
+            if (nextInterestLifetime > this.maxInterestLifetime)
+                return this.callerClosure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, upcallInfo);
+            
+            var nextInterest = upcallInfo.interest.clone();
+            nextInterest.interestLifetime = nextInterestLifetime;
+            console.log("Re-express nextInterestLifetime " + nextInterestLifetime + 
+                " for " + upcallInfo.interest.name.to_uri()); // DEBUG
+            upcallInfo.ndn.expressInterest(nextInterest.name, this, nextInterest);
+            return Closure.RESULT_OK;
+        }  
+        else
+            return this.callerClosure.upcall(kind, upcallInfo);
+    } catch (ex) {
+        console.log("ExponentialReExpressClosure.upcall exception: " + ex);
+        return Closure.RESULT_ERR;
+    }
+};
+/**
  * @author: Meki Cheraoui, Jeff Thompson
  * See COPYING for copyright and distribution information.
  * This class represents a Name as an array of components where each is a byte array.
@@ -1322,7 +1377,9 @@
  */
 
 // _interestLifetime is in milliseconds.
-var Interest = function Interest(_name,_faceInstance,_minSuffixComponents,_maxSuffixComponents,_publisherPublicKeyDigest, _exclude, _childSelector,_answerOriginKind,_scope,_interestLifetime,_nonce){
+var Interest = function Interest
+   (_name, _faceInstance, _minSuffixComponents, _maxSuffixComponents, _publisherPublicKeyDigest, _exclude, 
+    _childSelector, _answerOriginKind, _scope, _interestLifetime, _nonce) {
 		
 	this.name = _name;
 	this.faceInstance = _faceInstance;
@@ -1451,7 +1508,18 @@
         return false;
     
     return true;
-}
+};
+
+/*
+ * Return a new Interest with the same fields as this Interest.  
+ * Note: This does NOT make a deep clone of the name, exclue or other objects.
+ */
+Interest.prototype.clone = function() {
+    return new Interest
+       (this.name, this.faceInstance, this.minSuffixComponents, this.maxSuffixComponents, 
+        this.publisherPublicKeyDigest, this.exclude, this.childSelector, this.answerOriginKind, 
+        this.scope, this.interestLifetime, this.nonce);
+};
 
 /*
  * Handle the interest Exclude element.
@@ -7633,7 +7701,9 @@
 // For fetching data
 NDN.PITTable = new Array();
 
+var DebugPITEntryCounter = 0;
 var PITEntry = function PITEntry(interest, closure) {
+    this.debugId = ++DebugPITEntryCounter;
 	this.interest = interest;  // Interest
 	this.closure = closure;    // Closure
 	this.timerID = -1;  // Timer ID
@@ -7747,7 +7817,9 @@
  * Do the work of reconnectAndExpressInterest once we know we are connected.  Set the PITTable and call
  *   this.transport.send to send the interest.
  */
+var DebugExpressInterestCounter = 0;
 NDN.prototype.expressInterestHelper = function(interest, closure) {
+    var expressInterestId = ++DebugExpressInterestCounter; //DEBUG
     var binaryInterest = encodeToBinaryInterest(interest);
     var thisNDN = this;    
 	//TODO: check local content store first
@@ -7766,6 +7838,8 @@
             //   the interest because we don't want to match it in the mean time.
             // TODO: Make this a thread-safe operation on the global PITTable.
 			var index = NDN.PITTable.indexOf(pitEntry);
+            if (index >= 0) console.log("expressInterest " + expressInterestId + ": timeout. remove PIT " + 
+                pitEntry.debugId); else console.log("can't find PIT " + pitEntry.debugId); //DEBUG
 			if (index >= 0) 
 	            NDN.PITTable.splice(index, 1);
 				
@@ -7779,6 +7853,9 @@
             }
 		};
 		pitEntry.timerID = setTimeout(timeoutCallback, timeoutMilliseconds);
+        console.log("expressInterest " + expressInterestId + ": (lifetime " + interest.interestLifetime + 
+            ") PIT " + pitEntry.debugId + " " + interest.name.to_uri() + 
+            (interest.childSelector != null ? " childSelector " + interest.childSelector : "")); //DEBUG
 	}
 
 	this.transport.send(binaryInterest);
@@ -7911,20 +7988,20 @@
 		co.from_ccnb(decoder);
 				
 		var pitEntry = NDN.getEntryForExpressedInterest(co.name);
+        if (pitEntry == null) console.log("PIT search failed for " + co.name.to_uri());//DEBUG
 		if (pitEntry != null) {
-			//console.log(pitEntry);
+			// Cancel interest timer
+			clearTimeout(pitEntry.timerID);
+            
 			// Remove PIT entry from NDN.PITTable
 			var index = NDN.PITTable.indexOf(pitEntry);
+            if (index >= 0) console.log("received. remove PIT " + pitEntry.debugId + ". " + co.name.to_uri()); 
+               else console.log("can't find PIT " + pitEntry.debugId); //DEBUG
 			if (index >= 0)
 				NDN.PITTable.splice(index, 1);
 						
 			var currentClosure = pitEntry.closure;
-						
-			// Cancel interest timer
-			clearTimeout(pitEntry.timerID);
-			//console.log("Clear interest timer");
-			//console.log(currentClosure.timerID);
-				
+										
 			if (this.verify == false) {
 				// Pass content up without verifying the signature
 				currentClosure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED, new UpcallInfo(this, null, 0, co));
diff --git a/js/tools/build/ndn-js.js b/js/tools/build/ndn-js.js
index 5c27b24..4ab1003 100644
--- a/js/tools/build/ndn-js.js
+++ b/js/tools/build/ndn-js.js
@@ -11,6 +11,9 @@
 null,null,"RootDigest","Signature","Start","Timestamp","Type","Nonce","Scope","Exclude","Bloom","BloomSeed",null,"AnswerOriginKind","InterestLifetime",null,null,null,null,"Witness","SignatureBits","DigestAlgorithm","BlockSize",null,"FreshnessSeconds","FinalBlockID","PublisherPublicKeyDigest","PublisherCertificateDigest","PublisherIssuerKeyDigest","PublisherIssuerCertificateDigest","ContentObject","WrappedKey","WrappingKeyIdentifier","WrapAlgorithm","KeyAlgorithm","Label","EncryptedKey","EncryptedNonceKey",
 "WrappingKeyName","Action","FaceID","IPProto","Host","Port","MulticastInterface","ForwardingFlags","FaceInstance","ForwardingEntry","MulticastTTL","MinSuffixComponents","MaxSuffixComponents","ChildSelector","RepositoryInfo","Version","RepositoryVersion","GlobalPrefix","LocalName","Policy","Namespace","GlobalPrefixName","PolicyVersion","KeyValueSet","KeyValuePair","IntegerValue","DecimalValue","StringValue","BinaryValue","NameValue","Entry","ACL","ParameterizedName","Prefix","Suffix","Root","ProfileName",
 "Parameters","InfoString",null,"StatusResponse","StatusCode","StatusText","SyncNode","SyncNodeKind","SyncNodeElement","SyncVersion","SyncNodeElements","SyncContentHash","SyncLeafCount","SyncTreeDepth","SyncByteCount","ConfigSlice","ConfigSliceList","ConfigSliceOp"],CCNTime=function(a){this.NANOS_MAX=999877929;"number"==typeof a?this.msec=a:1<LOG&&console.log("UNRECOGNIZED TYPE FOR TIME")};CCNTime.prototype.getJavascriptDate=function(){var a=new Date;a.setTime(this.msec);return a};
+var ExponentialReExpressClosure=function(a,b){Closure.call(this);this.callerClosure=a;b=b||{};this.maxInterestLifetime=b.maxInterestLifetime||16E3};
+ExponentialReExpressClosure.prototype.upcall=function(a,b){try{if(a==Closure.UPCALL_INTEREST_TIMED_OUT){var c=b.interest.interestLifetime;if(null==c)return this.callerClosure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,b);c*=2;c>this.maxInterestLifetime&&console.log("nextInterestLifetime "+c+" > max "+this.maxInterestLifetime+". Timing out for "+b.interest.name.to_uri());if(c>this.maxInterestLifetime)return this.callerClosure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,b);var d=b.interest.clone();d.interestLifetime=
+c;console.log("Re-express nextInterestLifetime "+c+" for "+b.interest.name.to_uri());b.ndn.expressInterest(d.name,this,d);return Closure.RESULT_OK}return this.callerClosure.upcall(a,b)}catch(e){return console.log("ExponentialReExpressClosure.upcall exception: "+e),Closure.RESULT_ERR}};
 var Name=function Name(b){if("string"==typeof b)3<LOG&&console.log("Content Name String "+b),this.components=Name.createNameArray(b);else if("object"===typeof b){4<LOG&&console.log("Content Name Array "+b);this.components=[];for(var c=0;c<b.length;++c)this.add(b[c])}else null==b?this.components=[]:1<LOG&&console.log("NO CONTENT NAME GIVEN")};Name.prototype.getName=function(){return this.to_uri()};
 Name.createNameArray=function(a){a=a.trim();if(0>=a.length)return[];var b=a.indexOf(":");if(0<=b){var c=a.indexOf("/");if(0>c||b<c)a=a.substr(b+1,a.length-b-1).trim()}if("/"==a[0])if(2<=a.length&&"/"==a[1]){b=a.indexOf("/",2);if(0>b)return[];a=a.substr(b+1,a.length-b-1).trim()}else a=a.substr(1,a.length-1).trim();a=a.split("/");for(b=0;b<a.length;++b)c=Name.fromEscapedString(a[b]),null==c?(a.splice(b,1),--b):a[b]=c;return a};
 Name.prototype.from_ccnb=function(a){a.readStartElement(this.getElementLabel());for(this.components=[];a.peekStartElement(CCNProtocolDTags.Component);)this.add(a.readBinaryElement(CCNProtocolDTags.Component));a.readEndElement()};Name.prototype.to_ccnb=function(a){if(null==this.components)throw Error("CANNOT ENCODE EMPTY CONTENT NAME");a.writeStartElement(this.getElementLabel());for(var b=this.components.length,c=0;c<b;c++)a.writeElement(CCNProtocolDTags.Component,this.components[c]);a.writeEndElement()};
@@ -52,7 +55,8 @@
 a.peekStartElement(CCNProtocolDTags.InterestLifetime)&&(this.interestLifetime=1E3*DataUtils.bigEndianToUnsignedInt(a.readBinaryElement(CCNProtocolDTags.InterestLifetime))/4096);a.peekStartElement(CCNProtocolDTags.Nonce)&&(this.nonce=a.readBinaryElement(CCNProtocolDTags.Nonce));a.readEndElement()};
 Interest.prototype.to_ccnb=function(a){a.writeStartElement(CCNProtocolDTags.Interest);this.name.to_ccnb(a);null!=this.minSuffixComponents&&a.writeElement(CCNProtocolDTags.MinSuffixComponents,this.minSuffixComponents);null!=this.maxSuffixComponents&&a.writeElement(CCNProtocolDTags.MaxSuffixComponents,this.maxSuffixComponents);null!=this.publisherPublicKeyDigest&&this.publisherPublicKeyDigest.to_ccnb(a);null!=this.exclude&&this.exclude.to_ccnb(a);null!=this.childSelector&&a.writeElement(CCNProtocolDTags.ChildSelector,
 this.childSelector);this.DEFAULT_ANSWER_ORIGIN_KIND!=this.answerOriginKind&&null!=this.answerOriginKind&&a.writeElement(CCNProtocolDTags.AnswerOriginKind,this.answerOriginKind);null!=this.scope&&a.writeElement(CCNProtocolDTags.Scope,this.scope);null!=this.interestLifetime&&a.writeElement(CCNProtocolDTags.InterestLifetime,DataUtils.nonNegativeIntToBigEndian(4096*(this.interestLifetime/1E3)));null!=this.nonce&&a.writeElement(CCNProtocolDTags.Nonce,this.nonce);a.writeEndElement()};
-Interest.prototype.matches_name=function(a){return!this.name.match(a)||null!=this.minSuffixComponents&&!(a.components.length+1-this.name.components.length>=this.minSuffixComponents)||null!=this.maxSuffixComponents&&!(a.components.length+1-this.name.components.length<=this.maxSuffixComponents)||null!=this.exclude&&a.components.length>this.name.components.length&&this.exclude.matches(a.components[this.name.components.length])?!1:!0};var Exclude=function(a){this.values=a||[]};Exclude.ANY="*";
+Interest.prototype.matches_name=function(a){return!this.name.match(a)||null!=this.minSuffixComponents&&!(a.components.length+1-this.name.components.length>=this.minSuffixComponents)||null!=this.maxSuffixComponents&&!(a.components.length+1-this.name.components.length<=this.maxSuffixComponents)||null!=this.exclude&&a.components.length>this.name.components.length&&this.exclude.matches(a.components[this.name.components.length])?!1:!0};
+Interest.prototype.clone=function(){return new Interest(this.name,this.faceInstance,this.minSuffixComponents,this.maxSuffixComponents,this.publisherPublicKeyDigest,this.exclude,this.childSelector,this.answerOriginKind,this.scope,this.interestLifetime,this.nonce)};var Exclude=function(a){this.values=a||[]};Exclude.ANY="*";
 Exclude.prototype.from_ccnb=function(a){for(a.readStartElement(CCNProtocolDTags.Exclude);;)if(a.peekStartElement(CCNProtocolDTags.Component))this.values.push(a.readBinaryElement(CCNProtocolDTags.Component));else if(a.peekStartElement(CCNProtocolDTags.Any))a.readStartElement(CCNProtocolDTags.Any),a.readEndElement(),this.values.push(Exclude.ANY);else if(a.peekStartElement(CCNProtocolDTags.Bloom))a.readBinaryElement(CCNProtocolDTags.Bloom),this.values.push(Exclude.ANY);else break;a.readEndElement()};
 Exclude.prototype.to_ccnb=function(a){if(!(null==this.values||0==this.values.length)){a.writeStartElement(CCNProtocolDTags.Exclude);for(var b=0;b<this.values.length;++b)this.values[b]==Exclude.ANY?(a.writeStartElement(CCNProtocolDTags.Any),a.writeEndElement()):a.writeElement(CCNProtocolDTags.Component,this.values[b]);a.writeEndElement()}};
 Exclude.prototype.to_uri=function(){if(null==this.values||0==this.values.length)return"";for(var a="",b=0;b<this.values.length;++b)0<b&&(a+=","),a=this.values[b]==Exclude.ANY?a+"*":a+Name.toEscapedString(this.values[b]);return a};
@@ -255,25 +259,25 @@
 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:null;this.port=b.port||9696;this.readyStatus=NDN.UNOPEN;this.verify=void 0!==b.verify?b.verify:!0;this.onopen=b.onopen||function(){3<LOG&&console.log("NDN connection established.")};this.onclose=b.onclose||function(){3<LOG&&console.log("NDN connection closed.")};this.ccndid=null};
 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;this.timerID=-1};
+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 DebugPITEntryCounter=0,PITEntry=function(a,b){this.debugId=++DebugPITEntryCounter;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.CSTable=[];var CSEntry=function(a,b){this.name=a;this.closure=b};function getEntryForRegisteredPrefix(a){for(var b=0;b<NDN.CSTable.length;b++)if(null!=NDN.CSTable[b].name.match(a))return NDN.CSTable[b];return null}
 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){var d=new Interest(a);null!=c?(d.minSuffixComponents=c.minSuffixComponents,d.maxSuffixComponents=c.maxSuffixComponents,d.publisherPublicKeyDigest=c.publisherPublicKeyDigest,d.exclude=c.exclude,d.childSelector=c.childSelector,d.answerOriginKind=c.answerOriginKind,d.scope=c.scope,d.interestLifetime=c.interestLifetime):d.interestLifetime=4E3;if(null==this.host||null==this.port)if(null==this.getHostAndPort)console.log("ERROR: host OR port NOT SET");else{var e=
-this;this.connectAndExecute(function(){e.reconnectAndExpressInterest(d,b)})}else this.reconnectAndExpressInterest(d,b)};NDN.prototype.reconnectAndExpressInterest=function(a,b){if(this.transport.connectedHost!=this.host||this.transport.connectedPort!=this.port){var c=this;this.transport.connect(c,function(){c.expressInterestHelper(a,b)})}else this.expressInterestHelper(a,b)};
-NDN.prototype.expressInterestHelper=function(a,b){var c=encodeToBinaryInterest(a),d=this;if(null!=b){var e=new PITEntry(a,b);NDN.PITTable.push(e);b.pitEntry=e;var f=a.interestLifetime||4E3,g=function(){3<LOG&&console.log("Interest time out: "+a.name.to_uri());var j=NDN.PITTable.indexOf(e);0<=j&&NDN.PITTable.splice(j,1);b.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,new UpcallInfo(d,a,0,null))==Closure.RESULT_REEXPRESS&&(3<LOG&&console.log("Re-express interest: "+a.name.to_uri()),e.timerID=setTimeout(g,
-f),NDN.PITTable.push(e),d.transport.send(c))};e.timerID=setTimeout(g,f)}this.transport.send(c)};
+this;this.connectAndExecute(function(){e.reconnectAndExpressInterest(d,b)})}else this.reconnectAndExpressInterest(d,b)};NDN.prototype.reconnectAndExpressInterest=function(a,b){if(this.transport.connectedHost!=this.host||this.transport.connectedPort!=this.port){var c=this;this.transport.connect(c,function(){c.expressInterestHelper(a,b)})}else this.expressInterestHelper(a,b)};var DebugExpressInterestCounter=0;
+NDN.prototype.expressInterestHelper=function(a,b){var c=++DebugExpressInterestCounter,d=encodeToBinaryInterest(a),e=this;if(null!=b){var f=new PITEntry(a,b);NDN.PITTable.push(f);b.pitEntry=f;var g=a.interestLifetime||4E3,j=function(){3<LOG&&console.log("Interest time out: "+a.name.to_uri());var k=NDN.PITTable.indexOf(f);0<=k?console.log("expressInterest "+c+": timeout. remove PIT "+f.debugId):console.log("can't find PIT "+f.debugId);0<=k&&NDN.PITTable.splice(k,1);b.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,
+new UpcallInfo(e,a,0,null))==Closure.RESULT_REEXPRESS&&(3<LOG&&console.log("Re-express interest: "+a.name.to_uri()),f.timerID=setTimeout(j,g),NDN.PITTable.push(f),e.transport.send(d))};f.timerID=setTimeout(j,g);console.log("expressInterest "+c+": (lifetime "+a.interestLifetime+") PIT "+f.debugId+" "+a.name.to_uri()+(null!=a.childSelector?" childSelector "+a.childSelector:""))}this.transport.send(d)};
 NDN.prototype.registerPrefix=function(a,b,c){var d=this,e=function(){if(null==d.ccndid){var e=new Interest(NDN.ccndIdFetcher);e.interestLifetime=4E3;3<LOG&&console.log("Expressing interest for ccndid from ccnd.");d.reconnectAndExpressInterest(e,new NDN.FetchCcndidClosure(d,a,b,c))}else d.registerPrefixHelper(a,b,c)};null==this.host||null==this.port?null==this.getHostAndPort?console.log("ERROR: host OR port NOT SET"):this.connectAndExecute(e):e()};
 NDN.FetchCcndidClosure=function(a,b,c,d){Closure.call(this);this.ndn=a;this.name=b;this.callerClosure=c;this.flag=d};
 NDN.FetchCcndidClosure.prototype.upcall=function(a,b){if(a==Closure.UPCALL_INTEREST_TIMED_OUT)return console.log("Timeout while requesting the ccndid.  Cannot registerPrefix for "+this.name.to_uri()+" ."),Closure.RESULT_OK;if(!(a==Closure.UPCALL_CONTENT||a==Closure.UPCALL_CONTENT_UNVERIFIED))return Closure.RESULT_ERR;var c=b.contentObject;!c.signedInfo||!c.signedInfo.publisher||!c.signedInfo.publisher.publisherPublicKeyDigest?console.log("ContentObject doesn't have a publisherPublicKeyDigest. Cannot set ccndid and registerPrefix for "+
 this.name.to_uri()+" ."):(3<LOG&&console.log("Got ccndid from ccnd."),this.ndn.ccndid=c.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(this.ndn.ccndid),this.ndn.registerPrefixHelper(this.name,this.callerClosure,this.flag));return Closure.RESULT_OK};
 NDN.prototype.registerPrefixHelper=function(a,b){var c=new ForwardingEntry("selfreg",a,null,null,3,2147483647),c=encodeForwardingEntry(c),d=new SignedInfo;d.setFields();c=new ContentObject(new Name,d,c,new Signature);c.sign();c=encodeToBinaryContentObject(c);c=new Name(["ccnx",this.ccndid,"selfreg",c]);c=new Interest(c);c.scope=1;3<LOG&&console.log("Send Interest registration packet.");d=new CSEntry(a.getName(),b);NDN.CSTable.push(d);this.transport.send(encodeToBinaryInterest(c))};
 NDN.prototype.onReceivedElement=function(a){3<LOG&&console.log("Complete element received. Length "+a.length+". Start decoding.");var b=new BinaryXMLDecoder(a);if(b.peekStartElement(CCNProtocolDTags.Interest))3<LOG&&console.log("Interest packet received."),a=new Interest,a.from_ccnb(b),3<LOG&&console.log(a),b=escape(a.name.getName()),3<LOG&&console.log(b),b=getEntryForRegisteredPrefix(b),null!=b&&(a=new UpcallInfo(this,a,0,null),b.closure.upcall(Closure.UPCALL_INTEREST,a)==Closure.RESULT_INTEREST_CONSUMED&&
-null!=a.contentObject&&this.transport.send(encodeToBinaryContentObject(a.contentObject)));else if(b.peekStartElement(CCNProtocolDTags.ContentObject)){3<LOG&&console.log("ContentObject packet received.");a=new ContentObject;a.from_ccnb(b);var c=NDN.getEntryForExpressedInterest(a.name);if(null!=c)if(b=NDN.PITTable.indexOf(c),0<=b&&NDN.PITTable.splice(b,1),b=c.closure,clearTimeout(c.timerID),!1==this.verify)b.upcall(Closure.UPCALL_CONTENT_UNVERIFIED,new UpcallInfo(this,null,0,a));else{var d=function(a,
-b,c,d,e){this.contentObject=a;this.closure=b;this.keyName=c;this.sigHex=d;this.witness=e;Closure.call(this)},e=this;d.prototype.upcall=function(a,b){if(a==Closure.UPCALL_INTEREST_TIMED_OUT)console.log("In KeyFetchClosure.upcall: interest time out."),console.log(this.keyName.contentName.getName());else if(a==Closure.UPCALL_CONTENT){var c=decodeSubjectPublicKeyInfo(b.contentObject.content),d=!0==c.verifyByteArray(this.contentObject.rawSignatureData,this.witness,this.sigHex)?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD;
-this.closure.upcall(d,new UpcallInfo(e,null,0,this.contentObject));c=new KeyStoreEntry(g.keyName,c,(new Date).getTime());NDN.addKeyEntry(c)}else a==Closure.UPCALL_CONTENT_BAD&&console.log("In KeyFetchClosure.upcall: signature verification failed")};if(a.signedInfo&&a.signedInfo.locator&&a.signature){3<LOG&&console.log("Key verification...");var c=DataUtils.toHex(a.signature.signature).toLowerCase(),f=null;null!=a.signature.Witness&&(f=new Witness,f.decode(a.signature.Witness));var g=a.signedInfo.locator;
-if(g.type==KeyLocatorType.KEYNAME)if(3<LOG&&console.log("KeyLocator contains KEYNAME"),g.keyName.contentName.match(a.name))3<LOG&&console.log("Content is key itself"),d=decodeSubjectPublicKeyInfo(a.content),c=d.verifyByteArray(a.rawSignatureData,f,c),c=!0==c?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,b.upcall(c,new UpcallInfo(this,null,0,a));else{var j=NDN.getKeyByName(g.keyName);j?(3<LOG&&console.log("Local key cache hit"),d=j.rsaKey,c=d.verifyByteArray(a.rawSignatureData,f,c),c=!0==c?Closure.UPCALL_CONTENT:
-Closure.UPCALL_CONTENT_BAD,b.upcall(c,new UpcallInfo(this,null,0,a))):(3<LOG&&console.log("Fetch key according to keylocator"),a=new d(a,b,g.keyName,c,f),this.expressInterest(g.keyName.contentName.getPrefix(4),a))}else g.type==KeyLocatorType.KEY?(3<LOG&&console.log("Keylocator contains KEY"),d=decodeSubjectPublicKeyInfo(a.signedInfo.locator.publicKey),c=d.verifyByteArray(a.rawSignatureData,f,c),c=!0==c?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,b.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(this,
-null,0,a))):(a=g.certificate,console.log("KeyLocator contains CERT"),console.log(a))}}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.")};
+null!=a.contentObject&&this.transport.send(encodeToBinaryContentObject(a.contentObject)));else if(b.peekStartElement(CCNProtocolDTags.ContentObject)){if(3<LOG&&console.log("ContentObject packet received."),a=new ContentObject,a.from_ccnb(b),b=NDN.getEntryForExpressedInterest(a.name),null==b&&console.log("PIT search failed for "+a.name.to_uri()),null!=b){clearTimeout(b.timerID);var c=NDN.PITTable.indexOf(b);0<=c?console.log("received. remove PIT "+b.debugId+". "+a.name.to_uri()):console.log("can't find PIT "+
+b.debugId);0<=c&&NDN.PITTable.splice(c,1);b=b.closure;if(!1==this.verify)b.upcall(Closure.UPCALL_CONTENT_UNVERIFIED,new UpcallInfo(this,null,0,a));else{var d=function(a,b,c,d,e){this.contentObject=a;this.closure=b;this.keyName=c;this.sigHex=d;this.witness=e;Closure.call(this)},e=this;d.prototype.upcall=function(a,b){if(a==Closure.UPCALL_INTEREST_TIMED_OUT)console.log("In KeyFetchClosure.upcall: interest time out."),console.log(this.keyName.contentName.getName());else if(a==Closure.UPCALL_CONTENT){var c=
+decodeSubjectPublicKeyInfo(b.contentObject.content),d=!0==c.verifyByteArray(this.contentObject.rawSignatureData,this.witness,this.sigHex)?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD;this.closure.upcall(d,new UpcallInfo(e,null,0,this.contentObject));c=new KeyStoreEntry(g.keyName,c,(new Date).getTime());NDN.addKeyEntry(c)}else a==Closure.UPCALL_CONTENT_BAD&&console.log("In KeyFetchClosure.upcall: signature verification failed")};if(a.signedInfo&&a.signedInfo.locator&&a.signature){3<LOG&&console.log("Key verification...");
+var c=DataUtils.toHex(a.signature.signature).toLowerCase(),f=null;null!=a.signature.Witness&&(f=new Witness,f.decode(a.signature.Witness));var g=a.signedInfo.locator;if(g.type==KeyLocatorType.KEYNAME)if(3<LOG&&console.log("KeyLocator contains KEYNAME"),g.keyName.contentName.match(a.name))3<LOG&&console.log("Content is key itself"),d=decodeSubjectPublicKeyInfo(a.content),c=d.verifyByteArray(a.rawSignatureData,f,c),c=!0==c?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,b.upcall(c,new UpcallInfo(this,
+null,0,a));else{var j=NDN.getKeyByName(g.keyName);j?(3<LOG&&console.log("Local key cache hit"),d=j.rsaKey,c=d.verifyByteArray(a.rawSignatureData,f,c),c=!0==c?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,b.upcall(c,new UpcallInfo(this,null,0,a))):(3<LOG&&console.log("Fetch key according to keylocator"),a=new d(a,b,g.keyName,c,f),this.expressInterest(g.keyName.contentName.getPrefix(4),a))}else g.type==KeyLocatorType.KEY?(3<LOG&&console.log("Keylocator contains KEY"),d=decodeSubjectPublicKeyInfo(a.signedInfo.locator.publicKey),
+c=d.verifyByteArray(a.rawSignatureData,f,c),c=!0==c?Closure.UPCALL_CONTENT:Closure.UPCALL_CONTENT_BAD,b.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(this,null,0,a))):(a=g.certificate,console.log("KeyLocator contains CERT"),console.log(a))}}}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.")};
 NDN.prototype.connectAndExecute=function(a){var b=this.getHostAndPort();if(null==b)console.log("ERROR: No more hosts from getHostAndPort"),this.host=null;else if(b.host==this.host&&b.port==this.port)console.log("ERROR: The host returned by getHostAndPort is not alive: "+this.host+":"+this.port);else{this.host=b.host;this.port=b.port;3<LOG&&console.log("Connect: trying host from getHostAndPort: "+this.host);b=new Interest(new Name("/"));b.interestLifetime=4E3;var c=this,d=setTimeout(function(){3<LOG&&
 console.log("Connect: timeout waiting for host "+c.host);c.connectAndExecute(a)},3E3);this.reconnectAndExpressInterest(b,new NDN.ConnectClosure(this,a,d))}};NDN.ConnectClosure=function(a,b,c){Closure.call(this);this.ndn=a;this.onConnected=b;this.timerID=c};NDN.ConnectClosure.prototype.upcall=function(a){if(!(a==Closure.UPCALL_CONTENT||a==Closure.UPCALL_CONTENT_UNVERIFIED))return Closure.RESULT_ERR;clearTimeout(this.timerID);this.ndn.readyStatus=NDN.OPENED;this.ndn.onopen();this.onConnected();return Closure.RESULT_OK};
 var BinaryXmlElementReader=function(a){this.elementListener=a;this.dataParts=[];this.structureDecoder=new BinaryXMLStructureDecoder};
diff --git a/js/util/ExponentialReExpressClosure.js b/js/util/ExponentialReExpressClosure.js
new file mode 100644
index 0000000..1057f60
--- /dev/null
+++ b/js/util/ExponentialReExpressClosure.js
@@ -0,0 +1,50 @@
+/**
+ * @author: Jeff Thompson
+ * See COPYING for copyright and distribution information.
+ * This is the closure class for use in expressInterest to re express with exponential falloff.
+ */
+
+/*
+ * Create a new ExponentialReExpressClosure where upcall responds to UPCALL_INTEREST_TIMED_OUT
+ *   by expressing the interest again with double the interestLifetime. If the interesLifetime goes
+ *   over maxInterestLifetime, then call callerClosure.upcall with UPCALL_INTEREST_TIMED_OUT.
+ * When upcall is not UPCALL_INTEREST_TIMED_OUT, just call callerClosure.upcall.
+ * 
+ * settings is an associative array with the following defaults:
+ * {
+ *   maxInterestLifetime: 16000 // milliseconds
+ * }
+ */
+var ExponentialReExpressClosure = function ExponentialReExpressClosure
+        (callerClosure, settings) {
+    // Inherit from Closure.
+    Closure.call(this);
+    
+    this.callerClosure = callerClosure;
+    settings = (settings || {});
+	this.maxInterestLifetime = (settings.maxInterestLifetime || 16000);
+};
+
+ExponentialReExpressClosure.prototype.upcall = function(kind, upcallInfo) {
+    try {
+        if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+            var interestLifetime = upcallInfo.interest.interestLifetime;
+            if (interestLifetime == null)
+                return this.callerClosure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, upcallInfo);
+            
+            var nextInterestLifetime = interestLifetime * 2;
+            if (nextInterestLifetime > this.maxInterestLifetime)
+                return this.callerClosure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, upcallInfo);
+            
+            var nextInterest = upcallInfo.interest.clone();
+            nextInterest.interestLifetime = nextInterestLifetime;
+            upcallInfo.ndn.expressInterest(nextInterest.name, this, nextInterest);
+            return Closure.RESULT_OK;
+        }  
+        else
+            return this.callerClosure.upcall(kind, upcallInfo);
+    } catch (ex) {
+        console.log("ExponentialReExpressClosure.upcall exception: " + ex);
+        return Closure.RESULT_ERR;
+    }
+};