For XPCOM (the Firefox add-on), connect to a random testbed host.
diff --git a/js/ndnProtocol.xpi b/js/ndnProtocol.xpi
index a42bb90..6b8fe85 100644
--- a/js/ndnProtocol.xpi
+++ b/js/ndnProtocol.xpi
Binary files differ
diff --git a/js/ndnProtocol/modules/ndn-js.jsm b/js/ndnProtocol/modules/ndn-js.jsm
index 2d36170..14bb540 100644
--- a/js/ndnProtocol/modules/ndn-js.jsm
+++ b/js/ndnProtocol/modules/ndn-js.jsm
@@ -24,19 +24,35 @@
     }
 };
 
+// The NDN class uses setTimeout and clearTimeout, so define them using XPCOM.
+function setTimeout(callback, delay) {
+    var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+    timer.initWithCallback({notify: callback}, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+    return timer;
+}
+
+function clearTimeout(timer) {
+    timer.cancel();
+}
 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.host=b.host||"localhost";this.port=b.port||9696;this.transport=(b.getTransport||function(){return new WebSocketTransport})();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.prototype.createRoute=function(a,b){this.host=a;this.port=b};
-NDN.prototype.expressInterest=function(a,b,c){null==this.host||null==this.port?dump("ERROR host OR port NOT SET\n"):(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=4,this.transport.expressInterest(this,a,b))};
-NDN.prototype.registerPrefix=function(a,b,c){return this.transport.registerPrefix(this,a,b,c)};var WebSocketTransport=function(){this.ccndid=this.ws=null;this.maxBufferSize=1E4;this.buffer=new Uint8Array(this.maxBufferSize);this.bufferOffset=0;this.structureDecoder=new BinaryXMLStructureDecoder},ccndIdFetcher="/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY";
+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";dump("this.host "+this.host+"\n");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.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=4;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=4;var d=this,e=setTimeout(function(){console.log("Timeout waiting for host "+
+d.host);d.connectAndExpressInterest(a,b)},3E3);this.transport.expressInterest(this,c,new NDN.ConnectClosure(this,a,b,e))}};NDN.ConnectClosure=function(a,b,c,d){Closure.call(this);this.ndn=a;this.callerInterest=b;this.callerClosure=c;this.timerID=d};
+NDN.ConnectClosure.prototype.upcall=function(a){if(!(a==Closure.UPCALL_CONTENT||a==Closure.UPCALL_CONTENT_UNVERIFIED||a==Closure.UPCALL_INTEREST))return Closure.RESULT_ERR;clearTimeout(this.timerID);console.log(this.ndn.host+": Host is alive. Fetching callerInterest.");this.ndn.transport.expressInterest(this.ndn,this.callerInterest,this.callerClosure);return Closure.RESULT_OK};
+var WebSocketTransport=function(){this.ccndid=this.ws=null;this.maxBufferSize=1E4;this.buffer=new Uint8Array(this.maxBufferSize);this.bufferOffset=0;this.structureDecoder=new BinaryXMLStructureDecoder;this.defaultGetHostAndPort=NDN.makeShuffledGetHostAndPort(["A.ws.ndn.ucla.edu","B.ws.ndn.ucla.edu","C.ws.ndn.ucla.edu","D.ws.ndn.ucla.edu","E.ws.ndn.ucla.edu"],9696)};
 WebSocketTransport.prototype.connectWebSocket=function(a){null!=this.ws&&delete this.ws;this.ws=new WebSocket("ws://"+a.host+":"+a.port);0<LOG&&console.log("ws connection created.");this.ws.binaryType="arraybuffer";var b=this;this.ws.onmessage=function(c){c=c.data;if(null==c||void 0==c||""==c)console.log("INVALID ANSWER");else if(c instanceof ArrayBuffer){c=new Uint8Array(c);3<LOG&&console.log("BINARY RESPONSE IS "+DataUtils.toHex(c));try{if(c.length+b.bufferOffset>=b.buffer.byteLength){3<LOG&&console.log("NDN.ws.onmessage: buffer overflow. Accumulate received length: "+
 b.bufferOffset+". Current packet length: "+c.length+".");delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0;return}b.buffer.set(c,b.bufferOffset);b.bufferOffset+=c.length;if(!b.structureDecoder.findElementEnd(b.buffer.subarray(0,b.bufferOffset))){3<LOG&&console.log("Incomplete packet received. Length "+c.length+". Wait for more input.");return}3<LOG&&console.log("Complete packet received. Length "+c.length+
 ". Start decoding.")}catch(d){console.log("NDN.ws.onmessage exception: "+d);return}c=new BinaryXMLDecoder(b.buffer);if(c.peekStartElement(CCNProtocolDTags.Interest)){3<LOG&&console.log("Interest packet received.");var e=new Interest;e.from_ccnb(c);3<LOG&&console.log(e);var f=escape(e.name.getName());3<LOG&&console.log(f);f=getEntryForRegisteredPrefix(f);null!=f&&f.closure.upcall(Closure.UPCALL_INTEREST,new UpcallInfo(a,e,0,null))}else if(c.peekStartElement(CCNProtocolDTags.ContentObject))if(3<LOG&&
-console.log("ContentObject packet received."),e=new ContentObject,e.from_ccnb(c),3<LOG&&console.log(e),f=e.name.getName(),3<LOG&&console.log(f),null==b.ccndid&&null!=f.match(ccndIdFetcher))!e.signedInfo||!e.signedInfo.publisher||!e.signedInfo.publisher.publisherPublicKeyDigest?(console.log("Cannot contact router, close NDN now."),a.readyStatus=NDN.CLOSED,a.onclose()):(b.ccndid=e.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(b.ccndid),a.readyStatus=NDN.OPENED,a.onopen());else{if(f=
-getEntryForExpressedInterest(f),null!=f){clearTimeout(f.closure.timerID);var g=PITTable.indexOf(f);PITTable.splice(g,1);f.closure.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,e))}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.");delete c;delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0}};this.ws.onopen=function(a){3<LOG&&console.log(a);3<LOG&&console.log("ws.onopen: WebSocket connection opened.");
-3<LOG&&console.log("ws.onopen: ReadyState: "+this.readyState);a=new Interest(new Name(ccndIdFetcher));a.interestLifetime=4;var a=encodeToBinaryInterest(a),d=new Uint8Array(a.length);d.set(a);b.ws.send(d.buffer)};this.ws.onerror=function(a){console.log("ws.onerror: ReadyState: "+this.readyState);console.log(a);console.log("ws.onerror: WebSocket error: "+a.data)};this.ws.onclose=function(){console.log("ws.onclose: WebSocket connection closed.");b.ws=null;a.readyStatus=NDN.CLOSED;a.onclose()}};
-var PITTable=[],PITEntry=function(a,b){this.interest=a;this.closure=b};function getEntryForExpressedInterest(a){for(var b=0;b<PITTable.length;b++)if(null!=a.match(PITTable[b].interest))return PITTable[b];return null}
-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.name.getName(),c);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=PITTable.indexOf(f);PITTable.splice(d,1);c.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,new UpcallInfo(a,b,0,null))},1E3*b.interestLifetime)}else console.log("WebSocket connection is not established.")};
+console.log("ContentObject packet received."),e=new ContentObject,e.from_ccnb(c),3<LOG&&console.log(e),f=e.name.getName(),3<LOG&&console.log(f),null==b.ccndid&&null!=f.match(NDN.ccndIdFetcher))!e.signedInfo||!e.signedInfo.publisher||!e.signedInfo.publisher.publisherPublicKeyDigest?(console.log("Cannot contact router, close NDN now."),a.readyStatus=NDN.CLOSED,a.onclose()):(b.ccndid=e.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(b.ccndid),a.readyStatus=NDN.OPENED,a.onopen());else{if(f=
+NDN.getEntryForExpressedInterest(e.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,e))}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.");delete c;delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0}};this.ws.onopen=function(a){3<LOG&&console.log(a);
+3<LOG&&console.log("ws.onopen: WebSocket connection opened.");3<LOG&&console.log("ws.onopen: ReadyState: "+this.readyState);a=new Interest(new Name(NDN.ccndIdFetcher));a.interestLifetime=4;var a=encodeToBinaryInterest(a),d=new Uint8Array(a.length);d.set(a);b.ws.send(d.buffer)};this.ws.onerror=function(a){console.log("ws.onerror: ReadyState: "+this.readyState);console.log(a);console.log("ws.onerror: WebSocket error: "+a.data)};this.ws.onclose=function(){console.log("ws.onclose: WebSocket connection closed.");
+b.ws=null;a.readyStatus=NDN.CLOSED;a.onclose()}};
+WebSocketTransport.prototype.expressInterest=function(a,b,c){if(null!=this.ws){var d=encodeToBinaryInterest(b),e=new Uint8Array(d.length);e.set(d);var f=new PITEntry(b,c);NDN.PITTable.push(f);this.ws.send(e.buffer);3<LOG&&console.log("ws.send() returned.");c.timerID=setTimeout(function(){3<LOG&&console.log("Interest time out.");var d=NDN.PITTable.indexOf(f);0<=d&&NDN.PITTable.splice(d,1);c.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,new UpcallInfo(a,b,0,null))},1E3*b.interestLifetime)}else console.log("WebSocket connection is not established.")};
 var CSTable=[],CSEntry=function(a,b){this.name=a;this.closure=b};function getEntryForRegisteredPrefix(a){for(var b=0;b<CSTable.length;b++)if(null!=CSTable[b].name.match(a))return CSTable[b];return null}
 WebSocketTransport.prototype.registerPrefix=function(a,b,c){if(null!=this.ws){if(null==this.ccndid)return console.log("ccnd node ID unkonwn. Cannot register prefix."),-1;var a=new ForwardingEntry("selfreg",b,null,null,3,2147483647),a=encodeForwardingEntry(a),d=new SignedInfo;d.setFields();a=new ContentObject(new Name,d,a,new Signature);a.sign();a=encodeToBinaryContentObject(a);a=new Name(["ccnx",this.ccndid,"selfreg",a]);a=new Interest(a);a.scope=1;d=encodeToBinaryInterest(a);a=new Uint8Array(d.length);
 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};
@@ -53,7 +69,7 @@
 Name.prototype.getElementLabel=function(){return CCNProtocolDTags.Name};
 Name.prototype.add=function(a){var b;if("string"==typeof a)b=DataUtils.stringToUtf8Array(a);else if("object"==typeof a&&a instanceof Uint8Array)b=new Uint8Array(a);else if("object"==typeof a&&a instanceof ArrayBuffer)b=new Uint8Array(new ArrayBuffer(a.byteLength)),b.set(new Uint8Array(a));else if("object"==typeof a)b=new Uint8Array(a);else throw Error("Cannot add Name element at index "+this.components.length+": Invalid type");return this.components.push(b)};
 Name.prototype.to_uri=function(){for(var a="",b=0;b<this.components.length;++b)a+="/"+Name.toEscapedString(this.components[b]);return a};Name.prototype.getPrefix=function(a){return new Name(this.components.slice(0,a))};Name.prototype.getComponent=function(a){var b=new ArrayBuffer(this.components[a].length);(new Uint8Array(b)).set(this.components[a]);return b};
-Name.prototype.indexOfFileName=function(){for(var a=this.components.length-1;0<=a;--a){var b=this.components[a];if(!(0>=b.length)&&!(0==b[0]||192==b[0]||193==b[0]||245<=b[0]&&255>=b[0]))return a}return-1};
+Name.prototype.indexOfFileName=function(){for(var a=this.components.length-1;0<=a;--a){var b=this.components[a];if(!(0>=b.length)&&!(0==b[0]||192==b[0]||193==b[0]||245<=b[0]&&255>=b[0]))return a}return-1};Name.prototype.equalsName=function(a){if(this.components.length!=a.components.length)return!1;for(var b=this.components.length-1;0<=b;--b)if(!DataUtils.arraysEqual(this.components[b],a.components[b]))return!1;return!0};
 Name.prototype.getContentDigestValue=function(){for(var a=Name.ContentDigestPrefix.length+32+Name.ContentDigestSuffix.length,b=this.components.length-1;0<=b;--b)if(this.components[b].length==a&&DataUtils.arraysEqual(this.components[b].subarray(0,Name.ContentDigestPrefix.length),Name.ContentDigestPrefix)&&DataUtils.arraysEqual(this.components[b].subarray(this.components[b].length-Name.ContentDigestSuffix.length,this.components[b].length),Name.ContentDigestSuffix))return this.components[b].subarray(Name.ContentDigestPrefix.length,
 Name.ContentDigestPrefix.length+32);return null};Name.ContentDigestPrefix=new Uint8Array([193,46,77,46,71,193,1,170,2,133]);Name.ContentDigestSuffix=new Uint8Array([0]);Name.toEscapedString=function(a){for(var b="",c=!1,d=0;d<a.length;++d)if(46!=a[d]){c=!0;break}if(c)for(d=0;d<a.length;++d)c=a[d],b=48<=c&&57>=c||65<=c&&90>=c||97<=c&&122>=c||43==c||45==c||46==c||95==c?b+String.fromCharCode(c):b+("%"+(16>c?"0":"")+c.toString(16).toUpperCase());else{b="...";for(d=0;d<a.length;++d)b+="."}return b};
 var ContentObject=function(a,b,c,d){this.name="string"===typeof a?new Name(a):a;this.signedInfo=b;this.content=c;this.signature=d;this.rawSignatureData=this.endContent=this.startSignedInfo=this.endSIG=this.startSIG=null};
@@ -157,10 +173,10 @@
 DataUtils.toString=function(a){for(var b="",c=0;c<a.length;c++)b+=String.fromCharCode(a[c]);return b};DataUtils.toNumbers=function(a){if("string"==typeof a){var b=new Uint8Array(Math.floor(a.length/2)),c=0;a.replace(/(..)/g,function(a){b[c++]=parseInt(a,16)});return b}};DataUtils.hexToRawString=function(a){if("string"==typeof a){var b="";a.replace(/(..)/g,function(a){b+=String.fromCharCode(parseInt(a,16))});return b}};
 DataUtils.toNumbersFromString=function(a){for(var b=new Uint8Array(a.length),c=0;c<a.length;c++)b[c]=a.charCodeAt(c);return b};DataUtils.stringToUtf8Array=function(a){return DataUtils.toNumbersFromString(str2rstr_utf8(a))};DataUtils.concatArrays=function(a){for(var b=0,c=0;c<a.length;++c)b+=a[c].length;for(var b=new Uint8Array(b),d=0,c=0;c<a.length;++c)b.set(a[c],d),d+=a[c].length;return b};
 DataUtils.decodeUtf8=function(a){for(var b="",c=0,d=0,e=0;c<a.length;)if(d=a.charCodeAt(c),128>d)b+=String.fromCharCode(d),c++;else if(191<d&&224>d)e=a.charCodeAt(c+1),b+=String.fromCharCode((d&31)<<6|e&63),c+=2;else var e=a.charCodeAt(c+1),f=a.charCodeAt(c+2),b=b+String.fromCharCode((d&15)<<12|(e&63)<<6|f&63),c=c+3;return b};DataUtils.arraysEqual=function(a,b){if(a.length!=b.length)return!1;for(var c=0;c<a.length;++c)if(a[c]!=b[c])return!1;return!0};
-DataUtils.bigEndianToUnsignedInt=function(a){for(var b=0,c=0;c<a.length;++c)b<<=8,b+=a[c];return b};DataUtils.nonNegativeIntToBigEndian=function(a){a=Math.round(a);if(0>=a)return new Uint8Array(0);for(var b=new Uint8Array(8),c=0;0!=a;)++c,b[8-c]=a&255,a>>=8;return b.subarray(8-c,8)};function encodeToHexInterest(a){return DataUtils.toHex(encodeToBinaryInterest(a))}function encodeToBinaryInterest(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}
-function encodeToHexContentObject(a){return DataUtils.toHex(encodeToBinaryContentObject(a))}function encodeToBinaryContentObject(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function encodeForwardingEntry(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function decodeHexFaceInstance(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX FACE INSTANCE  \n"+a);a=new FaceInstance;a.from_ccnb(decoder);return a}
-function decodeHexInterest(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX INTERST  \n"+a);a=new Interest;a.from_ccnb(decoder);return a}function decodeHexContentObject(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX CONTENT OBJECT \n"+a);co=new ContentObject;co.from_ccnb(decoder);return co}
-function decodeHexForwardingEntry(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX FORWARDING ENTRY \n"+a);forwardingEntry=new ForwardingEntry;forwardingEntry.from_ccnb(decoder);return forwardingEntry}
+DataUtils.bigEndianToUnsignedInt=function(a){for(var b=0,c=0;c<a.length;++c)b<<=8,b+=a[c];return b};DataUtils.nonNegativeIntToBigEndian=function(a){a=Math.round(a);if(0>=a)return new Uint8Array(0);for(var b=new Uint8Array(8),c=0;0!=a;)++c,b[8-c]=a&255,a>>=8;return b.subarray(8-c,8)};DataUtils.shuffle=function(a){for(var b=a.length-1;1<=b;--b){var c=Math.floor(Math.random()*(b+1)),d=a[b];a[b]=a[c];a[c]=d}};function encodeToHexInterest(a){return DataUtils.toHex(encodeToBinaryInterest(a))}
+function encodeToBinaryInterest(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function encodeToHexContentObject(a){return DataUtils.toHex(encodeToBinaryContentObject(a))}function encodeToBinaryContentObject(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function encodeForwardingEntry(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}
+function decodeHexFaceInstance(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX FACE INSTANCE  \n"+a);a=new FaceInstance;a.from_ccnb(decoder);return a}function decodeHexInterest(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX INTERST  \n"+a);a=new Interest;a.from_ccnb(decoder);return a}
+function decodeHexContentObject(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX CONTENT OBJECT \n"+a);co=new ContentObject;co.from_ccnb(decoder);return co}function decodeHexForwardingEntry(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX FORWARDING ENTRY \n"+a);forwardingEntry=new ForwardingEntry;forwardingEntry.from_ccnb(decoder);return forwardingEntry}
 function contentObjectToHtml(a){var b="";if(-1==a)b+="NO CONTENT FOUND";else if(-2==a)b+="CONTENT NAME IS EMPTY";else{null!=a.name&&null!=a.name.components&&(b+="NAME: "+a.name.to_uri(),b+="<br /><br />");null!=a.content&&(b+="CONTENT(ASCII): "+DataUtils.toString(a.content),b+="<br />",b+="<br />");null!=a.content&&(b+="CONTENT(hex): "+DataUtils.toHex(a.content),b+="<br />",b+="<br />");null!=a.signature&&null!=a.signature.signature&&(b+="SIGNATURE(hex): "+DataUtils.toHex(a.signature.signature),b+=
 "<br />",b+="<br />");null!=a.signedInfo&&(null!=a.signedInfo.publisher&&null!=a.signedInfo.publisher.publisherPublicKeyDigest)&&(b+="Publisher Public Key Digest(hex): "+DataUtils.toHex(a.signedInfo.publisher.publisherPublicKeyDigest),b+="<br />",b+="<br />");if(null!=a.signedInfo&&null!=a.signedInfo.timestamp){var c=new Date;c.setTime(a.signedInfo.timestamp.msec);b+="TimeStamp: "+c;b+="<br />";b+="TimeStamp(number): "+a.signedInfo.timestamp.msec;b+="<br />"}null!=a.signedInfo&&null!=a.signedInfo.finalBlockID&&
 (b+="FinalBlockID: "+DataUtils.toHex(a.signedInfo.finalBlockID),b+="<br />");if(null!=a.signedInfo&&null!=a.signedInfo.locator&&null!=a.signedInfo.locator.certificate){var c=DataUtils.toString(a.signedInfo.locator.certificate),d=rstr2b64(c),e=DataUtils.toHex(a.signedInfo.locator.certificate).toLowerCase(),f=DataUtils.toString(a.signedInfo.locator.certificate),c=DataUtils.toHex(a.signature.signature).toLowerCase(),g=DataUtils.toString(a.rawSignatureData),b=b+("DER Certificate: "+d),b=b+"<br />",b=
@@ -282,7 +298,12 @@
 // Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 // Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
-var XpcomTransport = function XpcomTransport() {    
+var XpcomTransport = function XpcomTransport() { 
+    this.defaultGetHostAndPort = NDN.makeShuffledGetHostAndPort
+        (["A.hub.ndn.ucla.edu", "B.hub.ndn.ucla.edu", "C.hub.ndn.ucla.edu", "D.hub.ndn.ucla.edu", 
+          "E.hub.ndn.ucla.edu", "F.hub.ndn.ucla.edu", "G.hub.ndn.ucla.edu", "H.hub.ndn.ucla.edu"],
+         9695);
+    this.defaultGetHostAndPort = NDN.makeShuffledGetHostAndPort(["E.hub.ndn.ucla.edu"], 9695); //DEBUG
 };
 
 XpcomTransport.prototype.expressInterest = function(ndn, interest, closure) {
@@ -293,28 +314,42 @@
 			if (data == null || data == undefined || data.length == 0)
 				dump("NDN.expressInterest: received empty data from socket.\n");
 			else {
-                var decoder = new BinaryXMLDecoder(data);	
-                var co = new ContentObject();
-                co.from_ccnb(decoder);
+                var decoder = new BinaryXMLDecoder(data);
+                if (decoder.peekStartElement(CCNProtocolDTags.Interest)) {
+                    // TODO: handle interest
+                    if (closure.upcall(Closure.UPCALL_INTEREST, null) == Closure.RESULT_OK)
+                        // success
+                        return true;
+                }
+                else if (decoder.peekStartElement(CCNProtocolDTags.ContentObject)) {
+                    var co = new ContentObject();
+                    co.from_ccnb(decoder);
                    					
-                // TODO: verify the content object and set kind to UPCALL_CONTENT.
-				var result = closure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED,
+                    // TODO: verify the content object and set kind to UPCALL_CONTENT.
+                    var result = closure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED,
                                new UpcallInfo(ndn, interest, 0, co));
-                if (result == Closure.RESULT_OK) {
-                    // success
+                    if (result == Closure.RESULT_OK)
+                        // success
+                        return true;
+                    else if (result == Closure.RESULT_ERR)
+                        dump("NDN.expressInterest: upcall returned RESULT_ERR.\n");
+                    else if (result == Closure.RESULT_REEXPRESS) {
+                        XpcomTransport.readAllFromSocket(ndn.host, ndn.port, binaryInterest, dataListener);
+                        return true;
+                    }
+                    else if (result == Closure.RESULT_VERIFY) {
+                        // TODO: force verification of content.
+                    }
+                    else if (result == Closure.RESULT_FETCHKEY) {
+                        // TODO: get the key in the key locator and re-call the interest
+                        //   with the key available in the local storage.
+                    }
                 }
-                else if (result == Closure.RESULT_ERR)
-                    dump("NDN.expressInterest: upcall returned RESULT_ERR.\n");
-                else if (result == Closure.RESULT_REEXPRESS)
-                    XpcomTransport.readAllFromSocket(ndn.host, ndn.port, binaryInterest, dataListener);
-                else if (result == Closure.RESULT_VERIFY) {
-                    // TODO: force verification of content.
-                }
-                else if (result == Closure.RESULT_FETCHKEY) {
-                    // TODO: get the key in the key locator and re-call the interest
-                    //   with the key available in the local storage.
-                }
+                else
+                    console.log('Incoming packet is not Interest or ContentObject. Discard now.');
 			}
+            
+            return false;
 		}
 	}    
     
@@ -322,7 +357,8 @@
 };
 
 /** Send outputData (Uint8Array) to host:port, read the entire response and call 
- *    listener.onReceivedData(data) where data is Uint8Array.
+ *    listener.onReceivedData(data) where data is Uint8Array and returns true if the data is consumed,
+ *    false if need to keep reading.
  *  Code derived from http://stackoverflow.com/questions/7816386/why-nsiscriptableinputstream-is-not-working .
  */
 XpcomTransport.readAllFromSocket = function(host, port, outputData, listener) {
@@ -339,20 +375,16 @@
 	var dataListener = {
 		dataParts: [],
         structureDecoder: new BinaryXMLStructureDecoder(),
-		calledOnReceivedData: false,
+		dataIsConsumed: false,
 		
 		onStartRequest: function (request, context) {
 		},
 		onStopRequest: function (request, context, status) {
 			inStream.close();
 			outStream.close();
-			if (!this.calledOnReceivedData) {
-				this.calledOnReceivedData = true;
-				listener.onReceivedData(DataUtils.concatArrays(this.dataParts));
-			}
 		},
 		onDataAvailable: function (request, context, _inputStream, offset, count) {
-            if (this.calledOnReceivedData)
+            if (this.dataIsConsumed)
                 // Already finished.  Ignore extra data.
                 return;
             
@@ -362,14 +394,35 @@
                 // TODO: Can we go directly from the stream to Uint8Array?
 				var rawData = DataUtils.toNumbersFromString
                     (NetUtil.readInputStreamToString(inStream, count));
-                // Save for later call to concatArrays so that we only reallocate a buffer once.
-                this.dataParts.push(rawData);
 				
-				// Scan the input to check if a whole ccnb object has been read.
-                this.structureDecoder.seek(0);
-                if (this.structureDecoder.findElementEnd(rawData))
-                    // Finish.
-                    this.onStopRequest();
+                // Process multiple objects in this packet.
+                while(true) {
+                    // Scan the input to check if a whole ccnb object has been read.
+                    this.structureDecoder.seek(0);
+                    if (this.structureDecoder.findElementEnd(rawData)) {
+                        // Got the remainder of an object.  Report to the caller.
+                        this.dataParts.push(rawData.subarray(0, this.structureDecoder.offset));
+                        if (listener.onReceivedData(DataUtils.concatArrays(this.dataParts))) {
+                            this.dataIsConsumed = true;
+                            this.onStopRequest();
+                            return;
+                        }
+                    
+                        // Need to read a new object.
+                        rawData = rawData.subarray(this.structureDecoder.offset, rawData.length);
+                        this.dataParts = [];
+                        this.structureDecoder = new BinaryXMLStructureDecoder();
+                        if (rawData.length == 0)
+                            // No more data in the packet.
+                            return;
+                        // else loop back to decode.
+                    }
+                    else {
+                        // Save for a later call to concatArrays so that we only copy data once.
+                        this.dataParts.push(rawData);
+                        return;
+                    }
+                }
 			} catch (ex) {
 				dump("readAllFromSocket.onDataAvailable exception: " + ex + "\n");
 			}
diff --git a/js/tools/build/ndn-js-uncomp.js b/js/tools/build/ndn-js-uncomp.js
index c2f610c..73d3862 100644
--- a/js/tools/build/ndn-js-uncomp.js
+++ b/js/tools/build/ndn-js-uncomp.js
@@ -75,19 +75,25 @@
 /**
  * settings is an associative array with the following defaults:
  * {
- *   host: 'localhost',
- *   port: 9696,
  *   getTransport: function() { return new WebSocketTransport(); }
+ *   getHostAndPort: transport.defaultGetHostAndPort,
+ *   host: 'localhost', // If null, use getHostAndPort when connecting.
+ *   port: 9696,
  *   onopen: function() { if (LOG > 3) console.log("NDN connection established."); }
  *   onclose: function() { if (LOG > 3) console.log("NDN connection closed."); }
  * }
+ * 
+ * getHostAndPort is a function, on each call it returns a new { host: host, port: port } or
+ *   null if there are no more hosts.
  */
 var NDN = function NDN(settings) {
     settings = (settings || {});
-	this.host = (settings.host || "localhost");
-	this.port = (settings.port || 9696);
     var getTransport = (settings.getTransport || function() { return new WebSocketTransport(); });
     this.transport = getTransport();
+    this.getHostAndPort = (settings.getHostAndPort || this.transport.defaultGetHostAndPort);
+	this.host = (settings.host !== undefined ? settings.host : 'localhost');
+    dump("this.host " + this.host + "\n");
+	this.port = (settings.port || 9696);
     this.readyStatus = NDN.UNOPEN;
     // Event handler
     this.onopen = (settings.onopen || function() { if (LOG > 3) console.log("NDN connection established."); });
@@ -98,13 +104,55 @@
 NDN.OPENED = 1;  // connection to ccnd opened
 NDN.CLOSED = 2;  // connection to ccnd closed
 
-/* Java Socket Bridge and XPCOM transport */
+NDN.ccndIdFetcher = '/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY';
 
 NDN.prototype.createRoute = function(host,port){
 	this.host=host;
 	this.port=port;
 }
 
+// For fetching data
+NDN.PITTable = new Array();
+
+var PITEntry = function PITEntry(interest, closure) {
+	this.interest = interest;  // Interest
+	this.closure = closure;    // Closure
+};
+
+// Return the longest entry from NDN.PITTable that matches name.
+NDN.getEntryForExpressedInterest = function(/*Name*/ name) {
+    // TODO: handle multiple matches?  Maybe not from registerPrefix because multiple ContentObject
+    //   could be sent for one Interest?
+    var result = null;
+    
+	for (var i = 0; i < NDN.PITTable.length; i++) {
+		if (NDN.PITTable[i].interest.matches_name(name)) {
+            if (result == null || 
+                NDN.PITTable[i].interest.name.components.length > result.interest.name.components.length)
+                result = NDN.PITTable[i];
+        }
+	}
+    
+	return result;
+};
+
+/*
+ * Return a function that selects a host at random from hostList and returns { host: host, port: port }.
+ * If no more hosts remain, return null.
+ */
+NDN.makeShuffledGetHostAndPort = function(hostList, port) {
+    // Make a copy.
+    hostList = hostList.slice(0, hostList.length);
+    DataUtils.shuffle(hostList);
+
+    return function() {
+        if (hostList.length == 0)
+            return null;
+        
+        return { host: hostList.splice(0, 1)[0], port: port };
+    };
+};
+
 /** 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),
@@ -117,11 +165,6 @@
         closure,
         // Interest
         template) {
-	if (this.host == null || this.port == null) {
-		dump('ERROR host OR port NOT SET\n');
-        return;
-    }
-    
 	var interest = new Interest(name);
     if (template != null) {
 		interest.minSuffixComponents = template.minSuffixComponents;
@@ -135,14 +178,83 @@
     }
     else
         interest.interestLifetime = 4.0;   // default interest timeout value in seconds.
-    
-    this.transport.expressInterest(this, interest, closure);
-};
 
+	if (this.host == null || this.port == null) {
+        if (this.getHostAndPort == null)
+            console.log('ERROR: host OR port NOT SET');
+        else
+            this.connectAndExpressInterest(interest, closure);
+    }
+    else
+        this.transport.expressInterest(this, interest, closure);
+};
 
 NDN.prototype.registerPrefix = function(name, closure, flag) {
     return this.transport.registerPrefix(this, name, closure, flag);
 }
+
+/*
+ * Assume this.getHostAndPort is not null.  This is called when this.host is null or its host
+ *   is not alive.  Get a host and port, connect, then express callerInterest with callerClosure.
+ */
+NDN.prototype.connectAndExpressInterest = function(callerInterest, callerClosure) {
+    var hostAndPort = this.getHostAndPort();
+    if (hostAndPort == null) {
+        console.log('ERROR: No more hosts from getHostAndPort');
+        this.host = null;
+        return;
+    }
+
+    if (hostAndPort.host == this.host && hostAndPort.port == this.port) {
+        console.log('ERROR: The host returned by getHostAndPort is not alive: ' + 
+                this.host + ":" + this.port);
+        return;
+    }
+        
+    this.host = hostAndPort.host;
+    this.port = hostAndPort.port;   
+    console.log("Trying host from getHostAndPort: " + this.host);
+    
+    // Fetch the ccndId.
+    var interest = new Interest(new Name(NDN.ccndIdFetcher));
+	interest.interestLifetime = 4.0; // seconds    
+
+    var thisNDN = this;
+	var timerID = setTimeout(function() {
+        console.log("Timeout waiting for host " + thisNDN.host);
+        // Try again.
+        thisNDN.connectAndExpressInterest(callerInterest, callerClosure);
+	}, 3000);
+  
+    this.transport.expressInterest
+        (this, interest, new NDN.ConnectClosure(this, callerInterest, callerClosure, timerID));
+}
+
+NDN.ConnectClosure = function ConnectClosure(ndn, callerInterest, callerClosure, timerID) {
+    // Inherit from Closure.
+    Closure.call(this);
+    
+    this.ndn = ndn;
+    this.callerInterest = callerInterest;
+    this.callerClosure = callerClosure;
+    this.timerID = timerID;
+};
+
+NDN.ConnectClosure.prototype.upcall = function(kind, upcallInfo) {
+    if (!(kind == Closure.UPCALL_CONTENT ||
+          kind == Closure.UPCALL_CONTENT_UNVERIFIED ||
+          kind == Closure.UPCALL_INTEREST))
+        // The upcall is not for us.
+        return Closure.RESULT_ERR;
+        
+    // The host is alive, so cancel the timeout and issue the caller's interest.
+    clearTimeout(this.timerID);
+    console.log(this.ndn.host + ": Host is alive. Fetching callerInterest.");
+    this.ndn.transport.expressInterest(this.ndn, this.callerInterest, this.callerClosure);
+
+    return Closure.RESULT_OK;
+};
+
 /** 
  * @author: Wentao Shang
  * See COPYING for copyright and distribution information.
@@ -155,10 +267,12 @@
 	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);
 };
 
-var ccndIdFetcher = '/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY';
-
 WebSocketTransport.prototype.connectWebSocket = function(ndn) {
 	if (this.ws != null)
 		delete this.ws;
@@ -239,7 +353,7 @@
 				nameStr = co.name.getName();
 				if (LOG > 3) console.log(nameStr);
 				
-				if (self.ccndid == null && nameStr.match(ccndIdFetcher) != null) {
+				if (self.ccndid == null && nameStr.match(NDN.ccndIdFetcher) != null) {
 					// We are in starting phase, record publisherPublicKeyDigest in self.ccndid
 					if(!co.signedInfo || !co.signedInfo.publisher 
 						|| !co.signedInfo.publisher.publisherPublicKeyDigest) {
@@ -260,7 +374,7 @@
 						//console.log("NDN.onopen event fired.");
 					}
 				} else {
-					var pitEntry = getEntryForExpressedInterest(nameStr);
+					var pitEntry = NDN.getEntryForExpressedInterest(co.name);
 					if (pitEntry != null) {
 						//console.log(pitEntry);
 						
@@ -269,9 +383,10 @@
 						//console.log("Clear interest timer");
 						//console.log(pitEntry.closure.timerID);
 						
-						// Remove PIT entry from PITTable
-						var index = PITTable.indexOf(pitEntry);
-						PITTable.splice(index, 1);
+						// Remove PIT entry from NDN.PITTable
+						var index = NDN.PITTable.indexOf(pitEntry);
+						if (index >= 0)
+                            NDN.PITTable.splice(index, 1);
 						
 						// Raise callback
 						pitEntry.closure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(ndn, null, 0, co));
@@ -298,7 +413,7 @@
 		if (LOG > 3) console.log('ws.onopen: ReadyState: ' + this.readyState);
 
 		// Fetch ccndid now
-		var interest = new Interest(new Name(ccndIdFetcher));
+		var interest = new Interest(new Name(NDN.ccndIdFetcher));
 		interest.interestLifetime = 4.0; // seconds
 		var subarray = encodeToBinaryInterest(interest);
 		
@@ -323,25 +438,7 @@
 		ndn.onclose();
 		//console.log("NDN.onclose event fired.");
 	}
-}
-
-
-// For fetching data
-var PITTable = new Array();
-
-var PITEntry = function PITEntry(interest, closure) {
-	this.interest = interest;  // String
-	this.closure = closure;    // Closure
-}
-
-function getEntryForExpressedInterest(name) {
-	for (var i = 0; i < PITTable.length; i++) {
-		if (name.match(PITTable[i].interest) != null)
-			return PITTable[i];
-			// TODO: handle multiple matching prefixes
-	}
-	return null;
-}
+};
 
 WebSocketTransport.prototype.expressInterest = function(ndn, interest, closure) {
 	if (this.ws != null) {
@@ -351,8 +448,8 @@
 		var bytearray = new Uint8Array(binaryInterest.length);
 		bytearray.set(binaryInterest);
 		
-		var pitEntry = new PITEntry(interest.name.getName(), closure);
-		PITTable.push(pitEntry);
+		var pitEntry = new PITEntry(interest, closure);
+		NDN.PITTable.push(pitEntry);
 		
 		this.ws.send(bytearray.buffer);
 		if (LOG > 3) console.log('ws.send() returned.');
@@ -361,11 +458,12 @@
 		closure.timerID = setTimeout(function() {
 			if (LOG > 3) console.log("Interest time out.");
 			
-			// Remove PIT entry from PITTable
-			var index = PITTable.indexOf(pitEntry);
-			//console.log(PITTable);
-			PITTable.splice(index, 1);
-			//console.log(PITTable);
+			// Remove PIT entry from NDN.PITTable
+			var index = NDN.PITTable.indexOf(pitEntry);
+			//console.log(NDN.PITTable);
+			if (index >= 0) 
+                NDN.PITTable.splice(index, 1);
+			//console.log(NDN.PITTable);
 			// Raise closure callback
 			closure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT, new UpcallInfo(ndn, interest, 0, null));
 		}, interest.interestLifetime * 1000);  // convert interestLifetime from seconds to ms.
@@ -382,7 +480,7 @@
 var CSEntry = function CSEntry(name, closure) {
 	this.name = name;        // String
 	this.closure = closure;  // Closure
-}
+};
 
 function getEntryForRegisteredPrefix(name) {
 	for (var i = 0; i < CSTable.length; i++) {
@@ -437,7 +535,7 @@
 		console.log('WebSocket connection is not established.');
 		return -1;
 	}
-}
+};
 
 /**
  * @author: Meki Cheraoui
@@ -900,6 +998,22 @@
 }
 
 /*
+ * Return true if this Name has the same components as name.
+ */
+Name.prototype.equalsName = function(name) {
+    if (this.components.length != name.components.length)
+        return false;
+    
+    // Start from the last component because they are more likely to differ.
+    for (var i = this.components.length - 1; i >= 0; --i) {
+        if (!DataUtils.arraysEqual(this.components[i], name.components[i]))
+            return false;
+    }
+    
+    return true;
+}
+
+/*
  * Find the last component in name that has a ContentDigest and return the digest value as Uint8Array, 
  *   or null if not found.
  * A ContentDigest component is Name.ContentDigestPrefix + 32 bytes + Name.ContentDigestSuffix.
@@ -4086,6 +4200,19 @@
     }
     return result.subarray(size - i, size);
 };
+
+/*
+ * Modify array to randomly shuffle the elements.
+ */
+DataUtils.shuffle = function(array) {
+    for (var i = array.length - 1; i >= 1; --i) {
+        // j is from 0 to i.
+        var j = Math.floor(Math.random() * (i + 1));
+        var temp = array[i];
+        array[i] = array[j];
+        array[j] = temp;
+    }
+}
 /**
  * This file contains utilities to help encode and decode NDN objects.
  * author: Meki Cheraoui
diff --git a/js/tools/build/ndn-js.js b/js/tools/build/ndn-js.js
index 8ce7d0a..a0dffdc 100644
--- a/js/tools/build/ndn-js.js
+++ b/js/tools/build/ndn-js.js
@@ -1,16 +1,22 @@
 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.host=b.host||"localhost";this.port=b.port||9696;this.transport=(b.getTransport||function(){return new WebSocketTransport})();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.prototype.createRoute=function(a,b){this.host=a;this.port=b};
-NDN.prototype.expressInterest=function(a,b,c){null==this.host||null==this.port?dump("ERROR host OR port NOT SET\n"):(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=4,this.transport.expressInterest(this,a,b))};
-NDN.prototype.registerPrefix=function(a,b,c){return this.transport.registerPrefix(this,a,b,c)};var WebSocketTransport=function(){this.ccndid=this.ws=null;this.maxBufferSize=1E4;this.buffer=new Uint8Array(this.maxBufferSize);this.bufferOffset=0;this.structureDecoder=new BinaryXMLStructureDecoder},ccndIdFetcher="/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY";
+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";dump("this.host "+this.host+"\n");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.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=4;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=4;var d=this,e=setTimeout(function(){console.log("Timeout waiting for host "+
+d.host);d.connectAndExpressInterest(a,b)},3E3);this.transport.expressInterest(this,c,new NDN.ConnectClosure(this,a,b,e))}};NDN.ConnectClosure=function(a,b,c,d){Closure.call(this);this.ndn=a;this.callerInterest=b;this.callerClosure=c;this.timerID=d};
+NDN.ConnectClosure.prototype.upcall=function(a){if(!(a==Closure.UPCALL_CONTENT||a==Closure.UPCALL_CONTENT_UNVERIFIED||a==Closure.UPCALL_INTEREST))return Closure.RESULT_ERR;clearTimeout(this.timerID);console.log(this.ndn.host+": Host is alive. Fetching callerInterest.");this.ndn.transport.expressInterest(this.ndn,this.callerInterest,this.callerClosure);return Closure.RESULT_OK};
+var WebSocketTransport=function(){this.ccndid=this.ws=null;this.maxBufferSize=1E4;this.buffer=new Uint8Array(this.maxBufferSize);this.bufferOffset=0;this.structureDecoder=new BinaryXMLStructureDecoder;this.defaultGetHostAndPort=NDN.makeShuffledGetHostAndPort(["A.ws.ndn.ucla.edu","B.ws.ndn.ucla.edu","C.ws.ndn.ucla.edu","D.ws.ndn.ucla.edu","E.ws.ndn.ucla.edu"],9696)};
 WebSocketTransport.prototype.connectWebSocket=function(a){null!=this.ws&&delete this.ws;this.ws=new WebSocket("ws://"+a.host+":"+a.port);0<LOG&&console.log("ws connection created.");this.ws.binaryType="arraybuffer";var b=this;this.ws.onmessage=function(c){c=c.data;if(null==c||void 0==c||""==c)console.log("INVALID ANSWER");else if(c instanceof ArrayBuffer){c=new Uint8Array(c);3<LOG&&console.log("BINARY RESPONSE IS "+DataUtils.toHex(c));try{if(c.length+b.bufferOffset>=b.buffer.byteLength){3<LOG&&console.log("NDN.ws.onmessage: buffer overflow. Accumulate received length: "+
 b.bufferOffset+". Current packet length: "+c.length+".");delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0;return}b.buffer.set(c,b.bufferOffset);b.bufferOffset+=c.length;if(!b.structureDecoder.findElementEnd(b.buffer.subarray(0,b.bufferOffset))){3<LOG&&console.log("Incomplete packet received. Length "+c.length+". Wait for more input.");return}3<LOG&&console.log("Complete packet received. Length "+c.length+
 ". Start decoding.")}catch(d){console.log("NDN.ws.onmessage exception: "+d);return}c=new BinaryXMLDecoder(b.buffer);if(c.peekStartElement(CCNProtocolDTags.Interest)){3<LOG&&console.log("Interest packet received.");var e=new Interest;e.from_ccnb(c);3<LOG&&console.log(e);var f=escape(e.name.getName());3<LOG&&console.log(f);f=getEntryForRegisteredPrefix(f);null!=f&&f.closure.upcall(Closure.UPCALL_INTEREST,new UpcallInfo(a,e,0,null))}else if(c.peekStartElement(CCNProtocolDTags.ContentObject))if(3<LOG&&
-console.log("ContentObject packet received."),e=new ContentObject,e.from_ccnb(c),3<LOG&&console.log(e),f=e.name.getName(),3<LOG&&console.log(f),null==b.ccndid&&null!=f.match(ccndIdFetcher))!e.signedInfo||!e.signedInfo.publisher||!e.signedInfo.publisher.publisherPublicKeyDigest?(console.log("Cannot contact router, close NDN now."),a.readyStatus=NDN.CLOSED,a.onclose()):(b.ccndid=e.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(b.ccndid),a.readyStatus=NDN.OPENED,a.onopen());else{if(f=
-getEntryForExpressedInterest(f),null!=f){clearTimeout(f.closure.timerID);var g=PITTable.indexOf(f);PITTable.splice(g,1);f.closure.upcall(Closure.UPCALL_CONTENT,new UpcallInfo(a,null,0,e))}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.");delete c;delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0}};this.ws.onopen=function(a){3<LOG&&console.log(a);3<LOG&&console.log("ws.onopen: WebSocket connection opened.");
-3<LOG&&console.log("ws.onopen: ReadyState: "+this.readyState);a=new Interest(new Name(ccndIdFetcher));a.interestLifetime=4;var a=encodeToBinaryInterest(a),d=new Uint8Array(a.length);d.set(a);b.ws.send(d.buffer)};this.ws.onerror=function(a){console.log("ws.onerror: ReadyState: "+this.readyState);console.log(a);console.log("ws.onerror: WebSocket error: "+a.data)};this.ws.onclose=function(){console.log("ws.onclose: WebSocket connection closed.");b.ws=null;a.readyStatus=NDN.CLOSED;a.onclose()}};
-var PITTable=[],PITEntry=function(a,b){this.interest=a;this.closure=b};function getEntryForExpressedInterest(a){for(var b=0;b<PITTable.length;b++)if(null!=a.match(PITTable[b].interest))return PITTable[b];return null}
-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.name.getName(),c);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=PITTable.indexOf(f);PITTable.splice(d,1);c.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,new UpcallInfo(a,b,0,null))},1E3*b.interestLifetime)}else console.log("WebSocket connection is not established.")};
+console.log("ContentObject packet received."),e=new ContentObject,e.from_ccnb(c),3<LOG&&console.log(e),f=e.name.getName(),3<LOG&&console.log(f),null==b.ccndid&&null!=f.match(NDN.ccndIdFetcher))!e.signedInfo||!e.signedInfo.publisher||!e.signedInfo.publisher.publisherPublicKeyDigest?(console.log("Cannot contact router, close NDN now."),a.readyStatus=NDN.CLOSED,a.onclose()):(b.ccndid=e.signedInfo.publisher.publisherPublicKeyDigest,3<LOG&&console.log(b.ccndid),a.readyStatus=NDN.OPENED,a.onopen());else{if(f=
+NDN.getEntryForExpressedInterest(e.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,e))}}else console.log("Incoming packet is not Interest or ContentObject. Discard now.");delete c;delete b.structureDecoder;delete b.buffer;b.structureDecoder=new BinaryXMLStructureDecoder;b.buffer=new Uint8Array(b.maxBufferSize);b.bufferOffset=0}};this.ws.onopen=function(a){3<LOG&&console.log(a);
+3<LOG&&console.log("ws.onopen: WebSocket connection opened.");3<LOG&&console.log("ws.onopen: ReadyState: "+this.readyState);a=new Interest(new Name(NDN.ccndIdFetcher));a.interestLifetime=4;var a=encodeToBinaryInterest(a),d=new Uint8Array(a.length);d.set(a);b.ws.send(d.buffer)};this.ws.onerror=function(a){console.log("ws.onerror: ReadyState: "+this.readyState);console.log(a);console.log("ws.onerror: WebSocket error: "+a.data)};this.ws.onclose=function(){console.log("ws.onclose: WebSocket connection closed.");
+b.ws=null;a.readyStatus=NDN.CLOSED;a.onclose()}};
+WebSocketTransport.prototype.expressInterest=function(a,b,c){if(null!=this.ws){var d=encodeToBinaryInterest(b),e=new Uint8Array(d.length);e.set(d);var f=new PITEntry(b,c);NDN.PITTable.push(f);this.ws.send(e.buffer);3<LOG&&console.log("ws.send() returned.");c.timerID=setTimeout(function(){3<LOG&&console.log("Interest time out.");var d=NDN.PITTable.indexOf(f);0<=d&&NDN.PITTable.splice(d,1);c.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,new UpcallInfo(a,b,0,null))},1E3*b.interestLifetime)}else console.log("WebSocket connection is not established.")};
 var CSTable=[],CSEntry=function(a,b){this.name=a;this.closure=b};function getEntryForRegisteredPrefix(a){for(var b=0;b<CSTable.length;b++)if(null!=CSTable[b].name.match(a))return CSTable[b];return null}
 WebSocketTransport.prototype.registerPrefix=function(a,b,c){if(null!=this.ws){if(null==this.ccndid)return console.log("ccnd node ID unkonwn. Cannot register prefix."),-1;var a=new ForwardingEntry("selfreg",b,null,null,3,2147483647),a=encodeForwardingEntry(a),d=new SignedInfo;d.setFields();a=new ContentObject(new Name,d,a,new Signature);a.sign();a=encodeToBinaryContentObject(a);a=new Name(["ccnx",this.ccndid,"selfreg",a]);a=new Interest(a);a.scope=1;d=encodeToBinaryInterest(a);a=new Uint8Array(d.length);
 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};
@@ -27,7 +33,7 @@
 Name.prototype.getElementLabel=function(){return CCNProtocolDTags.Name};
 Name.prototype.add=function(a){var b;if("string"==typeof a)b=DataUtils.stringToUtf8Array(a);else if("object"==typeof a&&a instanceof Uint8Array)b=new Uint8Array(a);else if("object"==typeof a&&a instanceof ArrayBuffer)b=new Uint8Array(new ArrayBuffer(a.byteLength)),b.set(new Uint8Array(a));else if("object"==typeof a)b=new Uint8Array(a);else throw Error("Cannot add Name element at index "+this.components.length+": Invalid type");return this.components.push(b)};
 Name.prototype.to_uri=function(){for(var a="",b=0;b<this.components.length;++b)a+="/"+Name.toEscapedString(this.components[b]);return a};Name.prototype.getPrefix=function(a){return new Name(this.components.slice(0,a))};Name.prototype.getComponent=function(a){var b=new ArrayBuffer(this.components[a].length);(new Uint8Array(b)).set(this.components[a]);return b};
-Name.prototype.indexOfFileName=function(){for(var a=this.components.length-1;0<=a;--a){var b=this.components[a];if(!(0>=b.length)&&!(0==b[0]||192==b[0]||193==b[0]||245<=b[0]&&255>=b[0]))return a}return-1};
+Name.prototype.indexOfFileName=function(){for(var a=this.components.length-1;0<=a;--a){var b=this.components[a];if(!(0>=b.length)&&!(0==b[0]||192==b[0]||193==b[0]||245<=b[0]&&255>=b[0]))return a}return-1};Name.prototype.equalsName=function(a){if(this.components.length!=a.components.length)return!1;for(var b=this.components.length-1;0<=b;--b)if(!DataUtils.arraysEqual(this.components[b],a.components[b]))return!1;return!0};
 Name.prototype.getContentDigestValue=function(){for(var a=Name.ContentDigestPrefix.length+32+Name.ContentDigestSuffix.length,b=this.components.length-1;0<=b;--b)if(this.components[b].length==a&&DataUtils.arraysEqual(this.components[b].subarray(0,Name.ContentDigestPrefix.length),Name.ContentDigestPrefix)&&DataUtils.arraysEqual(this.components[b].subarray(this.components[b].length-Name.ContentDigestSuffix.length,this.components[b].length),Name.ContentDigestSuffix))return this.components[b].subarray(Name.ContentDigestPrefix.length,
 Name.ContentDigestPrefix.length+32);return null};Name.ContentDigestPrefix=new Uint8Array([193,46,77,46,71,193,1,170,2,133]);Name.ContentDigestSuffix=new Uint8Array([0]);Name.toEscapedString=function(a){for(var b="",c=!1,d=0;d<a.length;++d)if(46!=a[d]){c=!0;break}if(c)for(d=0;d<a.length;++d)c=a[d],b=48<=c&&57>=c||65<=c&&90>=c||97<=c&&122>=c||43==c||45==c||46==c||95==c?b+String.fromCharCode(c):b+("%"+(16>c?"0":"")+c.toString(16).toUpperCase());else{b="...";for(d=0;d<a.length;++d)b+="."}return b};
 var ContentObject=function(a,b,c,d){this.name="string"===typeof a?new Name(a):a;this.signedInfo=b;this.content=c;this.signature=d;this.rawSignatureData=this.endContent=this.startSignedInfo=this.endSIG=this.startSIG=null};
@@ -131,10 +137,10 @@
 DataUtils.toString=function(a){for(var b="",c=0;c<a.length;c++)b+=String.fromCharCode(a[c]);return b};DataUtils.toNumbers=function(a){if("string"==typeof a){var b=new Uint8Array(Math.floor(a.length/2)),c=0;a.replace(/(..)/g,function(a){b[c++]=parseInt(a,16)});return b}};DataUtils.hexToRawString=function(a){if("string"==typeof a){var b="";a.replace(/(..)/g,function(a){b+=String.fromCharCode(parseInt(a,16))});return b}};
 DataUtils.toNumbersFromString=function(a){for(var b=new Uint8Array(a.length),c=0;c<a.length;c++)b[c]=a.charCodeAt(c);return b};DataUtils.stringToUtf8Array=function(a){return DataUtils.toNumbersFromString(str2rstr_utf8(a))};DataUtils.concatArrays=function(a){for(var b=0,c=0;c<a.length;++c)b+=a[c].length;for(var b=new Uint8Array(b),d=0,c=0;c<a.length;++c)b.set(a[c],d),d+=a[c].length;return b};
 DataUtils.decodeUtf8=function(a){for(var b="",c=0,d=0,e=0;c<a.length;)if(d=a.charCodeAt(c),128>d)b+=String.fromCharCode(d),c++;else if(191<d&&224>d)e=a.charCodeAt(c+1),b+=String.fromCharCode((d&31)<<6|e&63),c+=2;else var e=a.charCodeAt(c+1),f=a.charCodeAt(c+2),b=b+String.fromCharCode((d&15)<<12|(e&63)<<6|f&63),c=c+3;return b};DataUtils.arraysEqual=function(a,b){if(a.length!=b.length)return!1;for(var c=0;c<a.length;++c)if(a[c]!=b[c])return!1;return!0};
-DataUtils.bigEndianToUnsignedInt=function(a){for(var b=0,c=0;c<a.length;++c)b<<=8,b+=a[c];return b};DataUtils.nonNegativeIntToBigEndian=function(a){a=Math.round(a);if(0>=a)return new Uint8Array(0);for(var b=new Uint8Array(8),c=0;0!=a;)++c,b[8-c]=a&255,a>>=8;return b.subarray(8-c,8)};function encodeToHexInterest(a){return DataUtils.toHex(encodeToBinaryInterest(a))}function encodeToBinaryInterest(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}
-function encodeToHexContentObject(a){return DataUtils.toHex(encodeToBinaryContentObject(a))}function encodeToBinaryContentObject(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function encodeForwardingEntry(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function decodeHexFaceInstance(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX FACE INSTANCE  \n"+a);a=new FaceInstance;a.from_ccnb(decoder);return a}
-function decodeHexInterest(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX INTERST  \n"+a);a=new Interest;a.from_ccnb(decoder);return a}function decodeHexContentObject(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX CONTENT OBJECT \n"+a);co=new ContentObject;co.from_ccnb(decoder);return co}
-function decodeHexForwardingEntry(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX FORWARDING ENTRY \n"+a);forwardingEntry=new ForwardingEntry;forwardingEntry.from_ccnb(decoder);return forwardingEntry}
+DataUtils.bigEndianToUnsignedInt=function(a){for(var b=0,c=0;c<a.length;++c)b<<=8,b+=a[c];return b};DataUtils.nonNegativeIntToBigEndian=function(a){a=Math.round(a);if(0>=a)return new Uint8Array(0);for(var b=new Uint8Array(8),c=0;0!=a;)++c,b[8-c]=a&255,a>>=8;return b.subarray(8-c,8)};DataUtils.shuffle=function(a){for(var b=a.length-1;1<=b;--b){var c=Math.floor(Math.random()*(b+1)),d=a[b];a[b]=a[c];a[c]=d}};function encodeToHexInterest(a){return DataUtils.toHex(encodeToBinaryInterest(a))}
+function encodeToBinaryInterest(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function encodeToHexContentObject(a){return DataUtils.toHex(encodeToBinaryContentObject(a))}function encodeToBinaryContentObject(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}function encodeForwardingEntry(a){var b=new BinaryXMLEncoder;a.to_ccnb(b);return b.getReducedOstream()}
+function decodeHexFaceInstance(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX FACE INSTANCE  \n"+a);a=new FaceInstance;a.from_ccnb(decoder);return a}function decodeHexInterest(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODING HEX INTERST  \n"+a);a=new Interest;a.from_ccnb(decoder);return a}
+function decodeHexContentObject(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX CONTENT OBJECT \n"+a);co=new ContentObject;co.from_ccnb(decoder);return co}function decodeHexForwardingEntry(a){a=DataUtils.toNumbers(a);decoder=new BinaryXMLDecoder(a);3<LOG&&console.log("DECODED HEX FORWARDING ENTRY \n"+a);forwardingEntry=new ForwardingEntry;forwardingEntry.from_ccnb(decoder);return forwardingEntry}
 function contentObjectToHtml(a){var b="";if(-1==a)b+="NO CONTENT FOUND";else if(-2==a)b+="CONTENT NAME IS EMPTY";else{null!=a.name&&null!=a.name.components&&(b+="NAME: "+a.name.to_uri(),b+="<br /><br />");null!=a.content&&(b+="CONTENT(ASCII): "+DataUtils.toString(a.content),b+="<br />",b+="<br />");null!=a.content&&(b+="CONTENT(hex): "+DataUtils.toHex(a.content),b+="<br />",b+="<br />");null!=a.signature&&null!=a.signature.signature&&(b+="SIGNATURE(hex): "+DataUtils.toHex(a.signature.signature),b+=
 "<br />",b+="<br />");null!=a.signedInfo&&(null!=a.signedInfo.publisher&&null!=a.signedInfo.publisher.publisherPublicKeyDigest)&&(b+="Publisher Public Key Digest(hex): "+DataUtils.toHex(a.signedInfo.publisher.publisherPublicKeyDigest),b+="<br />",b+="<br />");if(null!=a.signedInfo&&null!=a.signedInfo.timestamp){var c=new Date;c.setTime(a.signedInfo.timestamp.msec);b+="TimeStamp: "+c;b+="<br />";b+="TimeStamp(number): "+a.signedInfo.timestamp.msec;b+="<br />"}null!=a.signedInfo&&null!=a.signedInfo.finalBlockID&&
 (b+="FinalBlockID: "+DataUtils.toHex(a.signedInfo.finalBlockID),b+="<br />");if(null!=a.signedInfo&&null!=a.signedInfo.locator&&null!=a.signedInfo.locator.certificate){var c=DataUtils.toString(a.signedInfo.locator.certificate),d=rstr2b64(c),e=DataUtils.toHex(a.signedInfo.locator.certificate).toLowerCase(),f=DataUtils.toString(a.signedInfo.locator.certificate),c=DataUtils.toHex(a.signature.signature).toLowerCase(),g=DataUtils.toString(a.rawSignatureData),b=b+("DER Certificate: "+d),b=b+"<br />",b=