Added Closure.js.  Put expressInterest in NDN.js.  Use it in ccnxProtocolService.js.
diff --git a/js/Closure.js b/js/Closure.js
new file mode 100644
index 0000000..909c3c8
--- /dev/null
+++ b/js/Closure.js
@@ -0,0 +1,62 @@
+/* 
+ * @author: ucla-cs
+ * See COPYING for copyright and distribution information.
+ * Provide the callback closure for the async communication methods in the NDN class.
+ * This is a port of Closure.py from PyCCN, written by: 
+ * Derek Kulinski <takeda@takeda.tk>
+ * Jeff Burke <jburke@ucla.edu>
+ */
+
+/*
+ * Create a subclass of Closure and pass an object to async calls.
+ */
+var Closure = function Closure() {
+	// I don't think storing NDN's closure is needed
+	// and it creates a reference loop, as of now both
+	// of those variables are never set -- Derek
+	//
+	// Use instance variables to return data to callback
+	this.ndn_data = null;  // this holds the ndn_closure
+    this.ndn_data_dirty = false;
+};
+
+// Upcall result
+Closure.RESULT_ERR               = -1; // upcall detected an error
+Closure.RESULT_OK                =  0; // normal upcall return
+Closure.RESULT_REEXPRESS         =  1; // reexpress the same interest again
+Closure.RESULT_INTEREST_CONSUMED =  2; // upcall claims to consume interest
+Closure.RESULT_VERIFY            =  3; // force an unverified result to be verified
+
+// Upcall kind
+Closure.UPCALL_FINAL              = 0; // handler is about to be deregistered
+Closure.UPCALL_INTEREST           = 1; // incoming interest
+Closure.UPCALL_CONSUMED_INTEREST  = 2; // incoming interest, someone has answered
+Closure.UPCALL_CONTENT            = 3; // incoming verified content
+Closure.UPCALL_INTEREST_TIMED_OUT = 4; // interest timed out
+Closure.UPCALL_CONTENT_UNVERIFIED = 5; // content that has not been verified
+Closure.UPCALL_CONTENT_BAD        = 6; // verification failed
+
+/*
+ * Override this in your subclass.
+ * If you're getting strange errors in upcall()
+ * check your code whether you're returning a value.
+ */
+Closure.prototype.upcall = function(kind, upcallInfo) {
+	//dump('upcall ' + this + " " + kind + " " + upcallInfo + "\n");
+	return Closure.RESULT_OK;
+};
+
+var UpcallInfo = function UpcallInfo(ndn, interest, matchedComps, contentObject) {
+	this.ndn = ndn;  // NDN object (not used)
+	this.interest = interest;  // Interest object
+	this.matchedComps = matchedComps;  // int
+	this.contentObject = contentObject;  // Content object
+};
+
+UpcallInfo.prototype.toString = function() {
+	var ret = "ndn = " + this.ndn;
+	ret += "\nInterest = " + this.interest;
+	ret += "\nmatchedComps = " + this.matchedComps;
+	ret += "\nContentObject: " + this.contentObject;
+	return ret;
+}
diff --git a/js/NDN.js b/js/NDN.js
index 2b32df8..1202f08 100644
--- a/js/NDN.js
+++ b/js/NDN.js
@@ -72,7 +72,6 @@
 
 }
 
-
 NDN.prototype.put = function(name,content){
 	if(this.host!=null && this.port!=null){
 		
@@ -149,13 +148,60 @@
 	return result;
 	}
 	else{
-
-		
 		console.log('ERROR URL OR PORT NOT SET');
 
 		return null;
-
 	}
-	
+}
 
