blob: f88f178a3b51d1d2c50362ee0a81fb86f461ec41 [file] [log] [blame]
Wentao Shangc0311e52012-12-03 10:38:23 -08001/**
Jeff Thompson5b265a72012-11-12 01:13:08 -08002 * @author: Meki Cherkaoui, Jeff Thompson, Wentao Shang
Jeff Thompson745026e2012-10-13 12:49:20 -07003 * See COPYING for copyright and distribution information.
4 * This class represents the top-level object for communicating with an NDN host.
5 */
Meki Cherkaoui8f173612012-06-06 01:05:40 -07006
Jeff Thompson2b14c7e2013-07-29 15:09:56 -07007/**
8 * Set this to a higher number to dump more debugging log messages.
9 * @type Number
10 */
Jeff Thompson3c263812012-12-01 17:20:28 -080011var LOG = 0;
Jeff Thompson5b265a72012-11-12 01:13:08 -080012
Jeff Thompsone06b31e2012-09-30 17:19:19 -070013/**
Jeff Thompson2b14c7e2013-07-29 15:09:56 -070014 * Create a new NDN with the given settings.
15 * This throws an exception if NDN.supported is false.
16 * @constructor
17 * @param {Object} settings if not null, an associative array with the following defaults:
Jeff Thompson5b265a72012-11-12 01:13:08 -080018 * {
Jeff Thompsonac0ed602013-02-18 21:52:42 -080019 * getTransport: function() { return new WebSocketTransport(); },
Jeff Thompson2b14c7e2013-07-29 15:09:56 -070020 * getHostAndPort: transport.defaultGetHostAndPort, // a function, on each call it returns a new { host: host, port: port } or null if there are no more hosts.
Jeff Thompsond771b122013-01-26 19:04:41 -080021 * host: null, // If null, use getHostAndPort when connecting.
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080022 * port: 9696,
Jeff Thompsonac0ed602013-02-18 21:52:42 -080023 * onopen: function() { if (LOG > 3) console.log("NDN connection established."); },
24 * onclose: function() { if (LOG > 3) console.log("NDN connection closed."); },
25 * verify: true // If false, don't verify and call upcall with Closure.UPCALL_CONTENT_UNVERIFIED.
Jeff Thompson5b265a72012-11-12 01:13:08 -080026 * }
Jeff Thompsone06b31e2012-09-30 17:19:19 -070027 */
Jeff Thompson5b265a72012-11-12 01:13:08 -080028var NDN = function NDN(settings) {
Jeff Thompson2b14c7e2013-07-29 15:09:56 -070029 if (!NDN.supported)
30 throw new Error("The necessary JavaScript support is not available on this platform.");
Jeff Thompsone2f7b6d2013-02-24 20:37:27 -080031
Jeff Thompson2b14c7e2013-07-29 15:09:56 -070032 settings = (settings || {});
33 var getTransport = (settings.getTransport || function() { return new WebSocketTransport(); });
34 this.transport = getTransport();
35 this.getHostAndPort = (settings.getHostAndPort || this.transport.defaultGetHostAndPort);
Jeff Thompsond771b122013-01-26 19:04:41 -080036 this.host = (settings.host !== undefined ? settings.host : null);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080037 this.port = (settings.port || 9696);
Jeff Thompson2b14c7e2013-07-29 15:09:56 -070038 this.readyStatus = NDN.UNOPEN;
39 this.verify = (settings.verify !== undefined ? settings.verify : true);
40 // Event handler
41 this.onopen = (settings.onopen || function() { if (LOG > 3) console.log("NDN connection established."); });
42 this.onclose = (settings.onclose || function() { if (LOG > 3) console.log("NDN connection closed."); });
Jeff Thompson75771cb2013-01-20 23:27:38 -080043 this.ccndid = null;
Meki Cherkaoui8f173612012-06-06 01:05:40 -070044};
45
Wentao Shang0e291c82012-12-02 23:36:29 -080046NDN.UNOPEN = 0; // created but not opened yet
47NDN.OPENED = 1; // connection to ccnd opened
48NDN.CLOSED = 2; // connection to ccnd closed
Jeff Thompson5b265a72012-11-12 01:13:08 -080049
Jeff Thompson2b14c7e2013-07-29 15:09:56 -070050/**
Jeff Thompsone2f7b6d2013-02-24 20:37:27 -080051 * Return true if necessary JavaScript support is available, else log an error and return false.
52 */
53NDN.getSupported = function() {
54 try {
55 var dummy = new Uint8Array(1).subarray(0, 1);
56 } catch (ex) {
57 console.log("NDN not available: Uint8Array not supported. " + ex);
58 return false;
59 }
60
61 return true;
62};
63
64NDN.supported = NDN.getSupported();
65
Wentao Shangb42483a2013-01-03 15:32:32 -080066NDN.ccndIdFetcher = new Name('/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY');
Jeff Thompson5b265a72012-11-12 01:13:08 -080067
Wentao Shangb42483a2013-01-03 15:32:32 -080068NDN.prototype.createRoute = function(host, port) {
Meki Cherkaoui8f173612012-06-06 01:05:40 -070069 this.host=host;
70 this.port=port;
Wentao Shangb42483a2013-01-03 15:32:32 -080071};
Meki Cherkaoui8f173612012-06-06 01:05:40 -070072
Wentao Shang82854bd2012-12-27 14:14:41 -080073
74NDN.KeyStore = new Array();
75
Wentao Shangb5d0c3e2012-12-30 11:12:03 -080076var KeyStoreEntry = function KeyStoreEntry(name, rsa, time) {
Wentao Shang82854bd2012-12-27 14:14:41 -080077 this.keyName = name; // KeyName
Wentao Shang82854bd2012-12-27 14:14:41 -080078 this.rsaKey = rsa; // RSA key
Wentao Shangb5d0c3e2012-12-30 11:12:03 -080079 this.timeStamp = time; // Time Stamp
80};
81
82NDN.addKeyEntry = function(/* KeyStoreEntry */ keyEntry) {
83 var result = NDN.getKeyByName(keyEntry.keyName);
84 if (result == null)
85 NDN.KeyStore.push(keyEntry);
86 else
87 result = keyEntry;
Wentao Shang82854bd2012-12-27 14:14:41 -080088};
89
90NDN.getKeyByName = function(/* KeyName */ name) {
91 var result = null;
92
93 for (var i = 0; i < NDN.KeyStore.length; i++) {
Wentao Shangb42483a2013-01-03 15:32:32 -080094 if (NDN.KeyStore[i].keyName.contentName.match(name.contentName)) {
Wentao Shang82854bd2012-12-27 14:14:41 -080095 if (result == null ||
96 NDN.KeyStore[i].keyName.contentName.components.length > result.keyName.contentName.components.length)
97 result = NDN.KeyStore[i];
98 }
99 }
100
101 return result;
102};
103
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800104// For fetching data
105NDN.PITTable = new Array();
106
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700107/**
108 * @constructor
109 */
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800110var PITEntry = function PITEntry(interest, closure) {
111 this.interest = interest; // Interest
112 this.closure = closure; // Closure
Wentao Shangfcb16262013-01-20 14:42:46 -0800113 this.timerID = -1; // Timer ID
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800114};
115
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700116/**
Jeff Thompson202728a2013-02-10 22:20:08 -0800117 * Return the entry from NDN.PITTable where the name conforms to the interest selectors, and
118 * the interest name is the longest that matches name.
119 */
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800120NDN.getEntryForExpressedInterest = function(/*Name*/ name) {
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800121 var result = null;
122
123 for (var i = 0; i < NDN.PITTable.length; i++) {
124 if (NDN.PITTable[i].interest.matches_name(name)) {
125 if (result == null ||
126 NDN.PITTable[i].interest.name.components.length > result.interest.name.components.length)
127 result = NDN.PITTable[i];
128 }
129 }
130
131 return result;
132};
133
Jeff Thompson75771cb2013-01-20 23:27:38 -0800134// For publishing data
135NDN.CSTable = new Array();
136
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700137/**
138 * @constructor
139 */
Jeff Thompson75771cb2013-01-20 23:27:38 -0800140var CSEntry = function CSEntry(name, closure) {
141 this.name = name; // String
142 this.closure = closure; // Closure
143};
144
145function getEntryForRegisteredPrefix(name) {
146 for (var i = 0; i < NDN.CSTable.length; i++) {
Wentao Shang2a74ed82013-07-26 20:00:49 -0700147 if (NDN.CSTable[i].name.match(name))
Jeff Thompson75771cb2013-01-20 23:27:38 -0800148 return NDN.CSTable[i];
149 }
150 return null;
151}
152
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700153/**
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800154 * Return a function that selects a host at random from hostList and returns { host: host, port: port }.
155 * If no more hosts remain, return null.
156 */
157NDN.makeShuffledGetHostAndPort = function(hostList, port) {
158 // Make a copy.
159 hostList = hostList.slice(0, hostList.length);
160 DataUtils.shuffle(hostList);
161
162 return function() {
163 if (hostList.length == 0)
164 return null;
165
166 return { host: hostList.splice(0, 1)[0], port: port };
167 };
168};
169
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700170/**
171 * Encode name as an Interest and send the it to host:port, read the entire response and call
Jeff Thompson34419762012-10-15 22:24:12 -0700172 * closure.upcall(Closure.UPCALL_CONTENT (or Closure.UPCALL_CONTENT_UNVERIFIED),
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700173 * new UpcallInfo(this, interest, 0, contentObject)).
174 * @param {Name} name
175 * @param {Closure} closure
176 * @param {Interest} template if not null, use its attributes
Jeff Thompson34419762012-10-15 22:24:12 -0700177 */
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700178NDN.prototype.expressInterest = function (name, closure, template) {
Jeff Thompson5b265a72012-11-12 01:13:08 -0800179 var interest = new Interest(name);
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700180 if (template != null) {
Jeff Thompson4404ab52012-10-21 10:29:48 -0700181 interest.minSuffixComponents = template.minSuffixComponents;
182 interest.maxSuffixComponents = template.maxSuffixComponents;
183 interest.publisherPublicKeyDigest = template.publisherPublicKeyDigest;
184 interest.exclude = template.exclude;
185 interest.childSelector = template.childSelector;
186 interest.answerOriginKind = template.answerOriginKind;
187 interest.scope = template.scope;
188 interest.interestLifetime = template.interestLifetime;
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700189 }
190 else
191 interest.interestLifetime = 4000; // default interest timeout value in milliseconds.
Jeff Thompson34419762012-10-15 22:24:12 -0700192
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800193 if (this.host == null || this.port == null) {
194 if (this.getHostAndPort == null)
195 console.log('ERROR: host OR port NOT SET');
Jeff Thompsona5668d52013-01-26 16:23:27 -0800196 else {
197 var thisNDN = this;
198 this.connectAndExecute
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800199 (function() { thisNDN.reconnectAndExpressInterest(interest, closure); });
Jeff Thompsona5668d52013-01-26 16:23:27 -0800200 }
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800201 }
202 else
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800203 this.reconnectAndExpressInterest(interest, closure);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800204};
Jeff Thompson5b265a72012-11-12 01:13:08 -0800205
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700206/**
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800207 * If the host and port are different than the ones in this.transport, then call
208 * this.transport.connect to change the connection (or connect for the first time).
209 * Then call expressInterestHelper.
210 */
211NDN.prototype.reconnectAndExpressInterest = function(interest, closure) {
212 if (this.transport.connectedHost != this.host || this.transport.connectedPort != this.port) {
213 var thisNDN = this;
214 this.transport.connect(thisNDN, function() { thisNDN.expressInterestHelper(interest, closure); });
215 }
216 else
217 this.expressInterestHelper(interest, closure);
218};
219
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700220/**
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800221 * Do the work of reconnectAndExpressInterest once we know we are connected. Set the PITTable and call
Jeff Thompson537ce142013-01-26 20:02:48 -0800222 * this.transport.send to send the interest.
223 */
224NDN.prototype.expressInterestHelper = function(interest, closure) {
Jeff Thompsone8de5822013-02-17 21:18:49 -0800225 var binaryInterest = encodeToBinaryInterest(interest);
226 var thisNDN = this;
Jeff Thompson537ce142013-01-26 20:02:48 -0800227 //TODO: check local content store first
228 if (closure != null) {
229 var pitEntry = new PITEntry(interest, closure);
230 // TODO: This needs to be a single thread-safe transaction on a global object.
231 NDN.PITTable.push(pitEntry);
232 closure.pitEntry = pitEntry;
Jeff Thompson537ce142013-01-26 20:02:48 -0800233
Jeff Thompsone8de5822013-02-17 21:18:49 -0800234 // Set interest timer.
Jeff Thompsona53e65a2013-02-10 10:52:52 -0800235 var timeoutMilliseconds = (interest.interestLifetime || 4000);
Jeff Thompsone8de5822013-02-17 21:18:49 -0800236 var timeoutCallback = function() {
Jeff Thompson3d9ad142013-03-10 16:55:28 -0700237 if (LOG > 1) console.log("Interest time out: " + interest.name.to_uri());
Jeff Thompson537ce142013-01-26 20:02:48 -0800238
Jeff Thompsone8de5822013-02-17 21:18:49 -0800239 // Remove PIT entry from NDN.PITTable, even if we add it again later to re-express
240 // the interest because we don't want to match it in the mean time.
Jeff Thompson537ce142013-01-26 20:02:48 -0800241 // TODO: Make this a thread-safe operation on the global PITTable.
242 var index = NDN.PITTable.indexOf(pitEntry);
Jeff Thompson537ce142013-01-26 20:02:48 -0800243 if (index >= 0)
244 NDN.PITTable.splice(index, 1);
Jeff Thompson537ce142013-01-26 20:02:48 -0800245
246 // Raise closure callback
Jeff Thompsone8de5822013-02-17 21:18:49 -0800247 if (closure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,
248 new UpcallInfo(thisNDN, interest, 0, null)) == Closure.RESULT_REEXPRESS) {
Jeff Thompson3d9ad142013-03-10 16:55:28 -0700249 if (LOG > 1) console.log("Re-express interest: " + interest.name.to_uri());
Jeff Thompsone8de5822013-02-17 21:18:49 -0800250 pitEntry.timerID = setTimeout(timeoutCallback, timeoutMilliseconds);
251 NDN.PITTable.push(pitEntry);
252 thisNDN.transport.send(binaryInterest);
253 }
254 };
255 pitEntry.timerID = setTimeout(timeoutCallback, timeoutMilliseconds);
Jeff Thompson537ce142013-01-26 20:02:48 -0800256 }
257
Jeff Thompsone8de5822013-02-17 21:18:49 -0800258 this.transport.send(binaryInterest);
Jeff Thompson537ce142013-01-26 20:02:48 -0800259};
260
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700261/**
262 * Register name with the connected NDN hub and receive interests with closure.upcall.
263 * @param {Name} name
264 * @param {Closure} closure
265 * @param {number} flag
266 */
Jeff Thompson5b265a72012-11-12 01:13:08 -0800267NDN.prototype.registerPrefix = function(name, closure, flag) {
Jeff Thompsond771b122013-01-26 19:04:41 -0800268 var thisNDN = this;
269 var onConnected = function() {
270 if (thisNDN.ccndid == null) {
271 // Fetch ccndid first, then register.
272 var interest = new Interest(NDN.ccndIdFetcher);
273 interest.interestLifetime = 4000; // milliseconds
274 if (LOG>3) console.log('Expressing interest for ccndid from ccnd.');
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800275 thisNDN.reconnectAndExpressInterest
276 (interest, new NDN.FetchCcndidClosure(thisNDN, name, closure, flag));
Jeff Thompsond771b122013-01-26 19:04:41 -0800277 }
278 else
279 thisNDN.registerPrefixHelper(name, closure, flag);
280 };
Jeff Thompson75771cb2013-01-20 23:27:38 -0800281
Jeff Thompsond771b122013-01-26 19:04:41 -0800282 if (this.host == null || this.port == null) {
283 if (this.getHostAndPort == null)
284 console.log('ERROR: host OR port NOT SET');
285 else
286 this.connectAndExecute(onConnected);
287 }
288 else
289 onConnected();
290};
291
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700292/**
Jeff Thompsond771b122013-01-26 19:04:41 -0800293 * This is a closure to receive the ContentObject for NDN.ccndIdFetcher and call
294 * registerPrefixHelper(name, callerClosure, flag).
295 */
296NDN.FetchCcndidClosure = function FetchCcndidClosure(ndn, name, callerClosure, flag) {
297 // Inherit from Closure.
298 Closure.call(this);
299
300 this.ndn = ndn;
301 this.name = name;
302 this.callerClosure = callerClosure;
303 this.flag = flag;
304};
305
306NDN.FetchCcndidClosure.prototype.upcall = function(kind, upcallInfo) {
307 if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
308 console.log("Timeout while requesting the ccndid. Cannot registerPrefix for " +
309 this.name.to_uri() + " .");
310 return Closure.RESULT_OK;
311 }
312 if (!(kind == Closure.UPCALL_CONTENT ||
313 kind == Closure.UPCALL_CONTENT_UNVERIFIED))
314 // The upcall is not for us.
315 return Closure.RESULT_ERR;
316
317 var co = upcallInfo.contentObject;
318 if (!co.signedInfo || !co.signedInfo.publisher
319 || !co.signedInfo.publisher.publisherPublicKeyDigest)
320 console.log
321 ("ContentObject doesn't have a publisherPublicKeyDigest. Cannot set ccndid and registerPrefix for "
322 + this.name.to_uri() + " .");
323 else {
324 if (LOG>3) console.log('Got ccndid from ccnd.');
325 this.ndn.ccndid = co.signedInfo.publisher.publisherPublicKeyDigest;
326 if (LOG>3) console.log(this.ndn.ccndid);
327
328 this.ndn.registerPrefixHelper(this.name, this.callerClosure, this.flag);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800329 }
Jeff Thompsond771b122013-01-26 19:04:41 -0800330
331 return Closure.RESULT_OK;
332};
333
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700334/**
Jeff Thompson537ce142013-01-26 20:02:48 -0800335 * Do the work of registerPrefix once we know we are connected with a ccndid.
336 */
Jeff Thompsond771b122013-01-26 19:04:41 -0800337NDN.prototype.registerPrefixHelper = function(name, closure, flag) {
Jeff Thompson75771cb2013-01-20 23:27:38 -0800338 var fe = new ForwardingEntry('selfreg', name, null, null, 3, 2147483647);
339 var bytes = encodeForwardingEntry(fe);
340
341 var si = new SignedInfo();
342 si.setFields();
343
Jeff Thompson2c580442013-07-31 11:51:18 -0700344 var co = new ContentObject(new Name(), si, bytes);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800345 co.sign();
346 var coBinary = encodeToBinaryContentObject(co);
347
348 //var nodename = unescape('%00%88%E2%F4%9C%91%16%16%D6%21%8E%A0c%95%A5%A6r%11%E0%A0%82%89%A6%A9%85%AB%D6%E2%065%DB%AF');
349 var nodename = this.ccndid;
350 var interestName = new Name(['ccnx', nodename, 'selfreg', coBinary]);
351
352 var interest = new Interest(interestName);
353 interest.scope = 1;
354 if (LOG > 3) console.log('Send Interest registration packet.');
355
356 var csEntry = new CSEntry(name.getName(), closure);
357 NDN.CSTable.push(csEntry);
358
359 this.transport.send(encodeToBinaryInterest(interest));
Jeff Thompson75771cb2013-01-20 23:27:38 -0800360};
361
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700362/**
Jeff Thompson75771cb2013-01-20 23:27:38 -0800363 * This is called when an entire binary XML element is received, such as a ContentObject or Interest.
364 * Look up in the PITTable and call the closure callback.
365 */
366NDN.prototype.onReceivedElement = function(element) {
Jeff Thompsona46083c2013-01-20 23:55:21 -0800367 if (LOG>3) console.log('Complete element received. Length ' + element.length + '. Start decoding.');
Jeff Thompson75771cb2013-01-20 23:27:38 -0800368 var decoder = new BinaryXMLDecoder(element);
369 // Dispatch according to packet type
370 if (decoder.peekStartElement(CCNProtocolDTags.Interest)) { // Interest packet
371 if (LOG > 3) console.log('Interest packet received.');
372
373 var interest = new Interest();
374 interest.from_ccnb(decoder);
375 if (LOG > 3) console.log(interest);
376 var nameStr = escape(interest.name.getName());
377 if (LOG > 3) console.log(nameStr);
378
379 var entry = getEntryForRegisteredPrefix(nameStr);
380 if (entry != null) {
381 //console.log(entry);
382 var info = new UpcallInfo(this, interest, 0, null);
383 var ret = entry.closure.upcall(Closure.UPCALL_INTEREST, info);
384 if (ret == Closure.RESULT_INTEREST_CONSUMED && info.contentObject != null)
385 this.transport.send(encodeToBinaryContentObject(info.contentObject));
386 }
387 } else if (decoder.peekStartElement(CCNProtocolDTags.ContentObject)) { // Content packet
388 if (LOG > 3) console.log('ContentObject packet received.');
389
390 var co = new ContentObject();
391 co.from_ccnb(decoder);
392
Jeff Thompsond771b122013-01-26 19:04:41 -0800393 var pitEntry = NDN.getEntryForExpressedInterest(co.name);
394 if (pitEntry != null) {
Jeff Thompson0ee721e2013-02-18 17:46:55 -0800395 // Cancel interest timer
396 clearTimeout(pitEntry.timerID);
397
Jeff Thompsond771b122013-01-26 19:04:41 -0800398 // Remove PIT entry from NDN.PITTable
399 var index = NDN.PITTable.indexOf(pitEntry);
400 if (index >= 0)
401 NDN.PITTable.splice(index, 1);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800402
Jeff Thompsond771b122013-01-26 19:04:41 -0800403 var currentClosure = pitEntry.closure;
Jeff Thompson0ee721e2013-02-18 17:46:55 -0800404
Jeff Thompsond771b122013-01-26 19:04:41 -0800405 if (this.verify == false) {
406 // Pass content up without verifying the signature
Alexander Afanasyevaa7e9a02013-03-02 13:48:21 -0800407 currentClosure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED, new UpcallInfo(this, pitEntry.interest, 0, co));
Jeff Thompsond771b122013-01-26 19:04:41 -0800408 return;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800409 }
Wentao Shangd4607392013-01-24 23:08:49 -0800410
Jeff Thompsond771b122013-01-26 19:04:41 -0800411 // Key verification
412
413 // Recursive key fetching & verification closure
414 var KeyFetchClosure = function KeyFetchClosure(content, closure, key, sig, wit) {
415 this.contentObject = content; // unverified content object
416 this.closure = closure; // closure corresponding to the contentObject
417 this.keyName = key; // name of current key to be fetched
418 this.sigHex = sig; // hex signature string to be verified
419 this.witness = wit;
420
421 Closure.call(this);
422 };
423
424 var thisNDN = this;
425 KeyFetchClosure.prototype.upcall = function(kind, upcallInfo) {
426 if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
427 console.log("In KeyFetchClosure.upcall: interest time out.");
428 console.log(this.keyName.contentName.getName());
429 } else if (kind == Closure.UPCALL_CONTENT) {
430 //console.log("In KeyFetchClosure.upcall: signature verification passed");
431
432 var rsakey = decodeSubjectPublicKeyInfo(upcallInfo.contentObject.content);
433 var verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, this.witness, this.sigHex);
434
435 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
436 //console.log("raise encapsulated closure");
437 this.closure.upcall(flag, new UpcallInfo(thisNDN, null, 0, this.contentObject));
438
439 // Store key in cache
440 var keyEntry = new KeyStoreEntry(keylocator.keyName, rsakey, new Date().getTime());
441 NDN.addKeyEntry(keyEntry);
442 //console.log(NDN.KeyStore);
443 } else if (kind == Closure.UPCALL_CONTENT_BAD) {
444 console.log("In KeyFetchClosure.upcall: signature verification failed");
Wentao Shangd4607392013-01-24 23:08:49 -0800445 }
Jeff Thompsond771b122013-01-26 19:04:41 -0800446 };
Jeff Thompson75771cb2013-01-20 23:27:38 -0800447
Jeff Thompsond771b122013-01-26 19:04:41 -0800448 if (co.signedInfo && co.signedInfo.locator && co.signature) {
449 if (LOG > 3) console.log("Key verification...");
450 var sigHex = DataUtils.toHex(co.signature.signature).toLowerCase();
Jeff Thompson75771cb2013-01-20 23:27:38 -0800451
Jeff Thompsond771b122013-01-26 19:04:41 -0800452 var wit = null;
453 if (co.signature.Witness != null) {
454 wit = new Witness();
455 wit.decode(co.signature.Witness);
456 }
Jeff Thompson75771cb2013-01-20 23:27:38 -0800457
Jeff Thompsond771b122013-01-26 19:04:41 -0800458 var keylocator = co.signedInfo.locator;
459 if (keylocator.type == KeyLocatorType.KEYNAME) {
460 if (LOG > 3) console.log("KeyLocator contains KEYNAME");
461 //var keyname = keylocator.keyName.contentName.getName();
462 //console.log(nameStr);
463 //console.log(keyname);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800464
Jeff Thompsond771b122013-01-26 19:04:41 -0800465 if (keylocator.keyName.contentName.match(co.name)) {
466 if (LOG > 3) console.log("Content is key itself");
Jeff Thompson75771cb2013-01-20 23:27:38 -0800467
Jeff Thompsond771b122013-01-26 19:04:41 -0800468 var rsakey = decodeSubjectPublicKeyInfo(co.content);
469 var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
470 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
Alexander Afanasyevaa7e9a02013-03-02 13:48:21 -0800471
472 currentClosure.upcall(flag, new UpcallInfo(this, pitEntry.interest, 0, co));
473
Jeff Thompsond771b122013-01-26 19:04:41 -0800474 // SWT: We don't need to store key here since the same key will be
475 // stored again in the closure.
476 //var keyEntry = new KeyStoreEntry(keylocator.keyName, rsakey, new Date().getTime());
477 //NDN.addKeyEntry(keyEntry);
478 //console.log(NDN.KeyStore);
479 } else {
480 // Check local key store
481 var keyEntry = NDN.getKeyByName(keylocator.keyName);
482 if (keyEntry) {
483 // Key found, verify now
484 if (LOG > 3) console.log("Local key cache hit");
485 var rsakey = keyEntry.rsaKey;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800486 var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
487 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
Alexander Afanasyevaa7e9a02013-03-02 13:48:21 -0800488
Jeff Thompsond771b122013-01-26 19:04:41 -0800489 // Raise callback
Alexander Afanasyevaa7e9a02013-03-02 13:48:21 -0800490 currentClosure.upcall(flag, new UpcallInfo(this, pitEntry.interest, 0, co));
Jeff Thompsond771b122013-01-26 19:04:41 -0800491 } else {
492 // Not found, fetch now
493 if (LOG > 3) console.log("Fetch key according to keylocator");
494 var nextClosure = new KeyFetchClosure(co, currentClosure, keylocator.keyName, sigHex, wit);
495 this.expressInterest(keylocator.keyName.contentName.getPrefix(4), nextClosure);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800496 }
Jeff Thompson75771cb2013-01-20 23:27:38 -0800497 }
Jeff Thompsond771b122013-01-26 19:04:41 -0800498 } else if (keylocator.type == KeyLocatorType.KEY) {
499 if (LOG > 3) console.log("Keylocator contains KEY");
500
501 var rsakey = decodeSubjectPublicKeyInfo(co.signedInfo.locator.publicKey);
502 var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
503
504 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
505 // Raise callback
Alexander Afanasyevaa7e9a02013-03-02 13:48:21 -0800506 currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(this, pitEntry.interest, 0, co));
507
Jeff Thompsond771b122013-01-26 19:04:41 -0800508 // Since KeyLocator does not contain key name for this key,
509 // we have no way to store it as a key entry in KeyStore.
510 } else {
511 var cert = keylocator.certificate;
512 console.log("KeyLocator contains CERT");
513 console.log(cert);
514
515 // TODO: verify certificate
Jeff Thompson75771cb2013-01-20 23:27:38 -0800516 }
517 }
518 }
519 } else
520 console.log('Incoming packet is not Interest or ContentObject. Discard now.');
Wentao Shangb42483a2013-01-03 15:32:32 -0800521};
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800522
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700523/**
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800524 * Assume this.getHostAndPort is not null. This is called when this.host is null or its host
Jeff Thompsona5668d52013-01-26 16:23:27 -0800525 * is not alive. Get a host and port, connect, then execute onConnected().
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800526 */
Jeff Thompsona5668d52013-01-26 16:23:27 -0800527NDN.prototype.connectAndExecute = function(onConnected) {
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800528 var hostAndPort = this.getHostAndPort();
529 if (hostAndPort == null) {
530 console.log('ERROR: No more hosts from getHostAndPort');
531 this.host = null;
532 return;
533 }
534
535 if (hostAndPort.host == this.host && hostAndPort.port == this.port) {
536 console.log('ERROR: The host returned by getHostAndPort is not alive: ' +
537 this.host + ":" + this.port);
538 return;
539 }
540
541 this.host = hostAndPort.host;
542 this.port = hostAndPort.port;
Jeff Thompson3d9ad142013-03-10 16:55:28 -0700543 if (LOG>0) console.log("connectAndExecute: trying host from getHostAndPort: " + this.host);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800544
Jeff Thompson75771cb2013-01-20 23:27:38 -0800545 // Fetch any content.
546 var interest = new Interest(new Name("/"));
Jeff Thompson42806a12012-12-29 18:19:39 -0800547 interest.interestLifetime = 4000; // milliseconds
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800548
549 var thisNDN = this;
550 var timerID = setTimeout(function() {
Jeff Thompson3d9ad142013-03-10 16:55:28 -0700551 if (LOG>0) console.log("connectAndExecute: timeout waiting for host " + thisNDN.host);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800552 // Try again.
Jeff Thompsona5668d52013-01-26 16:23:27 -0800553 thisNDN.connectAndExecute(onConnected);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800554 }, 3000);
555
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800556 this.reconnectAndExpressInterest
557 (interest, new NDN.ConnectClosure(this, onConnected, timerID));
Wentao Shangb42483a2013-01-03 15:32:32 -0800558};
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800559
Jeff Thompsona5668d52013-01-26 16:23:27 -0800560NDN.ConnectClosure = function ConnectClosure(ndn, onConnected, timerID) {
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800561 // Inherit from Closure.
562 Closure.call(this);
563
564 this.ndn = ndn;
Jeff Thompsona5668d52013-01-26 16:23:27 -0800565 this.onConnected = onConnected;
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800566 this.timerID = timerID;
567};
568
569NDN.ConnectClosure.prototype.upcall = function(kind, upcallInfo) {
570 if (!(kind == Closure.UPCALL_CONTENT ||
Jeff Thompson75771cb2013-01-20 23:27:38 -0800571 kind == Closure.UPCALL_CONTENT_UNVERIFIED))
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800572 // The upcall is not for us.
573 return Closure.RESULT_ERR;
574
Jeff Thompsona5668d52013-01-26 16:23:27 -0800575 // The host is alive, so cancel the timeout and continue with onConnected().
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800576 clearTimeout(this.timerID);
Jeff Thompsond771b122013-01-26 19:04:41 -0800577
578 // Call NDN.onopen after success
579 this.ndn.readyStatus = NDN.OPENED;
580 this.ndn.onopen();
581
Jeff Thompson3d9ad142013-03-10 16:55:28 -0700582 if (LOG>0) console.log("connectAndExecute: connected to host " + this.ndn.host);
Jeff Thompsona5668d52013-01-26 16:23:27 -0800583 this.onConnected();
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800584
585 return Closure.RESULT_OK;
586};
587
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700588/**
Jeff Thompson75771cb2013-01-20 23:27:38 -0800589 * A BinaryXmlElementReader lets you call onReceivedData multiple times which uses a
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700590 * BinaryXMLStructureDecoder to detect the end of a binary XML element and calls
591 * elementListener.onReceivedElement(element) with the element.
Jeff Thompson75771cb2013-01-20 23:27:38 -0800592 * This handles the case where a single call to onReceivedData may contain multiple elements.
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700593 * @constructor
594 * @param {{onReceivedElement:function}} elementListener
Jeff Thompson75771cb2013-01-20 23:27:38 -0800595 */
596var BinaryXmlElementReader = function BinaryXmlElementReader(elementListener) {
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700597 this.elementListener = elementListener;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800598 this.dataParts = [];
Jeff Thompson2b14c7e2013-07-29 15:09:56 -0700599 this.structureDecoder = new BinaryXMLStructureDecoder();
Jeff Thompson75771cb2013-01-20 23:27:38 -0800600};
601
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700602BinaryXmlElementReader.prototype.onReceivedData = function(/* Uint8Array */ data) {
Jeff Thompson75771cb2013-01-20 23:27:38 -0800603 // Process multiple objects in the data.
604 while(true) {
605 // Scan the input to check if a whole ccnb object has been read.
606 this.structureDecoder.seek(0);
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700607 if (this.structureDecoder.findElementEnd(data)) {
Jeff Thompson75771cb2013-01-20 23:27:38 -0800608 // Got the remainder of an object. Report to the caller.
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700609 this.dataParts.push(data.subarray(0, this.structureDecoder.offset));
Jeff Thompson0790af82013-01-26 19:54:27 -0800610 var element = DataUtils.concatArrays(this.dataParts);
611 this.dataParts = [];
612 try {
613 this.elementListener.onReceivedElement(element);
614 } catch (ex) {
615 console.log("BinaryXmlElementReader: ignoring exception from onReceivedElement: " + ex);
616 }
Jeff Thompson75771cb2013-01-20 23:27:38 -0800617
618 // Need to read a new object.
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700619 data = data.subarray(this.structureDecoder.offset, data.length);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800620 this.structureDecoder = new BinaryXMLStructureDecoder();
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700621 if (data.length == 0)
Jeff Thompson75771cb2013-01-20 23:27:38 -0800622 // No more data in the packet.
623 return;
624
625 // else loop back to decode.
626 }
627 else {
628 // Save for a later call to concatArrays so that we only copy data once.
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700629 this.dataParts.push(data);
630 if (LOG>3) console.log('Incomplete packet received. Length ' + data.length + '. Wait for more input.');
631 return;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800632 }
633 }
Jeff Thompson27fbfdc2013-07-15 14:36:54 -0700634};