-}
\ No newline at end of file
+/** Encode name as an Interest. If template is not null, use its attributes.
+ *  Send the interest to host:port, read the entire response and call
+ *  closure.upcall(Closure.UPCALL_CONTENT (or Closure.UPCALL_CONTENT_UNVERIFIED),
+ *                 new UpcallInfo(this, interest, 0, contentObject)).
+ */
+NDN.prototype.expressInterest = function(
+        // Name
+        name,
+        // Closure
+        closure,
+        // Interest
+        template) {
+	if (this.host == null || this.port == null) {
+		dump('ERROR host OR port NOT SET\n');
+        return;
+    }
+    
+	interest = new Interest(name);
+    if (template != null) {
+        // TODO: Exactly what do we copy from template?
+        interest.InterestLifetime = template.InterestLifetime;
+    }
+    else
+        interest.InterestLifetime = 4200;
+	var outputHex = encodeToHexInterest(interest);
+		
+	var dataListener = {
+		onReceivedData : function(result) {
+			if (result == null || result == undefined || result.length == 0)
+				listener.onReceivedContentObject(null);
+			else {
+                var decoder = new BinaryXMLDecoder(result);	
+                var co = new ContentObject();
+                co.from_ccnb(decoder);
+                   					
+				if(LOG>2) {
+					dump('DECODED CONTENT OBJECT\n');
+					dump(co);
+					dump('\n');
+				}
+					
+                // TODO: verify the content object and set kind to UPCALL_CONTENT.
+				closure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED,
+                   new UpcallInfo(this, interest, 0, co))
+			}
+		}
+	}
+        
+	return getAsync(this.host, this.port, outputHex, dataListener);
+};
+
diff --git a/js/NDNSocketTransportService.js b/js/NDNSocketTransportService.js
index aa12588..1f8a93d 100644
--- a/js/NDNSocketTransportService.js
+++ b/js/NDNSocketTransportService.js
@@ -63,7 +63,7 @@
                     // Finish.
                     this.onStopRequest();
 			} catch (ex) {
-				dump("onDataAvailable exception: " + ex + "\n");
+				dump("readAllFromSocket.onDataAvailable exception: " + ex + "\n");
 			}
 		}
     };
@@ -71,50 +71,3 @@
 	pump.init(inStream, -1, -1, 0, 0, true);
     pump.asyncRead(dataListener, null);
 }
-
-
-// TODO: This should be moved to the main NDN.js when we agree on how to do non-blocking get.
-// For now, assume this is included after NDN.js and modify it.
-/** Encode message as an Interest, send it to host:port, read the entire response and call
-      listener.onReceivedContentObject(contentObject).
- */
-NDN.prototype.getAsync = function(message, listener) {
-	if (this.host != null && this.port != null) {
-		var output ='';
-		message = message.trim();
-		if(message==null || message =="" ){
-			dump('INVALID INPUT TO GET\n');
-			return null;
-		}
-		
-		interest = new Interest(new Name(message));
-		interest.InterestLifetime = 4200;
-		var outputHex = encodeToHexInterest(interest);
-		
-		var dataListener = {
-			onReceivedData : function(result) {
-				if (result == null || result == undefined || result.length == 0)
-					listener.onReceivedContentObject(null);
-				else {
-                    var decoder = new BinaryXMLDecoder(result);	
-                    var co = new ContentObject();
-                    co.from_ccnb(decoder);
-					
-					if(LOG>2) {
-						dump('DECODED CONTENT OBJECT\n');
-						dump(co);
-						dump('\n');
-					}
-					
-					listener.onReceivedContentObject(co);
-				}
-			}
-		}
-        
-		return getAsync(this.host, this.port, outputHex, dataListener);
-	}
-	else {
-		dump('ERROR host OR port NOT SET\n');
-	}
-}
-
diff --git a/js/ccnxProtocol.xpi b/js/ccnxProtocol.xpi
index 9fd4dbb..45cb47f 100644
--- a/js/ccnxProtocol.xpi
+++ b/js/ccnxProtocol.xpi
Binary files differ
diff --git a/js/ccnxProtocol/components/ccnxProtocolService.js b/js/ccnxProtocol/components/ccnxProtocolService.js
index 2bf369b..c7d4d1e 100644
--- a/js/ccnxProtocol/components/ccnxProtocolService.js
+++ b/js/ccnxProtocol/components/ccnxProtocolService.js
@@ -35,50 +35,58 @@
 	{

 		try {

 			var requestContent = function(contentListener) {

-				var interest = aURI.spec.split(":")[1];

+				var name = aURI.spec.split(":")[1];

 			    // 131.179.141.18 is lioncub.metwi.ucla.edu .

 				var ndn = new NDN('131.179.141.18');

 				

-				var coListener = {

-					onReceivedContentObject : function(contentObject) {

-						// Set up defaults.

-						var content = "";

-						var contentType = "text/html";

-						var contentCharset = "utf-8";

+				var ContentClosure = function ContentClosure() {

+                    // Inherit from Closure.

+                    Closure.call(this);

+                }

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

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

+                          kind == Closure.UPCALL_CONTENT_UNVERIFIED))

+                        // The upcall is not for us.

+                        return;

+                        

+                    var contentObject = upcallInfo.contentObject;

+                        

+					// Set up defaults.

+					var content = "";

+					var contentType = "text/html";

+					var contentCharset = "utf-8";

 

-						// TODO: Need to check the signature, confirm that the name matches, etc.

-						if (contentObject.content != null) {

-							content = DataUtils.toString(contentObject.content);

+					if (contentObject.content != null) {

+						content = DataUtils.toString(contentObject.content);

 

-							// TODO: Should look at the returned Name to get contentType. For now,

-							//   just look for a file extension in the original interest.

-							var interestLowerCase = interest.toLowerCase();

-							if (interestLowerCase.indexOf(".gif") >= 0) {

-								contentType = "image/gif";

-								contentCharset = "ISO-8859-1";

-							}

-							else if (interestLowerCase.indexOf(".jpg") >= 0 ||

-									 interestLowerCase.indexOf(".jpeg") >= 0) {

-								contentType = "image/jpeg";

-								contentCharset = "ISO-8859-1";

-							}

-							else if (interestLowerCase.indexOf(".png") >= 0) {

-								contentType = "image/png";

-								contentCharset = "ISO-8859-1";

-							}

-							else if (interestLowerCase.indexOf(".bmp") >= 0) {

-								contentType = "image/bmp";

-								contentCharset = "ISO-8859-1";

-							}

-							else if (interestLowerCase.indexOf(".css") >= 0)

-								contentType = "text/css";

+						// TODO: Should look at the returned Name to get contentType. For now,

+						//   just look for a file extension in the original name.

+						var nameLowerCase = name.toLowerCase();

+						if (nameLowerCase.indexOf(".gif") >= 0) {

+							contentType = "image/gif";

+							contentCharset = "ISO-8859-1";

 						}

-						

-						contentListener.onReceivedContent(content, contentType, contentCharset);

+						else if (nameLowerCase.indexOf(".jpg") >= 0 ||

+								 nameLowerCase.indexOf(".jpeg") >= 0) {

+							contentType = "image/jpeg";

+							contentCharset = "ISO-8859-1";

+						}

+						else if (nameLowerCase.indexOf(".png") >= 0) {

+							contentType = "image/png";

+							contentCharset = "ISO-8859-1";

+						}

+						else if (nameLowerCase.indexOf(".bmp") >= 0) {

+							contentType = "image/bmp";

+							contentCharset = "ISO-8859-1";

+						}

+						else if (nameLowerCase.indexOf(".css") >= 0)

+							contentType = "text/css";

 					}

+						

+					contentListener.onReceivedContent(content, contentType, contentCharset);

 				};

 			

-				ndn.getAsync(interest, coListener);

+				ndn.expressInterest(new Name(name), new ContentClosure());

 			};

 

 			return new ContentChannel(aURI, requestContent);

diff --git a/js/ccnxProtocol/modules/make-ndn-js.jsm.sh b/js/ccnxProtocol/modules/make-ndn-js.jsm.sh
index 2c4209b..27749e3 100755
--- a/js/ccnxProtocol/modules/make-ndn-js.jsm.sh
+++ b/js/ccnxProtocol/modules/make-ndn-js.jsm.sh
@@ -1,5 +1,6 @@
 #!/bin/sh
 cat ndn-js-header.txt \
+  ../../Closure.js emptyLine.txt \
   ../../NDN.js emptyLine.txt \
   ../../NDNSocketTransportService.js emptyLine.txt \
   ../../util/CCNProtocolDTags.js emptyLine.txt \
diff --git a/js/ccnxProtocol/modules/ndn-js-header.txt b/js/ccnxProtocol/modules/ndn-js-header.txt
index 63c037c..967f9e2 100644
--- a/js/ccnxProtocol/modules/ndn-js-header.txt
+++ b/js/ccnxProtocol/modules/ndn-js-header.txt
@@ -5,7 +5,7 @@
  * See COPYING for copyright and distribution information.
  */
 
-var EXPORTED_SYMBOLS = ["NDN", "DataUtils", "readAllFromSocket"];
+var EXPORTED_SYMBOLS = ["NDN", "Closure", "Name", "Interest", "ContentObject", "DataUtils"];
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
diff --git a/js/ccnxProtocol/modules/ndn-js.jsm b/js/ccnxProtocol/modules/ndn-js.jsm
index e5f2837..a432819 100644
--- a/js/ccnxProtocol/modules/ndn-js.jsm
+++ b/js/ccnxProtocol/modules/ndn-js.jsm
@@ -5,7 +5,7 @@
  * See COPYING for copyright and distribution information.
  */
 
-var EXPORTED_SYMBOLS = ["NDN", "DataUtils", "readAllFromSocket"];
+var EXPORTED_SYMBOLS = ["NDN", "Closure", "Name", "Interest", "ContentObject", "DataUtils"];
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
@@ -25,6 +25,69 @@
     }
 };
 
+/* 
+ * @author: ucla-cs
+ * See COPYING for copyright and distribution information.
+ * Provide the callback closure for the async communication methods in the NDN class.
+ * This is a port of Closure.py from PyCCN, written by: 
+ * Derek Kulinski <takeda@takeda.tk>
+ * Jeff Burke <jburke@ucla.edu>
+ */
+
+/*
+ * Create a subclass of Closure and pass an object to async calls.
+ */
+var Closure = function Closure() {
+	// I don't think storing NDN's closure is needed
+	// and it creates a reference loop, as of now both
+	// of those variables are never set -- Derek
+	//
+	// Use instance variables to return data to callback
+	this.ndn_data = null;  // this holds the ndn_closure
+    this.ndn_data_dirty = false;
+};
+
+// Upcall result
+Closure.RESULT_ERR               = -1; // upcall detected an error
+Closure.RESULT_OK                =  0; // normal upcall return
+Closure.RESULT_REEXPRESS         =  1; // reexpress the same interest again
+Closure.RESULT_INTEREST_CONSUMED =  2; // upcall claims to consume interest
+Closure.RESULT_VERIFY            =  3; // force an unverified result to be verified
+
+// Upcall kind
+Closure.UPCALL_FINAL              = 0; // handler is about to be deregistered
+Closure.UPCALL_INTEREST           = 1; // incoming interest
+Closure.UPCALL_CONSUMED_INTEREST  = 2; // incoming interest, someone has answered
+Closure.UPCALL_CONTENT            = 3; // incoming verified content
+Closure.UPCALL_INTEREST_TIMED_OUT = 4; // interest timed out
+Closure.UPCALL_CONTENT_UNVERIFIED = 5; // content that has not been verified
+Closure.UPCALL_CONTENT_BAD        = 6; // verification failed
+
+/*
+ * Override this in your subclass.
+ * If you're getting strange errors in upcall()
+ * check your code whether you're returning a value.
+ */
+Closure.prototype.upcall = function(kind, upcallInfo) {
+	//dump('upcall ' + this + " " + kind + " " + upcallInfo + "\n");
+	return Closure.RESULT_OK;
+};
+
+var UpcallInfo = function UpcallInfo(ndn, interest, matchedComps, contentObject) {
+	this.ndn = ndn;  // NDN object (not used)
+	this.interest = interest;  // Interest object
+	this.matchedComps = matchedComps;  // int
+	this.contentObject = contentObject;  // Content object
+};
+
+UpcallInfo.prototype.toString = function() {
+	var ret = "ndn = " + this.ndn;
+	ret += "\nInterest = " + this.interest;
+	ret += "\nmatchedComps = " + this.matchedComps;
+	ret += "\nContentObject: " + this.contentObject;
+	return ret;
+}
+
 /*
  * @author: ucla-cs
  * See COPYING for copyright and distribution information.
@@ -99,7 +162,6 @@
 
 }
 
-
 NDN.prototype.put = function(name,content){
 	if(this.host!=null && this.port!=null){
 		
@@ -176,16 +238,64 @@
 	return result;
 	}
 	else{
-
-		
 		console.log('ERROR URL OR PORT NOT SET');
 
 		return null;
-
 	}
-	
-
 }
+
+/** Encode name as an Interest. If template is not null, use its attributes.
+ *  Send the interest to host:port, read the entire response and call
+ *  closure.upcall(Closure.UPCALL_CONTENT (or Closure.UPCALL_CONTENT_UNVERIFIED),
+ *                 new UpcallInfo(this, interest, 0, contentObject)).
+ */
+NDN.prototype.expressInterest = function(
+        // Name
+        name,
+        // Closure
+        closure,
+        // Interest
+        template) {
+	if (this.host == null || this.port == null) {
+		dump('ERROR host OR port NOT SET\n');
+        return;
+    }
+    
+	interest = new Interest(name);
+    if (template != null) {
+        // TODO: Exactly what do we copy from template?
+        interest.InterestLifetime = template.InterestLifetime;
+    }
+    else
+        interest.InterestLifetime = 4200;
+	var outputHex = encodeToHexInterest(interest);
+		
+	var dataListener = {
+		onReceivedData : function(result) {
+			if (result == null || result == undefined || result.length == 0)
+				listener.onReceivedContentObject(null);
+			else {
+                var decoder = new BinaryXMLDecoder(result);	
+                var co = new ContentObject();
+                co.from_ccnb(decoder);
+                   					
+				if(LOG>2) {
+					dump('DECODED CONTENT OBJECT\n');
+					dump(co);
+					dump('\n');
+				}
+					
+                // TODO: verify the content object and set kind to UPCALL_CONTENT.
+				closure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED,
+                   new UpcallInfo(this, interest, 0, co))
+			}
+		}
+	}
+        
+	return getAsync(this.host, this.port, outputHex, dataListener);
+};
+
+
 /* 
  * @author: ucla-cs
  * See COPYING for copyright and distribution information.
@@ -251,7 +361,7 @@
                     // Finish.
                     this.onStopRequest();
 			} catch (ex) {
-				dump("onDataAvailable exception: " + ex + "\n");
+				dump("readAllFromSocket.onDataAvailable exception: " + ex + "\n");
 			}
 		}
     };
@@ -260,53 +370,6 @@
     pump.asyncRead(dataListener, null);
 }
 
-
-// TODO: This should be moved to the main NDN.js when we agree on how to do non-blocking get.
-// For now, assume this is included after NDN.js and modify it.
-/** Encode message as an Interest, send it to host:port, read the entire response and call
-      listener.onReceivedContentObject(contentObject).
- */
-NDN.prototype.getAsync = function(message, listener) {
-	if (this.host != null && this.port != null) {
-		var output ='';
-		message = message.trim();
-		if(message==null || message =="" ){
-			dump('INVALID INPUT TO GET\n');
-			return null;
-		}
-		
-		interest = new Interest(new Name(message));
-		interest.InterestLifetime = 4200;
-		var outputHex = encodeToHexInterest(interest);
-		
-		var dataListener = {
-			onReceivedData : function(result) {
-				if (result == null || result == undefined || result.length == 0)
-					listener.onReceivedContentObject(null);
-				else {
-                    var decoder = new BinaryXMLDecoder(result);	
-                    var co = new ContentObject();
-                    co.from_ccnb(decoder);
-					
-					if(LOG>2) {
-						dump('DECODED CONTENT OBJECT\n');
-						dump(co);
-						dump('\n');
-					}
-					
-					listener.onReceivedContentObject(co);
-				}
-			}
-		}
-        
-		return getAsync(this.host, this.port, outputHex, dataListener);
-	}
-	else {
-		dump('ERROR host OR port NOT SET\n');
-	}
-}
-
-
 /*
  * @author: ucla-cs
  * See COPYING for copyright and distribution information.
@@ -4005,6 +4068,10 @@
 	    
 	    output+= "<br />";
 	}
+	if(co.signedInfo !=null && co.signedInfo.finalBlockID!=null){
+	    output += "FinalBlockID: "+ DataUtils.toHex(co.signedInfo.finalBlockID);
+	    output+= "<br />";
+	}
 	if(co.signedInfo!=null && co.signedInfo.locator!=null && co.signedInfo.locator.certificate!=null){
 	    var tmp = DataUtils.toString(co.signedInfo.locator.certificate);
 	    var publickey = rstr2b64(tmp);